[REMOVE]REMOVE digital sig

This commit is contained in:
zainab8585 2024-10-02 11:41:14 +02:00
parent 4125851923
commit a4a4cd4fd7
62 changed files with 0 additions and 29713 deletions

View File

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import wizard

View File

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': 'Sign',
'version': '1.0',
'category':'Odex25-Sales/Odex25-Sales',
'sequence': 105,
'summary': "Send documents to sign online and handle filled copies",
'description': """
Sign and complete your documents easily. Customize your documents with text and signature fields and send them to your recipients.\n
Let your customers follow the signature process easily.
""",
'author': "Expert Co. Ltd.",
'website': "http://www.exp-sa.com",
'depends': ['mail', 'attachment_indexation', 'portal', 'sms'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/sign_data.xml',
'views/sign_template_views_mobile.xml',
'wizard/sign_send_request_views.xml',
'wizard/sign_template_share_views.xml',
'wizard/sign_request_send_copy_views.xml',
'views/sign_request_templates.xml',
'views/sign_template_templates.xml',
'views/sign_request_views.xml',
'views/sign_template_views.xml',
'views/sign_log_views.xml',
'views/sign_portal_templates.xml',
'views/res_users_views.xml',
'views/res_partner_views.xml',
'views/sign_pdf_iframe_templates.xml',
'report/sign_log_reports.xml',
],
'qweb': ['static/src/xml/*.xml'],
'demo': [
'data/sign_demo.xml',
],
'application': True,
'installable': True,
}

View File

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import main
from . import portal

View File

@ -1,342 +0,0 @@
# -*- coding: utf-8 -*-
import base64
import io
import logging
import mimetypes
import re
import werkzeug
from PyPDF2 import PdfFileReader
from odoo import http, _
from odoo.http import request
from odoo.addons.web.controllers.main import content_disposition
from odoo.addons.iap.tools import iap_tools
_logger = logging.getLogger()
class Sign(http.Controller):
def get_document_qweb_context(self, id, token):
sign_request = http.request.env['sign.request'].sudo().browse(id).exists()
if not sign_request:
if token:
return http.request.render('odex25_sign.deleted_sign_request')
else:
return http.request.not_found()
current_request_item = None
if token:
current_request_item = sign_request.request_item_ids.filtered(lambda r: r.access_token == token)
if not current_request_item and sign_request.access_token != token and http.request.env.user.id != sign_request.create_uid.id:
return http.request.render('odex25_sign.deleted_sign_request')
elif sign_request.create_uid.id != http.request.env.user.id:
return http.request.not_found()
sign_item_types = http.request.env['sign.item.type'].sudo().search_read([])
if current_request_item:
for item_type in sign_item_types:
if item_type['auto_field']:
fields = item_type['auto_field'].split('.')
auto_field = current_request_item.partner_id
for field in fields:
if auto_field and field in auto_field:
auto_field = auto_field[field]
else:
auto_field = ""
break
item_type['auto_field'] = auto_field
if current_request_item.state != 'completed':
""" When signer attempts to sign the request again,
its localisation should be reset.
We prefer having no/approximative (from geoip) information
than having wrong old information (from geoip/browser)
on the signer localisation.
"""
current_request_item.write({
'latitude': request.session['geoip'].get('latitude') if 'geoip' in request.session else 0,
'longitude': request.session['geoip'].get('longitude') if 'geoip' in request.session else 0,
})
item_values = {}
sr_values = http.request.env['sign.request.item.value'].sudo().search([('sign_request_id', '=', sign_request.id), '|', ('sign_request_item_id', '=', current_request_item.id), ('sign_request_item_id.state', '=', 'completed')])
for value in sr_values:
item_values[value.sign_item_id.id] = value.value
Log = request.env['sign.log'].sudo()
vals = Log._prepare_vals_from_item(current_request_item) if current_request_item else Log._prepare_vals_from_request(sign_request)
vals['action'] = 'open'
vals = Log._update_vals_with_http_request(vals)
Log.create(vals)
return {
'sign_request': sign_request,
'current_request_item': current_request_item,
'token': token,
'nbComments': len(sign_request.message_ids.filtered(lambda m: m.message_type == 'comment')),
'isPDF': (sign_request.template_id.attachment_id.mimetype.find('pdf') > -1),
'webimage': re.match('image.*(gif|jpe|jpg|png)', sign_request.template_id.attachment_id.mimetype),
'hasItems': len(sign_request.template_id.sign_item_ids) > 0,
'sign_items': sign_request.template_id.sign_item_ids,
'item_values': item_values,
'role': current_request_item.role_id.id if current_request_item else 0,
'readonly': not (current_request_item and current_request_item.state == 'sent'),
'sign_item_types': sign_item_types,
'sign_item_select_options': sign_request.template_id.sign_item_ids.mapped('option_ids'),
}
# -------------
# HTTP Routes
# -------------
@http.route(["/odex25_sign/document/<int:id>"], type='http', auth='user')
def sign_document_user(self, id, **post):
return self.sign_document_public(id, None)
@http.route(["/odex25_sign/document/mail/<int:id>/<token>"], type='http', auth='public')
def sign_document_from_mail(self, id, token):
sign_request = request.env['sign.request'].sudo().browse(id)
if not sign_request:
return http.request.render('odex25_sign.deleted_sign_request')
current_request_item = sign_request.request_item_ids.filtered(lambda r: r.access_token == token)
current_request_item.access_via_link = True
return werkzeug.redirect('/odex25_sign/document/%s/%s' % (id, token))
@http.route(["/odex25_sign/document/<int:id>/<token>"], type='http', auth='public')
def sign_document_public(self, id, token, **post):
document_context = self.get_document_qweb_context(id, token)
document_context['portal'] = post.get('portal')
if not isinstance(document_context, dict):
return document_context
current_request_item = document_context.get('current_request_item')
if current_request_item and current_request_item.partner_id.lang:
http.request.env.context = dict(http.request.env.context, lang=current_request_item.partner_id.lang)
return http.request.render('odex25_sign.doc_sign', document_context)
@http.route(['/odex25_sign/download/<int:id>/<token>/<download_type>'], type='http', auth='public')
def download_document(self, id, token, download_type, **post):
sign_request = http.request.env['sign.request'].sudo().browse(id).exists()
if not sign_request or sign_request.access_token != token:
return http.request.not_found()
document = None
if download_type == "log":
report_action = http.request.env.ref('odex25_sign.action_sign_request_print_logs').sudo()
pdf_content, __ = report_action._render_qweb_pdf(sign_request.id)
pdfhttpheaders = [
('Content-Type', 'application/pdf'),
('Content-Length', len(pdf_content)),
('Content-Disposition', 'attachment; filename=' + "Certificate.pdf;")
]
return request.make_response(pdf_content, headers=pdfhttpheaders)
elif download_type == "origin":
document = sign_request.template_id.attachment_id.datas
elif download_type == "completed":
document = sign_request.completed_document
if not document: # if the document is completed but the document is encrypted
return http.redirect_with_hash('/odex25_sign/password/%(request_id)s/%(access_token)s' % {'request_id': id, 'access_token': token})
if not document:
# Shouldn't it fall back on 'origin' download type?
return http.redirect_with_hash("/odex25_sign/document/%(request_id)s/%(access_token)s" % {'request_id': id, 'access_token': token})
# Avoid to have file named "test file.pdf (V2)" impossible to open on Windows.
# This line produce: test file (V2).pdf
extension = '.' + sign_request.template_id.attachment_id.mimetype.replace('application/', '').replace(';base64', '')
filename = sign_request.reference.replace(extension, '') + extension
return http.request.make_response(
base64.b64decode(document),
headers = [
('Content-Type', mimetypes.guess_type(filename)[0] or 'application/octet-stream'),
('Content-Disposition', content_disposition(filename))
]
)
@http.route(['/odex25_sign/<link>'], type='http', auth='public')
def share_link(self, link, **post):
template = http.request.env['sign.template'].sudo().search([('share_link', '=', link)], limit=1)
if not template:
return http.request.not_found()
sign_request = http.request.env['sign.request'].with_user(template.create_uid).create({
'template_id': template.id,
'reference': "%(template_name)s-public" % {'template_name': template.attachment_id.name},
'favorited_ids': [(4, template.create_uid.id)],
})
request_item = http.request.env['sign.request.item'].sudo().create({'sign_request_id': sign_request.id, 'role_id': template.sign_item_ids.mapped('responsible_id').id})
sign_request.action_sent()
return http.redirect_with_hash('/odex25_sign/document/%(request_id)s/%(access_token)s' % {'request_id': sign_request.id, 'access_token': request_item.access_token})
@http.route(['/odex25_sign/password/<int:sign_request_id>/<token>'], type='http', auth='public')
def check_password_page(self, sign_request_id, token, **post):
values = http.request.params.copy()
request_item = http.request.env['sign.request.item'].sudo().search([
('sign_request_id', '=', sign_request_id),
('state', '=', 'completed'),
('sign_request_id.access_token', '=', token)], limit=1)
if not request_item:
return http.request.not_found()
if 'password' not in http.request.params:
return http.request.render('odex25_sign.encrypted_ask_password')
password = http.request.params['password']
template_id = request_item.sign_request_id.template_id
old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
if old_pdf.isEncrypted and not old_pdf.decrypt(password):
values['error'] = _("Wrong password")
return http.request.render('odex25_sign.encrypted_ask_password', values)
request_item.sign_request_id.generate_completed_document(password)
request_item.sign_request_id.send_completed_document()
return http.redirect_with_hash('/odex25_sign/document/%(request_id)s/%(access_token)s' % {'request_id': sign_request_id, 'access_token': token})
# -------------
# JSON Routes
# -------------
@http.route(["/odex25_sign/get_document/<int:id>/<token>"], type='json', auth='user')
def get_document(self, id, token):
return http.Response(template='odex25_sign._doc_sign', qcontext=self.get_document_qweb_context(id, token)).render()
@http.route(['/odex25_sign/new_partners'], type='json', auth='user')
def new_partners(self, partners=[]):
ResPartner = http.request.env['res.partner']
pIDs = []
for p in partners:
existing = ResPartner.search([('email', '=', p[1])], limit=1)
pIDs.append(existing.id if existing else ResPartner.create({'name': p[0], 'email': p[1]}).id)
return pIDs
@http.route(['/odex25_sign/get_signature/<int:request_id>/<item_access_token>'], type='json', auth='public')
def sign_get_user_signature(self, request_id, item_access_token, signature_type='signature'):
sign_request_item = http.request.env['sign.request.item'].sudo().search([
('sign_request_id', '=', request_id),
('access_token', '=', item_access_token)
])
if not sign_request_item:
return False
sign_request_user = http.request.env['res.users'].sudo().search([('partner_id', '=', sign_request_item.partner_id.id)], limit=1)
if sign_request_user and signature_type == 'signature':
return sign_request_user.sign_signature
elif sign_request_user and signature_type == 'initial':
return sign_request_user.sign_initials
return False
@http.route(['/odex25_sign/send_public/<int:id>/<token>'], type='json', auth='public')
def make_public_user(self, id, token, name=None, mail=None):
sign_request = http.request.env['sign.request'].sudo().search([('id', '=', id), ('access_token', '=', token)])
if not sign_request or len(sign_request.request_item_ids) != 1 or sign_request.request_item_ids.partner_id:
return False
ResPartner = http.request.env['res.partner'].sudo()
partner = ResPartner.search([('email', '=', mail)], limit=1)
if not partner:
partner = ResPartner.create({'name': name, 'email': mail})
sign_request.request_item_ids[0].write({'partner_id': partner.id})
@http.route([
'/odex25_sign/send-sms/<int:id>/<token>/<phone_number>',
], type='json', auth='public')
def send_sms(self, id, token, phone_number):
request_item = http.request.env['sign.request.item'].sudo().search([('sign_request_id', '=', id), ('access_token', '=', token), ('state', '=', 'sent')], limit=1)
if not request_item:
return False
if request_item.role_id and request_item.role_id.sms_authentification:
request_item.sms_number = phone_number
try:
request_item._send_sms()
except iap_tools.InsufficientCreditError:
_logger.warning('Unable to send SMS: no more credits')
request_item.sign_request_id.activity_schedule(
'mail.mail_activity_data_todo',
note=_("%s couldn't sign the document due to an insufficient credit error.", request_item.partner_id.display_name),
user_id=request_item.sign_request_id.create_uid.id
)
return False
return True
@http.route([
'/odex25_sign/odex25_sign/<int:id>/<token>',
'/odex25_sign/odex25_sign/<int:id>/<token>/<sms_token>'
], type='json', auth='public')
def sign(self, id, token, sms_token=False, signature=None):
request_item = http.request.env['sign.request.item'].sudo().search([('sign_request_id', '=', id), ('access_token', '=', token), ('state', '=', 'sent')], limit=1)
if not request_item:
return False
if request_item.role_id and request_item.role_id.sms_authentification:
if not sms_token:
return {
'sms': True
}
if sms_token != request_item.sms_token:
return False
if sms_token == request_item.sms_token:
request_item.sign_request_id._message_log(body=_('%s validated the signature by SMS with the phone number %s.') % (request_item.partner_id.display_name, request_item.sms_number))
if not request_item.sign(signature):
return False
# mark signature as done in next activity
user_ids = http.request.env['res.users'].search([('partner_id', '=', request_item.partner_id.id)])
sign_users = user_ids.filtered(lambda u: u.has_group('odex25_sign.group_sign_employee'))
for sign_user in sign_users:
request_item.sign_request_id.activity_feedback(['mail.mail_activity_data_todo'], user_id=sign_user.id)
Log = request.env['sign.log'].sudo()
vals = Log._prepare_vals_from_item(request_item)
vals['action'] = 'sign'
vals['token'] = token
vals = Log._update_vals_with_http_request(vals)
Log.create(vals)
request_item.action_completed()
return True
@http.route(['/odex25_sign/password/<int:sign_request_id>'], type='json', auth='public')
def check_password(self, sign_request_id, password=None):
request_item = http.request.env['sign.request.item'].sudo().search([
('sign_request_id', '=', sign_request_id),
('state', '=', 'completed')], limit=1)
if not request_item:
return False
template_id = request_item.sign_request_id.template_id
old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
if old_pdf.isEncrypted and not old_pdf.decrypt(password):
return False
# if the password is correct, we generate document and send it
request_item.sign_request_id.generate_completed_document(password)
request_item.sign_request_id.send_completed_document()
return True
@http.route(['/odex25_sign/encrypted/<int:sign_request_id>'], type='json', auth='public')
def check_encrypted(self, sign_request_id):
request_item = http.request.env['sign.request.item'].sudo().search([('sign_request_id', '=', sign_request_id)], limit=1)
if not request_item:
return False
# we verify that the document is completed by all signor
if request_item.sign_request_id.nb_total != request_item.sign_request_id.nb_closed:
return False
template_id = request_item.sign_request_id.template_id
old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
return True if old_pdf.isEncrypted else False
@http.route(['/odex25_sign/save_location/<int:id>/<token>'], type='json', auth='public')
def save_location(self, id, token, latitude=0, longitude=0):
sign_request_item = http.request.env['sign.request.item'].sudo().search([('sign_request_id', '=', id), ('access_token', '=', token)], limit=1)
sign_request_item.write({'latitude': latitude, 'longitude': longitude})
@http.route("/odex25_sign/render_assets_pdf_iframe", type="json", auth="public")
def render_assets_pdf_iframe(self, **kw):
context = {'debug': kw.get('debug')} if 'debug' in kw else {}
return request.env['ir.ui.view'].sudo()._render_template('odex25_sign.compiled_assets_pdf_iframe', context)

View File

@ -1,128 +0,0 @@
# -*- coding: utf-8 -*-
from operator import itemgetter
from collections import OrderedDict
from odoo import http, _
from odoo.exceptions import MissingError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager, get_records_pager
from odoo.tools import groupby as groupbyelem
from odoo.osv.expression import AND
class CustomerPortal(CustomerPortal):
def _prepare_home_portal_values(self, counters):
values = super(CustomerPortal, self)._prepare_home_portal_values(counters)
if 'sign_count' in counters:
partner_id = request.env.user.partner_id
values['sign_count'] = request.env['sign.request.item'].sudo().search_count([
('partner_id', '=', partner_id.id), ('state', '!=', 'draft')
])
return values
@http.route(['/my/signatures', '/my/signatures/page/<int:page>'], type='http', auth='user', website=True)
def portal_my_signatures(self, page=1, date_begin=None, date_end=None, sortby=None, search=None, search_in='all',
groupby='none', filterby=None, **kw):
values = self._prepare_portal_layout_values()
partner_id = request.env.user.partner_id
SignRequestItem = request.env['sign.request.item'].sudo()
default_domain = [('partner_id', '=', partner_id.id), ('state', '!=', 'draft')]
searchbar_sortings = {
'new': {'label': _('Newest'), 'order': 'sign_request_id desc'},
'date': {'label': _('Signing Date'), 'order': 'signing_date desc'},
}
searchbar_filters = {
'all': {'label': _('All'), 'domain': default_domain},
'tosign': {'label': _('To sign'), 'domain': AND([default_domain, [('state', '=', 'sent'),
('sign_request_id.state', '=', 'sent')]])},
'completed': {'label': _('Completed'), 'domain': AND([default_domain, [('state', '=', 'completed')]])},
'signed': {'label': _('Fully Signed'),
'domain': AND([default_domain, [('sign_request_id.state', '=', 'signed')]])},
}
searchbar_inputs = {
'all': {'input': 'all', 'label': _('Search <span class="nolabel"> (in Document)</span>')},
}
searchbar_groupby = {
'none': {'input': 'none', 'label': _('None')},
'state': {'input': 'state', 'label': _('Status')},
}
# default sortby order
if not sortby:
sortby = 'new'
sort_order = searchbar_sortings[sortby]['order']
# default filter by value
if not filterby:
filterby = 'all'
domain = searchbar_filters[filterby]['domain']
if date_begin and date_end:
domain = AND([domain, [('signing_date', '>', date_begin), ('signing_date', '<=', date_end)]])
# search only the document name
if search and search_in:
domain = AND([domain, [('reference', 'ilike', search)]])
pager = portal_pager(
url='/my/signatures',
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby,
'search_in': search_in, 'search': search},
total=SignRequestItem.search_count(domain),
page=page,
step=self._items_per_page
)
# content according to pager and archive selected
if groupby == 'state':
sort_order = 'state, %s' % sort_order
# search the count to display, according to the pager data
sign_requests_items = SignRequestItem.search(domain, order=sort_order, limit=self._items_per_page,
offset=pager['offset'])
request.session['my_signatures_history'] = sign_requests_items.ids[:100]
if groupby == 'state':
grouped_signatures = [SignRequestItem.concat(*g)
for k, g in groupbyelem(sign_requests_items, itemgetter('state'))]
else:
grouped_signatures = [sign_requests_items]
values.update({
'date': date_begin,
'grouped_signatures': grouped_signatures,
'page_name': 'signatures',
'pager': pager,
'default_url': '/my/signatures',
'searchbar_sortings': searchbar_sortings,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'searchbar_groupby': searchbar_groupby,
'searchbar_inputs': searchbar_inputs,
'search_in': search_in,
'groupby': groupby,
'sortby': sortby,
'filterby': filterby,
})
return request.render('odex25_sign.sign_portal_my_requests', values)
@http.route(['/my/signature/<int:item_id>'], type='http', auth='public', website=True)
def portal_my_signature(self, item_id, access_token=None, **kwargs):
partner_id = request.env.user.partner_id
sign_item_sudo = request.env['sign.request.item'].sudo().browse(item_id)
try:
if sign_item_sudo.state != 'draft' and not sign_item_sudo.partner_id == partner_id:
return request.redirect('/my/')
url = f'/odex25_sign/document/{sign_item_sudo.sign_request_id.id}/{sign_item_sudo.access_token}?portal=1'
values = {
'page_name': 'signature',
'my_sign_item': sign_item_sudo,
'url': url
}
values = self._get_page_view_values(sign_item_sudo, sign_item_sudo.access_token, values,
'my_signatures_history', False, **kwargs)
return request.render('odex25_sign.sign_portal_my_request', values)
except MissingError:
return request.redirect('/my')

View File

@ -1,216 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="sign_template_mail_request">
<table border="0" cellpadding="0" style="background-color: white; padding: 0px; border-collapse:separate;">
<tr><td valign="top">
<t t-esc="record.create_uid.name"/>
(<a t-att-href="'mailto:%s?subject=%s' % (record.create_uid.email, 'Re: %s' % subject)"
style="color:#428BCA; text-decoration:none;"
target="_blank"><t t-esc="record.create_uid.email"/></a>)
has requested your signature on the document <t t-esc="record.sign_request_id.reference"/>.
</td></tr>
<tr t-if="body"><td valign="top">
<div style="margin: 16px 0px 16px 0px; padding: 8px 16px 8px 16px; background-color: #F1F1F1;">
<span>
<t t-raw="body"/>
</span>
</div>
</td></tr>
<tr><td valign="top">
<div style="margin:16px auto; text-align:center;">
<a t-att-href="link"
style="padding: 8px 16px 8px 16px; border-radius: 3px; background-color:#875A7B; text-align:center; text-decoration:none; color: #FFFFFF;">
Sign document
</a>
</div>
</td></tr>
<tr><td valign="top">
<div style="opacity: 0.7;">
<strong>Warning</strong> do not forward this email to other people!<br/>
They will be able to access this document and sign it as yourself.<br/>
<span>Your IP address and localization are associated to your signature to ensure traceability.</span>
</div>
</td></tr>
</table>
</template>
<template id="sign_template_mail_follower">
<table border="0" cellpadding="0" style="background-color: white; padding: 0px; border-collapse:separate;">
<tr><td valign="top">
<t t-esc="record.create_uid.name"/>
(<a t-att-href="'mailto:%s?subject=%s' % (record.create_uid.email, 'Re: %s' % subject)"
style="color:#428BCA; text-decoration:none;"
target="_blank"><t t-esc="record.create_uid.email"/>
</a>) has added you as a follower of the document <t t-esc="record.reference"/>.
</td></tr>
<tr t-if="body"><td valign="top">
<div style="margin: 16px 0px 16px 0px;">
<span style="padding: 16px 8px 16px 8px; border-radius: 3px;">
<t t-raw="body"/>
</span>
</div>
</td></tr>
<tr><td valign="top">
<div style="margin:16px 0px 16px 0px;">
<a t-att-href="link"
style="padding: 8px 16px 8px 16px; border-radius: 3px; background-color:#875A7B; text-align:center; text-decoration:none; color: #FFFFFF;">
View signature request
</a>
</div>
</td></tr>
<tr><td valign="top">
<div style="opacity: 0.7;">
You will be notified once the document has been signed by everyone involved.
</div>
</td></tr>
</table>
</template>
<template id="sign_template_mail_completed">
<table border="0" cellpadding="0" style="background-color: white; padding: 0px; border-collapse:separate;">
<tr><td valign="top">
The document <t t-esc="record.reference"/> has been completed and signed by everyone.
</td></tr>
<tr t-if="body"><td valign="top">
<div style="margin: 16px 0px 16px 0px;">
<span style="padding: 16px 8px 16px 8px; border-radius: 3px;">
<t t-raw="body"/>
</span>
</div>
</td></tr>
<tr><td valign="top">
<div style="margin:16px auto; text-align:center">
<a t-att-href="link"
style="padding: 8px 16px 8px 16px; border-radius: 3px; background-color:#875A7B; text-align:center; text-decoration:none; color: #FFFFFF;">
View document
</a>
</div>
</td></tr>
<tr><td valign="top">
<div style="opacity: 0.7;">
<strong>Warning</strong> do not forward this email to other people!<br/>
They will be able to access this document and sign it as yourself.<br/>
<span>Your IP address and localization are associated to your signature to ensure traceability.</span>
</div>
</td></tr>
</table>
</template>
<!-- Tour template -->
<record id="attachment_sign_tour" model="ir.attachment">
<field name="name">Sample Contract.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/sample_contract.pdf"/>
<field name="mimetype">application/pdf</field>
</record>
<record id="template_sign_tour" model="sign.template">
<field name="attachment_id" ref="attachment_sign_tour"/>
<field name="share_link">tour-template</field>
<field name="active" eval="False"/>
</record>
<!-- Item types -->
<record model="sign.item.type" id="sign_item_type_signature">
<field name="name">Signature</field>
<field name="item_type">signature</field>
<field name="tip">sign it</field>
<field name="placeholder">Signature</field>
<field name="default_width" type="float">0.200</field>
<field name="default_height" type="float">0.050</field>
</record>
<record model="sign.item.type" id="sign_item_type_initial">
<field name="name">Initials</field>
<field name="item_type">initial</field>
<field name="tip">mark it</field>
<field name="placeholder">Initials</field>
<field name="default_width" type="float">0.085</field>
<field name="default_height" type="float">0.030</field>
</record>
<record model="sign.item.type" id="sign_item_type_name">
<field name="name">Name</field>
<field name="placeholder">Name</field>
<field name="auto_field">name</field>
</record>
<record model="sign.item.type" id="sign_item_type_email">
<field name="name">Email</field>
<field name="placeholder">Email</field>
<field name="auto_field">email</field>
</record>
<record model="sign.item.type" id="sign_item_type_phone">
<field name="name">Phone</field>
<field name="placeholder">Phone</field>
<field name="auto_field">phone</field>
</record>
<record model="sign.item.type" id="sign_item_type_company">
<field name="name">Company</field>
<field name="placeholder">Company</field>
<field name="auto_field">company_id.name</field>
</record>
<record model="sign.item.type" id="sign_item_type_text">
<field name="name">Text</field> <!-- default: type 'text', tip 'fill in', empty placeholder, default_width and default_height, no auto_field -->
<field name="placeholder">Text</field>
</record>
<record model="sign.item.type" id="sign_item_type_multiline_text">
<field name="name">Multiline Text</field>
<field name="placeholder">Multiline Text</field>
<field name="item_type">textarea</field>
<field name="default_width" type="float">0.300</field>
<field name="default_height" type="float">0.0500</field>
</record>
<record model="sign.item.type" id="sign_item_type_checkbox">
<field name="name">Checkbox</field>
<field name="placeholder">&#9745;</field>
<field name="item_type">checkbox</field>
<field name="default_width" type="float">0.028</field>
<field name="default_height" type="float">0.025</field>
</record>
<record model="sign.item.type" id="sign_item_type_selection">
<field name="name">Selection</field>
<field name="item_type">selection</field>
<field name="placeholder">Selection</field>
<field name="tip">Select an option</field>
<field name="default_width" type="float">0.300</field>
<field name="default_height" type="float">0.030</field>
</record>
<record model="sign.item.type" id="sign_item_type_date">
<field name="name">Date</field>
<field name="placeholder">Date</field>
</record>
<!-- Item responsibles -->
<record model="sign.item.role" id="sign_item_role_customer">
<field name="name">Customer</field>
</record>
<record model="sign.item.role" id="sign_item_role_company">
<field name="name">Company</field>
</record>
<record model="sign.item.role" id="sign_item_role_employee">
<field name="name">Employee</field>
</record>
<!-- Template tags -->
<record id="sign_template_tag_1" model="sign.template.tag">
<field name="name">HR</field>
<field name="color">2</field>
</record>
<record id="sign_template_tag_2" model="sign.template.tag">
<field name="name">NDA</field>
<field name="color">3</field>
</record>
<record id="sign_template_tag_3" model="sign.template.tag">
<field name="name">Sales</field>
<field name="color">4</field>
</record>
</odoo>

View File

@ -1,440 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="attachment_sign_1" model="ir.attachment">
<field name="name">Employment Contract.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/employment.pdf"/>
<field name="mimetype">application/pdf;base64</field>
</record>
<record id="template_sign_1" model="sign.template">
<field name="attachment_id" ref="attachment_sign_1"/>
<field name="share_link">demo-template-1</field>
<field name="create_uid" ref="base.user_admin"/>
<field name="tag_ids" eval="[(4, ref('odex25_sign.sign_template_tag_1'))]"/>
</record>
<record model="sign.item" id="sign_item_1">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.265</field>
<field name="posY" type="float">0.160</field>
<field name="width" type="float">0.375</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_2">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_date"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.700</field>
<field name="posY" type="float">0.160</field>
<field name="width" type="float">0.170</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_3">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_text"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.190</field>
<field name="posY" type="float">0.185</field>
<field name="width" type="float">0.680</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_4">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_5">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_8">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_signature"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">3</field>
<field name="posX" type="float">0.325</field>
<field name="posY" type="float">0.670</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.050</field>
</record>
<record model="sign.item" id="sign_item_9">
<field name="template_id" ref="template_sign_1"/>
<field name="type_id" ref="sign_item_type_date"/>
<field name="responsible_id" ref="sign_item_role_employee"/>
<field name="page" type="int">3</field>
<field name="posX" type="float">0.655</field>
<field name="posY" type="float">0.695</field>
<field name="width" type="float">0.205</field>
<field name="height" type="float">0.015</field>
</record>
<record id="attachment_sign_2" model="ir.attachment">
<field name="name">Non-Disclosure Agreement.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/nda.pdf"/>
<field name="mimetype">application/pdf</field>
</record>
<record id="template_sign_2" model="sign.template">
<field name="attachment_id" ref="attachment_sign_2"/>
<field name="share_link">demo-template-2</field>
<field name="create_uid" ref="base.user_admin"/>
<field name="favorited_ids" eval="[(4, ref('base.user_admin'))]"/>
<field name="tag_ids" eval="[(4, ref('odex25_sign.sign_template_tag_2')), (4, ref('odex25_sign.sign_template_tag_1'))]"/>
</record>
<record model="sign.item" id="sign_item_10">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.125</field>
<field name="posY" type="float">0.175</field>
<field name="width" type="float">0.140</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_11">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_text"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.355</field>
<field name="posY" type="float">0.175</field>
<field name="width" type="float">0.230</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_12">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_13">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_14">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">3</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_15">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">4</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_16">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_signature"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">5</field>
<field name="posX" type="float">0.215</field>
<field name="posY" type="float">0.365</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.050</field>
</record>
<record model="sign.item" id="sign_item_18">
<field name="template_id" ref="template_sign_2"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">5</field>
<field name="posX" type="float">0.215</field>
<field name="posY" type="float">0.445</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.015</field>
</record>
<record id="attachment_sign_3" model="ir.attachment">
<field name="name">Odoo_Individual_Contributor_License_Agreement.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/cla.pdf"/>
<field name="mimetype">application/pdf;base64</field>
</record>
<record id="template_sign_3" model="sign.template">
<field name="attachment_id" ref="attachment_sign_3"/>
<field name="share_link">demo-template-3</field>
<field name="create_uid" ref="base.user_admin"/>
</record>
<record model="sign.item" id="sign_item_20">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_21">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.290</field>
<field name="posY" type="float">0.600</field>
<field name="width" type="float">0.300</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_22">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_email"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.290</field>
<field name="posY" type="float">0.631</field>
<field name="width" type="float">0.300</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_23">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_text"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.290</field>
<field name="posY" type="float">0.662</field>
<field name="width" type="float">0.300</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_24">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_date"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.290</field>
<field name="posY" type="float">0.693</field>
<field name="width" type="float">0.300</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_25">
<field name="template_id" ref="template_sign_3"/>
<field name="type_id" ref="sign_item_type_signature"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.290</field>
<field name="posY" type="float">0.725</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.050</field>
</record>
<record id="attachment_sign_4" model="ir.attachment">
<field name="name">Real_Estate_Listing_Agreement.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/rela.pdf"/>
<field name="mimetype">application/pdf</field>
</record>
<record id="template_sign_4" model="sign.template">
<field name="attachment_id" ref="attachment_sign_4"/>
<field name="share_link">demo-template-4</field>
<field name="create_uid" ref="base.user_admin"/>
<field name="favorited_ids" eval="[(4, ref('base.user_admin'))]"/>
<field name="tag_ids" eval="[(4, ref('odex25_sign.sign_template_tag_3'))]"/>
</record>
<record model="sign.item" id="sign_item_27">
<field name="template_id" ref="template_sign_4"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.155</field>
<field name="posY" type="float">0.206</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.012</field>
</record>
<record model="sign.item" id="sign_item_29">
<field name="template_id" ref="template_sign_4"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_31">
<field name="template_id" ref="template_sign_4"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.535</field>
<field name="posY" type="float">0.801</field>
<field name="width" type="float">0.180</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_33">
<field name="template_id" ref="template_sign_4"/>
<field name="type_id" ref="sign_item_type_signature"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.565</field>
<field name="posY" type="float">0.820</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.050</field>
</record>
<record id="attachment_sign_5" model="ir.attachment">
<field name="name">YC_Form_SaaS_Agreement.pdf</field>
<field name="datas" type="base64" file="odex25_sign/static/demo/saas.pdf"/>
<field name="mimetype">application/pdf</field>
</record>
<record id="template_sign_5" model="sign.template">
<field name="attachment_id" ref="attachment_sign_5"/>
<field name="share_link">demo-template-5</field>
<field name="create_uid" ref="base.user_demo"/>
<field name="favorited_ids" eval="[(4, ref('base.user_demo'))]"/>
<field name="tag_ids" eval="[(4, ref('odex25_sign.sign_template_tag_3'))]"/>
</record>
<record model="sign.item" id="sign_item_34">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_company"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.210</field>
<field name="posY" type="float">0.125</field>
<field name="width" type="float">0.315</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_35">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_name"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.610</field>
<field name="posY" type="float">0.125</field>
<field name="width" type="float">0.315</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_36">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_email"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.180</field>
<field name="posY" type="float">0.150</field>
<field name="width" type="float">0.345</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_37">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_phone"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.600</field>
<field name="posY" type="float">0.150</field>
<field name="width" type="float">0.325</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_43">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_company"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.570</field>
<field name="posY" type="float">0.655</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.015</field>
</record>
<record model="sign.item" id="sign_item_45">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_signature"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">1</field>
<field name="posX" type="float">0.570</field>
<field name="posY" type="float">0.675</field>
<field name="width" type="float">0.200</field>
<field name="height" type="float">0.050</field>
</record>
<record model="sign.item" id="sign_item_46">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">2</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_47">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">3</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record model="sign.item" id="sign_item_48">
<field name="template_id" ref="template_sign_5"/>
<field name="type_id" ref="sign_item_type_initial"/>
<field name="responsible_id" ref="sign_item_role_customer"/>
<field name="page" type="int">4</field>
<field name="posX" type="float">0.915</field>
<field name="posY" type="float">0.970</field>
<field name="width" type="float">0.085</field>
<field name="height" type="float">0.030</field>
</record>
<record id="base.user_admin" model="res.users">
<field name="sign_signature" type="base64" file="odex25_sign/static/demo/signature.png"/>
</record>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
</record>
<record model="sign.request" id="sign_request_01">
<field name="template_id" ref="template_sign_4"/>
<field name="reference">Your Real Estate Listing Agreement</field>
<field name="state">sent</field>
</record>
<record model="sign.request.item" id="sign_request_item_01">
<field name="partner_id" ref="base.partner_demo_portal"/>
<field name="role_id" ref="sign_item_role_customer"/>
<field name="sign_request_id" ref="sign_request_01"/>
<field name="state">sent</field>
<field name="signer_email">portal@example.com</field>
</record>
<record model="sign.request" id="sign_request_02">
<field name="template_id" ref="template_sign_5"/>
<field name="reference">Your SAAS order</field>
<field name="state">signed</field>
</record>
<record model="sign.request.item" id="sign_request_item_02">
<field name="partner_id" ref="base.partner_demo_portal"/>
<field name="role_id" ref="sign_item_role_customer"/>
<field name="sign_request_id" ref="sign_request_02"/>
<field name="state">completed</field>
<field name="signer_email">portal@example.com</field>
<field name="signing_date" eval="(DateTime.now() - timedelta(days=15))"/>
</record>
</odoo>

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
from . import res_partner
from . import res_users
from . import sign_request
from . import sign_template
from . import sign_log

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import fields, models, _
class ResPartner(models.Model):
_inherit = 'res.partner'
signature_count = fields.Integer(compute='_compute_signature_count', string="# Signatures")
def _compute_signature_count(self):
signature_data = self.env['sign.request.item'].sudo().read_group([('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id'])
signature_data_mapped = dict((data['partner_id'][0], data['partner_id_count']) for data in signature_data)
for partner in self:
partner.signature_count = signature_data_mapped.get(partner.id, 0)
def open_signatures(self):
self.ensure_one()
request_ids = self.env['sign.request.item'].search([('partner_id', '=', self.id)]).mapped('sign_request_id')
return {
'type': 'ir.actions.act_window',
'name': _('Signature(s)'),
'view_mode': 'kanban,tree,form',
'res_model': 'sign.request',
'domain': [('id', 'in', request_ids.ids)],
'context': {
'search_default_reference': self.name,
'search_default_signed': 1,
'search_default_in_progress': 1,
},
}

View File

@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import fields, models
class ResUsers(models.Model):
_inherit = 'res.users'
sign_signature = fields.Binary(string="Digital Signature", groups="base.group_system")
sign_initials = fields.Binary(string="Digitial Initials", groups="base.group_system")

View File

@ -1,152 +0,0 @@
# -*- coding: utf-8 -*-
from hashlib import sha256
from json import dumps
from datetime import datetime
import logging
from odoo import fields, models, api, _
from odoo.exceptions import ValidationError, UserError
from odoo.http import request
_logger = logging.getLogger(__name__)
LOG_FIELDS = ['log_date', 'action', 'partner_id', 'request_state', 'latitude', 'longitude', 'ip',]
class SignLog(models.Model):
_name = 'sign.log'
_order = 'log_date, id'
_description = "Sign requests access history"
# Accessed on ?
log_date = fields.Datetime(default=fields.Datetime.now, required=True)
sign_request_id = fields.Many2one('sign.request', required=True, ondelete="cascade")
sign_request_item_id = fields.Many2one('sign.request.item')
# Accessed as ?
user_id = fields.Many2one('res.users', groups="odex25_sign.group_sign_manager")
partner_id = fields.Many2one('res.partner')
# Accessed from ?
# If defined on request item when signing: take from it
# Else : taken from geoip
latitude = fields.Float(digits=(10, 7), groups="odex25_sign.group_sign_manager")
longitude = fields.Float(digits=(10, 7), groups="odex25_sign.group_sign_manager")
ip = fields.Char("IP address of the visitor", required=True, groups="odex25_sign.group_sign_manager")
log_hash = fields.Char(string="Inalterability Hash", readonly=True, copy=False)
token = fields.Char(string="User token")
# Accessed for ?
action = fields.Selection(
string="Action Performed",
selection=[
('create', 'Creation'),
('open', 'View/Download'),
('save', 'Save'),
('sign', 'Signature'),
], required=True,
)
request_state = fields.Selection([
("sent", "Before Signature"),
("signed", "After Signature"),
("canceled", "Canceled")
], required=True, string="State of the request on action log", groups="odex25_sign.group_sign_manager")
# Not related on purpose :P
def write(self, vals):
raise ValidationError(_("Log history of sign requests cannot be modified !"))
def unlink(self):
raise ValidationError(_("Log history of sign requests cannot be deleted !"))
def create(self, vals):
"""
1/ if action=='create': get initial shasign from template (checksum pdf)
2/ if action == 'sign': search for logs with hash for the same request and use that to compute new hash
"""
vals['log_date'] = datetime.utcnow()
vals['log_hash'] = self._get_or_check_hash(vals)
res = super(SignLog, self).create(vals)
return res
def _get_or_check_hash(self, vals):
""" Returns the hash to write on sign log entries """
if vals['action'] not in ['sign', 'create']:
return False
# When we check the hash, we need to restrict the previous activity to logs created before
domain = [('sign_request_id', '=', vals['sign_request_id']), ('action', 'in', ['create', 'sign'])]
if 'id' in vals:
domain.append(('id', '<', vals['id']))
prev_activity = self.sudo().search(domain, limit=1, order='id desc')
# Multiple signers lead to multiple creation actions but for them, the hash of the PDF must be calculated.
previous_hash = ""
if not prev_activity:
sign_request = self.env['sign.request'].browse(vals['sign_request_id'])
body = sign_request.template_id.with_context(bin_size=False).attachment_id.datas
else:
previous_hash = prev_activity.log_hash
body = self._compute_string_to_hash(vals)
hash = sha256((previous_hash + str(body)).encode('utf-8')).hexdigest()
return hash
def _compute_string_to_hash(self, vals):
values = {}
for field in LOG_FIELDS:
values[field] = str(vals[field])
# Values are filtered based on the token
# Signer is signing the document. We save the value of its field. self is an empty recordset.
item_values = self.env['sign.request.item.value'].search([('sign_request_id', '=', vals['sign_request_id'])]).filtered(lambda item: item.sign_request_item_id.access_token == vals['token'])
for item_value in item_values:
values[str(item_value.id)] = str(item_value.value)
return dumps(values, sort_keys=True, ensure_ascii=True, indent=None)
def _check_document_integrity(self):
"""
Check the integrity of a sign request by comparing the logs hash to the computed values.
"""
logs = self.filtered(lambda item: item.action in ['sign', 'create'])
for log in logs:
vals = {key: value[0] if isinstance(value, tuple) else value for key, value in log.read()[0].items()}
hash = self._get_or_check_hash(vals)
if hash != log.log_hash:
# TODO add logs and comments
return False
return True
def _prepare_vals_from_item(self, request_item):
request = request_item.sign_request_id
return dict(
sign_request_item_id=request_item.id,
sign_request_id=request.id,
request_state=request.state,
latitude=request_item.latitude or 0.0,
longitude=request_item.longitude or 0.0,
partner_id=request_item.partner_id.id)
def _prepare_vals_from_request(self, sign_request):
return dict(
sign_request_id=sign_request.id,
request_state=sign_request.state,
)
def _update_vals_with_http_request(self, vals):
vals.update({
'user_id': request.env.user.id if not request.env.user._is_public() else None,
'ip': request.httprequest.remote_addr,
})
if not vals.get('partner_id', False):
vals.update({
'partner_id': request.env.user.partner_id.id if not request.env.user._is_public() else None
})
# NOTE: during signing, this method is always called after the log is generated based on the
# request item. This means that if the signer accepted the browser geolocation request, the `vals`
# will already contain much more precise coordinates. We should use the GeoIP ones only if the
# browser did not send anything
if 'geoip' in request.session and not (vals.get('latitude') and vals.get('longitude')):
vals.update({
'latitude': request.session['geoip'].get('latitude') or 0.0,
'longitude': request.session['geoip'].get('longitude') or 0.0,
})
return vals

View File

@ -1,735 +0,0 @@
# -*- coding: utf-8 -*-
import base64
import io
import os
import time
import uuid
from PyPDF2 import PdfFileReader, PdfFileWriter
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.rl_config import TTFSearchPath
from reportlab.pdfgen import canvas
from reportlab.platypus import Paragraph
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.pdfbase.pdfmetrics import stringWidth
from werkzeug.urls import url_join
from random import randint
from odoo import api, fields, models, http, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, formataddr, config, get_lang
from odoo.exceptions import UserError
TTFSearchPath.append(os.path.join(config["root_path"], "..", "addons", "web", "static", "src", "fonts", "sign"))
def _fix_image_transparency(image):
""" Modify image transparency to minimize issue of grey bar artefact.
When an image has a transparent pixel zone next to white pixel zone on a
white background, this may cause on some renderer grey line artefacts at
the edge between white and transparent.
This method sets transparent pixel to white transparent pixel which solves
the issue for the most probable case. With this the issue happen for a
black zone on black background but this is less likely to happen.
"""
pixels = image.load()
for x in range(image.size[0]):
for y in range(image.size[1]):
if pixels[x, y] == (0, 0, 0, 0):
pixels[x, y] = (255, 255, 255, 0)
class SignRequest(models.Model):
_name = "sign.request"
_description = "Signature Request"
_rec_name = 'reference'
_inherit = ['mail.thread', 'mail.activity.mixin']
def _default_access_token(self):
return str(uuid.uuid4())
def _expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
template_id = fields.Many2one('sign.template', string="Template", required=True)
reference = fields.Char(required=True, string="Document Name", help="This is how the document will be named in the mail")
access_token = fields.Char('Security Token', required=True, default=_default_access_token, readonly=True)
request_item_ids = fields.One2many('sign.request.item', 'sign_request_id', string="Signers")
state = fields.Selection([
("sent", "Sent"),
("signed", "Fully Signed"),
("canceled", "Canceled")
], default='sent', tracking=True, group_expand='_expand_states')
completed_document = fields.Binary(readonly=True, string="Completed Document", attachment=True)
nb_wait = fields.Integer(string="Sent Requests", compute="_compute_count", store=True)
nb_closed = fields.Integer(string="Completed Signatures", compute="_compute_count", store=True)
nb_total = fields.Integer(string="Requested Signatures", compute="_compute_count", store=True)
progress = fields.Char(string="Progress", compute="_compute_count", compute_sudo=True)
start_sign = fields.Boolean(string="Signature Started", help="At least one signer has signed the document.", compute="_compute_count", compute_sudo=True)
integrity = fields.Boolean(string="Integrity of the Sign request", compute='_compute_hashes', compute_sudo=True)
active = fields.Boolean(default=True, string="Active")
favorited_ids = fields.Many2many('res.users', string="Favorite of")
color = fields.Integer()
request_item_infos = fields.Binary(compute="_compute_request_item_infos")
last_action_date = fields.Datetime(related="message_ids.create_date", readonly=True, string="Last Action Date")
completion_date = fields.Date(string="Completion Date", compute="_compute_count", compute_sudo=True)
sign_log_ids = fields.One2many('sign.log', 'sign_request_id', string="Logs", help="Activity logs linked to this request")
template_tags = fields.Many2many('sign.template.tag', string='Template Tags', related='template_id.tag_ids')
@api.depends('request_item_ids.state')
def _compute_count(self):
for rec in self:
wait, closed = 0, 0
for s in rec.request_item_ids:
if s.state == "sent":
wait += 1
if s.state == "completed":
closed += 1
rec.nb_wait = wait
rec.nb_closed = closed
rec.nb_total = wait + closed
rec.start_sign = bool(closed)
rec.progress = "{} / {}".format(closed, wait + closed)
if closed:
rec.start_sign = True
signed_requests = rec.request_item_ids.filtered('signing_date')
if wait == 0 and closed and signed_requests:
last_completed_request = signed_requests.sorted(key=lambda i: i.signing_date, reverse=True)[0]
rec.completion_date = last_completed_request.signing_date
else:
rec.completion_date = None
@api.depends('request_item_ids.state', 'request_item_ids.partner_id.name')
def _compute_request_item_infos(self):
for request in self:
request.request_item_infos = [{
'id': item.id,
'partner_name': item.partner_id.name or _('Public User'),
'state': item.state,
'signing_date': item.signing_date or ''
} for item in request.request_item_ids]
def _check_after_compute(self):
for rec in self:
if rec.state == 'sent' and rec.nb_closed == len(rec.request_item_ids) and len(rec.request_item_ids) > 0: # All signed
rec.action_signed()
def _get_final_recipients(self):
self.ensure_one()
all_recipients = set(self.request_item_ids.mapped('signer_email'))
all_recipients |= set(self.mapped('message_follower_ids.partner_id.email'))
# Remove False from all_recipients to avoid crashing later
all_recipients.discard(False)
return all_recipients
def button_send(self):
self.action_sent()
def go_to_document(self):
self.ensure_one()
request_item = self.request_item_ids.filtered(lambda r: r.partner_id and r.partner_id.id == self.env.user.partner_id.id)[:1]
return {
'name': self.reference,
'type': 'ir.actions.client',
'tag': 'odex25_sign.Document',
'context': {
'id': self.id,
'token': self.access_token,
'sign_token': request_item.access_token if request_item and request_item.state == "sent" else None,
'create_uid': self.create_uid.id,
'state': self.state,
},
}
def open_request(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"res_model": "sign.request",
"views": [[False, "form"]],
"res_id": self.id,
}
def get_completed_document(self):
self.ensure_one()
if not self.completed_document:
self.generate_completed_document()
return {
'name': 'Signed Document',
'type': 'ir.actions.act_url',
'url': '/odex25_sign/download/%(request_id)s/%(access_token)s/completed' % {'request_id': self.id, 'access_token': self.access_token},
}
def open_logs(self):
self.ensure_one()
return {
"name": _("Access History"),
"type": "ir.actions.act_window",
"res_model": "sign.log",
'view_mode': 'tree,form',
'domain': [('sign_request_id', '=', self.id)],
}
@api.onchange("progress", "start_sign")
def _compute_hashes(self):
for document in self:
try:
document.integrity = self.sign_log_ids._check_document_integrity()
except Exception:
document.integrity = False
def toggle_favorited(self):
self.ensure_one()
self.write({'favorited_ids': [(3 if self.env.user in self.favorited_ids else 4, self.env.user.id)]})
def action_resend(self):
self.action_draft()
subject = _("Signature Request - %s") % (self.template_id.attachment_id.name)
self.action_sent(subject=subject)
def action_draft(self):
self.write({'completed_document': None, 'access_token': self._default_access_token()})
def action_sent_without_mail(self):
self.write({'state': 'sent'})
for sign_request in self:
for sign_request_item in sign_request.request_item_ids:
sign_request_item.write({'state':'sent'})
Log = http.request.env['sign.log'].sudo()
vals = Log._prepare_vals_from_request(sign_request)
vals['action'] = 'create'
vals = Log._update_vals_with_http_request(vals)
Log.create(vals)
def action_sent(self, subject=None, message=None):
# Send accesses by email
self.write({'state': 'sent'})
for sign_request in self:
ignored_partners = []
for request_item in sign_request.request_item_ids:
if request_item.state != 'draft':
ignored_partners.append(request_item.partner_id.id)
included_request_items = sign_request.request_item_ids.filtered(lambda r: not r.partner_id or r.partner_id.id not in ignored_partners)
if sign_request.send_signature_accesses(subject, message, ignored_partners=ignored_partners):
Log = http.request.env['sign.log'].sudo()
vals = Log._prepare_vals_from_request(sign_request)
vals['action'] = 'create'
vals = Log._update_vals_with_http_request(vals)
Log.create(vals)
followers = sign_request.message_follower_ids.mapped('partner_id')
followers -= sign_request.create_uid.partner_id
followers -= sign_request.request_item_ids.mapped('partner_id')
if followers:
sign_request.send_follower_accesses(followers, subject, message)
included_request_items.action_sent()
else:
sign_request.action_draft()
def action_signed(self):
self.write({'state': 'signed'})
self.env.cr.commit()
if not self.check_is_encrypted():
# if the file is encrypted, we must wait that the document is decrypted
self.send_completed_document()
def check_is_encrypted(self):
self.ensure_one()
if not self.template_id.sign_item_ids:
return False
old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(self.template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
return old_pdf.isEncrypted
def action_canceled(self):
self.write({'completed_document': None, 'access_token': self._default_access_token(), 'state': 'canceled'})
for request_item in self.mapped('request_item_ids'):
request_item.action_draft()
def set_signers(self, signers):
SignRequestItem = self.env['sign.request.item']
for rec in self:
rec.request_item_ids.filtered(lambda r: not r.partner_id or not r.role_id).unlink()
ids_to_remove = []
for request_item in rec.request_item_ids:
for i in range(0, len(signers)):
if signers[i]['partner_id'] == request_item.partner_id.id and signers[i]['role'] == request_item.role_id.id:
signers.pop(i)
break
else:
ids_to_remove.append(request_item.id)
SignRequestItem.browse(ids_to_remove).unlink()
for signer in signers:
SignRequestItem.create({
'partner_id': signer['partner_id'],
'sign_request_id': rec.id,
'role_id': signer['role'],
})
def send_signature_accesses(self, subject=None, message=None, ignored_partners=[]):
self.ensure_one()
if len(self.request_item_ids) <= 0 or (set(self.request_item_ids.mapped('role_id')) != set(self.template_id.sign_item_ids.mapped('responsible_id'))):
return False
self.request_item_ids.filtered(lambda r: not r.partner_id or r.partner_id.id not in ignored_partners).send_signature_accesses(subject, message)
return True
def send_follower_accesses(self, followers, subject=None, message=None):
self.ensure_one()
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
tpl = self.env.ref('odex25_sign.sign_template_mail_follower')
for follower in followers:
if not follower.email:
continue
if not self.create_uid.email:
raise UserError(_("Please configure the sender's email address"))
tpl_follower = tpl.with_context(lang=get_lang(self.env, lang_code=follower.lang).code)
body = tpl_follower._render({
'record': self,
'link': url_join(base_url, 'odex25_sign/document/%s/%s' % (self.id, self.access_token)),
'subject': subject,
'body': message,
}, engine='ir.qweb', minimal_qcontext=True)
self.env['sign.request']._message_send_mail(
body, 'mail.mail_notification_light',
{'record_name': self.reference},
{'model_description': 'signature', 'company': self.create_uid.company_id},
{'email_from': self.create_uid.email_formatted,
'author_id': self.create_uid.partner_id.id,
'email_to': follower.email_formatted,
'subject': subject or _('%s : Signature request', self.reference)},
lang=follower.lang,
)
self.message_subscribe(partner_ids=follower.ids)
def send_completed_document(self):
self.ensure_one()
if len(self.request_item_ids) <= 0 or self.state != 'signed':
return False
if not self.completed_document:
self.generate_completed_document()
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
attachment = self.env['ir.attachment'].create({
'name': "%s.pdf" % self.reference if self.reference.split('.')[-1] != 'pdf' else self.reference,
'datas': self.completed_document,
'type': 'binary',
'res_model': self._name,
'res_id': self.id,
})
report_action = self.env.ref('odex25_sign.action_sign_request_print_logs')
# print the report with the public user in a sudoed env
# public user because we don't want groups to pollute the result
# (e.g. if the current user has the group Sign Manager,
# some private information will be sent to *all* signers)
# sudoed env because we have checked access higher up the stack
public_user = self.env.ref('base.public_user', raise_if_not_found=False)
if not public_user:
# public user was deleted, fallback to avoid crash (info may leak)
public_user = self.env.user
pdf_content, __ = report_action.with_user(public_user).sudo()._render_qweb_pdf(self.id)
attachment_log = self.env['ir.attachment'].create({
'name': "Certificate of completion - %s.pdf" % time.strftime('%Y-%m-%d - %H:%M:%S'),
'datas': base64.b64encode(pdf_content),
'type': 'binary',
'res_model': self._name,
'res_id': self.id,
})
tpl = self.env.ref('odex25_sign.sign_template_mail_completed')
for signer in self.request_item_ids:
if not signer.signer_email:
continue
signer_lang = get_lang(self.env, lang_code=signer.partner_id.lang).code
tpl = tpl.with_context(lang=signer_lang)
body = tpl._render({
'record': self,
'link': url_join(base_url, 'odex25_sign/document/%s/%s' % (self.id, signer.access_token)),
'subject': '%s signed' % self.reference,
'body': False,
}, engine='ir.qweb', minimal_qcontext=True)
if not self.create_uid.email:
raise UserError(_("Please configure the sender's email address"))
if not signer.signer_email:
raise UserError(_("Please configure the signer's email address"))
self.env['sign.request']._message_send_mail(
body, 'mail.mail_notification_light',
{'record_name': self.reference},
{'model_description': 'signature', 'company': self.create_uid.company_id},
{'email_from': self.create_uid.email_formatted,
'author_id': self.create_uid.partner_id.id,
'email_to': signer.partner_id.email_formatted,
'subject': _('%s has been signed', self.reference),
'attachment_ids': [(4, attachment.id), (4, attachment_log.id)]},
force_send=True,
lang=signer_lang,
)
tpl = self.env.ref('odex25_sign.sign_template_mail_completed')
for follower in self.mapped('message_follower_ids.partner_id') - self.request_item_ids.mapped('partner_id'):
if not follower.email:
continue
if not self.create_uid.email:
raise UserError(_("Please configure the sender's email address"))
tpl_follower = tpl.with_context(lang=get_lang(self.env, lang_code=follower.lang).code)
body = tpl._render({
'record': self,
'link': url_join(base_url, 'odex25_sign/document/%s/%s' % (self.id, self.access_token)),
'subject': '%s signed' % self.reference,
'body': '',
}, engine='ir.qweb', minimal_qcontext=True)
self.env['sign.request']._message_send_mail(
body, 'mail.mail_notification_light',
{'record_name': self.reference},
{'model_description': 'signature', 'company': self.create_uid.company_id},
{'email_from': self.create_uid.email_formatted,
'author_id': self.create_uid.partner_id.id,
'email_to': follower.email_formatted,
'subject': _('%s has been signed', self.reference)},
lang=follower.lang,
)
return True
def _get_font(self):
custom_font = self.env["ir.config_parameter"].sudo().get_param("odex25_sign.use_custom_font")
# The font must be a TTF font. The tool 'otf2ttf' may be useful for conversion.
if custom_font:
pdfmetrics.registerFont(TTFont(custom_font, custom_font + ".ttf"))
return custom_font
return "Helvetica"
def _get_normal_font_size(self):
return 0.015
def generate_completed_document(self, password=""):
self.ensure_one()
if not self.template_id.sign_item_ids:
self.completed_document = self.template_id.attachment_id.datas
return
old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(self.template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
isEncrypted = old_pdf.isEncrypted
if isEncrypted and not old_pdf.decrypt(password):
# password is not correct
return
font = self._get_font()
normalFontSize = self._get_normal_font_size()
packet = io.BytesIO()
can = canvas.Canvas(packet)
itemsByPage = self.template_id.sign_item_ids.getByPage()
SignItemValue = self.env['sign.request.item.value']
for p in range(0, old_pdf.getNumPages()):
page = old_pdf.getPage(p)
# Absolute values are taken as it depends on the MediaBox template PDF metadata, they may be negative
width = float(abs(page.mediaBox.getWidth()))
height = float(abs(page.mediaBox.getHeight()))
# Set page orientation (either 0, 90, 180 or 270)
rotation = page.get('/Rotate')
if rotation:
can.rotate(rotation)
# Translate system so that elements are placed correctly
# despite of the orientation
if rotation == 90:
width, height = height, width
can.translate(0, -height)
elif rotation == 180:
can.translate(-width, -height)
elif rotation == 270:
width, height = height, width
can.translate(-width, 0)
items = itemsByPage[p + 1] if p + 1 in itemsByPage else []
for item in items:
value = SignItemValue.search([('sign_item_id', '=', item.id), ('sign_request_id', '=', self.id)], limit=1)
if not value or not value.value:
continue
value = value.value
if item.type_id.item_type == "text":
can.setFont(font, height*item.height*0.8)
can.drawString(width*item.posX, height*(1-item.posY-item.height*0.9), value)
elif item.type_id.item_type == "selection":
content = []
for option in item.option_ids:
if option.id != int(value):
content.append("<strike>%s</strike>" % (option.value))
else:
content.append(option.value)
font_size = height * normalFontSize * 0.8
can.setFont(font, font_size)
text = " / ".join(content)
string_width = stringWidth(text.replace("<strike>", "").replace("</strike>", ""), font, font_size)
p = Paragraph(text, getSampleStyleSheet()["Normal"])
w, h = p.wrap(width, height)
posX = width * (item.posX + item.width * 0.5) - string_width // 2
posY = height * (1 - item.posY - item.height * 0.5) - h // 2
p.drawOn(can, posX, posY)
elif item.type_id.item_type == "textarea":
can.setFont(font, height*normalFontSize*0.8)
lines = value.split('\n')
y = (1-item.posY)
for line in lines:
y -= normalFontSize*0.9
can.drawString(width*item.posX, height*y, line)
y -= normalFontSize*0.1
elif item.type_id.item_type == "checkbox":
can.setFont(font, height*item.height*0.8)
value = 'X' if value == 'on' else ''
can.drawString(width*item.posX, height*(1-item.posY-item.height*0.9), value)
elif item.type_id.item_type == "signature" or item.type_id.item_type == "initial":
image_reader = ImageReader(io.BytesIO(base64.b64decode(value[value.find(',')+1:])))
_fix_image_transparency(image_reader._image)
can.drawImage(image_reader, width*item.posX, height*(1-item.posY-item.height), width*item.width, height*item.height, 'auto', True)
can.showPage()
can.save()
item_pdf = PdfFileReader(packet, overwriteWarnings=False)
new_pdf = PdfFileWriter()
for p in range(0, old_pdf.getNumPages()):
page = old_pdf.getPage(p)
page.mergePage(item_pdf.getPage(p))
new_pdf.addPage(page)
if isEncrypted:
new_pdf.encrypt(password)
output = io.BytesIO()
new_pdf.write(output)
self.completed_document = base64.b64encode(output.getvalue())
output.close()
@api.model
def _message_send_mail(self, body, notif_template_xmlid, message_values, notif_values, mail_values, force_send=False, **kwargs):
""" Shortcut to send an email. """
default_lang = get_lang(self.env, lang_code=kwargs.get('lang')).code
lang = kwargs.get('lang', default_lang)
sign_request = self.with_context(lang=lang)
# the notif layout wrapping expects a mail.message record, but we don't want
# to actually create the record
# See @tde-banana-odoo for details
msg = sign_request.env['mail.message'].sudo().new(dict(body=body, **message_values))
notif_layout = sign_request.env.ref(notif_template_xmlid)
body_html = notif_layout._render(dict(message=msg, **notif_values), engine='ir.qweb', minimal_qcontext=True)
body_html = sign_request.env['mail.render.mixin']._replace_local_links(body_html)
mail = sign_request.env['mail.mail'].sudo().create(dict(body_html=body_html, state='outgoing', **mail_values))
if force_send:
mail.send()
return mail
@api.model
def initialize_new(self, id, signers, followers, reference, subject, message, send=True, without_mail=False):
sign_users = self.env['res.users'].search([('partner_id', 'in', [signer['partner_id'] for signer in signers])]).filtered(lambda u: u.has_group('odex25_sign.group_sign_employee'))
sign_request = self.create({'template_id': id, 'reference': reference})
sign_request.message_subscribe(partner_ids=followers)
sign_request.activity_update(sign_users)
sign_request.set_signers(signers)
if send:
sign_request.action_sent(subject, message)
if without_mail:
sign_request.action_sent_without_mail()
return {
'id': sign_request.id,
'token': sign_request.access_token,
'sign_token': sign_request.request_item_ids.filtered(lambda r: r.partner_id == self.env.user.partner_id)[:1].access_token,
}
@api.model
def add_followers(self, id, followers):
sign_request = self.browse(id)
old_followers = set(sign_request.message_follower_ids.mapped('partner_id.id'))
followers = list(set(followers) - old_followers)
if followers:
sign_request.message_subscribe(partner_ids=followers)
sign_request.send_follower_accesses(self.env['res.partner'].browse(followers))
return sign_request.id
@api.model
def activity_update(self, sign_users):
for user in sign_users:
self.with_context(mail_activity_quick_update=True).activity_schedule(
'mail.mail_activity_data_todo',
user_id=user.id
)
class SignRequestItem(models.Model):
_name = "sign.request.item"
_description = "Signature Request Item"
_inherit = ['portal.mixin']
_rec_name = 'partner_id'
def _default_access_token(self):
return str(uuid.uuid4())
partner_id = fields.Many2one('res.partner', string="Contact", ondelete='cascade')
sign_request_id = fields.Many2one('sign.request', string="Signature Request", ondelete='cascade', required=True)
sign_item_value_ids = fields.One2many('sign.request.item.value', 'sign_request_item_id', string="Value")
reference = fields.Char(related='sign_request_id.reference', string="Document Name")
access_token = fields.Char(required=True, default=_default_access_token, readonly=True)
access_via_link = fields.Boolean('Accessed Through Token')
role_id = fields.Many2one('sign.item.role', string="Role")
sms_number = fields.Char(related='partner_id.mobile', readonly=False, depends=(['partner_id']), store=True)
sms_token = fields.Char('SMS Token', readonly=True)
signature = fields.Binary(attachment=True)
signing_date = fields.Date('Signed on', readonly=True)
state = fields.Selection([
("draft", "Draft"),
("sent", "To Sign"),
("completed", "Completed")
], readonly=True, default="draft")
signer_email = fields.Char(related='partner_id.email', readonly=False, depends=(['partner_id']), store=True)
latitude = fields.Float(digits=(10, 7))
longitude = fields.Float(digits=(10, 7))
def action_draft(self):
self.write({
'signature': None,
'signing_date': None,
'access_token': self._default_access_token(),
'state': 'draft',
})
for request_item in self:
itemsToClean = request_item.sign_request_id.template_id.sign_item_ids.filtered(lambda r: r.responsible_id == request_item.role_id or not r.responsible_id)
self.env['sign.request.item.value'].search([('sign_item_id', 'in', itemsToClean.mapped('id')), ('sign_request_id', '=', request_item.sign_request_id.id)]).unlink()
self.mapped('sign_request_id')._check_after_compute()
def action_sent(self):
self.write({'state': 'sent'})
self.mapped('sign_request_id')._check_after_compute()
def action_completed(self):
date = fields.Date.context_today(self).strftime(DEFAULT_SERVER_DATE_FORMAT)
self.write({'signing_date': date, 'state': 'completed'})
self.mapped('sign_request_id')._check_after_compute()
def send_signature_accesses(self, subject=None, message=None):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
tpl = self.env.ref('odex25_sign.sign_template_mail_request')
for signer in self:
if not signer.partner_id or not signer.partner_id.email:
continue
if not signer.create_uid.email:
continue
signer_lang = get_lang(self.env, lang_code=signer.partner_id.lang).code
tpl = tpl.with_context(lang=signer_lang)
body = tpl._render({
'record': signer,
'link': url_join(base_url, "odex25_sign/document/mail/%(request_id)s/%(access_token)s" % {'request_id': signer.sign_request_id.id, 'access_token': signer.access_token}),
'subject': subject,
'body': message if message != '<p><br></p>' else False,
}, engine='ir.qweb', minimal_qcontext=True)
if not signer.signer_email:
raise UserError(_("Please configure the signer's email address"))
self.env['sign.request']._message_send_mail(
body, 'mail.mail_notification_light',
{'record_name': signer.sign_request_id.reference},
{'model_description': 'signature', 'company': signer.create_uid.company_id},
{'email_from': signer.create_uid.email_formatted,
'author_id': signer.create_uid.partner_id.id,
'email_to': signer.partner_id.email_formatted,
'subject': subject},
force_send=True,
lang=signer_lang,
)
def sign(self, signature):
self.ensure_one()
if not isinstance(signature, dict):
self.signature = signature
else:
SignItemValue = self.env['sign.request.item.value']
request = self.sign_request_id
signerItems = request.template_id.sign_item_ids.filtered(lambda r: not r.responsible_id or r.responsible_id.id == self.role_id.id)
autorizedIDs = set(signerItems.mapped('id'))
requiredIDs = set(signerItems.filtered('required').mapped('id'))
itemIDs = {int(k) for k in signature}
if not (itemIDs <= autorizedIDs and requiredIDs <= itemIDs): # Security check
return False
user = self.env['res.users'].search([('partner_id', '=', self.partner_id.id)], limit=1).sudo()
for itemId in signature:
item_value = SignItemValue.search([('sign_item_id', '=', int(itemId)), ('sign_request_id', '=', request.id)])
if not item_value:
item_value = SignItemValue.create({'sign_item_id': int(itemId), 'sign_request_id': request.id,
'value': signature[itemId], 'sign_request_item_id': self.id})
else:
item_value.write({'value': signature[itemId]})
if item_value.sign_item_id.type_id.item_type == 'signature':
self.signature = signature[itemId][signature[itemId].find(',')+1:]
if user:
user.sign_signature = self.signature
if item_value.sign_item_id.type_id.item_type == 'initial' and user:
user.sign_initials = signature[itemId][signature[itemId].find(',')+1:]
return True
@api.model
def resend_access(self, id):
sign_request_item = self.browse(id)
subject = _("Signature Request - %s") % (sign_request_item.sign_request_id.template_id.attachment_id.name)
self.browse(id).send_signature_accesses(subject=subject)
def _reset_sms_token(self):
for record in self:
record.sms_token = randint(100000, 999999)
def _send_sms(self):
for rec in self:
rec._reset_sms_token()
self.env['sms.api']._send_sms([rec.sms_number], _('Your confirmation code is %s', rec.sms_token))
def _compute_access_url(self):
super(SignRequestItem, self)._compute_access_url()
for signature_request in self:
signature_request.access_url = '/my/signature/%s' % signature_request.id
class SignRequestItemValue(models.Model):
_name = "sign.request.item.value"
_description = "Signature Item Value"
_rec_name = 'sign_request_id'
sign_request_item_id = fields.Many2one('sign.request.item', string="Signature Request item", required=True,
ondelete='cascade')
sign_item_id = fields.Many2one('sign.item', string="Signature Item", required=True, ondelete='cascade')
sign_request_id = fields.Many2one(string="Signature Request", required=True, ondelete='cascade', related='sign_request_item_id.sign_request_id')
value = fields.Text()

View File

@ -1,288 +0,0 @@
# -*- coding: utf-8 -*-
import re
import base64
import io
from PyPDF2 import PdfFileReader
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError
from odoo.tools import pdf
class SignTemplate(models.Model):
_name = "sign.template"
_description = "Signature Template"
_rec_name = "attachment_id"
def _default_favorited_ids(self):
return [(4, self.env.user.id)]
attachment_id = fields.Many2one('ir.attachment', string="Attachment", required=True, ondelete='cascade')
name = fields.Char(related='attachment_id.name', readonly=False)
datas = fields.Binary(related='attachment_id.datas', readonly=False)
sign_item_ids = fields.One2many('sign.item', 'template_id', string="Signature Items", copy=True)
responsible_count = fields.Integer(compute='_compute_responsible_count', string="Responsible Count")
active = fields.Boolean(default=True, string="Active")
privacy = fields.Selection([('employee', 'All Users'), ('invite', 'On Invitation')],
string="Who can Sign", default="invite",
help="Set who can use this template:\n"
"- All Users: all users of the Sign application can view and use the template\n"
"- On Invitation: only invited users can view and use the template\n"
"Invited users can always edit the document template.\n"
"Existing requests based on this template will not be affected by changes.")
favorited_ids = fields.Many2many('res.users', string="Invited Users", default=lambda s: s._default_favorited_ids())
share_link = fields.Char(string="Share Link", copy=False)
sign_request_ids = fields.One2many('sign.request', 'template_id', string="Signature Requests")
tag_ids = fields.Many2many('sign.template.tag', string='Tags')
color = fields.Integer()
redirect_url = fields.Char(string="Redirect Link", default="",
help="Optional link for redirection after signature")
redirect_url_text = fields.Char(string="Link Label", default="Open Link", translate=True,
help="Optional text to display on the button link")
signed_count = fields.Integer(compute='_compute_signed_in_progress_template')
in_progress_count = fields.Integer(compute='_compute_signed_in_progress_template')
group_ids = fields.Many2many("res.groups", string="Template Access Group")
@api.model
def create(self, vals):
if 'active' in vals and vals['active'] and not self.env.user.has_group('odex25_sign.group_sign_user'):
raise AccessError(_("Do not have access to create templates"))
return super(SignTemplate, self).create(vals)
@api.depends('sign_item_ids.responsible_id')
def _compute_responsible_count(self):
for template in self:
template.responsible_count = len(template.sign_item_ids.mapped('responsible_id'))
def _compute_signed_in_progress_template(self):
sign_requests = self.env['sign.request'].read_group([('state', '!=', 'canceled')], ['state', 'template_id'], ['state', 'template_id'], lazy=False)
signed_request_dict = defaultdict(int)
in_progress_request_dict = defaultdict(int)
for sign_request in sign_requests:
if sign_request['state'] == 'sent':
template_id = sign_request['template_id'][0]
in_progress_request_dict[template_id] = sign_request['__count']
elif sign_request['state'] == 'signed':
template_id = sign_request['template_id'][0]
signed_request_dict[template_id] = sign_request['__count']
for template in self:
template.signed_count = signed_request_dict[template.id]
template.in_progress_count = in_progress_request_dict[template.id]
@api.model
def get_empty_list_help(self, help):
if not self.env.ref('sign.template_sign_tour', raise_if_not_found=False) or not self.env.user.has_group('odex25_sign.group_sign_user'):
return '<p class="o_view_nocontent_smiling_face">%s</p>' % _('Upload a PDF')
return super().get_empty_list_help(help)
def go_to_custom_template(self, sign_directly_without_mail=False):
self.ensure_one()
return {
'name': "Template \"%(name)s\"" % {'name': self.attachment_id.name},
'type': 'ir.actions.client',
'tag': 'sign.template',
'context': {
'id': self.id,
'sign_directly_without_mail': sign_directly_without_mail,
},
}
def toggle_favorited(self):
self.ensure_one()
self.write({'favorited_ids': [(3 if self.env.user in self[0].favorited_ids else 4, self.env.user.id)]})
def unlink(self):
if self.filtered(lambda template: template.sign_request_ids):
raise UserError(_("You can't delete a template for which signature requests exist but you can archive it instead."))
return super(SignTemplate, self).unlink()
@api.model
def upload_template(self, name=None, dataURL=None, active=True):
mimetype = dataURL[dataURL.find(':')+1:dataURL.find(',')]
datas = dataURL[dataURL.find(',')+1:]
# TODO: for now, PDF files without extension are recognized as application/octet-stream;base64
try:
file_pdf = PdfFileReader(io.BytesIO(base64.b64decode(datas)), strict=False, overwriteWarnings=False)
except Exception as e:
raise UserError(_("This file cannot be read. Is it a valid PDF?"))
file_type = mimetype.replace('application/', '').replace(';base64', '')
extension = re.compile(re.escape(file_type), re.IGNORECASE)
name = extension.sub(file_type, name)
attachment = self.env['ir.attachment'].create({'name': name, 'datas': datas, 'mimetype': mimetype})
template = self.create({'attachment_id': attachment.id, 'favorited_ids': [(4, self.env.user.id)], 'active': active})
return {'template': template.id, 'attachment': attachment.id}
@api.model
def update_from_pdfviewer(self, template_id=None, duplicate=None, sign_items=None, name=None):
template = self.browse(template_id)
if not duplicate and len(template.sign_request_ids) > 0:
return False
if duplicate:
new_attachment = template.attachment_id.copy()
r = re.compile(' \(v(\d+)\)$')
m = r.search(name)
v = str(int(m.group(1))+1) if m else "2"
index = m.start() if m else len(name)
new_attachment.name = name[:index] + " (v" + v + ")"
template = template.copy({
'attachment_id': new_attachment.id,
'favorited_ids': [(4, self.env.user.id)]
})
elif name:
template.attachment_id.name = name
item_ids = {
it
for it in map(int, sign_items)
if it > 0
}
template.sign_item_ids.filtered(lambda r: r.id not in item_ids).unlink()
for item in template.sign_item_ids:
values = sign_items.pop(str(item.id))
values['option_ids'] = [(6, False, [int(op) for op in values.get('option_ids', [])])]
item.write(values)
for item in sign_items.values():
item['template_id'] = template.id
item['option_ids'] = [(6, False, [int(op) for op in item.get('option_ids', [])])]
self.env['sign.item'].create(item)
if len(template.sign_item_ids.mapped('responsible_id')) > 1:
template.share_link = None
return template.id
@api.model
def rotate_pdf(self, template_id=None):
template = self.browse(template_id)
if len(template.sign_request_ids) > 0:
return False
template.datas = base64.b64encode(pdf.rotate_pdf(base64.b64decode(template.datas)))
return True
@api.model
def add_option(self, value):
option = self.env['sign.item.option'].search([('value', '=', value)])
option_id = option if option else self.env['sign.item.option'].create({'value': value})
return option_id.id
def open_requests(self):
return {
"type": "ir.actions.act_window",
"name": _("Sign requests"),
"res_model": "sign.request",
"res_id": self.id,
"domain": [["template_id.id", "in", self.ids]],
"views": [[False, 'kanban'], [False, "form"]],
"context": {'search_default_signed': True}
}
class SignTemplateTag(models.Model):
_name = "sign.template.tag"
_description = "Sign Template Tag"
_order = "name"
name = fields.Char('Tag Name', required=True, translate=True)
color = fields.Integer('Color Index')
_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists !"),
]
class SignItemSelectionOption(models.Model):
_name = "sign.item.option"
_description = "Option of a selection Field"
value = fields.Text(string="Option")
class SignItem(models.Model):
_name = "sign.item"
_description = "Fields to be sign on Document"
_rec_name = 'template_id'
template_id = fields.Many2one('sign.template', string="Document Template", required=True, ondelete='cascade')
type_id = fields.Many2one('sign.item.type', string="Type", required=True, ondelete='cascade')
required = fields.Boolean(default=True)
responsible_id = fields.Many2one("sign.item.role", string="Responsible")
option_ids = fields.Many2many("sign.item.option", string="Selection options")
name = fields.Char(string="Field Name")
page = fields.Integer(string="Document Page", required=True, default=1)
posX = fields.Float(digits=(4, 3), string="Position X", required=True)
posY = fields.Float(digits=(4, 3), string="Position Y", required=True)
width = fields.Float(digits=(4, 3), required=True)
height = fields.Float(digits=(4, 3), required=True)
def getByPage(self):
items = {}
for item in self:
if item.page not in items:
items[item.page] = []
items[item.page].append(item)
return items
class SignItemType(models.Model):
_name = "sign.item.type"
_description = "Signature Item Type"
name = fields.Char(string="Field Name", required=True, translate=True)
item_type = fields.Selection([
('signature', "Signature"),
('initial', "Initial"),
('text', "Text"),
('textarea', "Multiline Text"),
('checkbox', "Checkbox"),
('selection', "Selection"),
], required=True, string='Type', default='text')
tip = fields.Char(required=True, default="fill in", translate=True)
placeholder = fields.Char(translate=True)
default_width = fields.Float(string="Default Width", digits=(4, 3), required=True, default=0.150)
default_height = fields.Float(string="Default Height", digits=(4, 3), required=True, default=0.015)
auto_field = fields.Char(string="Automatic Partner Field", help="Partner field to use to auto-complete the fields of this type")
class SignItemParty(models.Model):
_name = "sign.item.role"
_description = "Signature Item Party"
name = fields.Char(required=True, translate=True)
sms_authentification = fields.Boolean('SMS Authentication', default=False,)
@api.model
def add(self, name):
party = self.search([('name', '=', name)])
return party.id if party else self.create({'name': name}).id
def buy_credits(self):
service_name = 'sms'
url = self.env['iap.account'].get_credits_url(service_name)
return {
'name': 'Buy SMS credits',
'res_model': 'ir.actions.act_url',
'type': 'ir.actions.act_url',
'target': 'current',
'url': url
}

View File

@ -1,164 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="sign_request_logs_user">
<t t-call="web.html_container">
<t t-set="print_date" t-value="datetime.datetime.utcnow().strftime('%Y-%m-%d - %H:%M:%S')"/>
<t t-foreach="docs" t-as="o">
<!-- we want UTC for the reports-->
<t t-set="o" t-value="o.with_context(tz='UTC')"/>
<t t-set="view_logs" t-value="o.sudo().sign_log_ids.filtered(lambda l: l.action == 'open')"/>
<t t-set="change_logs" t-value="o.sudo().sign_log_ids - view_logs"/>
<t t-set="creation_log" t-value="o.sudo().sign_log_ids.filtered(lambda l: l.action == 'create')"/>
<t t-call="web.external_layout">
<h2 class="mt16">
Certificate of Completion<br/>
</h2>
<h4>
<strong t-field="o.reference"/>
</h4>
<div class="text-muted small">Printed on <t t-esc="print_date"/> UTC</div>
<h5 class="mt16">Document Details</h5>
<div class="row">
<div class="col-6">
<div><b>Created by:</b> <span t-field="o.create_uid.sudo().partner_id"/></div>
<div><b>Created on:</b> <span t-field="o.create_date"/> (UTC)</div>
<div><b>Creation IP Address:</b> <span t-esc="creation_log and creation_log[0].ip"/></div>
<div><b>Signers:</b> <span t-esc="len(o.sudo().request_item_ids)"/></div>
</div>
<div class="col-6">
<div><b>Document ID:</b> <t t-esc="o.id"/></div>
<div><b>Signature:</b> <small t-esc="creation_log and creation_log[0].log_hash"/></div>
</div>
</div>
<br/>
<h5>Signing Events</h5>
<table class="table table-sm o_main_table small">
<t t-set="has_sms_validation" t-value="o.sudo().request_item_ids.filtered(lambda i: i.sms_token)"/>
<thead>
<tr>
<th class="text-left">Action</th>
<th class="text-left">By</th>
<th class="text-left">Date (UTC)</th>
<th class="text-left">Email Validation</th>
<th class="text-left" t-if="has_sms_validation">SMS Validation</th>
<th class="text-left" groups="odex25_sign.group_sign_manager">Geolocation</th>
<th class="text-left" groups="odex25_sign.group_sign_manager">IP</th>
</tr>
</thead>
<tbody>
<t t-foreach="change_logs" t-as="log">
<tr>
<td>
<span t-field="log.action"/>
<img t-if="log.action != 'save' and log.sign_request_item_id.signature" class="text-left" t-att-src="image_data_uri(log.sign_request_item_id.signature)" style="max-height: 40px"/>
</td>
<td> <span t-field="log.partner_id"/>
<t t-if="log.partner_id.email">
<br/><span t-field="log.partner_id.email"/>
</t>
</td>
<td> <span t-field="log.log_date"/> </td>
<td>
<t t-if="log.user_id != False and log.user_id in log.sign_request_item_id.partner_id.user_ids">
<span class="text-muted text-nowrap"><i class="fa fa-user"/> <t t-esc="log.sign_request_item_id.partner_id.user_ids[0].email"/></span>
</t>
<t t-elif="log.sign_request_item_id.access_via_link">
<span t-esc="log.sign_request_item_id.signer_email"/>
</t>
<t t-elif="log.action == 'sign'">
<span class="text-muted"><i class="fa fa-times"/> No email Validation</span>
</t>
</td>
<td t-if="has_sms_validation">
<t t-if="log.sign_request_item_id.sms_token">
<t t-set="number" t-value="''.join(['******', log.sign_request_item_id.sms_number[-4:]])"/>
<span t-esc="number"/>
</t>
<t t-elif="log.action == 'sign'">
<span class="text-muted"><i class="fa fa-times"/> No SMS Validation</span>
</t>
</td>
<td groups="odex25_sign.group_sign_manager">
<t t-if="log.latitude and log.longitude">
<span t-field="log.latitude"/>,
<span t-field="log.longitude"/>
<div>
<a t-attf-href="https://www.google.com/maps/search/?api=1&amp;query={{log.latitude}},{{log.longitude}}">
<i class="fa fa-globe"/> View
</a>
</div>
</t>
<t t-else="">
<span class="text-muted">Not available</span>
</t>
</td>
<td groups="odex25_sign.group_sign_manager"> <span t-field="log.ip"/> </td>
</tr>
<tr t-if="log.log_hash">
<td colspan="7" class="text-right text-muted small" style="border-right-style:hidden;border-left-style:hidden;">
Signature: <pre style="display:inline" t-field="log.log_hash"/>
</td>
</tr>
</t>
</tbody>
</table>
<div t-if="o.integrity" class="small text-right text-success">
<p><i class="fa fa-check"/> The document's integrity is valid.</p>
</div>
<div t-else="" class="small text-right text-muted">
<p><i class="fa fa-exclamation-circle"/> The document's integrity could not be verified.</p>
</div>
<div class="small" t-if="o.state == 'signed'">
<p>The final document and this completion history have been sent by email on&amp;nbsp; <span t-field="o.completion_date"/> to: <t t-esc="', '.join(o.sudo()._get_final_recipients())"/>.</p>
</div>
<h5>Access Logs</h5>
<table class="table table-sm o_main_table small">
<thead>
<tr>
<th class="text-left">Viewed/downloaded by</th>
<th class="text-left">Date (UTC)</th>
<th class="text-left">State</th>
<th class="text-left" groups="odex25_sign.group_sign_manager">Geolocation</th>
<th class="text-left" groups="odex25_sign.group_sign_manager">ip</th>
</tr>
</thead>
<tbody>
<tr t-foreach="view_logs" t-as="log">
<td> <span t-field="log.partner_id"/> <span t-field="log.partner_id.email"/></td>
<td> <span t-field="log.log_date"/> </td>
<td> <span t-field="log.request_state"/> </td>
<td groups="odex25_sign.group_sign_manager">
<t t-if="log.latitude and log.longitude">
<span t-field="log.latitude"/>,
<span t-field="log.longitude"/>
<a t-attf-href="https://www.google.com/maps/search/?api=1&amp;query={{log.latitude}},{{log.longitude}}">
<i class="fa fa-globe"/> View
</a>
</t>
<t t-else="">
<span class="text-muted">Not available</span>
</t>
</td>
<td groups="odex25_sign.group_sign_manager"> <span t-field="log.ip"/> </td>
</tr>
</tbody>
</table>
</t>
</t>
</t>
</template>
<record id="action_sign_request_print_logs" model="ir.actions.report">
<field name="name">Activity Logs</field>
<field name="model">sign.request</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">odex25_sign.sign_request_logs_user</field>
<field name="report_file">odex25_sign.sign_request_logs_user</field>
<field name="binding_model_id" ref="model_sign_request"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@ -1,36 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sign_request_all,sign_request all,model_sign_request,,0,0,0,0
access_sign_request_group_employee,sign_request group_employee,model_sign_request,group_sign_employee,1,1,1,1
access_sign_template_all,sign_template all,model_sign_template,,0,0,0,0
access_sign_template_group_user,sign_template group_user,model_sign_template,group_sign_user,1,1,1,1
access_sign_template_group_employee,sign_template group_employee,model_sign_template,group_sign_employee,1,1,1,1
access_sign_request_item_all,sign_request_item all,model_sign_request_item,,0,0,0,0
access_sign_request_item_group_user,sign_request_item group_user,model_sign_request_item,group_sign_user,1,1,1,1
access_sign_request_item_group_employee,sign_request_item group_employee,model_sign_request_item,group_sign_employee,1,1,1,0
access_sign_item_all,sign_item all,model_sign_item,,0,0,0,0
access_sign_item_group_user,sign_item group_user,model_sign_item,group_sign_user,1,1,1,1
access_sign_item_group_employee,sign_item group_employee,model_sign_item,group_sign_employee,1,1,1,1
access_sign_item_option_group_user,sign_item_option group_user,model_sign_item_option,group_sign_user,1,1,1,1
access_sign_item_option_group_employee,sign_item_option group_employee,model_sign_item_option,group_sign_employee,1,1,1,1
access_sign_request_item_value_all,sign_request_item_value all,model_sign_request_item_value,,0,0,0,0
access_sign_request_item_value_group_user,sign_request_item_value group_user,model_sign_request_item_value,group_sign_user,1,1,1,1
access_sign_item_role_all,sign_item_role all,model_sign_item_role,,0,0,0,0
access_sign_item_role_group_user,sign_item_role group_user,model_sign_item_role,group_sign_user,1,1,1,1
access_sign_item_role_group_employee,sign_item_role group_employee,model_sign_item_role,group_sign_employee,1,0,0,0
access_sign_item_type_all,sign_item_type all,model_sign_item_type,,0,0,0,0
access_sign_item_type_group_user,sign_item_type group_user,model_sign_item_type,group_sign_user,1,1,1,1
access_sign_item_type_group_employee,sign_item_type group_employee,model_sign_item_type,group_sign_employee,1,0,0,0
access_sign_template_tag_user,access_sign_template_tag_user,model_sign_template_tag,group_sign_user,1,0,0,0
access_sign_template_tag_employee,access_sign_template_tag_employee,model_sign_template_tag,group_sign_employee,1,0,0,0
access_sign_template_tag_manager,access_sign_template_tag_manager,model_sign_template_tag,group_sign_manager,1,1,1,1
access_sign_log_all,sign_log all,model_sign_log,,0,0,0,0
access_sign_log_group_manager,sign_log group_manager,model_sign_log,group_sign_manager,1,0,0,0
access_sign_request_portal,sign.request.portal,odex25_sign.model_sign_request,base.group_portal,1,0,0,0
access_sign_send_request,access.sign.send.request,model_sign_send_request,odex25_sign.group_sign_user,1,1,1,0
access_sign_send_request_employee,access.sign.send.request.employee,model_sign_send_request,odex25_sign.group_sign_employee,1,1,1,0
access_sign_send_request_signer,access.sign.send.request.signer,model_sign_send_request_signer,odex25_sign.group_sign_user,1,1,1,0
access_sign_send_request_signer_employee,access.sign.send.request.signer.employee,model_sign_send_request_signer,odex25_sign.group_sign_employee,1,1,1,0
access_sign_template_share,access.sign.template.share,model_sign_template_share,odex25_sign.group_sign_user,1,1,1,0
access_sign_template_share_employee,access.sign.template.share.employee,model_sign_template_share,odex25_sign.group_sign_employee,1,1,1,0
access_sign_request_send_copy,access.sign.request.send.copy,model_sign_request_send_copy,odex25_sign.group_sign_user,1,1,1,0
access_sign_request_send_copy_employee,access.sign.request.send.copy.employee,model_sign_request_send_copy,odex25_sign.group_sign_employee,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sign_request_all sign_request all model_sign_request 0 0 0 0
3 access_sign_request_group_employee sign_request group_employee model_sign_request group_sign_employee 1 1 1 1
4 access_sign_template_all sign_template all model_sign_template 0 0 0 0
5 access_sign_template_group_user sign_template group_user model_sign_template group_sign_user 1 1 1 1
6 access_sign_template_group_employee sign_template group_employee model_sign_template group_sign_employee 1 1 1 1
7 access_sign_request_item_all sign_request_item all model_sign_request_item 0 0 0 0
8 access_sign_request_item_group_user sign_request_item group_user model_sign_request_item group_sign_user 1 1 1 1
9 access_sign_request_item_group_employee sign_request_item group_employee model_sign_request_item group_sign_employee 1 1 1 0
10 access_sign_item_all sign_item all model_sign_item 0 0 0 0
11 access_sign_item_group_user sign_item group_user model_sign_item group_sign_user 1 1 1 1
12 access_sign_item_group_employee sign_item group_employee model_sign_item group_sign_employee 1 1 1 1
13 access_sign_item_option_group_user sign_item_option group_user model_sign_item_option group_sign_user 1 1 1 1
14 access_sign_item_option_group_employee sign_item_option group_employee model_sign_item_option group_sign_employee 1 1 1 1
15 access_sign_request_item_value_all sign_request_item_value all model_sign_request_item_value 0 0 0 0
16 access_sign_request_item_value_group_user sign_request_item_value group_user model_sign_request_item_value group_sign_user 1 1 1 1
17 access_sign_item_role_all sign_item_role all model_sign_item_role 0 0 0 0
18 access_sign_item_role_group_user sign_item_role group_user model_sign_item_role group_sign_user 1 1 1 1
19 access_sign_item_role_group_employee sign_item_role group_employee model_sign_item_role group_sign_employee 1 0 0 0
20 access_sign_item_type_all sign_item_type all model_sign_item_type 0 0 0 0
21 access_sign_item_type_group_user sign_item_type group_user model_sign_item_type group_sign_user 1 1 1 1
22 access_sign_item_type_group_employee sign_item_type group_employee model_sign_item_type group_sign_employee 1 0 0 0
23 access_sign_template_tag_user access_sign_template_tag_user model_sign_template_tag group_sign_user 1 0 0 0
24 access_sign_template_tag_employee access_sign_template_tag_employee model_sign_template_tag group_sign_employee 1 0 0 0
25 access_sign_template_tag_manager access_sign_template_tag_manager model_sign_template_tag group_sign_manager 1 1 1 1
26 access_sign_log_all sign_log all model_sign_log 0 0 0 0
27 access_sign_log_group_manager sign_log group_manager model_sign_log group_sign_manager 1 0 0 0
28 access_sign_request_portal sign.request.portal odex25_sign.model_sign_request base.group_portal 1 0 0 0
29 access_sign_send_request access.sign.send.request model_sign_send_request odex25_sign.group_sign_user 1 1 1 0
30 access_sign_send_request_employee access.sign.send.request.employee model_sign_send_request odex25_sign.group_sign_employee 1 1 1 0
31 access_sign_send_request_signer access.sign.send.request.signer model_sign_send_request_signer odex25_sign.group_sign_user 1 1 1 0
32 access_sign_send_request_signer_employee access.sign.send.request.signer.employee model_sign_send_request_signer odex25_sign.group_sign_employee 1 1 1 0
33 access_sign_template_share access.sign.template.share model_sign_template_share odex25_sign.group_sign_user 1 1 1 0
34 access_sign_template_share_employee access.sign.template.share.employee model_sign_template_share odex25_sign.group_sign_employee 1 1 1 0
35 access_sign_request_send_copy access.sign.request.send.copy model_sign_request_send_copy odex25_sign.group_sign_user 1 1 1 0
36 access_sign_request_send_copy_employee access.sign.request.send.copy.employee model_sign_request_send_copy odex25_sign.group_sign_employee 1 1 1 0

View File

@ -1,189 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="group_sign_employee" model="res.groups">
<field name="name">Employee: Send file to sign</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="category_id" ref="base.module_category_sales_sign"/>
</record>
<record id="group_sign_user" model="res.groups">
<field name="name">User: Own and Shared Templates</field>
<field name="implied_ids" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
<field name="category_id" ref="base.module_category_sales_sign"/>
</record>
<record id="group_sign_manager" model="res.groups">
<field name="name">Administrator</field>
<field name="category_id" ref="base.module_category_sales_sign"/>
<field name="implied_ids" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('group_sign_manager'))]"/>
</record>
<record id="ir_rule_sign_template_group_sign_user" model="ir.rule">
<field name="name">sign.template: group_sign_user: Manage favorited templates</field>
<field name="model_id" ref="odex25_sign.model_sign_template"/>
<field name="domain_force">[('favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
</record>
<record id="ir_rule_sign_template_group_sign_employee" model="ir.rule">
<field name="name">sign.template: group_sign_employee: Manage favorited templates</field>
<field name="model_id" ref="odex25_sign.model_sign_template"/>
<field name="domain_force">[('favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
</record>
<record id="ir_rule_sign_template_access_group_sign_employee" model="ir.rule">
<field name="name">sign.template: group_sign_employee: seen template access</field>
<field name="model_id" ref="odex25_sign.model_sign_template"/>
<field name="domain_force">[('group_ids', 'in', user.groups_id.ids)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
</record>
<record id="ir_rule_sign_template_item_access_group_sign_employee" model="ir.rule">
<field name="name">sign.template: group_sign_employee: seen template item access</field>
<field name="model_id" ref="odex25_sign.model_sign_item"/>
<field name="domain_force">['|', ('template_id.group_ids', 'in', user.groups_id.ids), ('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
</record>
<record id="ir_rule_sign_template_item_access_group_sign_user" model="ir.rule">
<field name="name">sign.template: group_sign_user: seen all template item</field>
<field name="model_id" ref="odex25_sign.model_sign_item"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
</record>
<record id="ir_rule_sign_template_group_sign_manager" model="ir.rule">
<field name="name">sign.template: group_sign_manager: Manage all templates</field>
<field name="model_id" ref="odex25_sign.model_sign_template"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_manager'))]"/>
</record>
<record id="ir_rule_sign_request_group_sign_user_create" model="ir.rule">
<field name="name">sign.request: group_sign_user: Create requests on favorite and public templates</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">['|', ('template_id.favorited_ids', 'in', user.id), ('template_id.privacy', '=', 'employee')]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="ir_rule_sign_request_group_sign_user_modify" model="ir.rule">
<field name="name">sign.request: group_sign_user: Allow to edit favorited requests</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[('favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="ir_rule_sign_request_group_sign_employee_modify" model="ir.rule">
<field name="name">sign.request: group_sign_employee: Allow to edit favorited requests</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[('favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="ir_rule_sign_request_group_sign_user_modify" model="ir.rule">
<field name="name">sign.request: group_sign_user: Read the requests that I follow</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[('message_partner_ids', 'in', user.partner_id.ids)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="ir_rule_sign_request_group_sign_employee_modify" model="ir.rule">
<field name="name">sign.request: group_sign_employee: Read the requests that I follow</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[('message_partner_ids', 'in', user.partner_id.ids)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="True"/>
</record>
<record id="ir_rule_sign_request_group_sign_manager" model="ir.rule">
<field name="name">sign.template: group_sign_manager: Manage all requests</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_manager'))]"/>
</record>
<record id="ir_rule_sign_log_group_sign_user_modify" model="ir.rule">
<field name="name">sign.log: group_sign_user: Allow to read logs of own requests</field>
<field name="model_id" ref="odex25_sign.model_sign_log"/>
<field name="domain_force">[('sign_request_id.favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="ir_rule_sign_log_group_sign_employee_modify" model="ir.rule">
<field name="name">sign.log: group_sign_employee: Allow to read logs of own requests</field>
<field name="model_id" ref="odex25_sign.model_sign_log"/>
<field name="domain_force">[('sign_request_id.favorited_ids', 'in', user.id)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_employee'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="ir_rule_sign_log_group_sign_manager" model="ir.rule">
<field name="name">sign.log: group_sign_manager: See all logs</field>
<field name="model_id" ref="odex25_sign.model_sign_log"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('odex25_sign.group_sign_manager'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- Portal Access Rules -->
<record id="ir_rule_sign_request_portal" model="ir.rule">
<field name="name">Portal Personal Sign Request</field>
<field name="model_id" ref="odex25_sign.model_sign_request"/>
<field name="domain_force">[('message_partner_ids','child_of',[user.partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_unlink" eval="False"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_create" eval="False"/>
</record>
<record id="sign_request_line_rule_portal" model="ir.rule">
<field name="name">Portal Sign Requests Line</field>
<field name="model_id" ref="odex25_sign.model_sign_request_item"/>
<field name="domain_force">[('message_partner_ids','child_of',[user.partner_id.id])]</field>
<field name="perm_unlink" eval="False"/>
<field name="perm_write" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="98.162%" x2="0%" y1="1.838%" y2="100%"><stop offset="0%" stop-color="#797DA5"/><stop offset="50.799%" stop-color="#6D7194"/><stop offset="100%" stop-color="#626584"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-1-4-4V36.285L18 14h28v15.96l3.608-3.433L56.775 29 43.03 45.984 46 54 31.248 69H4z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#000" d="M46 28.917l-3 2.75V19H21v35h22v-6l3-3v11H18V16h28v12.917zM24 47h15v2H24v-2zm32.448-15.453l-1.8 1.8a.47.47 0 0 1-.665 0l-4.335-4.335a.47.47 0 0 1 0-.664l1.8-1.8a1.88 1.88 0 0 1 2.653 0l2.347 2.347c.735.73.735 1.918 0 2.652zm-8.347-1.652a.474.474 0 0 1 .668 0l4.336 4.336a.47.47 0 0 1 0 .664L42.847 45.15l-4.746.832a.939.939 0 0 1-1.086-1.086l.828-4.745L48.1 29.895zm-6.254 9.378a.545.545 0 0 0 .773 0l6.016-6.015a.545.545 0 0 0 0-.773.545.545 0 0 0-.774 0L41.847 38.5a.545.545 0 0 0 0 .773zm-1.41 3.285v-1.875h-1.418l-.442 2.52 1.215 1.214 2.52-.441v-1.418h-1.875zm-4.897-14.56l1.258 1.23c.375.355.2.986-.292 1.103l-1.713.438.483 1.695a.662.662 0 0 1-.811.812l-1.695-.483-.437 1.713c-.119.497-.761.66-1.103.292L30 33.54l-1.23 1.258c-.346.365-.981.216-1.103-.292l-.437-1.713-1.695.483a.662.662 0 0 1-.811-.812l.483-1.695-1.713-.438a.651.651 0 0 1-.292-1.103l1.258-1.23-1.258-1.23a.651.651 0 0 1 .292-1.104l1.713-.437-.483-1.696a.662.662 0 0 1 .811-.811l1.695.483.437-1.714c.117-.49.757-.656 1.103-.291L30 22.465l1.23-1.267c.35-.37.987-.193 1.103.291l.437 1.714 1.695-.483a.662.662 0 0 1 .811.811l-.483 1.696 1.713.437a.651.651 0 0 1 .292 1.103l-1.258 1.23zm-8.446.513l2.085 2.084a.32.32 0 0 0 .453 0l3.689-3.688a.32.32 0 0 0 0-.454L32.867 26a.32.32 0 0 0-.453 0l-3.008 3.008L28 27.604a.32.32 0 0 0-.453 0l-.454.453a.32.32 0 0 0 0 .454z" opacity=".3"/><path fill="#FFF" d="M46 26.917l-3 2.75V17H21v35h22v-6l3-3v11H18V14h28v12.917zM24 45h15v2H24v-2zm32.448-15.453l-1.8 1.8a.47.47 0 0 1-.665 0l-4.335-4.335a.47.47 0 0 1 0-.664l1.8-1.8a1.88 1.88 0 0 1 2.653 0l2.347 2.347c.735.73.735 1.918 0 2.652zm-8.347-1.652a.474.474 0 0 1 .668 0l4.336 4.336a.47.47 0 0 1 0 .664L42.847 43.15l-4.746.832a.939.939 0 0 1-1.086-1.086l.828-4.745L48.1 27.895zm-6.254 9.378a.545.545 0 0 0 .773 0l6.016-6.015a.545.545 0 0 0 0-.773.545.545 0 0 0-.774 0L41.847 36.5a.545.545 0 0 0 0 .773zm-1.41 3.285v-1.875h-1.418l-.442 2.52 1.215 1.214 2.52-.441v-1.418h-1.875zm-4.897-14.56l1.258 1.23c.375.355.2.986-.292 1.103l-1.713.438.483 1.695a.662.662 0 0 1-.811.812l-1.695-.483-.437 1.713c-.119.497-.761.66-1.103.292L30 31.54l-1.23 1.258c-.346.365-.981.216-1.103-.292l-.437-1.713-1.695.483a.662.662 0 0 1-.811-.812l.483-1.695-1.713-.438a.651.651 0 0 1-.292-1.103l1.258-1.23-1.258-1.23a.651.651 0 0 1 .292-1.104l1.713-.437-.483-1.696a.662.662 0 0 1 .811-.811l1.695.483.437-1.714c.117-.49.757-.656 1.103-.291L30 20.465l1.23-1.267c.35-.37.987-.193 1.103.291l.437 1.714 1.695-.483a.662.662 0 0 1 .811.811l-.483 1.696 1.713.437a.651.651 0 0 1 .292 1.103l-1.258 1.23zm-8.446.513l2.085 2.084a.32.32 0 0 0 .453 0l3.689-3.688a.32.32 0 0 0 0-.454L32.867 24a.32.32 0 0 0-.453 0l-3.008 3.008L28 25.604a.32.32 0 0 0-.453 0l-.454.453a.32.32 0 0 0 0 .454z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,510 +0,0 @@
.o_block_scroll {
overflow: hidden;
}
#viewerContainer { /* PDFJS Viewer */
visibility: hidden;
opacity: 0.01;
}
#viewer { /* PDFJS Viewer */
padding-top: 21px; /* Allow space for the types toolbar */
}
.page { /* PDFJS Viewer */
position: relative;
}
.o_sign_sign_item {
position: absolute;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
text-align: center;
border: 1px dashed #875A7B;
vertical-align: middle;
display: inline-block;
margin: 0;
padding: 0;
box-shadow: 0px 0px 3px 1px #875A7B;
resize: none;
background-color: rgba(255, 255, 255, 0.9);
font-family: Helvetica;
line-height: 1.25;
z-index: 100;
overflow: hidden;
cursor: pointer;
}
.o_sign_sign_item.o_readonly_mode {
border:none;
}
.o_sign_sign_item .o_placeholder {
font-size: 12pt !important;
vertical-align: middle;
}
textarea.o_sign_sign_item {
font-size: 12pt !important;
}
.o_sign_sign_item_required {
background-color: rgba(255, 235, 235, 0.9);
font-size: 10pt;
}
.o_sign_item_custom_popover .form-group {
margin-bottom: 8px;
}
.o_sign_item_custom_popover label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: bold;
font-size: 1em;
}
.o_sign_item_custom_popover .o_input {
padding: 5px;
}
.o_sign_item_custom_popover .o_sign_responsible_select {
display: block;
position: relative;
}
.o_sign_sign_item + .popover {
position: absolute;
top: 0;
left: 0;
z-index: 1060;
display: none;
max-width: 276px;
padding: 1px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
font-style: normal;
font-weight: normal;
line-height: 1.42857143;
text-align: left;
text-align: start;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
white-space: normal;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .2);
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
line-break: auto;
}
.o_sign_sign_item + .popover.top {
margin-top: -10px;
}
.o_sign_sign_item + .popover.right {
margin-left: 10px;
}
.o_sign_sign_item + .popover.bottom {
margin-top: 10px;
}
.o_sign_sign_item + .popover.left {
margin-left: -10px;
}
.o_sign_sign_item + .popover > .popover-header {
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.o_sign_sign_item + .popover > .popover-body {
padding: 9px 14px;
}
.o_sign_sign_item + .popover > .arrow,
.o_sign_sign_item + .popover > .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.o_sign_sign_item + .popover > .arrow {
border-width: 11px;
}
.o_sign_sign_item + .popover > .arrow:after {
content: "";
border-width: 10px;
}
.o_sign_sign_item + .popover.top > .arrow {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-top-color: #999;
border-top-color: rgba(0, 0, 0, .25);
border-bottom-width: 0;
}
.o_sign_sign_item + .popover.top > .arrow:after {
bottom: 1px;
margin-left: -10px;
content: " ";
border-top-color: #fff;
border-bottom-width: 0;
}
.o_sign_sign_item + .popover.right > .arrow {
top: 50%;
left: -11px;
margin-top: -11px;
border-right-color: #999;
border-right-color: rgba(0, 0, 0, .25);
border-left-width: 0;
}
.o_sign_sign_item + .popover.right > .arrow:after {
bottom: -10px;
left: 1px;
content: " ";
border-right-color: #fff;
border-left-width: 0;
}
.o_sign_sign_item + .popover.bottom > .arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: #999;
border-bottom-color: rgba(0, 0, 0, .25);
}
.o_sign_sign_item + .popover.bottom > .arrow:after {
top: 1px;
margin-left: -10px;
content: " ";
border-top-width: 0;
border-bottom-color: #fff;
}
.o_sign_sign_item + .popover.left > .arrow {
top: 50%;
right: -11px;
margin-top: -11px;
border-right-width: 0;
border-left-color: #999;
border-left-color: rgba(0, 0, 0, .25);
}
.o_sign_sign_item + .popover.left > .arrow:after {
right: 1px;
bottom: -10px;
content: " ";
border-right-width: 0;
border-left-color: #fff;
}
.popover .o_sign_delete_field_button {
float: right;
margin-top: 8px;
margin-bottom: 8px;
}
.popover .o_sign_delete_field_button.fa-trash {
cursor: pointer;
opacity: 0.5;
transition: opacity ease 250ms;
margin-top: 8px;
}
.popover .o_sign_delete_field_button.fa-trash:hover {
opacity: 1.0;
}
.o_sign_sign_item + .popover .o_sign_validate_field_button {
font-size: 13px;
padding: 5px;
cursor: pointer;
}
.o_sign_sign_textarea {
white-space: pre-wrap !important;
font-size: 12pt !important;
}
.o_sign_sign_item_pdfview {
border: 1px dashed silver;
box-shadow: 0px 0px 0px 0px white;
overflow: visible;
background: transparent;
color: black;
cursor: auto;
white-space: pre;
text-align: left;
display: flex;
}
.o_sign_sign_item .o_sign_helper {
vertical-align: middle;
height: 100%;
color: #757575;
display: inline-block;
}
.o_sign_sign_item img {
width: 100%;
height: 100%;
vertical-align: middle;
display: inline-block;
}
.o_sign_sign_item .o_sign_config_area {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
white-space: nowrap;
font-size: 10pt;
text-align: right;
padding: 2px;
margin: 2px;
}
.o_sign_sign_item .o_sign_config_area .fa-arrows {
display: block;
float: left;
margin-right: 5px;
}
.o_sign_sign_item .o_sign_config_area .o_sign_item_display {
display: block;
width: 100%;
height: 100%;
}
.o_sign_select_options_display {
height: 100%;
width: 100%;
display: flex;
margin-top: -22px;
align-items: center;
justify-content: left;
}
.o_sign_item_option {
padding: 5px;
/* font-size: 12pt; */
}
.o_sign_option_separator {
padding: 5px;
/* font-size: 12pt; */
}
.o_sign_selected_option {
text-decoration-line: none;
}
.o_sign_not_selected_option {
text-decoration-line: line-through;
}
.ui-selected, .ui-selecting { /* jQuery UI */
box-shadow: 0px 0px 5px 1px orange;
}
.ui-resizable {
text-align: center;
}
.ui-draggable .ui-draggable-handle, .o_sign_field_type_button.ui-draggable { /* jQuery UI */
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
}
.ui-draggable-dragging .ui-draggable-handle,
.o_sign_sign_item_to_add.ui-draggable-dragging,
.o_sign_sign_item.ui-draggable-dragging { /* jQuery UI */
box-shadow: 0 5px 25px -10px black;
transition: box-shadow 0.3s;
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
.o_sign_sign_item_navigator {
position: fixed;
top: 15%;
left: 0;
background-color: transparent;
background-image: url(../../img/navigator.png);
background-size: 100% 100%;
line-height: 50px;
height: 50px;
font-size: 1.4em;
text-transform: uppercase;
z-index: 90;
padding: 0 20px 0 10px;
color: white;
cursor: pointer;
}
@media (max-width: 767px) { /* @screen-xs-max */
.o_sign_sign_item_navigator {
width: 100%;
top: 32px !important;
z-index: 9999;
line-height: 25px;
height: 35px;
padding: 5px 0 0;
font-size: 0.8em;
background-image: none;
background-color: #875A7B;
text-align: center;
}
.o_sign_sign_item_navline {
display: none !important;
}
}
.o_sign_sign_item_navline {
position: fixed;
top: 15%;
left: 1%;
pointer-events: none;
z-index: 80;
width: 99%;
height: 25px;
border-bottom: 1px dashed silver;
opacity: 0.5;
}
@media (max-width: 767px) { /* @screen-xs-max */
.o_sign_sign_item_navline {
line-height: 12.5px;
height: 12.5px;
}
}
.o_sign_field_type_toolbar {
width: 14rem;
z-index: 1030;
height: 100%;
position: absolute;
background-color: #404040;
background-image: url(../../img/texture.png);
}
.o_sign_field_type_button {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
border: none;
background-color: #00A09D;
color: white;
min-height: 30px;
margin: 0.5rem 0.5rem 0 0.5rem;
padding: 0.5rem 2rem;
}
.o_sign_field_type_button:hover {
transition: transform 200ms ease 0s;
box-shadow: 0 8px 15px -10px black;
transform: translateX(1px) translateY(-1px) scale(1.05) rotate(1deg);
}
.o_sign_close_button {
padding: 0;
background-color: transparent;
border: 0;
float: right;
line-height: 1;
text-shadow: 0 1px 0 #FFFFFF;
opacity: .5;
}
.o_sign_save {
width: auto;
background: #A4498C;
}
.o_sign_save:hover {
width: auto;
background: #A4498C;
opacity: .8;
cursor: pointer;
}
.toolbarButton.rotateCw::before {
content: url(../../img/secondaryToolbarButton-rotateCw.png);
}
@media screen and (-webkit-min-device-pixel-ratio: 1.1), screen and (min-resolution: 1.1dppx) {
.toolbarButton.rotateCw::before {
content: url(../../img/secondaryToolbarButton-rotateCw@2x.png);
}
}
.o_sign_field_type_toolbar_title {
height: 12px;
color: white;
font-size: 12px;
background-color: #474747;
background-image: url(../../img/texture.png), -webkit-gradient(linear, left top, left bottom, from(hsla(0,0%,32%,.99)), to(hsla(0,0%,27%,.95)));
box-shadow: inset 0 1px 1px hsla(0,0%,0%,.15), inset 0 -1px 0 hsla(0,0%,100%,.05), 0 1px 0 hsla(0,0%,0%,.15), 0 1px 1px hsla(0,0%,0%,.1);
padding: 10px;
}
#outerContainer.o_sign_field_type_toolbar_visible {
margin-left: 14rem;
width: auto;
}
#outerContainer.o_sign_field_type_toolbar_visible #thumbnailView {
width: auto;
}
.o_tooltip > .o_tooltip_content {
font-size: 12px !important;
box-sizing: border-box;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +0,0 @@
odoo.define('odex25_sign.tour', function(require) {
"use strict";
var core = require('web.core');
var tour = require('web_tour.tour');
var _t = core._t;
tour.register('sign_tour', {
url: "/web",
rainbowManMessage: "<b>Congratulations</b>, your first document is fully signed!",
}, [tour.stepUtils.showAppsMenuItem(), {
trigger: '.o_app[data-menu-xmlid="odex25_sign.menu_document"]',
content: _t("Let's <b>prepare & sign</b> our first document."),
position: 'bottom',
edition: 'enterprise'
}, {
trigger: '.o_nocontent_help p a:contains("' + _t('start with our sample template') + '")',
content: _t("Try out this sample contract."),
position: "bottom",
}, {
trigger: 'iframe .o_sign_field_type_toolbar .o_sign_field_type_button:contains("' + _t('Name') + '")',
content: _t("<b>Drag & drop “Name”</b> into the document."),
position: "right",
}, {
trigger: 'iframe .o_sign_field_type_toolbar .o_sign_field_type_button:contains("' + _t('Date') + '")',
content: _t("<b>Drag & drop “Date”</b> into the document."),
position: "right",
}, {
trigger: 'iframe .o_sign_field_type_toolbar .o_sign_field_type_button:contains("' + _t('Signature') + '")',
content: _t("And finally, <b>drag & drop “Signature”</b> into the bottom of the document."),
position: "right",
}, {
trigger: '.o_control_panel .o_sign_template_sign_now',
content: _t("Well done, your document is ready!<br>Let's sign it directly."),
position: "bottom",
}, {
trigger: '.modal-dialog button[name="sign_directly_without_mail"]',
content: _t("Ok, lets sign the document now."),
position: "bottom",
}, {
trigger: 'iframe .o_sign_sign_item_navigator',
content: _t("Go to the first area you have to fill in."),
position: "bottom",
}, {
trigger: 'iframe .o_sign_sign_item_navigator',
alt_trigger: "iframe .o_sign_sign_item[placeholder='" + _t("Date") + "']",
content: _t("Your name has been auto-completed. Lets continue!"),
position: "bottom",
}, {
trigger: 'iframe .o_sign_sign_item_navigator',
content: _t("Lets sign the document!"),
position: "bottom",
}, {
trigger: 'iframe .o_sign_sign_item_navigator',
alt_trigger: 'iframe .o_sign_sign_item[data-signature]',
content: _t("Draw your most beautiful signature!<br>You can also create one automatically or load a signature from your computer."),
position: "bottom",
}, {
trigger: '.modal-dialog button:contains("' + _t('Adopt and Sign') + '")',
content: _t("Confirm and continue."),
position: "bottom",
}, {
trigger: '.o_sign_validate_banner button.o_validate_button',
content: _t("Great, the document is signed!<br>Lets validate it."),
position: "top",
}, {
trigger: '.modal-dialog button:contains("' + _t('View Document') + '")',
content: _t("Let's view the document you have just signed!"),
position: "bottom",
},
]);
});

View File

@ -1,218 +0,0 @@
.o_web_client {
.o_content {
.favorite_sign_button {
position: absolute;
color: $o-brand-secondary;
&:hover {
color: orange;
}
&.favorite_sign_button_enabled {
color: orange;
&:hover {
color: theme-color('danger');
}
}
}
.o_signers > div {
&.o_sign_completed {
color: theme-color('success');
}
}
.o_form_view {
.o_field_pdfviewer {
width: 100%;
height: 600px;
border: 0;
}
}
}
.modal-content {
.o_field_one2many.o_sign_flat_o2m {
// Ajust this value to match the standard components below
padding-right: 42px;
@include media-breakpoint-down(sm) {
padding-right: 0;
}
table {
border-collapse: collapse;
border: none;
}
thead {
display: none;
}
tr.o_data_row {
border-bottom: none !important;
background-color: white !important;
cursor: pointer !important;
}
tbody > tr {
border: none;
border-bottom: none
}
tbody > tr:not(.o_data_row) {
display: none;
}
tbody .o_form_uri:first-line {
color: #666666 !important;
background-color: white;
border: none !important;
box-shadow: none !important;
}
tbody > tr:nth-child(1) {
box-shadow: none !important;
border: none !important;
}
tbody > tr > td:nth-child(1) {
width: 150px !important;
color:#666666 !important;
font-weight: bold;
padding-left: 0px;
cursor: default !important;
border: none;
border-bottom: none
}
tbody > tr > td:nth-child(2) {
border-right-style: none;
&:not([style]) {
border: 1px solid #666666;
border-top-style: none;
border-right-style: none;
border-left-style: none;
}
}
tfoot {
display: none;
}
}
}
/* Module pages */
.o_sign_template, .o_sign_document {
display: flex;
flex-flow: column nowrap;
@include o-position-absolute(0, 0, 0, 0);
@include media-breakpoint-down(sm) {
position: static;
.o_sign_pdf_iframe {
flex: 0 0 500px;
}
}
background-color: $o-view-background-color;
}
/* Template edit view */
.o_sign_template {
.fa-pencil {
cursor: pointer;
opacity: 0.5;
transition: opacity ease 250ms;
}
:hover > .fa-pencil {
opacity: 1.0;
}
.o_sign_template_name_input {
width: auto;
display: inline-block;
border: 1px dashed white;
&:focus {
border: 1px solid silver;
}
&[disabled] {
background-color: transparent;
border: none;
}
}
.alert.o_sign_template_saved_info {
padding: 5px;
opacity: 0;
}
.o_sign_duplicate_sign_template {
padding: 0;
margin-bottom: 2px;
}
}
.o_sign_create_partner {
.fa-exclamation-circle {
padding: 0 10px;
color: theme-color('danger');
}
.fa-check-circle {
padding-left: 10px;
color: theme-color('success');
}
}
.o_sign_add_partner {
border-bottom: 1px dashed $o-brand-secondary;
}
.o_sign_delete_field_button {
float: right;
&:before {
font-family: FontAwesome;
content: "";
}
}
#o_sign_pdf_ext {
padding-top: 0;
padding-bottom: 0;
}
.o_sign_resend_access_button {
padding: 0;
}
.o_popover_offset {
top: 159px !important;
left: 4px !important;
}
}
.o_kanban_view {
.o_kanban_record {
.o_kanban_record_body {
.o_signers {
max-height: 6.3rem;
overflow: auto;
}
}
&.o_sign_sticky_bottom {
.o_kanban_record_body {
margin-bottom: 2rem;
}
.o_kanban_record_bottom {
position: absolute;
bottom: 0.5rem;
width: 100%;
@extend .pr-4;
}
}
}
}
@include media-breakpoint-down(sm) {
.o_kanban_view {
.o_kanban_record {
&.o_sign_sticky_bottom {
.o_kanban_record_body {
margin-bottom: 3rem;
}
.o_kanban_record_bottom {
position: absolute;
bottom: 0.5rem;
width: 100%;
padding-right: 32px !important;
}
}
}
}
}

View File

@ -1,183 +0,0 @@
.o_sign_pdf_iframe {
width: 100%;
height: 100%;
min-height: 0;
flex: 0 1 100%;
border: none;
display: block;
}
.o_sign_request_reference_title {
text-align: left;
}
.o_sign_signer_status_wrapper{
max-height: 76px;
overflow: auto;
}
.o_sign_signer_status {
line-height: 32px;
div {
line-height: $o-line-height-base;
}
&.clearfix {
text-align: right;
}
img {
height: 32px;
}
}
.o_sign_request_from {
text-align: left;
}
.o_sign_request_signer {
line-height: 38px;
font-size: 1.15rem;
margin: 0;
}
.o_sign_breadcrumb {
font-size: 1.25rem !important;
a:hover{
text-decoration: none;
}
}
.o_sign_document_buttons {
margin-top: 5px;
}
.o_sign_request_follower_name {
&:before {
content: ', ';
}
&:first-child:before {
content: none;
}
}
/* Document view */
.o_sign_document {
display: flex;
flex-flow: column nowrap;
background-color: $o-view-background-color;
> .container-fluid {
flex: 0 0 auto;
margin: 0;
}
> .container {
flex: 0 0 auto;
}
.row {
&.o_sign_page_info {
max-height: 33vh;
overflow-y: auto;
}
> div {
> .o_page_header, > .alert {
margin: 10px 0;
}
}
> .o_sign_sign_document_button {
margin: 0 $o-horizontal-padding;
}
}
}
.o_sign_image_document {
display: inline-block;
max-width: 100%;
margin-top: $o-horizontal-padding;
> img {
min-width: 100px;
}
}
.o_sign_validate_banner {
display: none;
opacity: 0;
@include o-position-absolute($bottom: 0, $left: 0);
width: 100%;
padding: 10px;
background-color: $o-brand-odoo;
color: white;
font-size: 1.25em;
text-align: center;
}
.modal.o_sign_thank_you_dialog {
border: none;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.6), 0 6px 20px 0 rgba(0, 0, 0, 0.1);
text-align: left;
.btn {
border-radius: 0;
border: none;
}
.modal-header {
border-top-left-radius: 0.0rem;
border-top-right-radius: 0.0rem;
background-color: $o-brand-odoo;
color: #fff;
.modal-title {
line-height: 1;
color: #fff;
}
.text-muted {
color: #fff !important;
}
.close {
color: #fff;
opacity: 1;
text-shadow: none;
}
}
.modal-body {
color: $o-main-text-color;
.o_thankyou_message {
margin-top: 14px;
}
.o_thankyou_link {
margin-top: 25px;
> img {
width: 30px;
}
a {
color: $o-main-text-color;
&:hover {
text-decoration: none;
color: $o-brand-primary;
}
}
}
}
.modal-footer {
border-top: none;
text-align: left;
display: block;
&:empty {
padding: 0px;
}
}
}
.modal.o_sign_signature_dialog {
.modal-footer {
display: flex;
justify-content: flex-start;
}
}
@media only screen and (max-width:650px) {
.mobile-hide{
display: none !important;
}
.flex-fill {
flex: none;
}
}

View File

@ -1,80 +0,0 @@
.o_sign_document {
height: 100%;
> header {
flex: 0 0 auto;
height: $nav-link-height;
background-color: $o-brand-lightsecondary;
@include media-breakpoint-down(sm) {
height: initial;
min-height: $nav-link-height;
}
> div {
height: 100%;
display: flex;
align-items: center;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
a {
flex: 0 0 auto;
> img {
height: $nav-link-height;
}
}
.o_sign_header_instruction {
font-size: 1.2em;
text-align: center;
}
.o_logo {
text-align: left;
}
.o_odoo {
text-align: right;
}
}
}
@include media-breakpoint-down(sm) {
.o_sign_page_info .col-lg-6 {
padding: 4px;
}
}
}
@include media-breakpoint-down(sm) {
.modal.o_sign_signature_dialog {
h1 {
font-size: $h1-font-size * 3 / 4;
}
h2, h3, h4, h5, h6 {
font-size: $o-font-size-base-touch;
}
.card.o_web_sign_signature_group {
padding: 0;
.row {
flex-wrap: nowrap;
}
}
}
}
.o_sign_portal {
.o_portal_category {
color: $o-enterprise-color;
font-weight:bold;
}
.o_sign_button {
padding-right: 75%;
}
.o_sign_button_content{
font-size: 1.25rem;
font-weight: bold;
}
}

View File

@ -1,108 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="odex25_sign.sign_item_custom_popover">
<div class="o_sign_item_custom_popover">
<div class="form-group clearfix o_popover_placeholder" t-if="widget.debug">
<label for="o_sign_name">Placeholder</label>
<div>
<input type="text" id="o_sign_name" class="o_input"/>
</div>
</div>
<div class="form-group o_sign_options_group">
<label for="o_sign_select_options_input">Options</label>
<div class="o_sign_options_select">
<input id="o_sign_options_select_input" type="hidden"/>
</div>
</div>
<div class="form-group">
<label for="o_sign_responsible_select_input">Filled by</label>
<div class="o_sign_responsible_select">
<select id="o_sign_responsible_select_input"/>
</div>
</div>
<div class="form-group">
<div>
<div class="checkbox">
<label for="o_sign_required_field">
<input type="checkbox" id="o_sign_required_field"/> Mandatory field
</label>
</div>
</div>
</div>
<div class="form-group">
<button class="btn o_sign_validate_field_button btn-primary">Validate</button>
<span class="fa fa-trash fa-lg o_sign_delete_field_button"></span>
</div>
</div>
</t>
<t t-name="odex25_sign.initial_all_pages_dialog">
<div class="form-group">
<label for="responsible_select_initials_input" class="col-md-2">Responsible</label>
<div class="o_sign_responsible_select_initials col-md-10">
<select id="responsible_select_initials_input"/>
</div>
</div>
</t>
<t t-name="sign.template_cp_buttons">
<t t-if="action_type===''||action_type==='sign_send_request'">
<button type="button" class="btn btn-primary mr-2 o_sign_template_send">Send</button>
<button type="button" class="btn btn-primary mr-2 o_sign_template_sign_now">Sign Now</button>
<t t-if="action_type!=='sign_send_request' &amp;&amp; widget.sign_template.sign_request_ids.length&lt;=1">
<button type="button" class="btn btn-secondary mr-2 o_sign_template_share">Share</button>
</t>
</t>
<t t-elif="action_type==='sign_template_edit'">
<button type="button" class="btn btn-primary mr-2 o_sign_template_save">Save</button>
</t>
<t t-if="widget.has_sign_requests &amp;&amp; widget.isPDF">
<div class="alert o_duplicate d-inline">
<span class="fa fa-exclamation-triangle"/> <button type="button" class="o_sign_template_duplicate btn btn-link mb-1 p-0">Duplicate</button> this template to modify it.
</div>
</t>
</t>
<t t-name="sign.template">
<div t-att-class="(widget.has_sign_requests &amp;&amp; widget.isPDF) ? 'd-none' : ''">
<div class="o_sign_template_header_wrapper px-4 py-2 align-items-center d-flex">
<div class="o_sign_template_header_document_name align-items-center d-flex mr-auto">
<span t-if="!widget.has_sign_requests" class="fa fa-pencil mr-2" title="Edit template name" role="img" aria-label="Edit template name"/>
<input type="text" class="o_sign_template_name_input o_input" t-att-value="widget.sign_template.display_name"/>
</div>
<div t-if="widget.debug" class="o_sign_template_privacy_and_save align-items-center d-flex">
<label for="o_sign_template_privacy" class="p-0 m-0 mr-2">Who can Sign:</label>
<div id="o_sign_template_privacy" class="o_sign_template_privacy"/>
<div class="o_sign_template_saved_info alert alert-success m-0 ml-2" role="status"><span class="fa fa-check"/>Saved</div>
</div>
<div t-if="widget.debug" class="o_sign_template_group_id_and_save align-items-center d-flex">
<label for="o_sign_template_group_id" class="p-0 m-0 mr-2">Template Access Group:</label>
<div id="o_sign_template_groupe_id" class="o_sign_template_group_id"/>
<div class="o_sign_template_saved_info alert alert-success m-0 ml-2" role="status"><span class="fa fa-check"/>Saved</div>
</div>
<div class="o_sign_template_tags_and_save align-items-center d-flex">
<label for="o_sign_template_tags" class="p-0 m-0 mr-2">Tags:</label>
<div id="o_sign_template_tags" class="o_sign_template_tags"/>
<div class="o_sign_template_saved_info alert alert-success m-0 ml-2" role="status"><span class="fa fa-check"/>Saved</div>
</div>
</div>
</div>
<t t-if="widget.isPDF">
<iframe class="o_sign_pdf_iframe"/>
</t>
<t t-else="">
<div class="o_sign_image_document">
<t t-set="webimage" t-value="new RegExp('image.*(gif|jpe|jpg|png)').test(widget.sign_template.attachment_id.mimetype)"/>
<img t-if="webimage" class="img img-fluid" t-attf-src="/web/image/{{widget.sign_template.attachment_id.id}}" alt="Signature"/>
<div t-if="!webimage" class="o_image" t-att-data-mimetype="widget.sign_template.attachment_id.mimetype"/>
</div>
</t>
</t>
<t t-name="odex25_sign.type_buttons">
<div class="o_sign_field_type_toolbar_title d-flex justify-content-center align-items-center">Fields</div>
<t t-foreach="sign_item_types" t-as="item_type">
<button type="button" class="o_sign_field_type_button btn btn-primary flex-shrink-0" t-att-data-item-type-id="item_type.id" title="Drag &amp; Drop a field in the PDF"><t t-esc="item_type.name"/></button>
</t>
</t>
</templates>

View File

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="odex25_sign.sign_item" name="Signature Item">
<t t-if="readonly">
<!-- TEMPLATE EDITION or REQUEST DISPLAY -->
<div t-if="type == 'signature' || type == 'initial'" t-att-title="role" class="o_sign_sign_item" style="text-align: center;" t-att-data-signature="value"><span class="o_sign_helper"/> <img t-if="value" t-att-src="value" alt="Signature"/> <t t-if="!value"><span class="o_placeholder"><t t-esc="placeholder"/></span></t> <t t-if="editMode" t-call="odex25_sign.sign_item_configuration"/></div>
<div t-if="type == 'text'" t-att-title="role" class="o_sign_sign_item"><t t-if="!value"><span class="o_placeholder"><t t-esc="placeholder"/></span></t><t t-esc="value"/><t t-if="editMode" t-call="odex25_sign.sign_item_configuration"/></div>
<div t-if="type == 'textarea'" t-att-title="role" class="o_sign_sign_item o_sign_sign_textarea"><t t-if="!value"><span class="o_placeholder"><t t-esc="placeholder"/></span></t><t t-esc="value"/><t t-if="editMode" t-call="odex25_sign.sign_item_configuration"/></div>
<div t-if="type == 'checkbox'" t-att-title="role" class="o_sign_sign_item" style="margin: 2px; padding:2px"><t t-if="value == 'on'">&#9745;</t><t t-if="value == 'off'">&#9744;</t><t t-if="!value"><span class="o_placeholder">&#9745;</span></t><t t-if="editMode" t-call="odex25_sign.sign_item_configuration"/></div>
<div t-if="type == 'selection'" t-att-title="role" class="o_sign_sign_item" style="white-space: normal;text-align: center;"><t t-if="!value"><span class="o_placeholder"><t t-esc="placeholder"/></span></t><div class="o_sign_select_options_display"/><t t-if="editMode" t-call="odex25_sign.sign_item_configuration"/></div>
</t>
<t t-if="!readonly">
<!-- SIGN SESSION : filling the signature values -->
<button t-if="type == 'signature' || type == 'initial'" t-att-title="role" class="o_sign_sign_item text-center" style="color:#757575;" t-att-data-signature="value"><span class="o_sign_helper"/><img t-if="value" t-att-src="value" alt="Signature"/> <t t-if="!value"><span class="o_placeholder"><t t-esc="placeholder"/></span></t></button>
<input t-if="type == 'text'" t-att-title="role" type="text" class="o_sign_sign_item" t-att-placeholder="placeholder" t-att-value="value"/>
<input t-if="type == 'checkbox' and value == 'on'" t-att-title="role" type="checkbox" class="o_sign_sign_item" checked="1"/>
<input t-elif="type == 'checkbox'" t-att-title="role" type="checkbox" class="o_sign_sign_item"/>
<textarea t-if="type == 'textarea'" t-att-title="role" class="o_sign_sign_item" t-att-placeholder="placeholder" t-att-value="value" t-esc="value"/>
<div t-if="type == 'selection'" t-att-title="role" class="o_sign_sign_item" style="white-space: normal;color: #757575;" t-att-value="value"><div class="o_sign_select_options_display"/></div>
</t>
</t>
<div t-name="odex25_sign.sign_item_configuration" class="o_sign_config_area">
<span class="fa fa-arrows" role="img" aria-label="Signature configuration" title="Signature configuration"/>
<div class="o_sign_item_display">
<!-- Don't display role for checkbox. We don't have place-->
<t t-if="type != 'checkbox'"><span class="o_sign_responsible_display"/></t>
</div>
</div>
<!-- Signing part -->
<div t-name="odex25_sign.signature_dialog">
<div class="o_web_sign_name_and_signature"/>
<div class="mt16 small">By clicking Adopt and Sign, I agree that the chosen signature/initials will be a valid electronic representation of my hand-written signature/initials for all purposes when it is used on documents, including legally binding contracts.</div>
</div>
<div t-name="odex25_sign.public_signer_dialog">
<div class="form-group row">
<label for="o_sign_public_signer_name_input" class="col-lg-3 col-form-label">Your name</label>
<div class="col-lg-9">
<input type="text" id="o_sign_public_signer_name_input" placeholder="Your name" class="form-control"/>
</div>
</div>
<div class="form-group row">
<label for="o_sign_public_signer_mail_input" class="col-lg-3 col-form-label">Your email</label>
<div class="col-lg-9">
<input type="email" id="o_sign_public_signer_mail_input" placeholder="Your email" class="form-control"/>
</div>
</div>
</div>
<div t-name="odex25_sign.public_sms_signer">
<div class="form-group row">
<label class="col-sm-3 col-form-label" for="phone">Phone Number</label>
<div class="col-sm">
<div class="input-group">
<input type="text" name="phone" id="o_sign_phone_number_input" placeholder="e.g. +1 415 555 0100" class="form-control" t-att-value="widget.signerPhone"/>
<button class='input-group-append btn btn-sm btn-primary o_sign_resend_sms'>Send SMS</button>
</div>
<span class="text-muted form-text">A SMS will be sent to the following phone number. Please update it if it's not relevant.</span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-3 col-form-label" for="validation_code">Validation Code</label>
<div class="col-sm">
<input type="text" name="validation_code" id="o_sign_public_signer_sms_input" placeholder="e.g. 314159" class="form-control"/>
<span class="text-muted form-text">Enter the code received through SMS to complete your signature</span>
</div>
</div>
</div>
<div t-name="odex25_sign.public_password">
<div class="form-group">
<span>Your file is encrypted, PDF's password is required to generate final document. The final document will be encrypted with the same password.</span>
<div>
<input type="password" id="o_sign_public_signer_password_input" class="form-control"/>
</div>
</div>
</div>
<div t-name="odex25_sign.thank_you_dialog">
<div class="o_thankyou_message">You will receive a copy of the signed document by mail.</div>
<div class="o_thankyou_message" t-if="widget.has_next_document">Other documents have to be signed.</div>
</div>
<div t-name="odex25_sign.no_pub_thank_you_dialog" t-extend="odex25_sign.thank_you_dialog">
<t t-jquery=".o_promote_esign" t-operation="replace"/>
</div>
<div t-name="odex25_sign.next_direct_sign_dialog">
<div class="o_nextdirectsign_message">We will send you this document by email once everyone has signed.<br/>
</div>
</div>
</templates>

View File

@ -1,154 +0,0 @@
odoo.define('odex25_sign.document_backend_tests', function (require) {
"use strict";
var FormView = require('web.FormView');
const framework = require('web.framework');
var testUtils = require('web.test_utils');
var createActionManager = testUtils.createActionManager;
var createView = testUtils.createView;
const DocumentBackend = require('odex25_sign.DocumentBackend');
QUnit.module('document_backend_tests', {
beforeEach: function () {
this.data = {
'partner': {
fields: {
display_name: { string: "Displayed name", type: "char" },
template_id: {string: "Template", type: "many2one", relation: 'sign.template'},
},
records: [{
id: 1,
display_name: "some record",
template_id: 1,
}],
},
'sign.template': {
fields: {
display_name: {string: "Template Name", type: "char"}
},
records: [{
id: 1, display_name: "some template",
}],
},
};
}
}, function () {
QUnit.test('simple rendering', async function (assert) {
assert.expect(4);
const hasFinishedProm = testUtils.makeTestPromise();
testUtils.mock.patch(framework, {
blockUI: function () {
assert.step('blockUI');
},
unblockUI: function () {
assert.step('unblockUI');
hasFinishedProm.resolve();
}
});
var actionManager = await createActionManager({
actions: [{
id: 9,
name: 'A Client Action',
tag: 'odex25_sign.Document',
type: 'ir.actions.client',
context: {id: 5, token: 'abc'},
}],
mockRPC: function (route) {
if (route === '/odex25_sign/get_document/5/abc') {
return Promise.resolve('<span>def</span>');
}
return this._super.apply(this, arguments);
},
});
await actionManager.doAction(9);
await hasFinishedProm;
assert.verifySteps(['blockUI', 'unblockUI']);
assert.strictEqual(actionManager.$('.o_sign_document').text().trim(), 'def',
'should display text from server');
actionManager.destroy();
testUtils.mock.unpatch(framework);
});
QUnit.test('do not crash when leaving the action', async function (assert) {
assert.expect(0);
const proms = [];
testUtils.mock.patch(DocumentBackend, {
_init_page() {
const prom = this._super.apply(this, arguments);
proms.push(prom);
return prom;
}
});
var actionManager = await createActionManager({
actions: [{
id: 9,
name: 'A Client Action',
tag: 'odex25_sign.Document',
type: 'ir.actions.client',
context: {id: 5, token: 'abc'},
}],
mockRPC: function (route) {
if (route === '/odex25_sign/get_document/5/abc') {
return Promise.resolve('<span>def</span>');
}
return this._super.apply(this, arguments);
},
});
await actionManager.doAction(9);
await actionManager.doAction(9);
await Promise.all(proms);
actionManager.destroy();
testUtils.mock.unpatch(DocumentBackend);
});
QUnit.test('search more in many2one pointing to sign.template model', async function (assert) {
// Addon sign patches the ListController for some models, like 'sign.template'.
assert.expect(1);
this.data['sign.template'].records = this.data['sign.template'].records.concat([
{id: 11, display_name: "Template 11"},
{id: 12, display_name: "Template 12"},
{id: 13, display_name: "Template 13"},
{id: 14, display_name: "Template 14"},
{id: 15, display_name: "Template 15"},
{id: 16, display_name: "Template 16"},
{id: 17, display_name: "Template 17"},
]);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form><field name="template_id"/></form>',
archs: {
'sign.template,false,list': '<tree><field name="display_name"/></tree>',
'sign.template,false,search': '<search></search>',
},
});
await testUtils.fields.many2one.clickOpenDropdown('template_id');
await testUtils.fields.many2one.clickItem('template_id', 'Search');
await testUtils.dom.click($('.modal .o_data_row:first'));
assert.strictEqual(form.$('.o_field_widget[name=template_id] input').val(), 'some template');
form.destroy();
});
});
});

View File

@ -1,92 +0,0 @@
odoo.define('sign_widgets_tour', function (require) {
"use strict";
var core = require('web.core');
var tour = require('web_tour.tour');
var _t = core._t;
tour.register('sign_widgets_tour', {
test: true,
url: "/web",
},
[tour.stepUtils.showAppsMenuItem(),
{
content: "Settings",
trigger: 'a[data-menu-xmlid="base.menu_administration"]',
run: 'click',
},
{
content: "Users",
trigger: 'div#invite_users button.o_web_settings_access_rights',
run: 'click',
},
{
content: "Mitchel",
trigger: 'table.o_list_table td.o_data_cell:contains(Admin)',
run: 'click',
},
{
content: "Preference tab",
trigger: 'a.nav-link:contains("' + _t("Preferences") + '")',
run: 'click',
},
{
content: "Check widget sign is present",
trigger: '.o_signature',
},
{
content: "Edit Mitchell",
trigger: 'div.o_cp_buttons .o_form_button_edit',
run: 'click',
},
{
content: "Click on widget sign",
trigger: '.o_signature:first',
run: 'click',
},
{
content: "Click on auto button",
trigger: '.o_web_sign_auto_button',
run: 'click',
},
{
content: "Click on style button",
trigger: 'div.o_web_sign_auto_select_style > a',
run: 'click',
},
{
content: "Select a style",
trigger: 'div.o_web_sign_auto_font_list > a:nth-child(3)',
run: 'click',
},
{
content: "Click on style button",
trigger: 'div.o_web_sign_auto_select_style > a',
run: 'click',
},
{
content: "Select a style",
trigger: 'div.o_web_sign_auto_font_list > a:nth-child(2)',
run: 'click',
},
{
content: "Sign",
trigger: 'button.btn-primary:contains("Adopt and Sign")',
run: 'click',
extra_trigger: 'canvas.jSignature',
run: function () {
setTimeout(() => {
this.$anchor.click();
}, 1000);
},
},
{
content: "Save Mitchell",
trigger: 'button.o_form_button_save',
run: 'click',
},
]
);
});

View File

@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# import tests
from . import test_ui

View File

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
import odoo.tests
@odoo.tests.tagged('-at_install', 'post_install')
class TestUi(odoo.tests.HttpCase):
def test_ui(self):
self.start_tour("/web", 'sign_widgets_tour', login='admin')

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_view_form" model="ir.ui.view">
<field name="name">res.partner.form.odex25_sign.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="groups_id" eval="[(4, ref('odex25_sign.group_sign_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button name="open_signatures" type="object" class="oe_stat_button" icon="fa-pencil"
attrs="{'invisible': ['|', ('signature_count', '=', 0), ('is_company', '=', True)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="signature_count"/></span>
<span class="o_stat_text">Signature Requested</span>
</div>
</button>
</div>
</field>
</record>
</odoo>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_users_view_form" model="ir.ui.view">
<field name="name">res.users.form.odex25_sign.inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="priority">140</field>
<field name="arch" type="xml">
<group name="messaging" position="after">
<group string="Digital Signatures" name="sign" groups="base.group_system">
<field name="sign_signature" widget="signature" options="{'full_name': 'display_name', 'size': ['',200]}"/>
<field name="sign_initials" widget="signature" options="{'size': [200,'']}"/>
</group>
</group>
</field>
</record>
</odoo>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="sign_log_view_tree" model="ir.ui.view">
<field name="name">sign.log.tree</field>
<field name="model">sign.log</field>
<field name="arch" type="xml">
<tree string="Logs">
<field name="log_date"/>
<field name="action"/>
<field name="partner_id"/>
<field name="latitude"/>
<field name="longitude"/>
<field name="ip"/>
<!-- who can acces what ?? groups on the fields for the moment -->
<field name="request_state" optional="hide"/>
</tree>
</field>
</record>
</odoo>

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="compiled_assets_pdf_iframe" name="Sign PDF IFrame Viewer">
<t t-call-assets="odex25_sign.assets_pdf_iframe"/>
</template>
<template id="assets_pdf_iframe" name="Sign PDF IFrame Viewer Assets">
<!-- lib -->
<link rel="stylesheet" type="text/css" href="/web/static/lib/fontawesome/css/font-awesome.css"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/jquery.ui/jquery-ui.css"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/select2/select2.css"/>
<!-- odoo utils -->
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/_functions.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/_variables.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/mixins/_breakpoints.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/mixins/_grid-framework.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/mixins/_grid.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/utilities/_display.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/_grid.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/utilities/_flex.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/src/scss/bs_mixins_overrides.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/mixins/_deprecate.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/lib/bootstrap/scss/mixins/_size.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/src/scss/utils.scss"/>
<link rel="stylesheet" type="text/css" href="/web/static/src/scss/primary_variables.scss"/>
<link rel="stylesheet" type="text/css" href="/web_enterprise/static/src/scss/primary_variables.scss"/>
<link rel="stylesheet" type="text/css" href="/web_tour/static/src/scss/tip.scss"/>
<link rel="stylesheet" type="text/css" href="/web_tour/static/src/scss/keyframes.scss"/>
<!-- integration -->
<link rel="stylesheet" type="text/css" href="/odex25_sign/static/src/css/iframe.css"/>
</template>
</odoo>

View File

@ -1,156 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_my_home_menu_sign" name="Portal layout : sign menu entries" inherit_id="portal.portal_breadcrumbs" priority="60">
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li t-if="page_name == 'signatures' or my_sign_item" t-attf-class="breadcrumb-item #{'active ' if not sign_requests else ''}">
<a t-if="my_sign_item" t-attf-href="/my/signatures?{{ keep_query() }}">Signatures</a>
<t t-else="">Signatures</t>
</li>
<li t-if="my_sign_item" class="breadcrumb-item active">
<span t-field="my_sign_item.reference"/>
</li>
</xpath>
</template>
<template id="portal_my_home_sign" name="Show Signatures" customize_show="True" inherit_id="portal.portal_my_home" priority="60">
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
<t t-call="portal.portal_docs_entry">
<t t-set="title">Signatures</t>
<t t-set="url" t-value="'/my/signatures'"/>
<t t-set="placeholder_count" t-value="'sign_count'"/>
</t>
</xpath>
</template>
<template id="sign_portal_my_requests" name="My Signatures">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<t t-call="portal.portal_searchbar">
<t t-set="title">Signatures</t>
</t>
<div t-if="not grouped_signatures" class="alert alert-warning mt8" role="alert">
There are no signatures request.
</div>
<t t-if="grouped_signatures" t-call="portal.portal_table">
<t t-foreach="grouped_signatures" t-as="signature_requests">
<thead>
<tr t-attf-class="{{'thead-light' if not groupby == 'none' else ''}}">
<th t-if="groupby == 'none'">Document</th>
<th t-else="">
<span t-field="signature_requests[0].sudo().state"/>
</th>
<th class="text-center">Signature Date</th>
<th class="text-right">Status</th>
</tr>
</thead>
<tbody>
<tr t-foreach="signature_requests" t-as="sign_request_item">
<td>
<a t-attf-href="/my/signature/#{sign_request_item.id}?{{ keep_query() }}">
<t t-esc="sign_request_item.reference"/>
</a>
</td>
<td class="text-center">
<span t-field="sign_request_item.signing_date"/>
</td>
<td class="text-right">
<span class="badge badge-pill badge-info"
t-field="sign_request_item.sudo().sign_request_id.state"/>
</td>
</tr>
</tbody>
</t>
</t>
</t>
</template>
<template id="sign_portal_my_request" name="My Signature">
<t t-call="portal.portal_layout">
<t t-set="o_portal_fullwidth_alert" groups="odex25_sign.access_sign_request_item_all">
<t t-call="portal.portal_back_in_edit_mode">
<t t-set="backend_url" t-value="'/web#model=sign.request&amp;id=%s&amp;view_type=form' % (sign.request.id)"/>
</t>
</t>
<t t-call="portal.portal_record_layout">
<t t-set="card_header">
<div class="row no-gutters">
<div class="col-md">
<h5 class="mb-1 mb-md-0">
<span t-field="my_sign_item.reference"/>
</h5>
</div>
<div class="col-md text-md-right">
<span t-field="my_sign_item.sign_request_id.state" class=" badge badge-pill badge-info"
title="Current status of the signature request"/>
</div>
</div>
</t>
<t t-set="card_body">
<div class="o_sign_portal">
<div class="o_sign_button mb-4 text-center align-items-center">
<a t-att-href="url" class="btn btn-primary btn-block">
<div class="o_sign_button_content" t-if="my_sign_item.state == 'completed'">View Document</div>
<div class="o_sign_button_content" t-else="">Sign</div>
</a>
</div>
<div class="row mb-4" t-if="my_sign_item.partner_id">
<div class="col-12 col-md-6 pb-2">
<span class="o_portal_category">Summary</span>
<div>
<strong>Creation Date:</strong>
<span t-field="my_sign_item.create_date" t-options="{'widget': 'date'}"/>
</div>
<div>
<t t-if="my_sign_item.sign_request_id.state != 'signed'">
<t t-foreach="my_sign_item.sign_request_id.request_item_ids" t-as="sign">
<div t-if="odex25_sign.state != 'completed'" class="_status clearfix" t-att-data-id="odex25_sign.id">
<b><t t-esc="odex25_sign.partner_id.name if odex25_sign.partner_id else 'Public user'"/></b>
<t t-if="odex25_sign.role_id"><t t-esc="' - ' + odex25_sign.role_id.name"/></t> - <em>Waiting Signature</em>
<em t-if="odex25_sign.state != 'sent'"><br/>(the email access has not been sent)</em>
</div>
</t>
</t>
<t t-if="my_sign_item.sign_request_id.nb_closed > 0">
<t t-foreach="my_sign_item.sign_request_id.request_item_ids" t-as="sign">
<div t-if="odex25_sign.state == 'completed'" class="_status clearfix"><b>
<t t-esc="odex25_sign.partner_id.name"/></b><t t-if="odex25_sign.role_id">
<t t-esc="' - ' + odex25_sign.role_id.name"/></t> - <em>Signed on <span t-field="odex25_sign.signing_date"></span></em>
</div>
</t>
</t>
</div>
</div>
<div class="coll-12 col-md-6 pb-2" t-if="my_sign_item.partner_id">
<span class="o_portal_category">Your Information</span>
<div class="row">
<div class="col flex-grow-0 pr-3">
<img t-if="my_sign_item.partner_id.image_1024" class="rounded-circle mt-1 o_portal_contact_img"
t-att-src="image_data_uri(my_sign_item.partner_id.image_1024)" alt="Contact"/>
<img t-else="" class="rounded-circle mt-1 o_portal_contact_img"
src="/web/static/src/img/user_menu_avatar.png" alt="Contact"/>
</div>
<div class="col pl-md-0">
<div t-field="my_sign_item.partner_id"
t-options="{'widget': 'contact', 'fields': ['name', 'email', 'phone']}"/>
</div>
</div>
</div>
</div>
</div>
</t>
</t>
<div class="mt32">
<h4>
<strong>Message and communication history</strong>
</h4>
<t t-call="portal.message_thread">
<t t-set="object" t-value="my_sign_item.sign_request_id"/>
<t t-set="token" t-value="my_sign_item.sign_request_id.access_token"/>
<t t-set="pid" t-value="pid"/>
<t t-set="hash" t-value="hash"/>
</t>
</div>
</t>
</template>
</odoo>

View File

@ -1,234 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_common" inherit_id="web.assets_common">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/odex25_sign/static/src/js/sign_common.js"></script>
</xpath>
<xpath expr="link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/odex25_sign/static/src/scss/sign_common.scss"/>
</xpath>
</template>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/odex25_sign/static/src/js/sign_backend.js"></script>
<script type="text/javascript" src="/odex25_sign/static/src/js/tours/odex25_sign.js"></script>
</xpath>
<xpath expr="link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/odex25_sign/static/src/scss/sign_backend.scss"/>
</xpath>
<xpath expr="link[last()]" position="after">
<link rel="stylesheet" type="text/css" href="/odex25_sign/static/src/css/iframe.css"/>
</xpath>
</template>
<template id="assets_frontend" inherit_id="web.assets_frontend">
<xpath expr="link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/odex25_sign/static/src/scss/sign_frontend.scss"/>
</xpath>
</template>
<template id="assets_tests" name="Sign Assets Tests" inherit_id="web.assets_tests">
<xpath expr="." position="inside">
<script type="text/javascript" src="/odex25_sign/static/tests/tours/sign_widgets_tour.js"></script>
</xpath>
</template>
<template id="qunit_suite" name="sign_tests" inherit_id="web.qunit_suite_tests">
<xpath expr="." position="inside">
<script type="text/javascript" src="/odex25_sign/static/tests/document_backend_tests.js"></script>
</xpath>
</template>
<template id="odex25_sign._doc_sign" name="Document Sign">
<div t-if="current_request_item and current_request_item.state == 'sent' and hasItems" class="o_sign_validate_banner">
<button type="button" class="btn btn-primary o_validate_button">Validate &amp; Send Completed Document</button>
</div>
<div class="container-fluid">
<button t-if="current_request_item and current_request_item.state == 'sent' and not hasItems" type="button" class="o_sign_sign_document_button btn btn-primary">Sign Document</button>
<div class="row o_sign_page_info mobile-hide">
<div class="col-lg-4">
<div class="o_sign_request_from">
<span t-if="portal" class="o_sign_breadcrumb"><a t-attf-href="/my/signature/{{current_request_item.id}}?{{ keep_query() }}"
class="o_sign_portal_link">Portal</a> / </span>
<span class="o_sign_request_reference_title o_sign_breadcrumb"><t t-esc="sign_request.reference"/></span>
</div>
<div class="o_sign_document_buttons">
<a role="button" t-if="sign_request.state == 'signed'" t-attf-href="/odex25_sign/download/{{sign_request.id}}/{{sign_request.access_token}}/completed" class="btn btn-primary o_sign_download_document_button">Download Document</a>
<a role="button" t-if="sign_request.state == 'signed'" t-attf-href="/odex25_sign/download/{{sign_request.id}}/{{sign_request.access_token}}/log" class="btn btn-secondary o_sign_download_log_button">Certificate</a>
</div>
</div>
<div class="col-lg-4 mobile-hide">
<div class="o_sign_request_from">
<t t-if="sign_request.create_uid.partner_id.image_128">
<img class="float-left mr-2 mt-1" t-attf-src="data:image/png;base64,#{sign_request.create_uid.partner_id.image_128}" alt="Signature" style="max-width: 32px;"/>
</t>
<t t-if="sign_request.create_uid.partner_id.name">Requested by <b><t t-esc="sign_request.create_uid.partner_id.name"/></b><br/></t>
<t t-if="sign_request.create_uid.partner_id.email"><a t-attf-href="mailto:{{sign_request.create_uid.partner_id.email}}"><t t-esc="sign_request.create_uid.partner_id.email"/></a><br/></t>
</div>
</div>
<div class="o_sign_signer_status_wrapper col-lg-4 d-flex justify-content-end flex-wrap mobile-hide">
<t t-if="sign_request.state != 'signed'">
<t t-foreach="sign_request.request_item_ids" t-as="sign">
<div t-if="odex25_sign.state != 'completed'" class="o_sign_signer_status clearfix pl-4 d-flex" t-att-data-id="odex25_sign.id">
<div class="o_sign_signer_status_info d-flex flex-column">
<div class="text-left"><b><t t-esc="odex25_sign.partner_id.name if odex25_sign.partner_id else 'Public user'"/></b></div>
<div class="text-left"><small><i> Waiting Signature</i><em t-if="odex25_sign.state != 'sent'"><br/>(the email access has not been sent)</em></small></div>
</div>
</div>
</t>
</t>
<t t-if="sign_request.nb_closed > 0">
<t t-foreach="sign_request.request_item_ids" t-as="sign">
<div t-if="odex25_sign.state == 'completed'" class="o_sign_signer_status o_sign_signer_signed clearfix pl-4 d-flex">
<div class="o_sign_signer_status_info d-flex flex-column">
</div>
<div class="text-left">
<div class="text-left"><b><t t-esc="odex25_sign.partner_id.name if odex25_sign.partner_id else 'Public user'"/></b></div>
<div class="text-left"><small><i> Signed on <t t-esc="odex25_sign.signing_date"/></i></small></div>
</div>
<img t-if="odex25_sign.signature" t-attf-src="/web/image/sign.request.item/{{odex25_sign.id}}/signature" alt="Signature"/>
</div>
</t>
</t>
</div>
</div>
<div t-if="not hasItems and not isPDF" class="row">
<div class="col-lg-12">
<a class="o_sign_image_document" t-attf-href="/odex25_sign/download/{{sign_request.id}}/{{sign_request.access_token}}/origin" target="_blank">
<img t-if="webimage" class="img img-fluid" t-attf-src="/odex25_sign/download/{{sign_request.id}}/{{sign_request.access_token}}/origin" alt="Signature"/>
<div t-if="not webimage" class="o_image" t-att-data-mimetype="sign_request.template_id.attachment_id.mimetype"/>
</a>
</div>
</div>
</div>
<t t-if="hasItems or isPDF">
<t t-call="odex25_sign.items_view"/>
</t>
<input id="o_sign_input_sign_request_id" type="hidden" t-att-value="sign_request.id"/>
<input id="o_sign_input_sign_request_token" type="hidden" t-att-value="sign_request.access_token"/>
<input id="o_sign_input_access_token" type="hidden" t-att-value="token"/>
<input id="o_sign_signer_name_input_info" type="hidden" t-att-value="current_request_item.partner_id.name if current_request_item and current_request_item.partner_id else None"/>
<input id="o_sign_signer_phone_input_info" type="hidden" t-att-value="current_request_item.partner_id.mobile if current_request_item and current_request_item.partner_id else None"/>
<input id="o_sign_input_optional_redirect_url" type="hidden" t-att-value="sign_request.template_id.redirect_url"/>
<input id="o_sign_input_optional_redirect_url_text" type="hidden" t-att-value="sign_request.template_id.redirect_url_text"/>
<input t-if="current_request_item and current_request_item.state == 'sent'" id="o_sign_ask_location_input" type="hidden"/>
<t t-if="len(sign_request.request_item_ids) == 1 and not sign_request.request_item_ids[0].partner_id">
<input id="o_sign_is_public_user" type="hidden"/>
</t>
</template>
<template id="odex25_sign.doc_sign" name="Document Sign">
<t t-call="web.layout">
<t t-if="current_request_item and current_request_item.partner_id.lang">
<t t-set="html_data" t-value="{'lang': current_request_item.partner_id.lang.replace('_', '-')}"/>
</t>
<t t-set="head">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<link rel="stylesheet" type="text/scss" href="/web_enterprise/static/src/scss/modal_mobile.scss"/>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="web.assets_frontend" t-css="false"/>
<t t-call="web.conditional_assets_tests"/>
<script type="text/javascript">
odoo.define("odex25_sign.document_custom_page", function (require) {
var ajax = require("web.ajax");
var core = require("web.core");
var document_signing = require("odex25_sign.document_signing");
var rootWidget = require('root.widget');
// YTI FIXME We need the tour to wait the Tip template to be loaded
odoo.__TipTemplateDef = ajax.loadXML("/web_tour/static/src/xml/tip.xml", core.qweb).then(function () {
ajax.loadXML("/odex25_sign/static/src/xml/sign_common.xml", core.qweb).then(function () {
document_signing.initDocumentToSign(rootWidget);
});
});
});
</script>
</t>
<div class="o_sign_document">
<header>
<div class="container-fluid">
<div class="d-flex justify-content-between flex-fill">
<div class="col-lg-4 justify-content-start">
<div class="o_logo">
<a href="/"><img src="/logo.png" alt="Logo"/></a>
</div>
</div>
<div class="col-lg-4 justify-content-center mobile-hide">
<div t-if="sign_request" class="o_sign_header_instruction">
<t t-if="not current_request_item">Need to sign? Check your inbox to get your secure access</t>
<t t-if="current_request_item and current_request_item.state == 'sent'">Please Review And Act On This Document</t>
<t t-if="current_request_item and current_request_item.state == 'completed'">You have completed the document</t>
</div>
</div>
<div class="col-lg-4 justify-content-end">
<div class="o_odoo">
<a href="https://www.odoo.com/page/sign?utm_source=db&amp;utm_medium=sign"><img src="/odex25_sign/static/img/odoo_signed.png" alt="Signed"/></a>
</div>
</div>
</div>
</div>
</header>
<t t-call="odex25_sign._doc_sign"/>
</div>
</t>
</template>
<template id="odex25_sign.encrypted_ask_password" name="PDF Encrypted Password Request">
<t t-call="web.layout">
<t t-set="head">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<link rel="stylesheet" type="text/scss" href="/web_enterprise/static/src/scss/modal_mobile.scss"/>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="web.assets_frontend" t-css="false"/>
</t>
<div class="container text-center">
<h3>Missing Password</h3>
<p>
The PDF's password is required to generate the final document.
</p>
<form string="PDF is encrypted" role="form" method="post" onsubmit="this.action = this.action + location.hash">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div>
<p class="alert alert-danger mb8" t-if="error" role="alert">
<t t-esc="error"/>
</p>
</div>
<input type="password" required="required" name="password" t-att-autofocus="autofocus" maxlength="50"/>
<input type="submit" value="Generate Document" ></input>
</form>
</div>
</t>
</template>
<template id="odex25_sign.deleted_sign_request" name="Missing Signature Request">
<t t-call="web.layout">
<t t-set="head">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_frontend" t-js="false"/>
<link rel="stylesheet" type="text/scss" href="/web_enterprise/static/src/scss/modal_mobile.scss"/>
<t t-call-assets="web.assets_common" t-css="false"/>
<t t-call-assets="web.assets_frontend" t-css="false"/>
</t>
<div class="container">
<h3>Missing signature request</h3>
<p>
The signature access you are trying to reach does not exist. Maybe the signature request has been deleted or modified. <br/>
If there still exists a signature request for this document, check your inbox to get your access!
</p>
</div>
</t>
</template>
</odoo>

View File

@ -1,253 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Signature Request Views -->
<record id="sign_request_view_kanban" model="ir.ui.view">
<field name="name">sign.request.kanban</field>
<field name="model">sign.request</field>
<field name="arch" type="xml">
<kanban quick_create="false" class="o_sign_request_kanban" default_order="create_date desc" sample="1">
<field name="active"/>
<field name="color"/>
<field name="create_uid"/>
<field name="favorited_ids"/>
<field name="last_action_date"/>
<field name="request_item_infos"/>
<field name="state"/>
<field name="activity_ids"/>
<field name="activity_state"/>
<field name="template_tags"/>
<progressbar field="activity_state" colors='{"planned": "success", "overdue": "danger", "today": "warning"}'/>
<templates>
<div t-name="kanban-box" t-attf-class="o_sign_sticky_bottom oe_kanban_global_click {{!selection_mode ? kanban_color(record.color.raw_value) : ''}}">
<div class="oe_kanban_main">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<div class="o_kanban_record_title">
<t t-if="record.favorited_ids.raw_value.indexOf(user_context.uid) &lt; 0">
<a type="object" name="toggle_favorited" title="Not in favorites, add it" aria-label="Not in favorites, add it"
class="fa fa-lg fa-star-o favorite_sign_button"/>
</t>
<t t-else="">
<a type="object" name="toggle_favorited" title="In favorites, remove it" aria-label="In favorites, remove it"
class="fa fa-lg fa-star favorite_sign_button_enabled favorite_sign_button"/>
</t>
<span class="pl-4"><field name="reference"/></span>
</div>
</div>
<div class="o_dropdown_kanban dropdown">
<a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="dropdown-menu" role="menu">
<a role="menuitem" type="object" name="open_request" class="dropdown-item">Details</a>
<a role="menuitem" type="object" name="toggle_active" class="dropdown-item">
<t t-if="!record.active.raw_value">Restore</t>
<t t-if="record.active.raw_value">Archive</t>
</a>
<a role="menuitem" type="delete" groups="base.group_no_one" class="dropdown-item">Delete</a>
<ul class="oe_kanban_colorpicker" data-field="color"/>
</div>
</div>
</div>
<div class="o_kanban_record_body">
<field name="template_tags" widget="many2many_tags" options="{'color_field': 'color'}"/>
<div class="o_signers container-fluid mt-2">
<div t-foreach="record.request_item_infos.raw_value" t-as="request_item_info" t-att-class="(request_item_info.state === 'completed')? 'o_sign_completed row' : 'o_sign_waiting row'">
<input t-att-checked="(request_item_info.state === 'completed') ? 'checked' : undefined" class="mt-1 mr-1" type="checkbox" disabled="True"/>
<span>
<t t-esc="request_item_info.partner_name"/><t t-if="request_item_info.signing_date" class="ml-1" t-esc="' ' + request_item_info.signing_date"/>
</span>
</div>
</div>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<em><t t-esc="moment(record.last_action_date.raw_value).format('L')" /></em>
<div class="o_kanban_inline_block">
<field name="activity_ids" widget="kanban_activity"/>
</div>
</div>
<div class="oe_kanban_bottom_right">
<field name="create_uid" widget="many2one_avatar_user"/>
</div>
</div>
</div>
</div>
</templates>
</kanban>
</field>
</record>
<record id="sign_request_view_tree" model="ir.ui.view">
<field name="name">sign.request.tree</field>
<field name="model">sign.request</field>
<field name="arch" type="xml">
<tree sample="1">
<field name="reference"/>
<field name="template_id"/>
<field name="state"/>
<field name="progress"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
</tree>
</field>
</record>
<record id="sign_request_view_form" model="ir.ui.view">
<field name="name">sign.request.form</field>
<field name="model">sign.request</field>
<field name="arch" type="xml">
<form create="false" edit="false">
<header>
<button string="Resend" type="object" states="canceled" name="action_resend" class="oe_highlight oe_read_only"/>
<button string="Preview" type="object" name="go_to_document" class="oe_highlight"/>
<button string="Download Document" type="object" states="signed" name="get_completed_document" class="oe_read_only oe_highlight"/>
<button name="%(odex25_sign.action_sign_request_send_copy)d" string="Send a Copy" type="action"/>
<button string="Cancel" type="object" states="sent,signed" name="action_canceled" class="oe_read_only" confirm="This will delete all the already completed documents of this request and disable every sent accesses, are you sure?"/>
<field name="state" widget="statusbar" statusbar_visible="sent,signed"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button type="object" name="go_to_document" class="oe_read_only oe_stat_button" icon="fa-pencil">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="nb_closed"/>/<field name="nb_total"/><span class="o_stat_text"> Signed</span></span>
<span class="o_stat_text">View</span>
</div>
</button>
<button type="object" name="open_logs" class="oe_read_only oe_stat_button" icon="fa-pencil" groups="odex25_sign.group_sign_manager">
<!-- TODO show only in debug to sign managers? -->
<div class="o_field_widget o_stat_info">
<span class="o_stat_text">Activity Logs</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Fully Signed" attrs="{'invisible': [('state', '!=', 'signed')]}"/>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<field name="integrity" invisible="1"/>
<div class="oe_title">
<label for="reference" class="oe_edit_only"/>
<h1><field name="reference"/></h1>
</div>
<div class="alert alert-warning" role="alert" attrs="{'invisible': [('integrity', '=', True)]}">
The integrity of the document's history cannot be verified. This could mean that signature values or the underlying PDF document may have been modified after the fact.
</div>
<field name="request_item_ids" context="{'default_sign_request_id': id}" nolabel="1"/>
<group>
<group>
<field name="active" invisible="1"/>
<field name="template_id" invisible="1" kanban_view_ref="%(odex25_sign.sign_template_view_kanban_mobile)s"/>
<field name="favorited_ids" widget="many2many_tags" options="{'color_field': 'color'}" invisible="1"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id="sign_request_view_search" model="ir.ui.view">
<field name="name">sign.request.search</field>
<field name="model">sign.request</field>
<field name="arch" type="xml">
<search>
<field name="reference" string="Document/Signer" filter_domain="['|', '|', ('reference', 'ilike', self), ('request_item_ids.partner_id.email', 'ilike', self), ('request_item_ids.partner_id.name', 'ilike', self)]"/>
<field name="template_id" string="Template or Tag" filter_domain="['|', ('template_id.attachment_id.name', 'ilike', self), ('template_id.tag_ids.name', 'ilike', self)]"/>
<filter name="my_docs" string="My Documents" domain="[('create_uid', '=', uid)]"/>
<filter name="my_sign_request" string="My Requests" domain="[('request_item_ids.partner_id.user_ids', 'like', uid)]"/>
<separator/>
<filter name="favorite" string="My Favorites" domain="[('favorited_ids', 'in', uid)]"/>
<separator/>
<filter name="in_progress" string="To Sign" domain="[('state', '=', 'sent')]"/>
<filter name="signed" string="Signed" domain="[('state', '=', 'signed')]"/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter invisible="1" string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter invisible="1" string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<separator/>
<filter name="all_documents" string="All Documents" domain="['|', ('active', '=', False), ('active', '=', True)]"/>
<group expand="0" string="Group By">
<filter name="group_by_template" string="Template" domain="[]" context="{'group_by': 'template_id'}"/>
</group>
</search>
</field>
</record>
<record id="sign_request_action" model="ir.actions.act_window">
<field name="name">Documents</field>
<field name="res_model">sign.request</field>
<field name="view_mode">kanban,tree,form,activity</field>
<field name="search_view_id" ref="sign_request_view_search"/>
<field name="context" eval="{'search_default_my_docs': 1, 'search_default_my_sign_request': 1}"/>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No documents to sign
</p><p>
Request a signature to upload a template (or use an existing one)
to automate your signature process.
</p>
</field>
</record>
<!-- Signature Request Item Views -->
<record id="sign_request_item_view_tree" model="ir.ui.view">
<field name="name">sign.request.item.tree</field>
<field name="model">sign.request.item</field>
<field name="arch" type="xml">
<tree>
<field name="partner_id"/>
<field name="signer_email" class="oe_read_only"/>
<field name="role_id" class="oe_read_only"/>
<field name="state" class="oe_read_only"/>
</tree>
</field>
</record>
<record id="sign_request_item_view_form" model="ir.ui.view">
<field name="name">sign.request.item.form</field>
<field name="model">sign.request.item</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="partner_id"/>
<field name="role_id"/>
<field name="signer_email"/>
<field name="access_token" invisible="1"/>
<field name="signing_date"/>
</group>
<group>
<field name="latitude"/>
<field name="longitude"/>
<field name="sms_number"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="sign_request_item_action" model="ir.actions.act_window">
<field name="name">Signature Request Items</field>
<field name="res_model">sign.request.item</field>
<field name="view_mode">tree,form</field>
</record>
<!-- After installation of the module, open the related menu -->
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="base.action_open_website"/>
<field name="state">open</field>
</record>
</odoo>

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="odex25_sign.items_view" name="Digital Signatures - Items View">
<iframe class="o_sign_pdf_iframe" t-att-readonly="'readonly' if readonly else None"/>
<input id="o_sign_input_template_name" type="hidden" t-attf-value="{{sign_request.template_id.name}}"/>
<input id="o_sign_input_attachment_location" type="hidden" t-attf-value="/odex25_sign/download/{{sign_request.id}}/{{sign_request.access_token}}/origin"/>
<input id="o_sign_input_current_role" type="hidden" t-att-value="role"/>
<t t-call="odex25_sign.items_input_info"/>
<t t-call="odex25_sign.item_types_input_info"/>
<t t-call="odex25_sign.item_select_options_input_info"/>
</template>
<template id="odex25_sign.items_input_info" name="Items input info">
<t t-if="sign_items">
<t t-foreach="sign_items" t-as="item">
<input type="hidden" class="o_sign_item_input_info"
t-att-data-id="item.id"
t-att-data-type="item.type_id.id"
t-att-data-required="item.required"
t-att-data-name="item.name"
t-att-data-responsible="item.responsible_id.id"
t-att-data-responsible_name="item.responsible_id.name"
t-att-data-page="item.page"
t-att-data-option_ids="item.option_ids.ids"
t-att-data-template_id="item.template_id.ids"
t-att-data-pos-x="str(item.posX)"
t-att-data-pos-y="str(item.posY)"
t-att-data-width="item.width"
t-att-data-height="item.height"
t-att-data-value="item_values[item.id] if item_values and item.id in item_values else None"/>
</t>
</t>
</template>
<template id="odex25_sign.item_types_input_info" name="Item types input info">
<t t-foreach="sign_item_types" t-as="item_type">
<input type="hidden" class="o_sign_field_type_input_info"
t-att-data-id="item_type['id']"
t-att-data-name="item_type['name']"
t-att-data-item_type="item_type['item_type']"
t-att-data-tip="item_type['tip']"
t-att-data-placeholder="item_type['placeholder']"
t-att-data-auto_field="item_type['auto_field']"/>
</t>
</template>
<template id="odex25_sign.item_select_options_input_info" name="Item select options input info">
<t t-foreach="sign_item_select_options" t-as="item_select_option">
<input type="hidden" class="o_sign_select_options_input_info"
t-att-data-id="item_select_option['id']"
t-att-data-value="item_select_option['value']"/>
</t>
</template>
</odoo>

View File

@ -1,334 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Signature Request Template Views -->
<record id="sign_template_view_kanban" model="ir.ui.view">
<field name="name">sign.template.kanban</field>
<field name="model">sign.template</field>
<field name="arch" type="xml">
<kanban quick_create="false" class="o_sign_template_kanban" default_order="create_date desc" sample="1">
<field name="active"/>
<field name="attachment_id"/>
<field name="color"/>
<field name="create_date"/>
<field name="favorited_ids"/>
<field name="responsible_count"/>
<field name="create_uid"/>
<field name="signed_count"/>
<field name="in_progress_count"/>
<templates>
<div t-name="kanban-box" t-attf-class="o_sign_sticky_bottom oe_kanban_global_click {{kanban_color(record.color.raw_value)}}">
<div class="oe_kanban_main">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings">
<div class="o_kanban_record_title">
<t t-if="record.favorited_ids.raw_value.indexOf(user_context.uid) &lt; 0">
<a type="object" name="toggle_favorited" aria-label="Not in favorites, set it" title="Not in favorites, add it"
class="fa fa-lg fa-star-o favorite_sign_button" groups="odex25_sign.group_sign_manager"/>
</t>
<t t-else="">
<a type="object" name="toggle_favorited" aria-label="In favorites, remove it" title="In favorites, remove it"
class="fa fa-lg fa-star favorite_sign_button_enabled favorite_sign_button"/>
</t>
<span class="pl-4"><field name="display_name"/></span>
</div>
</div>
<div class="o_dropdown_kanban dropdown">
<a role="button" class="o_kanban_manage_toggle_button o_left o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
<span class="fa fa-ellipsis-v"/>
</a>
<div class="o_kanban_card_manage_pane dropdown-menu" role="menu">
<t t-if="widget.editable" groups="base.group_no_one"><a role="menuitem" type="edit" class="dropdown-item">Properties</a></t>
<a role="menuitem" type="object" name="go_to_custom_template" class="d-none d-md-block dropdown-item" context="{'sign_edit_call': 'sign_template_edit'}">Modify Template</a>
<a role="menuitem" type="object" name="open_requests" class="dropdown-item">Signed Documents</a>
<a role="menuitem" type="object" name="toggle_active" class="dropdown-item">
<t t-if="!record.active.raw_value">Restore</t>
<t t-if="record.active.raw_value">Archive</t>
</a>
<a role="menuitem" type="delete" class="dropdown-item">Delete</a>
<ul role="menu" class="oe_kanban_colorpicker menu-item" data-field="color"/>
</div>
</div>
</div>
<div class="o_kanban_record_body">
<em><t t-esc="moment(record.create_date.raw_value).format('L')" /></em>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left w-50">
<button name="%(odex25_sign.action_sign_send_request)d" type="action" class="btn btn-primary btn-sm mt8 o_kanban_sign_send_request" context="{'sign_directly_without_mail': 0}">Send</button>
<button name="%(odex25_sign.action_sign_send_request)d" type="action" class="btn btn-primary btn-sm mt8 o_kanban_sign_directly text-truncate" context="{'sign_directly_without_mail': 1}">Sign Now</button>
<button name="%(odex25_sign.action_sign_template_share)d" type="action" class="btn btn-secondary btn-sm mt8" attrs="{'invisible': ['|', ('active', '=', False), ('responsible_count', '&gt;', 1)]}">Share</button>
</div>
<div class="oe_kanban_bottom_right">
<div class="float-right mt8">
<span class="mr-2" title="Number of documents in progress for this template.">
<span class="fa fa-hourglass-half"/>
<field name="in_progress_count"/>
</span>
<span class="mr-2" title="Number of documents signed for this template.">
<span class="fa fa-check ml-1"/>
<field name="signed_count"/>
</span>
</div>
<field name="create_uid" widget="many2one_avatar_user"/>
</div>
</div>
</div>
</div>
</templates>
</kanban>
</field>
</record>
<record id="sign_template_view_tree" model="ir.ui.view">
<field name="name">sign.template.tree</field>
<field name="model">sign.template</field>
<field name="arch" type="xml">
<tree sample="1">
<field name="attachment_id"/>
<field name="create_date"/>
<field name="sign_item_ids"/>
</tree>
</field>
</record>
<record id="sign_template_view_form" model="ir.ui.view">
<field name="name">sign.template.form</field>
<field name="model">sign.template</field>
<field name="arch" type="xml">
<form create="false">
<sheet>
<div class="oe_button_box" name="button_box">
<button type="object" name="open_requests" class="oe_stat_button" icon="fa-pencil-square-o" >
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="signed_count"/>
</span>
<span class="o_stat_text">Signed Document</span>
</div>
</button>
<button string="Edit fields" icon="fa-wrench" type="object" name="go_to_custom_template" class="oe_stat_button"/>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
<h1>
<div class="o_row">
<field name="name" placeholder="Name of the file" nolabel="1"/>
</div>
</h1>
</div>
<group>
<group>
<field name="active" invisible="1"/>
<field name="attachment_id" invisible="1"/>
<field name="responsible_count" invisible="1"/>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" placeholder="Tags"/>
</group>
<group>
<field name="redirect_url" widget="url"/>
<field name="redirect_url_text" attrs="{'invisible':[('redirect_url','=','')]}"/>
<field name="privacy"/>
<field name="favorited_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="group_ids" groups="base.group_no_one" widget="many2many_tags" />
</group>
</group>
<notebook>
<page name="document" string="Document">
<field name="datas" widget="pdf_viewer"/>
</page>
<page string="Fields" name="signatures" groups="base.group_no_one">
<field name="sign_item_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="sign_template_view_search" model="ir.ui.view">
<field name="name">sign.template.search</field>
<field name="model">sign.template</field>
<field name="arch" type="xml">
<search>
<field name="attachment_id" string="Document Name"/>
<field name="tag_ids"/>
<filter name="my_templates" string="My Templates" domain="[('create_uid', '=', uid)]"/>
<filter name="favorite" string="My Favorites" domain="[('favorited_ids', 'in', uid)]"/>
<searchpanel>
<field name="tag_ids" select="multi" icon="fa-tag" enable_counters="1"/>
</searchpanel>
</search>
</field>
</record>
<record id="sign_template_tour_action" model="ir.actions.client">
<field name="name">Template Sample Contract.pdf</field>
<field name="tag">sign.template</field>
<field name="context" eval="{'sign_edit_call': 'sign_send_request', 'id':ref('template_sign_tour'), 'sign_directly_without_mail': False}"/>
</record>
<record id="sign_template_action" model="ir.actions.act_window">
<field name="name">Templates</field>
<field name="res_model">sign.template</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="sign_template_view_search"/>
<field name="context" eval="{'search_default_favorite': 1}"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No template yet
</p><p>
<a type="action" name="%(odex25_sign.sign_template_tour_action)d" class="btn btn-primary text-white">start with our sample template</a>
</p>
</field>
</record>
<!-- Signature Item Views -->
<record id="sign_item_view_tree" model="ir.ui.view">
<field name="name">sign.item.tree</field>
<field name="model">sign.item</field>
<field name="arch" type="xml">
<tree default_order="page,posY,posX,id" editable="bottom">
<field name="type_id"/>
<field name="required"/>
<field name="responsible_id"/>
<field name="page"/>
<field name="posX"/>
<field name="posY"/>
<field name="width"/>
<field name="height"/>
</tree>
</field>
</record>
<record id="sign_item_view_form" model="ir.ui.view">
<field name="name">sign.item.form</field>
<field name="model">sign.item</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group string="Information">
<field name="type_id"/>
<field name="required"/>
<field name="responsible_id"/>
</group>
<group string="Display">
<field name="page"/>
<field name="posX"/>
<field name="posY"/>
<field name="width"/>
<field name="height"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Signature Item Type Views -->
<record id="sign_item_type_view_tree" model="ir.ui.view">
<field name="name">sign.item.type.tree</field>
<field name="model">sign.item.type</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="item_type"/>
<field name="auto_field"/>
</tree>
</field>
</record>
<record id="sign_item_type_view_form" model="ir.ui.view">
<field name="name">sign.item.type.form</field>
<field name="model">sign.item.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<group>
<field name="item_type"/>
<field name="auto_field"/>
</group>
<group>
<group>
<label for="default_width"/>
<div class="o_row">
<field name="default_width"/>
<span>(1.0 = full page size)</span>
</div>
<label for="default_height"/>
<div class="o_row">
<field name="default_height"/>
<span>(1.0 = full page size)</span>
</div>
</group>
<group>
<field name="tip"/>
<field name="placeholder"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="sign_item_type_action" model="ir.actions.act_window">
<field name="name">Signature Item Type</field>
<field name="res_model">sign.item.type</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Signature Item Party Views -->
<record id="sign_item_role_view_tree" model="ir.ui.view">
<field name="name">sign.item.role.tree</field>
<field name="model">sign.item.role</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name" string="Role Name"/>
<field name="sms_authentification"/>
</tree>
</field>
</record>
<record id="sign_item_role_view_form" model="ir.ui.view">
<field name="name">sign.item.role.form</field>
<field name="model">sign.item.role</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<group>
<field name="sms_authentification"/>
<button type="object" name="buy_credits" string="Buy SMS credit" class="btn-primary"></button>
</group>
</sheet>
</form>
</field>
</record>
<record id="sign_item_role_action" model="ir.actions.act_window">
<field name="name">Signature Item Role</field>
<field name="res_model">sign.item.role</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Menus -->
<menuitem id="menu_document" name="Sign" web_icon="sign,static/description/icon.png" groups="odex25_sign.group_sign_employee"/>
<menuitem id="odex25_sign.sign_template_menu" name="Dashboard" parent="odex25_sign.menu_document" action="sign_template_action" sequence="10"/>
<menuitem id="sign_request_menu" name="Documents" parent="menu_document" action="sign_request_action" groups="odex25_sign.group_sign_employee" sequence="20"/>
<menuitem id="menu_sign_configuration" sequence="100" name="Configuration" parent="menu_document"/>
<!-- Menus -->
<menuitem id="odex25_sign.sign_item_type_menu" name="Field Types" parent="odex25_sign.menu_sign_configuration" action="sign_item_type_action" groups="base.group_no_one"/>
<menuitem id="odex25_sign.sign_item_role_menu" name="Roles" parent="odex25_sign.menu_sign_configuration" action="sign_item_role_action" groups="odex25_sign.group_sign_manager"/>
<menuitem id="sign_request_item_menu" name="Signature Requests Items" parent="menu_sign_configuration" action="sign_request_item_action" groups="base.group_no_one" sequence="30"/>
</odoo>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Signature Request Template Views -->
<record id="sign_template_view_kanban_mobile" model="ir.ui.view">
<field name="name">sign.template.kanban.mobile</field>
<field name="model">sign.template</field>
<field name="arch" type="xml">
<kanban>
<field name="color"/>
<templates>
<div t-name="kanban-box" class="oe_kanban_global_click">
<div class="o_kanban_title clearfix">
<span><field name="attachment_id"/></span>
</div>
<div class="oe_kanban_content">
<em><field name="create_date"/></em>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
</div>
</div>
</templates>
</kanban>
</field>
</record>
</odoo>

View File

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
from . import sign_send_request
from . import sign_template_share
from . import sign_request_send_copy

View File

@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class SignRequestSendCopy(models.TransientModel):
_name = 'sign.request.send.copy'
_description = 'Sign send request copy'
request_id = fields.Many2one(
'sign.request',
default=lambda self: self.env.context.get('active_id', None),
)
partner_ids = fields.Many2many('res.partner', string="Contact")
def send_a_copy(self):
return self.env['sign.request'].add_followers(self.request_id.id, self.partner_ids.ids)

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sign_request_send_copy_view_form" model="ir.ui.view">
<field name="name">sign.request.send.copy.view.form</field>
<field name="model">sign.request.send.copy</field>
<field name="arch" type="xml">
<form>
<group>
<field name="partner_ids" widget="many2many_tags"/>
</group>
<footer>
<button string="Send" name="send_a_copy" type="object" class="btn-primary"/>
<button string="Discard" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_sign_request_send_copy" model="ir.actions.act_window">
<field name="name">Send a copy</field>
<field name="res_model">sign.request.send.copy</field>
<field name="target">new</field>
<field name="view_mode">form</field>
</record>
</odoo>

View File

@ -1,140 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class SignSendRequest(models.TransientModel):
_name = 'sign.send.request'
_description = 'Sign send request'
@api.model
def default_get(self, fields):
res = super(SignSendRequest, self).default_get(fields)
if not res.get('template_id'):
return res
template = self.env['sign.template'].browse(res['template_id'])
if 'filename' in fields:
res['filename'] = template.display_name
if 'subject' in fields:
res['subject'] = _("Signature Request - %(file_name)s", file_name=template.attachment_id.name)
if 'signers_count' in fields or 'signer_ids' in fields or 'signer_id' in fields:
roles = template.sign_item_ids.responsible_id
if 'signers_count' in fields:
res['signers_count'] = len(roles)
if 'signer_ids' in fields:
res['signer_ids'] = [(0, 0, {
'role_id': role.id,
'partner_id': False,
}) for role in roles]
if self.env.context.get('sign_directly_without_mail'):
if len(roles) == 1 and 'signer_ids' in fields and res.get('signer_ids'):
res['signer_ids'][0][2]['partner_id'] = self.env.user.partner_id.id
elif not roles and 'signer_id' in fields:
res['signer_id'] = self.env.user.partner_id.id
return res
template_id = fields.Many2one(
'sign.template', required=True, ondelete='cascade',
default=lambda self: self.env.context.get('active_id', None),
)
signer_ids = fields.One2many('sign.send.request.signer', 'sign_send_request_id', string="Signers")
signer_id = fields.Many2one('res.partner', string="Send To")
signers_count = fields.Integer()
follower_ids = fields.Many2many('res.partner', string="Copy to")
is_user_signer = fields.Boolean(compute='_compute_is_user_signer')
subject = fields.Char(string="Subject", required=True)
message = fields.Html("Message")
filename = fields.Char("Filename", required=True)
@api.depends('signer_ids.partner_id', 'signer_id', 'signers_count')
def _compute_is_user_signer(self):
if self.signers_count and self.env.user.partner_id in self.signer_ids.mapped('partner_id'):
self.is_user_signer = True
elif not self.signers_count and self.env.user.partner_id == self.signer_id:
self.is_user_signer = True
else:
self.is_user_signer = False
def create_request(self, send=True, without_mail=False):
template_id = self.template_id.id
if self.signers_count:
signers = [{'partner_id': signer.partner_id.id, 'role': signer.role_id.id} for signer in self.signer_ids]
else:
signers = [{'partner_id': self.signer_id.id, 'role': False}]
followers = self.follower_ids.ids
reference = self.filename
subject = self.subject
message = self.message
return self.env['sign.request'].initialize_new(template_id, signers, followers, reference, subject, message, send, without_mail)
def send_request(self):
res = self.create_request()
request = self.env['sign.request'].browse(res['id'])
return request.go_to_document()
def sign_directly(self):
res = self.create_request()
request = self.env['sign.request'].browse(res['id'])
user_item = request.request_item_ids.filtered(
lambda item: item.partner_id == item.env.user.partner_id)[:1]
return {
'type': 'ir.actions.client',
'tag': 'odex25_sign.SignableDocument',
'name': _('Sign'),
'context': {
'id': request.id,
'token': user_item.access_token,
'sign_token': user_item.access_token,
'create_uid': request.create_uid.id,
'state': request.state,
},
}
def sign_directly_without_mail(self):
res = self.create_request(False, True)
request = self.env['sign.request'].browse(res['id'])
user_item = request.request_item_ids[0]
return {
'type': 'ir.actions.client',
'tag': 'odex25_sign.SignableDocument',
'name': _('Sign'),
'context': {
'id': request.id,
'token': user_item.access_token,
'sign_token': user_item.access_token,
'create_uid': request.create_uid.id,
'state': request.state,
# Don't use mapped to avoid ignoring duplicated signatories
'token_list': [item.access_token for item in request.request_item_ids[1:]],
'current_signor_name': user_item.partner_id.name,
'name_list': [item.partner_id.name for item in request.request_item_ids[1:]],
},
}
class SignSendRequestSigner(models.TransientModel):
_name = "sign.send.request.signer"
_description = 'Sign send request signer'
role_id = fields.Many2one('sign.item.role', readonly=True, required=True)
partner_id = fields.Many2one('res.partner', required=True, string="Contact")
sign_send_request_id = fields.Many2one('sign.send.request')
def create(self, vals_list):
missing_roles = []
for vals in vals_list:
if not vals.get('partner_id'):
role_id = vals.get('role_id')
role = self.env['sign.item.role'].browse(role_id)
missing_roles.append(role.name)
if missing_roles:
missing_roles_str = ', '.join(missing_roles)
raise UserError(_(
'The following roles must be set to create the signature request: %(roles)s',
roles=missing_roles_str,
))
return super().create(vals_list)

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sign_send_request_view_form" model="ir.ui.view">
<field name="name">sign.send.request.view.form</field>
<field name="model">sign.send.request</field>
<field name="arch" type="xml">
<form>
<group col="2">
<field colspan="2" name="signer_ids" placeholder="Write email or search contact..." attrs="{'invisible': [('signers_count', '=', 0)]}" nolabel="1" class="o_sign_flat_o2m">
<tree editable="bottom" create="false" delete="false" >
<field name="role_id" force_save="1" options="{'no_open': True}"/>
<field name="partner_id" placeholder="Name or email..." context="{'force_email': True, 'show_email': True}"/>
</tree>
</field>
<field name="signer_id" attrs="{'invisible': [('signers_count', '!=', 0)], 'required': [('signers_count', '=', 0)]}" context="{'force_email':True, 'show_email': True}"/>
<field name="signers_count" invisible="1"/>
<field name="is_user_signer" invisible="1"/>
<field name="template_id" invisible="1" kanban_view_ref="%(odex25_sign.sign_template_view_kanban_mobile)s"/>
<field name="follower_ids" widget="many2many_tags" placeholder="Write email or search contact..." context="{'show_email': True}"/>
<field name="subject" placeholder="Signature Request" invisible="context.get('sign_directly_without_mail',False)"/>
<field name="message" placeholder="Optional Message..." invisible="context.get('sign_directly_without_mail',False)"/>
<label for="filename" invisible="context.get('sign_directly_without_mail',True)"/>
<div class="o_row" invisible="context.get('sign_directly_without_mail',True)">
<field name="filename" placeholder="Name for the file" nolabel="1" />
</div>
</group>
<footer>
<button string="Sign Now" name="sign_directly" type="object" class="btn-primary" invisible="context.get('sign_directly_without_mail',False)" attrs="{'invisible': [('is_user_signer', '=', False)]}"/>
<button string="Send" name="send_request" type="object" class="btn-primary" invisible="context.get('sign_directly_without_mail',False)"/>
<button string="Sign Now" name="sign_directly_without_mail" type="object" class="btn-primary" invisible="not context.get('sign_directly_without_mail',False)"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_sign_send_request" model="ir.actions.act_window">
<field name="name">Signature Request</field>
<field name="res_model">sign.send.request</field>
<field name="target">new</field>
<field name="view_mode">form</field>
</record>
</odoo>

View File

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
import uuid
from odoo import api, fields, models, _
class SignTemplateShare(models.TransientModel):
_name = 'sign.template.share'
_description = 'Sign Share Template'
@api.model
def default_get(self, fields):
res = super(SignTemplateShare, self).default_get(fields)
if 'url' in fields:
template = self.env['sign.template'].browse(res.get('template_id'))
if template.responsible_count > 1:
res['url'] = False
else:
if not template.share_link:
template.share_link = str(uuid.uuid4())
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
res['url'] = "%s/odex25_sign/%s" % (base_url, template.share_link)
return res
template_id = fields.Many2one(
'sign.template', required=True, ondelete='cascade',
default=lambda s: s.env.context.get("active_id", None),
)
url = fields.Char(string="Link to Share")
is_one_responsible = fields.Boolean()
def open(self):
return {
'name': _('Sign'),
'type': 'ir.actions.act_url',
'url': '/odex25_sign/%s' % (self.template_id.share_link),
}

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sign_template_share_view_form" model="ir.ui.view">
<field name="name">sign.template.share.view.form</field>
<field name="model">sign.template.share</field>
<field name="arch" type="xml">
<form string="Multiple Signature Requests">
<field name="template_id" invisible="1"/>
<span class="text-muted" attrs="{'invisible': [('url', '=', False)]}">Share this link and Odoo will create a new document per person who clicks on the link. The link is private, only those that receive the link will be able to sign it.</span>
<group attrs="{'invisible': [('url', '=', False)]}">
<field name="url" widget="CopyClipboardChar" readonly="1"/>
</group>
<span attrs="{'invisible': [('url', '!=', False)]}">To be able to share, there should be only one responsible for all the fields.</span>
<footer>
<button string="Close" class="btn-primary" special="cancel"/>
<button string="Sign Now" name="open" type="object" attrs="{'invisible': [('url', '=', False)]}"/>
</footer>
</form>
</field>
</record>
<record id="action_sign_template_share" model="ir.actions.act_window">
<field name="name">Share Document by Link</field>
<field name="res_model">sign.template.share</field>
<field name="target">new</field>
<field name="view_mode">form</field>
</record>
</odoo>