From 30be61f2c7af35dc1adecf9d510e1a5319413588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Mon, 30 Mar 2026 06:15:42 +0300 Subject: [PATCH] Duplicate Record --- .../GridOptionsDto/GridEditingDto.cs | 1 + .../ListFormQueryPreviewAppService.cs | 2 +- .../ListForms/ListFormDataAppService.cs | 14 ++++ .../Seeds/LanguagesData.json | 18 +++++ .../Seeds/PermissionsData.json | 9 --- .../Enums/OperationEnum.cs | 3 +- .../Queries/QueryManager.cs | 67 ++++++++++++++++--- ui/src/proxy/form/models.ts | 1 + .../views/admin/listForm/edit/FormTabEdit.tsx | 14 ++++ ui/src/views/list/useListFormColumns.ts | 38 +++++++++++ 10 files changed, 148 insertions(+), 19 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridEditingDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridEditingDto.cs index 2a91a00..2d30756 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridEditingDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridEditingDto.cs @@ -18,6 +18,7 @@ public class GridEditingDto public bool AllowDeleting { get; set; } = false; public bool AllowAllDeleting { get; set; } = false; public bool AllowAdding { get; set; } = false; + public bool AllowDuplicate { get; set; } = false; public bool UseIcons { get; set; } = false; public bool ConfirmDelete { get; set; } = true; /// Accepted Values: 'first' | 'last' | 'pageBottom' | 'pageTop' | 'viewportBottom' | 'viewportTop' diff --git a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormQueryPreviewAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormQueryPreviewAppService.cs index 3ef63bc..f1ad979 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormQueryPreviewAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/Administration/ListFormQueryPreviewAppService.cs @@ -104,7 +104,7 @@ public class ListFormQueryPreviewAppService : PlatformAppService var (_, _, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode); - return qManager.GenerateQuery(listForm, parameters, op, dataSourceType); + return qManager.GenerateQuery(listForm, listFormFields, parameters, op, dataSourceType); } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs index 64a328a..824fb23 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs @@ -64,5 +64,19 @@ public class ListFormDataAppService : PlatformAppService var queryParameters = httpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value); return await qManager.GenerateAndRunQueryAsync(input.ListFormCode, OperationEnum.Delete, input.Data, input.Keys, queryParameters); } + + public async Task 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(input.ListFormCode, OperationEnum.Duplicate, input.Data, input.Keys, queryParameters); + } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index f287952..d7ff9d1 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -4092,6 +4092,12 @@ "en": "Allow Detail", "tr": "Detaya İzin Ver" }, + { + "resourceName": "Platform", + "key": "ListForms.ListFormEdit.EditingAllowDuplicate", + "en": "Allow Duplicate", + "tr": "Çoğaltmaya İzin Ver" + }, { "resourceName": "Platform", "key": "ListForms.ListFormEdit.EditingAllowDeleting", @@ -10338,6 +10344,18 @@ "tr": "Sil", "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", "key": "App.Platform.Warning", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json index deec80e..e6fe555 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json @@ -3421,15 +3421,6 @@ "MultiTenancySide": 3, "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", "Name": "App.Reports.ReportTemplates.Update", diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/OperationEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/OperationEnum.cs index 102c96c..2222818 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/OperationEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/OperationEnum.cs @@ -11,6 +11,7 @@ public enum OperationEnum Delete, DeleteBefore, DeleteAfter, - Select + Select, + Duplicate, } diff --git a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs index 12d715a..52dc7a9 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs @@ -27,6 +27,7 @@ public interface IQueryManager string GenerateQuery( ListForm listForm, + List listFormFields, Dictionary parameters, OperationEnum op, DataSourceTypeEnum dataSourceType, @@ -84,17 +85,17 @@ public class QueryManager : PlatformDomainService, IQueryManager 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 if (!string.IsNullOrEmpty(sql)) { // TODO: Log - if (op == OperationEnum.Insert) + if (op == OperationEnum.Insert || op == OperationEnum.Duplicate) { 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); } @@ -102,7 +103,7 @@ public class QueryManager : PlatformDomainService, IQueryManager 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); } @@ -113,12 +114,12 @@ public class QueryManager : PlatformDomainService, IQueryManager // Before komutlari varsa calistir 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); } 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); } @@ -128,12 +129,12 @@ public class QueryManager : PlatformDomainService, IQueryManager // After komutlari varsa calistir 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); } 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); } @@ -146,6 +147,7 @@ public class QueryManager : PlatformDomainService, IQueryManager public string GenerateQuery( ListForm listForm, + List listFormField, Dictionary parameters, OperationEnum op, DataSourceTypeEnum dataSourceType, @@ -186,6 +188,55 @@ public class QueryManager : PlatformDomainService, IQueryManager _ => 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(); + if (!string.IsNullOrWhiteSpace(listForm.InsertFieldsDefaultValueJson)) + { + try + { + insertDefaults = System.Text.Json.JsonSerializer.Deserialize>(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) { var where = dataSourceType switch diff --git a/ui/src/proxy/form/models.ts b/ui/src/proxy/form/models.ts index 26227dd..1d4d49f 100644 --- a/ui/src/proxy/form/models.ts +++ b/ui/src/proxy/form/models.ts @@ -444,6 +444,7 @@ export interface GridEditingDto { allowDeleting: boolean allowAllDeleting: boolean allowAdding: boolean + allowDuplicate: boolean useIcons: boolean confirmDelete: boolean newRowPosition?: NewRowPosition diff --git a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx index f08f68e..b2242cc 100644 --- a/ui/src/views/admin/listForm/edit/FormTabEdit.tsx +++ b/ui/src/views/admin/listForm/edit/FormTabEdit.tsx @@ -118,6 +118,20 @@ function FormTabEdit(props: FormEditProps & { listFormCode: string }) { /> + + + + 0 // Eğer hiçbir buton eklenecek durumda değilse: çık @@ -374,6 +378,40 @@ const useListFormColumns = ({ 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) => { if (action.buttonPosition !== UiCommandButtonPositionTypeEnum.CommandColumn) return if (!checkPermission(action.authName)) return