Refactor 100% discount accounting by removing custom journal entries from pos_session and implementing dynamic account mapping in pos_order_line.

This commit is contained in:
Suherdy Yacob 2026-03-12 15:05:08 +07:00
parent 6640b0bc98
commit d975966b70
4 changed files with 39 additions and 76 deletions

View File

@ -24,8 +24,8 @@ This module modifies the loyalty reward discount calculation in POS to apply dis
### Backend Changes ### Backend Changes
- Extended `pos.session` (`models/pos_session.py`) to generate additional journal entry lines for 0-value orders. - 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` to allow configuration of Income and Expense accounts for 100% discounts. - Extended `res.config.settings` (`models/res_config_settings.py`) to allow configuration of Income and Expense accounts for 100% discounts.
## Installation ## Installation

View File

@ -1,3 +1,4 @@
from . import pos_config from . import pos_config
from . import pos_session from . import pos_session
from . import res_config_settings from . import res_config_settings
from . import pos_order_line

32
models/pos_order_line.py Normal file
View File

@ -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)

View File

@ -4,77 +4,7 @@ from odoo.tools import float_is_zero
class PosSession(models.Model): class PosSession(models.Model):
_inherit = 'pos.session' _inherit = 'pos.session'
def _create_account_move(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None): # The manual creation of journal entries for 100% discount orders
""" # has been removed because it duplicates the aggregated entries that
Extend _create_account_move to generate additional journal entry lines # Odoo POS natively creates when closing a session. The mapping is
for 100% discount orders (where total amount is 0). # instead handled at the pos.order.line level dynamically.
"""
# 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