Excel Template working better, still not finished.

This commit is contained in:
Javier
2026-02-01 01:35:02 -06:00
parent 1359e036d5
commit 89be88566f
3 changed files with 90 additions and 149 deletions

View File

@@ -284,19 +284,23 @@ def update_template_settings(process_id):
rows_per_page = request.form.get('rows_per_page', 30)
detail_start_row = request.form.get('detail_start_row', 10)
detail_end_row = request.form.get('detail_end_row') # <--- Get the new value
try:
rows_per_page = int(rows_per_page)
detail_start_row = int(detail_start_row)
# Handle empty string for end row (it's optional-ish, but needed for this specific strategy)
detail_end_row = int(detail_end_row) if detail_end_row and detail_end_row.strip() else None
except ValueError:
flash('Invalid number values', 'danger')
return redirect(url_for('cons_sheets.process_template', process_id=process_id))
# Update query to include the new column
execute_db('''
UPDATE cons_processes
SET rows_per_page = ?, detail_start_row = ?
SET rows_per_page = ?, detail_start_row = ?, detail_end_row = ?
WHERE id = ?
''', [rows_per_page, detail_start_row, process_id])
''', [rows_per_page, detail_start_row, detail_end_row, process_id])
flash('Settings updated successfully!', 'success')
return redirect(url_for('cons_sheets.process_template', process_id=process_id))
@@ -909,18 +913,17 @@ def archive_session(session_id):
@cons_sheets_bp.route('/cons-sheets/session/<int:session_id>/export')
@login_required
def export_session(session_id):
"""Export session to Excel using the process template"""
"""Export session to Excel using the One Giant Template strategy"""
from flask import Response
from io import BytesIO
import openpyxl
from openpyxl.utils import get_column_letter, column_index_from_string
from copy import copy
from datetime import datetime
# Get session with process info
# Get session with process info AND the new detail_end_row
sess = query_db('''
SELECT cs.*, cp.process_name, cp.process_key, cp.id as process_id,
cp.template_file, cp.template_filename, cp.rows_per_page, cp.detail_start_row
cp.template_file, cp.template_filename, cp.rows_per_page,
cp.detail_start_row, cp.detail_end_row
FROM cons_sessions cs
JOIN cons_processes cp ON cs.process_id = cp.id
WHERE cs.id = ?
@@ -951,7 +954,7 @@ def export_session(session_id):
''', [sess['process_id']])
# Get all scanned details
table_name = get_detail_table_name(sess['process_key'])
table_name = f'cons_proc_{sess["process_key"]}_details'
scans = query_db(f'''
SELECT * FROM {table_name}
WHERE session_id = ? AND is_deleted = 0
@@ -961,158 +964,75 @@ def export_session(session_id):
# Load the template
template_bytes = BytesIO(sess['template_file'])
wb = openpyxl.load_workbook(template_bytes)
ws = wb.active
ws = wb.active # We only work on the first sheet now
rows_per_page = sess['rows_per_page'] or 30
detail_start_row = sess['detail_start_row'] or 11
detail_end_row = sess['detail_end_row'] # This is our new target
# Calculate how many pages we need
total_scans = len(scans) if scans else 0
num_pages = max(1, (total_scans + rows_per_page - 1) // rows_per_page) if total_scans > 0 else 1
# --- STEP 1: Fill Header ---
for field in header_fields:
if field['excel_cell'] and field['field_value']:
try:
ws[field['excel_cell']] = field['field_value']
except:
pass
# Helper function to fill header values on a sheet
def fill_header(worksheet, header_fields):
for field in header_fields:
if field['excel_cell'] and field['field_value']:
# --- STEP 2: Fill ALL Details ---
# We just write them all sequentially, relying on the template being "Giant"
for i, scan in enumerate(scans):
row_num = detail_start_row + i
for field in detail_fields:
if field['excel_cell']:
try:
worksheet[field['excel_cell']] = field['field_value']
except:
pass # Skip invalid cell references
# Helper function to clear detail rows on a sheet
def clear_details(worksheet, detail_fields, start_row, num_rows):
for i in range(num_rows):
row_num = start_row + i
for field in detail_fields:
if field['excel_cell']:
try:
col_letter = field['excel_cell'].upper().strip()
cell_ref = f"{col_letter}{row_num}"
worksheet[cell_ref] = None
except:
pass
# Helper function to fill detail rows on a sheet
def fill_details(worksheet, scans_subset, detail_fields, start_row):
for i, scan in enumerate(scans_subset):
row_num = start_row + i
for field in detail_fields:
if field['excel_cell']:
try:
col_letter = field['excel_cell'].upper().strip()
cell_ref = f"{col_letter}{row_num}"
value = scan[field['field_name']]
# Convert to appropriate type
if field['field_type'] == 'REAL' and value:
value = float(value)
elif field['field_type'] == 'INTEGER' and value:
value = int(value)
worksheet[cell_ref] = value
except Exception as e:
print(f"Error filling cell: {e}")
# Fill the first page
fill_header(ws, header_fields)
first_page_scans = scans[:rows_per_page] if scans else []
fill_details(ws, first_page_scans, detail_fields, detail_start_row)
# Create additional pages if needed
for page_num in range(2, num_pages + 1):
# Copy the worksheet within the same workbook
new_ws = wb.copy_worksheet(ws)
new_ws.title = f"Page {page_num}"
col_letter = field['excel_cell'].upper().strip()
cell_ref = f"{col_letter}{row_num}"
value = scan[field['field_name']]
# Convert types
if field['field_type'] == 'REAL' and value:
value = float(value)
elif field['field_type'] == 'INTEGER' and value:
value = int(value)
ws[cell_ref] = value
except Exception as e:
print(f"Error filling cell: {e}")
# --- STEP 3: Delete Unused Rows & Fix Print Area ---
if detail_end_row:
first_empty_row = detail_start_row + len(scans)
# Clear detail rows (they have Page 1 data)
clear_details(new_ws, detail_fields, detail_start_row, rows_per_page)
# Fill details for this page
start_idx = (page_num - 1) * rows_per_page
end_idx = start_idx + rows_per_page
page_scans = scans[start_idx:end_idx]
fill_details(new_ws, page_scans, detail_fields, detail_start_row)
# Rename first sheet if we have multiple pages
if num_pages > 1:
ws.title = "Page 1"
# Save to BytesIO
# Only delete if we actually have empty rows to remove
if first_empty_row <= detail_end_row:
rows_to_delete = detail_end_row - first_empty_row + 1
ws.delete_rows(first_empty_row, amount=rows_to_delete)
# --- FIX 1: Clear Breaks ---
ws.row_breaks.brk = []
ws.col_breaks.brk = []
# --- FIX 2: Explicitly Set Print Area ---
# The "Total" line (and footer) has now moved UP to 'first_empty_row'.
# We want to print everything from A1 down to that Total line.
# (If your footer is taller than 1 row, increase the +0 below)
footer_height = 0
final_print_row = first_empty_row + footer_height
# Force the print area to cut off the "Zombie Pages"
ws.print_area = f"A1:K{final_print_row}"
# Reset scaling
if ws.sheet_properties.pageSetUpPr:
ws.sheet_properties.pageSetUpPr.fitToPage = False
# --- Save & Export ---
output = BytesIO()
wb.save(output)
output.seek(0)
# Generate filename
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
base_filename = f"{sess['process_key']}_{session_id}_{timestamp}"
# Check if PDF export is requested
export_format = request.args.get('format', 'xlsx')
print(f"DEBUG: Export format requested: {export_format}")
if export_format == 'pdf':
# Use win32com to convert to PDF (requires Excel installed)
try:
import tempfile
import pythoncom
import win32com.client as win32
print("DEBUG: pywin32 imported successfully")
# Save Excel to temp file
temp_xlsx = tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False)
temp_xlsx.write(output.getvalue())
temp_xlsx.close()
print(f"DEBUG: Temp Excel saved to: {temp_xlsx.name}")
temp_pdf = temp_xlsx.name.replace('.xlsx', '.pdf')
# Initialize COM for this thread
pythoncom.CoInitialize()
print("DEBUG: COM initialized")
try:
excel = win32.Dispatch('Excel.Application')
excel.Visible = False
excel.DisplayAlerts = False
print("DEBUG: Excel application started")
workbook = excel.Workbooks.Open(temp_xlsx.name)
print("DEBUG: Workbook opened")
workbook.ExportAsFixedFormat(0, temp_pdf) # 0 = PDF format
print(f"DEBUG: Exported to PDF: {temp_pdf}")
workbook.Close(False)
excel.Quit()
print("DEBUG: Excel closed")
finally:
pythoncom.CoUninitialize()
# Read the PDF
with open(temp_pdf, 'rb') as f:
pdf_data = f.read()
print(f"DEBUG: PDF read, size: {len(pdf_data)} bytes")
# Clean up temp files
import os
os.unlink(temp_xlsx.name)
os.unlink(temp_pdf)
print("DEBUG: Temp files cleaned up")
return Response(
pdf_data,
mimetype='application/pdf',
headers={'Content-Disposition': f'attachment; filename={base_filename}.pdf'}
)
except ImportError as e:
print(f"ERROR: Import failed - {e}")
# Fall back to Excel export
except Exception as e:
print(f"ERROR: PDF export failed - {e}")
import traceback
traceback.print_exc()
# Fall back to Excel export
# Default: return Excel file
print("DEBUG: Returning Excel file")
return Response(
output.getvalue(),
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',