Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
import base64
import logging
from io import BytesIO
from odoo import models, fields, api
_logger = logging.getLogger(__name__)
class FusionSaSignatureTemplate(models.Model):
_name = 'fusion.sa.signature.template'
_description = 'SA Mobility Signature Position Template'
_order = 'name'
name = fields.Char(string='Template Name', required=True)
active = fields.Boolean(default=True)
notes = fields.Text(string='Notes')
sa_default_sig_page = fields.Integer(string='Default Signature Page', default=2)
# Absolute PDF point coordinates. Y = distance from top of page.
sa_sig_name_x = fields.Integer(string='Name X', default=105)
sa_sig_name_y = fields.Integer(string='Name Y from top', default=97)
sa_sig_date_x = fields.Integer(string='Date X', default=430)
sa_sig_date_y = fields.Integer(string='Date Y from top', default=97)
sa_sig_x = fields.Integer(string='Signature X', default=72)
sa_sig_y = fields.Integer(string='Signature Y from top', default=68)
sa_sig_w = fields.Integer(string='Signature Width', default=190)
sa_sig_h = fields.Integer(string='Signature Height', default=25)
preview_pdf = fields.Binary(
string='Sample PDF',
help='Upload a sample SA Mobility approval form to preview signature placement.',
attachment=True,
)
preview_pdf_filename = fields.Char(string='PDF Filename')
preview_pdf_page = fields.Integer(
string='Preview Page', default=0,
help='Page to render preview for. 0 = use Default Signature Page.',
)
preview_image = fields.Binary(
string='Preview', readonly=True,
compute='_compute_preview_image',
)
@api.depends(
'preview_pdf', 'preview_pdf_page', 'sa_default_sig_page',
'sa_sig_name_x', 'sa_sig_name_y',
'sa_sig_date_x', 'sa_sig_date_y',
'sa_sig_x', 'sa_sig_y', 'sa_sig_w', 'sa_sig_h',
)
def _compute_preview_image(self):
for rec in self:
if not rec.preview_pdf:
rec.preview_image = False
continue
try:
rec.preview_image = rec._render_preview()
except Exception as e:
_logger.warning("SA template preview render failed: %s", e)
rec.preview_image = False
def _render_preview(self):
"""Render the sample PDF page with colored markers for all signature positions."""
self.ensure_one()
from odoo.tools.pdf import PdfFileReader
pdf_bytes = base64.b64decode(self.preview_pdf)
reader = PdfFileReader(BytesIO(pdf_bytes))
num_pages = reader.getNumPages()
page_num = self.preview_pdf_page or self.sa_default_sig_page or 2
page_idx = page_num - 1
if page_idx < 0 or page_idx >= num_pages:
return False
try:
from pdf2image import convert_from_bytes
except ImportError:
_logger.warning("pdf2image not installed")
return False
images = convert_from_bytes(
pdf_bytes, first_page=page_idx + 1, last_page=page_idx + 1, dpi=150,
)
if not images:
return False
from PIL import ImageDraw, ImageFont
img = images[0]
draw = ImageDraw.Draw(img)
page = reader.getPage(page_idx)
page_w_pts = float(page.mediaBox.getWidth())
page_h_pts = float(page.mediaBox.getHeight())
img_w, img_h = img.size
sx = img_w / page_w_pts
sy = img_h / page_h_pts
try:
font_b = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
font_sm = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
except Exception:
font_b = font = font_sm = ImageFont.load_default()
def _draw_sample_text(label, x_pts, y_top_pts, color, sample_text):
px_x = int(x_pts * sx)
px_y = int(y_top_pts * sy)
draw.text((px_x, px_y - 16), sample_text, fill=color, font=font_b)
draw.text((px_x, px_y + 2), label, fill=color, font=font_sm)
def _draw_box(label, x_pts, y_top_pts, w_pts, h_pts, color):
px_x = int(x_pts * sx)
px_y = int(y_top_pts * sy)
pw = int(w_pts * sx)
ph = int(h_pts * sy)
for off in range(3):
draw.rectangle(
[px_x - off, px_y - off, px_x + pw + off, px_y + ph + off],
outline=color,
)
draw.text((px_x + 4, px_y + 4), label, fill=color, font=font_sm)
_draw_sample_text(
"Name", self.sa_sig_name_x, self.sa_sig_name_y,
'blue', "John Smith",
)
_draw_sample_text(
"Date", self.sa_sig_date_x, self.sa_sig_date_y,
'purple', "2026-02-17",
)
_draw_box(
"Signature", self.sa_sig_x, self.sa_sig_y,
self.sa_sig_w, self.sa_sig_h, 'red',
)
buf = BytesIO()
img.save(buf, format='PNG')
return base64.b64encode(buf.getvalue())
def get_sa_coordinates(self, page_h=792):
"""Convert to ReportLab bottom-origin coordinates.
Template stores Y as distance from TOP of page.
ReportLab uses Y from BOTTOM.
For text (name/date): baseline Y = page_h - y_from_top
For signature image: drawImage Y is bottom-left corner,
so Y = page_h - y_from_top - height
"""
self.ensure_one()
return {
'name_x': self.sa_sig_name_x,
'name_y': page_h - self.sa_sig_name_y,
'date_x': self.sa_sig_date_x,
'date_y': page_h - self.sa_sig_date_y,
'sig_x': self.sa_sig_x,
'sig_y': page_h - self.sa_sig_y - self.sa_sig_h,
'sig_w': self.sa_sig_w,
'sig_h': self.sa_sig_h,
}