Столкнулся с проблемой при работе с деталью. Каждый раз когда создается новая запись, вне зависимости от содержания, так же создается еще одна запись с фиксированным содержанием. Никаких БП привязанных к детали нету.
На картинке: 1-я запись, это запись созданная мной, 2-я запись появляется каждый раз при создании новой записи
Такое поведение являеться ожидаемым, поскольку единица измерения "штук" является базовой, и необходима для корректной работы в последующих процессах в которых присутствуют продукты.
Например при формировании заказа, цены и т.д
Вы можете добавить несколько единиц измерения, но этот продукт кроме этого, будет измеряться в штуках:
1 единица продука = 1 штука.
Например если вы добавите единицу измерения 8 часов, значит 1 штука продука = 8 часов.
В процессе администрирования базы данных возникла необходимость определить причину возникновения ошибки. Определенный объём информации импортируется в базу данных, с которым далее пользователи работают. В процессе заполнения определенного набора полей автоматически высчитывалась итоговая сумма в поле «Итого». Но в определённый промежуток времени использования продукта начали появляться ошибки, связанные с несоответствием значения поля «Итого» сумме полей из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Так как ошибку не получалось явно повторить, необходимо было разработать механизм для решения данной проблемы.
Естественно самой реальной и первой причиной возникновения такой ошибки приходила идея о сбоях в работе событий полей окна редактирования (то есть значения в полях изменялись, а события данных полей(-я) не срабатывали).
В основу решения было положено создание двух таблиц в базе данных для ведения логов, что происходят с записью набора данных. Первая таблица WindowLog, а вторая TriggerLog.
Первая таблица WindowLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Ответственный» (WindowsUser), «Имя поля породившего событие»(FieldName), «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для наполнения таблицы было использованы события невизуального компонента окна dlData: dlDataOnDatasetDataChange, dlDataOnDatasetBeforePost и dlDataOnDatasetAfterPost. В скрипте в событиях была создана функция, которая формировала SQL запрос к таблице WindowLog базы данных с фиксацией информации по указанным полям на момент срабатывания события.
Вторая таблица TriggerLog включает в себя поля «Дата создания»(CreatedOn), «Идентификатор записи» (RecordID), «Состояние» (до изменения записи и после), «SystemUser», «Итого» и поля из которых оно вычисляется («Сумма покупки», «Наценка», «Сбор» и т.д.). Для заполнения данной таблицы был создан триггер на инструкцию UPDATE проблемной таблицы с двумя запросами вставки значений в таблицу. В одном запросе вставлялись значения до изменений, а во втором после.
Запрос №1:
INSERTINTO TriggerLog (*набор полей*) SELECT(*набор полей*) FROM deleted
Запрос №2:
INSERTINTO TriggerLog (*набор полей*) SELECT(*набор полей*) FROM inserted
Результатом использования данного решения на основе анализа таблицы WindowLog было установлено, что срабатывают все события окна редактирования, влияющие на вычисление значения поля «Итого». В процессе использования окна редактирования и после сохранения записи значения поля «Итого» были корректны.
Проанализировав записи в таблице TriggerLog было установлено, что в результате выполнения инструкции UPDATE было внесено некорректное значение. Сопоставив даты создания записей в таблице TriggerLog и WindowLog было установлено, что инструкция UPDATE была вызвана не в результате манипуляций с окном редактирования, а иным источником. На основании поля «SystemUser» таблицы TriggerLog было установлено что изменения были внесены с помощью импортера данных.
Таблицу TriggerLog возможно расширить, добавив в нее поля, которые помогут ускорить процесс обнаружение источника изменений записи базы данных. Список дополнительных полей может выгладять следующим образом: ApplicationName, LoginName, HostName.
PS: Принимаю предложения на доработку вашей конфигурации!!! Для более детальной информации можно связаться по следующему e-mail адресу: providnui@ukr.net !!!
В случае возникновения дополнительных вопрос по теме могу поделиться более детальной информацией.
Добрый день. Возможно данный вопрос уже был но все же, хотелось бы знать есть ограничение на кол-во добавления колонок(строк, справочников и тд) в карточке продажи или других карточках! И если есть можно ли его убрать? и как это сделать?
Заранее спасибо!
Всем привет! Все мы знаем, что, если при удалении записи на нее есть ссылки из других таблиц, то система выдает список связей для того, чтобы пользователь попробовал разобраться с этим, где нужно удалил эти связи или вообще отказался от затеи удалять ту самую запись. Но, на мой взгляд, в 90% случаев пользователь уверен, что эту запись точно нужно удалить и разбираться со связями он 100% не хочет. Так почему бы не дать ему возможность просто удалить эту запись, предварительно отвязав ее от остальных объектов автоматически?
Для решения этой задачи был взят за основу скрипт отсюда.
1. Создаем хранимую процедуру под sa или под пользователем с правом sysadmin, которая будет делать отвязку записи и ее удаление:
CREATE procedure tsp_UnbindAndDeleteRecord(@RecordID nvarchar(max), @ParentTableName nvarchar(max))WITH exec AS owner AS
begin
declare @ColumnName nvarchar(max)
declare @TableName nvarchar(max)
declare @TempSQL nvarchar(max)
declare @RecordValue nvarchar(max)
declare local_cursor cursor LOCALFOR SELECTDISTINCT
CONSTRAINT_COLUMN_USAGE.COLUMN_NAME AS COLUMN_NAME,
CONSTRAINT_COLUMN_USAGE.TABLE_NAME AS TABLE_NAME FROM((INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS CONSTRAINT_COLUMN_USAGE INNERJOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TABLE_CONSTRAINTS ON CONSTRAINT_COLUMN_USAGE.CONSTRAINT_NAME = TABLE_CONSTRAINTS.CONSTRAINT_NAME) INNERJOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS REFERENTIAL_CONSTRAINTS ON CONSTRAINT_COLUMN_USAGE.CONSTRAINT_NAME = REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME) INNERJOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS PARENT_TABLES ON REFERENTIAL_CONSTRAINTS.UNIQUE_CONSTRAINT_NAME = PARENT_TABLES.CONSTRAINT_NAME WHERE TABLE_CONSTRAINTS.CONSTRAINT_TYPE ='FOREIGN KEY'AND
PARENT_TABLES.TABLE_NAME = @ParentTableName
IF(isnull(cast(@RecordID AS nvarchar(max)),'')='') RETURN
open local_cursor
fetch next FROM local_cursor INTO @ColumnName, @TableName
while @@fetch_Status =0
begin SET @TempSQL ='UPDATE ['+ @TableName +'] SET ['+ @ColumnName +'] = null'+' WHERE ['+ @ColumnName +'] = '''+ @RecordID +''''
exec sp_executesql @TempSQL
fetch next FROM local_cursor INTO @ColumnName, @TableName
end
close local_cursor
deallocate local_cursor
SET @TempSQL ='delete from ['+ @ParentTableName +'] WHERE [ID] = '''+ @RecordID +''''
exec sp_executesql @TempSQL
end
--grant execute on tsp_UnbindAndDeleteRecord to public
Как вы может быть заметили, процедура создана с "with exec as owner". Добавил это я на всякий случай, если вдруг вы не дадите гранты на использование этой процедуры пользователям.
2. Ищем сервис окна wnd_DeleteRecordsWizard, которое появляется, когда выводится список связанных объектов при удалении записей. Добавляем во фрейм с кнопками свою кнопку btnDeleteForce и даем ей "Заголовок" = "Удалить принудительно".
3. На OnClick этой кнопки "вешаем" наш код, в котором запускаем нашу процедуру из пункта 1, передавая в нее в качестве параметров ID удаляемых записей (у нас их ведь может быть сколько угодно) и имя таблицы, из которой производится удаление.
function btnDeleteForceOnClick(Control){ var Dataset = dlData.Dataset; if(Dataset.IsEmptyPage){ return; } if(ShowConfirmationDialog("Вы уверены?")!= wmrYes){ return; } var TableName = Self.Attributes('TableName'); if(IsEmptyValue(TableName)){ return; } var RecordIDs = Self.Attributes('RecordIDs'); if(!RecordIDs){ return; } var Count = RecordIDs.length; var RecordID; try{
System.BeginProcessing(); for(var i =0; i Count; i++){
System.ProcessMessages();
RecordID = RecordIDs[i]; var sql ="exec tsp_UnbindAndDeleteRecord '"+
RecordID +"', '"+ TableName +"'";
Connector.DBEngine.ExecuteCustomSQL(sql, System.EmptyValue); } }finally{
System.EndProcessing(); }
Self.Close(); }
4. Все-таки позволять всем пользователям принудительно удалять запись - дело рискованное. Поэтому предлагаю позволять это делать ответственному человеку, а именно пользователю с правами администратора системы. Для этого в OnPrepare окна wnd_DeleteRecordsWizard добавляем код показа нашей кнопки:
Если же вы, все-таки, решите позволять такое удаление любому пользователю, то, как понимаете, пункт 4 делать не нужно.
Примечание: Проверялся этот функционал на MS SQL 2008 (для Oracle он вообще не подходит, как Вы можете судить по синтаксису хранимой процедуры), но, думаю, он будет работать и на MS SQL 2005.
Спасибо S.Kalishenko. Полезный функционал. Только я так понял доработка для версии террасофт начиная с 3.3.2 так как в версии 3.3.1 еще нет окна wnd_DeleteRecordsWizard. Я думаю как вариант сделать в окне wnd_BaseGridArea в amDelete еще один пунк "Удалить принудительно" и поправить код следующим образом
var Dataset = dlData.Dataset;if(Dataset.IsEmptyPage){return;}if(ShowConfirmationDialog("Вы уверены?")!= wmrYes){return;}
var TableName = GetTableFromDataset(Dataset);if(IsEmptyValue(TableName)){return;}
var RecordIDs = GetGridSelectedIDsArray();if(!RecordIDs){return;}
var Count = RecordIDs.length;
var RecordID;try{
System.BeginProcessing();for(var i =0; i < Count; i++){
System.ProcessMessages();
RecordID = RecordIDs[i];
var sql ="exec tsp_UnbindAndDeleteRecord '"+
RecordID +"', '"+ TableName.SQLName+"'";
Connector.DBEngine.ExecuteCustomSQL(sql, System.EmptyValue);}} finally {
System.EndProcessing();}
"Мещеринов Иван Александрович" написал:Только я так понял доработка для версии террасофт начиная с 3.3.2 так как в версии 3.3.1 еще нет окна wnd_DeleteRecordsWizard.
Да, в 3.3.1 можно реализовать функционал как Вы описали.
"Мещеринов Иван Александрович" написал:а и еще вопрос а возможно сделать деталь в которой бы отображалось в каких таблицах данная запись имеет связанные записи?
Можно, только код я не готов Вам предоставить. Для такой детали можно воспользоваться реализацией получения датасета в wnd_DeleteRecordsWizard из 3.3.2.
Как известно, для удаления записей из таблицы, правильней всего создавать специальный DeleteQuery, но как показала практика - делать это всем лень :)
для таких случаев была написана следующая функция.
function DeleteRecords(Dataset, Dictionary){ if(!Assigned(Dataset)){ if(!IsEmptyValue(Dataset)&&
Assigned(Services.InformationsByUSI(Dataset))){
Dataset = GetSingleItemByCode(Dataset,'DeleteRecords'); }else{ return; } } if('DBDataset'== Dataset.ServiceTypeCode){ var Table = Dataset.SelectQuery.Items(0).FromTable; }else if('Table'== Dataset.ServiceTypeCode){ var Table = Dataset; }else{ return; } var dq = Services.CreateItem('DeleteQuery');
dq.Table= Table; var TableFields = Table.Fields; var Filters = dq.Filters; var Parameters = dq.Parameters;
var TableField; var FilterFieldType; var CompareOperatorType = cotEqual; var Parameter;
var Keys =new VBArray(Dictionary.Keys()).toArray(); var KeysLength = Keys.length; for(var i =0; i KeysLength; i++){
TableField = TableFields.ItemsByName(Keys[i]);
FilterFieldType =
GetParameterTypeBySQLDataType(TableField.SQLDataType);
Parameter = AddQueryParameter(Parameters, Keys[i],
FilterFieldType, Dictionary(Keys[i]));
AddQueryCompareFilter(Filters, Keys[i], TableField,
Parameter, CompareOperatorType); } return dq.Execute(); }
первый параметр - может быть как именем сервиса так и самим объектом Таблица или Датасет;
второй параметр - это набор полей и их значений в виде Словаря.
Функция апдейта:
function UpdateRecord(Dataset, RecordID, Dictionary){ if(!Assigned(Dataset)){ if(!IsEmptyValue(Dataset)&&
Assigned(Services.InformationsByUSI(Dataset))){
Dataset = GetSingleItemByCode(Dataset,'UpdateRecord'); }else{ return; } } if('DBDataset'== Dataset.ServiceTypeCode){ var Table = Dataset.SelectQuery.Items(0).FromTable; }else if('Table'== Dataset.ServiceTypeCode){ var Table = Dataset; }else{ return; } var uq = Services.CreateItem('UpdateQuery');
uq.Table= Table; var TableFields = Table.Fields; var Filters = uq.Filters; var Parameters = uq.Parameters; var Columns = uq.ColumnsValues;
var TableField; var FieldType; var CompareOperatorType = cotEqual; var Parameter; var Column;
var Keys =new VBArray(Dictionary.Keys()).toArray(); var KeysLength = Keys.length; for(var i =0; i KeysLength; i++){
TableField = TableFields.ItemsByName(Keys[i]); if(!Assigned(TableField)){ continue; }
FieldType = GetParameterTypeBySQLDataType(TableField.SQLDataType);
AddQueryParameter(Parameters, Keys[i], FieldType,
Dictionary(Keys[i]));
Column = Columns.CreateItem();
Column.ParameterName= Column.KeyValue= Column.Name= Keys[i];
Column.DataType= FieldType;
Columns.Add(Column); } var Result = uq.Execute(); return Result; }
первый параметр - может быть как именем сервиса так и самим объектом Таблица или Датасет;
второй параметр - это ID записи в таблице, которую нужно обновить;
третий параметр - это набор полей и их значений в виде Словаря.
Также нужно подключить scr_DB
Пример использования:
var Dictionary = GetNewDictionary();
Dictionary('AccountID')= RecordID;
Dictionary('TypeID')= tOne;
DeleteRecords('tbl_AccountAddress', Dictionary);
var Dictionary = GetNewDictionary();
Dictionary('Name')='NewValue';
Dictionary('Name2')='NewValue2';
Dictionary('Name3')='NewValue3';
UpdateRecord('tbl_Account', RecordID, Dictionary);
С некоторых пор функция UpdateRecord уже есть "в коробке", в скрипте scr_JobManagerUtils, но с небольшой доработкой, чтобы не пытались обновить значение ID.
После