diff --git a/models/sign_request.py b/models/sign_request.py
index ec05ec4..631a31d 100755
--- a/models/sign_request.py
+++ b/models/sign_request.py
@@ -85,218 +85,5 @@ class SignRequest(models.Model):
return super(SignRequest, self)._sign()
- def _generate_completed_document(self, password=""):
- # We need to override this entire method to handle 'sequence' and 'image' item types
- # as the original method doesn't have hooks or generic handling.
- _logger.info("Starting _generate_completed_document for request %s", self.id)
- self.ensure_one()
- if self.state != 'signed':
- raise UserError(_("The completed document cannot be created because the sign request is not fully signed"))
- if not self.template_id.sign_item_ids:
- _logger.info("No sign items, copying original attachment")
- self.completed_document = self.template_id.attachment_id.datas
- else:
- try:
- _logger.info("Reading original PDF")
- old_pdf = PdfFileReader(io.BytesIO(base64.b64decode(self.template_id.attachment_id.datas)), strict=False, overwriteWarnings=False)
- num_pages = old_pdf.getNumPages()
- _logger.info("Original PDF has %s pages", num_pages)
- except Exception as e:
- _logger.error("Failed to read PDF: %s", e)
- raise ValidationError(_("ERROR: Invalid PDF file!"))
- 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, pagesize=self.get_page_size(old_pdf))
- itemsByPage = self.template_id._get_sign_items_by_page()
- items_ids = [id for items in itemsByPage.values() for id in items.ids]
- _logger.info("Fetching item values for %s items", len(items_ids))
- values_dict = self.env['sign.request.item.value']._read_group(
- [('sign_item_id', 'in', items_ids), ('sign_request_id', '=', self.id)],
- groupby=['sign_item_id'],
- aggregates=['value:array_agg', 'frame_value:array_agg', 'frame_has_hash:array_agg']
- )
- values = {
- sign_item.id : {
- 'value': v[0] if v else None,
- 'frame': f[0] if f else None,
- 'frame_has_hash': h[0] if h else None,
- }
- for sign_item, v, f, h in values_dict
- }
- _logger.info("Retrieved %s item values", len(values))
-
- 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['/Rotate'] if '/Rotate' in page else 0
- if rotation and isinstance(rotation, int):
- 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_dict = values.get(item.id)
- if not value_dict:
- continue
- # only get the 1st
- value = value_dict['value']
- frame = value_dict['frame']
-
- if frame:
- try:
- image_reader = ImageReader(io.BytesIO(base64.b64decode(frame[frame.find(',')+1:])))
- except UnidentifiedImageError:
- raise ValidationError(_("There was an issue downloading your document. Please contact an administrator."))
- _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
- )
-
- # LOGIC FOR SEQUENCE TYPE (Same as text)
- if item.type_id.item_type == "sequence":
- if value:
- value = reshape_text(value)
- can.setFont(font, height*item.height*0.8)
- if item.alignment == "left":
- can.drawString(width*item.posX, height*(1-item.posY-item.height*0.9), value)
- elif item.alignment == "right":
- can.drawRightString(width*(item.posX+item.width), height*(1-item.posY-item.height*0.9), value)
- else:
- can.drawCentredString(width*(item.posX+item.width/2), height*(1-item.posY-item.height*0.9), value)
-
- elif item.type_id.item_type == "text":
- if value:
- value = reshape_text(value)
- can.setFont(font, height*item.height*0.8)
- if item.alignment == "left":
- can.drawString(width*item.posX, height*(1-item.posY-item.height*0.9), value)
- elif item.alignment == "right":
- can.drawRightString(width*(item.posX+item.width), height*(1-item.posY-item.height*0.9), value)
- else:
- can.drawCentredString(width*(item.posX+item.width/2), height*(1-item.posY-item.height*0.9), value)
-
- elif item.type_id.item_type == "selection":
- if value:
- content = []
- for option in item.option_ids:
- if option.id != int(value):
- content.append("%s" % (option.value))
- else:
- content.append(option.value)
- font_size = height * normalFontSize * 0.8
- text = " / ".join(content)
- string_width = stringWidth(text.replace("", "").replace("", ""), font, font_size)
- p = Paragraph(text, ParagraphStyle(name='Selection Paragraph', fontName=font, fontSize=font_size, leading=12))
- posX = width * (item.posX + item.width * 0.5) - string_width // 2
- posY = height * (1 - item.posY - item.height * 0.5) - p.wrap(width, height)[1] // 2
- p.drawOn(can, posX, posY)
-
- elif item.type_id.item_type == "textarea":
- if value:
- font_size = height * normalFontSize * 0.8
- can.setFont(font, font_size)
- lines = value.split('\n')
- y = (1-item.posY)
- for line in lines:
- empty_space = width * item.width - can.stringWidth(line, font, font_size)
- x_shift = 0
- if item.alignment == 'center':
- x_shift = empty_space / 2
- elif item.alignment == 'right':
- x_shift = empty_space
- y -= normalFontSize * 0.9
- line = reshape_text(line)
- can.drawString(width * item.posX + x_shift, 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 == "radio":
- x = width * item.posX
- y = height * (1 - item.posY)
- w = item.width * width
- h = item.height * height
- # Calculate the center of the sign item rectangle.
- c_x = x + w * 0.5
- c_y = y - h * 0.5
- # Draw the outer empty circle.
- can.circle(c_x, c_y, h * 0.5)
- if value == "on":
- # Draw the inner filled circle.
- can.circle(x_cen=c_x, y_cen=c_y, r=h * 0.5 * 0.75, fill=1)
- elif item.type_id.item_type in ["signature", "initial", "image"]:
- if value:
- try:
- image_reader = ImageReader(io.BytesIO(base64.b64decode(value[value.find(',')+1:])))
- except UnidentifiedImageError:
- raise ValidationError(_("There was an issue downloading your document. Please contact an administrator."))
- _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)
-
- try:
- output = io.BytesIO()
- new_pdf.write(output)
- self.completed_document = base64.b64encode(output.getvalue())
- except PdfReadError:
- raise ValidationError(_("There was an issue downloading your document. Please contact an administrator."))
-
- _logger.info("Completed document generated, size: %s bytes", len(self.completed_document))
- output.close()
-
- # Odoo 18 logic to create attachment and link it
- # This matches enterprise/sign/models/sign_request.py:797+
- _logger.info("Creating ir.attachment for completed document")
- 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,
- })
- self.completed_document_attachment_ids = [Command.set([attachment.id])]
- _logger.info("Attachment created: %s", attachment.id)
diff --git a/static/src/xml/sign_item_sequence.xml b/static/src/xml/sign_item_sequence.xml
index 6f782fb..0b281b2 100755
--- a/static/src/xml/sign_item_sequence.xml
+++ b/static/src/xml/sign_item_sequence.xml
@@ -6,7 +6,7 @@
-