132 lines
4.8 KiB
Python
132 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}")
|
|||
|
|
|