AXForum  
Вернуться   AXForum > Блоги > Gustav'ово бложище, или Записки DAX-дилетанта-III
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

Стараюсь писать про Аксапту, хотя частенько тянет в Офис
Оценить эту запись

Контроль возможности создания журнала

Запись от Gustav размещена 28.07.2009 в 16:41

Ну, что... попробуем создать первую, так сказать, содержательную запись. Итак...

Возникла необходимость не разрешать пользователям создание журналов (в различных модулях), если не выполнено некоторое условие. В нашем приложении Axapta от GMCS этим условием является заполненность поля "Первичная группа пользователя" формы "Параметры" (верхнее меню \ Сервис \ Параметры \ вкладка Разное; поле UserGroupDim в таблице SysUserInfo). Значение этого поля фактически определяет конкретный филиал нашего холдинга, к которому принадлежит пользователь.

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

Неприятности начинаются, когда пользователю снова необходимо переключиться с аналитической работы на операционную и ввести какой-нибудь журнал. Если пользователь забыл включить конкретный филиал, то созданный журнал сохранится и разнесется "вне филиала" со всеми вытекающими последствиями - конечно, не супер-пагубными, но всё же вызывающими раздражение. Коррекцией ситуации обычно занимается системный администратор, который по звонку пользователя, допустившего оплошность, средствами СУБД или джобиком Аксапты прописывает недостающие филиальные данные в журнальные таблицы.

И вот было решено избавить администратора от этих неконструктивных действий и контролировать пользователя при создании журнала. Возник вопрос - в каком конкретном месте этот контроль выполнять? Я не смог найти (или плохо искал) единого предка для всех журналов (какой-нибудь JournalTable-папа), чтобы в каком-либо подобающем методе (new, construct, create, insert и т.п.) которого вставить необходимую проверку. Попробовал самое начало метода construct класса JournalTableData - получилось не очень удачно, ну в смысле - совсем не получилось:
X++:
private static JournalTableData construct(
    JournalTableMap _journalTable
    )
{
    // мои эксперименты -->
    ;
    box::info('Привет из констракта');
    // return;
    // мои эксперименты <--

    switch (_journalTable.tableId)
    {
        case tableNum(TutorialJournalTable):    return new tutorialJournalTableData(_journalTable);
        case tableNum(ProjJournalTable):        return new ProjJournalTableData(_journalTable);
        case tableNum(LedgerJournalTable):      return new LedgerJournalTableData(_journalTable);
        case tableNum(WMSJournalTable):         return new WMSJournalTableData(_journalTable);
        case tableNum(InventJournalTable):      return new InventJournalTableData(_journalTable);
        case tableNum(ProdJournalTable):        return prodJournalTableData::newTable(_journalTable);
        case tableNum(RPayJournalTable):        return new RPayJournalTableData(_journalTable);
        case tableNum(RHRMOrderTable):          return new RHRMJournalTableData(_journalTable);

        default :
            return new JournalTableData(_journalTable);
    }
}
Тогда я решил просто навставлять проверочный код в начало метода insert тех таблиц, которые присутствуют в вышеприведенном методе construct, благо что из этого перечня живыми в нашем приложении оказалось всего две: LedgerJournalTable и InventJournalTable.

Сказано - сделано. В начало методов insert двух указанных таблиц был добавлен одинаковый фрагмент:
X++:
    if (KKu::userUnknownFilialToCreateJournal(this))
        return; // да - прерываемся
а сам статический метод определен как:
X++:
static boolean userUnknownFilialToCreateJournal(Common _common)
{
    #define.alarmJournalDescr('ПОЖАЛУЙСТА, УДАЛИТЕ ЭТУ НЕПРАВИЛЬНУЮ ЗАПИСЬ БЕЗ ФИЛИАЛА!')
    ;
    if (SysUserInfo::find(curUserId()).UserGroupDim)
    {
        return false;
    }
    else
    {
        box::stop(strFmt('%1\n\n%2\n%3\n\n%4\n%5',
            'Невозможно создать журнал - не указан филиал текущего пользователя!',
            'Пожалуйста, укажите филиал в поле "Первичная группа пользователя" формы "Параметры",',
            'после чего начните создание журнала заново.',
            'Если Вы работаете в форме-списке журналов, то удалите неправильную запись без филиала',
            'или закройте и снова откройте форму.'));

        switch (_common.TableId)
        {
            case tableNum(LedgerJournalTable):
                _common.(fieldNum(LedgerJournalTable, Name)) = #alarmJournalDescr;
                break;

            case tableNum(InventJournalTable):
                _common.(fieldNum(InventJournalTable, Description)) = #alarmJournalDescr;
                break;
        }
        return true; // Неизвестный филиал пользователя для создания журнала
    }
}
Получилось, конечно, несколько громоздковато, но, с другой стороны, производимый эффект совпадает с ожиданиями заказчика

Происходит следующее. В зависимости от конкретной формы, для которой срабатывает insert, пользователю "без филиала" разрешается создать "запись" в гриде формы, и внешне она выглядит как обычная запись, до тех пор пока пользователь не перейдет на другую запись или не попробует перейти в строки журнала при помощи соответствующей кнопки. Тут и вылетает предупреждение, а поле описания журнала заполняется просьбой об удалении записи. Если пользователь закроет форму и потом откроет ее, то "записи" этой уже не будет.

Буду рад, если кто-нибудь из читателей укажет путь к какому-нибудь более правильному методу решения этой задачи. Я почти уверен, что он существует!
Размещено в Без категории
Просмотров 267550 Комментарии 7
Всего комментариев 7

Комментарии

  1. Старый комментарий
    Возможно окажется полезным, один из следующих вариантов (если устраивает проверка в момент validateWrite и создание записей происходит на форме):

    1. journalTableData\validateWritePre (если честно, я не совсем понимаю, почему он не подошел сразу)

    2. journalFormTable\datasourceValidateWritePost (можно конечно и другой более лучший метод данного класса попробывать)

    3. classFactory\createRecord (разумеется здесь нужно делать заглушку на те таблицы, которые нужно проверять, в момент создания записи)
    Запись от SRF размещена 03.08.2009 в 09:39 SRF is offline
    Обновил(-а) SRF 03.08.2009 в 09:44
  2. Старый комментарий
    Аватар для Gustav
    Цитата:
    Сообщение от SRF Просмотреть комментарий
    Возможно окажется полезным, один из следующих вариантов (если устраивает проверка в момент validateWrite и создание записей происходит на форме):
    К сожалению, не только на форме - еще надо при безинтерфейсном пакетном создании журналов (в частности, амортизации). Но Ваши нижеследующие советы все равно очень интересны.
    Цитата:
    Сообщение от SRF Просмотреть комментарий
    1. journalTableData\validateWritePre (если честно, я не совсем понимаю, почему он не подошел сразу)
    А вот чего-то все равно не получилось: вставил return false; и запись журнала спокойно создалась...

    Цитата:
    Сообщение от SRF Просмотреть комментарий
    2. journalFormTable\datasourceValidateWritePost (можно конечно и другой более лучший метод данного класса попробывать)
    Конкретно этот метод не стал использовать - слово Post смутило, но нашёл в этом классе метод datasourceCreatePre - вот это, похоже, в яблочко! Т.е. для ФОРМ идеально - не создается даже фантомная запись в гриде и пользователя можно сразу "завернуть".

    Цитата:
    Сообщение от SRF Просмотреть комментарий
    3. classFactory\createRecord (разумеется здесь нужно делать заглушку на те таблицы, которые нужно проверять, в момент создания записи)
    пока не попробовал, но обязательно буду иметь в виду!

    SRF, большое Вам спасибо! К сожалению, функционал блогов пока не позволяет количественно повлиять на репутацию, по надеюсь, что именно "пока".

    P.S. В результате модификации статический метод выродился и стал безобразно прост:
    X++:
    static boolean userUnknownFilialToCreateJournal2()
    {
        ;
        if (SysUserInfo::find(curUserId()).UserGroupDim)
        {
            return false;
        }
        else
        {
            box::stop(strFmt('%1\n\n%2\n%3',
                'Невозможно создать журнал - не указан филиал текущего пользователя!',
                'Пожалуйста, укажите филиал в поле "Первичная группа пользователя" формы "Параметры",',
                'после чего начните создание журнала заново.'));
            return true; // Неизвестный филиал пользователя для создания журнала
        }
    }
    И к двум уже имеющимся местам его вызова (в методах insert таблиц LedgerJournalTable и InventJournallTable) добавился вызов в методе datasourceCreatePre класса JournalFormTable:
    X++:
    boolean datasourceCreatePre()
    {
        boolean allowCreate = ctrlAllOpenPosted.selection() == AllOpenPosted::Posted ? false : true;
    
        if (formRunLines && allowCreate)
        {
            formRunLines.close();
            formRunLines = null;
        }
    
        // KKu, 03.08.2009 --> Неизвестен филиал пользователя для создания журнала?
        if (KKu::userUnknownFilialToCreateJournal2())
            return false; // да - прерываемся
        // KKu, 03.08.2009 <--
    
        return allowCreate;
    }
    Запись от Gustav размещена 03.08.2009 в 11:36 Gustav is offline
    Обновил(-а) Gustav 03.08.2009 в 12:20
  3. Старый комментарий
    Цитата:
    Сообщение от Gustav Просмотреть комментарий
    А вот чего-то все равно не получилось: вставил return false; и запись журнала спокойно создалась...
    Посмотрел в код и вот, что обнаружил, в методе construct класса JournalTableData, содержится некий список таблиц
    X++:
    private static JournalTableData construct(
        JournalTableMap _journalTable
        )
    {
        switch (_journalTable.tableId)
        {
            case tableNum(TutorialJournalTable):    return new tutorialJournalTableData(_journalTable);
            case tableNum(ProjJournalTable):        return new ProjJournalTableData(_journalTable);
            case tableNum(LedgerJournalTable):      return new LedgerJournalTableData(_journalTable);
            case tableNum(WMSJournalTable):         return new WMSJournalTableData(_journalTable);
            case tableNum(InventJournalTable):      return new InventJournalTableData(_journalTable);
            case tableNum(ProdJournalTable):        return prodJournalTableData::newTable(_journalTable);
            case tableNum(RPayJournalTable):        return new RPayJournalTableData(_journalTable);
            case tableNum(RHRMOrderTable):          return new RHRMJournalTableData(_journalTable);
            default :
                return new JournalTableData(_journalTable);
        }
    }
    Так вот, во всех, перечисленных таблицах, кроме LedgerJournalTable, метод validateWrite содержит вызов :
    X++:
    ret = _journalTableData.validateWritePre(ret);
    Для сравнения залез в AX 4.0 и бинго! данный вызов имеется и на таблице LedgerJournalTable.

    Так что по всей видимости для AX 3.0 наблюдается баг, в отношении создания журналов.

    Думаю, если добавить данный вызов и в AX 3.0 в метод validateWrite таблицы LedgerJournalTable, то можно использовать JournalTableData\validateWritePre, причем, этим способом можно защиться и в случае, когда журнал создается из кода, конечно, если идет вызов метода validateWrite
    Запись от SRF размещена 03.08.2009 в 12:15 SRF is offline
  4. Старый комментарий
    Отчего столь содержательное обсуждение не выносится в форум? И как быть с разрастанием информационного поля, т.е. если раньше достаточно было мониторить форум, то теперь еще и блоги шерстить? Есть ли глобальный поиск по контенту (форум+блоги)?
    Запись от player размещена 11.08.2009 в 07:20 player is offline
  5. Старый комментарий
    Аватар для Gustav
    Цитата:
    Сообщение от player Просмотреть комментарий
    Отчего столь содержательное обсуждение не выносится в форум? И как быть с разрастанием информационного поля, т.е. если раньше достаточно было мониторить форум, то теперь еще и блоги шерстить? Есть ли глобальный поиск по контенту (форум+блоги)?
    Думаю, что не выдам страшную тайну, сказав о том, что эти вопросы уже поднимались в модераторских кулуарах и даже зафиксированы в плане на доработку. Функционал блогов проходит обкатку, поэтому блоги пока и не афишируются широко. Но не писать же все время тестово о погоде или ау-ау. Вот я и решил начать писать по существу - надо же посмотреть, как будет выглядеть в блоге реальное обсуждение по профессиональной тематике.
    Запись от Gustav размещена 11.08.2009 в 11:24 Gustav is offline
  6. Старый комментарий
    А пользователям удобно каждый раз залезать в Сервис/Параметры чтобы снять фильтр по филиалу?
    И потом еще раз, чтобы вернуть фильтр.
    Может на форме галочку добавить "А можно всех посмотреть?"?
    Запись от Кирилл размещена 14.10.2009 в 16:32
    Обновил(-а) Кирилл 14.10.2009 в 16:36
  7. Старый комментарий
    Аватар для Gustav
    Цитата:
    Сообщение от Кирилл Просмотреть комментарий
    А пользователям удобно каждый раз залезать в Сервис/Параметры чтобы снять фильтр по филиалу?
    И потом еще раз, чтобы вернуть фильтр.
    Может на форме галочку добавить "А можно всех посмотреть?"?
    Да, вроде, не жаловались пользователи, что им неудобно. Галочку на форму добавить, конечно, не вопрос, но так можно огрести доп.работу самому себе - пользователи увидят галочку на одной форме и захотят на других.

    А так они просто знают, что для смены филиала им надо выполнить такую-то команду, находящуюся в интерфейсе там-то. Не так же трудно щёлкнуть по "Сервис", а потом по "Параметры"? А далее даже сделано такое облегчение: им не надо раскрывать список филиалов в StringEdit с lookup, а достаточно пощелкать двойными щелчками, как в ComboBox'е, для переключения филиалов по кругу. Сделано это при помощи такого несложного метода:
    X++:
    // для того, чтобы первичная группа пользователя
    // менялась по кругу при очередном щелчке
    public int mouseDblClick(int _x, int _y, int _button, boolean _Ctrl, boolean _Shift)
    {
        int                 ret;
        str                 groupToFind;
        container           filials = ['Head','Branch1','Branch2',''];
        int                 pos;
    
        ret = super(_x, _y, _button, _Ctrl, _Shift);
    
        groupToFind = SysUserInfo.UserGroupDim;
        pos = conFind(filials, groupToFind);
        pos++;
        if (pos > conLen(filials)) pos = 1;
    
        SysUserInfo.UserGroupDim = conPeek(filials, pos);
        SysUserInfo.write();
        SysUserInfo_ds.reread();
        SysUserInfo_ds.refresh();
    
        return ret;
    }
    Пользователям нравится
    Запись от Gustav размещена 15.10.2009 в 10:30 Gustav is offline
 


Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 23:16.