<h3><b>Цель тестового задания</b></h3><p>Реализовать Drupal-модуль, который выступает в роли <b>моста</b> между:</p><ul><li><p>внешней SQLite БД (production monitor),</p></li><li><p>и базой данных Drupal.</p></li></ul><p>Модуль должен:</p><ul><li><p>читать данные об изделии из SQLite,</p></li><li><p>выполнять расчёты на стороне PHP,</p></li><li><p>сохранять агрегированную информацию в Drupal,</p></li><li><p>выводить таблицу операций изделия.</p></li></ul><p>? Задание является <b>частью будущего переноса всей системы на Drupal</b>.<br />Важно не только «чтобы работало», но и <b>как это реализовано</b>.</p><p></p><hr /><p></p><h2><b>Общие условия</b></h2><ul><li><p><b>CMS:</b> Drupal 10 или 11</p></li><li><p><b>Источник данных:</b> SQLite (workshop_bot.db)</p></li><li><p><b>Дизайн / верстка:</b> не важны</p></li><li><p><b>Twig:</b> только отображение, без логики</p></li><li><p><b>Фокус:</b> архитектура, чистота кода, расчёты</p></li></ul><p></p><hr /><p></p><h2><b>Архитектура подключения к SQLite</b></h2><p>Модуль должен получать данные из внешней SQLite БД, используя:</p><ul><li><p>Drupal Database API;</p></li><li><p>централизованное подключение (не в контроллере).</p></li></ul><p>? <b>Запрещено:</b></p><ul><li><p>прямые вызовы PDO в контроллерах;</p></li><li><p>sqlite_open, new PDO() в бизнес-логике;</p></li><li><p>SQL в Twig.</p></li></ul><p></p><hr /><p></p><h2><b>Модуль</b></h2><p>Название: factory_bridge</p><p>Структура ожидаемо включает:</p><ul><li><p>сервис для работы с SQLite;</p></li><li><p>контроллер (или блок) для вывода данных;</p></li><li><p>при необходимости — entity или node.</p></li></ul><p></p><hr /><p></p><h2><b>Данные, используемые в тестовом задании</b></h2><h3><b>Используемые таблицы SQLite (обязательно):</b></h3><ul><li><p>order_items</p></li><li><p>assigned_tasks</p></li><li><p>work_sessions</p></li></ul><p>? Остальные таблицы, описанные в DATABASE_<a href="http://SCHEMA.md">SCHEMA.md</a>,<br /><b>в рамках тестового задания использовать не требуется</b>.</p><p></p><hr /><p></p><h2><b>Алгоритмы расчёта (обязательные)</b></h2><p>Для выбранного изделия (order_items.internal_id):</p><h3><b>1. Операции</b></h3><p>Операция = одна запись из assigned_tasks, связанная с изделием.</p><h3><b>2. НЧ План</b></h3><p>Берётся напрямую из:</p><p>assigned_tasks.applied_norm_hours</p><p><br /></p><h3><b>3. Ч Факт</b></h3><p>Сумма длительности всех рабочих сессий операции:</p><p>? (work_sessions.end_time - work_sessions.start_time)</p><p><br /></p><ul><li><p>если end_time = NULL — сессия считается активной;</p></li><li><p>расчёт выполняется на стороне PHP.</p></li></ul><h3><b>4. Дельта</b></h3><p>? = План – Факт</p><p><br /></p><p></p><hr /><p></p><h2><b>Вывод данных</b></h2><p>Реализовать страницу или блок, который выводит таблицу операций изделия.</p><h3><b>Минимальный набор колонок:</b></h3><ul><li><p>Код / Название операции</p></li><li><p>НЧ План</p></li><li><p>Ч Факт</p></li><li><p>? (дельта)</p></li></ul><p>Скриншот текущей реализации (не на Drupal) приложен <b>только как пример структуры данных</b>,<br />визуальное совпадение <b>не требуется</b>.</p><p></p><hr /><p></p><h2><b>Сохранение в Drupal</b></h2><p>При импорте данных:</p><ul><li><p>создать или обновить сущность в Drupal:</p><ul><li><p>Node типа «Изделие» <b>или</b></p></li><li><p>Custom Entity (по выбору разработчика).</p></li></ul></li></ul><p>Сохранить минимум:</p><ul><li><p>название изделия,</p></li><li><p>артикул / код,</p></li><li><p>статус (произвольная логика допустима).</p></li></ul><p></p><hr /><p></p><h2><b>Критерии приёмки (на что смотрим)</b></h2><ul><li><p>? Drupal Coding Standards</p></li><li><p>? Использование Services и Dependency Injection</p></li><li><p>? Отсутствие прямых SQL-инъекций в БД Drupal</p></li><li><p>? Корректная работа с датами и таймзонами (ISO)</p></li><li><p>? Вся бизнес-логика в PHP, не в Twig</p></li><li><p>? Читаемость и структура кода</p></li></ul><p></p><hr /><p></p><h2><b>Формат сдачи</b></h2><ul><li><p>Git-репозиторий <b>или</b></p></li><li><p>архив с модулем factory_bridge.</p></li></ul><p>README приветствуется, но не обязателен.</p><p></p><hr />