~camptocamp/account-financial-tools/add-credit-control-legal-claim-nbi

« back to all changes in this revision

Viewing changes to account_credit_control/account.py

  • Committer: Guewen Baconnier @ Camptocamp
  • Date: 2012-10-22 11:06:14 UTC
  • Revision ID: guewen.baconnier@camptocamp.com-20121022110614-6dm0mu819ume9gl3
[IMP] renamed module to account_credit_control, wordings, removed scenarios (moved to lp:oerpscenario
(lp:c2c-addons/6.1  rev 89.1.1)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Author: Nicolas Bessi
 
5
#    Copyright 2012 Camptocamp SA
 
6
#
 
7
#    This program is free software: you can redistribute it and/or modify
 
8
#    it under the terms of the GNU Affero General Public License as
 
9
#    published by the Free Software Foundation, either version 3 of the
 
10
#    License, or (at your option) any later version.
 
11
#
 
12
#    This program is distributed in the hope that it will be useful,
 
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    GNU Affero General Public License for more details.
 
16
#
 
17
#    You should have received a copy of the GNU Affero General Public License
 
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
#
 
20
##############################################################################
 
21
from datetime import datetime
 
22
import operator
 
23
from openerp.osv.orm import Model, fields
 
24
from openerp.tools.translate import _
 
25
from openerp.addons.account_credit_control import run
 
26
 
 
27
class AccountAccount(Model):
 
28
    """Add a link to a credit control policy on account account"""
 
29
 
 
30
 
 
31
    def _check_account_type_compatibility(self, cursor, uid, acc_ids, context=None):
 
32
        """We check that account is of type reconcile"""
 
33
        if not isinstance(acc_ids, list):
 
34
            acc_ids = [acc_ids]
 
35
        for acc in self.browse(cursor, uid, acc_ids, context):
 
36
            if acc.credit_policy_id and not acc.reconcile:
 
37
                return False
 
38
        return True
 
39
 
 
40
    _inherit = "account.account"
 
41
    _description = """Add a link to a credit policy"""
 
42
    _columns = {'credit_policy_id': fields.many2one('credit.control.policy',
 
43
                                                     'Credit control policy',
 
44
                                                     help=("Define global credit policy"
 
45
                                                           "order is account partner invoice")),
 
46
 
 
47
                'credit_control_line_ids': fields.one2many('credit.control.line',
 
48
                                                              'account_id',
 
49
                                                              string='Credit Lines',
 
50
                                                              readonly=True)}
 
51
 
 
52
    _constraints = [(_check_account_type_compatibility,
 
53
                     _('You can not set a credit policy on a non reconciliable account'),
 
54
                     ['credit_policy_id'])]
 
55
 
 
56
class AccountInvoice(Model):
 
57
    """Add a link to a credit control policy on account account"""
 
58
 
 
59
    _inherit = "account.invoice"
 
60
    _description = """Add a link to a credit policy"""
 
61
    _columns = {'credit_policy_id': fields.many2one('credit.control.policy',
 
62
                                                     'Credit control policy',
 
63
                                                     help=("Define global credit policy"
 
64
                                                           "order is account partner invoice")),
 
65
 
 
66
                'credit_control_line_ids': fields.one2many('credit.control.line',
 
67
                                                              'account_id',
 
68
                                                              string='Credit Lines',
 
69
                                                              readonly=True)}
 
70
 
 
71
    def action_move_create(self, cursor, uid, ids, context=None):
 
72
        """We ensure writing of invoice id in move line because
 
73
           Trigger field may not work without account_voucher addon"""
 
74
        res = super(AccountInvoice, self).action_move_create(cursor, uid, ids, context=context)
 
75
        for inv in self.browse(cursor, uid, ids, context=context):
 
76
            if inv.move_id:
 
77
                for line in inv.move_id.line_id:
 
78
                    line.write({'invoice_id': inv.id})
 
79
        return res
 
80
 
 
81
 
 
82
class AccountMoveLine(Model):
 
83
    """Add a function that compute the residual amount using a follow up date
 
84
       Add relation between move line and invoicex"""
 
85
 
 
86
    _inherit = "account.move.line"
 
87
    # Store fields has strange behavior with voucher module we had to overwrite invoice
 
88
 
 
89
 
 
90
    # def _invoice_id(self, cursor, user, ids, name, arg, context=None):
 
91
    #     #Code taken from OpenERP account addon
 
92
    #     invoice_obj = self.pool.get('account.invoice')
 
93
    #     res = {}
 
94
    #     for line_id in ids:
 
95
    #         res[line_id] = False
 
96
    #     cursor.execute('SELECT l.id, i.id ' \
 
97
    #                     'FROM account_move_line l, account_invoice i ' \
 
98
    #                     'WHERE l.move_id = i.move_id ' \
 
99
    #                     'AND l.id IN %s',
 
100
    #                     (tuple(ids),))
 
101
    #     invoice_ids = []
 
102
    #     for line_id, invoice_id in cursor.fetchall():
 
103
    #         res[line_id] = invoice_id
 
104
    #         invoice_ids.append(invoice_id)
 
105
    #     invoice_names = {False: ''}
 
106
    #     for invoice_id, name in invoice_obj.name_get(cursor, user, invoice_ids, context=context):
 
107
    #         invoice_names[invoice_id] = name
 
108
    #     for line_id in res.keys():
 
109
    #         invoice_id = res[line_id]
 
110
    #         res[line_id] = (invoice_id, invoice_names[invoice_id])
 
111
    #     return res
 
112
 
 
113
    # def _get_invoice(self, cursor, uid, ids, context=None):
 
114
    #     result = set()
 
115
    #     for line in self.pool.get('account.invoice').browse(cursor, uid, ids, context=context):
 
116
    #         if line.move_id:
 
117
    #             ids = [x.id for x in line.move_id.line_id or []]
 
118
    #     return list(result)
 
119
 
 
120
    # _columns = {'invoice_id': fields.function(_invoice_id, string='Invoice',
 
121
    #             type='many2one', relation='account.invoice',
 
122
    #             store={'account.invoice': (_get_invoice, ['move_id'], 20)})}
 
123
 
 
124
    _columns = {'invoice_id': fields.many2one('account.invoice', 'Invoice')}
 
125
 
 
126
    def _get_payment_and_credit_lines(self, moveline_array, lookup_date):
 
127
        credit_lines = []
 
128
        payment_lines = []
 
129
        for line in moveline_array:
 
130
            if self._should_exlude_line(line):
 
131
                continue
 
132
            if line.account_id.type == 'receivable' and line.debit:
 
133
                credit_lines.append(line)
 
134
            else:
 
135
                if line.reconcile_partial_id:
 
136
                    payment_lines.append(line)
 
137
        credit_lines.sort(key=operator.attrgetter('date'))
 
138
        payment_lines.sort(key=operator.attrgetter('date'))
 
139
        return (credit_lines, payment_lines)
 
140
 
 
141
    def _validate_line_currencies(self, credit_lines):
 
142
        """Raise an excpetion if there is lines with different currency"""
 
143
        if len(credit_lines) == 0:
 
144
            return True
 
145
        currency = credit_lines[0].currency_id.id
 
146
        if not all(obj.currency_id.id == currency for obj in credit_lines):
 
147
            raise Exception('Not all line of move line are in the same currency')
 
148
 
 
149
    def _get_value_amount(self, mv_line_br):
 
150
        if mv_line_br.currency_id:
 
151
            return mv_line_br.amount_currency
 
152
        else:
 
153
            return mv_line_br.debit - mv_line_br.credit
 
154
 
 
155
    def _validate_partial(self, credit_lines):
 
156
        if len(credit_lines) == 0:
 
157
            return True
 
158
        else:
 
159
            line_with_partial = 0
 
160
            for line in credit_lines:
 
161
                if not line.reconcile_partial_id:
 
162
                    line_with_partial += 1
 
163
            if line_with_partial and line_with_partial != len(credit_lines):
 
164
                    raise Exception('Can not compute credit line if multiple'
 
165
                                    ' lines are not all linked to a partial')
 
166
 
 
167
    def _get_applicable_payment_lines(self, credit_line, payment_lines):
 
168
        applicable_payment = []
 
169
        for pay_line in payment_lines:
 
170
            if datetime.strptime(pay_line.date, "%Y-%m-%d").date() \
 
171
                <= datetime.strptime(credit_line.date, "%Y-%m-%d").date():
 
172
                applicable_payment.append(pay_line)
 
173
        return applicable_payment
 
174
 
 
175
    def _compute_partial_reconcile_residual(self, move_lines, lookup_date, move_id, memoizer):
 
176
        """ Compute open amount of multiple credit lines linked to multiple payment lines"""
 
177
        credit_lines, payment_lines = self._get_payment_and_credit_lines(move_lines, lookup_date, memoizer)
 
178
        self._validate_line_currencies(credit_lines)
 
179
        self._validate_line_currencies(payment_lines)
 
180
        self._validate_partial(credit_lines)
 
181
        # memoizer structure move_id : {move_line_id: open_amount}
 
182
        # paymnent line and credit line are sorted by date
 
183
        rest = 0.0
 
184
        for credit_line in credit_lines:
 
185
            applicable_payment = self._get_applicable_payment_lines(credit_line, payment_lines)
 
186
            paid_amount = 0.0
 
187
            for pay_line in applicable_payment:
 
188
                paid_amount += self._get_value_amount(pay_line)
 
189
            balance_amount = self._get_value_amount(credit_lines) - (paid_amount + rest)
 
190
            memoizer[move_id][credit_line.id] = balance_amount
 
191
            if balance_amount < 0.0:
 
192
                rest = balance_amount
 
193
            else:
 
194
                rest = 0.0
 
195
        return memoizer
 
196
 
 
197
    def _compute_fully_open_amount(self, move_lines, lookup_date, move_id, memoizer):
 
198
        for move_line in move_lines:
 
199
            memoizer[move_id][move_line.id] = self._get_value_amount(move_line)
 
200
        return memoizer
 
201
 
 
202
 
 
203
    def _amount_residual_from_date(self, cursor, uid, mv_line_br, lookup_date, context=None):
 
204
        """
 
205
        Code from function _amount_residual of account/account_move_line.py does not take
 
206
        in account mulitple line payment and reconciliation. We have to rewrite it
 
207
        Code computes residual amount at lookup date for mv_line_br in entry
 
208
        """
 
209
        memoizer = run.memoizers['credit_line_residuals']
 
210
        move_id = mv_line_br.move_id.id
 
211
        if mv_line_br.move_id.id in memoizer:
 
212
            pass # get back value
 
213
        else:
 
214
            memoizer[move_id] = {}
 
215
            move_lines = mv_line_br.move_id.line_id
 
216
            if mv_line_br.reconcile_partial_id:
 
217
                self._compute_partial_reconcile_residual(move_lines, lookup_date, move_id, memoizer)
 
218
            else:
 
219
                self._compute_fully_open_amount(move_lines, lookup_date, move_id, memoizer)
 
220
        return memoizer[move_id][mv_line_br.id]
 
221