~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to account_override/account_move_line.py

  • Committer: Olivier DOSSMANN
  • Date: 2013-05-31 14:22:09 UTC
  • mto: This revision was merged to the branch mainline in revision 1687.
  • Revision ID: od@tempo-consulting.fr-20130531142209-sbcwvzuema11guzz
UF-1991 [FIX] Problem with wizard on "msg" field. Change it to "name".

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#-*- encoding:utf-8 -*-
 
3
##############################################################################
 
4
#
 
5
#    OpenERP, Open Source Management Solution
 
6
#    Copyright (C) 2011 TeMPO Consulting, MSF. All Rights Reserved
 
7
#    Developer: Olivier DOSSMANN
 
8
#
 
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.
 
13
#
 
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.
 
18
#
 
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/>.
 
21
#
 
22
##############################################################################
 
23
 
 
24
from osv import osv
 
25
from osv import fields
 
26
import re
 
27
import decimal_precision as dp
 
28
from time import strftime
 
29
import logging
 
30
from tools.translate import _
 
31
from time import strftime
 
32
 
 
33
class account_move_line(osv.osv):
 
34
    _inherit = 'account.move.line'
 
35
    _name = 'account.move.line'
 
36
 
 
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)
 
40
        if not ids:
 
41
            return []
 
42
        result = []
 
43
        for line in self.browse(cr, uid, ids, context=context):
 
44
            result.append((line.id, line.move_id.name))
 
45
        return result
 
46
 
 
47
    def join_without_redundancy(self, text='', string=''):
 
48
        """
 
49
        Add string @ begining of text like that:
 
50
            mystring1 - mysupertext
 
51
        
 
52
        If mystring1 already exist, increment 1:
 
53
            mystring1 - mysupertext
 
54
        give:
 
55
            mystring2 - mysupertext
 
56
 
 
57
        NB: for 'REV' string, do nothing about incrementation
 
58
        """
 
59
        result = ''.join([string, '1 - ', text])
 
60
        if string == 'REV':
 
61
            result = ''.join([string, ' - ', text])
 
62
        if text == '' or string == '':
 
63
            return result
 
64
        pattern = re.compile('\%s([0-9]+) - ' % string)
 
65
        m = re.match(pattern, text)
 
66
        if m and m.groups():
 
67
            number = m.groups() and m.groups()[0]
 
68
            # Add a check on number due to UF-1396
 
69
            if not isinstance(number, int):
 
70
                try:
 
71
                    nn = int(number)
 
72
                except ValueError:
 
73
                    nn = 0
 
74
            replacement = string + str(nn + 1) + ' - '
 
75
            if string == 'REV':
 
76
                replacement = string + ' - '
 
77
            result = re.sub(pattern, replacement, text, 1)
 
78
        return result
 
79
 
 
80
    def _get_move_lines(self, cr, uid, ids, context=None):
 
81
        """
 
82
        Return default behaviour
 
83
        """
 
84
        return super(account_move_line, self)._get_move_lines(cr, uid, ids, context=context)
 
85
 
 
86
    def _get_reference(self, cr, uid, ids, field_names, args, context=None):
 
87
        """
 
88
        Give reference field content from account_move_line first. Then search move_id.reference field, otherwise display ''.
 
89
        """
 
90
        res = {}
 
91
        for line in self.browse(cr, uid, ids):
 
92
            res[line.id] = ''
 
93
            if line.reference:
 
94
                res[line.id] = line.reference
 
95
                continue
 
96
            elif line.move_id and line.move_id.ref:
 
97
                res[line.id] = line.move_id.ref
 
98
                continue
 
99
        return res
 
100
 
 
101
    def _set_fake_reference(self, cr, uid, id, name=None, value=None, fnct_inv_arg=None, context=None):
 
102
        """
 
103
        Just used to not break default OpenERP behaviour
 
104
        """
 
105
        if name and value:
 
106
            sql = "UPDATE "+ self._table + " SET " + name + " = %s WHERE id = %s"
 
107
            cr.execute(sql, (value, id))
 
108
        return True
 
109
 
 
110
    def _search_reference(self, cr, uid, obj, name, args, context):
 
111
        """
 
112
        Account MCDB (Selector) seems to be the only one that search on this field.
 
113
        It use 'ilike' operator
 
114
        """
 
115
        if not context:
 
116
            context = {}
 
117
        if not args:
 
118
            return []
 
119
        if args[0][2]:
 
120
            return [('move_id.reference', '=', args[0][2])]
 
121
        return []
 
122
 
 
123
    def _journal_type_get(self, cr, uid, context=None):
 
124
        """
 
125
        Get journal types
 
126
        """
 
127
        return self.pool.get('account.journal').get_journal_type(cr, uid, context)
 
128
 
 
129
    def _get_reconcile_txt(self, cr, uid, ids, field_names, args, context=None):
 
130
        """
 
131
        Get total/partial reconcile name
 
132
        """
 
133
        res = {}
 
134
        for aml in self.browse(cr, uid, ids):
 
135
            res[aml.id] = ''
 
136
            r_id = None
 
137
            if aml.reconcile_id:
 
138
                r_id = aml.reconcile_id.id
 
139
            elif aml.reconcile_partial_id:
 
140
                r_id = aml.reconcile_partial_id.id
 
141
            if r_id:
 
142
                d = self.pool.get('account.move.reconcile').name_get(cr, uid, [r_id])
 
143
                name = ''
 
144
                if d and d[0] and d[0][1]:
 
145
                    name = d[0][1]
 
146
                res[aml.id] = name
 
147
        return res
 
148
 
 
149
    def _get_move_lines_for_reconcile(self, cr, uid, ids, context=None):
 
150
        res = []
 
151
        for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
 
152
            for t in r.line_id:
 
153
                res.append(t.id)
 
154
            for p in r.line_partial_ids:
 
155
                res.append(p.id)
 
156
        return res
 
157
 
 
158
    _columns = {
 
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,
 
172
                store = {
 
173
                    'account.move': (_get_move_lines, ['date'], 20)
 
174
                }, readonly=True),
 
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",
 
187
            store = {
 
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),
 
190
            }
 
191
        ),
 
192
    }
 
193
 
 
194
    _defaults = {
 
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,
 
201
    }
 
202
 
 
203
    _order = 'move_id DESC'
 
204
 
 
205
    def _accounting_balance(self, cr, uid, ids, context=None):
 
206
        """
 
207
        Get the accounting balance of given lines
 
208
        """
 
209
        # Some verifications
 
210
        if not context:
 
211
            context = {}
 
212
        if isinstance(ids, (int, long)):
 
213
            ids = [ids]
 
214
        if not ids:
 
215
            return 0.0
 
216
        # Create an sql query
 
217
        sql =  """
 
218
            SELECT SUM(debit - credit)
 
219
            FROM account_move_line
 
220
            WHERE id in %s
 
221
        """
 
222
        cr.execute(sql, (tuple(ids),))
 
223
        res = cr.fetchall()
 
224
        if isinstance(ids, list):
 
225
            res = res[0]
 
226
        return res
 
227
 
 
228
    def _check_document_date(self, cr, uid, ids):
 
229
        """
 
230
        Check that document's date is done BEFORE posting date
 
231
        """
 
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.'))
 
235
        return True
 
236
 
 
237
    def _check_date_validity(self, cr, uid, ids):
 
238
        """
 
239
        Check that date is contained between period ' starting date and ending's date
 
240
        """
 
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 ''))
 
244
        return True
 
245
 
 
246
    def create(self, cr, uid, vals, context=None, check=True):
 
247
        """
 
248
        Filled in 'document_date' if we come from tests
 
249
        """
 
250
        if not context:
 
251
            context = {}
 
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})
 
261
            if m and m.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)
 
265
 
 
266
    def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
 
267
        """
 
268
        Check document_date and date validity
 
269
        """
 
270
        if not context:
 
271
            context = {}
 
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})
 
283
                if m and m.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)
 
288
        return res
 
289
 
 
290
    def button_duplicate(self, cr, uid, ids, context=None):
 
291
        """
 
292
        Copy given lines for manual unposted entries
 
293
        """
 
294
        if not context:
 
295
            context = {}
 
296
        if isinstance(ids, (int, long)):
 
297
            ids = [ids]
 
298
        ml_copied_ids = []
 
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)
 
303
        return ml_copied_ids
 
304
 
 
305
    def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
 
306
        """
 
307
        In search view permit to search regarding Entry Sequence from journal entry (move_id.name field).
 
308
        This comes from UF-1719.
 
309
        """
 
310
        if args is None:
 
311
            args = []
 
312
        if context is None:
 
313
            context = {}
 
314
        ids = []
 
315
        if name:
 
316
            ids = self.search(cr, user, ['|', ('name', 'ilike', name), ('move_id.name', 'ilike', name)]+ args, limit=limit)
 
317
        if not ids:
 
318
            ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
 
319
        return self.name_get(cr, user, ids, context=context)
 
320
 
 
321
account_move_line()
 
322
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: