- measurements.js: replace manual name/phone inputs with client picker overlay
(search by ФИО or phone, sorted alphabetically, picks from /api/clients)
- clients.js: new-client and edit-client forms now use 4 split address fields
(город, улица, дом, кв./офис) with geocode validation on save
- clients.js: add splitAddress() helper to pre-fill edit form from stored address
- clients.js: voice engine refactored (continuous=false + auto-restart, no duplication)
- clients.js: note block view/edit toggle (textarea closes after save)
- podbor.css: styles for picker overlay, picker-row, chosen-card, addr-grid, geo-status
- backend main.py: _handle_client_create and _handle_client_update accept gps_lat/gps_lng
- index.html: cache bump → v=20260514i
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced continuous=true with phrase-by-phrase mode in all 3 voice inputs
(setupVoiceMicForField, setupVoiceInput, setupVoiceMic). Chrome mobile
resets ev.results mid-session with continuous=true causing repeated words.
Now each phrase is an independent SR() session; baseText grows cleanly.
Shared _buildVoiceEngine factory in clients.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#7 — submit-flow:
- При успехе скрываем CTA с «Сохраняем...» (style.display=none)
- Обработчики «Ещё клиент» / «Открыть карточку» прикрепляются к
result-блоку (был form.querySelector — там их нет)
- Backend возвращает client_key = name.lower() — совместимо
с тем как ищет _handle_clients
- clientsCache = null после успеха
#3 — голос дублировался:
- Переписан алгоритм: финальные транскрипты пересчитываются с нуля
из ev.results[0..N] каждое событие (не аккумулируем дельтами).
- confirmedFinal фиксируется в baseText только на onend.
- Применено в measurements.js + clients.js
#2 — телефон:
- Frontend normalizePhone: убирает не-цифры, +7/8 → +7, добавляет +7
к 10-значным; auto-нормализация на blur
- Backend _normalize_phone(): тот же алгоритм
- Валидация: ровно 11 цифр начиная с 7
- Field-error «Введите корректный российский номер...»
#1 — адрес:
- Min 5 chars (улица + дом)
- Backend проверка длины
- Hint «Укажите город, улицу, дом, кв.»
#5 — номер клиента:
- Новая колонка client_no
- _next_client_no() — максимум для текущего менеджера + 1
- Шильд #N рядом с именем в карточке клиента
#6 — номер договора:
- Новые колонки contract_no, contract_date
- Поля в форме «Новый клиент» (опционально)
- Шильд «📋 договор N» в карточке клиента
#4 — удаление клиента:
- Soft-delete через колонку archived_at
- Endpoint /api/client_delete
- «⚠️ Опасная зона» в карточке клиента (collapsible)
- Confirm dialog через Telegram.WebApp.showConfirm
- Архивированные клиенты не показываются в /api/clients
#8 — правило самопроверки:
- docs/SELF_CHECK_RULE.md — 10 пунктов чек-листа перед «готово»
(end-to-end, ключи, UI-состояния, валидация, голос, deploy, логи)
Cache bust v=20260514a.
A — голосовой ввод заметок в мастере замера:
- Кнопка 🎤 Диктовать рядом с textarea «Заметки»
- Web Speech API ru-RU, interimResults показывает диктовку в реальном времени
- Текст накапливается + сохраняется в state
- Красная пульсация во время записи
B — Google Calendar:
- Новый модуль app/gcalendar.py — service account + Calendar API
- Создание/обновление события при /api/measurement_schedule
- 2 новые колонки в Measurements: gcal_event_id, gcal_event_url
- При ошибке (нет API/прав) — fail gracefully, лог warning
- Ссылка «📅 Открыть в Google Calendar» в карточке заявки
- В DM менеджеру при назначении — clickable ссылка на событие
- Требует env: GOOGLE_CALENDAR_ID + SA добавлен в редакторы календаря
ДОПОЛНИТЕЛЬНО — заведение клиента менеджером:
- Новый endpoint /api/client_create
- /api/clients теперь читает И Leads И Measurements (включая draft)
- UI: action card «Новый клиент» в quick-actions + кнопка
«+ Новый клиент» в шапке списка клиентов
- Форма (ФИО / Тел / Адрес / Примечание с 🎤 диктовкой)
- После сохранения — переход в карточку клиента
- has_role проверка вместо устаревшего user.role
Cache bust v=20260513zn.
По аналогии с пиктограммами категорий техники добавил инструктивные
рисунки к чек-листу замера. Walnut stroke + cream fill + лёгкая тень.
1. topview — вид сверху, комната с пронумерованными стенами + компас.
2. clockwise — стрелка по часовой стрелке с точкой «старт».
3. openings — фронт стены с дверью / окном / балконом + размеры
(ширина, высота, низ подоконника).
4. comms — стена с точками R1 / Sw1 / Wc1, двумя размерами на каждую
(горизонталь до угла-базы + вертикаль до пола), указанная БАЗА: ПУ.
5. levels — разрез помещения с нулевым полом (волнистая стяжка),
плитой +88, потолком и коробом справа, размеры H1/H2.
Реализация:
- Новый файл miniapp/assets/zamer-picts.js — экспортирует ZAMER_PICTS.
- renderMarkdown в measurements.js поддерживает директиву
«@pict:KEY» на отдельной строке → подставляет SVG.
- Эскизы вставлены в ЧЕКЛИСТ_ЗАМЕРА.md в соответствующие разделы
(1, 4, 6, 8). По часовой стрелке — после § 1.
- CSS .cl-pict — рамка пунктиром + цент., max-height 220.
Cache bust v=20260513x.
1. № замера подбирается автоматически:
- POST /api/measurement_next_no возвращает max(zamer_no) + 1
- Wizard при открытии вызывает endpoint и заполняет input
- Менеджер может переписать вручную (поле редактируемое)
- Подпись «Подобран автоматически — можно изменить»
2. Поле «Стяжка / нулевой пол» удалено из формы:
- По логике пользователя — стяжка пишется на самих фото с замером
- Backend колонка floor_base остаётся для backward compat (старые записи)
3. Чек-лист стал интерактивным:
- Каждый [ ] item теперь .cl-item с cursor:pointer
- Тап переключает галочку (☐ ↔ ☑) + страйкаут текста
- Состояние сохраняется в localStorage по measurement_id (или draft)
- Sticky прогресс-бар сверху: «N из M · X%» + градиентная полоса
- Кнопка ↺ в шапке — сбросить все галочки
- Hapt-фидбэк на каждый тап
Cache bust v=20260513m.
По чек-листу ЗАМЕРОВ (D:\!!! GOOGLE DISK\ЗАМЕРЫ\...\ЧЕКЛИСТ_ЗАМЕРА.md):
каждая стена снимается отдельно, имя файла отражает тип.
Wizard:
- Каждое фото получает dropdown «Что это»:
Стена 1, 2, 3, 4 · План комнаты · Общий вид · Деталь
- Авто-предложение типа: w1 → w2 → w3 → w4 → plan → general
- Добавлены поля общей инфы:
· № замера (опционально)
· Дата замера (auto-сегодня)
· Стяжка / нулевой пол (default «0,000 = +88 мм над плитой»)
- В шапке кнопка 📋 — открывает чек-лист отдельной страницей
- Inline-рендер markdown с поддержкой заголовков, списков, таблиц, code
Backend:
- _save_measurement_photo принимает kind+kind_seq → имена файлов
структурные: w1.jpg, w2.jpg, plan.jpg, general_2.jpg, detail_1.jpg.
Это упрощает дальнейшую обработку для генерации DWG.
- Расширена схема Measurements: zamer_no, zamer_date, floor_base, photos_meta.
- /api/measurement_detail отдаёт новые поля.
Cache bust v=20260513l.
По логике пользователя: задача замера в боте — только сфоткать
рукописные эскизы с размерами + общие фото помещения. Перевод
в DWG-чертёж происходит отдельным процессом (вручную технологом
или через AI vision позже).
Что убрано:
- Шаг «Форма кухни» (linear / l_shape / u_shape / island / peninsula)
- Шаг «Размеры стен + потолок + площадь»
- Шаг «Окна и двери»
Что осталось:
- Клиент (имя/телефон/адрес) — только для прямого ввода менеджером.
При закрытии заявки замерщиком берётся из заявки read-only.
- Фото (до 20, со сжатием до 1800px на клиенте)
- Заметки опционально
Wizard стал одностраничным: всё на одном экране, единый submit.
В update-mode (#/measure?id=...) wizard сразу подгружает заявку
и не спрашивает данные клиента.
Backend схема не менялась — старые поля (layout, walls и т.п.)
просто остаются пустыми. Существующие замеры с заполненными полями
отрисовываются без изменений.
Cache bust v=20260513k.
End-to-end поток:
1. Менеджер на главной тапает «Заказать замер» → #/request → форма:
ФИО · телефон · адрес · dropdown «Кому назначить» · заметки.
Submit → POST /api/measurement_request → строка в Measurements
со status=requested + assigned_to_tg_id. Бот шлёт DM замерщику.
2. Замерщик открывает кабинет (?role=staff) → видит inbox с заявкой.
Тап → #/inbox/<id> → карточка с реквизитами + поле datetime-local.
Сохранить дату → POST /api/measurement_schedule → status=scheduled.
Бот уведомляет менеджера.
3. В нужный день замерщик тапает «📐 Сделать замер сейчас» →
wizard открывается в update-mode (#/measure?id=<id>), pre-fill
client_name/phone из заявки, пропускает шаг «Клиент». После submit
→ backend обновляет ту же строку (status=completed) + DM менеджеру.
Backend changes:
- Расширена схема Measurements: assigned_to_tg_id, requested_by_tg_id,
scheduled_at, address, client_name, client_phone (отдельные колонки).
ensure_measurements_sheet() автоматически дополняет колонки.
- _handle_measurement переписан под 2 режима (create/update).
- 3 новые ручки: /api/measurement_request, /api/measurement_inbox,
/api/measurement_schedule. Все с правильной проверкой ролей.
- Telegram-уведомления на каждом переходе статуса.
MiniApp:
- Новый модуль request.js — wizard заявки с dropdown замерщиков
(грузится из /api/staff_list?role=measurer).
- renderStaff теперь грузит реальный инбокс из /api/measurement_inbox.
- renderInboxDetail — карточка заявки с datetime-picker.
- В quick-actions менеджера: «Заказать замер» (primary) +
«Замер сейчас» (legacy direct fill).
- measurements.js поддерживает update-mode через ?id=.
Cache bust v=20260513g.
Wizard: new 'photos' step (6 total) — camera/gallery input, client-side
canvas compression to 1600px @ ~78% JPEG, max 12 photos. Thumbnails
with delete in step; preview in summary.
Backend: POST /api/measurement now decodes data-URL photos and saves
to /app/photos/<id>/N.jpg (volume-mounted). New GET /api/photo/{id}/{n}
serves files with path-traversal protection. New POST /api/measurement_detail
returns full measurement record (walls/openings/photos/notes/...).
Clients page: measurement rows now clickable → renderMeasurement detail
view with key-value grid + photo gallery + 'Скачать PDF / Печать'.
Print stylesheet (@media print) hides navigation/buttons/uploaders and
prints clean A4-friendly layout.
Podbor report: existing 'Печать → PDF' now falls back to inline
window.print() inside Telegram WebApp (popups are blocked there).
Cache bust v=20260513a.
5 LAYOUT PICTOGRAMS (podbor.picts.js):
- linear: одна стена с гарнитуром
- l_shape: Г-образная, две стены с подсвеченным углом
- u_shape: П-образная, три стены
- island: линейный гарнитур + отдельный остров посередине
- peninsula: Г-образная + барная стойка-полуостров
Все в стиле D · top-down view, walnut stroke, теплые градиенты
MEASUREMENTS.JS WIZARD (5 шагов):
1. client_info — имя + телефон (валидация)
2. layout — pict-карточки 5 типов
3. size — длины стен (1-3 по layout), площадь, потолок (мм)
4. openings — окно / дверь / коммуникации / заметки
5. summary — обзор + Сохранить → POST /api/measurement
BACKEND (main.py):
- New /api/measurements (POST) для списка замеров менеджера
с опц. фильтрами по client_tg_id
- _handle_measurement теперь дописывает имя+телефон клиента в notes
(если client_tg_id не зарегистрирован — это новый клиент без аккаунта)
- handlers dispatcher: 'measurements' route added
ROUTING (app.js):
- Quick-action 'Новый замер' wired to '#/measure'
- routeByHash: Measurements.mount on #/measure
CLIENT PROFILE (clients.js):
- New section 'Замеры · N' on client history page
- fetchMeasurements() filters by client_tg_id or name match in notes
- layoutLabel() shows Russian label (Прямая / Угловая Г / etc.)
- Cache bump v=20260512c