~camptocamp/banking-addons/bank-statement-reconcile-7.0-add-cancel-line-lep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# -*- coding: utf-8 -*-
##############################################################################
#
#    Copyright 2012-2013 Camptocamp SA (Guewen Baconnier)
#    Copyright (C) 2010   Sébastien Beau
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from openerp.osv import fields, osv, orm
from openerp.tools.translate import _
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT


class easy_reconcile_options(orm.AbstractModel):
    """Options of a reconciliation profile

    Columns shared by the configuration of methods
    and by the reconciliation wizards.
    This allows decoupling of the methods and the
    wizards and allows to launch the wizards alone
    """

    _name = 'easy.reconcile.options'

    def _get_rec_base_date(self, cr, uid, context=None):
        return [('end_period_last_credit', 'End of period of most recent credit'),
                ('newest', 'Most recent move line'),
                ('actual', 'Today'),
                ('end_period', 'End of period of most recent move line'),
                ('newest_credit', 'Date of most recent credit'),
                ('newest_debit', 'Date of most recent debit')]

    _columns = {
            'write_off': fields.float('Write off allowed'),
            'account_lost_id': fields.many2one(
                'account.account', 'Account Lost'),
            'account_profit_id': fields.many2one(
                'account.account', 'Account Profit'),
            'journal_id': fields.many2one(
                'account.journal', 'Journal'),
            'date_base_on': fields.selection(
                _get_rec_base_date,
                required=True,
                string='Date of reconciliation'),
            'filter': fields.char('Filter', size=128),
    }

    _defaults = {
        'write_off': 0.,
        'date_base_on': 'end_period_last_credit',
    }


class account_easy_reconcile_method(orm.Model):

    _name = 'account.easy.reconcile.method'
    _description = 'reconcile method for account_easy_reconcile'

    _inherit = 'easy.reconcile.options'

    _order = 'sequence'

    def _get_all_rec_method(self, cr, uid, context=None):
        return [
            ('easy.reconcile.simple.name', 'Simple. Amount and Name'),
            ('easy.reconcile.simple.partner', 'Simple. Amount and Partner'),
            ('easy.reconcile.simple.reference', 'Simple. Amount and Reference'),
            ]

    def _get_rec_method(self, cr, uid, context=None):
        return self._get_all_rec_method(cr, uid, context=None)

    _columns = {
            'name': fields.selection(
                _get_rec_method, 'Type', required=True),
            'sequence': fields.integer(
                'Sequence',
                required=True,
                help="The sequence field is used to order "
                     "the reconcile method"),
            'task_id': fields.many2one(
                'account.easy.reconcile',
                string='Task',
                required=True,
                ondelete='cascade'),
            'company_id': fields.related('task_id','company_id',
                                         relation='res.company',
                                         type='many2one',
                                         string='Company',
                                         store=True,
                                         readonly=True),
    }

    _defaults = {
        'sequence': 1,
    }

    def init(self, cr):
        """ Migration stuff

        Name is not anymore methods names but the name
        of the model which does the reconciliation
        """
        cr.execute("""
        UPDATE account_easy_reconcile_method
        SET name = 'easy.reconcile.simple.partner'
        WHERE name = 'action_rec_auto_partner'
        """)
        cr.execute("""
        UPDATE account_easy_reconcile_method
        SET name = 'easy.reconcile.simple.name'
        WHERE name = 'action_rec_auto_name'
        """)


class account_easy_reconcile(orm.Model):

    _name = 'account.easy.reconcile'
    _description = 'account easy reconcile'

    def _get_total_unrec(self, cr, uid, ids, name, arg, context=None):
        obj_move_line = self.pool.get('account.move.line')
        res = {}
        for task in self.browse(cr, uid, ids, context=context):
            res[task.id] = len(obj_move_line.search(
                cr, uid,
                [('account_id', '=', task.account.id),
                 ('reconcile_id', '=', False),
                 ('reconcile_partial_id', '=', False)],
                context=context))
        return res

    def _get_partial_rec(self, cr, uid, ids, name, arg, context=None):
        obj_move_line = self.pool.get('account.move.line')
        res = {}
        for task in self.browse(cr, uid, ids, context=context):
            res[task.id] = len(obj_move_line.search(
                cr, uid,
                [('account_id', '=', task.account.id),
                 ('reconcile_id', '=', False),
                 ('reconcile_partial_id', '!=', False)],
                context=context))
        return res

    def _last_history(self, cr, uid, ids, name, args, context=None):
        result = {}
        for history in self.browse(cr, uid, ids, context=context):
            result[history.id] = False
            if history.history_ids:
                # history is sorted by date desc
                result[history.id] = history.history_ids[0].id
        return result

    _columns = {
        'name': fields.char('Name', required=True),
        'account': fields.many2one(
            'account.account', 'Account', required=True),
        'reconcile_method': fields.one2many(
            'account.easy.reconcile.method', 'task_id', 'Method'),
        'unreconciled_count': fields.function(
            _get_total_unrec, type='integer', string='Unreconciled Items'),
        'reconciled_partial_count': fields.function(
            _get_partial_rec,
            type='integer',
            string='Partially Reconciled Items'),
        'history_ids': fields.one2many(
            'easy.reconcile.history',
            'easy_reconcile_id',
            string='History',
            readonly=True),
        'last_history':
            fields.function(
                _last_history,
                string='Last History',
                type='many2one',
                relation='easy.reconcile.history',
                readonly=True),
        'company_id': fields.many2one('res.company', 'Company'),
    }

    def _prepare_run_transient(self, cr, uid, rec_method, context=None):
        return {'account_id': rec_method.task_id.account.id,
                'write_off': rec_method.write_off,
                'account_lost_id': (rec_method.account_lost_id and
                                    rec_method.account_lost_id.id),
                'account_profit_id': (rec_method.account_profit_id and
                                      rec_method.account_profit_id.id),
                'journal_id': (rec_method.journal_id and
                               rec_method.journal_id.id),
                'date_base_on': rec_method.date_base_on,
                'filter': rec_method.filter}

    def run_reconcile(self, cr, uid, ids, context=None):
        def find_reconcile_ids(fieldname, move_line_ids):
            if not move_line_ids:
                return []
            sql = ("SELECT DISTINCT " + fieldname +
                   " FROM account_move_line "
                   " WHERE id in %s "
                   " AND " + fieldname + " IS NOT NULL")
            cr.execute(sql, (tuple(move_line_ids),))
            res = cr.fetchall()
            return [row[0] for row in res]

        for rec in self.browse(cr, uid, ids, context=context):
            all_ml_rec_ids = []
            all_ml_partial_ids = []

            for method in rec.reconcile_method:
                rec_model = self.pool.get(method.name)
                auto_rec_id = rec_model.create(
                    cr, uid,
                    self._prepare_run_transient(
                        cr, uid, method, context=context),
                    context=context)

                ml_rec_ids, ml_partial_ids = rec_model.automatic_reconcile(
                    cr, uid, auto_rec_id, context=context)

                all_ml_rec_ids += ml_rec_ids
                all_ml_partial_ids += ml_partial_ids

            reconcile_ids = find_reconcile_ids(
                    'reconcile_id', all_ml_rec_ids)
            partial_ids = find_reconcile_ids(
                    'reconcile_partial_id', all_ml_partial_ids)

            self.pool.get('easy.reconcile.history').create(
                cr,
                uid,
                {'easy_reconcile_id': rec.id,
                 'date': fields.datetime.now(),
                 'reconcile_ids': [(4, rid) for rid in reconcile_ids],
                 'reconcile_partial_ids': [(4, rid) for rid in partial_ids]},
                context=context)
        return True

    def _no_history(self, cr, uid, rec, context=None):
        """ Raise an `osv.except_osv` error, supposed to
        be called when there is no history on the reconciliation
        task.
        """
        raise osv.except_osv(
                _('Error'),
                _('There is no history of reconciled '
                  'items on the task: %s.') % rec.name)

    def last_history_reconcile(self, cr, uid, rec_id, context=None):
        """ Get the last history record for this reconciliation profile
        and return the action which opens move lines reconciled
        """
        if isinstance(rec_id, (tuple, list)):
            assert len(rec_id) == 1, \
                    "Only 1 id expected"
            rec_id = rec_id[0]
        rec = self.browse(cr, uid, rec_id, context=context)
        if not rec.last_history:
            self._no_history(cr, uid, rec, context=context)
        return rec.last_history.open_reconcile()

    def last_history_partial(self, cr, uid, rec_id, context=None):
        """ Get the last history record for this reconciliation profile
        and return the action which opens move lines reconciled
        """
        if isinstance(rec_id, (tuple, list)):
            assert len(rec_id) == 1, \
                    "Only 1 id expected"
            rec_id = rec_id[0]
        rec = self.browse(cr, uid, rec_id, context=context)
        if not rec.last_history:
            self._no_history(cr, uid, rec, context=context)
        return rec.last_history.open_partial()