-
pak233
2026-06-11
Hotfix pak232: вытянутая подпись при размещении + белый фон не удалялся. **#1 Вытянутая подпись (Konva editor):** в drop-handler формула высоты `h_pct = (w_pct * STAGE_W / aspect) / STAGE_H * 100` давала результат **589%** для подписи 600×200 при STAGE 595×842 — лишний `* 100` в конце. Подпись «растягивалась» на 6 страниц высотой, вылезала за canvas → визуально «нельзя было разместить» (это и был сообщённый баг #3, он же по сути #2). Правильная формула: `w_px = (w_pct/100) * STAGE_W; h_px = w_px / aspect; h_pct = (h_px / STAGE_H) * 100` = `w_pct * STAGE_W / (aspect * STAGE_H)`. Для тех же входов → 5.89% (правильный размер). Дополнительно `Transformer.keepRatio` изменён с false на true — при resize пропорции автоматически сохраняются (нельзя случайно растянуть подпись на «приплюснутую»). **#2 Белый фон не удалялся (Imagick):** переписан порядок вызовов в `removeWhiteBackgroundImagick`: (а) alpha-канал активируется ДО `transparentPaintImage` (раньше — после, из-за чего на некоторых сборках Imagick прозрачность не применялась — alpha-channel ещё не существовал); (б) `paintTransparentImage` (deprecated alias) заменён на `transparentPaintImage` с правильной сигнатурой `(target, alpha, fuzz, invert)`; (в) `transformImageColorspace(SRGB)` в try-catch (для CMYK/grayscale JPG источников, где alpha не приживалась); (г) повторный `setImageAlphaChannel(ACTIVATE)` + `setBackgroundColor(transparent)` перед `writeImage` — гарантирует сохранение alpha в PNG. **Диагностика:** в `removeWhiteBackground` (router-метод) добавлен `Log::info` про путь обработки (Imagick/GD) + размеры файла. Если пользователь снова пожалуется — посмотрим в `storage/logs/laravel-YYYY-MM-DD.log` строку «Pak233 background removed via …». **Без миграций.** Деплой: `git pull && php artisan view:clear` (для Blade-кэша). Report: docs/reports/UPD_pak233_RESULTS.md.
-
pak232
2026-06-11
M17 «Подписание документов» — MVP. Реализация по дизайну pak231. **4 миграции:** `user_signatures` (type ENUM signature/seal + original_path + processed_path + width/height + is_active + index us_user_type_active_idx), `signed_documents` (original_name + original_path + final_path + layout JSON + pages_count + status ENUM + error_message + final_hash SHA-256 + completed_at + 2 индекса), миграция меню «Директор» (новая top-level секция sort_order=85 + 2 подпункта «Подписи и печати» + «Подписанные документы», все под permission `signatures.use`), backfill миграция для существующих установок — добавляет `signatures.use` в JSON-permissions роли «Генеральный директор» (поддерживает alias «Директор»). DepartmentSeeder тоже обновлён для свежих установок. **Модели:** UserSignature с TYPE_LABELS + scopeActive + typeLabel + softDeactivate. SignedDocument с STATUS_LABELS + statusLabel + isSigned + canEdit. **3 сервиса в `App\Services\Signatures\`:** SignatureImageService — uploadAndProcess валидирует jpg/png/<5MB + сохраняет в storage/app/private/signatures/{user_id}/orig|processed/ + removeWhiteBackground через Imagick (`paintTransparentImage` + fuzz=12% + ресайз до 1500px) с GD-fallback (точный threshold=240, для пользователей без Imagick) + asDataUrl для preview через base64. DocumentUploadService — приём PDF до 50MB + countPages через FPDI + AuditLog. PdfSignatureRenderService — applyLayout (`Fpdi->setSourceFile` + per-page `importPage`+`getTemplateSize`+`useTemplate`+`Image` с конвертацией %% координат в mm с учётом фактического размера каждой страницы + Rotate через StartTransform/StopTransform когда rotation ≠ 0 — на будущее) + footer-watermark «Скан-подпись от {ФИО} от {дата}. Не является ЭЦП по ФЗ-63.» на последней странице + SHA-256 hash + status=signed + AuditLog `document_signed` с full context. markFailed для error-state. **2 контроллера:** Director\SignatureController (index с data-URL для preview + Imagick warning + store с try-catch LogicException и RuntimeException + destroy = softDeactivate). Director\SignedDocumentController (index с FPDI pre-check warning + store redirect в editor + editor с пре-загруженной палитрой как data-URL + apply с validate 7 полей × array + preview-endpoint для рендера страниц через Imagick 120dpi в cache/signed-previews/ + download раздельно original|final + destroy с удалением файлов). **8 routes** под `permission:signatures.use` в prefix `/director` и name `director.`. **3 view:** signatures/index (двухколоночные карточки Подпись+Печать с checkerboard-фоном для PNG-with-alpha + drag-drop upload + disclaimer + Imagick warning), signed_documents/index (таблица с status-badges 4 цветов + upload modal с PDF-only валидацией + FPDI warning), signed_documents/editor — **Konva.js v9 редактор**: layout `200px 1fr 240px` (превью страниц / canvas / палитра), drag-drop из палитры в canvas с автодефолтом размера (signature 25%w / seal 15%w + aspect-ratio), Transformer с 4 corner-anchors для resize, click+Delete для удаления, hotkeys `←/→` для навигации между страницами + Esc deselect, layout сохраняется в %%-координатах per-page, submit собирает в плоский массив для backend. Превью страниц подтягиваются lazy через `/director/signed-documents/{doc}/page/{n}` (Imagick 120dpi). **Зависимости:** Composer `setasign/fpdi` + `tecnickcom/tcpdf` (user уже установил). PHP ext `imagick` (user устанавливает на проде через apt-get install php-imagick — без него GD-fallback). **Деплой:** `git pull && bash update.sh` накатит миграции + сидеры + сбросит кэши. Report: docs/reports/UPD_pak232_RESULTS.md.
-
pak231
2026-06-11
Design-doc нового модуля «Подписание документов» (Director Signatures). **Только дизайн, без кода.** Реализация в pak232 (MVP) после подтверждения. **Цель модуля:** дать члену департамента «Директор» поставить скан-подпись и печать на PDF-документ за минуту через UI — без графических редакторов и распечатки-сканирования. Типичный кейс — выставление счёта. **Юридическая оговорка:** это скан-подпись, НЕ криптографическая ЭЦП по ФЗ-63. Подходит для внутреннего документооборота. **Архитектура:** 2 таблицы — `user_signatures` (ассеты пользователя: подпись + печать, type ENUM, processed_path с alpha) + `signed_documents` (журнал — original/final/layout JSON/status/final_hash SHA-256). 3 сервиса — SignatureImageService (Imagick `transparentPaintImage` с fuzz=12% + GD fallback), DocumentUploadService (FPDI->getPageCount), PdfSignatureRenderService (FPDI накладывает PNG поверх через useTemplate + Image-вызовы, конвертация %% координат в mm). 2 контроллера — Director\SignatureController + Director\SignedDocumentController. Routes под `permission:signatures.use` (новый permission в Гендиректор-роль). UI — Konva.js-редактор (переиспользуем из Boards pak12-15): sidebar превью страниц + canvas с drag-drop подписи/печати из палитры + resize handles. **MVP scope (по выбору user):** только PDF на вход (Word/Excel — pak233+ через LibreOffice), журнал в БД, один подписант. **Зависимости:** Composer setasign/fpdi (free, PDF 1.4) + tecnickcom/tcpdf. PHP extension Imagick (pre-check в UI с install-hint). **Безопасность:** private storage, AuditLog на каждое действие, SHA-256 hash финала, disclaimer-плашка на каждой странице модуля + footer в финальный PDF. **План паков:** pak232 MVP (L-XL), pak233 Word/Excel + rotate + multi-page (M), pak234 шаблоны + интеграция с estimates/statements (M). Полный doc: docs/design/DESIGN_director_signatures.md (11 разделов: цели/не-цели/доступ/архитектура/безопасность/риски/UX flow/план/тесты/trade-offs/открытые вопросы).
-
pak230
2026-06-11
Hotfix pak229: bulk-delete задач лида ложно ругался «выберите хотя бы одну задачу». **Симптом:** на `/crm/leads/{id}` после включения bulk-mode user выделил все чекбоксы (или несколько), counter в toolbar показывал правильное число, но при нажатии «Удалить выбранные» вылетал alert «Сначала выберите хотя бы одну задачу». **Корневая причина:** в pak229 чекбоксы у задач рендерятся внутри `.task-row` (это столбец сетки task-row), а форма `#ltBulkForm` — отдельный элемент **выше** в DOM. Связь между ними реализована через HTML5 **Form Attribute pattern** (`<input form="ltBulkForm">`) — браузер при submit добавляет эти inputs в payload как если бы они лежали внутри формы. НО `form.querySelectorAll(.lt-bulk-cb:checked)` ищет **только physical descendants** внутри `<form>`-элемента — оно НЕ учитывает inputs привязанные через атрибут `form="..."`. Counter в `onLeadTaskBulkChange` работал корректно — он использовал `document.querySelectorAll(...)`. Но `confirmLeadTaskBulkDelete(form)` использовал `form.querySelectorAll(...)` → всегда 0 → ложный alert. **Фикс:** одна строка в `confirmLeadTaskBulkDelete` — `form.querySelectorAll` заменён на `document.querySelectorAll`. Также добавлен комментарий с пояснением Form Attribute pattern для будущих контрибьюторов. **Деплой:** `git pull && php artisan view:clear` (для сброса Blade-кэша). **Без миграций.** Report: docs/reports/UPD_pak230_RESULTS.md.
-
pak229
2026-06-11
CRM: лид-отказ больше не маячит как активный + bulk-удаление задач лида. **#1 Логика статуса «отказ»:** (а) **дашборд** виджеты «Последние лиды» и «Неактивные лиды» (и счётчик-KPI лидов) фильтровали по `is_deleted=false`+`is_archived=false`, **БЕЗ `is_lost`** — отказанный лид попадал в оба виджета. Заменено на `Lead::active()` (pak191-scope `is_archived=false AND is_lost=false`). (б) `InboxService::collectLeadInactivity` имел ту же дыру → заменено на `active()`. (в) `LeadService::markAsLost` при первичном пометке отказа теперь **отменяет все открытые задачи лида** — `LeadTask::where(lead_id, status IN [todo, in_progress])->update(status=cancelled)`. Раньше задачи оставались живыми → исполнитель продолжал видеть их в `/tasks`, `/dashboard`, `/calendar` и получать напоминания об overdue. (г) `LeadHistory` теперь содержит число отменённых задач: «❌ Лид помечен как отказ … · Отменено открытых задач: N». (д) `restoreLost` подсчитывает оставшиеся отменённые задачи и в history-record упоминает «Ранее отменённых задач: N (восстановите вручную при необходимости)» — automatically восстанавливать не безопасно, продавец сам решит. **#2 Bulk-удаление задач лида:** `LeadTaskController::bulkDestroy(Request, Lead)` — `authorize(update, lead)` + validate `task_ids[]` array → `update(is_deleted=true)` только по `lead_id=lead.id` (защита от подмены ids чужой задачи). Route `POST /crm/leads/{lead}/tasks/bulk-destroy`. UI на `/crm/leads/{id}`: новая кнопка `<i class=fa-check-square>` рядом с «+» в заголовке секции «Задачи» → toggle bulk-mode. В режиме: появляются чекбоксы у каждой задачи + красный toolbar с «Выделить все» / counter / «Отмена» / «Удалить выбранные». JS-функции `toggleLeadTaskBulkMode` / `toggleLeadTaskBulkAll` / `onLeadTaskBulkChange` / `confirmLeadTaskBulkDelete` (валидация >= 1 task + confirm-dialog). Главное применение — когда `CrmAutomationService` создал лишние задачи (pak222 защита от 80+ дублей фиксит проблему на старте, pak229 даёт ручную очистку накопленного). **Без миграций.** Деплой: `git pull && php artisan optimize:clear`. Report: docs/reports/UPD_pak229_RESULTS.md.