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,258 @@
# -*- coding: utf-8 -*-
import base64
import logging
from io import BytesIO
from odoo import models, fields, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class OdspReadyDeliveryWizard(models.TransientModel):
_name = 'fusion_claims.odsp.ready.delivery.wizard'
_description = 'Ready for Delivery - Signature Position Setup'
sale_order_id = fields.Many2one(
'sale.order', string='Sale Order', readonly=True, required=True,
)
approval_form = fields.Binary(
string='Approval Form', readonly=True,
)
approval_form_filename = fields.Char(string='Approval Form Filename')
signature_page = fields.Integer(
string='Signature Page', required=True, default=2,
help='Page number containing the signature area (1-indexed)',
)
total_pages = fields.Integer(
string='Total Pages', readonly=True, compute='_compute_total_pages',
)
signature_offset_x = fields.Integer(
string='X Offset (pts)', default=0,
help='Per-case horizontal fine-tune in points (positive = right)',
)
signature_offset_y = fields.Integer(
string='Y Offset (pts)', default=0,
help='Per-case vertical fine-tune in points (positive = up)',
)
preview_image = fields.Binary(
string='Preview', readonly=True,
compute='_compute_preview_image',
)
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
active_id = self.env.context.get('active_id')
if not active_id:
return res
order = self.env['sale.order'].browse(active_id)
res['sale_order_id'] = order.id
res['approval_form'] = order.x_fc_sa_approval_form
res['approval_form_filename'] = order.x_fc_sa_approval_form_filename
tpl = self.env['fusion.sa.signature.template'].search([
('active', '=', True),
], limit=1)
default_page = tpl.sa_default_sig_page if tpl else 2
res['signature_page'] = order.x_fc_sa_signature_page or default_page
res['signature_offset_x'] = order.x_fc_sa_signature_offset_x or 0
res['signature_offset_y'] = order.x_fc_sa_signature_offset_y or 0
return res
@api.depends('approval_form')
def _compute_total_pages(self):
for wiz in self:
if wiz.approval_form:
try:
from odoo.tools.pdf import PdfFileReader
pdf_bytes = base64.b64decode(wiz.approval_form)
reader = PdfFileReader(BytesIO(pdf_bytes))
wiz.total_pages = reader.getNumPages()
except Exception:
wiz.total_pages = 0
else:
wiz.total_pages = 0
@api.depends('approval_form', 'signature_page',
'signature_offset_x', 'signature_offset_y')
def _compute_preview_image(self):
for wiz in self:
if not wiz.approval_form or not wiz.signature_page:
wiz.preview_image = False
continue
try:
wiz.preview_image = wiz._render_preview()
except Exception as e:
_logger.warning("Preview render failed: %s", e)
wiz.preview_image = False
def _get_template_coords(self, page_h=792):
"""Load coordinates from SA Signature Template with per-case offsets."""
tpl = self.env['fusion.sa.signature.template'].search([
('active', '=', True),
], limit=1)
if tpl:
coords = tpl.get_sa_coordinates(page_h)
else:
coords = {
'name_x': 105, 'name_y': page_h - 97,
'date_x': 430, 'date_y': page_h - 97,
'sig_x': 72, 'sig_y': page_h - 72 - 25,
'sig_w': 190, 'sig_h': 25,
}
ox = self.signature_offset_x or 0
oy = self.signature_offset_y or 0
if ox or oy:
for k in ('name_x', 'date_x', 'sig_x'):
if k in coords:
coords[k] += ox
for k in ('name_y', 'date_y', 'sig_y'):
if k in coords:
coords[k] += oy
return coords
def _render_preview(self):
"""Render the selected page as a PNG with a red rectangle showing signature placement."""
from odoo.tools.pdf import PdfFileReader
pdf_bytes = base64.b64decode(self.approval_form)
reader = PdfFileReader(BytesIO(pdf_bytes))
num_pages = reader.getNumPages()
page_idx = (self.signature_page or 2) - 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, cannot generate preview.")
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
scale_x = img_w / page_w_pts
scale_y = img_h / page_h_pts
coords = self._get_template_coords(page_h_pts)
try:
font_b = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 14)
font_sm = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 10)
except Exception:
font_b = font_sm = ImageFont.load_default()
# Signature box (red) -- sig_y is bottom-left in ReportLab
# top edge of box in from-top coords = page_h - (sig_y + sig_h)
sig_from_top = page_h_pts - coords['sig_y'] - coords['sig_h']
px_x = int(coords['sig_x'] * scale_x)
px_y = int(sig_from_top * scale_y)
px_w = int(coords['sig_w'] * scale_x)
px_h = int(coords['sig_h'] * scale_y)
for off in range(3):
draw.rectangle(
[px_x - off, px_y - off, px_x + px_w + off, px_y + px_h + off],
outline='red',
)
draw.text((px_x + 4, px_y + 4), "Signature", fill='red', font=font_sm)
# Name (blue) -- convert ReportLab bottom-origin back to top-origin for PIL
if 'name_x' in coords:
name_from_top = page_h_pts - coords['name_y']
nx = int(coords['name_x'] * scale_x)
ny = int(name_from_top * scale_y)
draw.text((nx, ny - 16), "John Smith", fill='blue', font=font_b)
draw.text((nx, ny + 2), "Name", fill='blue', font=font_sm)
# Date (purple)
if 'date_x' in coords:
date_from_top = page_h_pts - coords['date_y']
dx = int(coords['date_x'] * scale_x)
dy = int(date_from_top * scale_y)
draw.text((dx, dy - 16), "2026-02-17", fill='purple', font=font_b)
draw.text((dx, dy + 2), "Date", fill='purple', font=font_sm)
buf = BytesIO()
img.save(buf, format='PNG')
return base64.b64encode(buf.getvalue())
def action_confirm(self):
"""Save signature settings, advance status, and open the delivery task form."""
self.ensure_one()
order = self.sale_order_id
if self.signature_page < 1 or (self.total_pages and self.signature_page > self.total_pages):
raise UserError(
"Invalid signature page. Must be between 1 and %s." % self.total_pages
)
order.write({
'x_fc_sa_signature_page': self.signature_page,
'x_fc_sa_signature_offset_x': self.signature_offset_x,
'x_fc_sa_signature_offset_y': self.signature_offset_y,
})
return {
'name': 'Schedule Delivery Task',
'type': 'ir.actions.act_window',
'res_model': 'fusion.technician.task',
'view_mode': 'form',
'target': 'new',
'context': {
'default_task_type': 'delivery',
'default_sale_order_id': order.id,
'default_partner_id': order.partner_id.id,
'default_pod_required': True,
'mark_odsp_ready_for_delivery': True,
},
}
def action_preview_full(self):
"""Open the full approval PDF for preview."""
self.ensure_one()
if not self.approval_form:
raise UserError("No approval form available to preview.")
att = self.env['ir.attachment'].search([
('res_model', '=', 'sale.order'),
('res_id', '=', self.sale_order_id.id),
('name', '=', self.approval_form_filename),
], order='create_date desc', limit=1)
if not att:
att = self.env['ir.attachment'].create({
'name': self.approval_form_filename or 'ODSP_Approval.pdf',
'type': 'binary',
'datas': self.approval_form,
'res_model': 'sale.order',
'res_id': self.sale_order_id.id,
'mimetype': 'application/pdf',
})
return {
'type': 'ir.actions.client',
'tag': 'fusion_claims.preview_document',
'params': {
'attachment_id': att.id,
'title': att.name,
},
}