fix: CoR server-side PDF, driver→Employee, customer list, address fix, button enable

This commit is contained in:
Westech Admin
2026-05-22 06:02:51 +00:00
parent be06bca0cf
commit 313da27e56
2 changed files with 213 additions and 115 deletions
+130 -62
View File
@@ -1,98 +1,166 @@
import frappe
from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
from reportlab.lib.colors import HexColor, black, white, grey
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.lib import colors
import sqlite3
import io
import os
DB_PATH = '/opt/eim/eim.db'
DARK_BLUE = HexColor('#2F5496')
LIGHT_BLUE = HexColor('#D6E4F0')
GRAY = HexColor('#666666')
def get_device_counts(pallet_number):
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute('SELECT device_type, COUNT(*) as count FROM devices WHERE pallet_number = ? GROUP BY device_type', (pallet_number,))
counts = {row['device_type']: row['count'] for row in cur.fetchall()}
conn.close()
return counts
@frappe.whitelist()
def generate_cor(pallet_number):
pallet = frappe.get_doc('Pallet', pallet_number)
device_counts = get_device_counts(pallet_number)
def generate_cor(company_name=None, weights=None, received_date=None, red_r2=None, contact_name=None, contact_number=None, address_line=None, pallet_name=None):
"""Generate Certificate of Recycling PDF from form data."""
# Format date
date_str = ''
if received_date:
from frappe.utils import formatdate
date_str = formatdate(received_date, 'MMMM d, Y')
items_recycled = 'e-waste'
if red_r2:
items_recycled += ' (' + red_r2 + ')'
output = io.BytesIO()
doc = SimpleDocTemplate(output, pagesize=letter, topMargin=0.75*inch, bottomMargin=0.75*inch, leftMargin=0.75*inch, rightMargin=0.75*inch)
doc = SimpleDocTemplate(
output,
pagesize=letter,
topMargin=0.5 * inch,
bottomMargin=0.5 * inch,
leftMargin=0.75 * inch,
rightMargin=0.75 * inch
)
styles = getSampleStyleSheet()
title_style = ParagraphStyle('CertTitle', parent=styles['Title'], fontSize=20, textColor=DARK_BLUE, spaceAfter=6, alignment=TA_CENTER)
body_style = ParagraphStyle('CertBody', parent=styles['Normal'], fontSize=11, spaceAfter=4)
section_style = ParagraphStyle('SectionHeader', parent=styles['Heading2'], fontSize=14, textColor=DARK_BLUE, spaceBefore=12, spaceAfter=6)
# Custom styles matching the Electron app
date_style = ParagraphStyle('DateBlock', parent=styles['Normal'], fontSize=14, fontName='Times-Bold', alignment=TA_LEFT)
title_style = ParagraphStyle('CertTitle', parent=styles['Title'], fontSize=16, fontName='Times-Bold', textColor=black, spaceAfter=6, alignment=TA_CENTER, letterSpacing=0.05)
cert_style = ParagraphStyle('CertBody', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=12, alignment=TA_JUSTIFY)
body_style = ParagraphStyle('BodyText2', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=10, alignment=TA_JUSTIFY)
bullet_style = ParagraphStyle('BulletText', parent=styles['Normal'], fontName='Times-Roman', fontSize=10, spaceAfter=4, leftIndent=24, bulletIndent=12, alignment=TA_JUSTIFY)
optin_style = ParagraphStyle('OptIn', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=10, alignment=TA_JUSTIFY)
sig_style = ParagraphStyle('Signature', parent=styles['Normal'], fontName='Times-Bold', fontSize=18, spaceBefore=18)
footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontName='Times-Roman', fontSize=10, textColor=GRAY)
elements = []
elements.append(Paragraph('CERTIFICATE OF RECYCLING', title_style))
elements.append(Spacer(1, 6))
intro = 'Full Circle Electronics AZ, LLC (dba Westech Recyclers) certifies that the materials submitted for recycling are received and will be properly recycled in accordance with all state and federal recycling regulations and in accordance with the R2 Standard.'
elements.append(Paragraph(intro, body_style))
elements.append(Spacer(1, 8))
elements.append(Paragraph('Materials Submitted by:', section_style))
# Header row: Date | Logo | Title
logo_path = os.path.join(frappe.get_app_path('westech_r2'), 'public', 'images', 'cor_logo.png')
logo_img = None
if os.path.exists(logo_path):
logo_img = Image(logo_path, width=2.45 * inch, height=0.8 * inch)
pallet_data = [
['Company:', pallet.company_name or pallet.customer_number or 'N/A'],
['Pallet Number:', pallet.pallet_number or pallet_number],
['Date Received:', str(pallet.received_date or 'N/A')],
['Weight:', str(pallet.inbound_weight or 'N/A') + ' lbs'],
['Technician:', pallet.tester or 'N/A'],
header_data = [
[Paragraph(date_str, date_style), logo_img or Paragraph('', styles['Normal']), Paragraph('CERTIFICATE OF RECYCLING', title_style)]
]
header_table = Table(header_data, colWidths=[1.8 * inch, 2.45 * inch, 2.75 * inch])
header_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (0, 0), 'LEFT'),
('ALIGN', (1, 0), (1, 0), 'CENTER'),
('ALIGN', (2, 0), (2, 0), 'CENTER'),
]))
elements.append(header_table)
elements.append(Spacer(1, 18))
pallet_table = Table(pallet_data, colWidths=[1.5*inch, 4*inch])
pallet_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), LIGHT_BLUE),
('TEXTCOLOR', (0, 0), (0, -1), DARK_BLUE),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 10),
# Certification paragraph
elements.append(Paragraph(
'Full Circle Electronics AZ, LLC (dba Westech Recyclers) certifies that the '
'materials submitted for recycling are received and will be properly recycled '
'in accordance with all state and federal recycling regulations and in '
'accordance with the R2 Standard.',
cert_style
))
# Data table
data_rows = [
['Company:', company_name or 'N/A'],
['Weight:', weights or 'N/A'],
['Items Recycled:', items_recycled],
]
if contact_name:
data_rows.append(['Contact:', contact_name])
if contact_number:
data_rows.append(['Phone:', contact_number])
if address_line:
data_rows.append(['Address:', address_line])
data_table = Table(data_rows, colWidths=[3.36 * inch, 3.64 * inch])
data_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Times-Roman'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('ALIGN', (1, 0), (1, -1), 'LEFT'),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('GRID', (0, 0), (-1, -1), 0.5, HexColor('#bfbfbf')),
('TOPPADDING', (0, 0), (-1, -1), 4),
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('RIGHTPADDING', (0, 0), (-1, -1), 6),
]))
elements.append(pallet_table)
elements.append(data_table)
elements.append(Spacer(1, 12))
if device_counts:
elements.append(Paragraph('Device Summary:', section_style))
device_data = [['Device Type', 'Count']]
for dtype, count in sorted(device_counts.items()):
device_data.append([dtype or 'Unknown', str(count)])
device_table = Table(device_data, colWidths=[3*inch, 2*inch])
device_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), DARK_BLUE),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, HexColor('#F2F2F2')]),
]))
elements.append(device_table)
elements.append(Spacer(1, 12))
# Body paragraphs
elements.append(Paragraph(
'Full Circle Electronics AZ, LLC further acknowledges the acceptance and '
'recycling of any material potentially containing data. Data containing '
'materials are stored in our secured facility ensuring the security of the '
'unit(s) prior to data sanitization.',
body_style
))
elements.append(Paragraph(
'Data containing materials are sanitized in compliance with NIST 800-88 '
'guidelines which is set forth by the U.S. government for a robust methodology '
'for erasing data from storage media. Depending upon the media received, the '
'data destruction methods used are as follows:',
body_style
))
# Bullet list
bullets = [
'Hard disk and solid-state drives will either be logically sanitized using professional software or physically destroyed via shredding or degaussing.',
'Media cards and small storage devices will either be degaussed / shredded at our facility or sent straight to a smelter.',
'Data tapes or reels will either be degaussed or shredded at a vetted and approved downstream service provider.',
'Electronics with embedded storage chips will either be destroyed by physical destruction at our facility or at a vetted and approved downstream service provider.',
'Small electronics containing data will either be logically sanitized using the manufacturer\'s application for destroying data or sent to a vetted and approved downstream service provider.',
]
for b in bullets:
elements.append(Paragraph('\u2022 ' + b, bullet_style))
elements.append(HRFlowable(width='100%', thickness=1, color=DARK_BLUE, spaceBefore=12))
footer = Paragraph('Full Circle Electronics AZ, LLC | 220 S 9th St Phoenix, AZ 85034 | www.westechrecyclers.com | 602.256.7626',
ParagraphStyle('Footer', parent=styles['Normal'], fontSize=9, textColor=GRAY, alignment=TA_CENTER))
elements.append(Spacer(1, 6))
elements.append(footer)
# Opt-in
elements.append(Paragraph(
'Opt-in option. If you desire to be informed of our data destruction process '
'changes or be notified of any unlikely security breaches, please let us know.',
optin_style
))
# Signature
elements.append(Paragraph('Westech Recyclers', sig_style))
# Footer
elements.append(Spacer(1, 10))
elements.append(Paragraph(
'220 S 9th St Phoenix, AZ 85034    '
'<link href="http://www.westechrecyclers.com" color="#1155cc">www.westechrecyclers.com</link> &nbsp;&nbsp; '
'602.256.7626',
footer_style
))
doc.build(elements)
output.seek(0)
frappe.response.filename = 'COR_' + pallet_number + '.pdf'
frappe.response.filename = 'COR_' + (company_name or 'document').replace(' ', '_') + '.pdf'
frappe.response.filecontent = output.getvalue()
frappe.response.type = 'download'
frappe.response.display_content_as = 'attachment'
frappe.response.display_content_as = 'attachment'