В этой статье я хотел бы познакомить читателей с архитектурой весьма интересного open-source (GPL3) проекта EspoCRM на примере создания нового модуля для этой системы.
Что такое CRM-система (Customer relationship management), думаю, многие уже давно знают. Особенность данной CRM-системы в том, что она написана как Single Page Application и поэтому довольно «шустрая».
Простой дизайн и современные технологии программирования многим придутся по вкусу, а быстродействие данной CRM-системы приятно удивит. На сайте доступна демо-версия.
Добиться высокой скорости работы помогло кэширование скриптов и шаблонов в Local Storage. Все view вместе с дочерними собираются в один большой HTML, который отображается на экране у пользователя.
Система имеет мощный API, использующий JSON, а веб-интерфейс по сути является API-клиентом.
Система не перегружена функционалом, но имеет все необходимое, а также неплохо настраивается.
Создание нового модуля
Для начала нужно создать рабочую директорию нового модуля (это будет пакет модуля) и поместить её в application/Espo/Modules
:
application/Espo/Modules/PM
Структура каталогов нашего модуля должна быть следующая:
application/Espo/Modules/PM/Controllers/
application/Espo/Modules/PM/Entities/
application/Espo/Modules/PM/Resources/
Описание метаданных
При разработке PM мы должны описать две сущности — Project и ProjectTask. Для этого необходимо создать два JSON-файла со следующим содержанием:
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"module": "PM",
"customizable": true,
"stream": true,
"importable": true
}
{
"entity": true,
"layouts": true,
"tab": false,
"acl": true,
"module": "PM",
"customizable": true,
"stream": true,
"importable": true
}
После этого мы должны описать поля и связи для наших сущностей в метаданных entityDefs
.
{
"fields": {
"name": {
"type": "varchar",
"required": true
},
"status": {
"type": "enum",
"options": [
"Draft",
"Active",
"Completed",
"On Hold",
"Dropped"
],
"default": "Active"
},
"description": {
"type": "text"
},
"account": {
"type": "link"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true
},
"modifiedBy": {
"type": "link",
"readOnly": true
},
"assignedUser": {
"type": "link",
"required": true
},
"teams": {
"type": "linkMultiple"
}
},
"links": {
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"assignedUser": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "EntityTeam"
},
"account": {
"type": "belongsTo",
"entity": "Account",
"foreign": "projects"
},
"projectTasks": {
"type": "hasMany",
"entity": "ProjectTask",
"foreign": "project"
}
},
"collection": {
"sortBy": "createdAt",
"asc": false,
"boolFilters": [
"onlyMy"
]
}
}
{
"fields": {
"name": {
"type": "varchar",
"required": true
},
"status": {
"type": "enum",
"options": [
"Not Started",
"Started",
"Completed",
"Canceled"
],
"default": "Not Started"
},
"dateStart": {
"type": "date"
},
"dateEnd": {
"type": "date"
},
"estimatedEffort": {
"type": "float"
},
"actualDuration": {
"type": "float"
},
"description": {
"type": "text"
},
"project": {
"type": "link"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true
},
"modifiedBy": {
"type": "link",
"readOnly": true
},
"assignedUser": {
"type": "link",
"required": true
},
"teams": {
"type": "linkMultiple"
}
},
"links": {
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"assignedUser": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "EntityTeam"
},
"project": {
"type": "belongsTo",
"entity": "Project",
"foreign": "projectTasks"
}
},
"collection": {
"sortBy": "createdAt",
"asc": false,
"boolFilters": [
"onlyMy"
]
}
}
Указываем, что будем использовать контроллер для CRUD. Файл помещаем в директорию clientDefs
.
{
"controller": "Controllers.Record"
}
{
"controller": "Controllers.Record"
}
Контроллеры классов
Для работы сущностей необходимо описать их контроллеры.
<?php
namespace EspoModulesPMControllers;
class Project extends EspoCoreControllersRecord
{
}
<?php
namespace EspoModulesPMControllers;
class ProjectTask extends EspoCoreControllersRecord
{
}
Классы сущностей
Помимо определения контроллеров, также необходимо определить и классы для сущностей.
<?php
namespace EspoModulesPMEntities;
class Project extends EspoCoreORMEntity
{
}
<?php
namespace EspoModulesPMEntities;
class ProjectTask extends EspoCoreORMEntity
{
}
Локализация (I18n)
Добавляем имена наших сущностей в глобальный файл локализации Global.json
:
{
"scopeNames": {
"Project": "Project",
"ProjectTask": "Project Task"
},
"scopeNamesPlural": {
"Project": "Projects",
"ProjectTask": "Project Tasks"
}
}
Для перевода полей, выпадающих списков, связей и т.п. в наших сущностях нужно создать отдельные файлы локализации:
{
"labels": {
"Create Project": "Create Project"
},
"fields": {
"name": "Name",
"status": "Status",
"account": "Account"
},
"links": {
"projectTasks": "Project Tasks"
}
}
{
"labels": {
"Create ProjectTask": "Create Project Task"
},
"fields": {
"name": "Name",
"status": "Status",
"project": "Project",
"dateStart": "Date Start",
"dateEnd": "Date End",
"estimatedEffort": "Estimated Effort (hrs)",
"actualDuration": "Actual Duration (hrs)"
}
}
Последний штрих
После осуществления всех вышеописанных действий переходим в панель Администратора и производим восстановление системы (Menu -> Administration -> Rebuild), а затем обновляем страницу.
Теперь наш модуль Projects по умолчанию является скрытым; для отображения переходим в панель Администратора (Administration -> User Interface) и делаем его видимым. При помощи Field Manager и Layout Manager можно легко добавить новые поля и настроить внешний вид отображения данных.
Я по сути не имею отношения к проекту, я лишь довольный пользователь. Процесс создания модуля описан разработчиками и публикуется с их согласия. И разработчики, и я готовы ответить на все интересующие вопросы.
Автор: peter23