Пакетные операции с записями

Предупреждение

Пакетные операции не являются частью бизнес-процессов и запускаются напрямую.

Выпадающий список действий при выборе нескольких записей и кнопка "Удалить записи"

Рисунок 1 - Выпадающий список действий при выборе нескольких записей и кнопка "Удалить записи"

Точка расширения UEBulkOperationSettingsStore предназначена для пакетных действий над записями.

Базовый инструмент для реализации пакетных операций - BulkOperationStore.

Основные задачи точки расширения:

  • Получить список дескрипторов операций (чтобы пользователь мог выбрать операцию).

  • Инициировать выбранную операцию для настройки, создать для нее хранилище. История конкретной операции - это точка расширения пользователя по его идентификатору типа UEBulkOperationSettingsStore.

  • Запуск выполнения операции с контекстом, полученным из хранилища.

Пример работы с этим хранилищем: компонент Действия в разделах "Поиск по активам" и "Поиск по справочникам" - выпадающий список действий при выборе нескольких записей.

Пример UEBulkOperationSettingsStore

import {UeModuleBase} from '../../userexit/type/UeModuleBase';
import {IBulkOperationSettings} from '../IBulkOperationSettings';
import {ClassCtor} from '@unidata/core';

interface IBulkOperationSettings {

    /**
     * Шаги для работы мастера с настройками операций
     */
    readonly wizardSteps: WizardStep[];

    /**
     * Общее хранилище пакетных операций (с методом execute)
     */
    readonly bulkOperationStore: BulkOperationStore;

    /**
     * Метод получения содержимого для выполнения операции
     */
    getContent(): Promise<any>; // Полезная нагрузка для операции BE (зависит от операций)

}

export type UEBulkOperationSettingsStore = UeModuleBase & {
    'default': {
        fn: () => ClassCtor<IBulkOperationSettings>;
    };
}

Пакетная операция по удалению записей

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

DeleteRecordsStore отвечает за пакетную операцию удаления записей - точка типа UEBulkOperationSettingsStore (см. выше).

Пример реализации:

import {Res, ResourceManager, Right, UEBulkOperationSettingsStore} from '@unidata/core-app';
import {DeleteRecordsStore} from './DeleteRecordsStore';
import {UEList} from '@unidata/types';
import {dataRecordBulkPayload} from '@universe-se/data/src/utils/dataRecordBulkPayload'; // Утилита, которая может получить DataRecordBulkPayload

export const UEDeleteRecordBulkOpStore: UEBulkOperationSettingsStore = {
    'default': {
        type: UEList.BulkOperationSettingsStore,
        moduleId: 'com.unidata.mdm.bulk.remove.records[operation]',
        active: true,
        system: false,
        meta: {},
        resolver: () => {
            return ResourceManager.userHasRight(Res.DATA_OPERATIONS_MANAGEMENT, Right.DELETE);
        },
        fn: () => {
            return DeleteRecordsStore;
        }
    }
};

Пакетная операция модификации записей

Кнопка "Модификация записей" появляется в секции "Действия", когда пользователь выбирает необходимые записи в таблице результатов поиска.

ModifyRecordsOperationStore отвечает за массовую операцию модификации записей - точка типа UEBulkOperationSettingsStore (см. выше).

Пример реализации:

export const UEModifyRecordsBulkOpStore: UEBulkOperationSettingsStore = {
    'default': {
        type: UEList.BulkOperationSettingsStore,
        moduleId: 'com.universe.mdm.bulk.modify.records[operation]',
        active: true,
        system: false,
        meta: {},
        resolver: (searchStore: AbstractSearchStore<AbstractSearchPanelStore, AbstractSearchColumnsStore>) => {
            if (!(searchStore instanceof DataSearchStore)) {
                return false;
            }

            const {typeName} = searchStore;

            return ResourceManager.userHasRight(
                ResourceUtil.stringifyResourceId(ResourceCategory.DATA, typeName),
                Right.UPDATE
            );
        },
        fn: () => {
            return ModifyRecordsOperationStore;
        }
    }
};

Пакетная операция без участия сервера

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

Описание UESearchPageOperationMenuItem

export type SearchPageMenuItemArgs = {
    routerStore: RouterStore;
    searchStore: AbstractSearchStore<
        AbstractSearchPanelStore,
        AbstractSearchColumnsStore
        >;
};

export type UESearchPageOperationMenuItem = UeModuleBase & {
    default: {
        fn: (args: SearchPageMenuItemArgs) => void;
        resolver: (args: SearchPageMenuItemArgs) => boolean;
        meta: {
            isActive?: (args: SearchPageMenuItemArgs) => boolean;
            getTitle: (args: SearchPageMenuItemArgs) => string;
        };
    };
};

Пример реализации кнопки сравнения записей

export const compareMenuItem: UESearchPageOperationMenuItem = {
    'default': {
        type: UEList.SearchPageOperationMenuItem,
        resolver: ({searchStore}: SearchPageMenuItemArgs) => {
            if (!(searchStore instanceof DataSearchStore)) {
                return false;
            }

            const {checkedSearchHits} = searchStore.searchResult;
            const hasDataRight = MetaDataRightUtils.userHasDataRight(searchStore.typeName, Right.READ);
            const checkedCount = checkedSearchHits.size;

            return checkedCount > 1 && hasDataRight; // Показывать только в том случае, если отмечено более 1 записи и пользователь имеет права на чтение выбранного типа актива
        },
        active: true,
        system: false,
        moduleId: 'search_page_compare_menu_item',
        fn: MenuItemCompare.openCompareDrawer, // Функция для открытия модального окна с CompareTable или другим маршрутом
        meta: {
            getTitle: () => 'Compare Records'
        }
    }
};

Расширение пакетной модификации записей

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

Используемые типы

interface IModifyInnerStore {
    /*
    * Добавляемые в мастер операции 1+ вкладка/и, компонент параметров будет рендерится в ней/них
    */
    tabs: TabItem[];
    /*
    * Возвращает фрагмент запроса
    */
    getContent: () => {};
    /*
    * True, если заданные во фрагменте параметры позволяют запустить операцию
    */
    allowConfirm: boolean;
    /*
    * True, если заданные во фрагменте параметры запрещают запуск операции
    */
    hasErrors: boolean;
    /*
    * Ключ, под которым в запрос будет добавлен фрагмент
    */
    payloadKey: string;
}

type UEModifyRecordsResolver = (modifyOperationStore: ModifyRecordsOperationStore) => boolean;

type UEModifyRecordsMeta = {
    /*
    * @param key - передается meta.key текущего User Exit
    */
    getStore: (modifyOperationStore: ModifyRecordsOperationStore, key: string) => IModifyInnerStore;
    key: string;
};

type UEModifyRecords = UeModuleBase<UEModifyRecordsResolver, UEModifyRecordsMeta> & {
    component: ComponentType<{modifyStore: ModifyRecordsOperationStore}>;
};

Пример UEModifyRecords

type Props = {
    modifyStore: ModifyRecordsOperationStore
}

class ModifyRecordAdditionalStore implements IModifyInnerStore {
    public readonly payloadKey: string;

    @observable
    public modificationProperty1: boolean = false;
    @observable
    public modificationProperty2: string = '';
    @observable
    public modificationProperty3: number = 0;

    constructor (payloadKey: string) {
        this.payloadKey = payloadKey;
    }

    @computed
    get allowConfirm () {
        return this.modificationProperty2 !== '';
    }

    @computed
    get hasErrors () {
        return this.modificationProperty1 && this.modificationProperty3 === 0;

    }

    get tabs () {
        return [
            {
                key: this.payloadKey,
                tab: 'Дополнительные параметры'
            }
        ];
    }

    @action
    setFieldValue = (name: 'modificationProperty1' | 'modificationProperty2' | 'modificationProperty3', value: any) => {
        this[name] = value as
            typeof name extends 'modificationProperty1' ? string :
                typeof name extends 'modificationProperty2' ? number :
                    typeof name extends 'modificationProperty3' ? boolean :
                        never;
    }

    getContent(): {} {
        const {
            modificationProperty1,
            modificationProperty2,
            modificationProperty3
        } = this;

        return {
            modificationProperty1,
            modificationProperty2,
            modificationProperty3
        };
    }
}

@observer
class CloneRecordUserExit extends React.Component<Props> {
    get store () {
        const {modifyStore} = this.props;

        return modifyStore.getStore(PAYLOAD_KEY) as ModifyRecordAdditionalStore;
    }

    override render () {
        const {store} = this;

        return (
            <>
                <Field.Checkbox
                    label={'Параметр 1'}
                    name={'modificationProperty1'}
                    defaultChecked={store.modificationProperty1}
                    onChange={store.setFieldValue}
                />
                <Field.Input
                    label={'Параметр 2'}
                    name={'modificationProperty2'}
                    type={'text'}
                    disabled={false}
                    readOnly={false}
                    defaultValue={store.modificationProperty2}
                    onChange={store.setFieldValue}
                />
                <Field.Input
                    label={'Параметр 3'}
                    name={'modificationProperty3'}
                    type={'number'}
                    disabled={false}
                    readOnly={false}
                    defaultValue={store.modificationProperty3}
                    onChange={store.setFieldValue}
                />
            </>
        );
    }
}

const PAYLOAD_KEY = 'modify-records-additional-fragment-v1';

ueModuleManager.addModule('ModifyRecords', {
    active: true,
    component: CloneRecordUserExit,
    meta: {
        getStore: () => {
            return new ModifyRecordAdditionalStore(PAYLOAD_KEY)
        },
        key: PAYLOAD_KEY
    },
    moduleId: PAYLOAD_KEY,
    resolver: (modifyOperationStore: ModifyRecordsOperationStore) => {
        const dataRecordInnerStore = modifyOperationStore.dataCardStore.getInnerStore('data-record-additional-store');

        return dataRecordInnerStore !== undefined;
    },
    system: false

});