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
col_letter = field['excel_cell'].upper().strip()
cell_ref = f"{col_letter}{row_num}"
value = scan[field['field_name']]
# 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
# Convert types
if field['field_type'] == 'REAL' and value:
value = float(value)
elif field['field_type'] == 'INTEGER' and value:
value = int(value)
# 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}")
ws[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)
# --- STEP 3: Delete Unused Rows & Fix Print Area ---
if detail_end_row:
first_empty_row = detail_start_row + len(scans)
# 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}"
# 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)
# Clear detail rows (they have Page 1 data)
clear_details(new_ws, detail_fields, detail_start_row, rows_per_page)
# --- FIX 1: Clear Breaks ---
ws.row_breaks.brk = []
ws.col_breaks.brk = []
# 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)
# --- 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
# Rename first sheet if we have multiple pages
if num_pages > 1:
ws.title = "Page 1"
# Force the print area to cut off the "Zombie Pages"
ws.print_area = f"A1:K{final_print_row}"
# Save to BytesIO
# 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',

View File

@@ -210,6 +210,19 @@ def migration_006_add_is_deleted_to_locationcounts():
conn.commit()
conn.close()
def migration_007_add_detail_end_row():
"""Add detail_end_row column to cons_processes table"""
conn = get_db()
if table_exists('cons_processes'):
if not column_exists('cons_processes', 'detail_end_row'):
conn.execute('ALTER TABLE cons_processes ADD COLUMN detail_end_row INTEGER')
print(" Added detail_end_row column to cons_processes")
conn.commit()
conn.close()
# List of all migrations in order
MIGRATIONS = [
(1, 'add_modules_tables', migration_001_add_modules_tables),
@@ -218,6 +231,7 @@ MIGRATIONS = [
(4, 'assign_modules_to_admins', migration_004_assign_modules_to_admins),
(5, 'add_cons_process_fields_duplicate_key', migration_005_add_cons_process_fields_duplicate_key),
(6, 'add_is_deleted_to_locationcounts', migration_006_add_is_deleted_to_locationcounts),
(7, 'add_detail_end_row', migration_007_add_detail_end_row),
]

View File

@@ -52,17 +52,24 @@
<div class="form-group">
<label for="rows_per_page" class="form-label">Rows Per Page</label>
<input type="number" id="rows_per_page" name="rows_per_page"
value="{{ process.rows_per_page or 30 }}" min="1" max="500" class="form-input">
value="{{ process.rows_per_page or 30 }}" min="1" max="5000" class="form-input">
<p class="form-hint">Max detail rows before starting a new page</p>
</div>
<div class="form-group">
<label for="detail_start_row" class="form-label">Detail Start Row</label>
<input type="number" id="detail_start_row" name="detail_start_row"
value="{{ process.detail_start_row or 10 }}" min="1" max="500" class="form-input">
value="{{ process.detail_start_row or 10 }}" min="1" max="5000" class="form-input">
<p class="form-hint">Excel row number where detail data begins</p>
</div>
<div class="form-group">
<label for="detail_end_row" class="form-label">Detail End Row (Footer Start)</label>
<input type="number" id="detail_end_row" name="detail_end_row"
value="{{ process.detail_end_row or '' }}" min="1" class="form-input">
<p class="form-hint">The row number where your pre-made blank lines end. Unused rows up to this point will be deleted.</p>
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
</div>