From abd0a2ceead05d3d3e13543f0bedf6f7b0eaa761 Mon Sep 17 00:00:00 2001 From: Carmen Bianca BAKKER Date: Fri, 17 May 2024 19:06:20 +0200 Subject: [PATCH] [IMP] pos_customer_wallet: After closing, correctly handle purchased wallet products Signed-off-by: Carmen Bianca BAKKER --- pos_customer_wallet/models/pos_session.py | 101 ++++++++++++++++++++++ pos_customer_wallet/tests/test_balance.py | 52 +++++++++++ 2 files changed, 153 insertions(+) diff --git a/pos_customer_wallet/models/pos_session.py b/pos_customer_wallet/models/pos_session.py index b84c76155..bb2c24996 100644 --- a/pos_customer_wallet/models/pos_session.py +++ b/pos_customer_wallet/models/pos_session.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later from odoo import models +from odoo.fields import Command class PosSession(models.Model): @@ -22,3 +23,103 @@ def _loader_params_res_partner(self): result = super()._loader_params_res_partner() result["search_params"]["fields"].append("customer_wallet_balance") return result + + # This function is called as part of closing the session. We need to add + # some extra behaviour because, after closing, only a single + # account.move.line is created against the Customer Wallet account on the + # Account Receivable (PoS) journal. This is wrong. We need one + # account.move.line for every partner. + def _reconcile_account_move_lines(self, data): + data = super()._reconcile_account_move_lines(data) + self._reconcile_account_move_lines_customer_wallet(data) + # This data does not match exactly anymore (some move lines were + # shuffled). We could theoretically put in the effort to also do the + # shuffling in the data object, but it's fine for now, because this data + # doesn't appear to be subsequently used anywhere. + return data + + def _reconcile_account_move_lines_customer_wallet(self, data): + sales = data.get("sales") + # This account.move.line empty recordset has a special context that + # allows us to make changes that aren't exactly in sync with the + # account.move. We need this because we'll very temporarily be out of + # sync in between unlinking the old lines and creating the new ones. + MoveLine = data.get("MoveLine") + + # We want to remove all account.move.lines with accumulated information + # across many partners, and create new account.move.lines who have + # partner_ids. Instead of doing this one-by-one, we create a record of + # all the stuff we want to change, and then do it in bulk at the end. + to_unlink = MoveLine.browse() + to_create = [] + + for sale_key, sale_val in sales.items(): + sale_account = self.env["account.account"].browse(sale_key[0]) + # Only work on sales involving the customer wallet. Skip everything + # else. + if sale_account != self.env.company.customer_wallet_account_id: + continue + account_move_line = self.env["account.move.line"].browse( + sale_val["move_line_id"] + ) + account_move = account_move_line.move_id + + to_unlink |= account_move_line + + order_lines = self._search_customer_wallet_order_lines(sale_key) + for order_line in order_lines: + price = order_line.price_subtotal + partner = order_line.order_id.partner_id + + amounts = {"amount": 0, "amount_converted": 0} + amounts = self._update_amounts( + amounts, {"amount": price}, account_move_line.date + ) + + # These vals are similar to _get_sale_vals() in point_of_sale. + vals = { + "name": account_move_line.name, + "partner_id": partner.id, + "account_id": account_move_line.account_id.id, + "move_id": account_move.id, + "tax_ids": [Command.set(account_move_line.tax_ids.ids)], + "tax_tag_ids": [Command.set(account_move_line.tax_tag_ids.ids)], + } + # Add credit/debit stuff. + vals = self._credit_amounts( + vals, amounts["amount"], amounts["amount_converted"] + ) + + to_create.append(vals) + + # Make all changes in bulk. + moves = self.env["account.move"].search([("line_ids", "in", to_unlink.ids)]) + # Un-post and re-post the account.moves to be able to make changes to + # them. + moves.button_draft() + to_unlink.unlink() + MoveLine.create(to_create) + # This validates that we did everything right, too. + moves.action_post() + + def _search_customer_wallet_order_lines(self, sale_key): + result = self.env["pos.order.line"] + for order in self.order_ids.filtered(lambda order: not order.is_invoiced): + for order_line in order.lines: + line = self._prepare_line(order_line) + # Copied from point_of_sale. + reconstructed_sale_key = ( + # account + line["income_account_id"], + # sign + -1 if line["amount"] < 0 else 1, + # for taxes + tuple( + (tax["id"], tax["account_id"], tax["tax_repartition_line_id"]) + for tax in line["taxes"] + ), + line["base_tags"], + ) + if sale_key == reconstructed_sale_key: + result |= order_line + return result diff --git a/pos_customer_wallet/tests/test_balance.py b/pos_customer_wallet/tests/test_balance.py index 4d7f1f6ea..83e1add19 100644 --- a/pos_customer_wallet/tests/test_balance.py +++ b/pos_customer_wallet/tests/test_balance.py @@ -33,3 +33,55 @@ def test_credit_by_product(self): amount=1000, ) self.assertEqual(self.partner.customer_wallet_balance, 1500) + + def test_close_session(self): + """When closing a session, a correct account move is made.""" + self.create_wallet_pos_payment( + amount=40, + product=self.wallet_product, + payment_method=self.cash_payment_method, + ) + + other_partner = self.env.ref("base.res_partner_address_31") + self.create_wallet_pos_payment( + amount=20, + product=self.wallet_product, + payment_method=self.cash_payment_method, + partner=other_partner, + ) + + self.pos_session.action_pos_session_close() + + self.assertEqual(self.partner.customer_wallet_balance, 40) + self.assertEqual(other_partner.customer_wallet_balance, 20) + + move_lines = self.env["account.move.line"].search( + [ + ("partner_id", "in", (self.partner | other_partner).ids), + ( + "account_id", + "=", + self.customer_wallet_account.id, + ), + ] + ) + # One line for each partner + self.assertEqual(len(move_lines), 2) + # Two credits and one debit on the move + self.assertEqual(len(move_lines[0].move_id.line_ids), 3) + # Credit amount is correct + self.assertEqual( + move_lines.filtered(lambda line: line.partner_id == self.partner).credit, + 40, + ) + + def test_close_session_buy_negative_product(self): + """When buying a negatively priced wallet product, decrease balance.""" + self.create_wallet_pos_payment( + amount=-40, + product=self.wallet_product, + payment_method=self.cash_payment_method, + ) + self.pos_session.action_pos_session_close() + + self.assertEqual(self.partner.customer_wallet_balance, -40)