# ISAC - CRM
## цель документа
Помошь в организации архитектуры очередной CRM
## бекграунд
Для автоматизации определенного типа бизнесов требуется кастомная CRM.
Наши клиенты лучше всего описываются термином Стоматологическая клиника.
Со стороны клиента есть сеть клиник, в каждой клинике есть набор кабинетов, в кабиентах посменно работают врачи. У разных кабинетов разное оборудование, разные врачи имеют разные компетенции. Помимо услуг клиника ведет продажу расходиников (зубная паста и т.п.), а так же ведет учет расходиников (сверла).
Клиенты могу записываться на заданное время, то есть необходимо планирование времени клиентов и врачей.
У каждого клиента есть история покупок (она же история болезни), где мы можем делать апсейлы (чистка зубов), либо планировать обязательные собтытия - вырвать пломбу.
Может использоваться разная модель резервирования времени - через сайт, через телефонный звонок, личным приходом в кабинет.
Может использоваться разная модель оплат - предоплата, оплата постфактум, оплата в кредит, оплата сертификатом
Оплата может быть через сайт, оплата может быть наличными (фоп), оплата может быть терминалом (на какого-то из фопов).
Зарплата сотрудников может быть фиксированной, а может включать в себя разнообразные динамические составялющий (процент с продажи, фиксированая цифра с продажи), количестов рабочих дней в месяц, количество выходных и отгулов, прогулы.
Система должна поддерживать разнообразные языки интерфейса, помимо русского и украинского.
Система должна отражать историю взаимоотношений с клинетом, все изменения имени, телефона, адреса и т.п. должны быть доступны.
В рамках CRM должен быть товарный учет, у каждой сети может быть свой список товаров с совсем разными наборами атрибутов.
В рамках CRM каждая сеть может вести свой набор дополнительных полей связанных с клиентами, поля могут быть типизированными (enum)
В рамках сети отдельные кабинеты могут иметь независимых администраторов, которые не будут иметь доступа к общей бд клиентов, только к бд собвственных клиентов в рамках кабинета.
В рамкам сети отдельные кабинеты могут быть переданы другой сети.
## авторизация
необходимо различать две авторизации.
- авторизация сотрудников
- клиентская авторизация
### авторизация сотрудников
один человек может быть сотрудником нескольких сетей, поэтому необходимо при авторизации указывать в какую сеть будет просиходить логин. Вариант с авторизацией по логину паролю ок, но может потребоваться интеграция с SSO.
### клиентсакая авторизация
клиенты сети могут залогинится и увидеть список своих броней, забронировать новое посещение, покупку, изменить время бронированиея. Способы авторизации должны настраиваться в рамках виджета создаваемого в настройках сети. К примеру должна быть доступна авториазция по телефону, но поскольку СМС стоит денег должен быть способ ее отключить и оставить авторизацию только по емейлу или соц сетям. Возможно мы хотим сделать авторизацию только по телефону + подтвержденный емейл.
## бекенд
В наличии есть PHP разработчики, но не хотелось бы тратить много времени на написание CRUD, есть соблазн использовать https://hasura.io/ чтобы быстро получить АПИ.
### база данных
hasura.io работает с PostreSQL, его наверное и использовать. Однако механизм миграций hasura требует детальной проработки devops инженером на предмет его автоматизации. Так же остается вопрос с применением eventsourcing (хранения истории имзенений обьектов), т.к. Hasura удобна только для CRUD (однако для большинства обьеков системы этого должно хватить)
```graphviz
digraph {
graph [pad="0.5", nodesep="0.5", ranksep="2"];
node [shape=plain]
rankdir=LR;
Netowk [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Сеть</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="name">name:varchar</td></tr>
</table>>];
Place [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Филиал сети</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="PlaceID">PlaceID:uuid</td></tr>
<tr><td port="9">address</td></tr>
</table>>];
WorkPlace [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Рабочий кабинет</i></td></tr>
<tr><td port="NetorkID">NetorkID</td></tr>
<tr><td port="WorkPlaceID">WorkPlaceID</td></tr>
<tr><td port="servicesID">services</td></tr>
</table>>];
Services [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Услуги</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="ServiceID">ServiceID:uuid</td></tr>
<tr><td port="data">data:jsonb</td></tr>
<tr><td port="requirenments">requirenments:jsonb</td></tr>
</table>>];
Employee [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Сотрудники</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="ServiceList">ServiceList:[ServiceID]uuid</td></tr>
<tr><td port="WorkPlaceList">WorkPlaceList:[WorkPlaceID]uuid</td></tr>
<tr><td port="data">data:jsonb</td></tr>
</table>>];
Goods [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Товары</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="data">data:jsonb</td></tr>
</table>>];
Customer [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Клиент</i></td></tr>
<tr><td port="NetorkID">NetorkID:uuid</td></tr>
<tr><td port="CustomerID">CustomerID:uuid</td></tr>
<tr><td port="WorkPlaceList">WorkPlaceList:[WorkPlaceID]uuid</td></tr>
<tr><td port="data">data:jsonb</td></tr>
</table>>];
CustomerEvent [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td><i>Событие клиента</i></td></tr>
<tr><td port="CustomerID">CustomerID:uuid</td></tr>
<tr><td port="WorkPlaceList">WorkPlaceList:[WorkPlaceID]uuid</td></tr>
<tr><td port="data">data:jsonb</td></tr>
</table>>];
"User" [shape=record, label="{User|\
email :string\l\
password :string\l\
}"]
Customer:NetorkID -> Netowk:NetorkID;
Customer:WorkPlaceList -> WorkPlace:WorkPlaceID;
Customer:CustomerID -> CustomerEvent:CustomerID;
Netowk:NetorkID ->Goods:NetorkID;
Netowk:NetorkID -> Place:NetorkID;
Netowk:NetorkID -> Services:NetorkID;
Netowk:NetorkID -> Employee:NetorkID;
Services:ServiceID -> Employee:ServiceList;
WorkPlace:WorkPlaceID -> Employee:WorkPlaceList;
Place:NetorkID -> WorkPlace:NetorkID;
}
```
## фронтенд
### админ панель
для админ панели критической является стоимость поддержки в долгом периоде, при этом размер бандла, дизайн и т.п. вторичны. Возможно имеет смысл присмотрется к Angular. Сам по себе Angular + material очень удобен для создания больших админ панелей, но вопросы вызвает интеграция с GraphQL и возможность типизировать такие запросы в TS.
### виджет для клиентов
виджет для клиентов предназначен для встраивания в другие сайты, в виде webcomponent или скрипта. Оптимально иметь минимальный размер бандла и максимальную скорость загрузки. При этом сам виджет не очень сложен в плане количества экранов.
Возможно оптимально использовать Svelte без зависимостей вообще, стилизуя его через CSS-Variables на уровне кода сайта клиента.
## todo:
проверить механизм миграций hasura.io
проверить возможность типизации graphql запросов на фронте (https://graphql-code-generator.com/)
проверить возможность интеграции angular с apolo (apolo как store) (https://apollo-angular.com/docs/)