#!/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}") # Force formula recalculation before saving print("Forcing formula recalculation...") wb.calculation.calcMode = 'auto' wb.calculation.fullCalcOnLoad = True # 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}") # Ensure formulas are marked for recalculation before final save print("Ensuring formulas are marked for recalculation...") wb.calculation.calcMode = 'auto' wb.calculation.fullCalcOnLoad = True # 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")