diff --git a/README.md b/README.md index 0874be4..dc521fe 100755 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ This module modifies the loyalty reward discount calculation in POS to apply dis ### Backend Changes -- Extended `pos.session` (`models/pos_session.py`) to generate additional journal entry lines for 0-value orders. -- Extended `res.config.settings` to allow configuration of Income and Expense accounts for 100% discounts. +- Extended `pos.order.line` (`models/pos_order_line.py`) to override `_get_income_account`. For 100% discount orders, this dynamically maps standard item lines to the custom Income Account and discount reward lines to the custom Expense Account during standard POS session aggregation, preventing double journal entries. +- Extended `res.config.settings` (`models/res_config_settings.py`) to allow configuration of Income and Expense accounts for 100% discounts. ## Installation diff --git a/models/__init__.py b/models/__init__.py index 55380fe..d792fe3 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,3 +1,4 @@ from . import pos_config from . import pos_session from . import res_config_settings +from . import pos_order_line diff --git a/models/pos_order_line.py b/models/pos_order_line.py new file mode 100644 index 0000000..a3a1907 --- /dev/null +++ b/models/pos_order_line.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from odoo import models, api +from odoo.tools import float_is_zero + +class PosOrderLine(models.Model): + _inherit = 'pos.order.line' + + def _get_income_account(self, **kwargs): + """ + Dynamically route the income account for 100% discount orders + if the custom discount_100 accounts are configured. + """ + self.ensure_one() + + # If the whole order is essentially 0 (100% discount): + if float_is_zero(self.order_id.amount_total, precision_rounding=self.order_id.currency_id.rounding): + # Check if custom accounts are configured + income_account = self.order_id.config_id.discount_100_income_account_id + expense_account = self.order_id.config_id.discount_100_expense_account_id + + if income_account and expense_account: + # If this line is an income/product line (positive value) + if self.price_subtotal > 0: + return income_account + + # If this line is the discount/reward line (negative value) + elif self.price_subtotal < 0: + return expense_account + + # Fallback to standard Odoo logic + return super(PosOrderLine, self)._get_income_account(**kwargs) diff --git a/models/pos_session.py b/models/pos_session.py index 617aabd..cae2d0f 100644 --- a/models/pos_session.py +++ b/models/pos_session.py @@ -4,77 +4,7 @@ from odoo.tools import float_is_zero class PosSession(models.Model): _inherit = 'pos.session' - def _create_account_move(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None): - """ - Extend _create_account_move to generate additional journal entry lines - for 100% discount orders (where total amount is 0). - """ - # Call super to generate the standard move - data = super(PosSession, self)._create_account_move(balancing_account, amount_to_balance, bank_payment_method_diffs) - - if not self.config_id.discount_100_income_account_id or not self.config_id.discount_100_expense_account_id: - return data - - # Identify orders with 0 absolute paid amount but non-zero gross amount - # We look for orders where amount_total is near zero. - # Note: 100% discount orders have amount_total = 0. - - MoveLine = data.get('MoveLine') - income_account = self.config_id.discount_100_income_account_id - expense_account = self.config_id.discount_100_expense_account_id - - # Helper to convert amount to company currency if needed, similar to Odoo's internals - def _get_amounts(amount, date): - return self._update_amounts({'amount': 0, 'amount_converted': 0}, {'amount': amount}, date) - - zero_value_moves = [] - - for order in self._get_closed_orders(): - if float_is_zero(order.amount_total, precision_rounding=self.currency_id.rounding): - # Calculate gross amount (price before discount logic applied, or sum of positive lines) - # Since it's 100% discount, the "Gross Value" we want to record is roughly the sum - # of the products' prices *before* they were wiped out. - # However, in our new line-by-line strategy, we have: - # Item A: $100 - # Discount A: -$100 - # Net: $0 - # We want to record: Credit Income $100, Debit Expense $100. - - # Calculate the exact discount amount applied by loyalty rewards - # This avoids treating downpayments or refunds as "100% Discount Income" - discount_amount = sum( - abs(line.price_subtotal) - for line in order.lines - if line.price_subtotal < 0 and (getattr(line, 'is_reward_line', False) or getattr(line, 'reward_id', False)) - ) - - if float_is_zero(discount_amount, precision_rounding=self.currency_id.rounding): - continue - - amounts = _get_amounts(discount_amount, order.date_order) - - # Create Credit Line (Income) - # We use _credit_amounts helper logic style manually - credit_vals = { - 'name': _('100%% Discount Income: %s') % order.name, - 'account_id': income_account.id, - 'move_id': self.move_id.id, - 'partner_id': order.partner_id.id or False, - } - credit_vals.update(self._credit_amounts(credit_vals, amounts['amount'], amounts['amount_converted'])) - zero_value_moves.append(credit_vals) - - # Create Debit Line (Expense/Discount) - debit_vals = { - 'name': _('100%% Discount Expense: %s') % order.name, - 'account_id': expense_account.id, - 'move_id': self.move_id.id, - 'partner_id': order.partner_id.id or False, - } - debit_vals.update(self._debit_amounts(debit_vals, amounts['amount'], amounts['amount_converted'])) - zero_value_moves.append(debit_vals) - - if zero_value_moves: - MoveLine.create(zero_value_moves) - - return data + # The manual creation of journal entries for 100% discount orders + # has been removed because it duplicates the aggregated entries that + # Odoo POS natively creates when closing a session. The mapping is + # instead handled at the pos.order.line level dynamically.