Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions website_sale_checkout_skip_payment_quotation/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

======================================================
Website Sale Checkout Skip Payment - Keep as Quotation
======================================================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:54e6991977b71c755358a192904f67116f3052f0b94266f10ef2a6ef4ce86503
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fe--commerce-lightgray.png?logo=github
:target: https://github.com/OCA/e-commerce/tree/19.0/website_sale_checkout_skip_payment_quotation
:alt: OCA/e-commerce
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/e-commerce-19-0/e-commerce-19-0-website_sale_checkout_skip_payment_quotation
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/e-commerce&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module extends *Website Sale Checkout Skip Payment* so that, when a
logged-in customer with the *Skip Website Checkout Payment* flag
completes the website checkout:

- the order is **not** automatically confirmed and stays in *Quotation*
(draft) state, waiting for a salesperson to review it;
- a dedicated email template (*Sales: Quotation Sent (Skip Payment)*) is
sent to the customer to acknowledge the request, instead of the
standard sale order confirmation email;
- the order is flagged so the website cart resolver will **not**
resurrect it as the customer's active cart on subsequent visits. The
customer starts from an empty cart on their next visit.

This is useful for B2B scenarios where every order has to be reviewed by
a salesperson before it is confirmed, while still letting the customer
finalise the checkout flow without going through a payment step.

**Table of contents**

.. contents::
:local:

Usage
=====

To use this module, you need to:

1. Configure *Website Sale Checkout Skip Payment* as documented in that
module (enable *Checkout Skip Payment* on the website and tick *Skip
Website Checkout Payment* on the customer).
2. Optionally adjust the email content in *Settings > Technical > Email
Templates > Sales: Quotation Sent (Skip Payment)*.
3. Perform a checkout from the website with that customer.

Expected outcome:

- The order is created in *Sales > Quotations* in **Quotation** (draft)
state. No sales order is confirmed and no stock is reserved.
- The customer receives the *Sales: Quotation Sent (Skip Payment)*
email.
- The customer's cart is emptied; visiting the shop again starts a new
cart instead of reopening the just-submitted quotation.
- The quotation is flagged with *Skip-Payment Quotation* so a
salesperson can identify orders that originated from this flow before
confirming them manually.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/e-commerce/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/e-commerce/issues/new?body=module:%20website_sale_checkout_skip_payment_quotation%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* ForgeFlow

Contributors
------------

- `ForgeFlow <https://www.forgeflow.com>`__:

- Jasmin Solanki <jasmin.solanki@forgeflow.com>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/e-commerce <https://github.com/OCA/e-commerce/tree/19.0/website_sale_checkout_skip_payment_quotation>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions website_sale_checkout_skip_payment_quotation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import controllers
from . import models
20 changes: 20 additions & 0 deletions website_sale_checkout_skip_payment_quotation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2026 ForgeFlow S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{
"name": "Website Sale Checkout Skip Payment - Keep as Quotation",
"summary": "Skip payment in checkout and keep the order as a quotation, "
"notifying the customer with a dedicated email template.",
"version": "19.0.1.0.0",
"category": "Website",
"website": "https://github.com/OCA/e-commerce",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"license": "LGPL-3",
"application": False,
"installable": True,
"depends": ["website_sale_checkout_skip_payment"],
"data": [
"data/mail_template_data.xml",
"views/website_sale_template.xml",
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
55 changes: 55 additions & 0 deletions website_sale_checkout_skip_payment_quotation/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2026 ForgeFlow S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import logging

from odoo import http
from odoo.http import request

from odoo.addons.website_sale_checkout_skip_payment.controllers.main import (
CheckoutSkipPaymentWebsite,
)

_logger = logging.getLogger(__name__)


class CheckoutSkipPaymentQuotation(CheckoutSkipPaymentWebsite):
@http.route()
def shop_payment_confirmation(self, **post):
"""When skipping payment, keep the order as a quotation (do not confirm)
and notify the customer with the dedicated quotation email template.
"""
order_id = request.session.get("sale_last_order_id")
if not request.website.checkout_skip_payment or not order_id:
return super().shop_payment_confirmation(**post)
order = request.env["sale.order"].sudo().browse(order_id)
template = request.env.ref(
"website_sale_checkout_skip_payment_quotation."
"mail_template_sale_quotation_skip_payment",
raise_if_not_found=False,
)
if not template:
_logger.warning(
"Quotation skip-payment mail template not found; "
"falling back to parent behaviour."
)
return super().shop_payment_confirmation(**post)
try:
# Note: we deliberately do NOT pass ``mark_so_as_sent=True`` here so the
# order stays in ``draft`` (Quotation) state. The parent module passes
# that context flag, which would move the state to ``sent``.
order._send_order_notification_mail(template)
# Flag the order so the website cart resolver does not resurrect it
# as an abandoned cart on the customer's next visit. See
# ``website._get_and_cache_current_cart`` override in this module.
order.is_skip_payment_quotation = True
except Exception:
_logger.exception(
"Failed to send quotation confirmation email for order %s", order.name
)
return request.render(
"website_sale_checkout_skip_payment.confirmation_order_error"
)
request.website.sale_reset()
values = self._prepare_shop_payment_confirmation_values(order)
return request.render("website_sale.confirmation", values)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2026 ForgeFlow S.L.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<odoo noupdate="1">
<record id="mail_template_sale_quotation_skip_payment" model="mail.template">
<field name="name">Sales: Quotation Sent (Skip Payment)</field>
<field name="model_id" ref="sale.model_sale_order" />
<field
name="subject"
>{{ object.company_id.name }} Quotation (Ref {{ object.name or 'n/a' }})</field>
<field
name="email_from"
>{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
<field name="partner_to" eval="False" />
<field name="use_default_to" eval="True" />
<field
name="description"
>Sent to website customers after a skip-payment checkout. The order remains a quotation.</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 12px;">
Hello
<t t-out="object.partner_id.name or ''">Customer</t>,
<br />
<br />
Thank you for your interest. We have received your request
<span
style="font-weight:bold;"
t-out="object.name or ''"
>S00049</span>
for a total of
<span
style="font-weight:bold;"
t-out="format_amount(object.amount_total, object.currency_id) or ''"
>$ 10.00</span>.
<br />
<br />
Your quotation is being reviewed by our team and you will receive
a confirmation as soon as it is processed.
<br />
<br />
You can review the details of your quotation at any time using the link below:
<br />
<br />
<a
t-att-href="object.get_portal_url()"
style="background-color:#875A7B; padding: 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;"
>View your quotation</a>
<br />
<br />
If you have any question, simply reply to this email.
<br />
<br />
Best regards,
<br />
<t
t-out="object.user_id.name or object.company_id.name or ''"
>Sales Team</t>
</p>
</div>
</field>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True" />
</record>
</odoo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import sale_order
from . import website
19 changes: 19 additions & 0 deletions website_sale_checkout_skip_payment_quotation/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2026 ForgeFlow S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import fields, models


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

is_skip_payment_quotation = fields.Boolean(
# Set on orders that completed the website checkout via the
# skip-payment flow. These quotations must not be resurrected as the
# customer's cart on subsequent visits, otherwise the website would
# keep showing the items the customer already "ordered".
string="Skip-Payment Quotation",
copy=False,
readonly=True,
index=True,
)
18 changes: 18 additions & 0 deletions website_sale_checkout_skip_payment_quotation/models/website.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2026 ForgeFlow S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import models


class Website(models.Model):
_inherit = "website"

def _get_and_cache_current_cart(self):
sale_order_sudo = super()._get_and_cache_current_cart()
# Skip-payment quotations have been "handed off" to the salesperson;
# they must not be resurrected as the active cart, even though they
# remain in draft state.
if sale_order_sudo and sale_order_sudo.is_skip_payment_quotation:
self.sale_reset()
return self.env["sale.order"].sudo()
return sale_order_sudo
3 changes: 3 additions & 0 deletions website_sale_checkout_skip_payment_quotation/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [ForgeFlow](https://www.forgeflow.com):
- Jasmin Solanki \<<jasmin.solanki@forgeflow.com>\>
16 changes: 16 additions & 0 deletions website_sale_checkout_skip_payment_quotation/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
This module extends *Website Sale Checkout Skip Payment* so that, when a
logged-in customer with the *Skip Website Checkout Payment* flag completes
the website checkout:

- the order is **not** automatically confirmed and stays in *Quotation*
(draft) state, waiting for a salesperson to review it;
- a dedicated email template (*Sales: Quotation Sent (Skip Payment)*) is
sent to the customer to acknowledge the request, instead of the standard
sale order confirmation email;
- the order is flagged so the website cart resolver will **not** resurrect
it as the customer's active cart on subsequent visits. The customer
starts from an empty cart on their next visit.

This is useful for B2B scenarios where every order has to be reviewed by
a salesperson before it is confirmed, while still letting the customer
finalise the checkout flow without going through a payment step.
19 changes: 19 additions & 0 deletions website_sale_checkout_skip_payment_quotation/readme/USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
To use this module, you need to:

1. Configure *Website Sale Checkout Skip Payment* as documented in that
module (enable *Checkout Skip Payment* on the website and tick
*Skip Website Checkout Payment* on the customer).
2. Optionally adjust the email content in *Settings \> Technical \>
Email Templates \> Sales: Quotation Sent (Skip Payment)*.
3. Perform a checkout from the website with that customer.

Expected outcome:

- The order is created in *Sales \> Quotations* in **Quotation** (draft)
state. No sales order is confirmed and no stock is reserved.
- The customer receives the *Sales: Quotation Sent (Skip Payment)* email.
- The customer's cart is emptied; visiting the shop again starts a new
cart instead of reopening the just-submitted quotation.
- The quotation is flagged with *Skip-Payment Quotation* so a salesperson
can identify orders that originated from this flow before confirming
them manually.
Loading
Loading