diff --git a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/CustomEntityDto.cs b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/CustomEntityDto.cs index 021f3a7c..8bf67b8e 100644 --- a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/CustomEntityDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/CustomEntityDto.cs @@ -6,6 +6,7 @@ namespace Kurs.Platform.DeveloperKit; public class CustomEntityDto : FullAuditedEntityDto { + public string Menu { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string TableName { get; set; } = string.Empty; @@ -22,6 +23,7 @@ public class CustomEntityDto : FullAuditedEntityDto public class CreateUpdateCustomEntityDto { + public string Menu { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string TableName { get; set; } = string.Empty; diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/CustomEntityAppService.cs b/api/src/Kurs.Platform.Application/DeveloperKit/CustomEntityAppService.cs index 5a43df4b..c4a7ac40 100644 --- a/api/src/Kurs.Platform.Application/DeveloperKit/CustomEntityAppService.cs +++ b/api/src/Kurs.Platform.Application/DeveloperKit/CustomEntityAppService.cs @@ -118,6 +118,7 @@ public class CustomEntityAppService : CrudAppService< entity.MigrationId = null; } + entity.Menu = input.Menu; entity.Name = input.Name; entity.DisplayName = input.DisplayName; entity.TableName = input.TableName; @@ -191,6 +192,7 @@ public class CustomEntityAppService : CrudAppService< // Entity oluştur var entity = new CustomEntity { + Menu = input.Menu, Name = input.Name, DisplayName = input.DisplayName, TableName = input.TableName, diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json index f6ed100e..992075f8 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json @@ -10471,6 +10471,12 @@ "en": "Entity Name", "tr": "Varlık Adı" }, + { + "resourceName": "Platform", + "key": "App.DeveloperKit.EntityEditor.MenuName", + "en": "Menu Name", + "tr": "Menü Adı" + }, { "resourceName": "Platform", "key": "App.DeveloperKit.EntityEditor.DisplayName", diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json index 120d0251..45853c9a 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json @@ -1419,7 +1419,7 @@ "ParentCode": "App.Administration", "Code": "Abp.Identity", "DisplayName": "Abp.Identity", - "Order": 2, + "Order": 3, "Url": null, "Icon": "FcConferenceCall", "RequiredPermissionName": null, @@ -1519,7 +1519,7 @@ "ParentCode": "App.Administration", "Code": "App.DeveloperKit", "DisplayName": "App.DeveloperKit", - "Order": 6, + "Order": 8, "Url": null, "Icon": "FcAndroidOs", "RequiredPermissionName": null, @@ -1599,7 +1599,7 @@ "ParentCode": "App.Administration", "Code": "App.Reports.Management", "DisplayName": "App.Reports.Management", - "Order": 7, + "Order": 6, "Url": null, "Icon": "FcDocument", "RequiredPermissionName": null, @@ -1629,7 +1629,7 @@ "ParentCode": "App.Administration", "Code": "App.Public", "DisplayName": "App.Public", - "Order": 8, + "Order": 7, "Url": null, "Icon": "FcGenealogy", "RequiredPermissionName": null, @@ -1739,7 +1739,7 @@ "ParentCode": "App.Administration", "Code": "App.Definitions", "DisplayName": "App.Definitions", - "Order": 9, + "Order": 2, "Url": null, "Icon": "FcFilingCabinet", "RequiredPermissionName": null, @@ -1749,7 +1749,7 @@ "ParentCode": "App.Administration", "Code": "App.Files", "DisplayName": "App.Files", - "Order": 5, + "Order": 4, "Url": "/admin/files", "Icon": "FcFolder", "RequiredPermissionName": "App.Files", @@ -1825,7 +1825,6 @@ "RequiredPermissionName": "App.Definitions.District", "IsDisabled": false }, - { "ParentCode": "App.Definitions", "Code": "App.Definitions.WorkHour", diff --git a/api/src/Kurs.Platform.DbMigrator/appsettings.Dev.json b/api/src/Kurs.Platform.DbMigrator/appsettings.Dev.json index 59cf69df..9a144145 100644 --- a/api/src/Kurs.Platform.DbMigrator/appsettings.Dev.json +++ b/api/src/Kurs.Platform.DbMigrator/appsettings.Dev.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" }, "Redis": { diff --git a/api/src/Kurs.Platform.DbMigrator/appsettings.Production.json b/api/src/Kurs.Platform.DbMigrator/appsettings.Production.json index 2505fc40..212d8083 100644 --- a/api/src/Kurs.Platform.DbMigrator/appsettings.Production.json +++ b/api/src/Kurs.Platform.DbMigrator/appsettings.Production.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" }, "Redis": { diff --git a/api/src/Kurs.Platform.DbMigrator/appsettings.json b/api/src/Kurs.Platform.DbMigrator/appsettings.json index 9aff8e97..e2ee705e 100644 --- a/api/src/Kurs.Platform.DbMigrator/appsettings.json +++ b/api/src/Kurs.Platform.DbMigrator/appsettings.json @@ -1,7 +1,7 @@ { "Seed": false, "ConnectionStrings": { - "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;" }, "Redis": { diff --git a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs index e0c85770..355dcf04 100644 --- a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs @@ -283,8 +283,6 @@ public static class PlatformConsts public static class AppCodes { public const string Home = Prefix.App + ".Home"; - - //Saas public const string Saas = Prefix.App + ".Saas"; public const string Branches = Prefix.App + ".Branches"; public static class Settings @@ -328,7 +326,7 @@ public static class PlatformConsts public static class DynamicServices { public const string DynamicService = Default + ".DynamicServices"; - + public const string Create = DynamicService + ".Create"; public const string Edit = DynamicService + ".Edit"; public const string Delete = DynamicService + ".Delete"; @@ -338,14 +336,10 @@ public static class PlatformConsts public const string ViewCode = DynamicService + ".ViewCode"; } } - public const string Blog = Prefix.App + ".Blog"; public const string Forum = Prefix.App + ".Forum"; - - //Administration public const string Administration = Prefix.App + ".Administration"; public const string Setting = Prefix.App + ".Setting"; - public static class IdentityManagement { public const string ClaimTypes = Prefix.App + ".ClaimType"; @@ -393,8 +387,6 @@ public static class PlatformConsts public const string Class = Default + ".Class"; public const string Level = Default + ".Level"; } - - //Hr public static class Hr { public const string Default = Prefix.App + ".Hr"; diff --git a/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/CustomEntity.cs b/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/CustomEntity.cs index e9f9821b..99c8da3d 100644 --- a/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/CustomEntity.cs +++ b/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/CustomEntity.cs @@ -9,6 +9,7 @@ public class CustomEntity : FullAuditedEntity, IMultiTenant { public virtual Guid? TenantId { get; protected set; } + public string Menu { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string TableName { get; set; } = string.Empty; diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/DapperTransactionApi.cs b/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/DapperTransactionApi.cs index 5487bb39..390d946f 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/DapperTransactionApi.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/DapperTransactionApi.cs @@ -1,4 +1,5 @@ -using System.Data.Common; +using System.Data; +using System.Data.Common; using System.Threading; using System.Threading.Tasks; using Volo.Abp.Threading; @@ -9,6 +10,7 @@ namespace Kurs.Platform.Domain.DynamicData; public class DapperTransactionApi : ITransactionApi, ISupportsRollback { public DbTransaction DbTransaction { get; } + private bool _isCompleted; protected ICancellationTokenProvider CancellationTokenProvider { get; } @@ -18,20 +20,82 @@ public class DapperTransactionApi : ITransactionApi, ISupportsRollback { DbTransaction = dbTransaction; CancellationTokenProvider = cancellationTokenProvider; + _isCompleted = false; } public async Task CommitAsync(CancellationToken cancellationToken = default) { - await DbTransaction.CommitAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); + // Check if transaction is still active + if (_isCompleted) + { + return; // Already completed, nothing to do + } + + // Check if connection is still open and transaction is not disposed + if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open) + { + _isCompleted = true; + return; // Connection closed or transaction disposed + } + + try + { + await DbTransaction.CommitAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); + _isCompleted = true; + } + catch (System.InvalidOperationException) + { + // Transaction already completed or disposed + _isCompleted = true; + } } public void Dispose() { - DbTransaction.Dispose(); + if (!_isCompleted && DbTransaction?.Connection != null) + { + try + { + // If not completed, rollback before disposing + DbTransaction?.Rollback(); + } + catch + { + // Ignore rollback errors during disposal + } + finally + { + _isCompleted = true; + } + } + + DbTransaction?.Dispose(); } public async Task RollbackAsync(CancellationToken cancellationToken) { - await DbTransaction.RollbackAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); + // Check if transaction is still active + if (_isCompleted) + { + return; // Already completed, nothing to do + } + + // Check if connection is still open and transaction is not disposed + if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open) + { + _isCompleted = true; + return; // Connection closed or transaction disposed + } + + try + { + await DbTransaction.RollbackAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); + _isCompleted = true; + } + catch (System.InvalidOperationException) + { + // Transaction already completed or disposed + _isCompleted = true; + } } } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs b/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs index 58c039d3..673d20e6 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs @@ -17,8 +17,10 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency { private readonly IUnitOfWorkManager unitOfWorkManager; private readonly ICancellationTokenProvider cancellationTokenProvider; - private Dictionary transactions; - private Dictionary connections; + private readonly Dictionary transactions; + private readonly Dictionary connections; + private readonly HashSet registeredTransactions; // Track registered transactions + private readonly object _lock = new object(); public bool IsDisposed { get; private set; } public MsDynamicDataRepository( @@ -27,41 +29,159 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency { this.unitOfWorkManager = unitOfWorkManager; this.cancellationTokenProvider = cancellationTokenProvider; - transactions = []; - connections = []; + transactions = new Dictionary(); + connections = new Dictionary(); + registeredTransactions = new HashSet(); } private async Task GetOrCreateTransactionAsync(SqlConnection con) { var key = $"Dapper_{con.ConnectionString}"; - var transaction = transactions.GetOrDefault(key); - if (transaction == null || transaction.Connection == null) + + lock (_lock) { - transaction = await con.BeginTransactionAsync(); - unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(transaction, cancellationTokenProvider)); - transactions[key] = transaction; + // Check if we have a valid transaction for this connection + if (transactions.TryGetValue(key, out var transaction)) + { + // Validate transaction is still usable + if (transaction?.Connection != null && + transaction.Connection == con && + transaction.Connection.State == ConnectionState.Open) + { + return transaction; + } + + // Invalid transaction, remove it + try { transaction?.Dispose(); } catch { } + transactions.Remove(key); + } + } + + // Ensure connection is open + if (con.State != ConnectionState.Open) + { + await con.OpenAsync(); + } + + // Create new transaction + var newTransaction = await con.BeginTransactionAsync(); + bool shouldRegister = false; + + lock (_lock) + { + transactions[key] = newTransaction; + + // Only register with UnitOfWork once per transaction key + if (!registeredTransactions.Contains(key)) + { + registeredTransactions.Add(key); + shouldRegister = true; + } + } + + // Register with UnitOfWork if available and not already registered + if (shouldRegister && unitOfWorkManager.Current != null) + { + unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(newTransaction, cancellationTokenProvider)); + unitOfWorkManager.Current.OnCompleted(() => { - transaction = null; + lock (_lock) + { + transactions.Remove(key); + registeredTransactions.Remove(key); + } return Task.CompletedTask; }); } - return transaction; + + return newTransaction; } private async Task GetOrCreateConnectionAsync(string cs) { + SqlConnection connection; var key = $"Dapper_{cs}"; - var connection = connections.GetOrDefault(key); - if (connection == null) + + lock (_lock) { - connection = new SqlConnection(cs); - connections[key] = connection; // Use indexer instead of Add to avoid duplicate key exception + // Check if we have an existing connection + if (connections.TryGetValue(key, out connection)) + { + // Connection exists, check its state + if (connection.State == ConnectionState.Open) + { + return connection; + } + + // Connection is not open, will handle outside lock + } + else + { + // Create new connection + connection = new SqlConnection(cs); + connections[key] = connection; + } } - if (connection.State != ConnectionState.Open) + + // Handle connection state outside of lock + try { + if (connection.State == ConnectionState.Broken) + { + connection.Close(); + } + + if (connection.State == ConnectionState.Closed) + { + await connection.OpenAsync(); + } + } + catch + { + // If connection failed, create a new one + lock (_lock) + { + connections.Remove(key); + } + + connection = new SqlConnection(cs); + lock (_lock) + { + connections[key] = connection; + } await connection.OpenAsync(); } + + // Register cleanup on UnitOfWork completion (only once) + if (unitOfWorkManager.Current != null) + { + unitOfWorkManager.Current.OnCompleted(async () => + { + SqlConnection conn = null; + lock (_lock) + { + if (connections.TryGetValue(key, out conn)) + { + connections.Remove(key); + } + } + + if (conn != null) + { + try + { + if (conn.State != ConnectionState.Closed) + { + await conn.CloseAsync(); + } + conn.Dispose(); + } + catch { } + } + }); + } + return connection; } @@ -135,16 +255,42 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency { if (disposing) { - foreach (var connection in connections.Values) + lock (_lock) { - if (connection != null) + // Dispose transactions first + foreach (var transaction in transactions.Values) { - if (connection.State == ConnectionState.Open) + try { - connection.Close(); + transaction?.Dispose(); + } + catch + { + // Ignore disposal errors } - connection.Dispose(); } + transactions.Clear(); + + // Then dispose connections + foreach (var connection in connections.Values) + { + try + { + if (connection != null) + { + if (connection.State == ConnectionState.Open) + { + connection.Close(); + } + connection.Dispose(); + } + } + catch + { + // Ignore disposal errors + } + } + connections.Clear(); } } IsDisposed = true; diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.Designer.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.Designer.cs index e94445fa..42b51a3c 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20251105142841_Initial")] + [Migration("20251106171552_Initial")] partial class Initial { /// @@ -2951,6 +2951,9 @@ namespace Kurs.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("Menu") + .HasColumnType("nvarchar(max)"); + b.Property("MigrationId") .HasColumnType("uniqueidentifier"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.cs index 405f7fe7..fd8ce1cf 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251105142841_Initial.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251106171552_Initial.cs @@ -2132,6 +2132,7 @@ namespace Kurs.Platform.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), TenantId = table.Column(type: "uniqueidentifier", nullable: true), + Menu = table.Column(type: "nvarchar(max)", nullable: true), Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), DisplayName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), TableName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index dabbdfca..257ff9e0 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -2948,6 +2948,9 @@ namespace Kurs.Platform.Migrations .HasColumnType("uniqueidentifier") .HasColumnName("LastModifierId"); + b.Property("Menu") + .HasColumnType("nvarchar(max)"); + b.Property("MigrationId") .HasColumnType("uniqueidentifier"); diff --git a/api/src/Kurs.Platform.HttpApi.Host/appsettings.Dev.json b/api/src/Kurs.Platform.HttpApi.Host/appsettings.Dev.json index 9946e123..b776fd35 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/appsettings.Dev.json +++ b/api/src/Kurs.Platform.HttpApi.Host/appsettings.Dev.json @@ -9,7 +9,7 @@ "BaseDomain": "sozsoft.com" }, "ConnectionStrings": { - "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" }, "Redis": { diff --git a/api/src/Kurs.Platform.HttpApi.Host/appsettings.Production.json b/api/src/Kurs.Platform.HttpApi.Host/appsettings.Production.json index 37e7649e..3a940df4 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/appsettings.Production.json +++ b/api/src/Kurs.Platform.HttpApi.Host/appsettings.Production.json @@ -9,7 +9,7 @@ "BaseDomain": "sozsoft.com" }, "ConnectionStrings": { - "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" }, "Redis": { diff --git a/api/src/Kurs.Platform.HttpApi.Host/appsettings.json b/api/src/Kurs.Platform.HttpApi.Host/appsettings.json index 0aa2fb50..7d3b9938 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/appsettings.json +++ b/api/src/Kurs.Platform.HttpApi.Host/appsettings.json @@ -9,7 +9,7 @@ "Version": "1.0.1" }, "ConnectionStrings": { - "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", + "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;" }, "Redis": { diff --git a/ui/src/components/developerKit/EntityEditor.tsx b/ui/src/components/developerKit/EntityEditor.tsx index 96b1de3a..ac49f567 100644 --- a/ui/src/components/developerKit/EntityEditor.tsx +++ b/ui/src/components/developerKit/EntityEditor.tsx @@ -18,9 +18,11 @@ import { Formik, Form, Field, FieldProps, FieldArray } from 'formik' import * as Yup from 'yup' import { FormItem, Input, Select, Checkbox, FormContainer, Button } from '@/components/ui' import { SelectBoxOption } from '@/shared/types' +import { MenuPrefixEnum, menuPrefixValues, toPrefix } from '@/types/menu' // Validation schema const validationSchema = Yup.object({ + menu: Yup.string().required('Menu is required'), name: Yup.string().required('Entity name is required'), displayName: Yup.string().required('Display name is required'), tableName: Yup.string().required('Table name is required'), @@ -55,6 +57,7 @@ const EntityEditor: React.FC = () => { // Initial values for Formik const [initialValues, setInitialValues] = useState({ + menu: '', name: '', displayName: '', tableName: '', @@ -87,6 +90,7 @@ const EntityEditor: React.FC = () => { .map((f, idx) => ({ ...f, displayOrder: f.displayOrder ?? idx + 1 })) setInitialValues({ + menu: entity.menu, name: entity.name, displayName: entity.displayName, tableName: entity.tableName, @@ -121,6 +125,7 @@ const EntityEditor: React.FC = () => { }) const entityData = { + menu: values.menu.trim(), name: values.name.trim(), displayName: values.displayName.trim(), tableName: values.tableName.trim(), @@ -146,6 +151,18 @@ const EntityEditor: React.FC = () => { } } + // Helper function to generate table name + const generateTableName = (menuValue: string, entityName: string): string => { + if (!menuValue || !entityName) return '' + try { + const menuPrefix = toPrefix(menuValue as MenuPrefixEnum) + return `${menuPrefix}_D_${entityName}` + } catch (error) { + console.error('Error generating table name:', error) + return '' + } + } + const fieldTypes = [ { value: 'string', label: 'String' }, { value: 'number', label: 'Number' }, @@ -236,13 +253,15 @@ const EntityEditor: React.FC = () => { Migration Applied - Changes Will Require New Migration

- This entity has been migrated to the database. Any structural changes you make will reset the migration status to "pending", and you'll need to generate and apply a new migration to update the database schema. + This entity has been migrated to the database. Any structural changes you make + will reset the migration status to "pending", and you'll need to generate and + apply a new migration to update the database schema.

)} - + {/* Basic Entity Information */}
@@ -254,6 +273,33 @@ const EntityEditor: React.FC = () => {
+ + + {({ field, form }: FieldProps) => ( +