-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from onesteinbv/16.0-purchase-sign
[ADD] Added purchase_sign module
- Loading branch information
Showing
17 changed files
with
432 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
from . import controllers | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2024 Onestein (<http://www.onestein.eu>) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
|
||
{ | ||
"name": "Purchase Sign", | ||
"version": "16.0.1.0.0", | ||
"license": "AGPL-3", | ||
"category": "Purchase", | ||
"author": "Onestein, Odoo Community Association (OCA)", | ||
"website": "https://github.com/OCA/vertical-association", | ||
"depends": ["purchase"], | ||
"data": [ | ||
"report/purchase_order_template.xml", | ||
"templates/purchase_portal_templates.xml", | ||
"views/purchase_view.xml", | ||
"views/res_config_settings_view.xml", | ||
], | ||
"assets": { | ||
"web.assets_tests": ["purchase_sign/static/tests/tours/purchase_signature.js"], | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html | ||
from . import main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
import binascii | ||
|
||
from odoo import _, fields, http | ||
from odoo.exceptions import AccessError, MissingError | ||
from odoo.http import request | ||
|
||
from odoo.addons.portal.controllers.mail import _message_post_helper | ||
from odoo.addons.portal.controllers.portal import CustomerPortal | ||
|
||
|
||
class PortalPurchase(CustomerPortal): | ||
def _purchase_order_get_page_view_values(self, order, access_token, **kwargs): | ||
response = super(PortalPurchase, self)._purchase_order_get_page_view_values( | ||
order=order, access_token=access_token, **kwargs | ||
) | ||
if kwargs.get("message"): | ||
response.update({"message": kwargs.get("message")}) | ||
return response | ||
|
||
@http.route( | ||
["/my/purchase/<int:order_id>/accept"], type="json", auth="public", website=True | ||
) | ||
def portal_purchase_accept( | ||
self, order_id, access_token=None, name=None, signature=None | ||
): | ||
# get from query string if not on json param | ||
access_token = access_token or request.httprequest.args.get("access_token") | ||
try: | ||
order_sudo = self._document_check_access( | ||
"purchase.order", order_id, access_token=access_token | ||
) | ||
except (AccessError, MissingError): | ||
return {"error": _("Invalid order.")} | ||
|
||
if not order_sudo._has_to_be_signed(): | ||
return { | ||
"error": _("The order is not in a state requiring customer signature.") | ||
} | ||
if not signature: | ||
return {"error": _("Signature is missing.")} | ||
|
||
try: | ||
order_sudo.write( | ||
{ | ||
"signed_by": name, | ||
"signed_on": fields.Datetime.now(), | ||
"signature": signature, | ||
} | ||
) | ||
request.env.cr.commit() | ||
except (TypeError, binascii.Error): | ||
return {"error": _("Invalid signature data.")} | ||
order_sudo.button_confirm() | ||
pdf = ( | ||
request.env["ir.actions.report"] | ||
.sudo() | ||
._render_qweb_pdf("purchase.action_report_purchase_order", [order_sudo.id])[ | ||
0 | ||
] | ||
) | ||
|
||
_message_post_helper( | ||
"purchase.order", | ||
order_sudo.id, | ||
_("Order signed by %s", name), | ||
attachments=[("%s.pdf" % order_sudo.name, pdf)], | ||
token=access_token, | ||
) | ||
|
||
query_string = "&message=sign_ok" | ||
return { | ||
"force_refresh": True, | ||
"redirect_url": order_sudo.get_portal_url(query_string=query_string), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html | ||
from . import purchase_order | ||
from . import res_company | ||
from . import res_config_settings |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
from odoo import api, fields, models | ||
|
||
from odoo.addons.purchase.models.purchase import PurchaseOrder as Purchase | ||
|
||
|
||
class PurchaseOrder(models.Model): | ||
_inherit = "purchase.order" | ||
|
||
require_signature = fields.Boolean( | ||
string="Online Signature", | ||
compute="_compute_require_signature", | ||
store=True, | ||
readonly=False, | ||
precompute=True, | ||
states=Purchase.READONLY_STATES, | ||
help="Request a online signature and/or payment to the customer in order to confirm orders automatically.", | ||
) | ||
signature = fields.Image( | ||
copy=False, attachment=True, max_width=1024, max_height=1024 | ||
) | ||
signed_by = fields.Char(copy=False) | ||
signed_on = fields.Datetime(copy=False) | ||
|
||
@api.depends("company_id") | ||
def _compute_require_signature(self): | ||
for order in self: | ||
order.require_signature = order.company_id.purchase_portal_confirmation_sign | ||
|
||
def _has_to_be_signed(self): | ||
return self.state == "sent" and self.require_signature and not self.signature |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
from odoo import fields, models | ||
|
||
|
||
class ResCompany(models.Model): | ||
_inherit = "res.company" | ||
|
||
purchase_portal_confirmation_sign = fields.Boolean( | ||
string="Purchase Online Signature", default=False | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
from odoo import fields, models | ||
|
||
|
||
class ResConfigSettings(models.TransientModel): | ||
_inherit = "res.config.settings" | ||
|
||
purchase_portal_confirmation_sign = fields.Boolean( | ||
related="company_id.purchase_portal_confirmation_sign", | ||
readonly=False, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* `Onestein <https://www.onestein.nl>`__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This module allows to take online signatures from vendors to confirm purchase orders.It adds a global configuration (Online signature) which can be accessed through Purchase->Configuration->Settings and it can further be handled also on each of the purchase orders.When it is enabled, it shows a button 'Accept & Sign' on the portal to the vendors for RFQ which are sent by email to them.Once the vendor accepts and adds their signature, the RFQ gets confirmed into a purchase order |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<template id="report_purchaseorder_document" inherit_id="purchase.report_purchaseorder_document"> | ||
<xpath expr="//p[@t-field='o.notes']" position="before"> | ||
<div t-if="o.signature" class="mt-4 ml64 mr4" name="signature"> | ||
<div class="offset-8"> | ||
<strong>Signature</strong> | ||
</div> | ||
<div class="offset-8"> | ||
<img t-att-src="image_data_uri(o.signature)" style="max-height: 4cm; max-width: 8cm;"/> | ||
</div> | ||
<div class="offset-8 text-center"> | ||
<p t-field="o.signed_by"/> | ||
</div> | ||
</div> | ||
</xpath> | ||
</template> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** @odoo-module **/ | ||
|
||
import tour from "web_tour.tour"; | ||
|
||
// This tour relies on data created on the Python test. | ||
tour.register("purchase_signature", { | ||
test: true, | ||
url: "/my/rfq", | ||
}, | ||
[ | ||
{ | ||
content: "open the test PO", | ||
trigger: "a:containsExact('test PO')", | ||
}, | ||
{ | ||
content: "click sign", | ||
trigger: "a:contains('Sign')", | ||
}, | ||
{ | ||
content: "check submit is enabled", | ||
trigger: ".o_portal_sign_submit:enabled", | ||
run: function () {}, | ||
}, | ||
{ | ||
content: "click select style", | ||
trigger: ".o_web_sign_auto_select_style a", | ||
}, | ||
{ | ||
content: "click style 4", | ||
trigger: ".o_web_sign_auto_font_selection a:eq(3)", | ||
}, | ||
{ | ||
content: "click submit", | ||
trigger: ".o_portal_sign_submit:enabled", | ||
}, | ||
{ | ||
content: "check it's confirmed", | ||
trigger: "#quote_content:contains('Thank You')", | ||
}, { | ||
trigger: "#quote_content", | ||
run: function () { | ||
window.location.href = window.location.origin + "/web"; | ||
}, // Avoid race condition at the end of the tour by returning to the home page. | ||
}, | ||
{ | ||
trigger: "nav", | ||
run: function() {}, | ||
} | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<odoo> | ||
|
||
<template id="portal_my_purchase_order" name="Purchase Order Portal Template" | ||
inherit_id="purchase.portal_my_purchase_order"> | ||
<xpath expr="//div[hasclass('o_download_pdf')]" position="before"> | ||
<a t-if="order._has_to_be_signed()" role="button" class="btn btn-primary mb8" | ||
data-bs-toggle="modal" data-bs-target="#modalaccept" href="#" style="width:100%"> | ||
<i class="fa fa-check"/> | ||
Accept & Sign | ||
</a> | ||
</xpath> | ||
|
||
<xpath expr="//div[@id='portal_purchase_content']" position="before"> | ||
<!-- modal relative to the action sign--> | ||
<div role="dialog" class="modal fade" id="modalaccept"> | ||
<div class="modal-dialog" t-if="order._has_to_be_signed()"> | ||
<form id="accept" method="POST" t-att-data-order-id="order.id" t-att-data-token="order.access_token" class="js_accept_json modal-content js_website_submit_form"> | ||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> | ||
<header class="modal-header"> | ||
<h4 class="modal-title">Validate Order</h4> | ||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | ||
</header> | ||
<main class="modal-body" id="sign-dialog"> | ||
<p> | ||
<span>By signing this proposal, I agree to the following terms:</span> | ||
<ul> | ||
<li> | ||
<span>Accepted on the behalf of:</span> <b t-field="order.partner_id.commercial_partner_id"/> | ||
</li> | ||
<li> | ||
<span>For an amount of:</span> <b data-id="amount_total" t-field="order.amount_total"/> | ||
</li> | ||
<li t-if="order.payment_term_id"> | ||
<span>With payment terms:</span> <b t-field="order.payment_term_id.note"/> | ||
</li> | ||
</ul> | ||
</p> | ||
<t t-call="portal.signature_form"> | ||
<t t-set="call_url" t-value="order.get_portal_url(suffix='/accept')"/> | ||
<t t-set="default_name" t-value="order.partner_id.name"/> | ||
</t> | ||
</main> | ||
</form> | ||
</div> | ||
</div> | ||
|
||
<!-- status messages --> | ||
<div t-if="message == 'sign_ok'" class="alert alert-success alert-dismissible d-print-none" role="status"> | ||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | ||
<strong>Thank You!</strong><br/> | ||
Order has been confirmed. | ||
</div> | ||
</xpath> | ||
|
||
<xpath expr="//div[@id='portal_purchase_content']" position="after"> | ||
<div t-if="order._has_to_be_signed()" class="row justify-content-center text-center d-print-none pt-1 pb-4"> | ||
<div class="col-sm-auto mt8"> | ||
<a role="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalaccept" | ||
href="#"> | ||
<i class="fa fa-check"/> | ||
Accept & Sign | ||
</a> | ||
</div> | ||
<div class="col-sm-auto mt8"> | ||
<a role="button" class="btn btn-secondary" href="#discussion"> | ||
<i class="fa fa-comment"/> | ||
Feedback | ||
</a> | ||
</div> | ||
</div> | ||
</xpath> | ||
</template> | ||
|
||
<template id="purchase_order_portal_content" inherit_id="purchase.purchase_order_portal_content"> | ||
<xpath expr="//section[@id='terms']" position="before"> | ||
<section t-if="order.signature" id="signature" name="Signature"> | ||
<div class="row mt-4" name="signature"> | ||
<div t-attf-class="#{'col-3' if report_type != 'html' else 'col-sm-7 col-md-4'} ms-auto text-center"> | ||
<h5>Signature</h5> | ||
<img t-att-src="image_data_uri(order.signature)" style="max-height: 6rem; max-width: 100%;"/> | ||
<p t-field="order.signed_by"/> | ||
</div> | ||
</div> | ||
</section> | ||
</xpath> | ||
</template> | ||
|
||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html | ||
from . import test_purchase_sign |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# Copyright 2024 Onestein | ||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
from odoo.tests import tagged | ||
|
||
from odoo.addons.base.tests.common import HttpCaseWithUserPortal | ||
|
||
|
||
@tagged("post_install", "-at_install") | ||
class TestPurchaseSign(HttpCaseWithUserPortal): | ||
def test_01_portal_purchase_signature_tour(self): | ||
"""The goal of this test is to make sure the portal user can sign PO.""" | ||
self.user_portal.company_id.purchase_portal_confirmation_sign = True | ||
portal_user_partner = self.partner_portal | ||
# create a PO to be signed | ||
purchase_order = self.env["purchase.order"].create( | ||
{ | ||
"name": "test PO", | ||
"partner_id": portal_user_partner.id, | ||
"state": "sent", | ||
} | ||
) | ||
self.env["purchase.order.line"].create( | ||
{ | ||
"order_id": purchase_order.id, | ||
"product_id": self.env["product.product"] | ||
.create({"name": "A product"}) | ||
.id, | ||
} | ||
) | ||
|
||
# must be sent to the user so he can see it | ||
email_act = purchase_order.action_rfq_send() | ||
email_ctx = email_act.get("context", {}) | ||
purchase_order.with_context(**email_ctx).message_post_with_template( | ||
email_ctx.get("default_template_id") | ||
) | ||
|
||
self.start_tour("/", "purchase_signature", login="portal") | ||
|
||
def test_02_purchase_has_to_be_signed(self): | ||
"""The goal of this test is to check whether PO needs signature if Online signature is turned off for the company.""" | ||
self.user_portal.company_id.purchase_portal_confirmation_sign = False | ||
portal_user_partner = self.partner_portal | ||
purchase_order = self.env["purchase.order"].create( | ||
{ | ||
"name": "test PO", | ||
"partner_id": portal_user_partner.id, | ||
"state": "sent", | ||
} | ||
) | ||
self.env["purchase.order.line"].create( | ||
{ | ||
"order_id": purchase_order.id, | ||
"product_id": self.env["product.product"] | ||
.create({"name": "A product"}) | ||
.id, | ||
} | ||
) | ||
self.assertFalse(purchase_order._has_to_be_signed()) |
Oops, something went wrong.