131 lines
4.8 KiB
Python
131 lines
4.8 KiB
Python
#!/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}")
|
||
|