Регистрация  |  Вход

Передача "флага" с одного объекта на другой посредством триггеров

Всем привет!

Столкнулся с логической задачей, совсем не оригинальной, уверен, что многим знакома, но не могу кратко ее описать в двух словах (поэтому название темы получилось таким загадочным), так что, как говорится в индийском кино "долго объяснять - давайте станцую":

Есть три связанных объекта, образующих три уровня, причем нижние уровни не являются чайлд записями верхних объектов, просто релайтед записи через обычный лукап.

Каждый объект имеет флаг-чекбокс "Завершено", который выставляется в тру с помощью тригера в случае если:
(1) требуемые поля на объекте заполнены и
(2) все связанные записи низлежащего уровня тоже "Завершены" (там аналогичный чек-бокс)

И вот в чем штука: тригеры работаю только с записями самого объекта. И в чем же проблема?

А вот в чем: у записи были не заполнены требуемые поля, и вот юзер их заполняет. Сохраняет. Но запись все еще не "Завершена", потому что одна из низлежащих записей все еще не "Завершена". Ну да не беда, идем ниже и "завершаем" ту запись. И казалось бы вот и все: все рилейтед записи готовы, у верхней записи тоже все поля заполнены. Но ведь нет: чекбокс на верхней записи все еще в фальсе, показывает что запись еще не "Завершена"... Почему? Да потому что когда мы Завершили низлежащую запись, ее апдейт не вызвал апдейт верхней записи, и там все еще по-старому...
Т.е. в тригере низлежащего объекта нужно провоцировать тригер вышележащего объекта, чтоб тот сделал свое дело, и так по цепочке вверх.

Ну и вопрос, вы не сталкивались с такой ситуацией, может есть какие светлые идеи, бэст практис на тему?

Всем привет!

Столкнулся с логической задачей, совсем не оригинальной, уверен, что многим знакома, но не могу кратко ее описать в двух словах (поэтому название темы получилось таким загадочным), так что, как говорится в индийском кино "долго объяснять - давайте станцую":

Есть три связанных объекта, образующих три уровня, причем нижние уровни не являются чайлд записями верхних объектов, просто релайтед записи через обычный лукап.

Каждый объект имеет флаг-чекбокс "Завершено", который выставляется в тру с помощью тригера в случае если:
(1) требуемые поля на объекте заполнены и
(2) все связанные записи низлежащего уровня тоже "Завершены" (там аналогичный чек-бокс)

И вот в чем штука: тригеры работаю только с записями самого объекта. И в чем же проблема?

А вот в чем: у записи были не заполнены требуемые поля, и вот юзер их заполняет. Сохраняет. Но запись все еще не   "Завершена", потому что одна из низлежащих записей все еще не "Завершена". Ну да не беда, идем ниже и "завершаем" ту запись. И казалось бы вот и все: все рилейтед записи готовы, у верхней записи тоже все поля заполнены. Но ведь нет: чекбокс на верхней записи все еще в фальсе, показывает что запись еще не "Завершена"... Почему? Да потому что когда мы Завершили низлежащую запись, ее апдейт не вызвал апдейт верхней записи, и там все еще по-старому...
Т.е. в тригере низлежащего объекта нужно провоцировать тригер вышележащего объекта, чтоб тот сделал свое дело, и так по цепочке вверх.

Ну и вопрос, вы не сталкивались с такой ситуацией, может есть какие светлые идеи, бэст практис на тему?

Из того что ты написал, ты сам ответил на свой вопрос. Да нужно каскадное срабатывание триггеров, но проблема в этом что все эти срабатывания могут зайти в цикл. Поэтому нужно очень аккуратно проверять условия срабатывания триггеров и фильтровать требуемые записи.

Из того что ты написал, ты сам ответил на свой вопрос. Да нужно каскадное срабатывание триггеров, но проблема в этом что все эти срабатывания могут зайти в цикл. Поэтому нужно очень аккуратно проверять условия срабатывания триггеров и фильтровать требуемые записи.

Через future или scheduler такие моменты придется решать.
Или разруливать последовательность срабатывания триггера через глобальные переменные.
Вот в твое случае так как триггер сперва сработал на паренте, выставил все нужные поля и еще вдобавок обновил детей, то логичнее было бы запретить срабатываение триггера на детях, так как ты и так уже все выставляешь как нужно в триггере на родителе.

Через future или scheduler такие моменты придется решать.
Или разруливать последовательность срабатывания триггера через глобальные переменные.
Вот в твое случае так как триггер сперва сработал на паренте, выставил все нужные поля и еще вдобавок обновил детей, то логичнее было бы запретить срабатываение триггера на детях, так как ты и так уже все выставляешь как нужно в триггере на родителе. 

Можно через организовать класс в котором будет хэндлится твой кейс.
типа такого:

public with sharing class TriggerStateMachine {
public static Boolean TRIGGER_A_DISABLED = false;
public static Boolean TRIGGER_B_DISABLED = false;
public static Boolean TRIGGER_C_DISABLED = false;

// Only 1 constructor should be used.
public TriggerStateMachine(Sobject p_object) {
if(p_object type of ARecord__c) {
handleA(p_object);
} else if(p_object type of BRecord__c) {
handleB(p_object);
} else if(p_objec type of CRecord__c) {
handleC(p_object);
}
}

private void handleA(Sobject p_object) {
if (TRIGGER_A_DISABLED) return;

//some stuff
// просто вырубить рекурсию
TRIGGER_A_DISABLED = true;
handleB(p_object);
}

private void handleB(Sobject p_object) {
if (TRIGGER_B_DISABLED) return;

// просто вырубить рекурсию
TRIGGER_B_DISABLED = true;
handleC(p_object);
}

private void handleC(Sobject p_object) {
if (TRIGGER_C_DISABLED) return;

// просто вырубить рекурсию
TRIGGER_C_DISABLED = true;
handleA(p_object);
}
}

ps: класс гавно, я знаю, есть более крутые решения

Можно через организовать класс в котором будет хэндлится твой кейс.
типа такого:
[code]
public with sharing class TriggerStateMachine {
	public static Boolean TRIGGER_A_DISABLED  = false;
	public static Boolean TRIGGER_B_DISABLED  = false;
	public static Boolean TRIGGER_C_DISABLED  = false;

	// Only 1 constructor should be used.
	public TriggerStateMachine(Sobject p_object) {
		if(p_object type of ARecord__c) {
			handleA(p_object);
		} else if(p_object type of BRecord__c) {
			handleB(p_object);
		} else if(p_objec type of CRecord__c) {
			handleC(p_object);
		}
	}

	private void handleA(Sobject p_object) {
		if (TRIGGER_A_DISABLED) return;

		//some stuff
		// просто вырубить рекурсию
		TRIGGER_A_DISABLED = true;
		handleB(p_object);
	}

	private void handleB(Sobject p_object) {
		if (TRIGGER_B_DISABLED) return;	

		// просто вырубить рекурсию
		TRIGGER_B_DISABLED = true;
		handleC(p_object);
	}

	private void handleC(Sobject p_object) {
		if (TRIGGER_C_DISABLED) return;

		// просто вырубить рекурсию
		TRIGGER_C_DISABLED = true;
		handleA(p_object);
	}
}
[/code]

ps: класс гавно, я знаю, есть более крутые решения

В принципе не должно быть проблем реализовать каскадное обновление снизу вверх, если только у вас нет или не будет петли обновления сверху вниз. Я бы логику выставления флага реализовал непосредственно в триггерах и из нижних объектов просто дёргал апдейт верхних, а триггер вверху всё выполнит сам.

В принципе не должно быть проблем реализовать каскадное обновление снизу вверх, если только у вас нет или не будет петли обновления сверху вниз. Я бы логику выставления флага реализовал непосредственно в триггерах и из нижних объектов просто дёргал апдейт верхних, а триггер вверху всё выполнит сам.