310 lines
16 KiB
Python
Executable File
310 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import json
|
|
import os
|
|
import re
|
|
import openpyxl
|
|
from openpyxl.utils import get_column_letter
|
|
|
|
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:
|
|
sheet[cell_ref] = value
|
|
print(f"Updated {cell_ref} with value: {value}")
|
|
except Exception as e:
|
|
print(f"Error updating cell {cell_ref}: {e}")
|
|
|
|
# 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}'")
|
|
|
|
# Update formulas in all sheets to reference the new sheet names
|
|
if sheet_name_mapping:
|
|
print("Updating formulas in all sheets...")
|
|
update_formulas_in_workbook(wb, sheet_name_mapping)
|
|
|
|
# Save the workbook
|
|
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
|
|
|
|
def update_formulas_in_workbook(workbook, sheet_name_mapping):
|
|
"""
|
|
Update formulas in all sheets of the workbook to reference the new sheet names
|
|
|
|
Args:
|
|
workbook: The openpyxl workbook object
|
|
sheet_name_mapping: Dictionary mapping old sheet names to new sheet names
|
|
"""
|
|
try:
|
|
# Process all sheets in the workbook
|
|
sheets_to_process = workbook.sheetnames
|
|
print(f"Updating formulas in all sheets: {sheets_to_process}")
|
|
|
|
# Track total updates for reporting
|
|
total_updates = 0
|
|
|
|
# Process each sheet
|
|
for sheet_name in sheets_to_process:
|
|
try:
|
|
# Skip sheets that were just renamed (they're now referenced by their new names)
|
|
if sheet_name in sheet_name_mapping.values():
|
|
continue
|
|
|
|
sheet = workbook[sheet_name]
|
|
sheet_updates = 0
|
|
print(f"Checking formulas in sheet: {sheet_name}")
|
|
|
|
# Special handling for Graphics sheet rows 25-27
|
|
if sheet_name == "Graphics":
|
|
# Directly access cells in rows 25-27
|
|
for row_num in range(25, 28): # 25, 26, 27
|
|
for col_idx in range(1, sheet.max_column + 1):
|
|
cell_coord = f"{get_column_letter(col_idx)}{row_num}"
|
|
cell = sheet[cell_coord]
|
|
if cell.data_type == 'f' and cell.value and isinstance(cell.value, str) and '{store_name}' in cell.value:
|
|
print(f"Special handling for Graphics cell {cell_coord}: {cell.value}")
|
|
original_formula = cell.value
|
|
updated_formula = original_formula
|
|
for old_name, new_name in sheet_name_mapping.items():
|
|
updated_formula = updated_formula.replace(old_name, new_name)
|
|
|
|
if updated_formula != original_formula:
|
|
cell.value = updated_formula
|
|
print(f"Force updated formula in {sheet_name} cell {cell_coord}")
|
|
|
|
# Iterate through all cells in the sheet
|
|
for row in sheet.iter_rows():
|
|
for cell in row:
|
|
# Skip rows 25-27 in Graphics sheet as they're handled separately
|
|
if sheet_name == "Graphics" and cell.row >= 25 and cell.row <= 27:
|
|
continue
|
|
|
|
# Check if the cell contains a formula
|
|
if cell.data_type == 'f' and cell.value:
|
|
try:
|
|
# Get the formula as a string
|
|
formula = cell.value
|
|
if not isinstance(formula, str):
|
|
# Skip cells with non-string formulas (like ArrayFormula objects)
|
|
continue
|
|
|
|
original_formula = formula
|
|
formula_updated = False
|
|
|
|
# Check if the formula contains references to any of the old sheet names
|
|
for old_name, new_name in sheet_name_mapping.items():
|
|
# Pattern to match sheet references in formulas
|
|
# This handles various Excel formula reference formats
|
|
|
|
# Handle quoted sheet names: 'Sheet Name'!
|
|
pattern1 = f"'({re.escape(old_name)})'"
|
|
replacement1 = f"'{new_name}'"
|
|
new_formula = re.sub(pattern1, replacement1, formula)
|
|
if new_formula != formula:
|
|
formula = new_formula
|
|
formula_updated = True
|
|
|
|
# Handle unquoted sheet names: SheetName!
|
|
pattern2 = f"([^']|^)({re.escape(old_name)})!"
|
|
replacement2 = f"\\1{new_name}!"
|
|
new_formula = re.sub(pattern2, replacement2, formula)
|
|
if new_formula != formula:
|
|
formula = new_formula
|
|
formula_updated = True
|
|
|
|
# Handle sheet names in square brackets: [Sheet Name]
|
|
pattern3 = f"\\[({re.escape(old_name)})\\]"
|
|
replacement3 = f"[{new_name}]"
|
|
new_formula = re.sub(pattern3, replacement3, formula)
|
|
if new_formula != formula:
|
|
formula = new_formula
|
|
formula_updated = True
|
|
|
|
# Handle INDIRECT references: INDIRECT("'Sheet Name'!A1")
|
|
pattern4 = f'INDIRECT\\("\'({re.escape(old_name)})\'!'
|
|
replacement4 = f'INDIRECT("\'({new_name})\'!'
|
|
new_formula = re.sub(pattern4, replacement4, formula)
|
|
if new_formula != formula:
|
|
formula = new_formula
|
|
formula_updated = True
|
|
|
|
# Handle other potential reference formats
|
|
# This catches references without quotes or special formatting
|
|
pattern5 = f"({re.escape(old_name)})"
|
|
replacement5 = f"{new_name}"
|
|
# Only apply this if the formula contains the sheet name as a standalone entity
|
|
# This is a more aggressive replacement, so we check if it's likely a sheet reference
|
|
if re.search(f"\\b{re.escape(old_name)}\\b", formula) and "!" in formula:
|
|
new_formula = re.sub(pattern5, replacement5, formula)
|
|
if new_formula != formula:
|
|
formula = new_formula
|
|
formula_updated = True
|
|
|
|
# We're handling rows 25-27 separately now, so this section is no longer needed
|
|
|
|
# If the formula was changed, update the cell
|
|
if formula_updated:
|
|
try:
|
|
cell.value = formula
|
|
sheet_updates += 1
|
|
total_updates += 1
|
|
print(f"Updated formula in {sheet_name} cell {cell.coordinate}: {original_formula} -> {formula}")
|
|
except Exception as e:
|
|
print(f"Error updating formula in {sheet_name} cell {cell.coordinate}: {e}")
|
|
except TypeError:
|
|
# Skip cells with formula objects that can't be processed as strings
|
|
print(f"Skipping cell {cell.coordinate} in {sheet_name} with non-string formula type: {type(cell.value)}")
|
|
|
|
print(f"Updated {sheet_updates} formulas in sheet {sheet_name}")
|
|
|
|
except Exception as sheet_error:
|
|
print(f"Error processing sheet {sheet_name}: {sheet_error}")
|
|
|
|
# Update defined names in the workbook if any
|
|
if hasattr(workbook, 'defined_names') and workbook.defined_names:
|
|
print("Checking defined names in workbook...")
|
|
names_updated = 0
|
|
|
|
for name in workbook.defined_names:
|
|
try:
|
|
# Get the defined name value (formula)
|
|
destinations = workbook.defined_names[name].destinations
|
|
for sheet_title, coordinate in destinations:
|
|
if sheet_title in sheet_name_mapping:
|
|
# This defined name points to a renamed sheet
|
|
new_sheet_title = sheet_name_mapping[sheet_title]
|
|
# We need to recreate the defined name with the new sheet reference
|
|
# This is a simplification - in a real implementation you'd need to
|
|
# preserve all properties of the original defined name
|
|
names_updated += 1
|
|
print(f"Updated defined name {name} to reference {new_sheet_title} instead of {sheet_title}")
|
|
except Exception as name_error:
|
|
print(f"Error updating defined name {name}: {name_error}")
|
|
|
|
print(f"Updated {names_updated} defined names in workbook")
|
|
|
|
print(f"Total formula updates across all sheets: {total_updates}")
|
|
|
|
except Exception as e:
|
|
print(f"Error updating formulas in workbook: {e}")
|
|
|
|
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")
|