Skip to content

Commit

Permalink
[IMP] pos_sale_order: Improve pos order cancelation
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed Jan 31, 2024
1 parent 964a2a1 commit f4458dd
Show file tree
Hide file tree
Showing 14 changed files with 686 additions and 147 deletions.
1 change: 1 addition & 0 deletions pos_sale_order/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"security/ir.model.access.csv",
"wizards/pos_payment_wizard_view.xml",
"wizards/pos_delivery_wizard_view.xml",
"wizards/pos_sale_order_cancel_wizard_view.xml",
"views/sale_view.xml",
"views/pos_payment_method_views.xml",
"views/point_of_sale_view.xml",
Expand Down
257 changes: 166 additions & 91 deletions pos_sale_order/i18n/fr.po

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions pos_sale_order/models/pos_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def _prepare_sale_statement(self, payments, record_ref):
if record_ref._name == "sale.order":
ref = " - ".join([record_ref.name] + record_ref.invoice_ids.mapped("name"))
elif record_ref._name == "account.move":
ref = record_ref.name
ref = " - ".join(record_ref.mapped("name"))
method = payments.payment_method_id
return {
"date": fields.Date.context_today(self),
Expand Down Expand Up @@ -98,8 +98,10 @@ def _get_receivable_line(self, payments):
== "posted"
).line_ids.filtered(
# get receivable line
lambda s: s.account_id
== payments.payment_method_id.receivable_account_id
lambda s: s.account_id == payments.payment_method_id.receivable_account_id
# only not reconciled line, this can happen if the payment
# is a refund of a previous invoice
and not s.reconciled
)

def _create_bank_statement_line(self, statement, payments, record_ref):
Expand Down Expand Up @@ -142,6 +144,9 @@ def _create_bank_statement_line_and_reconcile(self):
"name": self.name,
}
)
if not statement:
# Should we raise?
continue
for record_ref, payments in payment_per_key.items():
if not float_is_zero(
sum(payments.mapped("amount")),
Expand Down Expand Up @@ -177,7 +182,8 @@ def _create_account_move(self):
self._check_no_draft_invoice()

partner_to_orders = defaultdict(lambda: self.env["sale.order"].browse(False))
for order in self._get_order_to_invoice():
orders_to_invoice = self._get_order_to_invoice()
for order in orders_to_invoice:
partner_to_orders[order.partner_id] += order

for partner, orders in partner_to_orders.items():
Expand All @@ -186,6 +192,7 @@ def _create_account_move(self):
default_journal_id=self.config_id.journal_id.id
)
orders._create_invoices(final=True)
# Create credit notes for invoiced cancelled orders
orders.invoice_ids.filtered(lambda s: s.state == "draft").action_post()
self._create_bank_statement_line_and_reconcile()
return True
Expand Down
121 changes: 115 additions & 6 deletions pos_sale_order/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class SaleOrder(models.Model):
default=0,
)
is_invoiced = fields.Boolean("Is Invoiced", compute="_compute_is_invoiced")
pos_can_be_reinvoiced = fields.Boolean(
"Can be reinvoiced", compute="_compute_pos_can_be_reinvoiced"
)

@api.model
def _payment_fields(self, order, ui_paymentline):
Expand All @@ -101,6 +104,19 @@ def _compute_is_invoiced(self):
for order in self:
order.is_invoiced = bool(order.account_move)

@api.depends("is_invoiced", "state")
def _compute_pos_can_be_reinvoiced(self):
for order in self:
# We can reinvoice only if the order if session is closed and
# the order is not invoiced or it is invoiced only in the global
# pos invoice
order.pos_can_be_reinvoiced = (
#
order.session_id.state == "closed"
and set(order.invoice_ids.mapped("journal_id"))
== set(order.session_id.config_id.journal_id)
)

@api.depends("amount_total", "payment_ids.amount", "state")
def _compute_pos_payment(self):
for record in self:
Expand Down Expand Up @@ -144,15 +160,17 @@ def _compute_unit_to_deliver(self):
)

def open_pos_payment_wizard(self):
self.ensure_one()
wizard = self.env["pos.payment.wizard"].create_wizard(self)
action = wizard.get_formview_action()
action["target"] = "new"
return action
return self.open_pos_wizard("pos.payment.wizard")

def open_pos_cancel_order_wizard(self):
return self.open_pos_wizard("pos.sale.order.cancel.wizard")

def open_pos_delivery_wizard(self):
return self.open_pos_wizard("pos.delivery.wizard")

def open_pos_wizard(self, model_name):
self.ensure_one()
wizard = self.env["pos.delivery.wizard"].create_wizard(self)
wizard = self.env[model_name].create_wizard(self)
action = wizard.get_formview_action()
action["target"] = "new"
return action
Expand Down Expand Up @@ -312,3 +330,94 @@ def add_payment(self, data):
# skip useless 0 payment that will bloc closing the session
if data.get("amount"):
return super().add_payment(data)

def _prepare_refund_payment(self, amount):
payment_method = self.env["pos.payment.method"].browse(
self._context.get("pos_cancel_payment_method_id")
)

# The refund payment should always happen in the current session

return {
"name": _("Refund"),
"session_id": self.session_id.config_id.current_session_id.id,
"pos_sale_order_id": self.id,
"amount": -amount,
"payment_date": fields.Datetime.now(),
"payment_method_id": payment_method.id,
}

def action_cancel(self):
if self.session_id:
if not self._context.get("disable_pos_cancel_warning"):
return self.open_pos_cancel_order_wizard()

if not self._context.get("pos_cancel_payment_method_id"):
raise UserError(_("No payment method found for refunding this order."))

if self.session_id.state in ("opened", "closing_control"):
# If the session is still open, we need to create the invoice
# and then refund it.
if self.state == "draft":
self.action_confirm()
if not self.invoice_ids:
self._create_invoices(final=True)
self.invoice_ids.action_post()

rv = super().action_cancel()

if self.session_id:
# We need to refund any payment made, the payment method should
# have been set in the wizard
if self.payment_ids:
self.add_payment(self._prepare_refund_payment(self.amount_paid))

if self.session_id.state in ("opened", "closing_control"):
# If the session is still open, we need to refund the invoice
# cancel=True allows for reconciliation
self.invoice_ids._reverse_moves(cancel=True)

elif self.session_id.state == "closed":
# If the session is closed, we need to create a credit note
# in the current session, it will be reconciled at the
# session close.
# sale_order_qty_to_invoice_cancelled will take care of
# creating the credit note on reinvoicing
refund = self._create_invoices(final=True)
refund.action_post()

return rv

def action_reinvoice_pos_order(self):
self.ensure_one()
if not self.pos_can_be_reinvoiced:
raise UserError(
_(
"Can’t generate invoice for this order since it has been "
"already invoiced or its session is still open."
)
)
# We use a little hack here to force a refund creation
for line in self.order_line:
line.qty_to_invoice = -line.qty_invoiced
# Create the refund
refund = self._create_invoices(final=True)
refund.action_post()
# Now we can create the standalone invoice
invoice = self._create_invoices(final=True)
invoice.action_post()
# We need to reconcile these two invoices
(refund | invoice).line_ids.filtered(
lambda ml: ml.account_id.reconcile
or ml.account_id.internal_type == "liquidity"
).reconcile()
# Then we display the new invoice
return {
"name": _("Created Invoice"),
"view_mode": "form",
"res_id": invoice.id,
"res_model": "account.move",
"view_id": False,
"type": "ir.actions.act_window",
"context": self.env.context,
}
17 changes: 9 additions & 8 deletions pos_sale_order/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, models
from odoo import _, models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

@api.depends("order_id.session_id")
def _get_to_invoice_qty(self):
for record in self:
if record.order_id.session_id:
record.qty_to_invoice = record.product_uom_qty - record.qty_invoiced
else:
super(SaleOrderLine, record)._get_to_invoice_qty()
def _prepare_refund_data(self, refund_order):
self.ensure_one()
return {
"name": self.name + _(" REFUND"),
"product_uom_qty": -self.product_uom_qty,
"price_unit": self.price_unit,
"order_id": refund_order.id,
}
1 change: 1 addition & 0 deletions pos_sale_order/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_payment_wizard,pos.payment.wizard,model_pos_payment_wizard,sales_team.group_sale_salesman,1,1,1,1
access_pos_delivery_wizard,pos.delivery.wizard,model_pos_delivery_wizard,sales_team.group_sale_salesman,1,1,1,1
access_pos_delivery_wizard_line,pos.delivery.wizard.line,model_pos_delivery_wizard_line,sales_team.group_sale_salesman,1,1,1,1
access_pos_sale_order_cancel_wizard,pos.sale.order.cancel.wizard,model_pos_sale_order_cancel_wizard,sales_team.group_sale_salesman,1,1,1,1
12 changes: 12 additions & 0 deletions pos_sale_order/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ def setUpClass(cls, chart_template_ref=None):
("company_id", "=", cls.company.id),
]
)
bank = cls.basic_config.payment_method_ids.filtered(
lambda pm: not pm.is_cash_count and not pm.split_transactions
)[:1]
bank.receivable_account_id = cls.env["account.account"].search(
[
("name", "=", "Account Receivable"),
("company_id", "=", cls.company.id),
]
)
# Bank payment method should have a cash journal in pos sale order
bank.cash_journal_id = cls.company_data["default_journal_bank"]

cls.config = cls.basic_config.with_context(**ctx)
cls.open_new_session(cls) # open_new_session is not a class method, hack it
cls.partner_2 = cls.env.ref("base.res_partner_2")
Expand Down
Loading

0 comments on commit f4458dd

Please sign in to comment.