Решили 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.   В итоге решили разрабатывать на связке 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; Roadmap системы a) Нужно перенести генерацию отчетов в Excel с фронта на бекэнд. Excel файлы генерировать на бекэнде, и уже готовые файлы пересылать на фронт пользователю. Почему это кажется предпочтительнее, объясню в следующей статье. b) Нужно прикрутить очередь заданий. Очевидно, что некоторые задачи могут выполняться неопределенное время, соответственно такие задачи нужно ставить в очередь, и потом уже забирать результаты.  c) Нужен workflow, например прохождения документа, хотя бы в элементарном виде, например, в виде справочника с этапами согласования документа. d) Нужно прикрутить чаты и чат-бота.  Какие то куски того, что получается, будем показывать на демо стенде - PrimeNG demo.  CRM, Angular, NestJs