# Урок 4: Бот-магазин — списание остатков и блокировки двойных продаж

В данном уроке вы узнаете как создать самый простой бот для интернет-магазина с контролем остатков на складе.

Мы создадим бот для небольшого магазина глиняных изделий, который даст возможность клиентам рассмотреть имеющиеся товары и заказать их. Все заказы будут сохраняться в базу данных, а на складе будет производиться списание остатков.

Интерфейс бота в мессенджере будет состоять из экранов выбора категории и просмотра товаров конкретной категории. На экране товара, будут доступны кнопки навигации по товарам текущей категории и кнопка оформить заказ.

Главной особенностью данного урока будет работа с блокировками. Мы можем столкнуться с случаем когда остался один экземпляр определенного товара. Для того чтобы исключить ошибки, когда несколько заказчиков в одно и то же время хотят заказать этот товар, мы используем блокировки.

<div id="bkmrk-"></div>При использовании блокировок вы сможете обеспечить последовательное выполнение операций, запрошенных одновременно, гарантируя таким образом более надежную работу вашего приложения.

<p class="callout info align-left">Подробнее познакомиться с блокировками вы можете из инструкции **[Блокировки как средство управления параллелизмом](https://docs.metabot24.ru/books/4-instrukciya-js-programmista/page/blokirovki-kak-sredstvo-upravleniya-parallelizmom "Блокировки как средство управления параллелизмом")**</p>

<p class="callout info align-left">Подробнее изучить работу бота вы можете с помощью нашего примера — бота в Telegram: [**@BlockingMetabot**](https://t.me/BlockingMetabot)</p>

В ходе урока вы узнаете:

- Как работать с кастомными таблицами и создать с их помощью БД соответствующую вашим потребностям;
- Как контролировать параллельные процессы в ботах при помощи блокировок.

Отметим, что урок является одним из вариантов реализации данной задачи, целью которого является знакомство с функционалом для работы с блокировками.

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

Пора перейти к практике!

<p class="callout info align-left">Перед началом следует ознакомиться с предыдущими уроками **[Hello Humans: ваше руководство по быстрому старту](https://docs.metabot24.ru/books/3-ucimsya-na-primerax/page/hello-humans-vase-rukovodstvo-po-bystromu-startu "Hello Humans: ваше руководство по быстрому старту")** и **[Metabot 101: Вывод фото в боте по REST API](https://docs.metabot24.ru/books/3-ucimsya-na-primerax/page/metabot-101-vyvod-foto-v-bote-po-rest-api "Metabot 101: Вывод фото в боте по REST API")**</p>

### Создание скриптов

**1.** Чтобы не отвлекаться на это в будущем, сразу создадим стартовый скрипт с приветствием, маршрут связанный с ним и канал Телеграм, при помощи которого будем тестировать бота, как мы делали это в предыдущих уроках.

Нам потребуются следующие скрипты:

- Стартовый скрипт;
- Меню;
- Скрипты категорий;
- Скрипты выбора товара;
- Скрипт оформления заказа.

Перейдем к их созданию

#### Стартовый скрипт и меню

Стартовый скрипт будет активироваться всего раз после начала лидом диалога с ботом. В нем должно быть всего две команды: отправка сообщения с приветствием и переход на скрипт меню.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/z5QF1x8JDO7Jf6kO-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/z5QF1x8JDO7Jf6kO-image.png)

Скрипт меню будет выполнять роль навигатора для переключения между категориями товаров. Основным его наполнением будут кнопки, каждая из которых ведет в скрипт определенной категории товаров. Например, в нашем случае, для магазина глиняных изделий, будет создано две категории: тарелки и кружки.

**2.** Таким образом в скрипте меню нам нужно добавить команду отправки сообщения-подсказки, например: "Какие товары вас интересуют?", и кнопки меню соответствующие каждой категории.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/CJyQL9RhFvg6NoRW-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/CJyQL9RhFvg6NoRW-image.png)

#### Скрипты категорий

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

Для удобства все скрипты категорий можно занести в меню скриптов в один раздел. Разделы можно сворачивать, чтобы ненужные в данный момент скрипты не мешались перед глазами, а так же их можно сортировать между собой и организовывать свое рабочее пространство как вам удобно.

**3.** Чтобы это сделать создайте раздел при помощи кнопки **Создать раздел**. Введите название раздела и подтвердите действия кнопкой **Создать**.

[![Снимок экрана 2023-07-26 131333.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/oCXYN6po1wfeksvu-snimok-ekrana-2023-07-26-131333.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/oCXYN6po1wfeksvu-snimok-ekrana-2023-07-26-131333.png)

**4.** Далее в редакторе свойств скрипта из выпадающего списка **Раздел** выберите созданный ранее раздел.

[![Снимок экрана 2023-07-26 131333.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/h3lkt95aWfcYuogB-snimok-ekrana-2023-07-26-131333.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/h3lkt95aWfcYuogB-snimok-ekrana-2023-07-26-131333.png)

Теперь ваше рабочее пространство будет лучше организованно и с ним будет удобнее работать.

**5.** Но вернемся к наполнению скриптов категорий.

Данные скрипты понадобятся нам для того, чтобы бот мог запомнить, товары из какой категории следует выводить боту. В каждом таком скрипте мы создадим две команды: JS код с созданием атрибутов и переход к скрипту вывода товаров.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/fzcuj5Rhf2AInf7I-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/fzcuj5Rhf2AInf7I-image.png)

В команде **Выполнить JavaScript** в атрибуты бота будут добавлены две переменные:

- **category** — переменная в которой будет храниться наименование выбранной лидом категории;
- **productNum** — переменная в которой будет храниться начальный номер выводимого продукта.

Ниже вы можете скопировать код с фото.

```JavaScript
lead.setAttr('category', 'mugs');
lead.setAttr('productNum', 0);
```

Перед созданием оставшихся скриптов следует реализовать БД для магазина.

### Создание кастомных таблиц

Платформа Metabot предоставляет возможность создания своей БД при помощи кастомных таблиц.

<p class="callout info align-left">Подробнее познакомиться с кастомными таблицами вы можете из инструкции **[Кастомные таблицы](https://docs.metabot24.ru/books/4-instrukciya-js-programmista/page/kastomnye-tablicy "Кастомные таблицы")**</p>

**1.** Чтобы перейти в меню кастомных таблиц в верхнем меню бота из выпадающего списка **Таблицы** выберите **Конструктор таблиц**.

[![Снимок экрана 2023-07-26 131333.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/F7rStLbb72fL9ea9-snimok-ekrana-2023-07-26-131333.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/F7rStLbb72fL9ea9-snimok-ekrana-2023-07-26-131333.png)

Для нашего бота нужны следующие таблицы:

- Таблица товаров;
- Таблица заказов.

Перейдем к их созданию.

#### Таблица товаров

**2.** Для создания новой таблицы нажмите на кнопку **Создать** в меню кастомных таблиц. В открывшемся окне заполните следующие поля:

- **Наименование** — название таблицы используемое в JS методах (должно состоять только из латинских букв без пробелов);
- **Заголовок в интерфейсе** — название таблицы отображаемое в интерфейсе (может состоять из любых символов).

**3.** Остальные поля оставьте без изменения и нажмите кнопку **Создать**.

[![Без имени.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/ruTDJLcpNvyto2fK-bez-imeni.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/ruTDJLcpNvyto2fK-bez-imeni.png)

**4.** После создания перейдем в структуру таблицы по соответствующей кнопке в конструкторе таблиц.

[![Без имени.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/LVh8JgWV9tim8OTN-bez-imeni.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/LVh8JgWV9tim8OTN-bez-imeni.png)

Здесь мы можем указать какие поля будет содержать наша таблица. Для примера создадим первое поле таблицы товаров **Название**, в котором будут храниться названия продающихся в магазине товаров.

**5.** Для этого в меню структуры таблицы нажимаем на кнопку **Создать**. В открывшемся окне заполняем следующие поля:

- **Наименование** — название поля используемое в JS методах (должно состоять только из латинских букв без пробелов);
- **Заголовок в интерфейсе** — название поля отображаемое в интерфейсе (может состоять из любых символов);
- **Тип** — выпадающий список с типами значений, которые может принимать поле.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/ysiiwiR2BR9pgBlE-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/ysiiwiR2BR9pgBlE-image.png)

**6.** Таким же образом создаем остальные поля таблицы товаров:

- **Категория** — категория товара (в нашем случае **plates** или **mugs**);
- **Количество** — количество товаров данного типа оставшихся на складе;
- **Цена** — цена товара;
- **Фото** — ссылка на изображение товара.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/2ozVlxhFINz2KG9I-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/2ozVlxhFINz2KG9I-image.png)

<p class="callout warning">Значения в поле **category** и названия категорий из скриптов категорий должны быть одинаковыми для исправной работы данного бота.</p>

**7.** Затем таблицу нужно заполнить товарами для каждой категории, для исправной работы бота:

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/uX9H2eQeMI0u1P2z-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/uX9H2eQeMI0u1P2z-image.png)

#### Таблица заказов

**8.** По тому же принципу создаем таблицу заказов со следующими полями:

- **ФИО** — имя заказчика;
- **Адрес** — адрес на который нужно прислать товар;
- **Телефон** — номер телефона заказчика;
- **Категория** — категория из которой был заказан товар;
- **Товар** — id товара в таблице товаров.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/Phk4H8z0DbwmXpoZ-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/Phk4H8z0DbwmXpoZ-image.png)

Таблица заказов будет заполняться автоматически при взаимодействии лидов с ботом. Теперь мы можем переходить к созданию оставшихся скриптов.

### Работа с методами таблиц 

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

#### Скрипт вывода товаров

В скрипте вывода товара при помощи обращения к таблице товаров и команды отправки текста лиду будет выведена вся информация о товаре.

**1.** Для начала добавим команду **Выполнить JavaScript** со следующим кодом:

```JavaScript
let category = lead.getAttr('category');
let productNum = lead.getAttr('productNum')

let item = table.find("products", [], [["category", "=", category]]);

lead.setAttr('productNumber', parseInt(productNum) + 1);
lead.setAttr('productName', item[productNum].name);
lead.setAttr('productQua', item[productNum].qua);
lead.setAttr('productPrice', item[productNum].price);
lead.setAttr('productPhoto', item[productNum].photo);
```

В первой и второй строке мы берем из атрибутов лида информацию о том, какая категория была выбрана пользователем и какой по счету товар следует выводить.

Затем в четвертой строке мы обращаемся к таблице с помощью команды нахождения записей и записываем результат в локальную переменную **item**. Для этого указываем наименование таблицы и условие по которому будет искаться запись. В нашем случае мы ищем запись сопоставляя категорию выбранную лидом с категорией товара в таблице.

<p class="callout info align-left">Подробнее изучить методы кастомных таблиц вы можете из [**Справочника функций JS**](https://docs.metabot24.ru/books/7-spravocnik-po-funkciyam-js/page/spravocnik-vsex-funkcii "Справочник всех функций")</p>

В строках 5-8 из записи под номером **productNum** в массиве записей запоминаем в атрибутах лида все данные о товаре. Эти данные затем будут выведены в диалог с лидом при помощи команды отправки текста.

**2.** После JS команды добавляем команду **Отправить текст** со следующим содержимым:

<table border="1" class="align-center" id="bkmrk-%D0%A2%D0%BE%D0%B2%D0%B0%D1%80-%E2%84%96%7B%7B%24productnum" style="border-collapse: collapse; width: 41.8524%; height: 113.565px;"><tbody><tr style="height: 113.565px;"><td class="align-left" style="width: 100%; height: 113.565px;">Товар №{{$productNumber}} — {{$productName}}  
  
Осталось на складе: {{$productQua}}  
Цена: {{$productPrice}}  
  
Фото товара: {{$productPhoto}}</td></tr></tbody></table>

Таким образом у нас получается следующий набор команд:

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/nGOfA35wg3Og56qW-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/nGOfA35wg3Og56qW-image.png)

**3.** В данном скрипте осталось только создать меню со следующими кнопками:

- **Следующий заказ** — будет ссылаться на скрипт для вывода следующего товара;
- **Оформить товар** — будет ссылаться на скрипт оформления заказа;
- **Выбрать другую категорию** — будет ссылаться на скрипт меню.

#### Скрипт перехода на следующий товар

Данный скрипт поможет нам выводить лиду следующие товары из таблицы. В нем нужно создать две команды: JS скрипт с изменением номера товара и переход в скрипт вывода товаров.

**4.** Для начала добавим команду **Выполнить JavaScript** со следующим кодом:

```JavaScript
let category = lead.getAttr('category');
let productNum = lead.getAttr('productNum');

let item = table.find("products", [], [["category", "=", category]]);

if (item.length > productNum + 1){
  lead.setAttr('productNum', parseInt(productNum) + 1);
} else {
  lead.setAttr('productNum', 0);
}
```

В данном скрипте происходит проверка больше ли количество записей в таблице товаров выбранной категории чем значение номера товара, который должен быть выведен лиду. Если да, то номер товара увеличивается на один, иначе счет номера товара начинается сначала (с нуля).

**5.** После JS команды добавляем команду **Выполнить скрипт** — **Вывод товара** и переходим к созданию следующего скрипта.

### Работа с блокировками

Перейдем к созданию последнего скрипта в нашем боте — **Оформление заказа**.

В данном скрипте у лида будут запрошены контактные данные, а затем они и остальные данные о заказе будут добавлены в таблицу заказов.

**1.** Первым делом создадим три команды Запросить значение. Из них мы узнаем имя, адрес и телефон заказчика.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/cEpkCEJzTVt972MB-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/cEpkCEJzTVt972MB-image.png)

**2.** Затем перейдем к созданию команды **Выполнить JavaScript**. Одной из основных ее частей будет являться работа с блокировками.

```JavaScript
let customer = lead.getAttr('customer');
let address = lead.getAttr('address');
let phone = lead.getAttr('phone');
let category = lead.getAttr('category');
let productNum = lead.getAttr('productNum');

// Запись для таблицы заказов

let order = {
  "name": customer,
  "address": address,
  "phone": phone,
  "category": category,
  "product_id": productNum
}

//----------------------------

let isLocked = false

// Получаем блокировку по боту

bot.sendText('Рассматриваем ваш заказ! Ожидайте подтверждения');

isLocked = bot.waitForBotLock('my_lock', '', 20, 300);

//----------------------------

if (isLocked) {
  
  // Проверяем остались ли товары на складе
  
  let item = table.find("products", [], [["category", "=", category]]);

  if (item[productNum].qua > 0){
    
  //----------------------------
    
  // Обновляем данные на складе и создаем запись в таблице заказов
    
    item[productNum].update({"qua": item[productNum].qua - 1});
    
    table.createItem("orders", order);
    
  //----------------------------
    
    bot.sendText('Заказ оформлен');
    
  } else {
    
    bot.sendText('К сожалению данного товара уже нет на складе');
  
  }
} else {
  
  bot.sendText('Время ожидания истекло, попробуйте еще раз');

}

// Освобождаем блокировку полученную в данном скрипте

let hasLock = bot.hasLockForBot('my_lock');

if (hasLock) {
  
  bot.releaseCurrentLockForBot('my_lock');

}

//----------------------------
```

Для начала скрипт узнает все необходимые переменные из атрибутов лида и заносит их в JSON переменную, которая понадобится при создании новой записи в таблице заказов.

Далее переходим к работе с блокировками.

Скрипт отправляет лиду сообщение о том что его заказ рассматривается и пытается перехватить блокировку. Если время ожидания захвата истекло, то пользователю отправляется сообщение с просьбой попробовать еще раз.

Если же блокировка была захвачена, то скрипт проверяет, точно ли остались товары на складе, и если и эта проверка прошла успешно, то из поле **Количество** в записи товара вычитается единица, а данные заказа добавляются в таблицу заказов.

В конце скрипта происходит проверка на существование ранее захваченной блокировки и ее удаление.

**3.** После JS команды добавляем команду **Выполнить скрипт** — **Меню**.

Бот готов к тестированию.

### Проверка работы бота

Проверим как работает наш бот.

**1.** Выберите категорию в меню. У вас должны вывестись данные о товаре.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/QmVEemMLC6pnxCtS-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/QmVEemMLC6pnxCtS-image.png)

**2.** Нажмите на кнопку **Следующий товар**. У вас должны вывестись данные о другом товаре.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/eoJbZAWDk1fq3Bdy-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/eoJbZAWDk1fq3Bdy-image.png)

**3.** Нажмите на кнопку **Оформить заказ**. Бот должен запросить у вас данные и прислать сообщение о принятии заказа.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/pdwYg32QTDYNxHjs-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/pdwYg32QTDYNxHjs-image.png)

В боте при этом должны обновиться данные в таблицах. В таблице заказов должен появиться новый заказ. А в таблице товаров должно уменьшиться количество выбранного товара.

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/YUhzfDAuVqloQX1U-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/YUhzfDAuVqloQX1U-image.png)

[![image.png](https://docs.metabot24.ru/uploads/images/gallery/2023-07/scaled-1680-/RSpMc93p4x4jwWK3-image.png)](https://docs.metabot24.ru/uploads/images/gallery/2023-07/RSpMc93p4x4jwWK3-image.png)

Если в каждом из пунктов все работает правильно, значит у вас получилось сделать свой бот-магазин.

Поздравляем вас с прохождением урока!