8 (800) 302-62-68
+7 (499) 348-29-58
sale@rbs-crm.ru

Документация разработчика

РосБизнесСофт CRM > База знаний > Разработчикам > Табличная часть объекта

Табличная часть объекта

«Табличная часть» — это по вложенный объект в другой объект (тип объекта: positions). Обычно табличный части используются в «Справочниках» и «Документах.

Например, табличной частью является список «Номенклатуры» в документах «Счет» и «Отгрузка».

У одного объекта может быть несколько табличных частей. Например, в документе «Ремонт» существуют табличный части «Запчасти» и «Выполненные работы».

Для добавления табличной части необходимо зайти в “Конфигуратор” необходимого Объекта, найти раздел “Табличные части” и нажать кнопку “Добавить”.

Имя табличной частить должно быть уникальным для выбранного Объекта.

После добавления новой «Табличной части» автоматически добавятся поля: “id”, “visible” и “owner”.

Связь между «Табличной частью» и «Объектом» осуществляется по полю-указателю “owner” (в поле хранится ID основного объекта). Удалять эти поля нельзя.

Пример добавления табличной части

Рассмотрим добавление табличной части на примере справочника “Автопарк” (references.autos). Данный справочник добавлен в базовую конфигурацию в качестве примера для разработчика и не выведен в основной меню. Ссылка на объект: http://my_crm_ru/autos/

Перейдем в справочник “Автопарк” (references.autos). Добавим табличную часть “Техническое обслуживание” (services):

Затем в «Табличную часть» необходимо добавить “Форму редактирования”.

Новая форма должна быть вложена в «Форму редактирования” родительского объекта (в нашем случае autos/edit). Название формы должно быть уникальным в рамках текущего Объекта.

После добавления появится новая форма у «Табличной части».

Для того, чтобы в “Форме редактирования” автомобиля (references.autos) появилась таблица, ее необходимо прописать в «PHP-сценарии» и в «Шаблоне» родительской «Формы редактирования».

Данная табличная часть будет динамической. Изменения в ней будет отображаться на странице без перезагрузки страницы. За вывод динамической табличной части отвечает метод printDataDTable().

Добавим следующий код в «PHP-сценарий» в метод onPlay() основной “Формы редактирования” автомобиля:

$this->printDataDTable($this->structure->services, "services", [
    "date",
    "number",
    "km",
    "comment",
    "total" => function($obj) {
        return Text::money($obj->total);
    }
],[
    "path" => "/autos/" . $this->structure->id . "/services",
    "modalTitle" => Language::getVariable($this->form, 'Services'),
    "modalSize" => "tiny",
    "modalMulti" => 0,
    "modalName" => "services",
    "selectAll" => 1,
    "calcFields" => ["total"],
    "moreActions" => [
        [
        "onclick"   => "functionName(); return false;",
        "name"      => Language::getVariable($this->form, 'ButtonName'),
        "icon"      => ['before' => 'fa fa-plus'],
        "access"    => ACCESS_EDIT,
]);

Где:

  • $this->structure->services — ссылка на структуру, которую необходимо вывести
  • services — название табличной части (должен совпадать с названием табличной части в шаблоне)
  • 3-й параметр — массив полей для вывода в таблицу. Можно использовать комбинированные значения. В переменной obj находится ссылка на запись табличной части (references.autos.services)
  • 4-й параметр — массив настроек для табличной части
  • path — путь (ссылка) для открытия формы добавления в табличную часть
  • modalTitle — заголовок модального окна
  • modalSize — размер модального окна (small, tiny, fullscreen, large)
  • modalMulti — если “true”, то после добавления записи модальное окно не закроется
  • modalCustomReady — если “true”, то при добавлении новой записи в «Табличную часть» в модальной форме кнопка «Сохранить» не активна по умолчанию. Флаг используется при добавлении «Номенклатуры» в документы «Счета», «Коммерческие предложения», «Договоры». Т.е. когда без выбора «Номенклатуры» пользователю нельзя добавить значение в «Табличную часть». Кнопка «Сохранить» активируется через JS-сценарий «Модальной формы» согласно логике бизнес-процессов.
  • modalName — системное имя модального окна
  • selectAll — возможность выбрать 1 и более (все) записи с помощью чек-боксов. По умолчанию = «1» .  Если стоит «0» (ноль), то запрет.
  • add — возможностью добавлять записи в таб. часть. По умолчанию = «1» .  Если стоит «0» (ноль), то запрет.
  • delete — возможностью удалять записи из таб. часть. По умолчанию = «1».  Если стоит «0» (ноль), то запрет.
  • edit — возможностью редактировать записи в таб. часть. По умолчанию = «1». Если стоит «0» (ноль), то запрет.
  • calcFields — массив калькулируемых полей.
  • blockName — указывается блок, в котором лежит табличная часть. По умолчанию = ‘BASE’, пример смотреть в Сделке. Опция доступна с версии 24.04.
  • moreActions — добавление кнопок в кнопку «Еще» табличной части.

В “Шаблон” необходимо добавить таблицу, отвечающую за вывод динамической табличной части:

[[CONTAINER | header: $data.services.name | name: services | template: table]]
    [[dTable | name: services | id: servicesTable | key: num]]
        [[column | table: services | name: num | header: # | type: num]]
        [[column | table: services | name: date | tags:  nowrap center]]
        [[column | table: services | name: number | align: center]]
        [[column | table: services | name: km]]
        [[column | table: services | name: comment]]
        [[column | table: services | name: total]]
        [[edit | table: services]]
        [[delete | table: services]]
    [[table_end]]
[[CONTAINER_END]]

Для контейнера необходимо указать параметр “template: table”, чтобы добавилась кнопка добавления нового элемента в табличную часть.

Обращаем внимание, что «плюсик» в контейнере становится активным (можно добавлять элементы в табличную часть) только тогда, когда родительский Объект уже создан и имеет уникальную ссылку вида: http://my_crm_ru/autos/[id].

В блоке dTable параметр «name» (название табличной части) должен быть обязательно равен 2-му параметру из метода printDataDTable().

Параметр «id» у dTable должен быть уникальным для страницы. Обычно его называют: название табличной части и постфикс «Table». Например: servicesTable.

В результате контейнер с табличной частью будет выглядеть так:

Затем необходимо отредактировать “Форму редактирования” нашей новой табличной части.

В сценарии PHP поменять наследование на ModalEdit:

class Edit extends \Kernel\Actions\Forms\ModalEdit

В «PHP-сценарий» в метод onSave() добавим следующий код, чтобы была связь между табличной частью и родительским Объектом (references.autos):

protected function onSave() {
    	$this->structure->owner = $this->parents[0][0];
}

При наследовании от ModalEdit необходимо реализовать метод responseChange() с вызовом результирующего метода response().

В него передается массив, ключ которого равен названию таблицы в dTable в шаблоне основной «Формы редактирования».

Для динамического редактирования также необходимо всегда передать ключи id и act.

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

protected function responseChange() {
    $this->response([
        "services" => [
            "id" => $this->structure->id,
            "act" => $this->formType,
            "date" => $this->structure->date->toFormat('d.m.Y'),
            "number" => $this->structure->number,
            "km" => $this->structure->km,
            "comment" => $this->structure->comment,
            "total" => Text::money($this->structure->total)
        ]
    ]);
}

Добавим в «Шаблон» формы редактирования табличной части новый контейнер с полями  и подключить форму js:

{BASE append}
[[CONTAINER | h: hide | name: data]]
    [[field | name: date]]
    [[field | name: number]]
    [[field | name: km]]
    [[field | name: comment]]
    [[field | name: total]]
[[CONTAINER_END]]
{/BASE}

{JS append}
    [[js | name: autos/services/edit]]
{/JS}

На закладке «Интерфейс (JS-код)» формы редактирования таб. части реализуем следующие условия:

  • При добавлении записи в табличной части должно быть заполнено хотя бы одно значение (как правило это поле “Количество”, либо “Номенклатура”.) Если поле не заполнено, то необходимо заблокировать кнопку “Сохранить”.
  • При изменении обязательного для заполнения поля сделать проверку: заполнено ли поле после изменения и в зависимости от этого снять/добавить блокировку кнопки
  • После загрузки всплывающего окна убрать иконку загрузки (loader).
  • Если параметр ModalMutli в методе printDataDTable установлен true, то необходимо реализовать также перезагрузку формы (reset).
// функция разблокировки
function enabled(id_number){
    if(id_number == 0 || !id_number){
        modal.find(".actions .btn.ok").addClass("disabled");
    } else {
        modal.find(".actions .btn.ok").removeClass("disabled");
    }
}

// убираем иконку загрузки страницы
var modal;
top.jsApplication.findTop("body").on("readyModal", function(e, selectorModal, modalName){
    if(selectorModal == "#EditModal_0"){
        modal = jsApplication.getModal(selectorModal);
        modal.find(".actions .btn.cancel").removeClass("disabled");
        modal.find(".content").removeClass("loading");
    }
});

$(function () {
// после сохранения записи очищаем все поля на форме
    $("form.ui.form").on("reset",function(){
        modal.find(".actions .btn.cancel").removeClass("disabled");
        modal.find(".content").removeClass("loading");
    });
    
// при изменении поля number Блокируем либо разблокируем кнопку “Сохранить”
    $("#f_number").on("keyup",function(){
        var number = $("#f_number").val();
        enabled(number);
    });
});

Итоговый результат добавления добавления табличной части:

Пользователя нажимает на «плюсик».

Открывается модальная «Форма редактирования» табличной части «Техническое обслуживание» с полями.

 

Пользователь заполняет поля на форме и нажимаем «Сохранить».

После чего форма модальная закрывается и на экране отображается новая (отредактированная) строка в «Табличной части» родительского Объекта. Строка всегда подкрашивается желтым цветом.

Пример работы формы добавления товара в «Счет»

Рассмотрим пример «Табличной части» с одновременным выбором «Номенклатуры» на примере модуля “Счет” (documents.orders).

В табличной части «products» создаются 2 поля для номенклатуры:

  • product — ссылка на объект references.product (Справочник «Номенклатура»)
  • name — название номенклатуры (строка). В него вставляется текстовое наименование «Номенклатуры» из модуля “Номенклатура” (поле «name»). Данное поле можно скорректировать в момент добавления «Номенклатуры» в «Счет».

В шаблоне «Формы редактирования» табличной части «products» указывается скрытым поле «product».

<input type="hidden" name="object_product" id="f_product" value="$data.product.value">

Добавляем фрейм на форму выбора «Номенклатуры».

<div class="m-5">
    <iframe class="selectProduct" style="width:100%" height="600"  resize-element="#productsTree" frameborder="0" scrolling="yes" id="products" src="/products/select/?noTree=1&pType=$pType$&store=$store$&noLoader=1$appendFilterParents"></iframe>
</div>

Важно! В этом случаем мы добавляем «Формы выбора» номенклатуры из объекта «Номенклатуры», а не из табличной части «Номенклатура» документа «Счет».

Обратите внимание на путь формы выбора из фрейма:

/products/select/?noTree=1&pType=$pType$&store=$store$&noLoader=1$appendFilterParents

В «Форму редактирования» табличной части «product» в метод onPlay() добавляем метод для передачи переменных на форму. Необходимо передать «Склад» и «Тип цены» (если они есть в Объекте), чтобы «Форма выбора» Номенклатура показала нам остатки товара на определенном «Складе» и выбранный «Тип цен» (например, оптовые).

$this->page->set([
    "BASE.pType" => $this->parents[0]->price_type->id,
    "BASE.store" => $this->parents[0]->storehouse->id
]);

Где:

  • $this->parents[0] — ссылка на родительский элемент (document.orders)

В результате при добавления «Номенклатуры» в «Счет» откроется следующая форма.

При выборе «Номенклатуры» в списке добавим следующую логику:

  • Наименование = Название номенклатуры
  • Количество = 1
  • Цена = Цена из номенклатуры
  • Сумма = Цена * Количество

Для этого внесем правки в «Форму редактирования» табличной части «Products» на закладке «Интерфейс (JS-код)»:

// блокировка / разблокировка кнопки "Сохранить"
function enabled(number){
    if(number == 0 || !number || parseFloat(minNumber) > number){
        modal.find(".actions .btn.ok").addClass("disabled");
    } else {
        modal.find(".actions .btn.ok").removeClass("disabled");
    }
}

// подсчет сумм
function result(a, b){
    return (b != 0) ? (a / b).toFixed(2) : 0;
}

// блокировка / разблокировка полей
function enabledFields(isEnabled) {
    if(isEnabled == undefined)
        isEnabled = false;

    /* если режим редактирования */
    productId = jsApplication.getString($("#f_product").val());
    number = jsApplication.getNumber($("#f_number").val());
    if(productId.length > 0 && number > 0) {
        isEnabled = true;
    }

    if(isEnabled) {
        $("input, select, textarea").removeAttr("disabled").closest(".field").removeClass("disabled").find(".dropdown").removeClass("disabled");
    } else {
        $("input, select, textarea").attr("disabled", "disabled").closest(".field").addClass("disabled").find(".dropdown").addClass("disabled");
    }
}

var modal;

// после загрузки модального окна выключаем иконку загрузки (loader) и снимаем disabled с кнопки "Закрыть"
top.jsApplication.findTop("body").on("readyModal", function(e, selectorModal, modalName){
    if(selectorModal == "#EditModal_0"){
        modal = jsApplication.getModal(selectorModal);
        enabledFields();

        modal.find(".actions .btn.cancel").removeClass("disabled");
        modal.find(".content").removeClass("loading");
    }
});

$(function () {
// после отправки формы очищаем поля
    $("form.ui.form").on("reset",function(){
        enabledFields();
        modal.find(".actions .btn.cancel").removeClass("disabled");
        modal.find(".content").removeClass("loading");
    });

// при нажатии на товар из фрейма вставляем данные в шаблон
    jsApplication.OnEventTableInFrame("iframe#products", "products", "onSelect", function(e, extra){ 
        $("#f_product").val(extra.id);
        $("#f_name").val($(extra.tr).children("td:eq(2)").text());
        $("#f_price_nds, #f_total").val(parseFloat($(extra.tr).children("td:eq(3)").text().replace("'", '')));
        $("#f_number").val(1);

        enabledFields(true);
        enabled(extra.id);
    });

// при изменении полей number или price_nds пересчитываем total (сумма)
    $("#f_number, #f_price_nds").on("keyup",function(){
        var number = $("#f_number").val(),
            price_nds = $("#f_price_nds").val();
        $("#f_total").val(result(number * price_nds, 1));
        enabled(number);
    });

// при изменении поля total пересчитываем price_nds (цена)
    $("#f_total").on("keyup",function(){
        var number = $("#f_number").val(),
            total = $("#f_total").val();
        $("#f_price_nds").val(result(total, number));
        enabled(number);
    });
});