Browse Source

Nov 04: [FIX] Bug Fixed 'pos_partial_payment_odoo'

16.0
Risvana Cybro 1 week ago
parent
commit
9dbf7930e5
  1. 2
      pos_partial_payment_odoo/__manifest__.py
  2. 5
      pos_partial_payment_odoo/doc/RELEASE_NOTES.md
  3. 169
      pos_partial_payment_odoo/models/pos_order.py

2
pos_partial_payment_odoo/__manifest__.py

@ -21,7 +21,7 @@
################################################################################
{
'name': 'POS Partial Payment',
'version': '16.0.1.0.0',
'version': '16.0.1.0.1',
'category': 'Point of Sale',
'summary': "Simplify Payments with Partial Payment in Odoo POS",
'description': "In Odoo POS, partial payments allow customers to pay for "

5
pos_partial_payment_odoo/doc/RELEASE_NOTES.md

@ -5,3 +5,8 @@
### ADD
- Initial Commit for POS Partial Payment
#### 03.11.2025
#### Version 16.0.1.0.1
#### UPDT
- Bug fix in amount_due miscalculation due to premature rounding in Pos invoice.

169
pos_partial_payment_odoo/models/pos_order.py

@ -21,8 +21,14 @@
################################################################################
from odoo import api, models, fields, _
from odoo.exceptions import UserError
from odoo.osv.expression import AND
from odoo.tools import float_round, float_is_zero
from odoo.osv.expression import AND, _logger
from odoo.tools.float_utils import float_round, float_is_zero, float_compare
try:
# Optional: use psycopg2 constants if available for more robust checks
from psycopg2 import errors as pg_errors
except Exception:
pg_errors = None
class PosOrder(models.Model):
@ -77,25 +83,35 @@ class PosOrder(models.Model):
payment.
"""
self.ensure_one()
# TODO: add support for mix of cash and non-cash payments when both cash_rounding and only_round_cash_method are True
if not self.config_id.cash_rounding \
or self.config_id.only_round_cash_method \
and not any(
p.payment_method_id.is_cash_count for p in self.payment_ids):
# compute the total to compare taking into account cash rounding config on the order's config
if (not self.config_id.cash_rounding) or (
self.config_id.only_round_cash_method and not any(
p.payment_method_id.is_cash_count for p in self.payment_ids)):
total = self.amount_total
else:
# use the configured rounding method value and rounding algorithm if present
precision_rounding = getattr(self.config_id.rounding_method,
'rounding',
self.currency_id.rounding) or self.currency_id.rounding
total = float_round(self.amount_total,
precision_rounding=self.config_id.rounding_method.rounding,
rounding_method=self.config_id.rounding_method.rounding_method)
precision_rounding=precision_rounding,
rounding_method=getattr(
self.config_id.rounding_method,
'rounding_method', None))
isPaid = float_is_zero(total - self.amount_paid,
precision_rounding=self.currency_id.rounding)
# Only allow marking as paid for partial payments when this order specifically is flagged as partial payment
if not isPaid:
pos_config = self.env['pos.config'].search([])
for shop in pos_config:
if shop.partial_payment:
isPaid = True
# previously code flipped isPaid True if any pos_config.partial_payment existed,
# which caused incorrect marking. Instead: only allow if the *current order* is a partial payment order.
if self.is_partial_payment:
isPaid = True
# Keep the cash rounding tolerance check if still not isPaid (original behavior)
if not isPaid and not self.config_id.cash_rounding:
raise UserError(_("Order %s is not fully paid.", self.name))
raise UserError(_("Order %s is not fully paid.") % (self.name,))
elif not isPaid and self.config_id.cash_rounding:
currency = self.currency_id
if self.config_id.rounding_method.rounding_method == "HALF-UP":
@ -104,10 +120,10 @@ class PosOrder(models.Model):
else:
maxDiff = currency.round(
self.config_id.rounding_method.rounding)
diff = currency.round(self.amount_total - self.amount_paid)
if not abs(diff) <= maxDiff:
raise UserError(_("Order %s is not fully paid.", self.name))
raise UserError(_("Order %s is not fully paid.") % (self.name,))
self.write({'state': 'paid'})
return True
@ -123,3 +139,124 @@ class PosOrder(models.Model):
offset=offset).ids
totalCount = self.search_count(real_domain)
return {'ids': ids, 'totalCount': totalCount}
def _create_invoice(self, move_vals):
"""
Create the account.move (invoice) and apply cash rounding adjustments
ONLY if the order is effectively paid within rounding tolerance.
NOTE: do NOT call super()._create_invoice(move_vals) because the original
implementation already contains rounding logic that would run unconditionally.
"""
self.ensure_one()
# Create the invoice move directly (same as original POS code)
new_move = self.env['account.move'].sudo().with_company(
self.company_id).with_context(
default_move_type=move_vals['move_type']
).create(move_vals)
# Post a message on the invoice linking to the POS order
message = _(
"This invoice has been created from the point of sale session: %s",
self._get_html_link(),
)
new_move.message_post(body=message)
# If no cash rounding configured for the POS, nothing to do
if not self.config_id.cash_rounding:
return new_move
# determine rounding precision
rounding_precision = new_move.currency_id.rounding or self.currency_id.rounding or 0.0
# Determine whether the order is paid within rounding tolerance
is_paid_within_rounding = float_compare(self.amount_paid,
self.amount_total,
precision_rounding=rounding_precision) >= 0
# If only_round_cash_method is set, require at least one cash payment
if self.config_id.only_round_cash_method and not any(
p.payment_method_id.is_cash_count for p in self.payment_ids):
apply_rounding = False
else:
apply_rounding = is_paid_within_rounding
# If not applying rounding, return the invoice as-is
if not apply_rounding:
return new_move
# Compute rounding applied and adjust rounding + receivable lines
rounding_applied = float_round(self.amount_paid - self.amount_total,
precision_rounding=rounding_precision)
rounding_line_difference = 0.0
rounding_line = new_move.line_ids.filtered(
lambda line: line.display_type == 'rounding')
if rounding_line and rounding_line.filtered(lambda l: l.debit > 0):
rl = rounding_line.filtered(lambda l: l.debit > 0)[0]
rounding_line_difference = rl.debit + rounding_applied
elif rounding_line and rounding_line.filtered(lambda l: l.credit > 0):
rl = rounding_line.filtered(lambda l: l.credit > 0)[0]
rounding_line_difference = -rl.credit + rounding_applied
else:
rounding_line_difference = rounding_applied
if rounding_applied:
if rounding_applied > 0.0:
account_id = new_move.invoice_cash_rounding_id.loss_account_id.id
else:
account_id = new_move.invoice_cash_rounding_id.profit_account_id.id
if rounding_line:
if rounding_line_difference:
rounding_line.with_context(skip_invoice_sync=True,
check_move_validity=False).write(
{
'debit': rounding_applied < 0.0 and -rounding_applied or 0.0,
'credit': rounding_applied > 0.0 and rounding_applied or 0.0,
'account_id': account_id,
'price_unit': rounding_applied,
})
else:
# create the rounding line
self.env['account.move.line'].with_context(
skip_invoice_sync=True, check_move_validity=False).create({
'balance': -rounding_applied,
'quantity': 1.0,
'partner_id': new_move.partner_id.id,
'move_id': new_move.id,
'currency_id': new_move.currency_id.id,
'company_id': new_move.company_id.id,
'company_currency_id': new_move.company_id.currency_id.id,
'display_type': 'rounding',
'sequence': 9999,
'name': self.config_id.rounding_method.name,
'account_id': account_id,
})
else:
if rounding_line:
rounding_line.with_context(skip_invoice_sync=True,
check_move_validity=False).unlink()
if rounding_line_difference:
existing_terms_line = new_move.line_ids.filtered(
lambda line: line.account_id.account_type in (
'asset_receivable', 'liability_payable'))
if existing_terms_line:
if existing_terms_line.debit > 0:
existing_terms_line_new_val = float_round(
existing_terms_line.debit + rounding_line_difference,
precision_rounding=rounding_precision)
else:
existing_terms_line_new_val = float_round(
-existing_terms_line.credit + rounding_line_difference,
precision_rounding=rounding_precision)
existing_terms_line.with_context(skip_invoice_sync=True).write({
'debit': existing_terms_line_new_val > 0.0 and existing_terms_line_new_val or 0.0,
'credit': existing_terms_line_new_val < 0.0 and -existing_terms_line_new_val or 0.0,
})
return new_move

Loading…
Cancel
Save