219 lines
7.7 KiB
Python
219 lines
7.7 KiB
Python
# -*- 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',
|
|
)
|
|
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.pdf.template'].search([
|
|
('category', '=', 'odsp'), ('state', '=', 'active'),
|
|
], limit=1)
|
|
default_page = 2
|
|
if tpl and tpl.field_ids:
|
|
default_page = tpl.field_ids[0].page or 2
|
|
|
|
res['signature_page'] = order.x_fc_sa_signature_page or default_page
|
|
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')
|
|
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_fields(self):
|
|
"""Load field positions from the active ODSP PDF Template."""
|
|
tpl = self.env['fusion.pdf.template'].search([
|
|
('category', '=', 'odsp'), ('state', '=', 'active'),
|
|
], limit=1)
|
|
if not tpl:
|
|
return []
|
|
return tpl.field_ids.filtered(lambda f: f.is_active)
|
|
|
|
def _render_preview(self):
|
|
"""Render the selected page as a PNG with colored markers at field positions."""
|
|
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)
|
|
img_w, img_h = img.size
|
|
|
|
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()
|
|
|
|
colors = {
|
|
'text': 'blue',
|
|
'date': 'purple',
|
|
'signature': 'red',
|
|
}
|
|
sample_text = {
|
|
'text': 'John Smith',
|
|
'date': '2026-02-17',
|
|
}
|
|
|
|
for field in self._get_template_fields():
|
|
color = colors.get(field.field_type, 'gray')
|
|
px_x = int(field.pos_x * img_w)
|
|
px_y = int(field.pos_y * img_h)
|
|
|
|
if field.field_type == 'signature':
|
|
px_w = int(field.width * img_w)
|
|
px_h = int(field.height * img_h)
|
|
for off in range(3):
|
|
draw.rectangle(
|
|
[px_x - off, px_y - off, px_x + px_w + off, px_y + px_h + off],
|
|
outline=color,
|
|
)
|
|
draw.text((px_x + 4, px_y + 4), field.label or 'Signature', fill=color, font=font_sm)
|
|
else:
|
|
text = sample_text.get(field.field_type, field.label or field.name)
|
|
draw.text((px_x, px_y - 16), text, fill=color, font=font_b)
|
|
draw.text((px_x, px_y + 2), field.label or field.name, fill=color, font=font_sm)
|
|
|
|
buf = BytesIO()
|
|
img.save(buf, format='PNG')
|
|
return base64.b64encode(buf.getvalue())
|
|
|
|
def action_confirm(self):
|
|
"""Save signature page, 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,
|
|
})
|
|
|
|
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,
|
|
},
|
|
}
|