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