skeeks / cms-proposal
Commercial proposals for SkeekS CMS CRM: versions, items with sections, templates, PDF and a personal link for every sending
Package info
github.com/skeeks-cms/cms-proposal
Type:yii2-extension
pkg:composer/skeeks/cms-proposal
Requires
- php: >=8.0
- kartik-v/yii2-mpdf: ^1.0.6
- skeeks/cms: ^6.4.9.64
- skeeks/cms-backend: ^2.1.1.20
- skeeks/cms-communication: ^1.4
Requires (Dev)
None
Suggests
- skeeks/cms-shop: items linked to products and services of the catalog
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-23 14:57:34 UTC
README
Единый реестр «Предложения» для CRM SkeekS CMS. Два вида:
- КП — коммерческое предложение: версии, позиции с разделами, шаблоны, PDF, страница КП по личной ссылке с учётом просмотров, «Принять» и «Задать вопрос»;
- Презентация — страница сайта (своего или любого, например
/works/sfery/plitka) и короткий текст менеджера: личная ссылка засчитывает просмотр и сразу переводит клиента на страницу.
Отправка, личные ссылки, учёт просмотров, отсев проверок ссылок, лента,
уведомления и задача менеджеру при первом открытии — общие для обоих видов.
Этап 3 проекта коммуникаций, согласованная архитектура —
skeeks/cms-communication/ARCHITECTURE.md, разделы «КП (реестр
предложений)», «Единый реестр „Предложения“: КП и Презентация» и «Отправить
клиенту».
Что в пакете
| Часть | Классы |
|---|---|
Вид (kind): proposal — КП, presentation — Презентация; выбирается при создании и не меняется. У Презентации — адрес страницы target_url (только http(s), путь сайта дополняется адресом сайта, хранится в предложении) и текст менеджера message, срок — по желанию; версий, позиций, PDF и «Принять» нет |
models\CmsProposal |
КП: номер на сайте, название, клиент (лид, компания, сделка или клиент-человек cms_user не сотрудник), ответственный, статус, срок действия (14 дней), валюта, текущая версия |
models\CmsProposal, models\queries\CmsProposalQuery (forManager(), forRecord(), forCompany(), search()) |
Версия: вступление и условия (RichTextInputWidget, пресет standard), позиции, сумма, скидка на всё, итог, копия наших реквизитов. Отправленная версия не меняется |
models\CmsProposalVersion, services\ProposalVersions |
Позиция или раздел: как строка счёта, со ссылкой на товар cms-shop (без внешнего ключа) |
models\CmsProposalItem, services\ProposalItems |
| Отправка: канал, получатель, сообщение CRM, секретный код личной ссылки, первый и последний просмотр, их число. Просмотр: время, браузер, робот | models\CmsProposalSend, models\CmsProposalView |
«Отправить клиенту» и «Прикрепить из… → КП» (communication.sendables['proposal']): ссылка на каждую отправку, PDF вложением; первая отправка фиксирует версию и сохраняет её PDF, черновик становится «Отправлено», лента «КП отправлено (канал)» |
send\ProposalSendableSource, send\ProposalSendable, services\ProposalSends |
Страница КП для клиента /~kp/<код> без входа: позиции, итог, условия, срок, «Скачать PDF», «Принять», «Задать вопрос»; плашки «Есть обновлённое предложение» (?v=latest), «Предложение истекло», «Принято» |
controllers\ProposalController, views/proposal/*, assets\ProposalPageAsset |
| Честный учёт просмотров: только сигнал из показанной страницы, роботы предпросмотра, повторы за 30 минут и сотрудники не считаются; первый просмотр отправки — «Открыто», лента «КП открыто (канал)», уведомление | services\ProposalViews |
| «Принять» (только новейшая отправленная версия, один раз) и «Задать вопрос» (входящее «Сообщение с сайта» в переписке клиента, лимиты, скрытое поле) | services\ProposalAnswers, models\CmsProposalQuestion |
| Уведомления ответственному (иначе автору) в колокольчике CRM | services\ProposalNotifier (CmsWebNotify) |
Страница-переход Презентации по той же ссылке /~kp/<код>: сигнал просмотра из страницы, затем переход на target_url с метками utm_source=crm, utm_medium=<канал>, utm_campaign=proposal-<номер> (уже указанные метки не затираются); без скриптов перехода нет, только ссылка «Перейти» |
views/proposal/presentation.php, services\PresentationLinks, sxPresentationPage в proposal-page.js |
| Отсев проверок ссылок (оба вида): открытие в первые 30 секунд после ухода письма, из сетей почтовых служб и защитных систем, автоматическим браузером — в истории с причиной, в ленте «возможно, проверка ссылок», без статуса, уведомления и задачи | services\ProposalViews + skeeks/cms-communication services\LinkChecks |
Общая Презентация — без клиента («одна на всех»): создаётся в разделе, отправляется из карточки любого лида, компании, сделки или клиента. Клиент записывается в отправку (cms_proposal_send.cms_lead_id … cms_user_id) — запись CRM получателя из окна «Отправить клиенту» или «Прикрепить из…»; без записи CRM отправка отклоняется. Лента, уведомление («<клиент>: клиент открыл презентацию…») и задача — у клиента отправки; в карточке клиента блок показывает отправленные ему общие Презентации и состояние его отправок; общую Презентацию видят все сотрудники |
CmsProposal::$isShared, CmsProposalSend::$client, CmsProposalQuery::shared()/forRecord(), ProposalUi::sentBadge() |
Текст менеджера Презентации — общий редактор текста (RichTextInputWidget, пресет basic, RichTextBehavior): в письмо идёт с оформлением (SendableHtmlInterface::getSendHtml()), в СМС — без тегов; старые тексты строками показываются абзацами |
CmsProposal::MESSAGE_PRESET, getMessageHtml(), getMessageText() |
«Отправить презентацию» и «Отправить КП» в меню «Действие» блока «Следующий шаг» карточки лида (точка skeeks/cms CrmCardBlocksWidget::EVENT_ACTIONS). «Отправить КП» — окно с КП этого клиента, ждущими отправки (черновики первыми; пустой черновик ведёт в «Содержимое», остальные — в «Отправить клиенту»), и «+ Создать новое КП»; если таких КП нет — сразу окно создания |
ProposalBootstrap, ProposalUi::nextStepActions(), AdminCmsProposalController::actionProposals(), views/admin-cms-proposal/proposals.php |
Шаги в одном окне: после «Сохранить» в окне создания КП открывается его «Содержимое», Презентация клиента — «Отправить клиенту», общая — её карточка; в «Содержимом» — «Сохранить и отправить клиенту» (submit-btn=send) |
create on afterSave → afterSaveUrl, ProposalUi::nextStepFormCallback() (activeFormConfig.clientCallback), ProposalUi::sendUrl() |
| «Отправить презентацию» в блоке «Предложения» карточек: окно со списком готовых общих Презентаций (действующих; чаще отправляемые сверху, поиск, если их больше 7), у каждой — «Отправляли …» / «Открывал …» для этого клиента (повторная отправка разрешена); выбор открывает «Отправить клиенту» этой Презентации с этим клиентом; без Презентаций — «Создать общую презентацию» | AdminCmsProposalController::actionPresentations() (?source=proposal-client:lead.29), views/admin-cms-proposal/presentations.php, ProposalUi::presentationsUrl(), ProposalUi::recordSendCondition() |
| Задача менеджеру при первом открытии (оба вида): «Позвонить: клиент открыл предложение №…» ответственному (иначе автору) на сегодня, вне рабочего времени — на начало ближайшего рабочего периода (график сотрудника, без графика — пн–пт 9–18); привязана к лиду/компании/клиенту (сделка — через её компанию) и к предложению; не ставится, пока у него есть открытая задача по этому предложению; отключается настройкой | services\ProposalTasks, models\CmsProposalTask, components\ProposalSettings |
| «Клиент открыл: …» вверху карточки такой задачи | source\ProposalClientSource::origins() (RecordOriginInterface) |
| Шаблоны КП | models\CmsProposalTemplate, controllers\AdminCmsProposalTemplateController |
Раздел «Предложения» (ProposalSettings — кнопка «Настройки» у администратора): список с фильтрами по виду, статусу и ответственному, окно создания — сначала вид, затем поля вида; карточка (отправки с каналом и просмотрами, история просмотров, вопросы клиента), вкладка «Содержимое» с редактором позиций, «Отправить клиенту», «Новая версия», «Скачать PDF» |
controllers\AdminCmsProposalController, widgets\ProposalItemsInputWidget, assets\* |
| «Создать предложение» из лида, компании, сделки | source\ProposalClientSource (?source=proposal-client:lead.15) |
| Блок «Предложения» в карточках лида, компании, сделки и клиента-человека | ProposalBootstrap, helpers\ProposalUi::cardBlock() на точке skeeks/cms CrmCardBlocksWidget |
| Лента: «КП создано», «Новая версия КП» (и события шага C) | services\ProposalFeed, log\ProposalLogRenderer |
| PDF версии через mPDF в оформлении шаблона документов сайта | services\ProposalPdf, views/pdf/proposal.php |
Права: раздел открыт всем сотрудникам с доступом в администрирование; каждое
КП видно администратору, ответственному и его руководителям, а также тем, кому
виден клиент КП (forManager() лида, компании, сделки). КП лида и сделки
видны в карточке их компании.
Зависимости
skeeks/cms^6.4.9.64 —CrmCardBlocksWidget(блоки пакетов в карточках),Skeeks::$recordSources,RichTextInputWidget,CmsDocumentTemplate.skeeks/cms-backend,kartik-v/yii2-mpdf(mPDF).skeeks/cms-communication^1.4 — «Отправить клиенту» (каналы письмо и СМС, позже мессенджеры),services\SiteMessagesдля вопросов клиента,services\LinkChecksдля проверок ссылок.skeeks/cms-shop— по желанию: выбор товара или услуги в редакторе позиций.
Страница КП для клиента
- Адрес —
/~kp/<код>(правилаurlManagerвconfig/web.php); код отправки — единственный доступ, как у страницы счётаcms-shop. Ссылка показывает ту версию, с которой её отправили;?v=latest— новейшую отправленную, действия страницы (seen,accept,ask,pdf) берут ту же. - Своя раскладка
views/proposal/layout.phpбезbeginPage()/head()/endBody(): пакеты сайта вешают на события View скрипты темы, баннер cookie, счётчики и ключевые слова, а страница отдаётся вResponse::$content, поэтомуcms-seoне дописывает в неё счётчики.noindexв заголовке и в meta,Referrer-Policy: no-referrer(код в адресе),Cache-Control: no-store. - Оформление — светлое, акцентный цвет и логотип из шаблона документов сайта (как PDF); на узком экране позиции идут карточками.
- Просмотр засчитывается запросом
POST …/seenиз скрипта страницы, когда она 1,5 секунды видна (visibilityState);curl, почтовые проверки и предпросмотр мессенджеров его не делают, а известные роботы (ProposalViews::BOT_PATTERNS) записываются в историю без счёта. - Сигнал передаёт признаки браузера (
navigator.webdriver, число языков, размер экрана): проверки ссылок в автоматическом браузере (LinkChecksпакетаskeeks/cms-communication) не засчитываются.
Презентация: страница-переход
- Та же ссылка
/~kp/<код>и то же оформление. Скрипт, как только страница 0,4 секунды видна, шлётPOST …/seenи затем (или через 2,5 секунды без ответа) заменяет страницу адресом презентации (location.replace: «Назад» не возвращает на переход). Сотрудник CRM видит, что переход не засчитан, и переходит через 3 секунды. - Адрес — только
CmsProposal::$target_url, никогда не параметр ссылки: страница не открытое перенаправление. К адресу добавляютсяutm_source=crm,utm_medium=<канал>,utm_campaign=proposal-<номер>(PresentationLinks::addUtm(), уже указанные метки и#фрагментсохраняются). - Без скриптов (роботы,
curl) просмотра и перехода нет — только ссылка «Перейти». После срока действия — «Ссылка больше не действует» и контакты ответственного, без перехода. - «Принять», «Задать вопрос» и PDF для Презентации отвечают 404.
Задача менеджеру при первом открытии
ProposalTasks::onFirstView()вызывается изProposalViewsпосле первого засчитанного просмотра отправки, для КП и Презентации; ошибка задачи пишется в журнал и не мешает клиенту.- Задача ядра
CmsTaskсебе (created_by= исполнитель, сразу «Принята»; на странице клиента никто не вошёл),plan_start_at— не раньше чем через 31 минуту (правило модели) в рабочем периоде сегодня, иначе начало ближайшего рабочего периода поwork_sheduleсотрудника. - Связь задачи с предложением —
cms_proposal_task: по ней ищется открытая задача ответственного по предложению (новая не ставится) и показывается «Клиент открыл: …» в карточке задачи. Закрытая задача не мешает новой при первом открытии следующей отправки. У общей Презентации открытая задача ищется по предложению и клиенту отправки: каждому открывшему клиенту — своя задача. - Настройка «Задача менеджеру при первом открытии»
(
ProposalSettings::$taskOnFirstView, по умолчанию включено) — кнопка «Настройки» в разделе у администратора; для кампаний этапа 4 будет своя настройка кампании.
Тест
php tests/proposal.php /app/vendor/autoload.php
SQLite в памяти: позиции и итоги, нумерация по сайтам, первая версия из
шаблона, реквизиты, правка черновика на месте, неизменяемость отправленной
версии, новая версия при правке, «Новая версия», лента, источник записи;
tests/client.php (запускается оттуда же) — отправка через «Отправить
клиенту» (ссылка на каждую отправку, фиксация версии, статус, лента, отказ
для истёкшего КП), просмотры (роботы, повтор, сотрудник, первый просмотр,
уведомление), «Принять» (только новейшая версия, один раз), «Задать вопрос»
(сообщение с сайта, связи с лидом и компанией, лимит), задача по первому
открытию КП; tests/presentation.php (оттуда же, шаг E) — Презентация (без
версий, проверка адреса, вид не меняется), её отправка («Посмотреть», без
файлов), адрес перехода с метками, отсев проверок ссылок, задача один раз
при открытой, новая после выполненной, выключение настройкой, расчёт
«сегодня / ближайший рабочий день».
Состояние работ
Выпущено 1.0.0 (23.10.2026) вместе с skeeks/cms 6.4.9.64 и
skeeks/cms-communication 1.4.0: КП (шаги B и C этапа 3) и Презентации с
задачей при первом открытии (шаг E). Проверено на стенде skeeks.com
настоящими письмами с тестового ящика: отправка письмом, отказ СМС до
провайдера на стенде, просмотр засчитывается один раз только из показанной
страницы (curl и проверки ссылок не засчитываются), «Принять», «Задать
вопрос», уведомления, задача «Позвонить», переход Презентации с метками;
светлая и тёмная темы, 375 px.
Дальше (по плану, cms-communication/ARCHITECTURE.md): шаг D — необязательные
позиции с выбором клиента и «Выставить счёт» из принятого КП.