diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs
new file mode 100644
index 0000000..b182da1
--- /dev/null
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Threading.Tasks;
+using Hangfire.SqlServer;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Sozsoft.Platform.Data;
+using Volo.Abp.DependencyInjection;
+using static Sozsoft.Settings.SettingsConsts;
+
+namespace Sozsoft.Platform.DbMigrator;
+
+///
+/// DbMigrator çalışırken HangFire şemasını oluşturur.
+/// EF Core migration'larından sonra (EntityFrameworkCorePlatformDbSchemaMigrator),
+/// SqlDataSeeder'dan önce çalışarak HangFire tablolarının (HangFire.Job vb.) mevcut
+/// olmasını sağlar. Bu sayede SqlData klasöründeki HangFire view'ları ilk çalışmada oluşturulabilir.
+/// [ExposeServices] ABP'nin naming convention'ından bağımsız olarak IPlatformDbSchemaMigrator
+/// listesine dahil edilmesini sağlar.
+///
+[ExposeServices(typeof(IPlatformDbSchemaMigrator))]
+public class HangfireDbSchemaMigrator : IPlatformDbSchemaMigrator, ITransientDependency
+{
+ private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
+
+ public HangfireDbSchemaMigrator(
+ IConfiguration configuration,
+ ILogger logger)
+ {
+ _configuration = configuration;
+ _logger = logger;
+ }
+
+ public async Task MigrateAsync()
+ {
+ var connectionString = _configuration.GetConnectionString(DefaultDatabaseProvider);
+ if (string.IsNullOrWhiteSpace(connectionString))
+ {
+ _logger.LogWarning("HangFire schema migration skipped: connection string '{Key}' not found.", DefaultDatabaseProvider);
+ return;
+ }
+
+ try
+ {
+ await using var connection = new SqlConnection(connectionString);
+ await connection.OpenAsync();
+
+ // HangFire tablolarını oluştur (idempotent — mevcutsa atlar).
+ SqlServerObjectsInstaller.Install(connection, "HangFire", enableHeavyMigrations: true);
+
+ _logger.LogInformation("HangFire schema initialized successfully.");
+ }
+ catch (SqlException sqlEx) when (sqlEx.Number == 4060)
+ {
+ // Veritabanı henüz oluşturulmamış (ilk migration dışında tenant DB senaryosu)
+ _logger.LogWarning("HangFire schema skipped: database does not exist yet (error 4060).");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "HangFire schema initialization FAILED.");
+ throw;
+ }
+ }
+}
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
index 3469e40..e49e3b1 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
@@ -1830,6 +1830,20 @@
"en": "Background Workers",
"tr": "Arkaplan İşleri"
},
+
+ {
+ "resourceName": "Platform",
+ "key": "App.BackgroundWorkers.Definitions",
+ "en": "Background Worker Definitions",
+ "tr": "Arkaplan İşleri Tanımları"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.BackgroundWorkers.JobFlow",
+ "en": "Background Worker Job Flow",
+ "tr": "Arkaplan İşleri İş Akışı"
+ },
+
{
"resourceName": "Platform",
"key": "App.Notifications",
@@ -14492,6 +14506,18 @@
"en": "Id",
"tr": "Kimlik"
},
+ {
+ "resourceName": "Platform",
+ "key": "App.Listform.ListformField.JobName",
+ "en": "Job Name",
+ "tr": "İş Adı"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.Listform.ListformField.ExecutionDate",
+ "en": "Execution Date",
+ "tr": "İşlem Tarihi"
+ },
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.SessionId",
@@ -16202,6 +16228,24 @@
"en": "Status",
"tr": "Durum"
},
+ {
+ "resourceName": "Platform",
+ "key": "App.Listform.ListformField.FailureReason",
+ "en": "Failure Reason",
+ "tr": "Hata Nedeni"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.Listform.ListformField.ExceptionType",
+ "en": "Exception Type",
+ "tr": "Hata Türü"
+ },
+ {
+ "resourceName": "Platform",
+ "key": "App.Listform.ListformField.ExceptionDetails",
+ "en": "Exception Details",
+ "tr": "Hata Detayları"
+ },
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.StatusId",
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs
index 34820f0..a6f3518 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs
@@ -4720,7 +4720,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region Background Worker
- listFormName = AppCodes.BackgroundWorkers;
+ listFormName = AppCodes.BackgroundWorkers.Definitions;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@@ -4957,6 +4957,181 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}
#endregion
+ #region Background Worker Job Flow
+ listFormName = AppCodes.BackgroundWorkers.JobFlow;
+ if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
+ {
+ var listForm = await _listFormRepository.InsertAsync(
+ new ListForm()
+ {
+ ListFormType = ListFormTypeEnum.List,
+ PageSize = 10,
+ ExportJson = DefaultExportJson,
+ IsSubForm = false,
+ ShowNote = true,
+ LayoutJson = DefaultLayoutJson(),
+ CultureName = LanguageCodes.En,
+ ListFormCode = listFormName,
+ Name = listFormName,
+ Title = listFormName,
+ DataSourceCode = SeedConsts.DataSources.DefaultCode,
+ IsTenant = false,
+ IsBranch = false,
+ IsOrganizationUnit = false,
+ Description = listFormName,
+ SelectCommandType = SelectCommandTypeEnum.View,
+ SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)),
+ KeyFieldName = "Id",
+ KeyFieldDbSourceType = DbType.Guid,
+ SortMode = GridOptions.SortModeSingle,
+ FilterRowJson = DefaultFilterRowJson,
+ HeaderFilterJson = DefaultHeaderFilterJson,
+ SearchPanelJson = DefaultSearchPanelJson,
+ GroupPanelJson = DefaultGroupPanelJson,
+ SelectionJson = DefaultSelectionSingleJson,
+ ColumnOptionJson = DefaultColumnOptionJson(),
+ PermissionJson = DefaultPermissionJson(listFormName),
+ PagerOptionJson = DefaultPagerOptionJson
+ }
+ );
+
+ #region Background Worker Job Flow Fields
+ await _listFormFieldRepository.InsertManyAsync([
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.Guid,
+ FieldName = "Id",
+ CaptionName = "App.Listform.ListformField.Id",
+ Width = 0,
+ ListOrderNo = 1,
+ Visible = false,
+ IsActive = true,
+
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.String,
+ FieldName = "JobName",
+ CaptionName = "App.Listform.ListformField.JobName",
+ Width = 0,
+ ListOrderNo = 2,
+ Visible = true,
+ IsActive = true,
+
+ SortIndex = 1,
+ SortDirection = GridColumnOptions.SortOrderAsc,
+ AllowSearch = true,
+ ValidationRuleJson = DefaultValidationRuleRequiredJson,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.DateTime,
+ FieldName = "ExecutionDate",
+ CaptionName = "App.Listform.ListformField.ExecutionDate",
+ Width = 0,
+ ListOrderNo = 3,
+ Visible = true,
+ IsActive = true,
+
+ AllowSearch = true,
+ ValidationRuleJson = DefaultValidationRuleRequiredJson,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.Int32,
+ FieldName = "Duration",
+ CaptionName = "App.Listform.ListformField.Duration",
+ Width = 0,
+ ListOrderNo = 4,
+ Visible = true,
+ IsActive = true,
+
+ ValidationRuleJson = DefaultValidationRuleRequiredJson,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.String,
+ FieldName = "Status",
+ CaptionName = "App.Listform.ListformField.Status",
+ Width = 0,
+ ListOrderNo = 5,
+ Visible = true,
+ IsActive = true,
+
+ AllowSearch = true,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.String,
+ FieldName = "FailureReason",
+ CaptionName = "App.Listform.ListformField.FailureReason",
+ Width = 0,
+ ListOrderNo = 6,
+ Visible = true,
+ IsActive = true,
+
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.String,
+ FieldName = "ExceptionType",
+ CaptionName = "App.Listform.ListformField.ExceptionType",
+ Width = 0,
+ ListOrderNo = 7,
+ Visible = true,
+ IsActive = true,
+
+ AllowSearch = true,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ new() {
+ ListFormCode = listForm.ListFormCode,
+ CultureName = LanguageCodes.En,
+ SourceDbType = DbType.String,
+ FieldName = "ExceptionDetails",
+ CaptionName = "App.Listform.ListformField.ExceptionDetails",
+ Width = 0,
+ ListOrderNo = 8,
+ Visible = true,
+ IsActive = true,
+
+ AllowSearch = true,
+ ColumnCustomizationJson = DefaultColumnCustomizationJson,
+ PermissionJson = DefaultFieldPermissionJson(listForm.Name),
+ PivotSettingsJson = DefaultPivotSettingsJson
+ },
+ ]);
+ #endregion
+ }
+ #endregion
+
#region Route
listFormName = AppCodes.Menus.Routes;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
index 8407c1a..2b5a122 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
@@ -152,27 +152,21 @@
"path": "/admin/menuManager",
"componentPath": "@/views/menu/MenuManager",
"routeType": "protected",
- "authority": [
- "App.Menus.Manager"
- ]
+ "authority": ["App.Menus.Manager"]
},
{
"key": "admin.listFormManagement.wizard",
"path": "/admin/listform/wizard",
"componentPath": "@/views/admin/listForm/wizard/Wizard",
"routeType": "protected",
- "authority": [
- "App.Listforms.Wizard"
- ]
+ "authority": ["App.Listforms.Wizard"]
},
{
"key": "admin.listFormManagement.wizardManager",
"path": "/admin/listform/wizardManager",
"componentPath": "@/views/admin/listForm/wizard/WizardFileManager",
"routeType": "protected",
- "authority": [
- "App.Listforms.Wizard"
- ]
+ "authority": ["App.Listforms.Wizard"]
},
{
"key": "admin.listFormManagement.edit",
@@ -186,18 +180,14 @@
"path": "/admin/forumManagement",
"componentPath": "@/views/forum/Management",
"routeType": "protected",
- "authority": [
- "App.ForumManagement"
- ]
+ "authority": ["App.ForumManagement"]
},
{
"key": "admin.ai",
"path": "/admin/ai",
"componentPath": "@/views/ai/Assistant",
"routeType": "protected",
- "authority": [
- "App.Definitions.AiBot.Asistant"
- ]
+ "authority": ["App.Definitions.AiBot.Asistant"]
},
{
"key": "admin.profile.general",
@@ -239,45 +229,35 @@
"path": "/admin/settings",
"componentPath": "@/views/settings/Settings",
"routeType": "protected",
- "authority": [
- "App.Setting"
- ]
+ "authority": ["App.Setting"]
},
{
"key": "admin.identity.user.detail",
"path": "/admin/users/detail/:userId",
"componentPath": "@/views/admin/user-management/Details",
"routeType": "protected",
- "authority": [
- "AbpIdentity.Users.Update"
- ]
+ "authority": ["AbpIdentity.Users.Update"]
},
{
"key": "admin.identity.ous",
"path": "/admin/ous",
"componentPath": "@/views/admin/organization-unit/OrganizationUnits",
"routeType": "protected",
- "authority": [
- "Abp.Identity.OrganizationUnits"
- ]
+ "authority": ["Abp.Identity.OrganizationUnits"]
},
{
"key": "admin.hr.organization",
"path": "/admin/organization",
"componentPath": "@/views/admin/hr/OrgChart",
"routeType": "protected",
- "authority": [
- "App.Definitions.Department"
- ]
+ "authority": ["App.Definitions.Department"]
},
{
"key": "admin.forum",
"path": "/admin/forum",
"componentPath": "@/views/forum/Forum",
"routeType": "protected",
- "authority": [
- "App.ForumManagement.Publish"
- ]
+ "authority": ["App.ForumManagement.Publish"]
},
{
"key": "admin.list",
@@ -319,90 +299,70 @@
"path": "/admin/sqlQueryManager",
"componentPath": "@/views/developerKit/SqlQueryManager",
"routeType": "protected",
- "authority": [
- "App.SqlQueryManager"
- ]
+ "authority": ["App.SqlQueryManager"]
},
{
"key": "admin.developerkit.endpoints",
"path": "/admin/developerkit/endpoints",
"componentPath": "@/views/developerKit/CrudEndpointManager",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.CrudEndpoints"
- ]
+ "authority": ["App.DeveloperKit.CrudEndpoints"]
},
{
"key": "admin.developerkit.dynamic-services",
"path": "/admin/developerkit/dynamic-services",
"componentPath": "@/views/developerKit/DynamicServiceManager",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.DynamicServices"
- ]
+ "authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.dynamic-services.new",
"path": "/admin/developerkit/dynamic-services/new",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.DynamicServices"
- ]
+ "authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.dynamic-services.edit",
"path": "/admin/developerkit/dynamic-services/edit/:id",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.DynamicServices"
- ]
+ "authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.components",
"path": "/admin/developerkit/components",
"componentPath": "@/views/developerKit/ComponentManagerPage",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.Components"
- ]
+ "authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.new",
"path": "/admin/developerkit/components/new",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.Components"
- ]
+ "authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.view",
"path": "/admin/developerkit/components/view/:id",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.Components"
- ]
+ "authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.edit",
"path": "/admin/developerkit/components/edit/:id",
"componentPath": "@/views/developerKit/ComponentCodeLayout",
"routeType": "protected",
- "authority": [
- "App.DeveloperKit.Components"
- ]
+ "authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.fileManagement",
"path": "/admin/files",
"componentPath": "@/views/admin/files/FileManager",
"routeType": "protected",
- "authority": [
- "App.Files"
- ]
+ "authority": ["App.Files"]
},
{
"key": "admin.devexpressReportView",
@@ -423,63 +383,49 @@
"path": "/admin/public/home/designer",
"componentPath": "@/views/public/Home",
"routeType": "protected",
- "authority": [
- "App.Home"
- ]
+ "authority": ["App.Home"]
},
{
"key": "aboutDesigner",
"path": "/admin/public/about/designer",
"componentPath": "@/views/public/About",
"routeType": "protected",
- "authority": [
- "App.About"
- ]
+ "authority": ["App.About"]
},
{
"key": "servicesDesigner",
"path": "/admin/public/services/designer",
"componentPath": "@/views/public/Services",
"routeType": "protected",
- "authority": [
- "App.Services"
- ]
+ "authority": ["App.Services"]
},
{
"key": "contactDesigner",
"path": "/admin/public/contact/designer",
"componentPath": "@/views/public/Contact",
"routeType": "protected",
- "authority": [
- "App.Contact"
- ]
+ "authority": ["App.Contact"]
},
{
"key": "admin.videoroom.dashboard",
"path": "/admin/videoroom/dashboard",
"componentPath": "@/views/admin/videoroom/Dashboard",
"routeType": "protected",
- "authority": [
- "App.Videoroom.Dashboard"
- ]
+ "authority": ["App.Videoroom.Dashboard"]
},
{
"key": "admin.videoroom.list",
"path": "/admin/videoroom/list",
"componentPath": "@/views/admin/videoroom/RoomList",
"routeType": "protected",
- "authority": [
- "App.Videoroom.List"
- ]
+ "authority": ["App.Videoroom.List"]
},
{
"key": "admin.videoroom.roomdetail",
"path": "/admin/videoroom/room/:id",
"componentPath": "@/views/admin/videoroom/RoomDetail",
"routeType": "protected",
- "authority": [
- "App.Videoroom.RoomDetail"
- ]
+ "authority": ["App.Videoroom.RoomDetail"]
}
],
"MenuGroups": [
@@ -732,14 +678,35 @@
"RequiredPermissionName": "App.Notifications.Notification",
"IsDisabled": false
},
+
{
"ParentCode": "App.Saas",
"Code": "App.BackgroundWorkers",
"DisplayName": "App.BackgroundWorkers",
"Order": 11,
- "Url": "/admin/list/App.BackgroundWorkers",
- "Icon": "FcScatterPlot",
- "RequiredPermissionName": "App.BackgroundWorkers",
+ "Url": null,
+ "Icon": "FcSynchronize",
+ "RequiredPermissionName": null,
+ "IsDisabled": false
+ },
+ {
+ "ParentCode": "App.BackgroundWorkers",
+ "Code": "App.BackgroundWorkers.Definitions",
+ "DisplayName": "App.BackgroundWorkers.Definitions",
+ "Order": 1,
+ "Url": "/admin/list/App.BackgroundWorkers.Definitions",
+ "Icon": "FcDataConfiguration",
+ "RequiredPermissionName": "App.BackgroundWorkers.Definitions",
+ "IsDisabled": false
+ },
+ {
+ "ParentCode": "App.BackgroundWorkers",
+ "Code": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "App.BackgroundWorkers.JobFlow",
+ "Order": 2,
+ "Url": "/admin/list/App.BackgroundWorkers.JobFlow",
+ "Icon": "FcFlowChart",
+ "RequiredPermissionName": "App.BackgroundWorkers.JobFlow",
"IsDisabled": false
},
{
@@ -1314,4 +1281,4 @@
"IsDisabled": false
}
]
-}
\ No newline at end of file
+}
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
index 046c410..ba4b9e6 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
@@ -1364,17 +1364,17 @@
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions",
"ParentName": null,
- "DisplayName": "App.BackgroundWorkers",
+ "DisplayName": "App.BackgroundWorkers.Definitions",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers.Create",
- "ParentName": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions.Create",
+ "ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2,
@@ -1382,8 +1382,8 @@
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers.Delete",
- "ParentName": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions.Delete",
+ "ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2,
@@ -1391,8 +1391,8 @@
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers.Export",
- "ParentName": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions.Export",
+ "ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2,
@@ -1400,8 +1400,8 @@
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers.Import",
- "ParentName": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions.Import",
+ "ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2,
@@ -1409,13 +1409,69 @@
},
{
"GroupName": "App.Saas",
- "Name": "App.BackgroundWorkers.Update",
- "ParentName": "App.BackgroundWorkers",
+ "Name": "App.BackgroundWorkers.Definitions.Update",
+ "ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
+
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow",
+ "ParentName": null,
+ "DisplayName": "App.BackgroundWorkers.JobFlow",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow.Create",
+ "ParentName": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "Create",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow.Delete",
+ "ParentName": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "Delete",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow.Export",
+ "ParentName": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "Export",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow.Import",
+ "ParentName": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "Import",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Saas",
+ "Name": "App.BackgroundWorkers.JobFlow.Update",
+ "ParentName": "App.BackgroundWorkers.JobFlow",
+ "DisplayName": "Update",
+ "IsEnabled": true,
+ "MultiTenancySide": 2,
+ "MenuGroup": "Erp|Kurs"
+ },
+
{
"GroupName": "App.Saas",
"Name": "App.Home",
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql
new file mode 100644
index 0000000..38ac2c0
--- /dev/null
+++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql
@@ -0,0 +1,107 @@
+CREATE OR ALTER VIEW [dbo].[Sas_H_BackgroundWorker_JobFlow] AS
+
+SELECT
+ J.Id,
+
+ ISNULL(P.RecurringJobId, N'OneTimeJob') AS [JobName],
+
+ COALESCE(PRC.ProcessingAt, ENQ.EnqueuedAt, J.CreatedAt) AS [ExecutionDate],
+
+ CASE
+ WHEN SUC.DurationMs IS NOT NULL
+ THEN SUC.DurationMs
+
+ WHEN PRC.ProcessingAt IS NOT NULL AND SUC.SucceededAt IS NOT NULL
+ THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, SUC.SucceededAt)
+
+ WHEN PRC.ProcessingAt IS NOT NULL AND FLD.FailedAt IS NOT NULL
+ THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, FLD.FailedAt)
+
+ ELSE NULL
+ END AS [Duration],
+
+ J.StateName AS [Status],
+
+ CASE
+ WHEN J.StateName = 'Failed'
+ THEN COALESCE(
+ FLD.ExceptionMessage,
+ FLD.FailedReason,
+ FLD.ExceptionType,
+ N'Unknown error'
+ )
+ ELSE NULL
+ END AS [FailureReason],
+
+ FLD.ExceptionType AS [ExceptionType],
+ FLD.ExceptionDetails AS [ExceptionDetails]
+
+FROM HangFire.Job J
+
+LEFT JOIN HangFire.JobQueue JQ
+ ON JQ.JobId = J.Id
+
+OUTER APPLY
+(
+ SELECT
+ MAX(CASE
+ WHEN JP.Name = 'RecurringJobId'
+ THEN REPLACE(JP.Value, '"', '')
+ END) AS RecurringJobId
+ FROM HangFire.JobParameter JP
+ WHERE JP.JobId = J.Id
+) P
+
+OUTER APPLY
+(
+ SELECT TOP 1
+ S.CreatedAt AS EnqueuedAt,
+ S.Reason AS EnqueuedReason,
+ JSON_VALUE(S.Data, '$.Queue') AS QueueName
+ FROM HangFire.State S
+ WHERE S.JobId = J.Id
+ AND S.Name = 'Enqueued'
+ ORDER BY S.CreatedAt DESC
+) ENQ
+
+OUTER APPLY
+(
+ SELECT TOP 1
+ S.CreatedAt AS ProcessingAt,
+ S.Reason AS ProcessingReason,
+ JSON_VALUE(S.Data, '$.ServerId') AS ServerName,
+ JSON_VALUE(S.Data, '$.WorkerId') AS WorkerId
+ FROM HangFire.State S
+ WHERE S.JobId = J.Id
+ AND S.Name = 'Processing'
+ ORDER BY S.CreatedAt DESC
+) PRC
+
+OUTER APPLY
+(
+ SELECT TOP 1
+ S.CreatedAt AS SucceededAt,
+ S.Reason AS SucceededReason,
+ TRY_CONVERT(BIGINT, JSON_VALUE(S.Data, '$.PerformanceDuration')) AS DurationMs
+ FROM HangFire.State S
+ WHERE S.JobId = J.Id
+ AND S.Name = 'Succeeded'
+ ORDER BY S.CreatedAt DESC
+) SUC
+
+OUTER APPLY
+(
+ SELECT TOP 1
+ S.CreatedAt AS FailedAt,
+ S.Reason AS FailedReason,
+ JSON_VALUE(S.Data, '$.ExceptionType') AS ExceptionType,
+ JSON_VALUE(S.Data, '$.ExceptionMessage') AS ExceptionMessage,
+ JSON_VALUE(S.Data, '$.ExceptionDetails') AS ExceptionDetails
+ FROM HangFire.State S
+ WHERE S.JobId = J.Id
+ AND S.Name = 'Failed'
+ ORDER BY S.CreatedAt DESC
+) FLD
+
+WHERE JSON_VALUE(J.InvocationData, '$.Method') <> 'DoWorkAsync'
+GO
diff --git a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj
index 0f670dd..eeb435a 100644
--- a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj
+++ b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj
@@ -96,6 +96,7 @@
+
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs
index 11864ed..85ebb42 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs
@@ -13,6 +13,7 @@ public enum TableNameEnum
BackgroundWorker_MailQueue,
BackgroundWorker_MailQueueEvents,
BackgroundWorker_MailQueueTableFormat,
+ BackgroundWorker_JobFlow,
LogEntry,
AiBot,
Branch,
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
index 9591298..7007fc8 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs
@@ -432,7 +432,13 @@ public static class PlatformConsts
public const string Notification = Default + ".Notification";
}
- public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
+ public static class BackgroundWorkers
+ {
+ public const string Default = Prefix.App + ".BackgroundWorkers";
+
+ public const string Definitions = Default + ".Definitions";
+ public const string JobFlow = Default + ".JobFlow";
+ }
public static class Menus
{
diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs
index 02a0192..79b6abe 100644
--- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs
+++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs
@@ -42,6 +42,7 @@ public static class TableNameResolver
{ nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
+ { nameof(TableNameEnum.BackgroundWorker_JobFlow), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueue), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueueEvents), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueueTableFormat), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
diff --git a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs
index 87c0aa4..1564546 100644
--- a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs
+++ b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs
@@ -336,7 +336,7 @@ public static class SeedConsts
public const string SocialPost = Default + ".SocialPost";
public const string SocialComment = Default + ".SocialComment";
-
+
public const string Events = Default + ".Events";
public const string EventType = Events + ".EventType";
public const string EventCategory = Events + ".EventCategory";
@@ -404,7 +404,13 @@ public static class SeedConsts
public const string Notification = Default + ".Notification";
}
- public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
+ public static class BackgroundWorkers
+ {
+ public const string Default = Prefix.App + ".BackgroundWorkers";
+
+ public const string Definitions = Default + ".Definitions";
+ public const string JobFlow = Default + ".JobFlow";
+ }
//Web Site
public const string Home = Prefix.App + ".Home";