Я думаю каждый сталкивался с проблемой, что на ячейки системной таблицы нет возможности навесить скрипт на изменение значения или добавить фильтр, например к столбцу с типом "Приложение".
Предлагаю вам ознакомиться с решением этой проблемы при помощи использования виджета "Динамический список".
По сути, под капотом, мы имеем ту же самую таблицу, которая будет выступать в роли источника данных.
Итак, покажу на примере созданного мной раздела "Отпуска". В разделе есть приложение "Тип отпуска", "Сотрудник" и "Отпуска". В приложение отпуска добавим таблицу с детализацией - она будет являться источником для динамического списка и еще пару атрибутов.
Далее создадим виджет на уровне раздела, и продублируем переменные из таблицы, а также саму таблицу и, забегая наперед, переменную которая будет хранить ID доступных типов отпусков для текущего сотрудника:
Создадим шаблон виджета, добавим 4 колонки и кнопку для удаления:
Каждая строка таблицы будет являться копией этого виджета с данными, которые находятся в этой строке. Создаем необходимые нам скрипты на изменения значений, навешиваем фильтр на переменную с типом отпуска, чтобы сотрудник мог выбрать только те типы, которые за ним закреплены:
Код:
async function onInit(): Promise<void> {
if (Context.data.vacation_types_id) {
let ids: string[] = JSON.parse(Context.data.vacation_types_id).result
Context.fields.type_vacation.data.setFilter(f => f.__id.in(ids));
}
}
async function on_change_start(): Promise<void> {
if (Context.data.end && Context.data.start) {
if (Context.data.end.before(Context.data.start)) {
Context.data.show_date_error = true;
Context.data.end = undefined;
} else {
Context.data.show_date_error = false;
}
//Скрипт для подсчета кол-ва дней в промежутке
let duration = Math.ceil(((Context.data.end!.asDatetime(new TTime(4, 0, 0, 0)).unix()) - (Context.data.start.asDatetime(new TTime(22, 0, 0, 0)).unix())) / (24 * 60 * 60))
Context.data.days = duration + 1;
let row = Context.data.vacation_details!.find(f => f.id == Context.data.id_row);
if (row) {
row.days = Context.data.days;
row.start = Context.data.start!;
Context.data.vacation_details = Context.data.vacation_details;
}
}
}
async function on_change_end(): Promise<void> {
if (Context.data.end && Context.data.start) {
if (Context.data.end.before(Context.data.start)) {
Context.data.show_date_error = true;
Context.data.end = undefined;
} else {
Context.data.show_date_error = false;
}
//Скрипт для подсчета кол-ва дней в промежутке
let duration = Math.ceil(((Context.data.end!.asDatetime(new TTime(4, 0, 0, 0)).unix()) - (Context.data.start.asDatetime(new TTime(22, 0, 0, 0)).unix())) / (24 * 60 * 60))
Context.data.days = duration + 1;
let row = Context.data.vacation_details!.find(f => f.id == Context.data.id_row);
if (row) {
row.days = Context.data.days;
row.end = Context.data.end!;
Context.data.vacation_details = Context.data.vacation_details;
}
}
}
async function on_change_type_vacation(): Promise<void> {
if (Context.data.type_vacation) {
let row = Context.data.vacation_details!.find(f => f.id == Context.data.id_row);
if (row) {
row.type_vacation = Context.data.type_vacation
Context.data.vacation_details = Context.data.vacation_details;
}
}
}
async function deleted(): Promise<void> {
for (let i = Context.data.vacation_details!.length - 1; i >= 0; i--) {
const row = Context.data.vacation_details![i];
if (row.id == Context.data.id_row) {
Context.data.vacation_details!.delete(i);
}
}
Context.data.vacation_details = Context.data.vacation_details;
}
Публикуем виджет и возвращаемся к форме создания отпуска. Добавляем на форму "Панель с заголовком", назовем ее "Детализация отпуска". Внутрь панели добавляем виджет "Динамический список" и в настройках выбираем таблицу из контекста приложения:
Внутрь динамического списка добавляем еще одну панель с заголовком, в поле "Заголовок" оставляем пробел. Это нужно для того чтобы наш динамический список выглядел более элегантно)). После чего в панель с заголовком без заголовка))) добавляем ранее созданный виджет и связываем поля с данными из таблицы - источника:
Выносим виджет "Кнопка", которая будет добавлять новую строку в таблицу и в конечном итоге должно получится следующее:
Пишем скрипты, тут как говориться, кто на что горазд. Я написал небольшой онинит скрипт, который определяет текущего пользователя, ищет в справочнике сотрудников его карточку и записывает ID доступных для него отпусков. ID мы передаем во внешний виджет вместе с данными из таблицы.
.
Также, чтобы нам было проще понимать, какая именно строка из таблицы источника перед нами, генерируем 8 значный идентификатор.
Ну и скрипт, который добавляет строку в таблицу источник, тут все просто.
Код:
async function onInit(): Promise<void> {
Context.data.staff = await System.users.getCurrentUser();
//ищем текущего пользователя в справочнике сотрудников и записываем ID доступных типов отпусков
let staff = await Namespace.app.staff.search().where((f, g) => g.and(
f.__deletedAt.eq(null),
f.user.eq(Context.data.staff!)
)).first();
if (staff && staff.data.vacation_types && staff.data.vacation_types.length > 0) {
let type_vacations = staff.data.vacation_types;
let ids = type_vacations.map(f => f.id);
//записываем в строку и передаем во внешний виджет
ViewContext.data.vacation_types_id = JSON.stringify({ result: ids });
}
}
async function addRow(): Promise<void> {
//добавление строки в таблицу-источник
let start_date_new: TDate | undefined = undefined;
if (Context.data.vacation_details && Context.data.vacation_details.length > 0) {
let last_row = Context.data.vacation_details[Context.data.vacation_details.length - 1];
if (last_row.end) {
start_date_new = last_row.end.addDate(0, 0, 1);
}
}
let row = Context.data.vacation_details!.insert();
if (start_date_new) {
row.start = start_date_new
}
let id = ID_generation();
row.id = id;
Context.data.vacation_details = Context.data.vacation_details;
}
//генерируем ID строки для дальнейшего определения строки в таблице-источнике из внешнего виджета
function ID_generation() {
const valid = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789!&?`~№;:^\|/'#$%&*=-+_)(}{][@";
let length = 8;
let id = '';
while (0 < length--) {
id += valid.charAt(Math.floor(Math.random() * valid.length))
}
return id
}
Чтобы динамический список запустился нужно, чтобы в таблице были какие-то данные, поэтому пишем ончейндж скрипт на изменение даты начала:
Код:
//при заполнении даты начала инициализируем динамический список, заполняя дату начала в первой строке
async function on_change_start_date(): Promise<void> {
console.log('on_change_start_date')
let row = Context.data.vacation_details!.insert();
row.start = Context.data.start!;
let id = ID_generation();
row.id = id;
Context.data.vacation_details = Context.data.vacation_details;
ViewContext.data.show_details = true;
}
Публикуем форму и идем создавать отпуск.
Из-за того, что виджет внешний, его валидация не будет срабатывать на форме создания отпуска. Поэтому идем и пишем функцию валидации на форме создания, где будем проверять заполнение полей.
Код:
//Валидация
async function validation(): Promise<ValidationResult> {
const result = new ValidationResult();
//проверка на то, что все бязательные поля в детализации заполнены
if (Context.data.vacation_details && Context.data.vacation_details.length > 0) {
for (let row of Context.data.vacation_details!) {
if(!row.start || !row.end || !row.type_vacation){
result.addMessage('Заполните все обязательные поля в детализации отпуска.')
return result
}
}
Context.data.start = Context.data.vacation_details[0].start;
Context.data.end = Context.data.vacation_details[Context.data.vacation_details.length - 1].end;
Context.data.amount_days = Context.data.vacation_details.map(f => f.days).reduce((sum,el) => sum + el, 0);
}
return result
}
Выгруженный раздел с виджетом во вложениях, можно пощупать.