fix: write Measurements rows by column name, not by position

Root cause: _row_for_measurement() returned a positional list based on
_measurement_columns() order, but the actual Google Sheet may have
columns in a different order (if the sheet was created before new
columns were added and _ensure_measurements_sheet() appended them
at the end rather than in the middle). Values ended up in wrong
columns — client_name, manager_tg_id etc. were misaligned, so
_handle_clients couldn't match any rows and returned an empty list.

Fix:
- _row_for_measurement() now returns dict {col_name: value}
- sheets.append_named_row() reads real headers from the sheet and
  builds the positional row accordingly — safe regardless of column order
- All three Measurements append calls updated to use append_named_row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-05-16 10:56:45 +03:00
parent 46812620eb
commit 8318b25999
2 changed files with 21 additions and 8 deletions

View File

@ -921,10 +921,10 @@ def _ensure_measurements_sheet() -> None:
log.info("Measurements: дополнили колонки: %s", missing)
def _row_for_measurement(measurement_id: str, ts: str, **fields) -> list[str]:
"""Собирает строку в нужном порядке колонок (для append_row)."""
cols = _measurement_columns()
base = {
def _row_for_measurement(measurement_id: str, ts: str, **fields) -> dict[str, str]:
"""Возвращает словарь колонка→значение для записи в Measurements.
Используется с sheets.append_named_row() безопасно к порядку колонок."""
base: dict[str, Any] = {
"id": measurement_id, "ts": ts,
"client_tg_id": "", "manager_tg_id": "", "filled_by": "",
"layout": "", "area_m2": "", "ceiling_mm": "",
@ -943,7 +943,8 @@ def _row_for_measurement(measurement_id: str, ts: str, **fields) -> list[str]:
"podbor_decision": "", "podbor_decision_at": "", "podbor_lead_id": "",
}
base.update(fields)
return [str(base.get(c, "")) for c in cols]
# Нормализуем: None → "", всё приводим к str
return {k: str(v) if v is not None else "" for k, v in base.items()}
def _handle_measurement(body: dict[str, Any]) -> dict[str, Any]:
@ -1066,7 +1067,7 @@ def _handle_measurement(body: dict[str, Any]) -> dict[str, Any]:
for col, val in updates.items():
sheets.update_cell_by_key("Measurements", "id", measurement_id, col, val)
else:
sheets.append_row("Measurements", _row_for_measurement(
sheets.append_named_row("Measurements", _row_for_measurement(
measurement_id, _now_iso(),
client_tg_id=client_tg_id or "",
manager_tg_id=manager_tg_id or "",
@ -1528,7 +1529,7 @@ def _handle_measurement_request(body: dict[str, Any]) -> dict[str, Any]:
return {"error": "assigned_not_measurer"}
measurement_id = _short_id()
sheets.append_row("Measurements", _row_for_measurement(
sheets.append_named_row("Measurements", _row_for_measurement(
measurement_id, _now_iso(),
manager_tg_id=tg_id,
filled_by="request",
@ -2431,7 +2432,7 @@ def _handle_client_create(body: dict[str, Any]) -> dict[str, Any]:
client_no = _next_client_no(str(tg_id))
# Создаём «карточку клиента» как заявку со статусом draft
sheets.append_row("Measurements", _row_for_measurement(
sheets.append_named_row("Measurements", _row_for_measurement(
measurement_id, _now_iso(),
manager_tg_id=str(tg_id),
requested_by_tg_id=str(tg_id),

View File

@ -60,6 +60,18 @@ def append_row(name: str, row: list[Any]) -> None:
sheet(name).append_row(row, value_input_option="USER_ENTERED")
def append_named_row(name: str, data: dict[str, Any]) -> None:
"""Записывает строку по ИМЕНАМ колонок из реального заголовка листа.
Безопасно при любом порядке колонок значения выставляются по имени,
а не по позиции, так что нет расхождения с _measurement_columns()."""
ws = sheet(name)
headers = ws.row_values(1)
if not headers:
raise ValueError(f"Sheet {name!r} has no header row")
row = [str(data.get(h, "") if data.get(h, "") is not None else "") for h in headers]
ws.append_row(row, value_input_option="USER_ENTERED")
def find_row(sheet_name: str, key_col: str, key_val: Any) -> dict[str, Any] | None:
"""Линейный поиск по колонке-ключу. Возвращает строку как dict или None."""
s = sheet(sheet_name)