Files
bussines_case_automation/update_excel.py

231 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
import json
import os
import re
import openpyxl
from openpyxl.utils import get_column_letter
from zipfile import ZipFile, ZIP_DEFLATED
def update_excel_variables(excel_path):
"""
Update the Variables sheet in the Excel file with values from config.json
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),
# Supermarket store type
'H38': user_data.get('supermarket_store_type', {}).get('stores_number', 0),
'C38': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I38': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J38': user_data.get('supermarket_store_type', {}).get('screen_count', 0),
'K38': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M38': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N38': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0),
# Hypermarket store type
'H39': user_data.get('hypermarket_store_type', {}).get('stores_number', 0),
'C39': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I39': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J39': user_data.get('hypermarket_store_type', {}).get('screen_count', 0),
'K39': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M39': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N39': 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)
# Update sheet names - replace {store_name} with actual store name
store_name = user_data.get('store_name', '')
if store_name:
# Dictionary to store old sheet name to new sheet name mappings
sheet_name_mapping = {}
# Make a copy of the sheet names to avoid modifying during iteration
sheet_names = wb.sheetnames.copy()
for sheet_name in sheet_names:
if '{store_name}' in sheet_name:
new_sheet_name = sheet_name.replace('{store_name}', store_name)
# Get the sheet by its old name
sheet = wb[sheet_name]
# Set the new title
sheet.title = new_sheet_name
# Store the mapping
sheet_name_mapping[sheet_name] = new_sheet_name
print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'")
# Save the workbook with renamed sheets
wb.save(excel_path)
# Use direct XML modification to replace all instances of {store_name} in formulas
print("Using direct XML modification to update all formulas...")
update_excel_with_direct_xml(excel_path, store_name)
print(f"Excel file updated successfully: {excel_path}")
return True
except Exception as e:
print(f"Error updating Excel file: {e}")
return False
def update_excel_with_direct_xml(excel_path, store_name):
"""
Update all references to {store_name} in the Excel file by directly modifying XML
Args:
excel_path: Path to the Excel file
store_name: The store name to replace {store_name} with
Returns:
bool: True if successful, False otherwise
"""
try:
print(f"Using direct XML modification to replace '{{store_name}}' with '{store_name}'...")
# Create a temporary file for modification
temp_dir = os.path.dirname(os.path.abspath(excel_path))
temp_file = os.path.join(temp_dir, "_temp_for_xml_edit.xlsx")
# Make a copy of the original file
import shutil
shutil.copy2(excel_path, temp_file)
# Count of replacements
total_replacements = 0
# Process the Excel file
with ZipFile(temp_file, 'r') as zip_in:
with ZipFile(excel_path, 'w', ZIP_DEFLATED) as zip_out:
# Process each file in the zip
for item in zip_in.infolist():
content = zip_in.read(item.filename)
# Only modify XML files that might contain formulas or text
if item.filename.endswith('.xml'):
# Skip sheet8.xml which is the Variables sheet (based on common Excel structure)
if 'sheet8.xml' in item.filename:
print(f"Skipping Variables sheet: {item.filename}")
else:
# Convert to string for text replacement
try:
text_content = content.decode('utf-8')
# Check if this file contains our placeholder
if '{store_name}' in text_content:
# Count occurrences before replacement
occurrences = text_content.count('{store_name}')
total_replacements += occurrences
# Replace all instances of {store_name} with the actual store name
modified_content = text_content.replace('{store_name}', store_name)
# Convert back to bytes
content = modified_content.encode('utf-8')
print(f"Replaced {occurrences} instances of '{{store_name}}' in {item.filename}")
except UnicodeDecodeError:
# Not a text file, leave as is
pass
# Write the file (original or modified) to the new zip
zip_out.writestr(item, content)
# Clean up the temporary file
if os.path.exists(temp_file):
os.remove(temp_file)
print(f"Total replacements: {total_replacements}")
return True
except Exception as e:
print(f"Error updating Excel file with direct XML modification: {e}")
import traceback
traceback.print_exc()
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")