Duplicate Record

This commit is contained in:
Sedat Öztürk 2026-03-30 06:15:42 +03:00
parent 08c495943b
commit 30be61f2c7
10 changed files with 148 additions and 19 deletions

View file

@ -18,6 +18,7 @@ public class GridEditingDto
public bool AllowDeleting { get; set; } = false; public bool AllowDeleting { get; set; } = false;
public bool AllowAllDeleting { get; set; } = false; public bool AllowAllDeleting { get; set; } = false;
public bool AllowAdding { get; set; } = false; public bool AllowAdding { get; set; } = false;
public bool AllowDuplicate { get; set; } = false;
public bool UseIcons { get; set; } = false; public bool UseIcons { get; set; } = false;
public bool ConfirmDelete { get; set; } = true; public bool ConfirmDelete { get; set; } = true;
/// <summary>Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop' /// <summary>Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop'

View file

@ -104,7 +104,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService
var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode); var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
return qManager.GenerateQuery(listForm, parameters, op, dataSourceType); return qManager.GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType);
} }
} }

View file

@ -64,5 +64,19 @@ public class ListFormDataAppService : PlatformAppService
var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value); var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
return await qManager.GenerateAndRunQueryAsync<int>(input.ListFormCode, OperationEnum.Delete, input.Data, input.Keys, queryParameters); return await qManager.GenerateAndRunQueryAsync<int>(input.ListFormCode, OperationEnum.Delete, input.Data, input.Keys, queryParameters);
} }
public async Task<int> PostDuplicateAsync(DataRequestDto input)
{
// Izin logic process
if (!await authManager.CanAccess(input.ListFormCode, AuthorizationTypeEnum.Create))
throw new Volo.Abp.UserFriendlyException(L[AppErrorCodes.NoAuth]);
var httpContext = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HTTP Context bulunamadı.");
var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value);
// Duplicate işlemi için OperationEnum.Duplicate kullanılır
return await qManager.GenerateAndRunQueryAsync<int>(input.ListFormCode, OperationEnum.Duplicate, input.Data, input.Keys, queryParameters);
}
} }

View file

@ -4092,6 +4092,12 @@
"en": "Allow Detail", "en": "Allow Detail",
"tr": "Detaya İzin Ver" "tr": "Detaya İzin Ver"
}, },
{
"resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingAllowDuplicate",
"en": "Allow Duplicate",
"tr": "Çoğaltmaya İzin Ver"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "ListForms.ListFormEdit.EditingAllowDeleting", "key": "ListForms.ListFormEdit.EditingAllowDeleting",
@ -10338,6 +10344,18 @@
"tr": "Sil", "tr": "Sil",
"en": "Delete" "en": "Delete"
}, },
{
"resourceName": "Platform",
"key": "App.Platform.Duplicate",
"tr": "Çoğalt",
"en": "Duplicate"
},
{
"resourceName": "Platform",
"key": "App.Platform.DuplicateError",
"tr": "Çoğaltma Hatası",
"en": "Duplicate Error"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.Warning", "key": "App.Platform.Warning",

View file

@ -3421,15 +3421,6 @@
"MultiTenancySide": 3, "MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs" "MenuGroup": "Erp|Kurs"
}, },
{
"GroupName": "App.Administration",
"Name": "App.Reports.ReportTemplates.Note",
"ParentName": "App.Reports.ReportTemplates",
"DisplayName": "Note",
"IsEnabled": true,
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
{ {
"GroupName": "App.Administration", "GroupName": "App.Administration",
"Name": "App.Reports.ReportTemplates.Update", "Name": "App.Reports.ReportTemplates.Update",

View file

@ -11,6 +11,7 @@ public enum OperationEnum
Delete, Delete,
DeleteBefore, DeleteBefore,
DeleteAfter, DeleteAfter,
Select Select,
Duplicate,
} }

View file

@ -27,6 +27,7 @@ public interface IQueryManager
string GenerateQuery( string GenerateQuery(
ListForm listForm, ListForm listForm,
List<ListFormField> listFormFields,
Dictionary<string, object> parameters, Dictionary<string, object> parameters,
OperationEnum op, OperationEnum op,
DataSourceTypeEnum dataSourceType, DataSourceTypeEnum dataSourceType,
@ -84,17 +85,17 @@ public class QueryManager : PlatformDomainService, IQueryManager
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode); var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
var sql = GenerateQuery(listForm, parameters, op, dataSourceType, keys); var sql = GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType, keys);
// Sorguyu calistir // Sorguyu calistir
if (!string.IsNullOrEmpty(sql)) if (!string.IsNullOrEmpty(sql))
{ {
// TODO: Log // TODO: Log
if (op == OperationEnum.Insert) if (op == OperationEnum.Insert || op == OperationEnum.Duplicate)
{ {
if (!string.IsNullOrEmpty(listForm.InsertBeforeCommand)) if (!string.IsNullOrEmpty(listForm.InsertBeforeCommand))
{ {
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.InsertBefore, dataSourceType, keys); var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
} }
@ -102,7 +103,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
if (!string.IsNullOrEmpty(listForm.InsertAfterCommand)) if (!string.IsNullOrEmpty(listForm.InsertAfterCommand))
{ {
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.InsertAfter, dataSourceType, keys); var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.InsertAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
} }
@ -113,12 +114,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
// Before komutlari varsa calistir // Before komutlari varsa calistir
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateBeforeCommand)) if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateBeforeCommand))
{ {
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateBefore, dataSourceType, keys); var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
} }
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteBeforeCommand)) else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteBeforeCommand))
{ {
var beforeSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteBefore, dataSourceType, keys); var beforeSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteBefore, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(beforeSql, connectionString, parameters);
} }
@ -128,12 +129,12 @@ public class QueryManager : PlatformDomainService, IQueryManager
// After komutlari varsa calistir // After komutlari varsa calistir
if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateAfterCommand)) if (op == OperationEnum.Update && !string.IsNullOrEmpty(listForm.UpdateAfterCommand))
{ {
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.UpdateAfter, dataSourceType, keys); var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.UpdateAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
} }
else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteAfterCommand)) else if (op == OperationEnum.Delete && !string.IsNullOrEmpty(listForm.DeleteAfterCommand))
{ {
var afterSql = GenerateQuery(listForm, parameters, OperationEnum.DeleteAfter, dataSourceType, keys); var afterSql = GenerateQuery(listForm, listFormFields, parameters, OperationEnum.DeleteAfter, dataSourceType, keys);
await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters); await dynamicDataRepository.ExecuteAsync(afterSql, connectionString, parameters);
} }
@ -146,6 +147,7 @@ public class QueryManager : PlatformDomainService, IQueryManager
public string GenerateQuery( public string GenerateQuery(
ListForm listForm, ListForm listForm,
List<ListFormField> listFormField,
Dictionary<string, object> parameters, Dictionary<string, object> parameters,
OperationEnum op, OperationEnum op,
DataSourceTypeEnum dataSourceType, DataSourceTypeEnum dataSourceType,
@ -186,6 +188,55 @@ public class QueryManager : PlatformDomainService, IQueryManager
_ => string.Empty, _ => string.Empty,
}; };
} }
else if (op == OperationEnum.Duplicate)
{
// Key parametresi yoksa ekle
if (!parameters.ContainsKey(listForm.KeyFieldName) && keys != null && keys.Length > 0)
{
parameters[listForm.KeyFieldName] = keys[0];
}
// InsertFieldsDefaultValueJson'u oku
var insertDefaults = new Dictionary<string, object>();
if (!string.IsNullOrWhiteSpace(listForm.InsertFieldsDefaultValueJson))
{
try
{
insertDefaults = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(listForm.InsertFieldsDefaultValueJson);
}
catch { }
}
// Tüm alanları (id/key dahil) listFormField listesinden sırala
var allFields = listFormField.Select(f => f.FieldName).ToList();
// Insert kısmı için alan adları
var insertFieldString = string.Join(",", allFields.Select(a => $"[{a}]"));
// Select kısmı için: eğer default value varsa onu kullan, yoksa select ile gelen değeri kullan
var selectFieldString = string.Join(",", allFields.Select(a =>
{
if (insertDefaults.ContainsKey(a) && insertDefaults[a] != null)
{
// String ise tek tırnakla, sayı ise direkt
var val = insertDefaults[a];
if (val is string || val?.GetType() == typeof(string))
return $"'{val.ToString().Replace("'", "''")}'";
else if (val is bool)
return (bool)val ? "1" : "0";
else if (val is null)
return "NULL";
else
return val.ToString();
}
// Key/id alanı için, default yoksa NULL ata (veya istenirse farklı bir şey)
if (string.Equals(a, listForm.KeyFieldName, StringComparison.OrdinalIgnoreCase))
return "NULL";
return $"[{a}]";
}));
sql = $"INSERT INTO [{listForm.SelectCommand}] ({insertFieldString}) SELECT {selectFieldString} FROM [{listForm.SelectCommand}] WHERE [{listForm.KeyFieldName}] = @{listForm.KeyFieldName}";
}
else if (op == OperationEnum.Update) else if (op == OperationEnum.Update)
{ {
var where = dataSourceType switch var where = dataSourceType switch

View file

@ -444,6 +444,7 @@ export interface GridEditingDto {
allowDeleting: boolean allowDeleting: boolean
allowAllDeleting: boolean allowAllDeleting: boolean
allowAdding: boolean allowAdding: boolean
allowDuplicate: boolean
useIcons: boolean useIcons: boolean
confirmDelete: boolean confirmDelete: boolean
newRowPosition?: NewRowPosition newRowPosition?: NewRowPosition

View file

@ -118,6 +118,20 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) {
/> />
</FormItem> </FormItem>
<FormItem
label={translate('::ListForms.ListFormEdit.EditingAllowDuplicate')}
invalid={
errors.editingOptionDto?.allowDuplicate && touched.editingOptionDto?.allowDuplicate
}
errorMessage={errors.editingOptionDto?.allowDuplicate}
>
<Field
name="editingOptionDto.allowDuplicate"
placeholder={translate('::ListForms.ListFormEdit.EditingAllowDuplicate')}
component={Checkbox}
/>
</FormItem>
<FormItem <FormItem
label={translate('::ListForms.ListFormEdit.EditingAllowDeleting')} label={translate('::ListForms.ListFormEdit.EditingAllowDeleting')}
invalid={ invalid={

View file

@ -329,6 +329,10 @@ const useListFormColumns = ({
gridDto.gridOptions.editingOptionDto.allowDetail && gridDto.gridOptions.editingOptionDto.allowDetail &&
checkPermission(gridDto.gridOptions.permissionDto.u) checkPermission(gridDto.gridOptions.permissionDto.u)
const hasDuplicate =
gridDto.gridOptions.editingOptionDto.allowDuplicate &&
checkPermission(gridDto.gridOptions.permissionDto.i)
const hasCommandButtons = gridDto.gridOptions.commandColumnDto.length > 0 const hasCommandButtons = gridDto.gridOptions.commandColumnDto.length > 0
// Eğer hiçbir buton eklenecek durumda değilse: çık // Eğer hiçbir buton eklenecek durumda değilse: çık
@ -374,6 +378,40 @@ const useListFormColumns = ({
buttons.push(item) buttons.push(item)
} }
if (hasDuplicate) {
const item = {
name: 'duplicate',
text: translate('::App.Platform.Duplicate'),
onClick: async (e: any) => {
if (typeof e.event?.preventDefault === 'function') {
e.event.preventDefault()
}
if (!gridDto.gridOptions.keyFieldName) return
const id = e.row.data[gridDto.gridOptions.keyFieldName]
if (!id) return
// Backend'e duplicate isteği gönder
try {
await dynamicFetch('list-form-data/duplicate', 'POST', null, {
listFormCode,
keys: [id],
data: {},
})
// Başarılı ise grid'i yenile
if (gridRef?.current?.instance()) {
gridRef.current.instance().refresh()
}
} catch (err) {
// Hata yönetimi
alert(translate('::App.Platform.DuplicateError') || 'Kayıt kopyalanamadı!')
}
},
}
buttons.push(item)
}
gridDto.gridOptions.commandColumnDto.forEach((action) => { gridDto.gridOptions.commandColumnDto.forEach((action) => {
if (action.buttonPosition !== UiCommandButtonPositionTypeEnum.CommandColumn) return if (action.buttonPosition !== UiCommandButtonPositionTypeEnum.CommandColumn) return
if (!checkPermission(action.authName)) return if (!checkPermission(action.authName)) return