Fix Excel formula references when renaming forecast sheets
This commit is contained in:
Binary file not shown.
352
update_excel.py
352
update_excel.py
@@ -124,10 +124,9 @@ def update_excel_variables(excel_path):
|
|||||||
sheet_name_mapping[sheet_name] = new_sheet_name
|
sheet_name_mapping[sheet_name] = new_sheet_name
|
||||||
print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'")
|
print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'")
|
||||||
|
|
||||||
# Update formulas in all sheets to reference the new sheet names
|
# Update formulas in the Graphics sheet to reference the new sheet names
|
||||||
if sheet_name_mapping:
|
if sheet_name_mapping and 'Graphics' in wb.sheetnames:
|
||||||
print("Updating formulas in all sheets...")
|
update_formula_references(wb, sheet_name_mapping)
|
||||||
update_formulas_in_workbook(wb, sheet_name_mapping)
|
|
||||||
|
|
||||||
# Save the workbook
|
# Save the workbook
|
||||||
wb.save(excel_path)
|
wb.save(excel_path)
|
||||||
@@ -138,224 +137,165 @@ def update_excel_variables(excel_path):
|
|||||||
print(f"Error updating Excel file: {e}")
|
print(f"Error updating Excel file: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_formulas_in_workbook(workbook, sheet_name_mapping):
|
def update_formula_references(workbook, sheet_name_mapping):
|
||||||
"""
|
"""
|
||||||
Update formulas in all sheets of the workbook to reference the new sheet names
|
Update formula references in the Graphics sheet to point to the renamed sheets
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
workbook: The openpyxl workbook object
|
workbook: The openpyxl workbook object
|
||||||
sheet_name_mapping: Dictionary mapping old sheet names to new sheet names
|
sheet_name_mapping: Dictionary mapping old sheet names to new sheet names
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Process all sheets in the workbook
|
# Get the Graphics sheet
|
||||||
sheets_to_process = workbook.sheetnames
|
graphics_sheet = workbook['Graphics']
|
||||||
print(f"Updating formulas in all sheets: {sheets_to_process}")
|
print("Updating formula references in Graphics sheet...")
|
||||||
|
|
||||||
# Track total updates for reporting
|
# Track the number of updates
|
||||||
total_updates = 0
|
updates_count = 0
|
||||||
|
total_formulas = 0
|
||||||
|
|
||||||
# Process each sheet
|
# Create a dictionary to track which cells have been processed
|
||||||
for sheet_name in sheets_to_process:
|
processed_cells = {}
|
||||||
try:
|
|
||||||
# Skip sheets that were just renamed (they're now referenced by their new names)
|
# First pass: Identify all cells with formulas that reference the forecast sheets
|
||||||
if sheet_name in sheet_name_mapping.values():
|
formula_cells = []
|
||||||
continue
|
|
||||||
|
# Iterate through all cells in the Graphics sheet
|
||||||
|
for row in graphics_sheet.iter_rows():
|
||||||
|
for cell in row:
|
||||||
|
# Check if the cell contains a formula
|
||||||
|
if cell.data_type == 'f' and cell.value and isinstance(cell.value, str):
|
||||||
|
total_formulas += 1
|
||||||
|
original_formula = cell.value
|
||||||
|
|
||||||
sheet = workbook[sheet_name]
|
# Check if the formula references any of the renamed sheets
|
||||||
sheet_updates = 0
|
references_renamed_sheet = False
|
||||||
print(f"Checking formulas in sheet: {sheet_name}")
|
for old_name in sheet_name_mapping.keys():
|
||||||
|
if old_name in original_formula:
|
||||||
# Special handling for Graphics sheet rows 25-27
|
references_renamed_sheet = True
|
||||||
if sheet_name == "Graphics":
|
break
|
||||||
# We'll create similar formulas for columns H, O, U, AA, AG based on column C
|
|
||||||
# First, let's process column C for rows 25-27 as usual
|
if references_renamed_sheet:
|
||||||
for row_num in range(25, 28): # 25, 26, 27
|
formula_cells.append((cell, original_formula))
|
||||||
# Process column C first
|
|
||||||
cell_coord_c = f"C{row_num}"
|
|
||||||
try:
|
|
||||||
cell_c = sheet[cell_coord_c]
|
|
||||||
if cell_c.data_type == 'f' and cell_c.value and isinstance(cell_c.value, str):
|
|
||||||
print(f"Special handling for Graphics cell {cell_coord_c}: {cell_c.value}")
|
|
||||||
original_formula = cell_c.value
|
|
||||||
updated_formula = original_formula
|
|
||||||
|
|
||||||
# Check if formula contains {store_name} or any of the old sheet names
|
|
||||||
needs_update = '{store_name}' in original_formula
|
|
||||||
if not needs_update:
|
|
||||||
for old_name in sheet_name_mapping.keys():
|
|
||||||
if old_name in original_formula:
|
|
||||||
needs_update = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if needs_update:
|
|
||||||
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_c.value = updated_formula
|
|
||||||
sheet_updates += 1
|
|
||||||
total_updates += 1
|
|
||||||
print(f"Force updated formula in {sheet_name} cell {cell_coord_c}: {original_formula} -> {updated_formula}")
|
|
||||||
|
|
||||||
# Now create similar formulas for H, O, U, AA, AG columns
|
|
||||||
# These should reference the specific year's forecast sheet
|
|
||||||
year_cols = {
|
|
||||||
'H': '2025 – Forecast',
|
|
||||||
'O': '2026 – Forecast',
|
|
||||||
'U': '2027 – Forecast',
|
|
||||||
'AA': '2028 – Forecast',
|
|
||||||
'AG': '2029 – Forecast'
|
|
||||||
}
|
|
||||||
|
|
||||||
for col, year_prefix in year_cols.items():
|
|
||||||
cell_coord = f"{col}{row_num}"
|
|
||||||
try:
|
|
||||||
# Create a formula for this cell that references the specific year's forecast sheet
|
|
||||||
cell_ref = f"C{row_num - 15}" if row_num == 25 else f"C{row_num - 14}" # Map to row 10, 11, 12
|
|
||||||
|
|
||||||
# Check if the cell already exists
|
|
||||||
cell = sheet[cell_coord]
|
|
||||||
|
|
||||||
# Create a new formula referencing the specific year's sheet
|
|
||||||
new_sheet_name = f"{year_prefix} {sheet_name_mapping.get('2025 – Forecast {store_name}', '').replace('2025 – Forecast ', '')}"
|
|
||||||
|
|
||||||
# Determine the row reference based on the current row
|
|
||||||
if row_num == 25:
|
|
||||||
row_ref = "10"
|
|
||||||
elif row_num == 26:
|
|
||||||
row_ref = "11"
|
|
||||||
else: # row_num == 27
|
|
||||||
row_ref = "12"
|
|
||||||
|
|
||||||
new_formula = f"=SUM('{new_sheet_name}'!C{row_ref}:E{row_ref})"
|
|
||||||
|
|
||||||
# Only update if needed
|
|
||||||
if cell.value != new_formula:
|
|
||||||
original_value = cell.value
|
|
||||||
cell.value = new_formula
|
|
||||||
sheet_updates += 1
|
|
||||||
total_updates += 1
|
|
||||||
print(f"Created/updated formula in {sheet_name} cell {cell_coord}: {original_value} -> {new_formula}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing cell {cell_coord}: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error processing cell {cell_coord_c}: {e}")
|
|
||||||
|
|
||||||
# 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
|
print(f"Found {len(formula_cells)} cells with formulas referencing renamed sheets (out of {total_formulas} total formulas)")
|
||||||
if hasattr(workbook, 'defined_names') and workbook.defined_names:
|
|
||||||
print("Checking defined names in workbook...")
|
# Second pass: Update the formulas
|
||||||
names_updated = 0
|
for cell, original_formula in formula_cells:
|
||||||
|
updated_formula = original_formula
|
||||||
|
formula_updated = False
|
||||||
|
|
||||||
for name in workbook.defined_names:
|
# Check if the formula references any of the renamed sheets
|
||||||
|
for old_name, new_name in sheet_name_mapping.items():
|
||||||
|
# Pattern 1: Sheet name in single quotes: 'Sheet Name'!
|
||||||
|
if f"'{old_name}'!" in updated_formula:
|
||||||
|
updated_formula = updated_formula.replace(f"'{old_name}'!", f"'{new_name}'!")
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
# Pattern 2: Sheet name without quotes: SheetName!
|
||||||
|
# We need to be careful with this pattern to avoid partial matches
|
||||||
|
pattern = re.compile(f"(^|[^'])({re.escape(old_name)})!")
|
||||||
|
if pattern.search(updated_formula):
|
||||||
|
updated_formula = pattern.sub(f"\\1{new_name}!", updated_formula)
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
# Pattern 3: Sheet name in INDIRECT function: INDIRECT("'Sheet Name'!...")
|
||||||
|
if f"INDIRECT(\"'{old_name}'!" in updated_formula:
|
||||||
|
updated_formula = updated_formula.replace(
|
||||||
|
f"INDIRECT(\"'{old_name}'!",
|
||||||
|
f"INDIRECT(\"'{new_name}'!"
|
||||||
|
)
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
# Pattern 4: Sheet name in double quotes: "Sheet Name"!
|
||||||
|
if f"\"{old_name}\"!" in updated_formula:
|
||||||
|
updated_formula = updated_formula.replace(f"\"{old_name}\"!", f"\"{new_name}\"!")
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
# Pattern 5: Simple text replacement for any remaining instances
|
||||||
|
# Only do this if we're sure it's a sheet reference
|
||||||
|
if old_name in updated_formula and not formula_updated:
|
||||||
|
updated_formula = updated_formula.replace(old_name, new_name)
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
# If the formula was updated, set it back to the cell
|
||||||
|
if formula_updated:
|
||||||
try:
|
try:
|
||||||
# Get the defined name value (formula)
|
cell.value = updated_formula
|
||||||
destinations = workbook.defined_names[name].destinations
|
updates_count += 1
|
||||||
for sheet_title, coordinate in destinations:
|
print(f"Updated formula in cell {cell.coordinate}: {original_formula} -> {updated_formula}")
|
||||||
if sheet_title in sheet_name_mapping:
|
except Exception as e:
|
||||||
# This defined name points to a renamed sheet
|
print(f"Error updating formula in cell {cell.coordinate}: {e}")
|
||||||
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}")
|
# Special handling for specific cells that might have been missed
|
||||||
|
# These are known cells with complex formulas referencing the forecast sheets
|
||||||
|
special_cells = ['C4', 'H4', 'O4', 'U4', 'AA4', 'AG4',
|
||||||
|
'C5', 'H5', 'O5', 'U5', 'AA5', 'AG5',
|
||||||
|
'C6', 'H6', 'O6', 'U6', 'AA6', 'AG6',
|
||||||
|
'C25', 'H25', 'O25', 'U25', 'AA25', 'AG25',
|
||||||
|
'C26', 'H26', 'O26', 'U26', 'AA26', 'AG26',
|
||||||
|
'C27', 'H27', 'O27', 'U27', 'AA27', 'AG27']
|
||||||
|
|
||||||
|
# Print all cells in the Graphics sheet that have formulas
|
||||||
|
print("\nDiagnostic: Checking all cells with formulas in Graphics sheet:")
|
||||||
|
formula_cells_diagnostic = []
|
||||||
|
for row in graphics_sheet.iter_rows():
|
||||||
|
for cell in row:
|
||||||
|
if cell.data_type == 'f' and cell.value and isinstance(cell.value, str):
|
||||||
|
formula = cell.value
|
||||||
|
if any(old_name in formula for old_name in sheet_name_mapping.keys()):
|
||||||
|
formula_cells_diagnostic.append(f"{cell.coordinate}: {formula[:50]}...")
|
||||||
|
|
||||||
|
print(f"Found {len(formula_cells_diagnostic)} cells with formulas referencing forecast sheets:")
|
||||||
|
for cell_info in formula_cells_diagnostic:
|
||||||
|
print(f" {cell_info}")
|
||||||
|
|
||||||
|
for cell_coord in special_cells:
|
||||||
|
if cell_coord not in processed_cells:
|
||||||
|
try:
|
||||||
|
cell = graphics_sheet[cell_coord]
|
||||||
|
if cell.data_type == 'f' and cell.value and isinstance(cell.value, str):
|
||||||
|
original_formula = cell.value
|
||||||
|
updated_formula = original_formula
|
||||||
|
formula_updated = False
|
||||||
|
|
||||||
|
for old_name, new_name in sheet_name_mapping.items():
|
||||||
|
if old_name in updated_formula:
|
||||||
|
updated_formula = updated_formula.replace(old_name, new_name)
|
||||||
|
formula_updated = True
|
||||||
|
|
||||||
|
if formula_updated:
|
||||||
|
cell.value = updated_formula
|
||||||
|
updates_count += 1
|
||||||
|
print(f"Special handling: Updated formula in cell {cell_coord}: {original_formula} -> {updated_formula}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in special handling for cell {cell_coord}: {e}")
|
||||||
|
|
||||||
|
print(f"Updated {updates_count} formula references in Graphics sheet")
|
||||||
|
|
||||||
|
# Check if there are any remaining formulas with the old sheet names
|
||||||
|
remaining_formulas = []
|
||||||
|
for row in graphics_sheet.iter_rows():
|
||||||
|
for cell in row:
|
||||||
|
if cell.data_type == 'f' and cell.value and isinstance(cell.value, str):
|
||||||
|
formula = cell.value
|
||||||
|
if any(old_name in formula for old_name in sheet_name_mapping.keys()):
|
||||||
|
remaining_formulas.append(f"{cell.coordinate}: {formula[:50]}...")
|
||||||
|
|
||||||
|
if remaining_formulas:
|
||||||
|
print(f"Warning: Found {len(remaining_formulas)} formulas that still reference old sheet names:")
|
||||||
|
for formula in remaining_formulas:
|
||||||
|
print(f" {formula}")
|
||||||
|
else:
|
||||||
|
print("All formulas have been successfully updated!")
|
||||||
|
|
||||||
|
return updates_count
|
||||||
|
except KeyError:
|
||||||
|
print("Graphics sheet not found in workbook")
|
||||||
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error updating formulas in workbook: {e}")
|
print(f"Error updating formula references: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# For testing purposes
|
# For testing purposes
|
||||||
|
|||||||
Reference in New Issue
Block a user