Fix BOM workbook monthly totals
All checks were successful
Deploy Skill to OCI / deploy (push) Successful in 21s

This commit is contained in:
root
2026-04-23 10:54:07 -03:00
parent 6de2251e00
commit 9d5cda8986

View File

@@ -204,6 +204,17 @@ class OCIBomGenerator:
def _col_count(self) -> int: def _col_count(self) -> int:
return len(self._col_defs()) return len(self._col_defs())
@staticmethod
def _monthly_values(item: dict) -> tuple[float, float]:
"""Return monthly values with and without discount for one BOM line."""
price = float(item.get("list_price_usd", 0) or 0)
hours_units = float(item.get("hours_units", 0) or 0)
qty = float(item.get("qty", 0) or 0)
discount = float(item.get("discount", 0) or 0)
monthly_wo = price * hours_units * qty
monthly_w = monthly_wo * (1 - discount)
return monthly_wo, monthly_w
def _build_sheet(self, ws): def _build_sheet(self, ws):
"""Build the complete BOM sheet.""" """Build the complete BOM sheet."""
ws.title = "BOM" ws.title = "BOM"
@@ -325,6 +336,11 @@ class OCIBomGenerator:
if cat_key not in render_order: if cat_key not in render_order:
render_order.append(cat_key) render_order.append(cat_key)
grand_total_wo = 0.0
grand_total_w = 0.0
grand_total_conv_wo = 0.0
grand_total_conv_w = 0.0
for cat_key in render_order: for cat_key in render_order:
if cat_key not in items_by_cat: if cat_key not in items_by_cat:
continue continue
@@ -344,6 +360,10 @@ class OCIBomGenerator:
row += 1 row += 1
cat_first_data = row cat_first_data = row
cat_total_wo = 0.0
cat_total_w = 0.0
cat_total_conv_wo = 0.0
cat_total_conv_w = 0.0
for idx, item in enumerate(cat_items): for idx, item in enumerate(cat_items):
# Static data # Static data
@@ -356,38 +376,22 @@ class OCIBomGenerator:
ws.cell(row=row, column=COL_MONTHS, value=item["months"]) ws.cell(row=row, column=COL_MONTHS, value=item["months"])
ws.cell(row=row, column=COL_DISC, value=item["discount"]) ws.cell(row=row, column=COL_DISC, value=item["discount"])
# Formula: Monthly w/o discount = Price × Hours × Qty monthly_wo, monthly_w = self._monthly_values(item)
p = get_column_letter(COL_PRICE) ws.cell(row=row, column=COL_M_WO, value=monthly_wo)
h = get_column_letter(COL_HOURS) ws.cell(row=row, column=COL_M_W, value=monthly_w)
q = get_column_letter(COL_QTY) cat_total_wo += monthly_wo
d = get_column_letter(COL_DISC) cat_total_w += monthly_w
ws.cell(
row=row, column=COL_M_WO,
value=f"={p}{row}*{h}{row}*{q}{row}",
)
# Formula: Monthly w/ discount = Monthly_wo × (1 - Discount)
mwo = get_column_letter(COL_M_WO)
ws.cell(
row=row, column=COL_M_W,
value=f"={mwo}{row}*(1-{d}{row})",
)
# Conversion formulas # Conversion formulas
if conv: if conv:
xr = self.conversion.get("exchange_rate", 1) xr = self.conversion.get("exchange_rate", 1)
tr = self.conversion.get("tax_rate", 0) tr = self.conversion.get("tax_rate", 0)
mw = get_column_letter(COL_M_W) conv_wo = monthly_w * xr
# Converted w/o tax conv_w = conv_wo * (1 + tr)
ws.cell( ws.cell(row=row, column=COL_CONV_WO, value=conv_wo)
row=row, column=COL_CONV_WO, ws.cell(row=row, column=COL_CONV_W, value=conv_w)
value=f"={mw}{row}*{xr}", cat_total_conv_wo += conv_wo
) cat_total_conv_w += conv_w
# Converted w/ tax
ws.cell(
row=row, column=COL_CONV_W,
value=f"={get_column_letter(COL_CONV_WO)}{row}*(1+{tr})",
)
# Styling per row # Styling per row
is_alt = idx % 2 == 1 is_alt = idx % 2 == 1
@@ -419,20 +423,20 @@ class OCIBomGenerator:
# Category subtotal row # Category subtotal row
cat_last_data = row - 1 cat_last_data = row - 1
ws.cell(row=row, column=COL_PRODUCT, value=f"Subtotal — {display_name}") ws.cell(row=row, column=COL_PRODUCT, value=f"Subtotal — {display_name}")
mwo_letter = get_column_letter(COL_M_WO) ws.cell(row=row, column=COL_M_WO, value=cat_total_wo)
mw_letter = get_column_letter(COL_M_W) ws.cell(row=row, column=COL_M_W, value=cat_total_w)
ws.cell(row=row, column=COL_M_WO, value=f"=SUM({mwo_letter}{cat_first_data}:{mwo_letter}{cat_last_data})")
ws.cell(row=row, column=COL_M_W, value=f"=SUM({mw_letter}{cat_first_data}:{mw_letter}{cat_last_data})")
ws.cell(row=row, column=COL_M_WO).number_format = '$#,##0.00' ws.cell(row=row, column=COL_M_WO).number_format = '$#,##0.00'
ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00' ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00'
grand_total_wo += cat_total_wo
grand_total_w += cat_total_w
if conv: if conv:
cwo_letter = get_column_letter(COL_CONV_WO) ws.cell(row=row, column=COL_CONV_WO, value=cat_total_conv_wo)
cw_letter = get_column_letter(COL_CONV_W) ws.cell(row=row, column=COL_CONV_W, value=cat_total_conv_w)
ws.cell(row=row, column=COL_CONV_WO, value=f"=SUM({cwo_letter}{cat_first_data}:{cwo_letter}{cat_last_data})")
ws.cell(row=row, column=COL_CONV_W, value=f"=SUM({cw_letter}{cat_first_data}:{cw_letter}{cat_last_data})")
ws.cell(row=row, column=COL_CONV_WO).number_format = '#,##0.00' ws.cell(row=row, column=COL_CONV_WO).number_format = '#,##0.00'
ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00' ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00'
grand_total_conv_wo += cat_total_conv_wo
grand_total_conv_w += cat_total_conv_w
for ci in range(1, num_cols + 1): for ci in range(1, num_cols + 1):
cell = ws.cell(row=row, column=ci) cell = ws.cell(row=row, column=ci)
@@ -448,19 +452,14 @@ class OCIBomGenerator:
total_row = row total_row = row
ws.cell(row=row, column=COL_PRODUCT, value="TOTAL").font = total_font ws.cell(row=row, column=COL_PRODUCT, value="TOTAL").font = total_font
# Sum of subtotals ws.cell(row=row, column=COL_M_WO, value=grand_total_wo)
mwo_refs = "+".join(f"{get_column_letter(COL_M_WO)}{r}" for r in cat_subtotal_rows) ws.cell(row=row, column=COL_M_W, value=grand_total_w)
mw_refs = "+".join(f"{get_column_letter(COL_M_W)}{r}" for r in cat_subtotal_rows)
ws.cell(row=row, column=COL_M_WO, value=f"={mwo_refs}")
ws.cell(row=row, column=COL_M_W, value=f"={mw_refs}")
ws.cell(row=row, column=COL_M_WO).number_format = '$#,##0.00' ws.cell(row=row, column=COL_M_WO).number_format = '$#,##0.00'
ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00' ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00'
if conv: if conv:
cwo_refs = "+".join(f"{get_column_letter(COL_CONV_WO)}{r}" for r in cat_subtotal_rows) ws.cell(row=row, column=COL_CONV_WO, value=grand_total_conv_wo)
cw_refs = "+".join(f"{get_column_letter(COL_CONV_W)}{r}" for r in cat_subtotal_rows) ws.cell(row=row, column=COL_CONV_W, value=grand_total_conv_w)
ws.cell(row=row, column=COL_CONV_WO, value=f"={cwo_refs}")
ws.cell(row=row, column=COL_CONV_W, value=f"={cw_refs}")
ws.cell(row=row, column=COL_CONV_WO).number_format = '#,##0.00' ws.cell(row=row, column=COL_CONV_WO).number_format = '#,##0.00'
ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00' ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00'
@@ -476,15 +475,13 @@ class OCIBomGenerator:
ws.cell(row=row, column=COL_PRODUCT, value="Annual Run Rate (ARR)").font = Font( ws.cell(row=row, column=COL_PRODUCT, value="Annual Run Rate (ARR)").font = Font(
name="Segoe UI", size=11, bold=True, color=Colors.COPPER name="Segoe UI", size=11, bold=True, color=Colors.COPPER
) )
mw_total = get_column_letter(COL_M_W) ws.cell(row=row, column=COL_M_W, value=grand_total_w * 12)
ws.cell(row=row, column=COL_M_W, value=f"={mw_total}{total_row}*12")
ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00' ws.cell(row=row, column=COL_M_W).number_format = '$#,##0.00'
ws.cell(row=row, column=COL_M_W).font = Font( ws.cell(row=row, column=COL_M_W).font = Font(
name="Segoe UI", size=11, bold=True, color=Colors.COPPER name="Segoe UI", size=11, bold=True, color=Colors.COPPER
) )
if conv: if conv:
cw_total = get_column_letter(COL_CONV_W) ws.cell(row=row, column=COL_CONV_W, value=grand_total_conv_w * 12)
ws.cell(row=row, column=COL_CONV_W, value=f"={cw_total}{total_row}*12")
ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00' ws.cell(row=row, column=COL_CONV_W).number_format = '#,##0.00'
ws.cell(row=row, column=COL_CONV_W).font = Font( ws.cell(row=row, column=COL_CONV_W).font = Font(
name="Segoe UI", size=11, bold=True, color=Colors.COPPER name="Segoe UI", size=11, bold=True, color=Colors.COPPER
@@ -630,11 +627,9 @@ class OCIBomGenerator:
ws_bom.cell(row=row, column=9, value=item["months"]) ws_bom.cell(row=row, column=9, value=item["months"])
ws_bom.cell(row=row, column=10, value=item["discount"]) ws_bom.cell(row=row, column=10, value=item["discount"])
# Formulas matching BOM.C1 style monthly_wo, monthly_w = self._monthly_values(item)
ws_bom.cell(row=row, column=11, ws_bom.cell(row=row, column=11, value=monthly_wo)
value="=(F{r}*G{r}*H{r})".format(r=row)) ws_bom.cell(row=row, column=12, value=monthly_w)
ws_bom.cell(row=row, column=12,
value="=IFERROR(K{r}*(1-J{r}),K{r})".format(r=row))
# Formats # Formats
ws_bom.cell(row=row, column=6).number_format = '#,##0.0000' ws_bom.cell(row=row, column=6).number_format = '#,##0.0000'