File: account_payment.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (279 lines) | stat: -rw-r--r-- 15,627 bytes parent folder | download
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
from odoo import fields, models, api, Command, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import format_date


class AccountPayment(models.Model):
    _inherit = 'account.payment'

    l10n_latam_new_check_ids = fields.One2many('l10n_latam.check', 'payment_id', string='Checks')
    l10n_latam_move_check_ids = fields.Many2many(
        comodel_name='l10n_latam.check',
        relation='l10n_latam_check_account_payment_rel',
        column1="payment_id",
        column2="check_id",
        required=True,
        copy=False,
        string="Checks Operations"
    )
    # Warning message in case of unlogical third party check operations
    l10n_latam_check_warning_msg = fields.Text(compute='_compute_l10n_latam_check_warning_msg')
    amount = fields.Monetary(compute="_compute_amount", readonly=False, store=True)

    @api.constrains('state', 'move_id')
    def _check_move_id(self):
        for payment in self:
            if (
                not payment.move_id and
                payment.payment_method_code in ('own_checks', 'new_third_party_checks', 'in_third_party_checks', 'out_third_party_checks', 'return_third_party_checks') and
                not payment.outstanding_account_id
            ):
                raise ValidationError(_("A payment with any Third Party Check or Own Check payment methods needs an outstanding account"))

    @api.depends('l10n_latam_move_check_ids.amount', 'l10n_latam_new_check_ids.amount', 'payment_method_code')
    def _compute_amount(self):
        for rec in self:
            checks = rec.l10n_latam_new_check_ids if rec._is_latam_check_payment(check_subtype='new_check') else rec.l10n_latam_move_check_ids
            if checks:
                rec.amount = sum(checks.mapped('amount'))

    def _is_latam_check_payment(self, check_subtype=False):
        if check_subtype == 'move_check':
            codes = ['in_third_party_checks', 'out_third_party_checks', 'return_third_party_checks']
        elif check_subtype == 'new_check':
            codes = ['new_third_party_checks', 'own_checks']
        else:
            codes = ['in_third_party_checks', 'out_third_party_checks', 'return_third_party_checks', 'new_third_party_checks', 'own_checks']
        return self.payment_method_code in codes

    def action_post(self):
        # unlink checks if payment method code is not for checks. We do it on post and not when changing payment
        # method so that the user don't loose checks data in case of changing payment method and coming back again
        # also, changing partner recompute payment method so all checks would be cleaned
        for payment in self.filtered(lambda x: x.l10n_latam_new_check_ids and not x._is_latam_check_payment(check_subtype='new_check')):
            payment.l10n_latam_new_check_ids.unlink()
        if not self.env.context.get('l10n_ar_skip_remove_check'):
            for payment in self.filtered(lambda x: x.l10n_latam_move_check_ids and not x._is_latam_check_payment(check_subtype='move_check')):
                payment.l10n_latam_move_check_ids = False
        msgs = self._get_blocking_l10n_latam_warning_msg()
        if msgs:
            raise ValidationError('* %s' % '\n* '.join(msgs))
        super().action_post()
        self._l10n_latam_check_split_move()

    def _get_latam_checks(self):
        self.ensure_one()
        if self._is_latam_check_payment(check_subtype='new_check'):
            return self.l10n_latam_new_check_ids
        elif self._is_latam_check_payment(check_subtype='move_check'):
            return self.l10n_latam_move_check_ids
        else:
            return self.env['l10n_latam.check']

    def _get_blocking_l10n_latam_warning_msg(self):
        msgs = []
        for rec in self.filtered(lambda x: x.state == 'draft' and x._is_latam_check_payment()):
            if any(rec.currency_id != check.currency_id for check in rec._get_latam_checks()):
                msgs.append(_('The currency of the payment and the currency of the check must be the same.'))
            if not rec.currency_id.is_zero(sum(rec._get_latam_checks().mapped('amount')) - rec.amount):
                msgs.append(
                    _('The amount of the payment  does not match the amount of the selected check. '
                      'Please try to deselect and select the check again.')
                )
            # checks being moved
            if rec._is_latam_check_payment(check_subtype='move_check'):
                if any(check.payment_id.state == 'draft' for check in rec.l10n_latam_move_check_ids):
                    msgs.append(
                        _('Selected checks "%s" are not posted', rec.l10n_latam_move_check_ids.filtered(lambda x: x.payment_id.state == 'draft').mapped('display_name'))
                    )
                elif rec.payment_type == 'outbound' and any(check.current_journal_id != rec.journal_id for check in rec.l10n_latam_move_check_ids):
                    # check outbound payment and transfer or inbound transfer
                    msgs.append(_(
                        'Some checks are not anymore in journal, it seems it has been moved by another payment.')
                    )
                elif rec.payment_type == 'inbound' and not rec._is_latam_check_transfer() and any(rec.l10n_latam_move_check_ids.mapped('current_journal_id')):
                    msgs.append(
                        _("Some checks are already in hand and can't be received again. Checks: %s",
                          ', '.join(rec.l10n_latam_move_check_ids.mapped('display_name')))
                    )

                for check in rec.l10n_latam_move_check_ids:
                    date = rec.date or fields.Datetime.now()

                    last_operation = check._get_last_operation()
                    if last_operation and last_operation[0].date > date:
                        msgs.append(
                            _(
                              "It seems you're trying to move a check with a date (%(date)s) prior to last "
                              "operation done with the check (%(last_operation)s). This may be wrong, please "
                              "double check it. By continue, the last operation on "
                              "the check will remain being %(last_operation)s",
                              date=format_date(self.env, date), last_operation=last_operation.display_name
                            )
                        )
        return msgs

    def _get_reconciled_checks_error(self):
        checks_reconciled = self.l10n_latam_new_check_ids.filtered(lambda x: x.issue_state in ['debited', 'voided'])
        if checks_reconciled:
            raise UserError(
                _("You can't cancel or re-open a payment with checks if some check has been debited or been voided. "
                  "Checks:\n%s", ('\n'.join(['* %s (%s)' % (x.name, x.issue_state) for x in checks_reconciled])))
            )

    def action_cancel(self):
        self._get_reconciled_checks_error()
        super().action_cancel()

    def action_draft(self):
        self._get_reconciled_checks_error()
        super().action_draft()

    def _l10n_latam_check_split_move(self):
        for payment in self.filtered(lambda x: x.payment_method_code == 'own_checks' and x.payment_type == 'outbound'):
            if len(payment.l10n_latam_new_check_ids) == 1:
                liquidity_line = payment._seek_for_lines()[0]
                payment.l10n_latam_new_check_ids.outstanding_line_id = liquidity_line.id
                continue

            vals = {
                'journal_id': payment.journal_id.id,
                'move_type': 'entry',
                'line_ids': [],
            }
            payment_liquidity_line = payment._seek_for_lines()[0]

            # One line per check
            checks_total = sum(payment.l10n_latam_new_check_ids.mapped('amount'))
            liquidity_balance_total = 0.0
            liquidity_balance = 0.0
            for check in payment.l10n_latam_new_check_ids:
                liquidity_amount_currency = -check.amount

                if check == payment.l10n_latam_new_check_ids[-1]:
                    liquidity_balance = payment.currency_id.round(payment_liquidity_line.balance - liquidity_balance)
                else:
                    liquidity_balance = payment.currency_id.round(payment_liquidity_line.balance * check.amount / checks_total)
                    liquidity_balance_total += liquidity_balance

                vals['line_ids'].append(
                    Command.create({
                        'name': _(
                            'Check %(check_number)s - %(suffix)s',
                            check_number=check.name,
                            suffix=''.join([item[1] for item in payment._get_aml_default_display_name_list()])),
                        'date_maturity': check.payment_date,
                        'amount_currency': liquidity_amount_currency,
                        'currency_id': check.currency_id.id,
                        'debit': max(0.0, liquidity_balance),
                        'credit': -min(liquidity_balance, 0.0),
                        'partner_id': payment_liquidity_line.partner_id.id,
                        'account_id': payment_liquidity_line.account_id.id,
                        'l10n_latam_check_ids': [Command.link(check.id)]
                    }),
                )

            # Cancel payment line
            vals['line_ids'].append(
                Command.create({
                    'name': payment_liquidity_line.name,
                    'date_maturity': payment_liquidity_line.date_maturity,
                    'amount_currency': -payment_liquidity_line.amount_currency,
                    'currency_id': payment_liquidity_line.currency_id.id,
                    'debit': -payment_liquidity_line.debit,
                    'credit': -payment_liquidity_line.credit,
                    'partner_id': payment_liquidity_line.partner_id.id,
                    'account_id': payment_liquidity_line.account_id.id,
                }),
            )
            move_id = self.env['account.move'].create(vals)
            move_id.action_post()
            split_move_counterpart_line = move_id.line_ids.filtered(lambda x: x.amount_currency == -payment_liquidity_line.amount_currency)
            (split_move_counterpart_line + payment_liquidity_line).reconcile()

    def _l10n_latam_check_unlink_split_move(self):
        self.ensure_one()
        for check in self.l10n_latam_new_check_ids:
            if self.move_id == check.outstanding_line_id.move_id:
                check.outstanding_line_id = False
                continue
            check.outstanding_line_id.move_id.button_draft()
            check.outstanding_line_id.move_id.unlink()

    @api.depends(
        'payment_method_line_id', 'state', 'date', 'amount', 'currency_id', 'company_id',
        'l10n_latam_move_check_ids.issuer_vat', 'l10n_latam_move_check_ids.bank_id', 'l10n_latam_move_check_ids.payment_id.date',
        'l10n_latam_new_check_ids.amount', 'l10n_latam_new_check_ids.name',
    )
    def _compute_l10n_latam_check_warning_msg(self):
        """
        Compute warning message for latam checks checks
        We use l10n_latam_check_number as de dependency because on the interface this is the field the user is using.
        Another approach could be to add an onchange on _inverse_l10n_latam_check_number method
        """
        self.l10n_latam_check_warning_msg = False
        for rec in self.filtered(lambda x: x._is_latam_check_payment()):
            msgs = rec._get_blocking_l10n_latam_warning_msg()
            # new third party check uniqueness warning (on own checks it's done by a sql constraint)
            if rec.payment_method_code == 'new_third_party_checks':
                same_checks = self.env['l10n_latam.check']
                for check in rec.l10n_latam_new_check_ids.filtered(
                        lambda x: x.name and x.payment_method_line_id.code == 'new_third_party_checks' and
                        x.bank_id and x.issuer_vat):
                    same_checks += same_checks.search([
                        ('company_id', '=', rec.company_id.id),
                        ('bank_id', '=', check.bank_id.id),
                        ('issuer_vat', '=', check.issuer_vat),
                        ('name', '=', check.name),
                        ('payment_id.state', '!=', 'draft'),
                        ('id', '!=', check._origin.id)], limit=1)
                if same_checks:
                    msgs.append(
                        _("Other checks were found with same number, issuer and bank. Please double check you are not "
                          "encoding the same check more than once. List of other payments/checks: %s",
                          ", ".join(same_checks.mapped('display_name')))
                    )
            rec.l10n_latam_check_warning_msg = msgs and '* %s' % '\n* '.join(msgs) or False

    @api.model
    def _get_trigger_fields_to_synchronize(self):
        res = super()._get_trigger_fields_to_synchronize()
        return res + ('l10n_latam_new_check_ids',)

    def _prepare_move_line_default_vals(self, write_off_line_vals=None, force_balance=None):
        """ Add check name and operation on liquidity line """
        res = super()._prepare_move_line_default_vals(write_off_line_vals=write_off_line_vals, force_balance=force_balance)

        # if only one check we don't create the split line, we add same data on liquidity line
        if self.payment_method_code == 'own_checks' and self.payment_type == 'outbound' and len(self.l10n_latam_new_check_ids) == 1:
            res[0].update({
                'name': _(
                    'Check %(check_number)s - %(suffix)s',
                    check_number=self.l10n_latam_new_check_ids.name,
                    suffix=''.join([item[1] for item in self._get_aml_default_display_name_list()])),
                'date_maturity': self.l10n_latam_new_check_ids.payment_date,
            })
        # we dont check the payment method code because when deposited on bank/cash journals pay method is manual but we still change the label
        # we dont want this names on the own checks because it doesn't add value, already each split/check line will have it name
        elif (self.l10n_latam_new_check_ids or self.l10n_latam_move_check_ids) and self.payment_method_code != 'own_checks':
            check_name = [check_name for check_name in (self.l10n_latam_new_check_ids | self.l10n_latam_move_check_ids).mapped('name') if check_name]
            document_name = (
                _('Checks %s received') if self.payment_type == 'inbound' else _('Checks %s delivered')) % (
                ', '.join(check_name)
            )
            res[0].update({
                'name': document_name + ' - ' + ''.join([item[1] for item in self._get_aml_default_display_name_list()]),
            })
        return res

    @api.depends('l10n_latam_move_check_ids')
    def _compute_destination_account_id(self):
        # EXTENDS 'account'
        super()._compute_destination_account_id()
        for payment in self:
            if payment.l10n_latam_move_check_ids and (not payment.partner_id or payment.partner_id == payment.company_id.partner_id):
                payment.destination_account_id = payment.company_id.transfer_account_id.id

    def _is_latam_check_transfer(self):
        self.ensure_one()
        return not self.partner_id and self.destination_account_id == self.company_id.transfer_account_id