JmixProperty и фильтрация по этому свойству

Здравствуйте.
Есть некая сущность Persons,
в которой указаны свойства:

    @Column(name = "BIRTHDAY")
    private LocalDate birthday;

    @JmixProperty
    @DependsOnProperties({"birthday"})
    public Integer getAge() {
        // возвращает возраст на текущую дату
        return (this.birthday!=null? Period.between( this.birthday,  LocalDate.now() ).getYears():0 );
    }

    @JmixProperty
    public LocalDate getWhenIsNextTheBirthday() {
        // возвращает, когда следующее день рождение
        return getBirthday()==null? LocalDate.now() : getBirthday().plusYears(getAge() + 1);
    }

Необходимо в интерфейсе сортировать по getWhenIsNextTheBirthday, накладывать фильтр на диапазон этого свойства, ограничивать интерфес по возрасту.

при попытке указать это свойство - его не видит.
idea64_Y7JeOal9Oz

в документации написано:

“Класс сущностей может иметь свойства (поле + геттер/сеттер) и методы, которые не являются атрибутами сущности, то есть не включены в метаданные. Таким образом, вы можете использовать такие свойства и методы в коде вашего приложения, но фреймворк не распознает их и не будет отображать в UI или передавать через REST API.”

Тогда каким образом решаються подобные задачи?

Так обращаться к Persons#birthday как вы представляете обращение по getWhenIsNextTheBirthday если такого поля нет в базе?

Возможно, неверно изъяснился. Могу ли я сортировать по вычисляемому полю и накладывать на него фильтр?

Так сортировка и фильтрация в загрузчике идет в базу. Если вы хотите именно по этому полю фильтровать и сортировать придется сделать свое отдельное поле в экране и при изменении в нем грузить ВСЕ сущности и сортировать и фильтровать в коде.

Если у вас какая-то сложная логика в поле и у вас острая необходимсть сортировать по этому полю. То наверное будет лучше сделать DbView (в котором это поле собственно будет) и навесить на него отдельную сущность с browse экраном. И при этом во всех actions на создание/редактирование/удаление грузить сущность из настоящей таблицы. Возможно есть еще какое-то более красивое решение, но я его не вижу.

Можно еще getWhenIsNextTheBirthday сделать реальным полем бд и при создании/обновлении сущности обновлять данное поле. Но в примере используется LocalDate.now() так что такой вариант навряд ли подойдет.

Логика экрана не сложная. Отобразить Сотрудников, у которых день рождение в ближайший период. Период можно выбрать. Сортировать нужно по дате наступления дня рождения.
Дату наступления дня рождения вычисляю - getWhenIsNextTheBirthday
Осталось ограничить “лишних”, которые не попадают в период, для этого введен slider
image

Если конкретно в этом примере то лучше в query все таки обращаться на прямую к полю birthday. Если у вас проблема в получении сегодняшней даты. То в query можно использовать CURRENT_DATE

Не могли бы вы привести пример с DBView? Совсем не понимаю, как это сделать.

Сделал тестовый проект. TestEntityView у нас смотрит на DbView. в create-test-entity-view.xml создается view. Правда я не уверен на счет clock_timestamp. Будет ли дата обновляться. Нужно потестить (Поставить дату на сег день и проверить что будет на завтра)

view сделана под postgresql!! в другий бд скорее всего будут другие функции.

jmix-test.zip (861.1 КБ)

Да и как я говрил в таком примере проще обращаться к birthday. так запар с 2 сущностями не будет и базе полегче будет без этой view

Получилось все таки через запрос и используя persons.birtday. Разбираться с DBView - видимо это уровень “не новичка” :slight_smile:

 public void updatePersonsBirthday()
    {
       LocalDate sDt = LocalDate.now(); // LocalDate.of(2022,02,01);
       LocalDate eDt = sDt.plusDays(slider.getValue().intValue());

       startDate.setValue( sDt );
       endDate.setValue(eDt);

       int calcStart = sDt.getMonthValue() * 100 + sDt.getDayOfMonth();
       int calcEnd   = eDt.getMonthValue() * 100 + eDt.getDayOfMonth();

       String query = "select e from Persons e where";

       if(sDt.getYear() == eDt.getYear())
        {
            //кондиция в рамках года.
            query += """
                         EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)
                         >=  :start_d
                         and  EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)
                         <=  :end_d   
                    """;
        }else
        {
            // если переходящий период
            // в рамках года с start до конца года,  и  с 1-го месяца года end до конца end
            query += """
                         ( EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)
                           >=  :start_d
                           and  EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)
                           <=  1231 )
                      or ( EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday) 
                           >=   101
                           and EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)
                           <=  :end_d  
                         )    
                    """;
        }

        query += """
                 order by  ( ( EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)) ) asc
                """;

        personsBirthdayDl = dataComponents.createCollectionLoader();

        personsBirthdayDl.setQuery(query);
        personsBirthdayDl.setContainer(personsBirthdayDc);
        personsBirthdayDl.setDataContext(getScreenData().getDataContext());
        personsBirthdayDl.setParameter("start_d", calcStart );
        personsBirthdayDl.setParameter("end_d"  , calcEnd   );
        personsBirthdayDl.load();
    }

Не уверен, что решение красивое, и к тому сортировка работает не так как надо, так как не учитывается год. Если бы нашел функцию (есть ли такая?) - которая делает дату из произвольных чисел - то было бы намного проще.
image

Видиться “костыльное” решение - принудительно программно щелкнуть на колонку “Когда ДР”, пока не разобрался, как.

Вот так у меня вроде все норм работает:

<loader id="testEntity1sDl">
                <query>
                    <![CDATA[select e from jt_TestEntity1 e]]>
                    <condition>
                        <and>
                            <c:jpql>
                                 <c:where><![CDATA[
                                    EXTRACT(DAY from e.birthday) > EXTRACT(DAY from current_date) and
                                    EXTRACT(MONTH from e.birthday) >= EXTRACT(MONTH from current_date) and
                                    EXTRACT(DAY from e.birthday) <= EXTRACT(DAY from CAST(:enDay timestamp)) and
                                    EXTRACT(MONTH from e.birthday) <= EXTRACT(MONTH from CAST(:enDay timestamp))
                                ]]></c:where>
                            </c:jpql>
                        </and>
                    </condition>
                </query>
            </loader>
    @Subscribe("slider")
    private fun onSliderValueChange(event: HasValue.ValueChangeEvent<Long>) {
        updatePersonsBirthday(event.value!!)
    }

    fun updatePersonsBirthday(value: Long) {
        val sDt = LocalDate.now()
        val eDt = sDt.plusDays(value)

        if (value == 0L) testEntity1sDl.removeParameter("enDay")
        else testEntity1sDl.setParameter("enDay", eDt)
        testEntity1sDl.load()
    }

image
image
image
Пс: Здесь слайдер на максимум 100 поставлен.

По сортировке сложно сказать. Пока не придумал…
Мб что-то типа

order by
    CASE WHEN EXTRACT(MONTH from e.birthday) > EXTRACT(MONTH from current_date) OR
    (
    EXTRACT(MONTH from e.birthday) == EXTRACT(MONTH from current_date) AND
    EXTRACT(DAY from e.birthday) > EXTRACT(DAY from current_date)
    ) THEN 10000 ELSE 0 +
    ((EXTRACT(MONTH FROM e.birthday) * 100 + EXTRACT(DAY FROM e.birthday)))

Вам поможет.

Спасибо за примеры. Ваше решение лаконичнее. Думаю, вопрос можно закрывать.

И все таки, почему то у меня не работает.
пишу самое простое, специально ставлю странное условие “лишь бы было”.
Если пишу просто order by e.birthday, то работает.
А если - в сортировке есть CASE, то валиться шлейф ошибок

JpqlSyntaxException: Errors found for input jpql:[select e from Persons e                               order by  (CASE WHEN (EXTRACT(MONTH from e.birthday) >6) THEN e.birthday ELSE e.birthday)]
CommonErrorNode [<unexpected: [@46,65:68='CASE',<9>,1:65], resync=(CASE WHEN (EXTRACT(MONTH from e.birthday) >6) THEN e.birthday ELSE e.birthday)>]
<query>
                    <![CDATA[ select e from Persons e
                              order by  (CASE WHEN (EXTRACT(MONTH from e.birthday) >6) THEN e.birthday ELSE e.birthday)
                    ]]>
                </query>

в IDE к тому же подчеркивает “красным” после order by
idea64_4Orf5VDZYc

Да… видать CASE не работает в order by. Нужно подумать как это сделать…

На текущий момент проблему с сортировкой решил принудительной установки сортировки в колонке.

 personsBirthdayDl = dataComponents.createCollectionLoader();

       personsBirthdayDl.setQuery(query);
       personsBirthdayDl.setContainer(personsBirthdayDc);
       personsBirthdayDl.setDataContext(getScreenData().getDataContext());
       personsBirthdayDl.setParameter("start_d", calcStart );
       personsBirthdayDl.setParameter("end_d"  , calcEnd   );
       personsBirthdayDl.load();

       personsTable.sort("whenIsNextTheBirthday", DataGrid.SortDirection.ASCENDING);