Описание задачи "Управление материальными потоками"

Создаваемая с помощью платформы lsFusion информационная система должна содержать функциональность для учета движения товаров на складе.

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

Задание предметной логики

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

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

Определение склада

Создаем модуль, в котором далее определим сущность склада и его атрибуты.

Задаем понятие склада и его атрибуты: наименование, адрес.

Определение товара

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

Задаем понятие товара и его атрибуты: наименование, штрих-код.

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

Определение организации

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

Задаем понятие организации и ее атрибуты: наименование, юридический адрес, ИНН.

Задаем уникальность ИНН для организации.

Свойство legalEntityINN связывает организацию и ИНН один к одному, и позволяет по заданному ИНН определить организацию. Выражение свойства можно трактовать следующим образом: при группировке организаций по ИНН (свойству innLegalEntity) в каждой из групп должна быть не повторяющаяся организация.

Определение приходной накладной

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

Зададим использование в модуле Receipt функциональности из других модулей.

Задаем понятия, определяющие логику приходной накладной. Будем исходить из принципа, что все документы (как приходные, так и расходные) в системе состоят из шапки и товарной спецификации. Соответственно определим понятия шапка приходной накладной и строка приходной накладной.


У каждой строки приходной накладной есть ссылка на шапку документа, и в итоге шапка документа и подмножество строк со ссылкой на этот документ в совокупности определяют приходную накладную с точке зрения пользователя. Параметр NONULL обозначает, что ссылка должна быть задана. Параметр DELETE определяет, что при удалении основного объекта Receipt, все строки ReceiptDetail, ссылающиеся на него, будут также удалены. По умолчанию, при удалении объекта все ссылки на него обнуляются. Таким образом, без параметра DELETE будет выдана ошибка, что ссылка не задана.


Определяем номер строки в приходной накладной.

Использование в выражениях имени класса объекта равнозначно использованию его идентификационного номера (id), создаваемого системой для всех объектов автоматическим счетчиком. В данном случае использование для сортировки конструкции ORDER receiptDetail позволяет отсортировать строки накладной в порядке возрастания их id, т.е. фактически в порядке последовательности их создания.

Здесь в инструкции PARTITION используется оператор BY, группирующий объекты по некоторому атрибуту и расчет нарастающим итогом суммы выражения выполняется в рамках каждой из групп. В данном случае определение номера строки идет только в рамках документа этой строки (свойство receipt(d)).

Задаем набор основных атрибутов шапки накладной: номер, дата, поставщик и его наименование, склад, на которой осуществляется оприходование товара, и его наименование. Наименование поставщика и склада в последующем понадобятся для удобного отображения на форме.

Задаем набор основных атрибутов строки накладной: товар и его наименование, количество, цена поставщика, сумма поставщика (рассчитывается умножением цены на количество).

Определение расходной накладной

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

 Задаем использование в модуле Shipment функциональности из других модулей.

Аналогично приходной накладной задаем сущности шапка расходной накладной и строка расходной накладной, а также ссылку в строке на шапку и ее номер.


Задаем набор атрибутов шапки накладной: номер, дата, покупатель и его наименование, склад, с которого осуществляется отгрузка товара, и его наименование.


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


Реализуем автозаполнение цены продажи товара по расходной накладной значением оптовой цены, заданной пользователем для товара (свойство salePrice). Автозаполнение должно срабатывать в момент изменения товара для строки расходной накладной (инструкция WHEN CHANGED).


Определение текущего остатка товара на складе

Текущий остаток товара на складе определяется как разница между всеми приходами товара на склад и всеми его расходами со склада.

Создаем отдельный модуль.


 Задаем использование в модуле StockItem функциональности из других модулей.


Задаем расчетное свойство текущего остатка товара на складе в количественном исчислении.


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


Задание логики представления

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

Вначале создаем формы справочников.

В модуле Stock добавляем форму, предоставляющую пользователю функциональность добавления и удаления складов, а также изменения их атрибутов.

Аналогично в модуле Item создаем форму товаров, а в модуле LegalEntity - форму организаций.

Создаем формы редактирования приходной накладной и редактирования расходной накладной. Эти формы будут использоваться при создании новых документов или редактировании существующих. Формы будут строиться по одинаковому принципу: состоять из двух вертикально расположенных блоков, верхний из которых будет содержать в панельном виде атрибуты шапки создаваемого/редактируемого документа, а нижний - строки данного документа в табличном виде и их атрибуты.

В модуле Receipt создаем форму редактирования приходной накладной. Для создаваемой формы указываем, что она будем использоваться в качестве формы по умолчанию при создании/редактировании приходных накладных (инструкция EDIT).

Фильтрация строк только текущей накладной выполняется с помощью выражения FILTERS receipt(d) == r. Конструкция FILTERS отображает объект соответствующего класса на форме, если выражение фильтра не возвращает NULL. В данном случае строка накладной отобразиться на форме в том случае, если шапка документа, на которую для строки задана ссылка (свойство receipt), равняется текущему объекту верхнего блока. Иными словами, отобразятся только строки создаваемого/редактируемого документа.

Кроме того, в случае, если для объектов данного класса на форме задан фильтр, то при нажатии пользователем кнопки NEW вновь добавленному объекту автоматически заполниться свойство исходя из того, чтобы этот объект удовлетворял заданному фильтру. В данном случае, при добавлении новой строки накладной этой строке автоматически заполниться свойство receipt ссылкой на текущую шапку накладной.

В модуле Shipment создаем форму редактирования расходной накладной. Для создаваемой формы указываем, что она будем использоваться в качестве формы по умолчанию при создании/редактировании расходных накладных (инструкция EDIT).

Формы приходных и расходных накладных графически будут выглядеть практически идентичными и состоять из двух вертикально расположенных блоков табличного вида - блока шапок документов и блока строк документов. Строки документа должны визуально фильтроваться по документам и их отображаемое на форме подмножество будет изменяться при навигации в верхнем блоке.

Создаем форму приходных накладных. На форму выведем все свойства, определенные выше для шапок документов и их строк. Дополнительно выносим автоматически определенные кнопки добавления и редактирования приходной накладной с помощью формы редактирования, созданной выше. Все свойства как шапки документов, так и их строк, кроме кнопок добавления и редактирования приходной накладной, делаем недоступным для изменения непосредственно на форме (оператор READONLY).

Аналогичным образом создаем форму расходных накладных.

Далее в модуле StockItem создадим форму отображения текущих остатков. Форма должна представлять собой таблицу, в строках которой указывается товар (его наименование и штрих-код), наименование склада и текущий остаток данного товара на данном складе.Количество строк на форме по умолчанию будет равняться количеству введенных в систему товаров умноженному на количество введенных складов. Для отображения только значимых данных (т.е. только тех товаров и складов, для пересечения которых есть текущий остаток) добавим на форму фильтр.

Конструкция OBJECTS si = (s = Stock, i = Item) добавляет группу объектов с псевдонином si, представляющая собой декартовое произведение объектов класса Stock и класса Item.

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

В модуле StockAccounting компонуем меню системы. Справочники добавляем в предопределенную папку навигатора masterData, а для документов создаем свою отдельную папку, которую показываем сразу после справочников. Вызов формы текущих остатков выносим в основное меню (горизонтальное окно root). Ссылки на формы справочников и документов будут отображаться в вертикальной панели инструментов (toolbar) при выборе пользователем соответствующей папки root.

Процесс создания информационной системы завершен.

Исходный код целиком (на Github)