Создание нового модуля для open-source CRM EspoCRM

в 10:32, , рубрики: CRM, CRM-системы, open source, php

imageВ этой статье я хотел бы познакомить читателей с архитектурой весьма интересного 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-файла со следующим содержанием:

application/Espo/Modules/PM/Resources/metadata/scopes/Project.json

{
	"entity": true,
	"layouts": true,
	"tab": true,
	"acl": true,
	"module": "PM",
	"customizable": true,
	"stream": true,
	"importable": true
}

application/Espo/Modules/PM/Resources/metadata/scopes/ProjectTask.json

{
	"entity": true,
	"layouts": true,
	"tab": false,
	"acl": true,
	"module": "PM",
	"customizable": true,
	"stream": true,
	"importable": true
}

После этого мы должны описать поля и связи для наших сущностей в метаданных entityDefs.

application/Espo/Modules/PM/Resources/metadata/entityDefs/Project.json

{
	"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"
		]
	}
}

application/Espo/Modules/PM/Resources/metadata/entityDefs/ProjectTask.json
{
	"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.

application/Espo/Modules/PM/Resources/metadata/clientDefs/Project.json

{
	"controller": "Controllers.Record"
}

application/Espo/Modules/PM/Resources/metadata/clientDefs/ProjectTask.json

{
	"controller": "Controllers.Record"
}

Контроллеры классов

Для работы сущностей необходимо описать их контроллеры.

application/Espo/Modules/PM/Controllers/Project.php

<?php
 
namespace EspoModulesPMControllers;
 
class Project extends EspoCoreControllersRecord
{
}

application/Espo/Modules/PM/Controllers/ProjectTask.php
<?php
 
namespace EspoModulesPMControllers;
 
class ProjectTask extends EspoCoreControllersRecord
{
}

Классы сущностей

Помимо определения контроллеров, также необходимо определить и классы для сущностей.

application/Espo/Modules/PM/Entities/Project.php

<?php
 
namespace EspoModulesPMEntities;
 
class Project extends EspoCoreORMEntity
{
}

application/Espo/Modules/PM/Entities/ProjectTask.php

<?php
 
namespace EspoModulesPMEntities;
 
class ProjectTask extends EspoCoreORMEntity
{
}

Локализация (I18n)

Добавляем имена наших сущностей в глобальный файл локализации Global.json:

application/Espo/Modules/PM/Resources/i18n/en_US/Global.json

{
	"scopeNames": {
		"Project": "Project",
		"ProjectTask": "Project Task"
	},
	"scopeNamesPlural": {
		"Project": "Projects",
		"ProjectTask": "Project Tasks"
	}
}

Для перевода полей, выпадающих списков, связей и т.п. в наших сущностях нужно создать отдельные файлы локализации:

application/Espo/Modules/PM/Resources/i18n/en_US/Project.json

{
	"labels": {
		"Create Project": "Create Project"
	},
	"fields": {
		"name": "Name",
		"status": "Status",
		"account": "Account"
	},
	"links": {
		"projectTasks": "Project Tasks"
	}
}

application/Espo/Modules/PM/Resources/i18n/en_US/ProjectTask.json

{
	"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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js