# Контрольное задание ## Вариант 1 ### Регистр движения книг #### Создание класса-журнала - Создайте директорию `regmov` и в нем класс `Lbr_RegisterMov` - `Регистр движения книг` с типом `journal` и атрибутами: |name|attribute-type|caption|Дополнительно| |-|-|-|-| | idBook | Long | Книга | Ссылается на класс Lbr_Book | | gidSrcDoc | Varchar | Документ источник | Атрибут переменной ссылочности (`type="refAnyObject"`), ссылается на класс Lbr_Document | | gidDet | Varchar | Позиция источник | Атрибут переменной ссылочности | | nDirection | Number | Направление | | | idLibrarian | Long | Библиотекарь | Ссылочный на объект, ссылается на класс Bs_Person | | idPerson | Long | Читатель | Ссылочный на объект, Ссылается на класс Bs_Person | | nQty | Number | Кол-во | | | dReg | Date | Дата операции | Редактор даты | | idDepOwner | Long | Организация | Ссылочный на объект, Ссылается на класс Bs_DepOwner | - Запустите кодогенерацию по данному классу - Добавьте `orm` класса в `all.xml` и запустите генератор таблиц для него - Добавьте в `Lbr_MainMenuAvi` пункт `Регистр движения книг` под `Тесты и отладка`, в котором пропишите открытие умол. списка для `Lbr_RegisterMovAvi` #### Отражение в регистр и удаление из него В `Lbr_RegisterMovApi` напишите логику для отражения и удаления из регистра: - Для универсальности и изоляции логики регистра объявите `case class` (для удобства можно объявить перед `class Lbr_RegisterMovApi`, например назвать `objReg`) с полями из таблицы ниже - Создайте метод отражения в регистр по списку объектов данного `case class` (например `wrtByObj(apObj: List[objReg]`). В нем напишите обход (например через `foreach`) по списку объектов `apObj` и создание записей в регистре по данным из них, при установке количества, можно записывать сразу с учетом nDirection, чтобы в дальнейшем при вычислении остатков не писать nQty*nDirection. - Напишите метод удаления из регистра по полю `gidSrcDoc`, на вход будет gid документа (например `deleteBySrcDoc(gipSrcDoc: NGid)`). Вместо запросов можно использовать транзакционный индекс. [Руководство разработчика: Взаимодействие с базой данных # Транзакционный индекс](books/AppGuide/020_common/055_взаимодействие_с_базой.md#транзакционный-индекс). В `Api` документов: - Напишите метод отражения в регистр по позициям документа, на вход будет rop документа (например `wrtToReg(rop: ApiRop)`). Для этого создайте переменную `val avObj = ArrayBuffer[objReg]()` и при обходе по позициям документа (через `refreshByParent`) заполните `avObj` объектами из `case class` с данными по таблице ниже. После обхода позиций вызовите метод отражения в регистр по данным заполненного списка `Lbr_RegisterMovApi().wrtByObj(avObj.toList)` - На переход состояния в >=300 и из < 300, после вызова метода с проверками, пропишите вызов метода отражения в регистр, на обратный переход пропишите вызов метода удаления из регистра. Таблица с полями для `case class` и откуда надо брать для них значения: |Имя поля в регистре|Тип|Значение| |-|-|-| | idBook | NLong | idBook из позиции | | gidSrcDoc | NGid | gid документа | | gidDet | NGid | gid позиции | | nDirection | NNumber | 1 для приходной накладной и возврата книг, -1 для выдачи книг | | idLibrarian | NLong | idLibrarian из документа | | idPerson | NLong | idPerson из документа, если есть такое поле| | nQty | NNumber | Количество в позиции, если атрибута с количеством нет, то берем 1 | | dReg | NDate | Дата документа | | idDepOwner | NLong | idDepOwner из документа | #### Метод проверки на отрицательные остатки В `Lbr_RegisterMovApi` напишите метод проверки на наличие отрицательных остатков для регистра, в разрезе idBook и idDepOwner. Для этого: - Добавьте еще один `case class` с атрибутами разреза (например `case class objRegVal(idBook: NLong, idDepOwner: NLong)`) уже внутри `Lbr_RegisterMovApi` - Добавьте новый метод, на вход которого будет список из данного `case class` (например `validateReg(apObjVal: List[objRegVal])`) - Для упрощения написания проверки, можно использовать `for (rv <- new ASelect` с запросом по регистру, с группировкой по разрезам и условием на отрицательное количество. Для передачи в запрос данных из параметра `apObjVal`, создайте массивы с типом `LongPgArray` для idBook и idDepOwner (например `val idavBook = LongPgArray(apObjVal.map(_.idBook))`) и в запрос пропишите в конструкции `with` трансформацию массивов в таблицу со столбцами. Далее уже можно обращаться к `data` как к обычной таблице. ``` with data as( select unnest(${idavBook}) idBook ,unnest(${idavDepOwner}) idDepOwner ) ``` - Внутри `for` соберите полученные данные запроса с отрицательными остатками в формате `книга ${HL книги} - остаток ${получившееся количество} (организация ${HL организации})"` в переменную (например `val avError = ArrayBuffer[NString]()`) объявленную до `for` и уже в конце если она не пустая, то пропишите вызов ошибки в формате `Действие невозможно, так как в регистре образовались отрицательные остатки: ${собранные в avError данные}`. - Если проверка будет осуществляться через запрос, то обязательно перед `for` пропишите `session.flush()`, чтобы в БД были актуальные данные. - В методе `wrtByObj` и `deleteBySrcDoc` допишите сбор данных разрезов в переменную со списком `case class` (например `val avObjRegVal = ArrayBuffer[objRegVal]()`) и в конце допишите вызов метода проверки `validateReg(avObjRegVal.toList)` #### Отчет по остаткам ##### Шапка Создайте отдельную `avi` для отчета по остаткам с полями: - ISBN книги - Наименование книги - Остаток на начало периода - Возврат/поступление за период - Выдача за период - Изменение за период - Остаток на конец периода Особенности реализации: 1. Данные для отчета берем по регистру `Lbr_RegisterMov` 2. Берем данные до конца периода и фильтруем их по организации. 3. Для расчета количество на начало периода суммируем только те данные, дата которых меньше начала периода. 4. Для расчета изменений в текущем периоде суммируем только те данные, дата которых входит в наш период. ~~~{note} Для того чтобы исключить данные из суммирования можно воспользоваться следующей конструкцией: ```sum(case when r.dRegDate <= :flt_dBegin then <расчет количества> end)``` ~~~ Фильтр с подстановкой значений по умолчанию из главной выборки приложения: - `Период с`\ редактор даты. - `по`\ редактор даты - `Организация`\ выпадающий список Расположите фильтры в одну строчку (принцип размещения атрибутов на панели фильтров аналогичен размещению в карточке. Если будете делать через разметку `hBox`, `vBox`, то используйте тег `layout` внутри тега `filter`) ```{note} В этом задании нельзя использовать макрос фильтра в тексте запроса, т.к. нам нужны данные до начала периода, чтобы посчитать количество на начало. Следует обращаться к переменным фильтра напрямую в запросе, например, `:flt_dBegin` Перечень атрибутов выборки главного приложения можно посмотреть через `debugger`. Для получения значения используйте метод `getVar`, передав в него имя нужного атрибута с префиксом `super$` ``` ##### Детализация Создайте и подключите детализацию к мастер-выборке с расшифровкой по документам: - `HL документа` (для удобства заголовок можно взять из `Lbr_Document`) - `Дата` - `Количество с учетом направления` ```{note} 1. Данные отчета берем по регистру `Lbr_RegisterMov` 2. Фильтруем их по периоду, книге и организации 3. Группируем по документу, считаем количество ``` Добавьте операцию открытия документа в карточке с помощью метода открытия по gid `Btk_CommonOperLib().cardEdit` ## Вариант 2 ### Регистр движения книг #### Создание класса-журнала - Создайте директорию `regmov` и в нем класс `Lbr_RegisterMov` - `Регистр движения книг` с типом `journal` и атрибутами: |name|attribute-type|caption|Дополнительно| |-|-|-|-| | idBook | Long | Книга | Ссылается на класс Lbr_Book | | gidSrcDoc | Varchar | Документ источник | Атрибут переменной ссылочности (`type="refAnyObject"`), ссылается на класс Lbr_Document | | gidDet | Varchar | Позиция источник | Атрибут переменной ссылочности | | nDirection | Number | Направление | | | idLibrarian | Long | Библиотекарь | Ссылочный на объект, ссылается на класс Bs_Person | | idPerson | Long | Читатель | Ссылочный на объект, Ссылается на класс Bs_Person | | nQty | Number | Кол-во | | | dReg | Date | Дата операции | Редактор даты | | idDepOwner | Long | Организация | Ссылочный на объект, Ссылается на класс Bs_DepOwner | - Запустите кодогенерацию по данному классу - Добавьте `orm` класса в `all.xml` и запустите генератор таблиц для него - Добавьте в `Lbr_MainMenuAvi` пункт `Регистр движения книг` под `Тесты и отладка`, в котором пропишите открытие умол. списка для `Lbr_RegisterMovAvi` #### Отражение в регистр и удаление из него В `Lbr_RegisterMovApi` напишите логику для отражения и удаления из регистра: - Для универсальности и изоляции логики регистра объявите `case class` (для удобства можно объявить перед `class Lbr_RegisterMovApi`, например назвать `objReg`) с полями из таблицы ниже - Создайте метод отражения в регистр по списку объектов данного `case class` (например `wrtByObj(apObj: List[objReg]`). В нем напишите обход (например через `foreach`) по списку объектов `apObj` и создание записей в регистре по данным из них, при установке количества, можно записывать сразу с учетом nDirection, чтобы в дальнейшем при вычислении остатков не писать nQty*nDirection. - Напишите метод удаления из регистра по полю `gidSrcDoc`, на вход будет gid документа (например `deleteBySrcDoc(gipSrcDoc: NGid)`). Вместо запросов можно использовать транзакционный индекс. [Руководство разработчика: Взаимодействие с базой данных # Транзакционный индекс](books/AppGuide/020_common/055_взаимодействие_с_базой.md#транзакционный-индекс). В `Api` документов: - Напишите метод отражения в регистр по позициям документа, на вход будет rop документа (например `wrtToReg(rop: ApiRop)`). Для этого создайте переменную `val avObj = ArrayBuffer[objReg]()` и при обходе по позициям документа (через `refreshByParent`) заполните `avObj` объектами из `case class` с данными по таблице ниже. После обхода позиций вызовите метод отражения в регистр по данным заполненного списка `Lbr_RegisterMovApi().wrtByObj(avObj.toList)` - На переход состояния в >=300 и из < 300, после вызова метода с проверками, пропишите вызов метода отражения в регистр, на обратный переход пропишите вызов метода удаления из регистра. Таблица с полями для `case class` и откуда надо брать для них значения: |Имя поля в регистре|Тип|Значение| |-|-|-| | idBook | NLong | idBook из позиции | | gidSrcDoc | NGid | gid документа | | gidDet | NGid | gid позиции | | nDirection | NNumber | 1 для приходной накладной и возврата книг, -1 для выдачи книг | | idLibrarian | NLong | idLibrarian из документа | | idPerson | NLong | idPerson из документа, если есть такое поле| | nQty | NNumber | Количество в позиции, если атрибута с количеством нет, то берем 1 | | dReg | NDate | Дата документа | | idDepOwner | NLong | idDepOwner из документа | #### Метод проверки на отрицательные остатки В `Lbr_RegisterMovApi` напишите метод проверки на наличие отрицательных остатков для регистра, в разрезе idBook и idDepOwner. Для этого: - Добавьте еще один `case class` с атрибутами разреза (например `case class objRegVal(idBook: NLong, idDepOwner: NLong)`) уже внутри `Lbr_RegisterMovApi` - Добавьте новый метод, на вход которого будет список из данного `case class` (например `validateReg(apObjVal: List[objRegVal])`) - Для упрощения написания проверки, можно использовать `for (rv <- new ASelect` с запросом по регистру, с группировкой по разрезам и условием на отрицательное количество. Для передачи в запрос данных из параметра `apObjVal`, создайте массивы с типом `LongPgArray` для idBook и idDepOwner (например `val idavBook = LongPgArray(apObjVal.map(_.idBook))`) и в запрос пропишите в конструкции `with` трансформацию массивов в таблицу со столбцами. Далее уже можно обращаться к `data` как к обычной таблице. ``` with data as( select unnest(${idavBook}) idBook ,unnest(${idavDepOwner}) idDepOwner ) ``` - Внутри `for` соберите полученные данные запроса с отрицательными остатками в формате `книга ${HL книги} - остаток ${получившееся количество} (организация ${HL организации})"` в переменную (например `val avError = ArrayBuffer[NString]()`) объявленную до `for` и уже в конце если она не пустая, то пропишите вызов ошибки в формате `Действие невозможно, так как в регистре образовались отрицательные остатки: ${собранные в avError данные}`. - Если проверка будет осуществляться через запрос, то обязательно перед `for` пропишите `session.flush()`, чтобы в БД были актуальные данные. - В методе `wrtByObj` и `deleteBySrcDoc` допишите сбор данных разрезов в переменную со списком `case class` (например `val avObjRegVal = ArrayBuffer[objRegVal]()`) и в конце допишите вызов метода проверки `validateReg(avObjRegVal.toList)` #### Отчет по остаткам ##### Главная форма Создайте отдельную `avi` для отчета по остаткам с полями: - `ISBN книги` - `Наименование книги` - `HL документа` - `Дата документа` - `Номер документа` - `Количество с учетом направления` Особенности реализации: 1. Данные для отчета берем по регистру `Lbr_RegisterMov` 2. Поля, которые относятся к документу можно взять из миксина `Lbr_Document` 3. Фильтруем их по периоду, организации и документу 4. Рабочий период определяется от даты в фильтре +- 30 дн. 5. Группируем по документу, считаем количество Фильтры: - `Дата`\ Текущая дата. - `Организация`\ выпадающий список. Значение берется из главной выборки приложения - `Документ` Фильтр с тремя точками. Позволяет открыть список объектов миксина `Lbr_Document`. Фильтры `дата` и `Организация` расположите в одной строчке. Фильтр `Документ` в отдельной строке (принцип размещения атрибутов на панели фильтров аналогичен размещению в карточке. Если будете делать через разметку `hBox`, `vBox`, то используйте тег `layout` внутри тега `filter`) ```{note} Перечень атрибутов выборки главного приложения можно посмотреть через `debugger`. Для получения значения используйте метод `getVar`, передав в него имя нужного атрибута с префиксом `super$` Текущую дату можно получить через `NDate.now()` ``` Добавьте операцию открытия документа в карточке с помощью метода открытия по gid `Btk_CommonOperLib().cardEdit` ##### Форма с остатками В главной форме добавьте операцию на тулбар, которая будет по выделенной строке регистра выводить информацию об остатках по книгам, по которым было движение в указанном периоде. Для этого необходимо: 1. Создать еще одно отображение в вышей выборке, которая будет отображать информацию с остатками 2. Из операции главной формы необходимо открывать форму с остатками, передав туда параметры о текущем выбранном периоде, организации, и документе в выделенной строке. 3. В форме-расшифровке реализуйте вывод следующих полей: - `ISBN книги` - `Наименование книги` - `Остаток на начало периода` - `Возврат/поступление за период` - `Выдача за период` - `Изменение за период` - `Остаток на конец периода` Особенности реализации: 1. Данные для отчета берем по регистру `Lbr_RegisterMov` 2. Берем данные до конца периода и фильтруем их по организации (они переданы через параметры в форму). 3. Берем только книги, по которым было движение в переданном документе. 4. Рабочий период определяется от переданной даты +- 30 дн. 4. Для расчета количество на начало периода суммируем только те данные, дата которых меньше начала периода. 5. Для расчета изменений в текущем периоде суммируем только те данные, дата которых входит в наш период. ~~~{note} Следует обращаться к переданным параметрам напрямую в запросе, например, `:dDate_dz` Для того чтобы исключить данные из суммирования можно воспользоваться следующей конструкцией: ```sum(case when r.dRegDate <= :dDate_dz - interval '30 days' then <расчет количества> end)``` ~~~