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
12 changes: 11 additions & 1 deletion sign_oca/controllers/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from werkzeug.wrappers import Response

from odoo import http
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
Expand All @@ -12,7 +14,15 @@ def get_sign_resources(self, ext):
bundle = "sign_oca.sign_assets"
files, _ = request.env["ir.qweb"]._get_asset_content(bundle)
asset = AssetsBundle(bundle, files)
mock_attachment = getattr(asset, ext)()
try:
mock_attachment = getattr(asset, ext)()
except Exception:
# Bundle has no assets for this type (e.g. no JS in a CSS-only bundle)
content_type = "application/javascript" if ext == "js" else "text/css"
return Response("", content_type=content_type, status=200)
if not mock_attachment:
content_type = "application/javascript" if ext == "js" else "text/css"
return Response("", content_type=content_type, status=200)
if isinstance(
mock_attachment, list
): # suppose that CSS asset will not required to be split in pages
Expand Down
21 changes: 20 additions & 1 deletion sign_oca/models/sign_oca_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ def action_send_signed_request(self):
]
)
)
# Set a proper filename for the PDF attachment
doc_name = self.template_id.name or self.name or "signed_document"
pdf_filename = doc_name + ".pdf"
for att in attachments:
if att.name != pdf_filename:
att.sudo().write({"name": pdf_filename})
# The message will not be linked to the record because we do not want
# it happen.
self.env["mail.thread"].message_notify(
Expand Down Expand Up @@ -398,11 +404,20 @@ def _onchange_role_id(self):
def get_info(self, access_token=False):
self.ensure_one()
self._set_action_log("view", access_token=access_token)
# Use sudo to read to_sign to avoid ACL issues when computed fields
# traverse signer_ids in the public user context.
# For logged-in users, the compute still correctly matches partner_id.
to_sign = self.sudo().request_id.to_sign
# For public/anonymous users, to_sign is always False because the
# public user's partner won't match any signer. Fall back to checking
# this specific signer's state directly.
if not to_sign and not self.signed_on and self.request_id.state == "0_sent":
to_sign = True
return {
"role_id": self.role_id.id if not self.signed_on else False,
"name": self.request_id.template_id.name,
"items": self.request_id.signatory_data,
"to_sign": self.request_id.to_sign,
"to_sign": to_sign,
"ask_location": self.request_id.ask_location,
"partner": {
"id": self.partner_id.id,
Expand Down Expand Up @@ -565,6 +580,10 @@ def _get_pdf_page_signature(self, item, box):
new_pdf = PdfFileReader(packet)
return new_pdf.getPage(0)

def _get_pdf_page_date(self, item, box):
"""Render date field as text in the PDF."""
return self._get_pdf_page_text(item, box)

def _get_pdf_page(self, item, box):
return getattr(self, "_get_pdf_page_%s" % item["field_type"])(item, box)

Expand Down
2 changes: 2 additions & 0 deletions sign_oca/security/ir.model.access.csv
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ edit_sign_field_admin,edit_sign_field_admin,model_sign_oca_field,sign_oca_group_
edit_sign_request_base_user,edit_sign_request,model_sign_oca_request,base.group_user,1,0,0,0
edit_sign_request,edit_sign_request,model_sign_oca_request,sign_oca_group_user,1,1,1,0
edit_sign_request_admin,edit_sign_request_admin,model_sign_oca_request,sign_oca_group_admin,1,1,1,1
access_sign_request_public,access_sign_request_public,model_sign_oca_request,base.group_public,1,0,0,0
edit_sign_request_signer_base_user,edit_sign_request_signer_base_user,model_sign_oca_request_signer,base.group_user,1,0,0,0
edit_sign_request_signer,edit_sign_request_signer,model_sign_oca_request_signer,sign_oca_group_user,1,1,1,1
access_sign_request_signer_public,access_sign_request_signer_public,model_sign_oca_request_signer,base.group_public,1,0,0,0
edit_sign_generate,edit_sign_generate,model_sign_oca_template_generate,sign_oca_group_user,1,1,1,1
edit_sign_generate_signer,edit_sign_generate_signer,model_sign_oca_template_generate_signer,sign_oca_group_user,1,1,1,1
edit_sign_generate_multi,edit_sign_generate_multi,model_sign_oca_template_generate_multi,sign_oca_group_user,1,1,1,1
Expand Down
21 changes: 21 additions & 0 deletions sign_oca/security/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,25 @@
<field name="perm_write" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
<!-- Public user record rules (access controlled via token at controller level) -->
<record id="sign_oca_request_rule_public_read" model="ir.rule">
<field name="name">Sign Request public: token-based read</field>
<field name="model_id" ref="model_sign_oca_request" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]" />
<field name="perm_read" eval="1" />
<field name="perm_create" eval="0" />
<field name="perm_write" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
<record id="sign_oca_request_signer_rule_public_read" model="ir.rule">
<field name="name">Sign Request Signer public: token-based read</field>
<field name="model_id" ref="model_sign_oca_request_signer" />
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]" />
<field name="perm_read" eval="1" />
<field name="perm_create" eval="0" />
<field name="perm_write" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
</odoo>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default class SignOcaPdfCommon extends Component {
iframeReject = reject;
});
this.items = {};
this._assetsInjected = false;
this._initialFieldsDone = false;
onWillUnmount(() => {
clearTimeout(this.reviewFieldsTimeout);
});
Expand Down Expand Up @@ -62,12 +64,16 @@ export default class SignOcaPdfCommon extends Component {
}
}
reviewFields() {
if (
this.iframe.el.contentDocument.getElementsByClassName("o_sign_oca_ready")
.length === 0
) {
this.postIframeFields();
}
// Check each individual item — pdfjs re-renders pages on scroll,
// which destroys field overlays. Re-inject any missing ones.
$.each(this.info.items, (key) => {
var item = this.info.items[key];
var el = this.items[item.id];
// Check if the element is still attached to the DOM
if (!el || !el.isConnected) {
this.postIframeField(item);
}
});
this.reviewFieldsTimeout = setTimeout(this.reviewFields.bind(this), 1000);
}
postIframeFields() {
Expand All @@ -81,37 +87,62 @@ export default class SignOcaPdfCommon extends Component {
},
true
);
var iframeCss = document.createElement("link");
iframeCss.setAttribute("rel", "stylesheet");
iframeCss.setAttribute("href", "/sign_oca/get_assets.css");

var iframeJs = document.createElement("script");
iframeJs.setAttribute("type", "text/javascript");
iframeJs.setAttribute("src", "/sign_oca/get_assets.js");
this.iframe.el.contentDocument
.getElementsByTagName("head")[0]
.append(iframeCss);
this.iframe.el.contentDocument.getElementsByTagName("head")[0].append(iframeJs);
// Only inject CSS once
if (!this._assetsInjected) {
var iframeCss = document.createElement("link");
iframeCss.setAttribute("rel", "stylesheet");
iframeCss.setAttribute("href", "/sign_oca/get_assets.css");
this.iframe.el.contentDocument
.getElementsByTagName("head")[0]
.append(iframeCss);
// Inject critical CSS inline to avoid stale SCSS bundle cache issues
var inlineStyle = document.createElement("style");
inlineStyle.textContent = [
".o_sign_oca_field {",
" z-index: 100 !important;",
" position: absolute;",
" cursor: pointer;",
"}",
".textLayer {",
" pointer-events: none !important;",
"}",
".annotationLayer {",
" pointer-events: none !important;",
"}",
].join("\n");
this.iframe.el.contentDocument
.getElementsByTagName("head")[0]
.append(inlineStyle);
this._assetsInjected = true;
}
$.each(this.info.items, (key) => {
this.postIframeField(this.info.items[key]);
});
$(this.iframe.el.contentDocument.getElementsByClassName("page")[0]).append(
$("<div class='o_sign_oca_ready'/>")
);

$(this.iframe.el.contentDocument.getElementById("viewer")).addClass(
"sign_oca_ready"
);
if (!this._initialFieldsDone) {
$(this.iframe.el.contentDocument.getElementsByClassName("page")[0]).append(
$("<div class='o_sign_oca_ready'/>")
);
$(this.iframe.el.contentDocument.getElementById("viewer")).addClass(
"sign_oca_ready"
);
this._initialFieldsDone = true;
}
this.iframeLoaded.resolve();
}
postIframeField(item) {
if (this.items[item.id]) {
this.items[item.id].remove();
// Only remove if still in the DOM
if (this.items[item.id].isConnected) {
this.items[item.id].remove();
}
}
var page =
this.iframe.el.contentDocument.getElementsByClassName("page")[
item.page - 1
];
if (!page) {
return $();
}
var signatureItem = $(
renderToString(this.field_template, {
...item,
Expand Down
4 changes: 3 additions & 1 deletion sign_oca/static/src/elements/signature.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const signatureSignOca = {
.filter((i) => i.tabindex > item.tabindex)
.sort((a, b) => a.tabindex - b.tabindex);
if (next_items.length > 0) {
parent.items[next_items[0].id].dispatchEvent(new Event("focus_signature"));
if (parent.items[next_items[0].id]) {
parent.items[next_items[0].id].dispatchEvent(new Event("focus_signature"));
}
}
},
generate: function (parent, item, signatureItem) {
Expand Down
2 changes: 2 additions & 0 deletions sign_oca/static/src/scss/sign.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.o_sign_oca_field {
background-color: rgba(0, 128, 128, 0.3);
z-index: 10;
cursor: pointer;
&.sign_oca_field_required {
background-color: rgba(255, 113, 113, 0.5);
}
Expand Down
Loading