Бизнес-процессы
триггер
запуск бизнес-процеса
пользователи

Добрый день, коллеги. 



Так вышло, что бизнес-процессы игнорирует сигнал по добавлению пользователя (или вообще любой записи в таблицу SysAdminUnit). 



Пользователь создается вручную. 



У кого есть идеи, как все-таки запускать процесс при создании пользователя?

Нравится

2 комментария
Лучший ответ

Здравствуйте! Разве что через событийный слой

[EntityEventListener(SchemaName = "SysAdminUnit")]
    public class SysAdminUnitEntityEventListener : BaseEntityEventListener
{
  public override void OnSaving(object sender, EntityBeforeEventArgs e) {
    base.OnSaving(sender, e)
  }
  public override void OnInserting(object sender, EntityBeforeEventArgs e) {
    base.OnInserting(sender, e);
  }
 public override void OnUpdating(object sender, EntityBeforeEventArgs e) {
   base.OnUpdating(sender, e);
}
public override void OnUpdated(object sender, EntityAfterEventArgs e) {
 base.OnUpdated(sender, e);
}
public override void OnInserted(object sender, EntityAfterEventArgs e) {
 base.OnInserted(sender, e);
}
}

Ну а там уже выбирайте при каких событиях Вам нужно обрабатывать про событийный слой прочтите данную статью

 

Так же БП можно запускать из серверного кода примерно следующее:

 

using Terrasoft.Core;
using Terrasoft.Core.Process;
using Terrasoft.Core.Process.Configuration;
 
ProcessSchema schema = UserConnection.ProcessSchemaManager.GetInstanceByName("LeadManagement");
//schema = UserConnection.ProcessSchemaManager.GetInstanceByUId(leadManagementProcessUId);
 
//Разные движки для интерпритируемых и компилироуемых БП
bool canUseFlowEngine = ProcessSchemaManager.GetCanUseFlowEngine(UserConnection, schema);
if(canUseFlowEngine) {
	var flowEngine = new FlowEngine(UserConnection);
	var param = new Dictionary<string, string>();
	param["LeadId"] = Entity.Id.ToString();
	flowEngine.RunProcess(schema, param);
} else {
	Process process = schema.CreateProcess(UserConnection);
	process.SetPropertyValue("LeadId", Entity.Id);
	process.Execute(UserConnection);
}		

 

Здравствуйте! Разве что через событийный слой

[EntityEventListener(SchemaName = "SysAdminUnit")]
    public class SysAdminUnitEntityEventListener : BaseEntityEventListener
{
  public override void OnSaving(object sender, EntityBeforeEventArgs e) {
    base.OnSaving(sender, e)
  }
  public override void OnInserting(object sender, EntityBeforeEventArgs e) {
    base.OnInserting(sender, e);
  }
 public override void OnUpdating(object sender, EntityBeforeEventArgs e) {
   base.OnUpdating(sender, e);
}
public override void OnUpdated(object sender, EntityAfterEventArgs e) {
 base.OnUpdated(sender, e);
}
public override void OnInserted(object sender, EntityAfterEventArgs e) {
 base.OnInserted(sender, e);
}
}

Ну а там уже выбирайте при каких событиях Вам нужно обрабатывать про событийный слой прочтите данную статью

 

Так же БП можно запускать из серверного кода примерно следующее:

 

using Terrasoft.Core;
using Terrasoft.Core.Process;
using Terrasoft.Core.Process.Configuration;
 
ProcessSchema schema = UserConnection.ProcessSchemaManager.GetInstanceByName("LeadManagement");
//schema = UserConnection.ProcessSchemaManager.GetInstanceByUId(leadManagementProcessUId);
 
//Разные движки для интерпритируемых и компилироуемых БП
bool canUseFlowEngine = ProcessSchemaManager.GetCanUseFlowEngine(UserConnection, schema);
if(canUseFlowEngine) {
	var flowEngine = new FlowEngine(UserConnection);
	var param = new Dictionary<string, string>();
	param["LeadId"] = Entity.Id.ToString();
	flowEngine.RunProcess(schema, param);
} else {
	Process process = schema.CreateProcess(UserConnection);
	process.SetPropertyValue("LeadId", Entity.Id);
	process.Execute(UserConnection);
}		

 

Мне кажется, вопрос в другом.

При добавлении пользователя через интерфейс идёт обращение к веб-сервису AdministrationService и его методу UpdateOrCreateUser. В схеме AdministrationServiceUsers виден его код:

protected void SaveUser(object roleId) {
	bool isNew = false;
	object primaryColumnValue;
	changedValues.TryGetValue("Id", out primaryColumnValue);
	EntitySchema entitySchema = UserConnection.EntitySchemaManager.GetInstanceByName("VwSysAdminUnit");
	Entity entity = entitySchema.CreateEntity(UserConnection);
	isNew = !entity.FetchFromDB(primaryColumnValue);
	if (isNew) {
		entity.SetDefColumnValues();
	}
	foreach (KeyValuePair<string, object> item in changedValues) {
		EntitySchemaColumn column = entitySchema.Columns.GetByName(item.Key);
		object columnValue = item.Value;
		if ((column.DataValueType is DateTimeDataValueType) && (item.Value != null)) {
			columnValue = DataTypeUtilities.ValueAsType<DateTime>(item.Value);
		}
		entity.SetColumnValue(column.ColumnValueName, columnValue);
	}
	entity.Save();
	if (isNew) {
		AddUserInRole(entity.PrimaryColumnValue, roleId);
	}
}

То есть видно, что вставка идёт через механизмы ESQ (значит, события отработают), но в объект VwSysAdminUnit, а не SysAdminUnit. Если посмотреть в коде встроенного БП на VwSysAdminUnit в пакетах Base, Translation и WebitelCollaborations, там как раз успешно добавляют свои обработчики на вставку, сохранение и другие события:

То есть нужно будет либо в Custom сделать своё переопределение схемы со встроенным БП, либо сделать отдельный БП, запускающийся по сигналу вставки туда:

В любом случае нужно учитывать, что в этой view видны и пользователи, и роли.

Показать все комментарии
insert
связанный сервер
триггер
Хранимая процедура
Технические вопросы
5.x

Доброго времени суток, коллеги. При интеграции BPM с внешней БД возникла необходимость вставлять даннные из карточки BPM в таблицу на связанном сервере (Firebird 2.0). Для работы с таблицами связанного сервера содали представления. С селектами из этих представлений никаких проблем не было. Но вот с инсертами и апдейтами возникла проблемка - после инсерта(апдейта или делита) транзакции подвисали минут на 5 и выполнить очередной запрос к этому представлению было невозможно.
Нашли выход из этой ситуевины:
1. Создали на связанном сервере хранимую процедуру (пишет данные в таблицу и возвращает одно значение).
2. На sql сервере в представлении создали триггер который срабатывает на инсерт, пинает хранимку на связанном сервере и передает параметры в эту хранимку.
3. В карточку BPM добавили кнопку и повесили на нее скрипт которые инсертит данные из полей карточки в представление.
После данные манипуляций все работает отлично, данные инсертятся, транзакции не подвисают.

Но вот возник вопросик. Как я написал в первом пункте, хранимка возвращает одно значение и мы не можем приложить ума как его перехватить чтобы вернуть это значение в текстовое поле в карточку BPM. Ниже код хранимки, триггера и скрипта инсерта.

Код хранимки.

BEGIN

INSERT INTO NAPRAVLENIE(NAPRAVLENIE.NAPRAVLENIE_NUM,NAPRAVLENIE.USLUGA_1) VALUES (:NUM,:U1);
NEW_OUTPARAM=gen_id(BP_GEN,1);

  SUSPEND;
END

Код триггера.

USE [BPMonline]
GO
/****** Object:  Trigger [dbo].[IOI_napr]    Script Date: 03/11/2015 17:56:44 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[IOI_napr]ON [dbo].[napr]
INSTEAD OF INSERT
AS
BEGIN
declare @U1 varchar(250)
declare @NUM varchar (250)
declare @NAP_NUM varchar (250)
SET @U1=(SELECT i.USLUGA_1 FROM inserted i)
SET @NUM=(SELECT i.NAPRAVLENIE_NUM FROM inserted i)
execute ('select * from BP_nap_ins('''+@NUM+''','''+@U1+''')') at DMS
 
END;

Код скрипта инсерта кнопки.

var U1=Page.TextEdit5.Value; // для теста передаем только 1 значение
var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
        return true;

если вы полнить инсерт из sql менеджера то в результах я вижу, что хранимка вернула значение.

каким оброзом можно запихнуть возвращаемое значение в переменную и использовать ее дальше в BPM ?
Спасибо что дочитали :)

Нравится

25 комментариев

если в скрипт кнопки добавить

Page.TextEdit14.SetValue(insert);

то в TextEdit14 пишется 1 (единица), но передоваемое значение не равно 1.

Попробуйте указанный код, вместо Insert

var selectSQL = new CustomQuery(UserConnection, sqlText);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
	using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
		while (dr.Read()) {
			PreviousMonth = dr.GetValue(0).ToString();
		}
	}
}

Спасибо за ответ, пошли немного другим путем. Запихнули текст триггера еще в одну хранимку, и через OPENQUERY пнули хранимку на связанном сервере. Таким макаром удалось вернуть значение в переменную. Сейчас бытаюсь разобраться как вызвать хранимку из формы.

может кому не сложно написать коды вызова хранимки:

в хранимку передаем значение из поля TextEdit5.
вернуть значение нужно в TextEdit14

почитал немного тут http://www.community.terrasoft.ru/blogs/7708

вот, что наваял:

var U1=Page.TextEdit5.Value;
//var NAP_NUM = null;
var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");
StoredProcedure.WithParameter(U1);
StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        Page.DataSource.ActiveRow.Save();
        StoredProcedure.Execute(dbExecutor);}
SetCloseStatus(1);
//Connector.DBEngine.ExecuteCustomSQL(SQLText);
//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
 	Page.TextEdit14.SetValue(NAP_NUM);
	return true;

но валится много ошибок при компиляции.

PS: извиняйте что туплю, я не программист

код хранимки

USE BPMonline;
IF OBJECT_ID ( 'dbo.BPM_to_DMS', 'P' ) IS NOT NULL 
    DROP PROCEDURE dbo.BPM_to_DMS;
GO
CREATE PROCEDURE dbo.BPM_to_DMS
    @U1 varchar(250),
    @NAP_NUM varchar (250) OUTPUT
    --@U2 varchar (250) 
  AS 
 
    SET NOCOUNT ON;
 
 set @NAP_NUM = (select * from OPENQUERY (dms,'select * from BP_nap_ins(''+@U1+'')'))
 
 GO

итак, удалось запустить хранимку, передать ей значение в U1, но никак не могу разобраться как вернуть из нее значение в TextEdit14. необходимое значение возвращается в NAP_NUM.

код вызова хранимки:

var U1=Page.TextEdit5.Value;
//var NAP_NUM;
var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var storedProcedure = new StoredProcedure(UserConnection, "BPM_to_DMS");
storedProcedure.WithParameter(U1);
storedProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        storedProcedure.Execute(dbExecutor);
				dbExecutor.CommitTransaction();
}
 
 	//Page.TextEdit14.SetValue(NAP_NUM);
	return true;

можно хотябы пример похожий (в примере с поиском дублей ничего не понятно)

PS: когда все допилю обещаю написать полный мануал по вызову хранимой процедуры на связанном сервере firebird 2.0

Добрый день. А почему Вы не используете тот пример, что я вам скинул? Им тоже можна "дергать" хранимые процедуры и получать значения

"Олейников Владимир Владимирович" написал:

Добрый день. А почему Вы не используете тот пример, что я вам скинул? Им тоже можна "дергать" хранимые процедуры и получать значения


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

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.
sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {
PreviousMonth = dr.GetValue(0).ToString();
}

"dr" - сохраняет в себе структуру возвращенных данных

"Олейников Владимир Владимирович" написал:

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.

sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {

PreviousMonth = dr.GetValue(0).ToString();

}

"dr" - сохраняет в себе структуру возвращенных данных


спасибо, будем пробовать

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

забал сказать что хранимка возвращает не просто число, а текст, по сути номер направления ( пример 15326/2015)

"Олейников Владимир Владимирович" написал:

там все очень просто. UserConnection - Переменная которая хранит в себе параметры подключения.

sqlText - сюда можно запихнуть любой SQL запрос, в том числе и вызов хранимой процедуры.

while (dr.Read()) {

PreviousMonth = dr.GetValue(0).ToString();

}

"dr" - сохраняет в себе структуру возвращенных данных

Владимир, ругается на PreviousMonth,

The name 'PreviousMonth' does not exist in the current context
DMSEditPage.cs
2918

PreviousMonth - это локальная переменная, которая создается и туда записывается значение. Можете вместо этого кода вписать
следующий код
Page.TextEdit14.SetValue(dr.GetValue(0).ToString());
только вместо "0" подставте нужный индекс. Посмотрите какой он по счету в выборке в запросе

"Олейников Владимир Владимирович" написал:

PreviousMonth - это локальная переменная, которая создается и туда записывается значение. Можете вместо этого кода вписать

следующий код

Page.TextEdit14.SetValue(dr.GetValue(0).ToString());

только вместо "0" подставте нужный индекс. Посмотрите какой он по счету в выборке в запросе


Владимир, попробовал подставить Page.TextEdit14.SetValue(dr.GetValue(0).ToString());

скрипт отрабатывает но в TextEdit14 ничего не возвращает(пробовал разные индексы(0 1 2), а вообще запись по счету первая).

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

var U1=Page.TextEdit5.Value;
//var result = 5;
var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var storedProcedure = new StoredProcedure(UserConnection, "BPM_to_DMS");
storedProcedure.WithParameter(U1);
storedProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
       // storedProcedure.Execute(dbExecutor);
	{
        using(var dr =storedProcedure.ExecuteReader(dbExecutor)) {
                while (dr.Read()) {
                     Page.TextEdit14.SetValue(dr.GetValue(0).ToString());
 
                }
        }
}
			dbExecutor.CommitTransaction();
 
}
 
 
	return true;

Профайлер перехватывает выполнение и показывает что в @NAP_NUM вернулось нужное значение

Попробуйте в дебаге посмотреть что вам возвращает переменная "dr" и все увидите

var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");
var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");
StoredProcedure.WithParameter(U1);
StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));
using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {
        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);
        Page.DataSource.ActiveRow.Save();
        StoredProcedure.Execute(dbExecutor);}
SetCloseStatus(1);
//Connector.DBEngine.ExecuteCustomSQL(SQLText);
//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();
        Page.TextEdit14.SetValue(NAP_NUM);
        return true;

Вместо этого кода напишите мой

"Олейников Владимир Владимирович" написал:

var dataValueTypeManager = (DataValueTypeManager)UserConnection.AppManagerProvider.GetManager("DataValueTypeManager");

var proc = new StoredProcedure(UserConnection, "BNS_to_DMS");

StoredProcedure.WithParameter(U1);

StoredProcedure.WithOutputParameter("NAP_NUM", dataValueTypeManager.GetInstanceByName("Text"));

using (DBExecutor dbExecutor = UserConnection.EnsureDBConnection()) {

        dbExecutor.StartTransaction(System.Data.IsolationLevel.ReadUncommitted);

        Page.DataSource.ActiveRow.Save();

        StoredProcedure.Execute(dbExecutor);}

SetCloseStatus(1);

//Connector.DBEngine.ExecuteCustomSQL(SQLText);

//var insert = new Insert(UserConnection).Into("napr").Set("USLUGA_1", Column.Parameter(U1)).Execute();

        Page.TextEdit14.SetValue(NAP_NUM);

        return true;

Вместо этого кода напишите мой


Владимир, простите что туплю, но я не знаю как через ваш код вызвать хранимку и передать в нее параметр, межете пример привести? если не сложно

вот что выдает дебагер

var sqlText = string.Format("ProcName {0},{1}", "value1", "value2")

Объявляем переменную, в которой хранится вызов хранимой процедуры с переметрами "value1", "value2"

var selectSQL = new CustomQuery(UserConnection, sqlText);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
        using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
                while (dr.Read()) {
                        PreviousMonth = dr.GetValue(0).ToString();
                }
        }
}

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

"Олейников Владимир Владимирович" написал:

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


спасибо за ответ, вот что вышло,

var U1=Page.TextEdit5.Value;
var Proc = string.Format("BPM_TO_DMS {0},{1}", "U1", "NAP_NUM");
	var selectSQL = new CustomQuery(UserConnection, Proc);
using (var dbExecutor = UserConnection.EnsureDBConnection()) {
        using(var dr = selectSQL.ExecuteReader(dbExecutor)) {
                while (dr.Read()) {
                        Page.TextEdit14.SetValue(dr.GetValue(1).ToString());
                }
        }
}
return true;

но при выполнении ничего не происходит, дебагер выдал все переменные по null, профайлер вы дал просто строку "BPM_TO_DMS U1,NAP_NUM" куда копать?

var Proc = string.Format("BPM_TO_DMS {0},{1}", "U1", "NAP_NUM");

Вы не коректно написали, UI Надо писать без кавычек, это имя переменной, в которой хранится значение передаваемой в хранимку переменной.

"Олейников Владимир Владимирович" написал:

Вы не коректно написали, UI Надо писать без кавычек, это имя переменной, в которой хранится значение передаваемой в хранимку переменной.


Владимир, если написать таким образом то просит объявить переменную NAP_NUM, если ее объявить то просит записать туда какаое-нить значение. Если передать туда 2 значения то процедура не срабатывает т.к. в процедуре ожидается 1 параметр входящий и 1 исходящий, а таким образом мы передаем туда 2 входящих. Может подскажите каким образом обявить NAP_NUM исходящим параметром? Спасибо.

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

var Proc = string.Format("BPM_TO_DMS {0},{1} ...", "U1", "NAP_NUM", ...);

"Олейников Владимир Владимирович" написал:

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

var Proc = string.Format("BPM_TO_DMS {0},{1} ...", "U1", "NAP_NUM", ...);


Владимир, это я понял, но мне нужно передать в хранимку 1 параметр, и хранимка должна вернуть 1 параметр. для этого в хранимке объявлен переменная NAP_NUM OUTPUT (в которую возвращается результат работы хранимки). елси вызывать хранимку через var proc = new StoredProcedure то там можно указать какой из параметров входящий а какой выходящий. Может вы подскажете каким образом в вашем скрипте указать что NAP_NUM это OUTPUT параметр? спасибо за помощь

В конце хранимки сделать выборку нужного вам параметра

DECLARE @d INT = 0
-- Какие-то действия с переменной
SELECT  @d
Показать все комментарии
события датасета
триггер
Технические вопросы
Разработка

3.4.0 XRM
Ситуация следующая:

При сохранении карточки (добавление\обновление) надо было создавать и обновлять, если уже было создано, заранее неизвестное количество операций (одна или две или три - в зависимости от значения поля). Я решил эти так:
сделал триггер After Update на tbl_Task, который удаляет все связанные с задачей (и "созданные автоматически" - поле в tbl_Cashflow) операции, а на AfterPost датасета задач добавил функцию добавления операций.
Все работает нормально, но есть 1-10% записей для которых операции отсутствуют. То ли не создаются, то ли удаляются - т.е. такое ощущение, что сначала отрабатывает AfterPost датасета - создает операции, а потом отрабатывает триггер (из-за каких-нибудь гипотетических тормозов сервера) - удаляет операции.
Нагрузка на сервер маленькая, если не минимальная.

Вопросы:
1) Возможна ли ситуация когда триггер After Update отрабатывает после AfterPost датасета?
2) если да, то есть ли еще варианты, как это решить, кроме как создавать доп. поля в задаче для записи ИД созданных операций или обновления\удаления записей по ним

Нравится

22 комментария

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

1. Такого быть не может. Скорее всего ошибка в самом триггере (в запросе на удаление). По умолчанию триггер работает в рамках одной транзакции, т.е. или он отрабатывает полностью или не отрабатывает вообще (вываливается с ошибкой). Смотрите в тело триггера.

"Евгений Либин" написал:триггер работает в рамках одной транзакции

да, действительно, что это я :lol:

однако в триггере "ничего такого":

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
 
ALTER TRIGGER [dbo].[tr_tbl_Task_AU] ON  [dbo].[tbl_Task] 
   AFTER UPDATE
AS 
BEGIN
	SET NOCOUNT ON;
declare @TaskID uniqueidentifier
select @TaskID = [INSERTED].[ID]
from [INSERTED]
 
DELETE FROM [dbo].[tbl_Cashflow]
WHERE([tbl_Cashflow].[TaskID] = @TaskID AND
	[tbl_Cashflow].[AutoCreated] = 1)
 
END

в карточке редактирования тоже все предельно просто:

function dlDataOnDatasetAfterPost(Dataset) {
	CopyContactInTasksIfNeed(Dataset);
	//ProcessSendMailMessageForTask(Dataset); //отключено
	CreateContactsInTask(Self, Dataset);
 
	CreateCashflowByTask(dlData.Dataset); // вот это
}

Не знаю, почему там используется даталинк - наверно, просто как следствие копипаста). Вряд ли это могло вызвать эту проблему, исправил.

в самой функции, если убрать все лишнее и заполнение значений тоже все довольно просто:

function CreateCashflowByTask(TaskDataset) {  // на апдейт задачи стоит tr_tbl_Task_AU (удаляет старые операции)
	var TaskCode = GetTaskTypeCodeByID(TaskDataset.ValAsGUID('TypeID'));
	var CashflowDataset = Services.GetNewItemByUSI('ds_Cashflow'); // сюда наверно лучше поставить GetSingleItemByCode()
 
	CashflowDataset.DataFields.ItemsByName('DebtorCreditorID').IsRequired = false;
	CashflowDataset.Append();
	FillCashFlowDatasetValuesByTask(CashflowDataset, TaskDataset, false, TaskCode);
 
	CashflowDataset.Post();
	CashflowDataset.Close();
}

В результате, по-прежнему не понимаю, почему может так происходить.
Причем воспроизвести сохранение задачи без формирования операции у меня пока не получилось. Не знаю, что и делать - не сидеть же мне и не клепать записи задач пока не получу такой случай...

А что внутри FillCashFlowDatasetValuesByTask?
Похоже что туда надо смотреть, т.е. триггер отработал а FillCashFlowDatasetValuesByTask нет.

если прочитаете, то вот ))
если коротко - там просто заполняются значения, все действия происходят вне функции

function FillCashFlowDatasetValuesByTask(CashflowDataset, TaskDataset, HeadPhones, TaskCode) {
	var ClauseID = GetCashflowClauseIDByCode(TaskCode);
	var ExpenseTypeID;
	if ((TaskCode == 'Bus') /*&& IsEmptyGUID(TaskDataset.ValAsGUID('OpportunityID'))*/) {
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode(TaskCode);
	} else 
	/*if (TaskDataset.ValAsBool('OrderByGuide')){
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode('drugoe');
	} else*/ {
		ExpenseTypeID = GetCashflowExpenseTypeIDByCode('Tour');
	}
	if (TaskDataset.ValAsBool('IsCash') || TaskCode == 'Bus' || HeadPhones) {
		CashflowDataset.ValAsGUID('CashAccountID') = '{4FAFDE39-D5EB-4309-BAFD-9C3DA1FCF0EB}'; // наличная касса
	} else {
		CashflowDataset.ValAsGUID('CashAccountID') = '{ADEC1E41-17EE-4B02-B04E-D677DEA48D39}'; // расчетный счет
	}
 
	if (TaskDataset.ValAsGUID('OpportunityID')) {
		CashflowDataset.ValAsGUID('OpportunityID') = TaskDataset.ValAsGUID('OpportunityID');
	}CashflowDataset.ValAsGUID('StatusID') = '{FDEA47BE-53FE-4730-BF4F-4F44C3B5D61A}'; // выполнена
	if (!HeadPhones) {
		CashflowDataset.ValAsStr('Subject') = TaskDataset.ValAsStr('Title');
	} else {
		CashflowDataset.ValAsStr('Subject') = TaskDataset.ValAsStr('Title') + ' наушники';
	}
	//var IsTop = GetIsUserInGroup(Connector.CurrentUser.Name, 'УЧРЕДИТЕЛИ');
	CashflowDataset.ValAsBool('UseAsCashflow') = false;
	CashflowDataset.ValAsBool('UseAsPandL') = true;
	CashflowDataset.ValAsBool('AutoCreated') = true;
	CashflowDataset.ValAsBool('ForFlow') = true;
	CashflowDataset.ValAsGUID('ClauseID') = ClauseID;
	CashflowDataset.ValAsGUID('ExpenseTypeID') = ExpenseTypeID; // группы
	CashflowDataset.ValAsGUID('CurrencyID') = TaskDataset.ValAsGUID('CurrencyID');
	if (!HeadPhones) {
		CashflowDataset.ValAsFloat('Amount') = TaskDataset.ValAsFloat('FullAmount');
	} else {
		CashflowDataset.ValAsFloat('Amount') = TaskDataset.ValAsFloat('HeadphonesAmount');
	}
	CashflowDataset.ValAsGUID('PayerID') = Connector.CurrentUser.AccountID;
	CashflowDataset.ValAsFloat('AutocalcAmount') = true;
	if (!HeadPhones) {
		CashflowDataset.Values('RecipientID') //= CashflowDataset.Values('DebtorCreditorID') 
			= TaskDataset.Values('SupplierID');
	} else {
		CashflowDataset.Values('RecipientID') //= CashflowDataset.Values('DebtorCreditorID') 
			= TaskDataset.Values('Supplier2ID');
	}
	//SetBasicPriceInDataset(CashflowDataset, 'BasicAmount', 'Amount', 'CurrencyID', 'CurrencyRate');		
	CashflowDataset.ValAsGUID('TaskID') = TaskDataset.ValAsGUID('ID');
	CashflowDataset.DisableEvents();
	CashflowDataset.ValAsDateTime('ActualDate') = CashflowDataset.ValAsDateTime('EstimatedDate') = TaskDataset.ValAsDateTime('StartDate');
	CashflowDataset.EnableEvents();
	return CashflowDataset;
}

на всякий случай вот полный текст функций
createcashflowbytask.txt

На первый взгляд всё ОК.
Проверьте 2 пункта
1. Посмотрите напрямую из базы

 Select * from tbl_CashFlow Where TaskID = 'Нужный ID задачи'

2. Если вышеуказанный запрос вернет данные, то посмотрите на sq_CashFlow возможно где-то inner join или в where доп. условия.

"Евгений Либин" написал:Проверьте 2 пункта

1. нет
2. нет(

А где CashflowDataset.Open(); ?

"Евгений Либин" написал:А где CashflowDataset.Open(); ?

мне его читать не надо - есть только Append() в CreateCashflowByTask
"SQK" написал:Если набор данных не находится в состоянии добавления записи (значение свойства IDataset::State не равно "dstInsert"), и у текущего пользователя есть права на добавление записи (значение свойства IDataset::CanInsert равно "True"), то вызывает событие IDatasetEvents::OnDatasetBeforeAppend. Иначе метод завершает работу.

Я бы рекомендовал всегда перед добавление делать открытие датасета, тем более есть такая строка

CashflowDataset.DataFields.ItemsByName('DebtorCreditorID').IsRequired = false;

.
Для ускорения можно делать фильтр по любому левому ID

ApplyDatasetFilter(CashflowDataset, 'ID', GUID_NULL, true);

Обращения по ID моментальное, набор данных будет пустой и датасет будет проинициализирован.

Спасибо, Евгений, воспользуюсь Вашими советами, хотя и не понимаю, для чего мне открывать датасет (на Before\After Open ничего нет)

Тут смысл не в отработке событий, а в инициализации самого датасета.

Здравствуйте, Дмитрий!
Помогли ли советы Евгения по открытию DataSet'а?
В данном случае также возможны следующие вариант, что операции создаются, но не привязываются к записям.
Попробуйте включить логирование и при возникновении проблемы проверьте, добавлялись ли записи вообще.

"Андрей Каспаревич" написал:Помогли ли советы Евгения по открытию DataSet'а?

Пока не знаю. Я дописал открытие с фильтрацией. Через какое-то время посмотрим, будет ли повторяться ситуация.

"Андрей Каспаревич" написал:операции создаются, но не привязываются к записям

почему? Не вижу причин, почему ИД задач и продаж могли бы не подставляться в скрипте. Кроме того я бы видел разницу между результатами запросов в SQL и в Террасофт

"Андрей Каспаревич" написал:
Попробуйте включить логирование и при возникновении проблемы проверьте, добавлялись ли записи вообще.

Так и сделал))

Через 2 недели - месяц отпишусь, что все ок, либо раньше - что нет

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?

"Борисов Михаил Евгеньевич" написал:

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?


триггеру можно)

"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:

А делать DELETE FROM [dbo].[tbl_Cashflow] можно под всеми пользователями?

триггеру можно)


Не понял, как? Тригер срабатывает в рамках транзакции где пошел update. Он будет обращаться к таблицам от имени того пользователя от которого пошел update.

"Борисов Михаил Евгеньевич" написал:Он будет обращаться к таблицам от имени того пользователя от которого пошел update

вот только пользователи работают с представлениями (и их триггерами), а я сделал на таблицу

"Андросов Дмитрий" написал:
Борисов Михаил Евгеньевич пишет:

Он будет обращаться к таблицам от имени того пользователя от которого пошел update

вот только пользователи работают с представлениями (и их триггерами), а я сделал на таблицу


У меня только один вопрос: от имени какого пользователя открыта транзакция в тригере таблицы? Что возращает suser_name(): supervisor, vasay или еще что то? Я подозреваю, что vasay и прав у него ровно столько сколько дали через grant.

На сколько я понял - проблем с удалением записей нет. Вопрос только с созданием новых. Если речь идет о контексте выполнения триггера, то нужно для начала уточнить версию СУБД. Разные СУБД и даже разные версии СУБД по разному используют (не используют) контекст.

Проблема была в том, что пользователи использовали массовое редактирование записей (делается через датасет), а создание операций повешено на скрипт карточки....

зато заглавный вопрос прояснил :wink:

Показать все комментарии
dlData
SystemUser
update
база
база данных
Записи
запись
запрос
запросы
значение
значения
логи
логов
окно редактирования
ошибка
поле
поля
событие
событие окна
событие поля
События
события окна
события полей
таблица
Таблицы
триггер
Технические вопросы
Разработка

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

Естественно самой реальной и первой причиной возникновения такой ошибки приходила идея о сбоях в работе событий полей окна редактирования (то есть значения в полях изменялись, а события данных полей(-я) не срабатывали).

В основу решения было положено создание двух таблиц в базе данных для ведения логов, что происходят с записью набора данных. Первая таблица WindowLog, а вторая TriggerLog.

Первая таблица WindowLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Ответственный» (WindowsUser), «Имя поля породившего событие»(FieldName), «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для наполнения таблицы было использованы события невизуального компонента окна dlData: dlDataOnDatasetDataChange, dlDataOnDatasetBeforePost и dlDataOnDatasetAfterPost. В скрипте в событиях была создана функция, которая формировала SQL запрос к таблице WindowLog базы данных с фиксацией информации по указанным полям на момент срабатывания события.

Запрос:

INSERT INTO WindowLog (*набор полей*)
SELECT (*набор полей*) -- Dataset('поле1'), Dataset('поле2'), Dataset('поле2')

Вторая таблица TriggerLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Состояние» (до изменения записи и после), «SystemUser», «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для заполнения данной таблицы был создан триггер на инструкцию UPDATE проблемной таблицы с двумя запросами вставки значений в таблицу. В одном запросе вставлялись значения до изменений, а во втором после.

Запрос №1:

INSERT INTO TriggerLog (*набор полей*)       
SELECT (*набор полей*)
FROM deleted

Запрос №2:

INSERT INTO TriggerLog (*набор полей*)       
SELECT (*набор полей*)
FROM inserted

Результатом использования данного решения на основе анализа таблицы WindowLog было установлено, что срабатывают все события окна редактирования, влияющие на вычисление значения поля «Итого». В процессе использования окна редактирования и после сохранения записи значения поля «Итого» были корректны.

Проанализировав записи в таблице TriggerLog было установлено, что в результате выполнения инструкции UPDATE было внесено некорректное значение. Сопоставив даты создания записей в таблице TriggerLog и WindowLog было установлено, что инструкция UPDATE была вызвана не в результате манипуляций с окном редактирования, а иным источником. На основании поля «SystemUser» таблицы TriggerLog было установлено что изменения были внесены с помощью импортера данных.

Таблицу TriggerLog возможно расширить, добавив в нее поля, которые помогут ускорить процесс обнаружение источника изменений записи базы данных. Список дополнительных полей может выгладять следующим образом: ApplicationName, LoginName, HostName.

PS: Принимаю предложения на доработку вашей конфигурации!!! Для более детальной информации можно связаться по следующему e-mail адресу: providnui@ukr.net !!!

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

Всем удачи в этом не легком процессе!!!

Нравится

0 комментариев
Показать все комментарии
сохранение записи
триггер

Столкнулся с интересной ситуацией при переводе проектного решения с версии 3.2 на версию 3.3. Карточка проекта не закрывалась при нажатии на кнопку OK. Запись в базе при этом сохранялась.

В результате отладки выяснил, что в 3.3 базовая карточка редактирования была доработана и теперь, если метод Post набора данных карточки возвращает 0, карточка не закрывается.

Метод Post набора данных возвращает количество строк, которые были затронуты при выполнении запроса. В моем случае, этот метод возвращал 0, несмотря на то, что запись сохранялась.

Более внимательный взгляд на таблицу проектов показал, что для нее есть несколько триггеров After Update, созданных в рамках проектного решения. Собственно, результат выполнения этих триггеров и возвращается методом Post в конечном итоге.

Чтобы все отрабатывало как нужно, и метод Post возвращал именно количество записей непосредственно затронутых запросом, в триггерах следует включать опцию NOCOUNT.

Для этого в каждом триггере первой строкой необходимо записать

SET NOCOUNT ON

Нравится

Поделиться

0 комментариев
Показать все комментарии