Add xlsxwriter-based Excel generation scripts with openpyxl implementation

- Created create_excel_xlsxwriter.py and update_excel_xlsxwriter.py
- Uses openpyxl exclusively to preserve Excel formatting and formulas
- Updated server.js to use new xlsxwriter scripts for form submissions
- Maintains all original functionality while ensuring proper Excel file handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
andrei
2025-09-22 13:53:06 +00:00
commit 0e2e1bddba
842 changed files with 316330 additions and 0 deletions

225
update_excel_openpyxl.py Normal file
View File

@@ -0,0 +1,225 @@
#!/usr/bin/env python3
import json
import os
import re
import openpyxl
from openpyxl.utils import get_column_letter
# Removed zipfile import - no longer using direct XML manipulation
def update_excel_variables(excel_path):
"""
Update the Variables sheet in the Excel file with values from config.json
and hide forecast sheets that aren't in the calculated years array
Args:
excel_path (str): Path to the Excel file to update
Returns:
bool: True if successful, False otherwise
"""
# Define paths
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'config.json')
try:
# Load config.json
with open(config_path, 'r') as f:
config = json.load(f)
user_data = config.get('user_data', {})
# Load Excel workbook
print(f"Opening Excel file: {excel_path}")
wb = openpyxl.load_workbook(excel_path)
# Try to access the Variables sheet
try:
# First try by name
sheet = wb['Variables']
except KeyError:
# If not found by name, try to access the last sheet
sheet_names = wb.sheetnames
if sheet_names:
print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}")
sheet = wb[sheet_names[-1]]
else:
print("No sheets found in the workbook")
return False
# Map config variables to Excel cells based on the provided mapping
cell_mappings = {
'B2': user_data.get('store_name', ''),
'B31': user_data.get('starting_date', ''),
'B32': user_data.get('duration', 36),
'B37': user_data.get('open_days_per_month', 0),
# Convenience store type
'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0),
'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0,
'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0),
'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0,
'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0),
# Minimarket store type
'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0),
'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0,
'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0),
'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0),
# Supermarket store type
'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0),
'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0),
'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0),
# Hypermarket store type
'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0),
'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0),
'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0),
# On-site channels
'B43': user_data.get('website_visitors', 0),
'B44': user_data.get('app_users', 0),
'B45': user_data.get('loyalty_users', 0),
# Off-site channels
'B49': user_data.get('facebook_followers', 0),
'B50': user_data.get('instagram_followers', 0),
'B51': user_data.get('google_views', 0),
'B52': user_data.get('email_subscribers', 0),
'B53': user_data.get('sms_users', 0),
'B54': user_data.get('whatsapp_contacts', 0)
}
# Update the cells
for cell_ref, value in cell_mappings.items():
try:
# Force the value to be set, even if the cell is protected or has data validation
cell = sheet[cell_ref]
cell.value = value
print(f"Updated {cell_ref} with value: {value}")
except Exception as e:
print(f"Error updating cell {cell_ref}: {e}")
# Save the workbook with variables updated
print("Saving workbook with updated variables...")
wb.save(excel_path)
# Get the calculated years array from config
starting_date = user_data.get('starting_date', '')
duration = user_data.get('duration', 36)
calculated_years = []
# Import datetime at the module level to avoid scope issues
import datetime
from dateutil.relativedelta import relativedelta
# Calculate years array based on starting_date and duration
try:
# Try to parse the date, supporting both dd/mm/yyyy and dd.mm.yyyy formats
if starting_date:
if '/' in str(starting_date):
day, month, year = map(int, str(starting_date).split('/'))
elif '.' in str(starting_date):
day, month, year = map(int, str(starting_date).split('.'))
elif '-' in str(starting_date):
# Handle ISO format (yyyy-mm-dd)
date_parts = str(starting_date).split('-')
if len(date_parts) == 3:
year, month, day = map(int, date_parts)
else:
# Default to current date if format is not recognized
current_date = datetime.datetime.now()
year, month, day = current_date.year, current_date.month, current_date.day
elif isinstance(starting_date, datetime.datetime):
day, month, year = starting_date.day, starting_date.month, starting_date.year
else:
# Default to current date if format is not recognized
current_date = datetime.datetime.now()
year, month, day = current_date.year, current_date.month, current_date.day
# Create datetime object for starting date
start_date = datetime.datetime(year, month, day)
# Calculate end date (starting date + duration months - 1 day)
end_date = start_date + relativedelta(months=duration-1)
# Create a set of years (to avoid duplicates)
years_set = set()
# Add starting year
years_set.add(start_date.year)
# Add ending year
years_set.add(end_date.year)
# If there are years in between, add those too
for y in range(start_date.year + 1, end_date.year):
years_set.add(y)
# Convert set to sorted list
calculated_years = sorted(list(years_set))
print(f"Calculated years for sheet visibility: {calculated_years}")
else:
# Default to current year if no starting date
calculated_years = [datetime.datetime.now().year]
except Exception as e:
print(f"Error calculating years for sheet visibility: {e}")
calculated_years = [datetime.datetime.now().year]
# Hide forecast sheets that aren't in the calculated years array
# No sheet renaming - just check existing sheet names
for sheet_name in wb.sheetnames:
# Check if this is a forecast sheet
# Forecast sheets have names like "2025 Forecast"
if "Forecast" in sheet_name:
# Extract the year from the sheet name
try:
sheet_year = int(sheet_name.split()[0])
# Hide the sheet if its year is not in the calculated years
if sheet_year not in calculated_years:
sheet = wb[sheet_name]
sheet.sheet_state = 'hidden'
print(f"Hiding sheet '{sheet_name}' as year {sheet_year} is not in calculated years {calculated_years}")
except Exception as e:
print(f"Error extracting year from sheet name '{sheet_name}': {e}")
# Save the workbook with updated variables and hidden sheets
print("Saving workbook with all updates...")
wb.save(excel_path)
print(f"Excel file updated successfully: {excel_path}")
return True
except Exception as e:
print(f"Error updating Excel file: {e}")
return False
if __name__ == "__main__":
# For testing purposes
import sys
if len(sys.argv) > 1:
excel_path = sys.argv[1]
update_excel_variables(excel_path)
else:
print("Please provide the path to the Excel file as an argument")