Skip to content

Commit

Permalink
base_upflow: add upflow_type and refactor reconcile payload
Browse files Browse the repository at this point in the history
  • Loading branch information
petrus-v committed Feb 2, 2024
1 parent a855fe7 commit bd26023
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 80 deletions.
103 changes: 79 additions & 24 deletions base_upflow/models/account_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
# @author Pierre Verkest <pierreverkest84@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
import logging

from odoo import _, fields, models
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare

_logger = logging.getLogger(__name__)


class AccountMove(models.Model):
Expand All @@ -17,6 +21,81 @@ class AccountMove(models.Model):
help="Technical field to get the upflow customer to use on account move",
)

upflow_type = fields.Selection(
selection=[
("none", "Not concerned"),
("invoices", "Invoice"),
("payments", "Invoice payment"),
("creditNotes", "Refund"),
("refunds", "Refund payment"),
],
compute="_compute_upflow_type",
help=(
"Technical fields to make sure consistency "
"while sending Journal entry and reconcile "
"payloads. Key values are current payload "
"keys while sending reconcile. While creating "
"malicious entries it can be hard to automatically "
"choose proper type"
),
)

def _compute_upflow_type(self):
for move in self:
if move.move_type.startswith("in_") or move.state != "posted":
move.upflow_type = "none"
continue
if move.move_type == "out_invoice":
move.upflow_type = "invoices"
elif move.move_type == "out_refund":
move.upflow_type = "creditNotes"
else:
receivables_lines = move.line_ids.filtered(
lambda line: line.account_id.user_type_id.type == "receivable"
)
if not receivables_lines:
move.upflow_type = "none"
continue
debit = sum(receivables_lines.mapped("debit"))
credit = sum(receivables_lines.mapped("credit"))
if (
float_compare(
debit,
0,
precision_rounding=move.currency_id.rounding,
)
== 0
and float_compare(
credit,
0,
precision_rounding=move.currency_id.rounding,
)
!= 0
):
move.upflow_type = "payments"
elif (
float_compare(
debit,
0,
precision_rounding=move.currency_id.rounding,
)
!= 0
and float_compare(
credit,
0,
precision_rounding=move.currency_id.rounding,
)
== 0
):
move.upflow_type = "refunds"
else:
_logger.error(
"Sum of receivable move lines on %s have credit (%d) and debit(%d). "
"Which sounds suspicious and can't set upflow type",
move.name,
)
move.upflow_type = "none"

def _compute_upflow_commercial_partner_id(self):
# while using OD as counter part or bank statement
# there are chance that partner_id is not set on account.move
Expand Down Expand Up @@ -56,35 +135,11 @@ def _prepare_upflow_api_payload(self):
def get_upflow_api_post_invoice_payload(self):
"""An upflow invoice match with account.move out_invoice odoo type"""
self.ensure_one()
if self.move_type != "out_invoice":
raise UserError(
_(
"You try to get upflow invoice payload "
"on account entry %s with an other type %s "
"(expected out_invoice)"
)
% (
self.name,
self.move_type,
)
)
return self._prepare_upflow_api_payload()

def get_upflow_api_post_credit_note_payload(self):
"""An upflow credit note match with account.move out_refund odoo type"""
self.ensure_one()
if self.move_type != "out_refund":
raise UserError(
_(
"You try to get upflow refund payload "
"on account entry %s with an other type %s "
"(expected out_refund)"
)
% (
self.name,
self.move_type,
)
)
return self._prepare_upflow_api_payload()

def get_upflow_api_post_payment_payload(self):
Expand Down
50 changes: 21 additions & 29 deletions base_upflow/models/account_partial_reconcile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@


class AccountPartialReconcile(models.Model):

_inherit = ["account.partial.reconcile"]
_name = "account.partial.reconcile"
_inherit = ["account.partial.reconcile", "upflow.mixin"]

def _prepare_reconcile_payload(self):
payload = {
Expand All @@ -18,6 +18,19 @@ def _prepare_reconcile_payload(self):
}
return payload

def _get_part_payload(self, move_line):
data = {
"externalId": str(move_line.move_id.id),
"amountLinked": self.company_currency_id.to_lowest_division(self.amount),
}
if move_line.move_id.upflow_uuid:
data["id"] = move_line.move_id.upflow_uuid

if move_line.move_id.upflow_type in ["invoices", "creditNotes"]:
data["customId"] = move_line.move_id.name

return data

def get_upflow_api_post_reconcile_payload(self):
"""expect to be called from account move type:
Expand All @@ -28,32 +41,11 @@ def get_upflow_api_post_reconcile_payload(self):
"""
payload = self._prepare_reconcile_payload()

data = {
"externalId": str(self.debit_move_id.move_id.id),
"amountLinked": self.company_currency_id.to_lowest_division(self.amount),
}
if self.debit_move_id.move_id.upflow_uuid:
data["id"] = self.debit_move_id.move_id.upflow_uuid
if self.debit_move_id.move_id.move_type == "out_invoice":
kind = "invoices"
data["customId"] = self.debit_move_id.move_id.name
else:
kind = "refunds"

payload[kind].append(data)

data = {
"externalId": str(self.credit_move_id.move_id.id),
"amountLinked": self.company_currency_id.to_lowest_division(self.amount),
}
if self.credit_move_id.move_id.upflow_uuid:
data["id"] = self.credit_move_id.move_id.upflow_uuid
if self.credit_move_id.move_id.move_type == "out_refund":
kind = "creditNotes"
data["customId"] = self.credit_move_id.move_id.name
else:
kind = "payments"

payload[kind].append(data)
payload[self.debit_move_id.move_id.upflow_type].append(
self._get_part_payload(self.debit_move_id)
)
payload[self.credit_move_id.move_id.upflow_type].append(
self._get_part_payload(self.credit_move_id)
)

return payload
152 changes: 152 additions & 0 deletions base_upflow/tests/test_account_move.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from odoo.tests.common import SavepointCase
from odoo.tools import mute_logger

from .common import AccountingCommonCase


class TestAccountMove(SavepointCase):
Expand Down Expand Up @@ -67,3 +70,152 @@ def test_compute_upflow_commercial_partner_id_invoice(self):
self.account_move.upflow_commercial_partner_id,
self.partner.commercial_partner_id,
)


class TestAccountMoveUpflowType(SavepointCase, AccountingCommonCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._setup_accounting()
cls.partner = cls.env["res.partner"].create(
{
"name": "My customer company",
"is_company": True,
"vat": "FR23334175221",
"street": "Street 1",
"street2": "and more",
"zip": "45500",
"city": "Customer city",
}
)
cls.invoice = cls._create_invoice()
cls.refund = cls._create_invoice(move_type="out_refund")
cls.purchase_invoice = cls._create_invoice(move_type="in_invoice")
cls.payment_bnk_stmt = cls._make_credit_transfer_payment_reconciled(cls.invoice)

def test_compute_upflow_type_draft(self):
# self.assertEqual(self.entry_move.upflow_type, "none")
self.assertEqual(self.invoice.upflow_type, "none")
self.assertEqual(self.refund.upflow_type, "none")
self.assertEqual(self.purchase_invoice.upflow_type, "none")

def test_compute_upflow_type_invoices(self):
self.invoice.action_post()
self.assertEqual(self.invoice.upflow_type, "invoices")

def test_compute_upflow_type_credit_notes(self):
self.refund.action_post()
self.assertEqual(self.refund.upflow_type, "creditNotes")

def test_payment_by_bank_statment(self):
self.assertEqual(self.payment_bnk_stmt.upflow_type, "payments")

def test_customer_payment_from_bank_statement(self):
self.refund.action_post()
payment_bnk_stmt = self._make_credit_transfer_payment_reconciled(
self.refund,
amount=-self.refund.amount_residual,
reconcile_param=[
{
"id": self.refund.line_ids.filtered(
lambda line: line.account_internal_type
in ("receivable", "payable")
).id
}
],
)
self.assertEqual(payment_bnk_stmt.upflow_type, "refunds")

def test_customer_payment_manual_payment(self):
self.refund.action_post()
move = self._register_manual_payment_reconciled(self.refund)
self.assertEqual(move.upflow_type, "refunds")

def test_bank_statment_not_reconciled(self):
# at that time we don't know yet if payment entry is related to
# payment refunds or paid purchase invoice

(
bank_journal,
_method,
payment_date,
amount,
_currency,
) = self._payment_params(
self.invoice,
)
bank_stmt = self.env["account.bank.statement"].create(
{
"journal_id": bank_journal.id,
"date": payment_date,
"name": "payment",
"line_ids": [
(
0,
0,
{
"payment_ref": "payment",
"partner_id": self.partner.id,
"amount": -amount,
},
)
],
}
)
bank_stmt.button_post()
self.assertEqual(bank_stmt.line_ids[0].move_id.upflow_type, "none")

def test_no_receivable_lines(self):
self.purchase_invoice.action_post()
self.assertEqual(self.purchase_invoice.upflow_type, "none")

@mute_logger("odoo.addons.base_upflow.models.account_move")
def test_receivables_null(self):

account_user_type = self.env["account.account.type"].create(
{
"name": "Test account type",
"type": "receivable",
"internal_group": "asset",
}
)
account = self.env["account.account"].create(
{
"name": "Test account",
"code": "TEST",
"user_type_id": account_user_type.id,
"reconcile": True,
}
)
entry_move = self.env["account.move"].create(
{
"journal_id": self.invoice.journal_id.id,
"move_type": "entry",
"line_ids": [
(
0,
0,
{
"account_id": account.id,
"partner_id": self.partner.id,
"name": "Test entry",
"debit": 0,
"credit": 0,
},
),
(
0,
0,
{
"account_id": account.id,
"partner_id": self.partner.id,
"name": "Test entry",
"debit": 0,
"credit": 0,
},
),
],
}
)
entry_move.action_post()
self.assertEquals(entry_move.upflow_type, "none")
8 changes: 0 additions & 8 deletions base_upflow/tests/test_upflow_post_invoices_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,19 +257,11 @@ def test_get_upflow_api_post_credit_notes_payload_content(self):
str(self.customer_company.id),
)

def test_get_payload_not_an_invoice(self):
with self.assertRaisesRegex(UserError, "expected out_invoice"):
self.refund.get_upflow_api_post_invoice_payload()

def test_get_invoice_pdf_payload_not_an_invoice(self):
invoice_payment_move = self._register_manual_payment_reconciled(self.invoice)
with self.assertRaisesRegex(UserError, "expected out_invoice"):
invoice_payment_move.get_upflow_api_pdf_payload()

def test_get_payload_not_a_refund(self):
with self.assertRaisesRegex(UserError, "expected out_refund"):
self.invoice.get_upflow_api_post_credit_note_payload()

def test_format_upflow_amount(self):
currency_euro = self.env.ref("base.EUR")
currency_dynar = self.env.ref("base.LYD")
Expand Down
Loading

0 comments on commit bd26023

Please sign in to comment.