Решили in-house разработать CRM систему. По ходу разработки встречались интересные моменты, которые постараюсь описать в нескольких статьях. В статьях постараюсь избежать банальностей типа: скачал, распаковал, запустил, и вот глядите, swagger из коробки. Таких статей, как и видео на Ютубах, уже очень много. Постараюсь поделиться просто интересными деталями, которые встречались по ходу разработки. Забегу вперед — систему настроили и запустили.
1. Почему разработка, а не покупка готовой системы
По двум причинам. Во вторых потому, что покупные системы со временем так обрастают донастройками, что от «коробки» там уже мало что остается. А во первых потому, что (та-дам!) фреймворки стремительно развились уже в какие то платформы, в которых кодить приходится мало.
2. Выбор фреймворка
Хотелось найти фреймворк, в котором уже есть весь Boiler код, который нужен для бизнес приложения: меню, разделы, графики, пользователи, и т. п. В ходе поисков такого фреймворка обратили внимание на .Net фреймворки https://aspnetboilerplate.com и https://abp.io, в которых уже «из коробки» много чего есть. Насколько я понял, оба фреймворка развивают то ли родственные команды, то ли вообще одна команда. Причем команды из Турции. Опять же та-дам! Вот что делается, когда в стране нет нефти (то ли )))), то ли (((( ). В фреймворке ASP.NET Boilerplate есть легаси код, от старых версий .Net Framework. В более новом фреймворке ABP легаси нет, он на .Net Core. У обоих фреймфорков прилично звезд на github.
Отмечу видео на Ютубе, Алексей Мерсин хорошо рассказывает по ASP.NET Boilerplate, и на 31:41 показывает симпатичный интерфейс, слева меню, есть цветная тема, в общем то, что нужно. Но возможно, я что-то не понял, но этот интерфейс, который демонстрирует Алексей, это платная версия фреймворка. Бесплатная версия «из коробки» выглядит уныло.
Далее попалась интересная библиотека для фронта - PrimeNG, у нее есть три ветки, под Angular, под React, под Vue. В каждой ветке есть магазин с темами оформления, есть платные темы, есть бесплатные темы. Выглядит все очень красиво, «из коробки» есть все, что нужно для фронта, меню, вкладки, кнопки, всплывающие уведомления. И опять же та-дам! За PrimeNG стоит опять же команда из Турции, PrimeTek.
![](https://webcf.waybackmachine.org/web/20220801213805/https://habrastorage.org/getpro/habr/upload_files/ab6/bc4/2c7/ab6bc42c71c6d181878a68298e2c8f50.png)
В итоге решили разрабатывать на связке PrimeNG (Angular) + NestJs. Потому что фронт очень хочется на Angular, далее появляется желание экономить на экспертизе разработчиков, и поэтому пусть и на фронте и на бекэнд будет TypeScript.
Недостатки Node.js известны:
- TypeScript это все таки надстройка, отсутствие в JavaScript типов данных считаю недостатком.
- В папке node_modules будет лежать несколько десятков (сотен) тысяч файлов, написанных неизвестно кем.
2. Логирование запросов TypeORM
Для работы с базой данных фреймворк NestJs использует TypeORM. Библиотека TypeORM удивила, хорошо отрабатывает вносимые изменения в структуру таблицы, замену типов колонок, даже с данными в таблицах. А для того, чтобы посмотреть логи SQL запросов, которые порождаются TypeORM, нужно добавить параметр logging:
TypeOrmModule.forRoot({
type: 'mysql', // ...
entities: [ ContactEntity, ],
logging: true, // Для логирования SQL запросов в консоль
}),
3. Генерация UUID primary key в TypeORM
Boiler колонки в таблицах, которые должны быть в каждой таблице по умолчанию, это первичный ключ, дата создания, дата обновления. Если в TypeORM объявить:
export class ContactEntity {
@ApiProperty({description: 'Первичный ключ'})
@PrimaryGeneratedColumn()
id: string;
@ApiProperty({description: 'Дата создания'})
@CreateDateColumn()
created: Date;
@ApiProperty({description: 'Дата обновления'})
@UpdateDateColumn()
updated: Date;
}
То в MySQL базе это превратится в:
CREATE TABLE contacts (
id int(11) NOT NULL AUTO_INCREMENT,
created datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id)
);
Все хорошо. Но для бизнес приложения хочется первичный ключ UUID, а не целочисленный.
И если в TypeORM объявить:
export class ContactEntity {
@ApiProperty({description: 'Первичный ключ'})
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({description: 'Дата создания'})
@CreateDateColumn()
created: Date;
@ApiProperty({description: 'Дата обновления'})
@UpdateDateColumn()
updated: Date;
}
То в SQL базе это превратится в:
CREATE TABLE contacts (
id varchar(36) NOT NULL,
created datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
updated datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (id)
);
То есть первичный ключ это просто строка, без автогенерации UUID значения! Сначала это показалось странно. Но, выяснилось, что в TypeORM осознанно сделано так, UUID генерируется в коде TypeORM и вставка записей идет с уже заполненным полем UUID ключа. Потому, что в случае автогенерируемой UUID колонки, при каких то видах вставки TypeORM приходилось бы затем считывать вставленные записи, и еще раз обновлять их. Это в итоге работало бы медленнее, чем генерировать UUID на стороне TypeORM.
4. Уведомления в главном меню
В главном меню, у названий разделов, можно вывести индикатор кол-ва записей в разделе. Например, на пункте меню «Заказы», нужно вывести индикатор заказов в статусе «Новый», чтобы сотрудник сразу обратил внимание на то, что в систему упали с сайта новые заказы, и нужно эти новые заказы быстрее отработать. Для этого в PrimeNG есть параметр badge.
В модуле AppMenuComponent, в модели меню и разделов, у пункта «Заказы» указываем badge и целочисленное значение:
this.model = [
{
label: 'Разделы',
items:[
{label: 'Контакты', icon: 'pi pi-fw pi-user', routerLink: ['/contact']},
{label: 'Заказы', icon: 'pi pi-fw pi-table', routerLink: ['/order'], badge: 0},
]
},
]
Обновлять значение придется по абсолютному адресу пункта меню в модели:
this.model[ 0 ].items[ 1 ].badge = countNewOrder;
![](https://webcf.waybackmachine.org/web/20220801213805/https://habrastorage.org/getpro/habr/upload_files/dd1/02d/72d/dd102d72df57dd7d56a70825d779622d.png)
Roadmap системы
a) Нужно перенести генерацию отчетов в Excel с фронта на бекэнд. Excel файлы генерировать на бекэнде, и уже готовые файлы пересылать на фронт пользователю. Почему это кажется предпочтительнее, объясню в следующей статье.
b) Нужно прикрутить очередь заданий. Очевидно, что некоторые задачи могут выполняться неопределенное время, соответственно такие задачи нужно ставить в очередь, и потом уже забирать результаты.
c) Нужен workflow, например прохождения документа, хотя бы в элементарном виде, например, в виде справочника с этапами согласования документа.
d) Нужно прикрутить чаты и чат-бота.
Какие то куски того, что получается, будем показывать на демо стенде - PrimeNG demo.