diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 950a0e1..375c912 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -28,10 +28,7 @@ public class ListFormWizardAppService( IRepository repoPerm, IRepository repoPermGroup, IRepository repoMenu, - IIdentityRoleRepository roleRepository, - IIdentityUserRepository userRepository, IPermissionGrantRepository permissionGrantRepository, - ILookupNormalizer LookupNormalizer, IConfiguration configuration, LanguageTextAppService languageTextAppService ) : PlatformAppService(), IListFormWizardAppService @@ -44,10 +41,7 @@ public class ListFormWizardAppService( private readonly IRepository repoPerm = repoPerm; private readonly IRepository repoPermGroup = repoPermGroup; private readonly IRepository repoMenu = repoMenu; - private readonly IIdentityRoleRepository roleRepository = roleRepository; - private readonly IIdentityUserRepository userRepository = userRepository; private readonly IPermissionGrantRepository permissionGrantRepository = permissionGrantRepository; - private readonly ILookupNormalizer lookupNormalizer = LookupNormalizer; private readonly IConfiguration _configuration = configuration; private readonly LanguageTextAppService _languageTextAppService = languageTextAppService; private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage; @@ -101,21 +95,23 @@ public class ListFormWizardAppService( var permNote = existingPerms.FirstOrDefault(a => a.Name == WizardConsts.PermNote(wizardName)) ?? await repoPerm.InsertAsync(new PermissionDefinitionRecord(Guid.NewGuid(), groupName, WizardConsts.PermNote(wizardName), permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: false); - // Permission Grants - Bulk Insert - var adminUserName = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue; - var adminUser = await userRepository.FindByNormalizedUserNameAsync(lookupNormalizer.NormalizeName(adminUserName)); - var adminRole = await roleRepository.FindByNormalizedNameAsync(lookupNormalizer.NormalizeName(PlatformConsts.AbpIdentity.User.AdminRoleName)); + // Permission Grants - Bulk Insert (only missing ones) + var existingGrants = await permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName); + var existingGrantNames = existingGrants.Select(g => g.Name).ToHashSet(); - await permissionGrantRepository.InsertManyAsync( - [ - new PermissionGrant(Guid.NewGuid(), permRead.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permCreate.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permUpdate.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permDelete.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permExport.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permImport.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - new PermissionGrant(Guid.NewGuid(), permNote.Name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName), - ], autoSave: false); + var grantsToInsert = new[] + { + permRead.Name, permCreate.Name, permUpdate.Name, + permDelete.Name, permExport.Name, permImport.Name, permNote.Name + } + .Where(name => !existingGrantNames.Contains(name)) + .Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName)) + .ToList(); + + if (grantsToInsert.Count > 0) + { + await permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: false); + } //Menu Parent var menuQueryable = await repoMenu.GetQueryableAsync(); @@ -185,44 +181,68 @@ public class ListFormWizardAppService( }) .ToList(); - //ListForm - var listForm = await repoListForm.InsertAsync(new ListForm + //ListForm - varsa sil, yeniden ekle + var listFormQueryable = await repoListForm.GetQueryableAsync(); + var existingListForm = await AsyncExecuter.FirstOrDefaultAsync(listFormQueryable.Where(a => a.ListFormCode == input.ListFormCode)); + + var listFormFieldQueryable = await repoListFormField.GetQueryableAsync(); + var existingListFormFields = await AsyncExecuter.ToListAsync( + listFormFieldQueryable.Where(a => a.ListFormCode == input.ListFormCode)); + if (existingListFormFields.Count > 0) { - ListFormType = ListFormTypeEnum.List, - PageSize = 10, - ExportJson = WizardConsts.DefaultExportJson, - IsSubForm = false, - ShowNote = true, - LayoutJson = WizardConsts.DefaultLayoutJson(), - CultureName = LanguageCodes.En, - ListFormCode = input.ListFormCode, - Name = nameLangKey, - Title = titleLangKey, - Description = descLangKey, - DataSourceCode = input.DataSourceCode, - IsTenant = input.IsTenant, - IsBranch = input.IsBranch, - IsOrganizationUnit = input.IsOrganizationUnit, - SelectCommandType = input.SelectCommandType, - SelectCommand = input.SelectCommand, - KeyFieldName = input.KeyFieldName, - KeyFieldDbSourceType = input.KeyFieldDbSourceType, - DefaultFilter = WizardConsts.DefaultFilterJson, - SortMode = GridOptions.SortModeSingle, - FilterRowJson = WizardConsts.DefaultFilterRowJson, - HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson, - SearchPanelJson = WizardConsts.DefaultSearchPanelJson, - GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }), - SelectionJson = WizardConsts.DefaultSelectionSingleJson, - ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(), - PermissionJson = WizardConsts.DefaultPermissionJson(code), - DeleteCommand = WizardConsts.DefaultDeleteCommand(input.SelectCommand), - DeleteFieldsDefaultValueJson = WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType), - InsertFieldsDefaultValueJson = WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType), - PagerOptionJson = WizardConsts.DefaultPagerOptionJson, - EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail), - EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null, - }, autoSave: true); + await repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true); + } + + void ApplyListFormValues(ListForm lf) + { + lf.ListFormType = ListFormTypeEnum.List; + lf.PageSize = 10; + lf.ExportJson = WizardConsts.DefaultExportJson; + lf.IsSubForm = false; + lf.ShowNote = true; + lf.LayoutJson = WizardConsts.DefaultLayoutJson(); + lf.CultureName = LanguageCodes.En; + lf.ListFormCode = input.ListFormCode; + lf.Name = nameLangKey; + lf.Title = titleLangKey; + lf.Description = descLangKey; + lf.DataSourceCode = input.DataSourceCode; + lf.IsTenant = input.IsTenant; + lf.IsBranch = input.IsBranch; + lf.IsOrganizationUnit = input.IsOrganizationUnit; + lf.SelectCommandType = input.SelectCommandType; + lf.SelectCommand = input.SelectCommand; + lf.KeyFieldName = input.KeyFieldName; + lf.KeyFieldDbSourceType = input.KeyFieldDbSourceType; + lf.DefaultFilter = WizardConsts.DefaultFilterJson; + lf.SortMode = GridOptions.SortModeSingle; + lf.FilterRowJson = WizardConsts.DefaultFilterRowJson; + lf.HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson; + lf.SearchPanelJson = WizardConsts.DefaultSearchPanelJson; + lf.GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }); + lf.SelectionJson = WizardConsts.DefaultSelectionSingleJson; + lf.ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(); + lf.PermissionJson = WizardConsts.DefaultPermissionJson(code); + lf.DeleteCommand = WizardConsts.DefaultDeleteCommand(input.SelectCommand); + lf.DeleteFieldsDefaultValueJson = WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType); + lf.InsertFieldsDefaultValueJson = WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType); + lf.PagerOptionJson = WizardConsts.DefaultPagerOptionJson; + lf.EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail); + lf.EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null; + } + + ListForm listForm; + if (existingListForm != null) + { + ApplyListFormValues(existingListForm); + listForm = await repoListForm.UpdateAsync(existingListForm, autoSave: true); + } + else + { + var newListForm = new ListForm(); + ApplyListFormValues(newListForm); + listForm = await repoListForm.InsertAsync(newListForm, autoSave: true); + } // ListFormField - each item in each group becomes a visible field record var fieldOrder = 0; diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index d734959..bb94657 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -203,6 +203,11 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep HeaderFilterJson = DefaultHeaderFilterJson, SearchPanelJson = DefaultSearchPanelJson, GroupPanelJson = DefaultGroupPanelJson, + SelectionJson = JsonSerializer.Serialize(new SelectionDto + { + Mode = GridOptions.SelectionAllModeAllPages, + AllowSelectAll = true + }), ColumnOptionJson = DefaultColumnOptionJson(), PermissionJson = DefaultPermissionJson(AbpIdentity.Permissions.Create, listFormName, AbpIdentity.Permissions.Update, AbpIdentity.Permissions.Delete, AbpIdentity.Permissions.Export, AbpIdentity.Permissions.Import, AbpIdentity.Permissions.Note), PagerOptionJson = DefaultPagerOptionJson, diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql index 9e33daf..bf05359 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql @@ -49,4 +49,28 @@ WITH INIT;' EXEC sp_executesql @SQL; END -GO \ No newline at end of file +GO + +IF OBJECT_ID(N'[dbo].[Adm_T_Deparment]', 'U') IS NULL +BEGIN + CREATE TABLE [dbo].[Adm_T_Deparment] + ( + [Id] uniqueidentifier NOT NULL DEFAULT NEWID(), + [CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(), + [CreatorId] uniqueidentifier NULL, + [LastModificationTime] datetime2 NULL, + [LastModifierId] uniqueidentifier NULL, + [IsDeleted] bit NOT NULL DEFAULT 0, + [DeletionTime] datetime2 NULL, + [DeleterId] uniqueidentifier NULL, + [TenantId] uniqueidentifier NULL, + [BranchId] uniqueidentifier NOT NULL, + [Name] nvarchar(64) NOT NULL, + [ParentId] uniqueidentifier NULL, + CONSTRAINT [PK_Adm_T_Deparment] PRIMARY KEY CLUSTERED + ( + [Id] ASC + )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] + ) ON [PRIMARY] +END +GO diff --git a/ui/src/views/admin/listForm/Wizard.tsx b/ui/src/views/admin/listForm/Wizard.tsx index e4f6540..e404fa8 100644 --- a/ui/src/views/admin/listForm/Wizard.tsx +++ b/ui/src/views/admin/listForm/Wizard.tsx @@ -166,6 +166,10 @@ const Wizard = () => { const selectableColumns = cols.filter((c) => !isAuditColumn(c.columnName)) setSelectedColumns(new Set(selectableColumns.map((c) => c.columnName))) setEditingGroups([]) + // Auto-check isTenant / isBranch based on column presence + const colNames = new Set(cols.map((c) => c.columnName.toLowerCase())) + formikRef.current?.setFieldValue('isTenant', colNames.has('tenantid')) + formikRef.current?.setFieldValue('isBranch', colNames.has('branchid')) // Auto-select first column as key field if (cols.length > 0) { const first = cols[0] diff --git a/ui/src/views/developerKit/SqlQueryManager.tsx b/ui/src/views/developerKit/SqlQueryManager.tsx index b86db9e..dfb4058 100644 --- a/ui/src/views/developerKit/SqlQueryManager.tsx +++ b/ui/src/views/developerKit/SqlQueryManager.tsx @@ -155,7 +155,7 @@ END; ( SELECT c.column_id, - ' ' + QUOTENAME(c.name) + ' ' + + ' ' + QUOTENAME(c.name) + ' ' + CASE WHEN t.name IN ('varchar', 'char', 'varbinary', 'binary') THEN t.name + '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR(10)) END + ')' @@ -183,29 +183,30 @@ END; pk AS ( SELECT - ' CONSTRAINT ' + QUOTENAME(k.name) + ' PRIMARY KEY ' + + ' CONSTRAINT ' + QUOTENAME(k.name) + ' PRIMARY KEY ' + CASE WHEN i.type = 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END + - ' (' + - STUFF( - ( - SELECT ', ' + QUOTENAME(c.name) + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END - FROM sys.index_columns ic - INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id - WHERE ic.object_id = i.object_id - AND ic.index_id = i.index_id - AND ic.is_included_column = 0 - ORDER BY ic.key_ordinal - FOR XML PATH(''), TYPE - ).value('.', 'NVARCHAR(MAX)') - ,1,2,'') + ')' AS line + CHAR(13) + CHAR(10) + ' (' + CHAR(13) + CHAR(10) + + ( + SELECT ' ' + QUOTENAME(c.name) + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END + CHAR(13) + CHAR(10) + FROM sys.index_columns ic + INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id + WHERE ic.object_id = i.object_id + AND ic.index_id = i.index_id + AND ic.is_included_column = 0 + ORDER BY ic.key_ordinal + FOR XML PATH(''), TYPE + ).value('.', 'NVARCHAR(MAX)') + + ' )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]' AS line FROM sys.key_constraints k INNER JOIN sys.indexes i ON k.parent_object_id = i.object_id AND k.unique_index_id = i.index_id WHERE k.parent_object_id = @ObjectId AND k.type = 'PK' ) SELECT - 'CREATE TABLE ${fullName}' + CHAR(13) + CHAR(10) + - '(' + CHAR(13) + CHAR(10) + + 'IF OBJECT_ID(N''${escapedFullName}'', ''U'') IS NULL' + CHAR(13) + CHAR(10) + + 'BEGIN' + CHAR(13) + CHAR(10) + + ' CREATE TABLE ${fullName}' + CHAR(13) + CHAR(10) + + ' (' + CHAR(13) + CHAR(10) + STUFF( ( SELECT ',' + CHAR(13) + CHAR(10) + line @@ -220,7 +221,7 @@ SELECT FROM pk ), '' - ) + CHAR(13) + CHAR(10) + ');' AS Script;` + ) + CHAR(13) + CHAR(10) + ' ) ON [PRIMARY]' + CHAR(13) + CHAR(10) + 'END' + CHAR(13) + CHAR(10) + 'GO' AS Script;` } const getTableCreateScript = async (schemaName: string, tableName: string): Promise => { diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index aafb9f7..9c7e627 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -245,7 +245,7 @@ function colToSqlLine(col: ColumnDefinition, addComma = true): string { } const nullPart = col.isNullable ? 'NULL' : 'NOT NULL' const defaultPart = col.defaultValue ? ` DEFAULT ${col.defaultValue}` : '' - return ` [${col.columnName}] ${typeSql} ${nullPart}${defaultPart}${addComma ? ',' : ''}` + return ` [${col.columnName}] ${typeSql} ${nullPart}${defaultPart}${addComma ? ',' : ''}` } function generateCreateTableSql( @@ -256,10 +256,57 @@ function generateCreateTableSql( ): string { const tableName = settings.tableName || 'NewTable' const fullTableName = `[dbo].[${tableName}]` + const escapedFullTableName = fullTableName.replace(/'/g, "''") const userCols = columns.filter((c) => c.columnName.trim()) - const allBodyCols = userCols - const bodyLines = allBodyCols.map((c, i) => colToSqlLine(c, i < allBodyCols.length - 1)) + const bodyLines = userCols.map((c) => colToSqlLine(c, false)) + + // Inline PRIMARY KEY constraint inside CREATE TABLE + const pkIndex = indexes.find((idx) => idx.indexType === 'PrimaryKey' && idx.columns.length > 0) + const pkConstraintLines: string[] = [] + if (pkIndex) { + const clustered = pkIndex.isClustered ? 'CLUSTERED' : 'NONCLUSTERED' + const colsSql = pkIndex.columns.map((c) => ` [${c.columnName}] ${c.order}`).join(',\n') + pkConstraintLines.push( + ` CONSTRAINT [${pkIndex.indexName}] PRIMARY KEY ${clustered}`, + ` (`, + colsSql, + ` )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]`, + ) + } + + // Build all column + pk lines with commas + const allInnerLines: string[] = [] + for (let i = 0; i < bodyLines.length; i++) { + allInnerLines.push(bodyLines[i] + ',') + } + if (pkConstraintLines.length > 0) { + for (let i = 0; i < pkConstraintLines.length; i++) { + allInnerLines.push(pkConstraintLines[i]) + } + } else if (allInnerLines.length > 0) { + // Remove trailing comma from last column if no PK + allInnerLines[allInnerLines.length - 1] = allInnerLines[allInnerLines.length - 1].replace(/,$/, '') + } + + // Non-PK index lines (UNIQUE, regular INDEX) — outside CREATE TABLE + const extraIndexLines: string[] = [] + for (const idx of indexes) { + if (idx.columns.length === 0 || idx.indexType === 'PrimaryKey') continue + const clustered = idx.isClustered ? 'CLUSTERED' : 'NONCLUSTERED' + const colsSql = idx.columns.map((c) => `[${c.columnName}] ${c.order}`).join(', ') + extraIndexLines.push('') + if (idx.indexType === 'UniqueKey') { + extraIndexLines.push(`-- 🔒 Unique Key: [${idx.indexName}]`) + extraIndexLines.push(`ALTER TABLE ${fullTableName}`) + extraIndexLines.push(` ADD CONSTRAINT [${idx.indexName}] UNIQUE ${clustered} (${colsSql});`) + } else { + extraIndexLines.push(`-- 📋 Index: [${idx.indexName}]`) + extraIndexLines.push( + `CREATE ${idx.isClustered ? 'CLUSTERED ' : ''}INDEX [${idx.indexName}] ON ${fullTableName} (${colsSql});`, + ) + } + } // FK constraint lines const fkLines: string[] = [] @@ -291,42 +338,18 @@ function generateCreateTableSql( fkLines.push(` ON UPDATE ${cascadeUpdate};`) } - // Index / Key SQL lines - const indexLines: string[] = [] - for (const idx of indexes) { - if (idx.columns.length === 0) continue - const clustered = idx.isClustered ? 'CLUSTERED' : 'NONCLUSTERED' - const colsSql = idx.columns.map((c) => `[${c.columnName}] ${c.order}`).join(', ') - indexLines.push('') - if (idx.indexType === 'PrimaryKey') { - indexLines.push(`-- 🔑 Primary Key: [${idx.indexName}]`) - indexLines.push(`ALTER TABLE ${fullTableName}`) - indexLines.push(` ADD CONSTRAINT [${idx.indexName}] PRIMARY KEY ${clustered} (${colsSql});`) - } else if (idx.indexType === 'UniqueKey') { - indexLines.push(`-- 🔒 Unique Key: [${idx.indexName}]`) - indexLines.push(`ALTER TABLE ${fullTableName}`) - indexLines.push(` ADD CONSTRAINT [${idx.indexName}] UNIQUE ${clustered} (${colsSql});`) - } else { - indexLines.push(`-- 📋 Index: [${idx.indexName}]`) - indexLines.push( - `CREATE ${idx.isClustered ? 'CLUSTERED ' : ''}INDEX [${idx.indexName}] ON ${fullTableName} (${colsSql});`, - ) - } - } - const lines: string[] = [ - `/* ── Table: ${fullTableName} ── */`, - ...(settings.entityName ? [`/* Entity Name: ${settings.entityName} */`] : []), - '', - `CREATE TABLE ${fullTableName}`, - `(`, - ...bodyLines, - `);`, - ...indexLines, - ...(fkLines.length > 0 ? ['/* Foreign Key Constraints */'] : []), + `IF OBJECT_ID(N'${escapedFullTableName}', 'U') IS NULL`, + `BEGIN`, + ` CREATE TABLE ${fullTableName}`, + ` (`, + ...allInnerLines, + ` ) ON [PRIMARY]`, + `END`, + `GO`, + ...extraIndexLines, + ...(fkLines.length > 0 ? ['', '/* Foreign Key Constraints */'] : []), ...fkLines, - '', - `/* Verify: SELECT TOP 10 * FROM ${fullTableName}; */`, ] return lines.join('\n')