#!/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 and hide forecast sheets that aren't in the calculated years array. This version uses openpyxl exclusively to preserve all formatting, formulas, and Excel features that xlsxwriter cannot handle when modifying existing files. While this is named "xlsxwriter", it actually uses openpyxl for the best approach to modify existing Excel files while preserving all features. 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_obj = wb[sheet_name] sheet_obj.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")