#!/usr/bin/env python3 """ Script to populate an ODS template with data extracted from a source Excel file. The script performs the following steps: 1. Reads the source Excel file "Production Order (mrp.production).xlsx". 2. Extracts a serial number suffix from column B where a cell contains "FA-". 3. Finds specific component rows (Barebone, GPU, RAM, CPU, SSD, RAID) and copies the value from the cell to the right of each. 4. Loads the ODS template "A4004_v3.0-260617.ods". 5. Writes the extracted values into designated ranges in the template. 6. Saves the modified template as a new ODS file named after the value in cell A2 of the source file. Dependencies: - pandas (for reading Excel files) - openpyxl (Excel engine used by pandas) - ezodf (for reading and writing ODS files) Install them via: pip install pandas openpyxl ezodf Usage: python populate_template.py """ import os import sys import pandas as pd import ezodf def find_fa_suffix(df: pd.DataFrame) -> str: """Search column B (index 1) for a cell containing "FA-" and return the suffix. Example cell content: "FA-2604N3" → returns "2604N3". """ # Iterate over column B values for idx, val in df.iloc[:, 1].items(): if isinstance(val, str) and "FA-" in val: parts = val.split("FA-") if len(parts) > 1: suffix = parts[1].strip() return suffix raise ValueError('No cell containing "FA-" found in column B.') def find_adjacent_value(df: pd.DataFrame, phrase: str) -> str: """Search the entire DataFrame for a cell containing *phrase* and return the value from the cell immediately to its right (same row, next column).""" for row_idx, row in df.iterrows(): for col_idx, cell in row.iteritems(): if isinstance(cell, str) and phrase in cell: # Ensure there is a column to the right if col_idx + 1 < df.shape[1]: adjacent = df.iat[row_idx, col_idx + 1] # Convert NaN to empty string if needed if pd.isna(adjacent): return "" return str(adjacent) else: raise IndexError(f"Cell containing '{phrase}' is at the last column; no adjacent cell to copy.") raise ValueError(f"Phrase '{phrase}' not found in the source sheet.") def set_range(sheet: ezodf.Table, start_row: int, start_col: int, end_row: int, end_col: int, value: str) -> None: """Fill a rectangular range (inclusive) in the ODS sheet with *value*. Rows and columns are zero‑based indices. """ for r in range(start_row, end_row + 1): for c in range(start_col, end_col + 1): sheet[r, c].set_value(value) def main() -> None: # File names (relative to the script location / workspace root) src_excel = "Production Order (mrp.production).xlsx" template_ods = "A4004_v3.0-260617.ods" # Load the Excel source file – no header, treat everything as string df = pd.read_excel(src_excel, header=None, dtype=str) # 1. Serial number suffix (FA-...) suffix = find_fa_suffix(df) # 2. Component values – copy the cell to the right of each label barebone_val = find_adjacent_value(df, "All / Components / Barebone") gpu_val = find_adjacent_value(df, "All / Components / GPU") ram_val = find_adjacent_value(df, "All / Components / RAM") cpu_val = find_adjacent_value(df, "All / Components / CPU") ssd_val = find_adjacent_value(df, "All / Components / SSD") raid_val = find_adjacent_value(df, "All / Components / RAID") # 3. Determine output file name from cell A2 (row 1, column 0) output_name_raw = df.iat[1, 0] if pd.isna(output_name_raw) or not str(output_name_raw).strip(): output_name = "output" else: output_name = str(output_name_raw).strip() output_file = f"{output_name}.ods" # Load the ODS template doc = ezodf.opendoc(template_ods) # Assume the first sheet is the target – adjust if needed sheet = doc.sheets[0] # 4. Populate the template ranges # B1:C2 → rows 0‑1, cols 1‑2 set_range(sheet, 0, 1, 1, 2, suffix) # C10:D10 → row 9, cols 2‑3 set_range(sheet, 9, 2, 9, 3, barebone_val) # C20:D23 → rows 19‑22, cols 2‑3 set_range(sheet, 19, 2, 22, 3, gpu_val) # C12:D19 → rows 11‑18, cols 2‑3 set_range(sheet, 11, 2, 18, 3, ram_val) # C11:D11 → row 10, cols 2‑3 set_range(sheet, 10, 2, 10, 3, cpu_val) # C24:D25 → rows 23‑24, cols 2‑3 set_range(sheet, 23, 2, 24, 3, ssd_val) # C27:D27 → row 26, cols 2‑3 set_range(sheet, 26, 2, 26, 3, raid_val) # 5. Save the populated template as a new file doc.saveas(output_file) print(f"Populated template saved as: {output_file}")