158 lines
5.1 KiB
Python
158 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Estrae i listini prezzi PVC da PVC_LISTINO.xls e genera i file
|
|
src/assets/data/modelli/pvc/<modello>/listino.json
|
|
|
|
Uso:
|
|
python3 scripts/extract_listini_pvc.py
|
|
|
|
Richiede: pip install xlrd
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import unicodedata
|
|
from pathlib import Path
|
|
|
|
import xlrd
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
XLS_PATH = ROOT / "PVC_LISTINO.xls"
|
|
PVC_DIR = ROOT / "src" / "assets" / "data" / "modelli" / "pvc"
|
|
|
|
# I titoli del listino ora coincidono direttamente con i nomi delle cartelle
|
|
# pvc/ (uno-a-uno). TYPO_FIXES corregge i titoli il cui slug non combacia
|
|
# esattamente col nome cartella per un refuso nel file excel stesso.
|
|
TYPO_FIXES = {
|
|
"finestra_wasistas": "finestra_vasistas",
|
|
}
|
|
|
|
# Titoli duplicati nel file (varianti mano dx/sx con lo stesso identico
|
|
# listino prezzi) sono gestiti naturalmente: la stessa chiave di titolo
|
|
# riscrive due volte lo stesso file con dati identici.
|
|
|
|
# Titoli del listino senza una cartella attiva corrispondente nel catalogo
|
|
# (prodotti archiviati o senza un modello 3D GAMMA univoco): non vengono
|
|
# processati, per non ricreare cartelle orfane.
|
|
SKIP_TITLES = {
|
|
"SCORREVOLE IN LINEA_3 ANTE",
|
|
"SCORREVOLE IN LINEA_4 ANTE_2 FISSE + 2 APRIBILI",
|
|
"ALZANTE_4ANTE(2 APRIBILI+2 FISSE)",
|
|
}
|
|
|
|
|
|
def slugify(title: str) -> str:
|
|
text = unicodedata.normalize("NFKD", title).encode("ascii", "ignore").decode()
|
|
text = text.lower()
|
|
text = re.sub(r"[^a-z0-9]+", "_", text)
|
|
return text.strip("_")
|
|
|
|
|
|
def parse_blocks(sheet):
|
|
"""Ritorna una lista di (titolo, larghezze, righe) dove righe è una
|
|
lista di (altezza, [prezzi...]) allineata alle larghezze."""
|
|
title_rows = []
|
|
for r in range(sheet.nrows):
|
|
v = sheet.cell_value(r, 0)
|
|
if isinstance(v, str) and v.strip():
|
|
title_rows.append((r, v.strip()))
|
|
|
|
# Un titolo "puro" (es. "CASSONETTO PVC") non ha larghezze sulla propria
|
|
# riga: la riga successiva è il suo header (es. "mm", 500, 600, ...) e va
|
|
# scartata dall'elenco titoli, non trattata come un nuovo blocco.
|
|
filtered = []
|
|
for i, (r, title) in enumerate(title_rows):
|
|
if i > 0:
|
|
prev_r, prev_title = title_rows[i - 1]
|
|
prev_header = sheet.row_values(prev_r)
|
|
prev_is_pure_title = not any(isinstance(v, float) for v in prev_header[1:])
|
|
if prev_is_pure_title and r == prev_r + 1:
|
|
continue
|
|
filtered.append((r, title))
|
|
title_rows = filtered
|
|
|
|
blocks = []
|
|
for i, (r, title) in enumerate(title_rows):
|
|
end = title_rows[i + 1][0] if i + 1 < len(title_rows) else sheet.nrows
|
|
|
|
header_row = r
|
|
header = sheet.row_values(header_row)
|
|
# Se la riga del titolo non contiene larghezze numeriche, l'header
|
|
# (es. "mm", larghezze...) è sulla riga successiva (es. CASSONETTO PVC).
|
|
if not any(isinstance(v, float) for v in header[1:]):
|
|
header_row = r + 1
|
|
header = sheet.row_values(header_row)
|
|
|
|
larghezze = []
|
|
c = 1
|
|
while c < len(header) and header[c] != "":
|
|
larghezze.append(header[c])
|
|
c += 1
|
|
|
|
rows = []
|
|
for rr in range(header_row + 1, end):
|
|
row_values = sheet.row_values(rr)
|
|
if not row_values or row_values[0] == "":
|
|
continue
|
|
altezza = row_values[0]
|
|
if not isinstance(altezza, float):
|
|
continue
|
|
rows.append((altezza, row_values[1:1 + len(larghezze)]))
|
|
|
|
if larghezze and rows:
|
|
blocks.append((title, larghezze, rows))
|
|
|
|
return blocks
|
|
|
|
|
|
def mm_to_cm(value: float) -> float:
|
|
cm = value / 10
|
|
return int(cm) if cm == int(cm) else round(cm, 2)
|
|
|
|
|
|
def round_price(value) -> float:
|
|
if not isinstance(value, (int, float)) or value == "":
|
|
return None
|
|
rounded = round(value, 2)
|
|
return int(rounded) if rounded == int(rounded) else rounded
|
|
|
|
|
|
def block_to_listino(larghezze, rows):
|
|
listino = []
|
|
for altezza, prezzi in rows:
|
|
for larghezza, prezzo in zip(larghezze, prezzi):
|
|
price = round_price(prezzo)
|
|
if price is None:
|
|
continue
|
|
listino.append({
|
|
"altezza": mm_to_cm(altezza),
|
|
"larghezza": mm_to_cm(larghezza),
|
|
"prezzo": price,
|
|
})
|
|
return listino
|
|
|
|
|
|
def main():
|
|
wb = xlrd.open_workbook(str(XLS_PATH))
|
|
sheet = wb.sheet_by_index(0)
|
|
blocks = parse_blocks(sheet)
|
|
|
|
for title, larghezze, rows in blocks:
|
|
if title in SKIP_TITLES:
|
|
print(f"{title!r:55s} -> (saltato, nessuna cartella attiva corrispondente)")
|
|
continue
|
|
folder_name = TYPO_FIXES.get(title, title)
|
|
folder = PVC_DIR / folder_name
|
|
if not folder.is_dir():
|
|
print(f"{title!r:55s} -> cartella {folder_name!r} non trovata, saltato")
|
|
continue
|
|
|
|
listino = block_to_listino(larghezze, rows)
|
|
|
|
out_path = folder / "listino.json"
|
|
out_path.write_text(json.dumps(listino, indent=3, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
print(f"{title!r:55s} -> {out_path.relative_to(ROOT)} ({len(listino)} righe)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|