From 969b048946c68d4ddec78242b06119746cbdf36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Sun, 20 Sep 2026 23:23:39 +0300 Subject: [PATCH] Notication problemleri giderildi ve Permission Order --- .github/instructions/ai.instructions.md | 2 +- .../lowcode-reference.instructions.md | 2 +- README.md | 81 ++- .../INotificationRuleAppService.cs | 18 +- .../NotificationRuleSummaryDto.cs | 22 + .../NotificationRules/NotificationTypeDto.cs | 3 + .../NotificationAppService.cs | 104 ++- .../NotificationRuleAppService.cs | 71 ++- .../Enums/NotificationChannels.cs | 7 + .../NotificationProviders/INotification.cs | 10 - .../Entities/NotificationType.cs | 6 + .../INotificationRealtimeNotifier.cs | 30 + .../NotificationManager.cs | 286 ++++----- .../NotificationProviders/MailNotification.cs | 13 - .../NotificationProviders/SmsNotification.cs | 13 - ...icationDbContextModelCreatingExtensions.cs | 1 + .../Notifications/NotificationHub.cs | 47 ++ .../NotificationRealtimeNotifier.cs | 52 ++ .../Permissions/PermissionAppService.cs | 55 +- .../Migrations/LanguagesData.json | 92 ++- .../ListFormSeeder_Administration.cs | 42 ++ .../Migrations/ListFormSeeder_Saas.cs | 23 +- .../PlatformConsts.cs | 6 + .../BackgroundWorkers/NotificationWorker.cs | 187 +++--- .../PlatformEfCoreEntityExtensionMappings.cs | 18 + ....cs => 20260920201729_Initial.Designer.cs} | 16 +- ...9_Initial.cs => 20260920201729_Initial.cs} | 7 +- .../PlatformDbContextModelSnapshot.cs | 14 + .../PlatformHttpApiHostModule.cs | 7 +- claude.md | 14 +- configs/deployment/configs/nginx.conf | 14 + .../data/Abp.Identity.PermissionGroups.json | 6 +- .../host/data/Abp.Identity.Permissions.json | 591 ++++++++++++++++++ .../App.Notifications.NotificationTypes.json | 9 +- ui/src/components/template/Notification.tsx | 189 ++++-- ui/src/proxy/notification/models.ts | 25 +- ui/src/services/notification-rule.service.ts | 20 +- ui/src/services/notification.service.ts | 7 - ui/src/services/notification.signalr.ts | 65 ++ ui/src/sw.ts | 62 +- .../views/admin/activityLog/ActivityLog.tsx | 54 +- .../admin/activityLog/components/Event.tsx | 59 +- .../admin/activityLog/components/Log.tsx | 2 +- .../admin/notification/CreateNotification.tsx | 164 +++-- .../components/NotificationSettings.tsx | 127 ++-- 45 files changed, 1998 insertions(+), 645 deletions(-) create mode 100644 api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationRuleSummaryDto.cs delete mode 100644 api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/NotificationProviders/INotification.cs create mode 100644 api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/INotificationRealtimeNotifier.cs delete mode 100644 api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/MailNotification.cs delete mode 100644 api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/SmsNotification.cs create mode 100644 api/src/Sozsoft.Platform.Application/Notifications/NotificationHub.cs create mode 100644 api/src/Sozsoft.Platform.Application/Notifications/NotificationRealtimeNotifier.cs rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260918122519_Initial.Designer.cs => 20260920201729_Initial.Designer.cs} (99%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260918122519_Initial.cs => 20260920201729_Initial.cs} (99%) create mode 100644 ui/src/services/notification.signalr.ts diff --git a/.github/instructions/ai.instructions.md b/.github/instructions/ai.instructions.md index ef876035..9c0d1b1b 100644 --- a/.github/instructions/ai.instructions.md +++ b/.github/instructions/ai.instructions.md @@ -369,7 +369,7 @@ on the calling tenant's own database. Tenant users are read-only over MCP (schem request to "let an agent query/update X" is a Decision Flow §1 (ListForm) request first — MCP only gates which of the existing screens/queries an already-authorized client may reach; it never grants access beyond the calling user's own ListForm/query permissions. Ayrıntı: -`README.md` §13.6, `CLAUDE.md`'nin `/mcp` cross-cutting maddeleri. +`README.md` §13.9, `CLAUDE.md`'nin `/mcp` cross-cutting maddeleri. --- diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index cf4ba750..4a33682e 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -1189,7 +1189,7 @@ soneklerini alır. | **Developer Kit › Components** | `App.DeveloperKit.CustomComponents` · `/admin/list/App.DeveloperKit.CustomComponents` | `Sas_H_CustomComponent` | host | Custom component listesi (ListForm) ve satırdaki **Design** butonuyla **Visual Designer** girişi (§10). Kayıtlar `data/App.DeveloperKit.CustomComponents.json` dosyasına senkronlanır. | | **Developer Kit › ListForm** | `App.Listforms.Listform` | `Sas_H_ListForm` · `Sas_H_ListFormField` · `Sas_H_ListFormWorkflow` · `Sas_H_ListFormCustomization` · `Sas_H_ListFormImport` | host | Ekran tanımlarının kendisi. Bu dosyada anlatılan bütün JSON kolonlarının canlı hâli; düzenleyicisi `/admin/listform/edit/{kod}`. | | **Developer Kit › Wizard Manager** | menü kodu `App.Listforms.WizardManager` · yetki `App.Listforms.Wizard` · `/admin/listform/wizardManager` | — | host | Wizard seed dosyalarının listesi, düzenlemesi, silinmesi, export/import'u. Sihirbazın kendisi ayrı bir rotadadır: `/admin/listform/wizard`. | -| **Developer Kit › MCP Servers** | `App.DeveloperKit.McpServers` · `/admin/list/App.DeveloperKit.McpServers` | `Sas_H_McpServer` | host | `/mcp` ucunun kiracı bazlı sözleşmesi: `Name`, `Url`, `Tenants`, `AllowedTools` (boşsa tüm araçlar, doluysa yalnızca seçilenler açık), `MaxRows`. Araç listesi bu kayda göre süzülür, çağrı anında aynı kural yeniden uygulanır. Yetkiler: ekran için `App.DeveloperKit.McpServers`, araç çağırma için `.Invoke`, veri yazma için `.Write`, prompt ile tablo/ekran/bileşen geliştirme (`create_table`, wizard deploy, custom component kaydı) için `.Develop` (host; Wizard ve Custom Components yetkilerini ayrıca ister). Kiracı kullanıcısı yetkisi ne olursa olsun yalnızca okur (`McpToolNames.TenantTools`: şema araçları, `query_list`, `run_select`); yazma, `create_table`, wizard, component ve `run_query` kiracıya kapalıdır. Host için tek sunucu anahtarı `AllowedTools`. MCP yetki vermez: kullanıcı yalnızca uygulamada okuyabildiği listeleri görür ve sorgular; ham SQL `App.SqlQueryManager` ister. Kayıtlar `data/App.DeveloperKit.McpServers.json` dosyasına senkronlanır. Araçlar ve şema keşfinin kaynağı: `README.md` §13.6. | +| **Developer Kit › MCP Servers** | `App.DeveloperKit.McpServers` · `/admin/list/App.DeveloperKit.McpServers` | `Sas_H_McpServer` | host | `/mcp` ucunun kiracı bazlı sözleşmesi: `Name`, `Url`, `Tenants`, `AllowedTools` (boşsa tüm araçlar, doluysa yalnızca seçilenler açık), `MaxRows`. Araç listesi bu kayda göre süzülür, çağrı anında aynı kural yeniden uygulanır. Yetkiler: ekran için `App.DeveloperKit.McpServers`, araç çağırma için `.Invoke`, veri yazma için `.Write`, prompt ile tablo/ekran/bileşen geliştirme (`create_table`, wizard deploy, custom component kaydı) için `.Develop` (host; Wizard ve Custom Components yetkilerini ayrıca ister). Kiracı kullanıcısı yetkisi ne olursa olsun yalnızca okur (`McpToolNames.TenantTools`: şema araçları, `query_list`, `run_select`); yazma, `create_table`, wizard, component ve `run_query` kiracıya kapalıdır. Host için tek sunucu anahtarı `AllowedTools`. MCP yetki vermez: kullanıcı yalnızca uygulamada okuyabildiği listeleri görür ve sorgular; ham SQL `App.SqlQueryManager` ister. Kayıtlar `data/App.DeveloperKit.McpServers.json` dosyasına senkronlanır. Araçlar ve şema keşfinin kaynağı: `README.md` §13.9. | | **Forum Management** | `App.ForumManagement` (grup menüsü) | `Sas_T_ForumCategory` · `Sas_T_ForumTopic` · `Sas_T_ForumPost` | tenant | Altında Categories / Topics / Posts ListForm ekranları; yayın tarafı Administration'daki `/admin/forum`. | | **Forum Categories** | `App.ForumManagement.Categories` · `/admin/list/App.ForumManagement.Categories` | `Sas_T_ForumCategory` | tenant | Forum kategorilerinin ListForm ekranı; veri `configs/seeds/host/data/App.ForumManagement.Categories.json` dosyasından seed edilir. | | **Forum Topics** | `App.ForumManagement.Topics` · `/admin/list/App.ForumManagement.Topics` | `Sas_T_ForumTopic` | tenant | Forum konuları; kategori lookup'ı, sayaçlar ve sabitle/kilitle/çözüldü bayrakları. | diff --git a/README.md b/README.md index b6a166df..d3cc77ae 100644 --- a/README.md +++ b/README.md @@ -903,6 +903,14 @@ Route.Authority ──┘ └► Permission `App.Orders.*`, `App.BlogManagement.*`, `App.IdentityManagement.*`, `App.Reports.*`, `App.Administration`, `App.Setting`, `App.Setup.Migrate`. +Yetki ekranındaki **sıralama** `AbpPermissions.Order` ve `AbpPermissionGroups.Order` +sütunlarından gelir (`Modules` gibi genişletme alanları, `PlatformEfCoreEntityExtensionMappings`); +ABP kayıtları sırasız okuduğu için sıra taşınmazsa ekran her açılışta başka bir dizilim gösterirdi. +Sıra **kardeşler arasındadır**: her üst yetkinin altında 1'den başlar, grubun kök yetkileri de +kendi aralarında 1'den. `PlatformPermissionAppService` sıralama anahtarını kökten yaprağa üst yetki +zincirinden kurar, böylece çocuk her zaman ebeveyninin altında kalır. Değerler seed dosyalarındaki +`Order` alanında tutulur ve Permissions / Permission Groups ekranlarından düzenlenebilir. + Aksiyon yetkisi olan alt gruplar (UI tarafındaki karşılıkları `constants/permission.constant.ts` içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppService'lerdedir): @@ -931,7 +939,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer | **Sozsoft.MailQueue** | Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme. | | **Sozsoft.Sender** | Amazon SES (mail), Posta Güvercini (SMS), WhatsApp Cloud API, Rocket.Chat göndericileri. | | **Sozsoft.SqlQueryManager** | SQL nesne yönetimi, sorgu çalıştırma, tablo/kolon metadata'sı. | -| **Sozsoft.Mcp** | MCP (Model Context Protocol) sunucusu: `/mcp` Streamable HTTP ucu, kiracı bazlı araç seti, şema keşfi, sorgu, veri yazma ve (host) tablo/ekran/bileşen geliştirme. Kiracı kullanıcısı yalnızca veri okur. Bkz. §13.6. | +| **Sozsoft.Mcp** | MCP (Model Context Protocol) sunucusu: `/mcp` Streamable HTTP ucu, kiracı bazlı araç seti, şema keşfi, sorgu, veri yazma ve (host) tablo/ekran/bileşen geliştirme. Kiracı kullanıcısı yalnızca veri okur. Bkz. §13.9. | ### 10.2. Uygulama servisleri (`api/src/Sozsoft.Platform.Application`) @@ -1130,7 +1138,65 @@ Bildirim **tip + kanal** eşleşmesiyle yönlendirilir. Alıcı hedefleme seçen `All`, `User`, `Role`, `OrganizationUnit`, `Custom`. Kullanıcılar kendi bildirim tercihlerini `/admin/profile/notification-settings` üzerinden kanal bazında yönetir. -### 13.3. Göndericiler (`Sozsoft.Sender`) +### 13.3. Kayıt durumu: gönderildi → okundu + +Bir bildirim kaydı iki bayrak taşır ve sıra değişmez: **önce `IsSent`, sonra `IsRead`**. +Gönderilmemiş bir kayıt okunmuş olamaz. + +| Kanal | `IsSent` ne zaman | +| --- | --- | +| `UiActivity` | Kayıt oluşurken (`NotificationManager`); ayrı bir gönderim adımı yoktur, kayıt doğrudan kullanıcının akışına yazılır | +| `UiToast` · `Desktop` | Tarayıcıda gösterildiğinde, UI `updateSentMany` ile işaretler | +| `Mail` · `Sms` · `Rocket` · `WhatsApp` · `Telegram` | `NotificationWorker` sağlayıcıya teslim ettiğinde | + +`IsRead` ve `ReadTime` birlikte yazılır (`NotificationAppService.UpdateRead*`): okundu işaretinde +`ReadTime = Clock.Now`, okunmadıya çevirmede `null`. `IsSent = false` olan kayıt okundu +işaretlenmez — tekil ve toplu okuma yolları bu kaydı sessizce atlar, gönderim gerçekleştiğinde +okunabilir olur. Bu yüzden UI işaretlemeden sonra listeyi sunucudan tazeler; iyimser güncelleme +rozette yanlış sayı bırakırdı. + +Tarayıcı kanalları tek kümededir (`NotificationChannels.BrowserChannels`); `sent-many` ucu +yalnızca bu kanalları işaretler, böylece istemci Mail/Sms gibi bir kanalı "gönderildi" yapamaz. +Masaüstü bildirimi izin verilmemişse hiç işaretlenmez — işaretlenseydi izin sonradan verildiğinde +de gösterilmezdi. + +### 13.4. Gerçek zamanlı iletim + +Bildirim üretildiği anda açık istemcilere **SignalR** ile duyurulur: hub `/notificationhub`, +bağlantı kullanıcı başına gruplanır (`NotificationHub.UserGroupName`, kiracı + kullanıcı). +`NotificationManager` kayıtları yazdıktan **sonra** duyurur (`INotificationRealtimeNotifier`); +duyuru yükü yalnızca hangi kanallarda kayıt oluştuğunu söyler, istemci listesini yine sunucudan +okur — okundu/gönderildi bayrakları tek kaynaktan gelir. + +Katman sınırı: sözleşme bildirim modülünün Domain katmanındadır (`INotificationRealtimeNotifier`), +SignalR gerçeklemesi ise barındıran uygulamadadır (`Sozsoft.Platform.Application/Notifications/`, +`MessengerHub` ile aynı yer) — modül katmanları ASP.NET Core'a bağlanmaz. Gerçekleme +`[Dependency(ReplaceServices = true)]` ile devreye girer; SignalR olmayan barındırmalarda +(DbMigrator, testler) `NullNotificationRealtimeNotifier` kalır. + +Duyuru **en iyi çaba**dır: bağlantı yoksa ya da mesaj kaybolursa bildirim kaybolmaz — +istemcideki yedek yoklama (5 dakika, sekme öne geldiğinde ayrıca) aynı kaydı getirir. Ters +vekilde `/notificationhub` kendi bloğunda WebSocket yükseltmesiyle karşılanır +(`configs/deployment/configs/nginx.conf`); token sorgu dizesinden geldiği için host bu yolu da +`Authorization` başlığına çevirir. + +Masaüstü bildirimine tıklamak uygulamaya döner: service worker açık sekmeyi öne alıp bildirimin +`data.url` adresine götürür, sekme yoksa yenisini açar (varsayılan hedef Activity Log). + +### 13.5. Kullanıcı tercihleri + +Kurallar alıcı kümesini belirler; kullanıcının profilinden yazdığı tercih (`IsCustomized`, +`RecipientType = User`) **kural değildir**, yalnızca sahibinin o tür/kanal aboneliğini kapatır +(`NotificationManager`). Tercihi kural gibi işletmek, bir kullanıcının kendi tercihini yazmasıyla +aynı tür ve kanaldaki herkesin bildirimini kesmesi anlamına gelirdi. `IsFixed` kural zorunludur, +tercih dikkate alınmaz. Aynı kullanıcıyı birden çok kural hedeflerse (All + Role) kanal başına tek +kayıt üretilir. + +Tercih ekranı (`/admin/profile/notification-settings`) satırlarını bildirim **tipinin id'si** +üzerinden eşler; tip adı yalnızca gösterimdir. `NotificationType.Description` alanı aynı ekranda +ipucu olarak görünür ve Bildirim Türleri ekranından yazılır. + +### 13.6. Göndericiler (`Sozsoft.Sender`) | Kanal | Sağlayıcı | | --- | --- | @@ -1142,12 +1208,12 @@ Bildirim **tip + kanal** eşleşmesiyle yönlendirilir. Alıcı hedefleme seçen Tüm sağlayıcı kimlik bilgileri ayar ekranından yönetilir (`App.Settings.*` anahtarları); koda gömülmez. -### 13.4. Mail kuyruğu +### 13.7. Mail kuyruğu `Sozsoft.MailQueue`; şablondan gövde üretir, PDF/XLS/TXT çıktıları ve ek dosyaları yönetir, kuyruğu arka plan işçisiyle çalıştırır ve sonuçları günlükler. -### 13.5. AI entegrasyonu +### 13.8. AI entegrasyonu `AiBot` kayıtları tenant bazlıdır; n8n webhook üzerinden bir niyet sınıflandırma adımı ve ardından sohbet ya da SQL dalı çalışır (`configs/ai/Chat.json`). Model, DevOps makinesindeki @@ -1160,7 +1226,7 @@ alan adı yönlendirmesi için §20 kullanılır. Akıştaki model adı değişt içindeki üç varsayılanın (`intentModel`, `chatModel`, `sqlModel`) birlikte güncellenmesi gerekir; biri kalırsa o dal "model not found" ile düşer. -### 13.6. MCP sunucusu +### 13.9. MCP sunucusu Yapay zekâ istemcileri (Claude Code, Claude Desktop, ChatGPT) platforma **`/mcp`** ucundan bağlanır. Uç, API imajının içinde aynı portta (`8080`) yayınlanır; ayrı bir container yoktur. @@ -1301,7 +1367,8 @@ istemci tarafında görülen hata genellikle "please register the OAuth client" - API konteynerinde `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true` (`docker-compose-app.yml`) - nginx'te **her** `location` bloğunun `X-Forwarded-Proto $scheme` göndermesi. `location /` - bunu `proxy_params` üzerinden yapar; `/mcp`, `/videoroomhub` ve `/messengerhub` kendi + bunu `proxy_params` üzerinden yapar; `/mcp`, `/videoroomhub`, `/messengerhub` ve + `/notificationhub` kendi blokları olduğu için başlığı ayrıca set eder. Bir blok atlanırsa istemcinin gönderdiği başlık olduğu gibi geçer ve şema taklit edilebilir hale gelir. @@ -1515,7 +1582,7 @@ eklerken portun boş olduğu buradan doğrulanır. | `ollama.sozsoft.com` | 3003 | Open WebUI — sohbet arayüzü | | `ollama.sozsoft.com/ollama-api/` | 11434 | Ollama ham API'si (basic auth) | | `ai.sozsoft.com` | 5678 | n8n | -| `api.sozsoft.com` | 8080 | API (`/mcp`, `/videoroomhub`, `/messengerhub` dahil) | +| `api.sozsoft.com` | 8080 | API (`/mcp`, `/videoroomhub`, `/messengerhub`, `/notificationhub` dahil) | | `cdn.sozsoft.com` | 8081 | CDN | Open WebUI kendi backend'ini `/api/...` altında sunduğu için Ollama'nın ham API'si `/ollama-api/` diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/INotificationRuleAppService.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/INotificationRuleAppService.cs index 4f7e930f..6e322648 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/INotificationRuleAppService.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/INotificationRuleAppService.cs @@ -12,10 +12,24 @@ public interface INotificationRuleAppService : ICrudAppService< PagedAndSortedResultRequestDto, CreateUpdateNotificationRuleDto> { - Task GetNotificationTypes(); - Task GetMyNotificationTypesAsync(); + /// + /// Kullanicinin kurallarina konu olan bildirim tipleri. Tercih ekrani satirlari bu tiplerden + /// kurar; eslesme ad uzerinden degil Id uzerinden yapilir. + /// + Task> GetMyNotificationTypesAsync(); + Task> GetMyNotificationRules(); + + /// + /// Elle bildirim diyaloguna kuralin ozetini verir. Parametre adi id olamaz: ABP o adi + /// gorunce ucu {id} rotasina tasir ve cagri CRUD'un Get ucuna duserdi. + /// + Task GetRuleSummaryAsync(Guid ruleId); + + /// Kullanicinin bir tur/kanal icin kendi tercihini yazar. Task PostMyNotificationRule(CreateUpdateNotificationRuleDto Input); + + /// Bir turdeki kullanici tercihlerini siler; kural yeniden varsayilana doner. Task DeleteMyNotificationRules(Guid NotificationTypeId); } diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationRuleSummaryDto.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationRuleSummaryDto.cs new file mode 100644 index 00000000..f42fdf42 --- /dev/null +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationRuleSummaryDto.cs @@ -0,0 +1,22 @@ +using System; + +namespace Sozsoft.Notifications.NotificationRules; + +/// +/// Bir kuralin elle bildirim diyalogunda gosterilen ozeti: hangi olay, kime, hangi kanaldan. +/// Diyalog yalnizca kural kimligini aldigi icin bu bilgiyi sunucudan okur. +/// +public class NotificationRuleSummaryDto +{ + public Guid Id { get; set; } + + public string NotificationTypeName { get; set; } = string.Empty; + + public string Channel { get; set; } = string.Empty; + + public string RecipientType { get; set; } = string.Empty; + + public string? RecipientId { get; set; } + + public bool IsActive { get; set; } +} diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationTypeDto.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationTypeDto.cs index 929fe18e..3d51d453 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationTypeDto.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/NotificationRules/NotificationTypeDto.cs @@ -6,5 +6,8 @@ namespace Sozsoft.Notifications.NotificationRules; public class NotificationTypeDto : FullAuditedEntityDto { public string Name { get; set; } + + /// Turun ne ise yaradigi; tercih ekraninda ipucu olarak gosterilir. + public string? Description { get; set; } } diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationAppService.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationAppService.cs index 5f2a628f..d0dd8620 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationAppService.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationAppService.cs @@ -15,6 +15,10 @@ using Volo.Abp.Identity; namespace Sozsoft.Notifications.Application; +/// +/// Kullanicinin kendi bildirimleri: liste, okundu isaretleme ve tarayici kanallarinda gosterim +/// isareti. Her sorgu UserId ile kendi kayitlarina kapalidir. +/// [Authorize] public class NotificationAppService : ReadOnlyAppService< Notification, @@ -39,29 +43,13 @@ public class NotificationAppService : ReadOnlyAppService< this.repositoryUser = repositoryUser; } - public async Task GetTestAsync() - { - // await notificationManager.SendNotificationAsync(NotificationTypes.SiparisPasla, "Sayin kullanici, nasilsiniz?"); - // await notificationManager.SendNotificationAsync(NotificationTypes.YeniKullanici, "Sayin kullanici, nasilsiniz?"); - // await notificationManager.SendNotificationAsync(NotificationTypes.YeniSiparis, "Sayin kullanici, nasilsiniz?"); - - //smsgönder - // alıcılar: +1435345134, +1435345134 - // mesaj: Sayin kullanici, nasilsiniz? - // await notificationManager.SendNotificationAsync( - // NotificationTypes.SiparisPasla, - // NotificationChannels.WhatsApp, - // "Sayin kullanici, nasilsiniz?", - // "+905449476346"); - } - + /// Bir kuraldan elle bildirim uretir; Bildirim Kurallari ekranindaki "Create" komutu. public async Task CreateNotificationByNotificationRuleIdAsync(NotificationRequestDto input) { - var notificationRule = await repositoryRule.GetAsync(input.Id, true); - if (notificationRule != null) - { - await notificationManager.SendNotificationAsync(notificationRule.NotificationTypeId, input.Message); - } + var notificationRule = await repositoryRule.FindAsync(input.Id) + ?? throw new EntityNotFoundException(L["RecordNotFound"]); + + await notificationManager.SendNotificationAsync(notificationRule.NotificationTypeId, input.Message); } protected override async Task> CreateFilteredQueryAsync(NotificationFilterRequestDto input) @@ -91,10 +79,10 @@ public class NotificationAppService : ReadOnlyAppService< foreach (var entity in entities) { var dto = await MapToGetListOutputDtoAsync(entity); - if (entity.CreatorId.HasValue && users.ContainsKey(entity.CreatorId.Value)) + if (entity.CreatorId.HasValue && users.TryGetValue(entity.CreatorId.Value, out var user)) { - dto.CreatorFullname = users[entity.CreatorId.Value].Fullname; - dto.TenantId = users[entity.CreatorId.Value].TenantId; + dto.CreatorFullname = user.Fullname; + dto.TenantId = user.TenantId; } dtos.Add(dto); } @@ -102,57 +90,26 @@ public class NotificationAppService : ReadOnlyAppService< return dtos; } - public async Task UpdateReadAsync(Guid notificationId, bool isRead) - { - var query = await repository.GetQueryableAsync(); - - var item = await query - .Include(a => a.NotificationType) - .FirstOrDefaultAsync(a => - a.UserId == CurrentUser.Id && - a.Id == notificationId); - if (item == null) - { - throw new EntityNotFoundException(L["RecordNotFound"]); - } - - item.IsRead = isRead; - await repository.UpdateAsync(item, autoSave: true); - - return ObjectMapper.Map(item); - } - + /// + /// Bir kanaldaki tum kayitlari okundu/okunmadi yapar. Kullaniciya henuz iletilmemis kayit + /// okunamaz; isaretleme onlari atlar ve gonderim gerceklestiginde tekrar okunabilir olurlar. + /// public async Task UpdateReadAllAsync(string notificationChannel, bool isRead) { var query = await repository.GetQueryableAsync(); - var affectedRows = await query.Where(a => a.UserId == CurrentUser.Id && a.NotificationChannel == notificationChannel && a.IsRead == !isRead) - .ExecuteUpdateAsync(b => - b.SetProperty(u => u.IsRead, isRead) + var readTime = isRead ? Clock.Now : (DateTime?)null; + var affectedRows = await query + .Where(a => a.UserId == CurrentUser.Id && a.NotificationChannel == notificationChannel && a.IsRead == !isRead) + .Where(a => !isRead || a.IsSent) + .ExecuteUpdateAsync(b => b + .SetProperty(u => u.IsRead, isRead) + .SetProperty(u => u.ReadTime, readTime) ); return affectedRows; } - public async Task UpdateSentAsync(Guid notificationId, bool isSent) - { - var query = await repository.GetQueryableAsync(); - - var item = await query - .Include(a => a.NotificationType) - .FirstOrDefaultAsync(a => - a.UserId == CurrentUser.Id && - a.Id == notificationId); - if (item == null) - { - throw new EntityNotFoundException(L["RecordNotFound"]); - } - - item.IsSent = isSent; - await repository.UpdateAsync(item, autoSave: true); - - return ObjectMapper.Map(item); - } - + /// Secili kayitlari okundu/okunmadi yapar; kural ile aynidir. public async Task UpdateReadManyAsync(List notificationIds, bool isRead) { if (notificationIds.Count == 0) @@ -161,11 +118,20 @@ public class NotificationAppService : ReadOnlyAppService< } var query = await repository.GetQueryableAsync(); + var readTime = isRead ? Clock.Now : (DateTime?)null; + return await query .Where(a => a.UserId == CurrentUser.Id && notificationIds.Contains(a.Id) && a.IsRead != isRead) - .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsRead, isRead)); + .Where(a => !isRead || a.IsSent) + .ExecuteUpdateAsync(setters => setters + .SetProperty(a => a.IsRead, isRead) + .SetProperty(a => a.ReadTime, readTime)); } + /// + /// Tarayici kanallarinda (toast, masaustu) gosterim isaretidir; gosterilen kayit iletilmis + /// sayilir. Diger kanallarin gonderimi NotificationWorker'a aittir. + /// public async Task UpdateSentManyAsync(List notificationIds, bool isSent) { if (notificationIds.Count == 0) @@ -176,6 +142,7 @@ public class NotificationAppService : ReadOnlyAppService< var query = await repository.GetQueryableAsync(); return await query .Where(a => a.UserId == CurrentUser.Id && notificationIds.Contains(a.Id) && a.IsSent != isSent) + .Where(a => NotificationChannels.BrowserChannels.Contains(a.NotificationChannel)) .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsSent, isSent)); } @@ -184,4 +151,3 @@ public class NotificationAppService : ReadOnlyAppService< [RemoteService(false)] protected override Task GetEntityByIdAsync(Guid id) => throw new NotImplementedException(); } - diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationRuleAppService.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationRuleAppService.cs index 807fee5e..5d40618e 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationRuleAppService.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationRuleAppService.cs @@ -8,6 +8,7 @@ using Sozsoft.Notifications.Domain; using Sozsoft.Notifications.Entities; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; @@ -22,38 +23,27 @@ public class NotificationRuleAppService : CrudAppService< CreateUpdateNotificationRuleDto>, INotificationRuleAppService { - private readonly IRepository repositoryType; private readonly IIdentityUserRepository repositoryUser; public NotificationRuleAppService( - IRepository repositoryType, IRepository repository, IIdentityUserRepository repositoryUser ) : base(repository) { - this.repositoryType = repositoryType; this.repositoryUser = repositoryUser; } - public async Task GetNotificationTypes() + /// + /// Birimin kendi kodu ve tum ust birim kodlari. Ust birime tanimlanan kural alt birimlere de + /// isledigi icin (NotificationManager birimi cocuklariyla birlikte cozer) kullanicinin + /// gorecegi kurallar bu zincirin tamamindan gelir. + /// + private static void AddUnitCodeWithParents(string? code, List codes) { - var types = await repositoryType.GetListAsync(); - - return types.Select(a => a.Name).ToArray(); - } - - private void GetAllParentCodes(string code, List? codes) - { - codes ??= []; - var parentCode = OrganizationUnit.GetParentCode(code); - codes.Add(parentCode); - if (parentCode is null) + while (!code.IsNullOrWhiteSpace()) { - return; - } - else - { - GetAllParentCodes(parentCode, codes); + codes.Add(code!); + code = OrganizationUnit.GetParentCode(code!); } } @@ -62,27 +52,54 @@ public class NotificationRuleAppService : CrudAppService< // Bir kullanıcının ait olduğu organization unit'lerin her bir parentının kodu lazım // Böylece bir kullanıcının üst birimleri de dahil alabileceği tüm bildirim tiplerini listeleyebileceğiz var organizationUnits = await repositoryUser.GetOrganizationUnitsAsync(CurrentUser.Id!.Value); - var organizationUnitCodes = organizationUnits.Select(a => a.Code).ToList(); //01.02.03, 43.44.45 var ouCodes = new List(); - foreach (var code in organizationUnitCodes) + foreach (var unit in organizationUnits) { - GetAllParentCodes(code, ouCodes); + AddUnitCodeWithParents(unit.Code, ouCodes); //01.02.03 -> 01.02.03, 01.02, 01 } - ouCodes = ouCodes.Distinct().ToList(); + ouCodes = [.. ouCodes.Distinct()]; var query = await base.CreateFilteredQueryAsync(input); return query.Where(a => a.RecipientType == NotificationRecipientTypes.User && a.RecipientId == CurrentUser.UserName || a.RecipientType == NotificationRecipientTypes.Role && CurrentUser.Roles.Contains(a.RecipientId) || - a.RecipientType == NotificationRecipientTypes.OrganizationUnit && ouCodes.Contains(a.RecipientId) || + a.RecipientType == NotificationRecipientTypes.OrganizationUnit && a.RecipientId != null && ouCodes.Contains(a.RecipientId) || a.RecipientType == NotificationRecipientTypes.All); } - public async Task GetMyNotificationTypesAsync() + public async Task> GetMyNotificationTypesAsync() { var query = await CreateFilteredQueryAsync(new PagedAndSortedResultRequestDto()); - return await query.Select(a => a.NotificationType.Name).Distinct().ToArrayAsync(); + return await query + .Select(a => new NotificationTypeDto + { + Id = a.NotificationTypeId, + Name = a.NotificationType.Name, + Description = a.NotificationType.Description + }) + .Distinct() + .OrderBy(a => a.Name) + .ToListAsync(); + } + + public async Task GetRuleSummaryAsync(Guid ruleId) + { + var query = await Repository.GetQueryableAsync(); + + return await query + .Where(rule => rule.Id == ruleId) + .Select(rule => new NotificationRuleSummaryDto + { + Id = rule.Id, + NotificationTypeName = rule.NotificationType.Name, + Channel = rule.Channel, + RecipientType = rule.RecipientType, + RecipientId = rule.RecipientId, + IsActive = rule.IsActive + }) + .FirstOrDefaultAsync() + ?? throw new EntityNotFoundException(typeof(NotificationRule), ruleId); } public async Task> GetMyNotificationRules() diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs index 95545aec..226c37f8 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs @@ -10,4 +10,11 @@ public static class NotificationChannels public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor. public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based) public const string Telegram = "Telegram"; //Telegram (Bot API, chat based) + + /// + /// Gonderimi tarayicida gerceklesen kanallar: kayit kullaniciya ancak gosterildiginde + /// iletilmis sayilir, bu yuzden IsSent isaretini istemci atar. Diger kanallari + /// NotificationWorker isaretler; UiActivity ise kayit olusurken iletilmis sayilir. + /// + public static readonly string[] BrowserChannels = [UiToast, Desktop]; } diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/NotificationProviders/INotification.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/NotificationProviders/INotification.cs deleted file mode 100644 index c9f8b30b..00000000 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/NotificationProviders/INotification.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; - -namespace Sozsoft.Notifications.Domain.NotificationProviders; - -public interface INotification : ITransientDependency -{ - Task Send(string[] NotificationType, string NotificationMessage); -} - diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Entities/NotificationType.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Entities/NotificationType.cs index 5964d25b..f6161647 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Entities/NotificationType.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Entities/NotificationType.cs @@ -8,6 +8,12 @@ public class NotificationType : FullAuditedEntity { public required string Name { get; set; } + /// + /// Turun ne ise yaradigi: hangi olayda tetiklendigi, kimin ne bekledigi. Kural tanimlayan + /// kisi ekranda yalnizca kod adini gorur; aciklama olmadan bir turun ne oldugu tahmin edilir. + /// + public string? Description { get; set; } + public virtual ICollection NotificationRules { get; set; } = []; public ICollection Notifications { get; set; } = []; } diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/INotificationRealtimeNotifier.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/INotificationRealtimeNotifier.cs new file mode 100644 index 00000000..715438cd --- /dev/null +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/INotificationRealtimeNotifier.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Sozsoft.Notifications.Domain; + +/// +/// Yeni uretilen bildirimleri acik istemcilere aninda duyurur. Sunum kanalidir (SignalR), bu +/// yuzden gerceklemesi Application katmanindadir; uretim tarafi yalnizca bu sozlesmeyi bilir. +/// +public interface INotificationRealtimeNotifier +{ + /// Duyuru kayit yazildiktan sonra yapilir; iletim basarisiz olursa uretim etkilenmez. + Task NotifyAsync(IReadOnlyCollection deliveries); +} + +/// Bildirimin sahibi; duyuru yalnizca bu kullanicinin baglantilarina gider. +/// Kanal adi; istemci hangi gosterimi yapacagina buna bakarak karar verir. +public sealed record NotificationDelivery(Guid UserId, string Channel); + +/// +/// Gercek zamanli duyuru yalnizca web sunucusunda anlamlidir. SignalR olmayan barindirmalarda +/// (DbMigrator, testler) bu varsayilan kullanilir; Application katmani kendi gerceklemesiyle +/// bunun yerine gecer. +/// +public class NullNotificationRealtimeNotifier : INotificationRealtimeNotifier, ITransientDependency +{ + public Task NotifyAsync(IReadOnlyCollection deliveries) => Task.CompletedTask; +} diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs index 3e0f4e98..ef6aea97 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs @@ -4,207 +4,199 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Sozsoft.Notifications.Entities; +using Sozsoft.Notifications.Enums; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; -using Volo.Abp.Users; namespace Sozsoft.Notifications.Domain; public interface INotificationManager : ITransientDependency { + /// + /// Bir bildirim tipinin tum kurallarini isletir ve her alici icin kanal basina bir bildirim + /// kaydi uretir. Kayitlarin kanaldan cikisi NotificationWorker'a aittir. + /// Task SendNotificationAsync(Guid NotificationTypeId, string NotificationMessage); - Task SendNotificationAsync(string NotificationType, string NotificationChannel, string NotificationMessage, string Identifier, Guid? UserId = null); } public class NotificationManager : INotificationManager { - private readonly IReadOnlyRepository repositoryType; private readonly IReadOnlyRepository repository; private readonly IReadOnlyRepository repositoryUser; private readonly IReadOnlyRepository repositoryRole; private readonly IRepository repositoryNotification; private readonly INotificationIdentifierProvider notificationIdentifierProvider; private readonly IOrganizationUnitRepository repositoryOrganizationUnit; - private readonly ICurrentUser currentUser; + private readonly INotificationRealtimeNotifier realtimeNotifier; public NotificationManager( - IReadOnlyRepository repositoryType, IReadOnlyRepository repository, IReadOnlyRepository repositoryUser, IReadOnlyRepository repositoryRole, IRepository repositoryNotification, INotificationIdentifierProvider notificationIdentifierProvider, IOrganizationUnitRepository repositoryOrganizationUnit, - ICurrentUser currentUser + INotificationRealtimeNotifier realtimeNotifier ) { - this.repositoryType = repositoryType; this.repository = repository; this.repositoryUser = repositoryUser; this.repositoryRole = repositoryRole; this.repositoryNotification = repositoryNotification; this.notificationIdentifierProvider = notificationIdentifierProvider; this.repositoryOrganizationUnit = repositoryOrganizationUnit; - this.currentUser = currentUser; + this.realtimeNotifier = realtimeNotifier; } public async Task SendNotificationAsync(Guid NotificationTypeId, string NotificationMessage) { - var list = await repository.GetListAsync(a => a.NotificationTypeId == NotificationTypeId); - var notificationRules = list - .GroupBy(a => new { a.NotificationType, a.Channel }) - .Select(a => - { - var item = a.FirstOrDefault(a => a.IsFixed) - ?? a.FirstOrDefault(a => a.IsCustomized) - ?? a.FirstOrDefault(a => a.IsActive) - ?? a.First(); - return item; - }) - .Where(a => a.IsActive) - .ToList(); + var rules = await repository.GetListAsync(a => a.NotificationTypeId == NotificationTypeId); + // Kullanici tercihleri (profil ekranindan yazilan IsCustomized kayitlari) alici kumesini + // belirlemez; yalnizca sahibinin o kanaldaki aboneligini kapatir. Tercihi kural gibi + // isletmek, bir kullanicinin kendi tercihini yazmasiyla ayni tur/kanaldaki herkesin + // bildirimini kesmesi anlamina gelirdi. + var optedOut = rules + .Where(rule => rule.IsCustomized + && !rule.IsActive + && rule.RecipientType == NotificationRecipientTypes.User + && rule.RecipientId != null) + .Select(rule => (rule.Channel, UserName: rule.RecipientId!)) + .ToHashSet(); + + // Ayni kullaniciyi birden fazla kural hedefleyebilir (All + Role gibi); kanal basina tek + // kayit uretilir. + var delivered = new HashSet<(string Channel, string Identifier)>(); + var notifications = new List(); var queryUsers = await repositoryUser.GetQueryableAsync(); - foreach (var notificationRule in notificationRules) + + foreach (var notificationRule in rules.Where(rule => rule.IsActive && !rule.IsCustomized)) { - List userIds = []; - - // RecipientType bakarak ilgilileri bul - if (notificationRule.RecipientType == NotificationRecipientTypes.All) - { - // Tum userlar - var allUserIds = await queryUsers - .Where(u => u.IsActive) - .Select(a => a.Id) - .ToListAsync(); - userIds.AddRange(allUserIds); - } - else if (notificationRule.RecipientType == NotificationRecipientTypes.User) - { - var user = await queryUsers.FirstOrDefaultAsync(a => a.UserName == notificationRule.RecipientId); - if (user != null) - { - userIds.Add(user.Id); - } - } - else if (notificationRule.RecipientType == NotificationRecipientTypes.Role) - { - var role = await repositoryRole.FirstOrDefaultAsync(a => a.Name == notificationRule.RecipientId); - if (role != null) - { - var roleUserIds = await queryUsers - .Where(u => u.Roles.Any(r => r.RoleId == role.Id) && u.IsActive) - .Select(a => a.Id) - .ToListAsync(); - userIds.AddRange(roleUserIds); - } - } - else if (notificationRule.RecipientType == NotificationRecipientTypes.OrganizationUnit) - { - var units = await repositoryOrganizationUnit.GetAllChildrenWithParentCodeAsync(notificationRule.RecipientId, null); - foreach (var unit in units) - { - var memberIds = await repositoryOrganizationUnit.GetMemberIdsAsync(unit.Id); - userIds.AddRange(memberIds); - } - } - - var notifications = new List(); - - //Eğer "Custom" seçildiyse girilen RecipientId dinamik olabilir. Telefon Numarası, Email vs. + // "Custom" alicida RecipientId adresin kendisidir (telefon, e-posta, chat id); + // sistemde bir kullaniciya karsilik gelmeyebilir, bu yuzden kullanici aramasi yapilmaz. if (notificationRule.RecipientType == NotificationRecipientTypes.Custom) { - var notification = new Notification - { - NotificationTypeId = notificationRule.NotificationTypeId, - NotificationRuleId = notificationRule.Id, - NotificationChannel = notificationRule.Channel, - Identifier = notificationRule.RecipientId!, - UserId = currentUser.Id, - Message = NotificationMessage, - IsSent = false, - IsRead = false - }; - notifications.Add(notification); - } - else - { - if (userIds.IsNullOrEmpty()) + if (notificationRule.RecipientId.IsNullOrWhiteSpace() + || !delivered.Add((notificationRule.Channel, notificationRule.RecipientId!))) { continue; } - userIds = [.. userIds.Distinct()]; - - foreach (var userId in userIds) - { - var identifier = await notificationIdentifierProvider.GetIdentifier(userId, notificationRule.Channel); - if (identifier.IsNullOrEmpty()) - { - continue; - } - - var notification = new Notification - { - NotificationTypeId = notificationRule.NotificationTypeId, - NotificationRuleId = notificationRule.Id, - NotificationChannel = notificationRule.Channel, - Identifier = identifier, - UserId = userId, - Message = NotificationMessage, - IsSent = false, - IsRead = false - }; - notifications.Add(notification); - } + notifications.Add(BuildNotification(notificationRule, notificationRule.RecipientId!, userId: null, NotificationMessage)); + continue; } - await repositoryNotification.InsertManyAsync(notifications); + var userIds = await ResolveRecipientUserIdsAsync(notificationRule, queryUsers); + if (userIds.Count == 0) + { + continue; + } + + var users = await queryUsers + .Where(user => userIds.Contains(user.Id)) + .Select(user => new { user.Id, user.UserName }) + .ToListAsync(); + + foreach (var user in users) + { + // Zorunlu bildirimde (IsFixed) kullanici tercihi dikkate alinmaz. + if (!notificationRule.IsFixed && optedOut.Contains((notificationRule.Channel, user.UserName))) + { + continue; + } + + var identifier = await notificationIdentifierProvider.GetIdentifier(user.Id, notificationRule.Channel); + if (identifier.IsNullOrWhiteSpace() || !delivered.Add((notificationRule.Channel, identifier!))) + { + continue; + } + + notifications.Add(BuildNotification(notificationRule, identifier!, user.Id, NotificationMessage)); + } } + + if (notifications.Count == 0) + { + return; + } + + await repositoryNotification.InsertManyAsync(notifications, autoSave: true); + + // Duyuru kayit yazildiktan sonra yapilir; istemci duyuruyu alip listesini okudugunda + // kaydi mutlaka gormeli. Duyuru gitmezse yedek yoklama ayni kaydi getirir. + await realtimeNotifier.NotifyAsync( + [.. notifications + .Where(notification => notification.UserId.HasValue) + .Select(notification => new NotificationDelivery(notification.UserId!.Value, notification.NotificationChannel))]); } + /// Kuralin hedefledigi kullanicilar; Custom alici bu yoldan gecmez. + private async Task> ResolveRecipientUserIdsAsync(NotificationRule notificationRule, IQueryable queryUsers) + { + List userIds = []; + + if (notificationRule.RecipientType == NotificationRecipientTypes.All) + { + userIds.AddRange(await queryUsers + .Where(user => user.IsActive) + .Select(user => user.Id) + .ToListAsync()); + } + else if (notificationRule.RecipientType == NotificationRecipientTypes.User) + { + var user = await queryUsers.FirstOrDefaultAsync(user => user.UserName == notificationRule.RecipientId); + if (user != null) + { + userIds.Add(user.Id); + } + } + else if (notificationRule.RecipientType == NotificationRecipientTypes.Role) + { + var role = await repositoryRole.FirstOrDefaultAsync(role => role.Name == notificationRule.RecipientId); + if (role != null) + { + userIds.AddRange(await queryUsers + .Where(user => user.Roles.Any(userRole => userRole.RoleId == role.Id) && user.IsActive) + .Select(user => user.Id) + .ToListAsync()); + } + } + else if (notificationRule.RecipientType == NotificationRecipientTypes.OrganizationUnit) + { + if (notificationRule.RecipientId.IsNullOrWhiteSpace()) + { + return userIds; + } + + var units = await repositoryOrganizationUnit.GetAllChildrenWithParentCodeAsync(notificationRule.RecipientId, null); + foreach (var unit in units) + { + userIds.AddRange(await repositoryOrganizationUnit.GetMemberIdsAsync(unit.Id)); + } + } + + return [.. userIds.Distinct()]; + } + + private static Notification BuildNotification(NotificationRule notificationRule, string identifier, Guid? userId, string message) => new() + { + NotificationTypeId = notificationRule.NotificationTypeId, + NotificationRuleId = notificationRule.Id, + NotificationChannel = notificationRule.Channel, + Identifier = identifier, + UserId = userId, + Message = message, + IsSent = IsDeliveredOnCreate(notificationRule.Channel), + IsRead = false + }; /// - /// Belirtilen Ident + /// Kanalin gonderimi kaydin kendisi mi. UiActivity'nin ayri bir gonderim adimi yoktur — + /// kayit dogrudan kullanicinin bildirim akisina yazilir, bu yuzden olusurken iletilmis + /// sayilir. Diger kanallar NotificationWorker ya da tarayici gosterimi ile isaretlenir; + /// isaretlenmeden okunamazlar. /// - /// - /// - /// - /// - /// - public async Task SendNotificationAsync( - string NotificationType, - string NotificationChannel, - string NotificationMessage, - string Identifier, - Guid? UserId = null) - { - if (NotificationChannel == Enums.NotificationChannels.WhatsApp) - { - if (Identifier.StartsWith('5')) - { - Identifier = "+90" + Identifier; - } - else if (Identifier.StartsWith("05")) - { - Identifier = "+9" + Identifier; - } - } - - var notificationType = await repositoryType.FirstOrDefaultAsync(a => a.Name == NotificationType) - ?? throw new Exception($"NotificationType '{NotificationType}' not found."); - - await repositoryNotification.InsertAsync(new Notification - { - NotificationTypeId = notificationType.Id, - NotificationChannel = NotificationChannel, - Identifier = Identifier, - UserId = UserId, - Message = NotificationMessage, - IsSent = false, - IsRead = false - }); - } + private static bool IsDeliveredOnCreate(string channel) + => channel == NotificationChannels.UiActivity; } - diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/MailNotification.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/MailNotification.cs deleted file mode 100644 index 4eac7a35..00000000 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/MailNotification.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Sozsoft.Notifications.Domain.NotificationProviders; - -public class MailNotification : INotification -{ - public Task Send(string[] NotificationType, string NotificationMessage) - { - throw new NotImplementedException(); - } -} - diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/SmsNotification.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/SmsNotification.cs deleted file mode 100644 index d57e1e83..00000000 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationProviders/SmsNotification.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Sozsoft.Notifications.Domain.NotificationProviders; - -public class SmsNotification : INotification -{ - public Task Send(string[] NotificationType, string NotificationMessage) - { - throw new NotImplementedException(); - } -} - diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationDbContextModelCreatingExtensions.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationDbContextModelCreatingExtensions.cs index 4d1522c2..80775e27 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationDbContextModelCreatingExtensions.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.EntityFrameworkCore/EntityFrameworkCore/NotificationDbContextModelCreatingExtensions.cs @@ -20,6 +20,7 @@ public static class NotificationDbContextModelCreatingExtensions b.ConfigureByConvention(); b.Property(x => x.Name).IsRequired().HasMaxLength(128); + b.Property(x => x.Description).HasMaxLength(2048); }); builder.Entity(b => diff --git a/api/src/Sozsoft.Platform.Application/Notifications/NotificationHub.cs b/api/src/Sozsoft.Platform.Application/Notifications/NotificationHub.cs new file mode 100644 index 00000000..a35faa0b --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/Notifications/NotificationHub.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using Volo.Abp.Security.Claims; + +namespace Sozsoft.Platform.Notifications; + +/// +/// Bildirimlerin aninda duyuruldugu baglanti. Istemci yalnizca dinler; okundu/gosterildi +/// isaretleri REST ucundan gider, boylece yetki ve dogrulama tek yoldan gecer. +/// +/// Kullanici ve kiraci bilgisi baglantinin claim'lerinden okunur: hub cagrilari HTTP ardisik +/// duzeni disinda calistigi icin AsyncLocal tabanli ICurrentUser her zaman dolu olmaz. +/// +/// +[Authorize] +public class NotificationHub : Hub +{ + /// Istemcinin dinledigi olay adi; yeni bildirim uretildiginde tetiklenir. + public const string NotificationReceived = "NotificationReceived"; + + public override async Task OnConnectedAsync() + { + var (tenantId, userId) = GetConnectionIdentity(); + if (userId.HasValue) + { + await Groups.AddToGroupAsync(Context.ConnectionId, UserGroupName(tenantId, userId.Value)); + } + + await base.OnConnectedAsync(); + } + + private (Guid? TenantId, Guid? UserId) GetConnectionIdentity() + { + var user = Context.User; + return (ParseGuid(user?.FindFirst(AbpClaimTypes.TenantId)?.Value), + ParseGuid(user?.FindFirst(AbpClaimTypes.UserId)?.Value)); + } + + private static Guid? ParseGuid(string? value) => + Guid.TryParse(value, out var parsed) ? parsed : null; + + /// Bildirimler kullanici bazlidir; grup adi kiraci ile birlikte anahtarlanir. + public static string UserGroupName(Guid? tenantId, Guid userId) => + $"notification:user:{tenantId?.ToString("N") ?? "host"}:{userId:N}"; +} diff --git a/api/src/Sozsoft.Platform.Application/Notifications/NotificationRealtimeNotifier.cs b/api/src/Sozsoft.Platform.Application/Notifications/NotificationRealtimeNotifier.cs new file mode 100644 index 00000000..bb450989 --- /dev/null +++ b/api/src/Sozsoft.Platform.Application/Notifications/NotificationRealtimeNotifier.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Sozsoft.Notifications.Domain; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Sozsoft.Platform.Notifications; + +/// +/// Duyuruyu SignalR uzerinden yapar. Yayin, kayit yazildiktan sonra "en iyi caba" ile denenir: +/// baglanti kurulamazsa bildirim kaybolmaz, istemci yedek yoklamada ayni kaydi gorur. +/// +[Dependency(ReplaceServices = true)] +public class NotificationRealtimeNotifier( + IHubContext hubContext, + ICurrentTenant currentTenant, + ILogger logger) + : INotificationRealtimeNotifier, ITransientDependency +{ + public async Task NotifyAsync(IReadOnlyCollection deliveries) + { + if (deliveries.Count == 0) + { + return; + } + + var tenantId = currentTenant.Id; + + // Ayni kullaniciya birden fazla kanaldan kayit dusebilir; istemci tek duyuruda kendi + // listesini tazeledigi icin kullanici basina bir mesaj yeterlidir. + foreach (var delivery in deliveries.GroupBy(delivery => delivery.UserId)) + { + try + { + await hubContext.Clients + .Group(NotificationHub.UserGroupName(tenantId, delivery.Key)) + .SendAsync(NotificationHub.NotificationReceived, new + { + Channels = delivery.Select(item => item.Channel).Distinct().ToArray() + }); + } + catch (Exception ex) + { + logger.LogWarning(ex, "Realtime notification could not be published to {UserId}.", delivery.Key); + } + } + } +} diff --git a/api/src/Sozsoft.Platform.Application/Permissions/PermissionAppService.cs b/api/src/Sozsoft.Platform.Application/Permissions/PermissionAppService.cs index c0d26314..8480f39b 100644 --- a/api/src/Sozsoft.Platform.Application/Permissions/PermissionAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Permissions/PermissionAppService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -44,6 +45,7 @@ public class PlatformPermissionAppService : PermissionAppService { var baseResult = await base.GetAsync(providerName, providerKey); var allDefinitions = await PermissionDefinitionManager.GetPermissionsAsync(); + var allGroups = await PermissionDefinitionManager.GetGroupsAsync(); // Yeni tipte result var result = new PlatformGetPermissionListResultDto @@ -61,7 +63,7 @@ public class PlatformPermissionAppService : PermissionAppService DisplayNameResource = group.DisplayNameResource }; - foreach (var permission in group.Permissions) + foreach (var permission in SortByOrder(group.Permissions, allDefinitions)) { var definition = allDefinitions.FirstOrDefault(x => x.Name == permission.Name); var modules = definition?.Properties.GetOrDefault(PlatformConsts.Permissions.Modules)?.ToString(); @@ -83,6 +85,55 @@ public class PlatformPermissionAppService : PermissionAppService result.Groups.Add(newGroup); } + // Gruplarin sirasi AbpPermissionGroups.Order sutunundandir. + result.Groups = [.. result.Groups.OrderBy(group => GetOrder(allGroups, group.Name))]; + return result; } + + /// + /// Yetkileri AbpPermissions.Order sutununa gore dizer; ABP kayitlari sirasiz okur. + /// Sira kardeşler arasindadir (her ust yetkinin altinda 1'den baslar), bu yuzden anahtar + /// kokten yapraga ust yetki zinciridir: ebeveynin anahtari cocugunkinin oneki oldugu icin + /// cocuk her zaman ebeveyninin altinda kalir. + /// + private static List SortByOrder( + List permissions, + IReadOnlyList definitions) + { + var byName = permissions.ToDictionary(permission => permission.Name!); + + return [.. permissions.OrderBy(permission => BuildOrderKey(permission, byName, definitions), StringComparer.Ordinal)]; + } + + private static string BuildOrderKey( + PermissionGrantInfoDto permission, + Dictionary byName, + IReadOnlyList definitions) + { + var chain = new List(); + + // Ust yetki listede yoksa (yetki suzulmusse) zincir orada biter. + for (var current = permission; current != null; current = current.ParentName == null ? null : byName.GetOrDefault(current.ParentName)) + { + chain.Insert(0, GetOrder(definitions, current.Name).ToString("D6")); + } + + // Ayirici rakamlardan kucuk bir karakterdir; boylece ebeveynin anahtari cocugunkinin onune duser. + return string.Join('/', chain); + } + + private static int GetOrder(IReadOnlyList definitions, string? name) + => GetOrder(definitions.FirstOrDefault(definition => definition.Name == name)?.Properties); + + private static int GetOrder(IReadOnlyList groups, string? name) + => GetOrder(groups.FirstOrDefault(group => group.Name == name)?.Properties); + + /// Deger kayitta yoksa (elle eklenmis yetki/grup) sona atilir. + private static int GetOrder(IDictionary? properties) + { + var value = properties?.GetOrDefault(PlatformConsts.Permissions.Order); + + return value == null ? int.MaxValue : Convert.ToInt32(value); + } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json index 7a330679..676f256c 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json @@ -1926,6 +1926,90 @@ "en": "Jobs", "tr": "İşler" }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.Sms", + "en": "SMS", + "tr": "SMS" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.Mail", + "en": "E-mail", + "tr": "E-posta" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.Rocket", + "en": "Rocket.Chat", + "tr": "Rocket.Chat" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.Desktop", + "en": "Desktop", + "tr": "Masaüstü" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.UiActivity", + "en": "Activity", + "tr": "Etkinlik" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.UiToast", + "en": "In-app", + "tr": "Uygulama içi" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.WhatsApp", + "en": "WhatsApp", + "tr": "WhatsApp" + }, + { + "resourceName": "Platform", + "key": "App.NotificationChannel.Telegram", + "en": "Telegram", + "tr": "Telegram" + }, + { + "resourceName": "Platform", + "key": "Abp.Identity.NotificationSettings.Reset", + "en": "Reset to default", + "tr": "Varsayılana döndür" + }, + { + "resourceName": "Platform", + "key": "Abp.Identity.NotificationSettings.NoRule", + "en": "There is no notification rule addressing you yet.", + "tr": "Sizi hedefleyen bir bildirim kuralı henüz yok." + }, + { + "resourceName": "Platform", + "key": "App.Notifications.Empty", + "en": "No notifications!", + "tr": "Bildirim yok!" + }, + { + "resourceName": "Platform", + "key": "App.Notifications.EmptyHint", + "en": "Please try again later", + "tr": "Lütfen daha sonra tekrar deneyin" + }, + { + "resourceName": "Platform", + "key": "App.Notifications.CreateTitle", + "en": "Send notification", + "tr": "Bildirim gönder" + }, + { + "resourceName": "Platform", + "key": "App.Notifications.CreateHint", + "en": "The message is delivered to every recipient of this rule through its channel.", + "tr": "Mesaj bu kuralın tüm alıcılarına kendi kanalından iletilir." + }, { "resourceName": "Platform", "key": "App.Notifications", @@ -28065,14 +28149,14 @@ { "resourceName": "Platform", "key": "App.Notifications.NotificationTypes.Description", - "en": "Notification Types — the event definitions that produce notifications. Table: Sas_H_NotificationType · host scope · list screen.\nUsage: one row per event that should notify someone — NewOrder, OrderApproved, StockBelowCritical, NewUser, ApprovalPending. The type is the joint that binds the event to the rules: the rule screen selects a type and the code raising the notification passes the same name.\nFields:\n• Name → the type code referenced from everywhere. Write it stable and descriptive; it is used from SQL and from services\nHow it is triggered: NotificationManager.SendNotificationAsync(typeId, message) reads every rule of the type and produces one notification row per recipient. The trigger may be a screen's InsertAfterCommand/UpdateAfterCommand, a stored procedure, a Dynamic Service, or the 'Create' dialog on the Notification Rules screen.\nOrder of setup: 1) define the type here, 2) define the recipient and channel on Notification Rules, 3) make sure the NotificationWorker job is active.\nCaution: renaming a type breaks every caller referencing the old name — add a new type instead. A type with no rule produces no notification, it fails silently.", - "tr": "Bildirim Türleri — bildirim üreten olay tanımları. Tablo: Sas_H_NotificationType · host kapsamlı · liste ekranı.\nKullanım: birine haber verilmesi gereken her olay için bir satır — YeniSiparis, SiparisOnaylandi, StokKritikAltinda, YeniKullanici, OnayBekliyor. Tür, olayı kurallara bağlayan eklem yeridir: kural ekranı bir tür seçer, bildirimi tetikleyen kod da aynı adı geçirir.\nAlanlar:\n• Name → her yerden referans verilen tür kodu. Kalıcı ve açıklayıcı yaz; SQL'den ve servislerden kullanılır\nNasıl tetiklenir: NotificationManager.SendNotificationAsync(typeId, mesaj) çağrısı türe ait tüm kuralları okur ve her alıcı için bir bildirim satırı üretir. Tetikleyici; ekranın InsertAfterCommand/UpdateAfterCommand tanımı, saklı yordam, Dynamic Service veya Bildirim Kuralları ekranındaki 'Create' diyaloğu olabilir.\nKurulum sırası: 1) türü burada tanımla, 2) Bildirim Kuralları ekranında alıcı ve kanalı tanımla, 3) NotificationWorker işinin aktif olduğundan emin ol.\nDikkat: türün adını değiştirmek eski adı referans veren tüm çağıranları bozar — bunun yerine yeni tür ekle. Kuralı olmayan tür bildirim üretmez, sessizce çalışır." + "en": "Notification Types — the event definitions that produce notifications. Table: Sas_H_NotificationType · host scope · list screen.\nUsage: one row per event that should notify someone — NewOrder, OrderApproved, StockBelowCritical, NewUser, ApprovalPending. The type is the joint that binds the event to the rules: the rule screen selects a type and the code raising the notification passes the same name.\nFields:\n• Name → the type code referenced from everywhere. Write it stable and descriptive; it is used from SQL and from services\n• Description → what the type is for: which event raises it and who is expected to act. It is shown as a hint on the user notification settings screen, so write it for the reader, at length if needed\nHow it is triggered: NotificationManager.SendNotificationAsync(typeId, message) reads every rule of the type and produces one notification row per recipient. The trigger may be a screen's InsertAfterCommand/UpdateAfterCommand, a stored procedure, a Dynamic Service, or the 'Create' dialog on the Notification Rules screen.\nOrder of setup: 1) define the type here, 2) define the recipient and channel on Notification Rules, 3) make sure the NotificationWorker job is active.\nCaution: renaming a type breaks every caller referencing the old name — add a new type instead. A type with no rule produces no notification, it fails silently.", + "tr": "Bildirim Türleri — bildirim üreten olay tanımları. Tablo: Sas_H_NotificationType · host kapsamlı · liste ekranı.\nKullanım: birine haber verilmesi gereken her olay için bir satır — YeniSiparis, SiparisOnaylandi, StokKritikAltinda, YeniKullanici, OnayBekliyor. Tür, olayı kurallara bağlayan eklem yeridir: kural ekranı bir tür seçer, bildirimi tetikleyen kod da aynı adı geçirir.\nAlanlar:\n• Name → her yerden referans verilen tür kodu. Kalıcı ve açıklayıcı yaz; SQL'den ve servislerden kullanılır\n• Description → türün ne işe yaradığı: hangi olayda tetiklendiği ve kimin ne yapması beklendiği. Kullanıcının bildirim tercihleri ekranında ipucu olarak görünür, gerekirse uzun uzun yaz\nNasıl tetiklenir: NotificationManager.SendNotificationAsync(typeId, mesaj) çağrısı türe ait tüm kuralları okur ve her alıcı için bir bildirim satırı üretir. Tetikleyici; ekranın InsertAfterCommand/UpdateAfterCommand tanımı, saklı yordam, Dynamic Service veya Bildirim Kuralları ekranındaki 'Create' diyaloğu olabilir.\nKurulum sırası: 1) türü burada tanımla, 2) Bildirim Kuralları ekranında alıcı ve kanalı tanımla, 3) NotificationWorker işinin aktif olduğundan emin ol.\nDikkat: türün adını değiştirmek eski adı referans veren tüm çağıranları bozar — bunun yerine yeni tür ekle. Kuralı olmayan tür bildirim üretmez, sessizce çalışır." }, { "resourceName": "Platform", "key": "App.Notifications.NotificationRules.Description", - "en": "Notification Rules — who receives which event, through which channel. Table: Sas_H_NotificationRule · host scope · list screen. The 'Create' row command produces a notification by hand from the selected rule (CreateNotification dialog).\nFields:\n• NotificationTypeId → which event; the lookup comes from the Notification Types screen\n• RecipientType + RecipientId → who receives it (see below)\n• Channel → through which channel it is delivered (see below)\n• IsActive → is the rule on\n• IsFixed → the user cannot turn it off from the profile; mandatory notification\n• IsCustomized → the user has written their own preference for this type/channel\nRecipientType — who the rule addresses:\n• All → every active user\n• User → RecipientId is the user name (AbpUsers.UserName)\n• Role → RecipientId is the role name; everyone holding the role receives it\n• OrganizationUnit → RecipientId is the unit code; the unit and all of its children receive it\n• Custom → RecipientId is the address itself (phone, e-mail, chat id); no user lookup is made, so it can also notify someone outside the system\nChannel setup — for every channel except Custom the address is resolved from the user record:\n• Mail → AbpUsers.Email. Sender settings: Abp.Mailing.* and App.Sender.Mail.*\n• Sms → AbpUsers.PhoneNumber. Settings: App.Sender.Sms.PostaGuvercini.* (Url, Username, Password)\n• WhatsApp → AbpUsers.PhoneNumber, normalized to +90…. Settings: App.Sender.WhatsApp.Url, PhoneNumberId, Token, TemplateName. The message is sent through an approved template\n• Telegram → AbpUsers.TelegramChatId. Settings: App.Sender.Telegram.Url, BotToken, ParseMode. How to obtain the chat id: the user sends /start to the bot once, then the id is read from the result[].message.chat.id field of https://api.telegram.org/bot/getUpdates and written into the Telegram Chat Id field of Identity › Users (the user sees it read-only on their profile). Without /start the send fails with 'chat not found'\n• Rocket → the user's RocketUsername. Settings: App.Sender.Rocket.Url, UserId, Token\n• Desktop / UiActivity / UiToast → the user id itself; delivered inside the application (activity log, toast, browser notification)\nFlow: the rule produces a Notification row, then the NotificationWorker (Background Workers › Recurring Jobs, WorkerType = NotificationWorker, cron */5 by default) empties the queue channel by channel.\nCaution: if several rules exist for the same type and channel, precedence is IsFixed › IsCustomized › IsActive and only one of them runs. A rule whose channel address is empty on the user is skipped silently — when a message does not arrive, look at the Notifications screen first: an empty Identifier means a missing address, IsSent = false means a failing send.", - "tr": "Bildirim Kuralları — hangi olayı kim, hangi kanaldan alacak. Tablo: Sas_H_NotificationRule · host kapsamlı · liste ekranı. Satırdaki 'Create' komutu seçili kuraldan elle bildirim üretir (CreateNotification diyaloğu).\nAlanlar:\n• NotificationTypeId → hangi olay; lookup Bildirim Türleri ekranından gelir\n• RecipientType + RecipientId → kimin alacağı (aşağıya bak)\n• Channel → hangi kanaldan iletileceği (aşağıya bak)\n• IsActive → kural açık mı\n• IsFixed → kullanıcı profilinden kapatamaz; zorunlu bildirim\n• IsCustomized → kullanıcı bu tür/kanal için kendi tercihini yazmış\nRecipientType — kural kimi hedefler:\n• All → tüm aktif kullanıcılar\n• User → RecipientId kullanıcı adıdır (AbpUsers.UserName)\n• Role → RecipientId rol adıdır; role sahip herkes alır\n• OrganizationUnit → RecipientId birim kodudur; birim ve tüm alt birimleri alır\n• Custom → RecipientId doğrudan adresin kendisidir (telefon, e-posta, chat id); kullanıcı araması yapılmaz, böylece sistem dışındaki birine de haber verilebilir\nKanal kurulumu — Custom dışındaki her kanalda adres kullanıcı kaydından çözülür:\n• Mail → AbpUsers.Email. Gönderim ayarları: Abp.Mailing.* ve App.Sender.Mail.*\n• Sms → AbpUsers.PhoneNumber. Ayarlar: App.Sender.Sms.PostaGuvercini.* (Url, Username, Password)\n• WhatsApp → AbpUsers.PhoneNumber, +90… biçimine normalize edilir. Ayarlar: App.Sender.WhatsApp.Url, PhoneNumberId, Token, TemplateName. Mesaj onaylı şablon üzerinden gider\n• Telegram → AbpUsers.TelegramChatId. Ayarlar: App.Sender.Telegram.Url, BotToken, ParseMode. Chat id nasıl alınır: kullanıcı bota bir kez /start yazar, ardından https://api.telegram.org/bot/getUpdates çıktısındaki result[].message.chat.id değeri okunur ve Identity › Kullanıcılar ekranındaki Telegram Chat Id alanına yazılır (kullanıcı kendi profilinde salt okunur görür). /start yazılmadan gönderim 'chat not found' hatası verir\n• Rocket → kullanıcının RocketUsername alanı. Ayarlar: App.Sender.Rocket.Url, UserId, Token\n• Desktop / UiActivity / UiToast → kullanıcının kendi kimliği; uygulama içinde iletilir (aktivite kaydı, toast, tarayıcı bildirimi)\nAkış: kural bir Notification satırı üretir, ardından NotificationWorker (Background Workers › Recurring Jobs, WorkerType = NotificationWorker, varsayılan cron */5) kuyruğu kanal kanal boşaltır.\nDikkat: aynı tür ve kanal için birden fazla kural varsa öncelik IsFixed › IsCustomized › IsActive sırasındadır ve yalnızca biri çalışır. Kanal adresi kullanıcıda boş olan kural sessizce atlanır — mesaj gitmediğinde önce Bildirimler ekranına bak: Identifier boşsa adres eksiktir, IsSent = false ise gönderim hata alıyordur." + "en": "Notification Rules — who receives which event, through which channel. Table: Sas_H_NotificationRule · host scope · list screen. The 'Create' row command produces a notification by hand from the selected rule (CreateNotification dialog).\nFields:\n• NotificationTypeId → which event; the lookup comes from the Notification Types screen\n• RecipientType + RecipientId → who receives it (see below)\n• Channel → through which channel it is delivered (see below)\n• IsActive → is the rule on\n• IsFixed → the user cannot turn it off from the profile; mandatory notification\n• IsCustomized → the user has written their own preference for this type/channel\nRecipientType — who the rule addresses:\n• All → every active user\n• User → RecipientId is the user name (AbpUsers.UserName)\n• Role → RecipientId is the role name; everyone holding the role receives it\n• OrganizationUnit → RecipientId is the unit code; the unit and all of its children receive it\n• Custom → RecipientId is the address itself (phone, e-mail, chat id); no user lookup is made, so it can also notify someone outside the system\nChannel setup — for every channel except Custom the address is resolved from the user record:\n• Mail → AbpUsers.Email. Sender settings: Abp.Mailing.* and App.Sender.Mail.*\n• Sms → AbpUsers.PhoneNumber. Settings: App.Sender.Sms.PostaGuvercini.* (Url, Username, Password)\n• WhatsApp → AbpUsers.PhoneNumber, normalized to +90…. Settings: App.Sender.WhatsApp.Url, PhoneNumberId, Token, TemplateName. The message is sent through an approved template\n• Telegram → AbpUsers.TelegramChatId. Settings: App.Sender.Telegram.Url, BotToken, ParseMode. How to obtain the chat id: the user sends /start to the bot once, then the id is read from the result[].message.chat.id field of https://api.telegram.org/bot/getUpdates and written into the Telegram Chat Id field of Identity › Users (the user sees it read-only on their profile). Without /start the send fails with 'chat not found'\n• Rocket → the user's RocketUsername. Settings: App.Sender.Rocket.Url, UserId, Token\n• Desktop / UiActivity / UiToast → the user id itself; delivered inside the application (activity log, toast, browser notification)\nFlow: the rule produces a Notification row, then the NotificationWorker (Background Workers › Recurring Jobs, WorkerType = NotificationWorker, cron */5 by default) empties the queue channel by channel.\nCaution: a user preference (IsCustomized with RecipientType = User) is not a rule: it only switches its own owner off for that type and channel, and an IsFixed rule ignores it. Rules themselves decide the recipients, and when several rules address the same user only one record is produced per channel. A rule whose channel address is empty on the user is skipped silently — when a message does not arrive, look at the Notifications screen first: an empty Identifier means a missing address, IsSent = false means a failing send.", + "tr": "Bildirim Kuralları — hangi olayı kim, hangi kanaldan alacak. Tablo: Sas_H_NotificationRule · host kapsamlı · liste ekranı. Satırdaki 'Create' komutu seçili kuraldan elle bildirim üretir (CreateNotification diyaloğu).\nAlanlar:\n• NotificationTypeId → hangi olay; lookup Bildirim Türleri ekranından gelir\n• RecipientType + RecipientId → kimin alacağı (aşağıya bak)\n• Channel → hangi kanaldan iletileceği (aşağıya bak)\n• IsActive → kural açık mı\n• IsFixed → kullanıcı profilinden kapatamaz; zorunlu bildirim\n• IsCustomized → kullanıcı bu tür/kanal için kendi tercihini yazmış\nRecipientType — kural kimi hedefler:\n• All → tüm aktif kullanıcılar\n• User → RecipientId kullanıcı adıdır (AbpUsers.UserName)\n• Role → RecipientId rol adıdır; role sahip herkes alır\n• OrganizationUnit → RecipientId birim kodudur; birim ve tüm alt birimleri alır\n• Custom → RecipientId doğrudan adresin kendisidir (telefon, e-posta, chat id); kullanıcı araması yapılmaz, böylece sistem dışındaki birine de haber verilebilir\nKanal kurulumu — Custom dışındaki her kanalda adres kullanıcı kaydından çözülür:\n• Mail → AbpUsers.Email. Gönderim ayarları: Abp.Mailing.* ve App.Sender.Mail.*\n• Sms → AbpUsers.PhoneNumber. Ayarlar: App.Sender.Sms.PostaGuvercini.* (Url, Username, Password)\n• WhatsApp → AbpUsers.PhoneNumber, +90… biçimine normalize edilir. Ayarlar: App.Sender.WhatsApp.Url, PhoneNumberId, Token, TemplateName. Mesaj onaylı şablon üzerinden gider\n• Telegram → AbpUsers.TelegramChatId. Ayarlar: App.Sender.Telegram.Url, BotToken, ParseMode. Chat id nasıl alınır: kullanıcı bota bir kez /start yazar, ardından https://api.telegram.org/bot/getUpdates çıktısındaki result[].message.chat.id değeri okunur ve Identity › Kullanıcılar ekranındaki Telegram Chat Id alanına yazılır (kullanıcı kendi profilinde salt okunur görür). /start yazılmadan gönderim 'chat not found' hatası verir\n• Rocket → kullanıcının RocketUsername alanı. Ayarlar: App.Sender.Rocket.Url, UserId, Token\n• Desktop / UiActivity / UiToast → kullanıcının kendi kimliği; uygulama içinde iletilir (aktivite kaydı, toast, tarayıcı bildirimi)\nAkış: kural bir Notification satırı üretir, ardından NotificationWorker (Background Workers › Recurring Jobs, WorkerType = NotificationWorker, varsayılan cron */5) kuyruğu kanal kanal boşaltır.\nDikkat: kullanıcı tercihi (IsCustomized + RecipientType = User) bir kural değildir: yalnızca kendi sahibini o tür ve kanalda kapatır, IsFixed kuralda ise dikkate alınmaz. Alıcıları kurallar belirler; aynı kullanıcıyı birden çok kural hedeflerse kanal başına tek kayıt üretilir. Kanal adresi kullanıcıda boş olan kural sessizce atlanır — mesaj gitmediğinde önce Bildirimler ekranına bak: Identifier boşsa adres eksiktir, IsSent = false ise gönderim hata alıyordur." }, { "resourceName": "Platform", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs index 17bc9e85..fe40256b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs @@ -179,6 +179,27 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep PermissionJson = DefaultFieldPermissionJson(AbpIdentity.PermissionGroups.Create, AbpIdentity.PermissionGroups.Default, AbpIdentity.PermissionGroups.Update, true, true, false), PivotSettingsJson = DefaultPivotSettingsJson }, + // Yetki ekranindaki grup sirasi; ABP kayitlari sirasiz okur. + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Int32, + FieldName = "Order", + EditGroupOrderNo = 1, + EditOrderNo = 4, + EditorType2 = EditorTypes.dxNumberBox, + ColSpan = 1, + CaptionName = "App.Listform.ListformField.Order", + Width = 0, + ListOrderNo = 5, + Visible = true, + IsActive = true, + + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(AbpIdentity.PermissionGroups.Create, AbpIdentity.PermissionGroups.Default, AbpIdentity.PermissionGroups.Update, true, true, false), + PivotSettingsJson = DefaultPivotSettingsJson + }, }); #endregion } @@ -440,6 +461,27 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep ValueExpr = "key", LookupQuery = LookupQueryValues.ModuleValues }), + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(AbpIdentity.Permissions.Create, AbpIdentity.Permissions.Default, AbpIdentity.Permissions.Update, true, true, false), + PivotSettingsJson = DefaultPivotSettingsJson + }, + // Yetki ekranindaki gosterim sirasi; ABP kayitlari sirasiz okudugu icin sira buradan gelir. + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Int32, + FieldName = "Order", + EditGroupOrderNo = 1, + EditOrderNo = 8, + EditorType2 = EditorTypes.dxNumberBox, + ColSpan = 1, + CaptionName = "App.Listform.ListformField.Order", + Width = 0, + ListOrderNo = 9, + Visible = true, + IsActive = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(AbpIdentity.Permissions.Create, AbpIdentity.Permissions.Default, AbpIdentity.Permissions.Update, true, true, false), diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs index 7790dca8..eaae32ce 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs @@ -5218,7 +5218,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency DeleteFieldsDefaultValueJson = DefaultDeleteFieldsDefaultValueJson(), InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(), PagerOptionJson = DefaultPagerOptionJson, - EditingOptionJson = DefaultEditingOptionJson(listFormName, 400, 200, true, true, true, true, false), + EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 420, true, true, true, true, false), EditingFormJson = JsonSerializer.Serialize(new List { new() { @@ -5262,6 +5262,27 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency ValidationRuleJson = DefaultValidationRuleRequiredJson, ColumnCustomizationJson = DefaultColumnCustomizationJson, PermissionJson = DefaultFieldPermissionJson(listForm.Name), + }, + new() + { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Description", + EditGroupOrderNo = 1, + EditOrderNo = 2, + EditorType2 = EditorTypes.dxTextArea, + EditorOptions = EditorOptions.Multiline(200), + ColSpan = 1, + CaptionName = "App.Listform.ListformField.Description", + Width = 0, + ListOrderNo = 3, + Visible = true, + IsActive = true, + + AllowSearch = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), } ]); #endregion diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 6887f2c5..d9f35592 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -31,6 +31,12 @@ public static class PlatformConsts public static class Permissions { public const string Modules = "Modules"; + + /// + /// Yetki agacinin gosterim sirasi. ABP kayitlari sirasiz okur; sira kayitta tasinmazsa + /// yetki ekrani her acilista baska bir dizilim gosterir. + /// + public const string Order = "Order"; } public static class Tenants diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs index 35e0d952..7cd490a5 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Sozsoft.Notifications.Entities; using Sozsoft.Notifications.Enums; @@ -14,17 +13,19 @@ using Sozsoft.Sender.Telegram; using Sozsoft.Sender.WhatsApp; using Volo.Abp.BackgroundWorkers; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Identity; using Volo.Abp.Settings; using Volo.Abp.Uow; namespace Sozsoft.Platform.BackgroundWorkers; +/// +/// Bildirim kuyrugunu kanal kanal bosaltir: gonderilmemis her kaydi kendi saglayicisina verir ve +/// yalnizca basarili gonderimde IsSent isaretler. Okundu bilgisi kullanicinin aksiyonudur, +/// gonderim onu belirlemez. Tetikleme zamanlanmis istir (Recurring Jobs, WorkerType = NotificationWorker). +/// public class NotificationWorker : BackgroundWorkerBase { protected IRepository Repository { get; } - protected IIdentityUserRepository UserRepository { get; } - protected IdentityUserManager IdentityUserManager { get; } protected ISozsoftRocketSender ErpRocketSender { get; } protected ISozsoftEmailSender ErpEmailSender { get; } protected ISozsoftSmsSender ErpSmsSender { get; } @@ -34,8 +35,6 @@ public class NotificationWorker : BackgroundWorkerBase public NotificationWorker( IRepository repository, - IIdentityUserRepository userRepository, - IdentityUserManager identityUserManager, ISozsoftSmsSender erpSmsSender, ISozsoftRocketSender erpRocketSender, ISozsoftEmailSender erpEmailSender, @@ -45,8 +44,6 @@ public class NotificationWorker : BackgroundWorkerBase { Repository = repository; ErpSmsSender = erpSmsSender; - UserRepository = userRepository; - IdentityUserManager = identityUserManager; ErpRocketSender = erpRocketSender; ErpEmailSender = erpEmailSender; ErpWhatsAppSender = erpWhatsAppSender; @@ -56,115 +53,79 @@ public class NotificationWorker : BackgroundWorkerBase public override async Task StartAsync(CancellationToken cancellationToken = default) { - using var scope = ServiceProvider.CreateScope(); var uowManager = LazyServiceProvider.LazyGetRequiredService(); using var uow = uowManager.Begin(); - var sms = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.Sms && !a.IsSent, cancellationToken: cancellationToken); - foreach (var notification in sms) - { - try - { - var result = await ErpSmsSender.SendAsync(notification.Identifier, notification.Message); - if (result.Success) - { - notification.IsSent = true; - notification.IsRead = true; - await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); - } - } - catch (Exception ex) - { - Logger.LogException(ex); - } - } - - var rocket = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.Rocket && !a.IsSent, cancellationToken: cancellationToken); - foreach (var notification in rocket) - { - try - { - var result = await ErpRocketSender.SendAsync(notification.Identifier, notification.Message); - if (result.Success) - { - notification.IsSent = true; - notification.IsRead = true; - await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); - } - } - catch (Exception ex) - { - Logger.LogException(ex); - } - } - - var mail = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.Mail && !a.IsSent, cancellationToken: cancellationToken); - var senderName = await SettingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName); - var senderEmail = await SettingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress); - foreach (var notification in mail) - { - try - { - var result = await ErpEmailSender.SendEmailAsync( - notification.Identifier, - new KeyValuePair(senderName, senderEmail), - new { }, - notification.Message, - $"Platform Bildirim: {notification.NotificationType}"); - - if (result.Success) - { - notification.IsSent = true; - notification.IsRead = true; - await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); - } - } - catch (Exception ex) - { - Logger.LogException(ex); - } - } - - var whatsapp = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.WhatsApp && !a.IsSent, cancellationToken: cancellationToken); - foreach (var notification in whatsapp) - { - try - { - var result = await ErpWhatsAppSender.SendAsync(notification.Identifier, notification.Message); - if (result.Success) - { - notification.IsSent = true; - notification.IsRead = true; - await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); - } - } - catch (Exception ex) - { - Logger.LogException(ex); - } - } - - var telegram = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.Telegram && !a.IsSent, cancellationToken: cancellationToken); - foreach (var notification in telegram) - { - try - { - var result = await ErpTelegramSender.SendAsync(notification.Identifier, notification.Message); - if (result.Success) - { - notification.IsSent = true; - notification.IsRead = true; - await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); - } - } - catch (Exception ex) - { - Logger.LogException(ex); - } - } + await SendChannelAsync(NotificationChannels.Sms, async notification => (await ErpSmsSender.SendAsync(notification.Identifier, notification.Message)).Success, cancellationToken); + await SendChannelAsync(NotificationChannels.Rocket, async notification => (await ErpRocketSender.SendAsync(notification.Identifier, notification.Message)).Success, cancellationToken); + await SendChannelAsync(NotificationChannels.WhatsApp, async notification => (await ErpWhatsAppSender.SendAsync(notification.Identifier, notification.Message)).Success, cancellationToken); + await SendChannelAsync(NotificationChannels.Telegram, async notification => (await ErpTelegramSender.SendAsync(notification.Identifier, notification.Message)).Success, cancellationToken); + await SendMailAsync(cancellationToken); await uow.SaveChangesAsync(cancellationToken); } + + /// + /// Bir kanalin bekleyen kayitlarini gonderir. Bir kaydin hatasi digerlerini durdurmaz; hata + /// loglanir ve kayit gonderilmemis kalir, boylece bir sonraki calismada yeniden denenir. + /// + private async Task SendChannelAsync( + string channel, + Func> send, + CancellationToken cancellationToken, + bool includeDetails = false) + { + var pending = await Repository.GetListAsync( + notification => notification.NotificationChannel == channel && !notification.IsSent, + includeDetails, + cancellationToken); + + foreach (var notification in pending) + { + try + { + if (!await send(notification)) + { + continue; + } + + notification.IsSent = true; + await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); + } + catch (Exception ex) + { + Logger.LogException(ex); + } + } + } + + /// + /// Mail kanali gonderen kimligini de tasir; bu yuzden ayri durur. Gonderen ayari eksikse + /// kuyruk denenmez — her kayit icin ayni hatayi almak yerine tek satir uyari yazilir. + /// + private async Task SendMailAsync(CancellationToken cancellationToken) + { + var senderName = await SettingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromDisplayName); + var senderEmail = await SettingProvider.GetOrNullAsync(SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress); + + if (senderEmail.IsNullOrWhiteSpace()) + { + Logger.LogWarning( + "Mail notifications are not sent: the {Setting} setting is empty.", + SeedConsts.AbpSettings.Mailing.Default.DefaultFromAddress); + return; + } + + // Konu satirinda bildirim tipinin adi gecer; navigasyon yuklenmezse konu bos kalirdi. + await SendChannelAsync( + NotificationChannels.Mail, + async notification => (await ErpEmailSender.SendEmailAsync( + notification.Identifier, + new KeyValuePair(senderName ?? senderEmail!, senderEmail!), + new { }, + notification.Message, + $"Platform Bildirim: {notification.NotificationType?.Name}")).Success, + cancellationToken, + includeDetails: true); + } } - - diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs index 72093597..7376f087 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs @@ -487,6 +487,24 @@ public static class PlatformEfCoreEntityExtensionMappings propertyBuilder.HasMaxLength(256).HasDefaultValue(null); } ); + + ObjectExtensionManager.Instance + .MapEfCoreProperty( + PlatformConsts.Permissions.Order, + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasDefaultValue(0); + } + ); + + ObjectExtensionManager.Instance + .MapEfCoreProperty( + PlatformConsts.Permissions.Order, + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasDefaultValue(0); + } + ); }); } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.Designer.cs index c1185765..a928906d 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260918122519_Initial")] + [Migration("20260920201729_Initial")] partial class Initial { /// @@ -680,6 +680,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -8399,6 +8403,11 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Order") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.Property("ParentName") .HasMaxLength(128) .HasColumnType("nvarchar(128)"); @@ -8475,6 +8484,11 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Order") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.HasKey("Id"); b.HasIndex("Name") diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.cs index f79e372c..414557b1 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260918122519_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260920201729_Initial.cs @@ -221,7 +221,8 @@ namespace Sozsoft.Platform.Migrations Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), DisplayName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true) + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + Order = table.Column(type: "int", nullable: false, defaultValue: 0) }, constraints: table => { @@ -242,7 +243,8 @@ namespace Sozsoft.Platform.Migrations Providers = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), StateCheckers = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - Modules = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) + Modules = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Order = table.Column(type: "int", nullable: false, defaultValue: 0) }, constraints: table => { @@ -1534,6 +1536,7 @@ namespace Sozsoft.Platform.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Name = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Description = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), CreatorId = table.Column(type: "uniqueidentifier", nullable: true), LastModificationTime = table.Column(type: "datetime2", nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 468c4476..f76353bf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -677,6 +677,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("Description") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnType("bit") @@ -8396,6 +8400,11 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Order") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.Property("ParentName") .HasMaxLength(128) .HasColumnType("nvarchar(128)"); @@ -8472,6 +8481,11 @@ namespace Sozsoft.Platform.Migrations .HasMaxLength(128) .HasColumnType("nvarchar(128)"); + b.Property("Order") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + b.HasKey("Id"); b.HasIndex("Name") diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index cc9afdc7..41ab2ccd 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -29,6 +29,7 @@ using Sozsoft.Mcp; using Sozsoft.Languages; using Sozsoft.MailQueue; using Sozsoft.Notifications.Application; +using Sozsoft.Platform.Notifications; using Sozsoft.Platform.DbStartup; using Sozsoft.Platform.DynamicServices; using Sozsoft.Platform.EntityFrameworkCore; @@ -508,7 +509,10 @@ public class PlatformHttpApiHostModule : AbpModule app.Use(async (context, next) => { - if (context.Request.Path.StartsWithSegments("/messengerhub") && + // SignalR tarayici istemcisi WebSocket disindaki tasimalarda Authorization basligi + // gonderemez; token sorgu dizesinden gelir ve baslik olarak yazilir. + if ((context.Request.Path.StartsWithSegments("/messengerhub") || + context.Request.Path.StartsWithSegments("/notificationhub")) && context.Request.Query.TryGetValue("access_token", out var accessToken) && !string.IsNullOrWhiteSpace(accessToken.ToString())) { @@ -561,6 +565,7 @@ public class PlatformHttpApiHostModule : AbpModule app.UseConfiguredEndpoints(endpoints => { endpoints.MapHub("/messengerhub"); + endpoints.MapHub("/notificationhub"); // MCP (Model Context Protocol) ucu. Kimlik dogrulama, kiraci cozumu ve unit of work // yukaridaki ardisik duzenden gelir; araclar kendi [Authorize] yetkilerini ister. diff --git a/claude.md b/claude.md index 935aca81..faefaef6 100644 --- a/claude.md +++ b/claude.md @@ -181,7 +181,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → `IListFormArtifact` / `IListFormFieldArtifact` sözleşmeleri üzerinden yapılır. Ayrıntı: `dotnet.instructions.md` §5. - **Yeni bir yetki** → `PlatformConsts.AppCodes` + `data/Abp.Identity.Permissions.json` + (UI'da kullanılacaksa) - `permission.constant.ts` + `LanguagesData.json`. + `permission.constant.ts` + `LanguagesData.json`. Yetki ekranındaki sıra `AbpPermissions.Order` + (grup için `AbpPermissionGroups.Order`) sütunundan gelir ve seed satırındaki `Order` alanıyla + yazılır; sıra kardeşler arasındadır — her üst yetkinin altında 1'den başlar, yeni satırı menüdeki + yerine denk gelen numarayla ekle. - **`/mcp` ucuna yeni bir araç** → üç yer birlikte değişir: `McpToolNames` (ad kayıt defteri), ilgili `Mcp*Tools` sınıfı (`Sozsoft.Mcp.HttpApi`) ve arkasındaki AppService sözleşmesi (`Sozsoft.Mcp.Application.Contracts`). Arac adı `McpToolNames.All` içinde yoksa araç listesi @@ -213,6 +216,15 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → karşılık gelen yetkiyle eklenir. `create_table` bu yüzden `.Develop`'a ek olarak `App.SqlQueryManager` ister; tablo scriptini tasarımcının `generateCreateTableSql` çıktısıyla aynı biçimde üretir (`McpTableCreator`) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir. +- **Bildirim bayrakları** → sıra değişmez: `IsSent` (iletildi) olmadan `IsRead` yazılmaz, `IsRead` + ile `ReadTime` birlikte yazılır. `UiActivity` kayıt oluşurken iletilmiş sayılır, `UiToast`/`Desktop` + tarayıcıda gösterildiğinde (`NotificationChannels.BrowserChannels`; `sent-many` ucu yalnızca bu + kümeyi işaretler), kalan kanalları `NotificationWorker` işaretler — gönderim okuma değildir. + Kullanıcı tercihi (`IsCustomized` + `RecipientType = User`) kural değildir, yalnızca sahibini + kapatır; alıcıyı kurallar belirler. Yeni bildirim üretildiği anda `/notificationhub` ile + duyurulur (`INotificationRealtimeNotifier`; sözleşme bildirim modülünün Domain'inde, SignalR + gerçeklemesi barındıran uygulamada — modül katmanı ASP.NET Core'a bağlanmaz) — duyuru en iyi çabadır, istemcideki yedek yoklama kaldırılmaz. + Ayrıntı: `README.md` §13.3–§13.5. - **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup olmadığını kontrol et. - **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca diff --git a/configs/deployment/configs/nginx.conf b/configs/deployment/configs/nginx.conf index bf920d03..91963f8f 100644 --- a/configs/deployment/configs/nginx.conf +++ b/configs/deployment/configs/nginx.conf @@ -111,6 +111,20 @@ server { proxy_send_timeout 3600; } + location /notificationhub { + proxy_pass http://127.0.0.1:8080/notificationhub; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header __tenant $http___tenant; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3600; + proxy_send_timeout 3600; + } + # MCP (Model Context Protocol) ucu. Streamable HTTP yanitlari SSE akisidir: location / # altindaki tamponlama akisi bekletip istemcinin zaman asimina dusmesine yol acar, # bu yuzden ayri bir blokta tamponlama kapatilir. diff --git a/configs/seeds/host/data/Abp.Identity.PermissionGroups.json b/configs/seeds/host/data/Abp.Identity.PermissionGroups.json index df703db2..b75a6369 100644 --- a/configs/seeds/host/data/Abp.Identity.PermissionGroups.json +++ b/configs/seeds/host/data/Abp.Identity.PermissionGroups.json @@ -6,11 +6,13 @@ "Rows": [ { "Name": "App.Saas", - "DisplayName": "App.Saas" + "DisplayName": "App.Saas", + "Order": 1 }, { "Name": "App.Administration", - "DisplayName": "App.Administration" + "DisplayName": "App.Administration", + "Order": 2 } ] } diff --git a/configs/seeds/host/data/Abp.Identity.Permissions.json b/configs/seeds/host/data/Abp.Identity.Permissions.json index 21e01f2c..0ff99ec0 100644 --- a/configs/seeds/host/data/Abp.Identity.Permissions.json +++ b/configs/seeds/host/data/Abp.Identity.Permissions.json @@ -9,6 +9,7 @@ "Name": "App.Saas.Definitions", "ParentName": null, "DisplayName": "App.Platform.Definitions", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -18,6 +19,7 @@ "Name": "App.Definitions.AiBot", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.AiBot", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -27,6 +29,7 @@ "Name": "App.Definitions.AiBot.Create", "ParentName": "App.Definitions.AiBot", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -36,6 +39,7 @@ "Name": "App.Definitions.AiBot.Delete", "ParentName": "App.Definitions.AiBot", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -45,6 +49,7 @@ "Name": "App.Definitions.AiBot.Export", "ParentName": "App.Definitions.AiBot", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -54,6 +59,7 @@ "Name": "App.Definitions.AiBot.Import", "ParentName": "App.Definitions.AiBot", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -63,6 +69,7 @@ "Name": "App.Definitions.AiBot.Update", "ParentName": "App.Definitions.AiBot", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -72,6 +79,7 @@ "Name": "App.Definitions.AiBot.Asistant", "ParentName": "App.Definitions.AiBot", "DisplayName": "App.Definitions.AiBot.Asistant", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -81,6 +89,7 @@ "Name": "App.Definitions.GlobalSearch", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.GlobalSearch", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -90,6 +99,7 @@ "Name": "App.Definitions.GlobalSearch.Create", "ParentName": "App.Definitions.GlobalSearch", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -99,6 +109,7 @@ "Name": "App.Definitions.GlobalSearch.Delete", "ParentName": "App.Definitions.GlobalSearch", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -108,6 +119,7 @@ "Name": "App.Definitions.GlobalSearch.Export", "ParentName": "App.Definitions.GlobalSearch", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -117,6 +129,7 @@ "Name": "App.Definitions.GlobalSearch.Import", "ParentName": "App.Definitions.GlobalSearch", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -126,6 +139,7 @@ "Name": "App.Definitions.GlobalSearch.Update", "ParentName": "App.Definitions.GlobalSearch", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -135,6 +149,7 @@ "Name": "App.Definitions.ContactTitle", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.ContactTitle", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -144,6 +159,7 @@ "Name": "App.Definitions.ContactTitle.Create", "ParentName": "App.Definitions.ContactTitle", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -153,6 +169,7 @@ "Name": "App.Definitions.ContactTitle.Update", "ParentName": "App.Definitions.ContactTitle", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -162,6 +179,7 @@ "Name": "App.Definitions.ContactTitle.Delete", "ParentName": "App.Definitions.ContactTitle", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -171,6 +189,7 @@ "Name": "App.Definitions.ContactTitle.Export", "ParentName": "App.Definitions.ContactTitle", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -180,6 +199,7 @@ "Name": "App.Definitions.ContactTitle.Import", "ParentName": "App.Definitions.ContactTitle", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -189,6 +209,7 @@ "Name": "App.Definitions.Currency", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.Currency", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -198,6 +219,7 @@ "Name": "App.Definitions.Currency.Create", "ParentName": "App.Definitions.Currency", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -207,6 +229,7 @@ "Name": "App.Definitions.Currency.Update", "ParentName": "App.Definitions.Currency", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -216,6 +239,7 @@ "Name": "App.Definitions.Currency.Delete", "ParentName": "App.Definitions.Currency", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -225,6 +249,7 @@ "Name": "App.Definitions.Currency.Export", "ParentName": "App.Definitions.Currency", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -234,6 +259,7 @@ "Name": "App.Definitions.Currency.Import", "ParentName": "App.Definitions.Currency", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -243,6 +269,7 @@ "Name": "App.Definitions.CountryGroup", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.CountryGroup", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -252,6 +279,7 @@ "Name": "App.Definitions.CountryGroup.Create", "ParentName": "App.Definitions.CountryGroup", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -261,6 +289,7 @@ "Name": "App.Definitions.CountryGroup.Update", "ParentName": "App.Definitions.CountryGroup", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -270,6 +299,7 @@ "Name": "App.Definitions.CountryGroup.Delete", "ParentName": "App.Definitions.CountryGroup", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -279,6 +309,7 @@ "Name": "App.Definitions.CountryGroup.Export", "ParentName": "App.Definitions.CountryGroup", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -288,6 +319,7 @@ "Name": "App.Definitions.CountryGroup.Import", "ParentName": "App.Definitions.CountryGroup", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -297,6 +329,7 @@ "Name": "App.Definitions.Country", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.Country", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -306,6 +339,7 @@ "Name": "App.Definitions.Country.Create", "ParentName": "App.Definitions.Country", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -315,6 +349,7 @@ "Name": "App.Definitions.Country.Update", "ParentName": "App.Definitions.Country", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -324,6 +359,7 @@ "Name": "App.Definitions.Country.Delete", "ParentName": "App.Definitions.Country", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -333,6 +369,7 @@ "Name": "App.Definitions.Country.Export", "ParentName": "App.Definitions.Country", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -342,6 +379,7 @@ "Name": "App.Definitions.Country.Import", "ParentName": "App.Definitions.Country", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -351,6 +389,7 @@ "Name": "App.Definitions.City", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.City", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -360,6 +399,7 @@ "Name": "App.Definitions.City.Create", "ParentName": "App.Definitions.City", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -369,6 +409,7 @@ "Name": "App.Definitions.City.Update", "ParentName": "App.Definitions.City", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -378,6 +419,7 @@ "Name": "App.Definitions.City.Delete", "ParentName": "App.Definitions.City", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -387,6 +429,7 @@ "Name": "App.Definitions.City.Export", "ParentName": "App.Definitions.City", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -396,6 +439,7 @@ "Name": "App.Definitions.City.Import", "ParentName": "App.Definitions.City", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -405,6 +449,7 @@ "Name": "App.Definitions.District", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.District", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -414,6 +459,7 @@ "Name": "App.Definitions.District.Create", "ParentName": "App.Definitions.District", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -423,6 +469,7 @@ "Name": "App.Definitions.District.Update", "ParentName": "App.Definitions.District", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -432,6 +479,7 @@ "Name": "App.Definitions.District.Delete", "ParentName": "App.Definitions.District", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -441,6 +489,7 @@ "Name": "App.Definitions.District.Export", "ParentName": "App.Definitions.District", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -450,6 +499,7 @@ "Name": "App.Definitions.District.Import", "ParentName": "App.Definitions.District", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -459,6 +509,7 @@ "Name": "App.Definitions.SkillType", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.SkillType", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -468,6 +519,7 @@ "Name": "App.Definitions.SkillType.Create", "ParentName": "App.Definitions.SkillType", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -477,6 +529,7 @@ "Name": "App.Definitions.SkillType.Update", "ParentName": "App.Definitions.SkillType", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -486,6 +539,7 @@ "Name": "App.Definitions.SkillType.Delete", "ParentName": "App.Definitions.SkillType", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -495,6 +549,7 @@ "Name": "App.Definitions.SkillType.Export", "ParentName": "App.Definitions.SkillType", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -504,6 +559,7 @@ "Name": "App.Definitions.SkillType.Import", "ParentName": "App.Definitions.SkillType", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -513,6 +569,7 @@ "Name": "App.Definitions.SkillType.Note", "ParentName": "App.Definitions.SkillType", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -522,6 +579,7 @@ "Name": "App.Definitions.SkillLevel", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.SkillLevel", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -531,6 +589,7 @@ "Name": "App.Definitions.SkillLevel.Create", "ParentName": "App.Definitions.SkillLevel", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -540,6 +599,7 @@ "Name": "App.Definitions.SkillLevel.Update", "ParentName": "App.Definitions.SkillLevel", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -549,6 +609,7 @@ "Name": "App.Definitions.SkillLevel.Delete", "ParentName": "App.Definitions.SkillLevel", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -558,6 +619,7 @@ "Name": "App.Definitions.SkillLevel.Export", "ParentName": "App.Definitions.SkillLevel", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -567,6 +629,7 @@ "Name": "App.Definitions.SkillLevel.Import", "ParentName": "App.Definitions.SkillLevel", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -576,6 +639,7 @@ "Name": "App.Definitions.Skill", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.Skill", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -585,6 +649,7 @@ "Name": "App.Definitions.Skill.Create", "ParentName": "App.Definitions.Skill", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -594,6 +659,7 @@ "Name": "App.Definitions.Skill.Update", "ParentName": "App.Definitions.Skill", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -603,6 +669,7 @@ "Name": "App.Definitions.Skill.Delete", "ParentName": "App.Definitions.Skill", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -612,6 +679,7 @@ "Name": "App.Definitions.Skill.Export", "ParentName": "App.Definitions.Skill", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -621,6 +689,7 @@ "Name": "App.Definitions.Skill.Import", "ParentName": "App.Definitions.Skill", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -630,6 +699,7 @@ "Name": "App.Definitions.UomCategory", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.UomCategory", + "Order": 12, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -639,6 +709,7 @@ "Name": "App.Definitions.UomCategory.Create", "ParentName": "App.Definitions.UomCategory", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -648,6 +719,7 @@ "Name": "App.Definitions.UomCategory.Update", "ParentName": "App.Definitions.UomCategory", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -657,6 +729,7 @@ "Name": "App.Definitions.UomCategory.Delete", "ParentName": "App.Definitions.UomCategory", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -666,6 +739,7 @@ "Name": "App.Definitions.UomCategory.Export", "ParentName": "App.Definitions.UomCategory", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -675,6 +749,7 @@ "Name": "App.Definitions.UomCategory.Import", "ParentName": "App.Definitions.UomCategory", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -684,6 +759,7 @@ "Name": "App.Definitions.UomCategory.Note", "ParentName": "App.Definitions.UomCategory", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -693,6 +769,7 @@ "Name": "App.Definitions.Uom", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.Uom", + "Order": 13, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -702,6 +779,7 @@ "Name": "App.Definitions.Uom.Create", "ParentName": "App.Definitions.Uom", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -711,6 +789,7 @@ "Name": "App.Definitions.Uom.Update", "ParentName": "App.Definitions.Uom", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -720,6 +799,7 @@ "Name": "App.Definitions.Uom.Delete", "ParentName": "App.Definitions.Uom", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -729,6 +809,7 @@ "Name": "App.Definitions.Uom.Export", "ParentName": "App.Definitions.Uom", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -738,6 +819,7 @@ "Name": "App.Definitions.Uom.Import", "ParentName": "App.Definitions.Uom", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -747,6 +829,7 @@ "Name": "App.Definitions.Sequence", "ParentName": "App.Saas.Definitions", "DisplayName": "App.Definitions.Sequence", + "Order": 14, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -756,6 +839,7 @@ "Name": "App.Definitions.Sequence.Create", "ParentName": "App.Definitions.Sequence", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -765,6 +849,7 @@ "Name": "App.Definitions.Sequence.Update", "ParentName": "App.Definitions.Sequence", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -774,6 +859,7 @@ "Name": "App.Definitions.Sequence.Delete", "ParentName": "App.Definitions.Sequence", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -783,6 +869,7 @@ "Name": "App.Definitions.Sequence.Export", "ParentName": "App.Definitions.Sequence", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -792,6 +879,7 @@ "Name": "App.Definitions.Sequence.Import", "ParentName": "App.Definitions.Sequence", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -801,6 +889,7 @@ "Name": "App.Public", "ParentName": null, "DisplayName": "App.Public", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -810,6 +899,7 @@ "Name": "App.Orders.Products", "ParentName": "App.Public", "DisplayName": "App.Orders.Products", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -819,6 +909,7 @@ "Name": "App.Orders.Products.Create", "ParentName": "App.Orders.Products", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -828,6 +919,7 @@ "Name": "App.Orders.Products.Delete", "ParentName": "App.Orders.Products", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -837,6 +929,7 @@ "Name": "App.Orders.Products.Export", "ParentName": "App.Orders.Products", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -846,6 +939,7 @@ "Name": "App.Orders.Products.Import", "ParentName": "App.Orders.Products", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -855,6 +949,7 @@ "Name": "App.Orders.Products.Update", "ParentName": "App.Orders.Products", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -864,6 +959,7 @@ "Name": "App.Orders.PaymentMethods", "ParentName": "App.Public", "DisplayName": "App.Orders.PaymentMethods", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -873,6 +969,7 @@ "Name": "App.Orders.PaymentMethods.Create", "ParentName": "App.Orders.PaymentMethods", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -882,6 +979,7 @@ "Name": "App.Orders.PaymentMethods.Delete", "ParentName": "App.Orders.PaymentMethods", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -891,6 +989,7 @@ "Name": "App.Orders.PaymentMethods.Export", "ParentName": "App.Orders.PaymentMethods", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -900,6 +999,7 @@ "Name": "App.Orders.PaymentMethods.Import", "ParentName": "App.Orders.PaymentMethods", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -909,6 +1009,7 @@ "Name": "App.Orders.PaymentMethods.Update", "ParentName": "App.Orders.PaymentMethods", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -918,6 +1019,7 @@ "Name": "App.Orders.InstallmentOptions", "ParentName": "App.Public", "DisplayName": "App.Orders.InstallmentOptions", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -927,6 +1029,7 @@ "Name": "App.Orders.InstallmentOptions.Create", "ParentName": "App.Orders.InstallmentOptions", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -936,6 +1039,7 @@ "Name": "App.Orders.InstallmentOptions.Delete", "ParentName": "App.Orders.InstallmentOptions", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -945,6 +1049,7 @@ "Name": "App.Orders.InstallmentOptions.Export", "ParentName": "App.Orders.InstallmentOptions", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -954,6 +1059,7 @@ "Name": "App.Orders.InstallmentOptions.Import", "ParentName": "App.Orders.InstallmentOptions", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -963,6 +1069,7 @@ "Name": "App.Orders.InstallmentOptions.Update", "ParentName": "App.Orders.InstallmentOptions", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -972,6 +1079,7 @@ "Name": "App.Orders.SalesOrders", "ParentName": "App.Public", "DisplayName": "App.Orders.SalesOrders", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -981,6 +1089,7 @@ "Name": "App.Orders.SalesOrders.Create", "ParentName": "App.Orders.SalesOrders", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -990,6 +1099,7 @@ "Name": "App.Orders.SalesOrders.Delete", "ParentName": "App.Orders.SalesOrders", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -999,6 +1109,7 @@ "Name": "App.Orders.SalesOrders.Export", "ParentName": "App.Orders.SalesOrders", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1008,6 +1119,7 @@ "Name": "App.Orders.SalesOrders.Import", "ParentName": "App.Orders.SalesOrders", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1017,6 +1129,7 @@ "Name": "App.Orders.SalesOrders.Update", "ParentName": "App.Orders.SalesOrders", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1026,6 +1139,7 @@ "Name": "App.Orders.SalesOrders.Note", "ParentName": "App.Orders.SalesOrders", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1035,6 +1149,7 @@ "Name": "App.Orders.SalesOrderItem", "ParentName": "App.Public", "DisplayName": "App.Orders.SalesOrderItem", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1044,6 +1159,7 @@ "Name": "App.Orders.SalesOrderItem.Create", "ParentName": "App.Orders.SalesOrderItem", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1053,6 +1169,7 @@ "Name": "App.Orders.SalesOrderItem.Delete", "ParentName": "App.Orders.SalesOrderItem", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1062,6 +1179,7 @@ "Name": "App.Orders.SalesOrderItem.Export", "ParentName": "App.Orders.SalesOrderItem", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1071,6 +1189,7 @@ "Name": "App.Orders.SalesOrderItem.Import", "ParentName": "App.Orders.SalesOrderItem", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1080,6 +1199,7 @@ "Name": "App.Orders.SalesOrderItem.Update", "ParentName": "App.Orders.SalesOrderItem", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1089,6 +1209,7 @@ "Name": "App.BlogManagement.Category", "ParentName": "App.Public", "DisplayName": "App.BlogManagement.Category", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1098,6 +1219,7 @@ "Name": "App.BlogManagement.Category.Create", "ParentName": "App.BlogManagement.Category", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1107,6 +1229,7 @@ "Name": "App.BlogManagement.Category.Delete", "ParentName": "App.BlogManagement.Category", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1116,6 +1239,7 @@ "Name": "App.BlogManagement.Category.Export", "ParentName": "App.BlogManagement.Category", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1125,6 +1249,7 @@ "Name": "App.BlogManagement.Category.Import", "ParentName": "App.BlogManagement.Category", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1134,6 +1259,7 @@ "Name": "App.BlogManagement.Category.Update", "ParentName": "App.BlogManagement.Category", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1143,6 +1269,7 @@ "Name": "App.BlogManagement.Posts", "ParentName": "App.Public", "DisplayName": "App.BlogManagement.Posts", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1152,6 +1279,7 @@ "Name": "App.BlogManagement.Posts.Create", "ParentName": "App.BlogManagement.Posts", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1161,6 +1289,7 @@ "Name": "App.BlogManagement.Posts.Delete", "ParentName": "App.BlogManagement.Posts", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1170,6 +1299,7 @@ "Name": "App.BlogManagement.Posts.Export", "ParentName": "App.BlogManagement.Posts", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1179,6 +1309,7 @@ "Name": "App.BlogManagement.Posts.Import", "ParentName": "App.BlogManagement.Posts", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1188,6 +1319,7 @@ "Name": "App.BlogManagement.Posts.Update", "ParentName": "App.BlogManagement.Posts", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1197,6 +1329,7 @@ "Name": "App.BlogManagement.Posts.Publish", "ParentName": "App.BlogManagement.Posts", "DisplayName": "Publish", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1206,6 +1339,7 @@ "Name": "App.Demos", "ParentName": "App.Public", "DisplayName": "App.Demos", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1215,6 +1349,7 @@ "Name": "App.Demos.Create", "ParentName": "App.Demos", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1224,6 +1359,7 @@ "Name": "App.Demos.Update", "ParentName": "App.Demos", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1233,6 +1369,7 @@ "Name": "App.Demos.Delete", "ParentName": "App.Demos", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1242,6 +1379,7 @@ "Name": "App.Demos.Export", "ParentName": "App.Demos", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1251,6 +1389,7 @@ "Name": "App.Demos.Import", "ParentName": "App.Demos", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1260,6 +1399,7 @@ "Name": "App.DevRequests", "ParentName": null, "DisplayName": "App.DevRequests", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1269,6 +1409,7 @@ "Name": "App.DevRequests.Create", "ParentName": "App.DevRequests", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1278,6 +1419,7 @@ "Name": "App.DevRequests.Update", "ParentName": "App.DevRequests", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1287,6 +1429,7 @@ "Name": "App.DevRequests.Delete", "ParentName": "App.DevRequests", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1296,6 +1439,7 @@ "Name": "App.DevRequests.Export", "ParentName": "App.DevRequests", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1305,6 +1449,7 @@ "Name": "App.DevRequests.Import", "ParentName": "App.DevRequests", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1314,6 +1459,7 @@ "Name": "App.DevRequests.Note", "ParentName": "App.DevRequests", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1323,6 +1469,7 @@ "Name": "AbpTenantManagement.Tenants", "ParentName": null, "DisplayName": "AbpTenantManagement.Tenants", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1332,6 +1479,7 @@ "Name": "AbpTenantManagement.Tenants.Create", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1341,6 +1489,7 @@ "Name": "AbpTenantManagement.Tenants.Delete", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1350,6 +1499,7 @@ "Name": "AbpTenantManagement.Tenants.Export", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1359,6 +1509,7 @@ "Name": "AbpTenantManagement.Tenants.Import", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1368,6 +1519,7 @@ "Name": "AbpTenantManagement.Tenants.ManageConnectionStrings", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "AbpTenantManagement.Tenants.ManageConnectionStrings", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1377,6 +1529,7 @@ "Name": "AbpTenantManagement.Tenants.ManageFeatures", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "AbpTenantManagement.Tenants.ManageFeatures", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1386,6 +1539,7 @@ "Name": "AbpTenantManagement.Tenants.Update", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "App.Platform.Update", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1395,6 +1549,7 @@ "Name": "AbpTenantManagement.Tenants.Note", "ParentName": "AbpTenantManagement.Tenants", "DisplayName": "Note", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1404,6 +1559,7 @@ "Name": "App.Branches", "ParentName": null, "DisplayName": "App.Branches", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1413,6 +1569,7 @@ "Name": "App.Branches.Create", "ParentName": "App.Branches", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1422,6 +1579,7 @@ "Name": "App.Branches.Delete", "ParentName": "App.Branches", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1431,6 +1589,7 @@ "Name": "App.Branches.Export", "ParentName": "App.Branches", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1440,6 +1599,7 @@ "Name": "App.Branches.Import", "ParentName": "App.Branches", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1449,6 +1609,7 @@ "Name": "App.Branches.Update", "ParentName": "App.Branches", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1458,6 +1619,7 @@ "Name": "App.SettingDefinitions", "ParentName": null, "DisplayName": "App.SettingDefinitions", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1467,6 +1629,7 @@ "Name": "App.SettingDefinitions.Create", "ParentName": "App.SettingDefinitions", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1476,6 +1639,7 @@ "Name": "App.SettingDefinitions.Delete", "ParentName": "App.SettingDefinitions", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1485,6 +1649,7 @@ "Name": "App.SettingDefinitions.Export", "ParentName": "App.SettingDefinitions", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1494,6 +1659,7 @@ "Name": "App.SettingDefinitions.Import", "ParentName": "App.SettingDefinitions", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1503,6 +1669,7 @@ "Name": "App.SettingDefinitions.Update", "ParentName": "App.SettingDefinitions", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1512,6 +1679,7 @@ "Name": "App.Languages", "ParentName": null, "DisplayName": "App.Languages", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1521,6 +1689,7 @@ "Name": "App.Languages.Language", "ParentName": "App.Languages", "DisplayName": "App.Languages.Language", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1530,6 +1699,7 @@ "Name": "App.Languages.Language.Create", "ParentName": "App.Languages.Language", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1539,6 +1709,7 @@ "Name": "App.Languages.Language.Delete", "ParentName": "App.Languages.Language", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1548,6 +1719,7 @@ "Name": "App.Languages.Language.Export", "ParentName": "App.Languages.Language", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1557,6 +1729,7 @@ "Name": "App.Languages.Language.Import", "ParentName": "App.Languages.Language", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1566,6 +1739,7 @@ "Name": "App.Languages.Language.Note", "ParentName": "App.Languages.Language", "DisplayName": "Note", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1575,6 +1749,7 @@ "Name": "App.Languages.Language.Update", "ParentName": "App.Languages.Language", "DisplayName": "App.Platform.Update", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1584,6 +1759,7 @@ "Name": "App.Languages.LanguageText", "ParentName": "App.Languages", "DisplayName": "App.Languages.LanguageText", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1593,6 +1769,7 @@ "Name": "App.Languages.LanguageText.Create", "ParentName": "App.Languages.LanguageText", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1602,6 +1779,7 @@ "Name": "App.Languages.LanguageText.Delete", "ParentName": "App.Languages.LanguageText", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1611,6 +1789,7 @@ "Name": "App.Languages.LanguageText.Export", "ParentName": "App.Languages.LanguageText", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1620,6 +1799,7 @@ "Name": "App.Languages.LanguageText.Import", "ParentName": "App.Languages.LanguageText", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1629,6 +1809,7 @@ "Name": "App.Languages.LanguageText.Update", "ParentName": "App.Languages.LanguageText", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1638,6 +1819,7 @@ "Name": "App.DataSource", "ParentName": null, "DisplayName": "App.DataSource", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1647,6 +1829,7 @@ "Name": "App.DataSource.Create", "ParentName": "App.DataSource", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1656,6 +1839,7 @@ "Name": "App.DataSource.Delete", "ParentName": "App.DataSource", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1665,6 +1849,7 @@ "Name": "App.DataSource.Export", "ParentName": "App.DataSource", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1674,6 +1859,7 @@ "Name": "App.DataSource.Import", "ParentName": "App.DataSource", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1683,6 +1869,7 @@ "Name": "App.DataSource.Update", "ParentName": "App.DataSource", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1692,6 +1879,7 @@ "Name": "App.Notifications", "ParentName": null, "DisplayName": "App.Notifications", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1701,6 +1889,7 @@ "Name": "App.Notifications.NotificationTypes", "ParentName": "App.Notifications", "DisplayName": "App.Notifications.NotificationTypes", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1710,6 +1899,7 @@ "Name": "App.Notifications.NotificationTypes.Create", "ParentName": "App.Notifications.NotificationTypes", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1719,6 +1909,7 @@ "Name": "App.Notifications.NotificationTypes.Delete", "ParentName": "App.Notifications.NotificationTypes", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1728,6 +1919,7 @@ "Name": "App.Notifications.NotificationTypes.Export", "ParentName": "App.Notifications.NotificationTypes", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1737,6 +1929,7 @@ "Name": "App.Notifications.NotificationTypes.Import", "ParentName": "App.Notifications.NotificationTypes", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1746,6 +1939,7 @@ "Name": "App.Notifications.NotificationTypes.Update", "ParentName": "App.Notifications.NotificationTypes", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1755,6 +1949,7 @@ "Name": "App.Notifications.NotificationRules", "ParentName": "App.Notifications", "DisplayName": "App.Notifications.NotificationRules", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1764,6 +1959,7 @@ "Name": "App.Notifications.NotificationRules.Create", "ParentName": "App.Notifications.NotificationRules", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1773,6 +1969,7 @@ "Name": "App.Notifications.NotificationRules.Delete", "ParentName": "App.Notifications.NotificationRules", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1782,6 +1979,7 @@ "Name": "App.Notifications.NotificationRules.Export", "ParentName": "App.Notifications.NotificationRules", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1791,6 +1989,7 @@ "Name": "App.Notifications.NotificationRules.Import", "ParentName": "App.Notifications.NotificationRules", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1800,6 +1999,7 @@ "Name": "App.Notifications.NotificationRules.Update", "ParentName": "App.Notifications.NotificationRules", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1809,6 +2009,7 @@ "Name": "App.Notifications.Notification", "ParentName": "App.Notifications", "DisplayName": "App.Notifications.Notification", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1818,6 +2019,7 @@ "Name": "App.Notifications.Notification.Create", "ParentName": "App.Notifications.Notification", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1827,6 +2029,7 @@ "Name": "App.Notifications.Notification.Delete", "ParentName": "App.Notifications.Notification", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1836,6 +2039,7 @@ "Name": "App.Notifications.Notification.Export", "ParentName": "App.Notifications.Notification", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1845,6 +2049,7 @@ "Name": "App.Notifications.Notification.Import", "ParentName": "App.Notifications.Notification", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1854,6 +2059,7 @@ "Name": "App.Notifications.Notification.Update", "ParentName": "App.Notifications.Notification", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1863,6 +2069,7 @@ "Name": "App.BackgroundWorkers", "ParentName": null, "DisplayName": "App.BackgroundWorkers", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1872,6 +2079,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs", "ParentName": "App.BackgroundWorkers", "DisplayName": "App.BackgroundWorkers.RecurringJobs", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1881,6 +2089,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs.Create", "ParentName": "App.BackgroundWorkers.RecurringJobs", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1890,6 +2099,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs.Delete", "ParentName": "App.BackgroundWorkers.RecurringJobs", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1899,6 +2109,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs.Export", "ParentName": "App.BackgroundWorkers.RecurringJobs", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1908,6 +2119,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs.Import", "ParentName": "App.BackgroundWorkers.RecurringJobs", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1917,6 +2129,7 @@ "Name": "App.BackgroundWorkers.RecurringJobs.Update", "ParentName": "App.BackgroundWorkers.RecurringJobs", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1926,6 +2139,7 @@ "Name": "App.BackgroundWorkers.Jobs", "ParentName": "App.BackgroundWorkers", "DisplayName": "App.BackgroundWorkers.Jobs", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1935,6 +2149,7 @@ "Name": "App.BackgroundWorkers.Jobs.Create", "ParentName": "App.BackgroundWorkers.Jobs", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1944,6 +2159,7 @@ "Name": "App.BackgroundWorkers.Jobs.Delete", "ParentName": "App.BackgroundWorkers.Jobs", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1953,6 +2169,7 @@ "Name": "App.BackgroundWorkers.Jobs.Export", "ParentName": "App.BackgroundWorkers.Jobs", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1962,6 +2179,7 @@ "Name": "App.BackgroundWorkers.Jobs.Import", "ParentName": "App.BackgroundWorkers.Jobs", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1971,6 +2189,7 @@ "Name": "App.BackgroundWorkers.Jobs.Update", "ParentName": "App.BackgroundWorkers.Jobs", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1980,6 +2199,7 @@ "Name": "App.Menus", "ParentName": null, "DisplayName": "App.Menus", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1989,6 +2209,7 @@ "Name": "App.Menus.Module", "ParentName": "App.Menus", "DisplayName": "App.Menus.Module", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1998,6 +2219,7 @@ "Name": "App.Menus.Module.Create", "ParentName": "App.Menus.Module", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2007,6 +2229,7 @@ "Name": "App.Menus.Module.Delete", "ParentName": "App.Menus.Module", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2016,6 +2239,7 @@ "Name": "App.Menus.Module.Export", "ParentName": "App.Menus.Module", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2025,6 +2249,7 @@ "Name": "App.Menus.Module.Import", "ParentName": "App.Menus.Module", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2034,6 +2259,7 @@ "Name": "App.Menus.Module.Update", "ParentName": "App.Menus.Module", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2043,6 +2269,7 @@ "Name": "App.Menus.Routes", "ParentName": "App.Menus", "DisplayName": "App.Menus.Routes", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2052,6 +2279,7 @@ "Name": "App.Menus.Routes.Create", "ParentName": "App.Menus.Routes", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2061,6 +2289,7 @@ "Name": "App.Menus.Routes.Delete", "ParentName": "App.Menus.Routes", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2070,6 +2299,7 @@ "Name": "App.Menus.Routes.Export", "ParentName": "App.Menus.Routes", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2079,6 +2309,7 @@ "Name": "App.Menus.Routes.Import", "ParentName": "App.Menus.Routes", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2088,6 +2319,7 @@ "Name": "App.Menus.Routes.Update", "ParentName": "App.Menus.Routes", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2097,6 +2329,7 @@ "Name": "App.Menus.Menu", "ParentName": "App.Menus", "DisplayName": "App.Menus.Menu", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2106,6 +2339,7 @@ "Name": "App.Menus.Menu.Create", "ParentName": "App.Menus.Menu", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2115,6 +2349,7 @@ "Name": "App.Menus.Menu.Delete", "ParentName": "App.Menus.Menu", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2124,6 +2359,7 @@ "Name": "App.Menus.Menu.Export", "ParentName": "App.Menus.Menu", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2133,6 +2369,7 @@ "Name": "App.Menus.Menu.Import", "ParentName": "App.Menus.Menu", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2142,6 +2379,7 @@ "Name": "App.Menus.Menu.Update", "ParentName": "App.Menus.Menu", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2151,6 +2389,7 @@ "Name": "App.Menus.Manager", "ParentName": "App.Menus", "DisplayName": "App.Menus.Manager", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2160,6 +2399,7 @@ "Name": "App.DeveloperKit", "ParentName": null, "DisplayName": "App.DeveloperKit", + "Order": 12, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2169,6 +2409,7 @@ "Name": "App.SqlQueryManager", "ParentName": "App.DeveloperKit", "DisplayName": "App.SqlQueryManager", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2178,6 +2419,7 @@ "Name": "App.SqlQueryManager.CrudEndpoints", "ParentName": "App.SqlQueryManager", "DisplayName": "App.SqlQueryManager.CrudEndpoints", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2187,6 +2429,7 @@ "Name": "App.Files", "ParentName": null, "DisplayName": "App.Files", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2196,6 +2439,7 @@ "Name": "App.Files.Create", "ParentName": "App.Files", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2205,6 +2449,7 @@ "Name": "App.Files.Update", "ParentName": "App.Files", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2214,6 +2459,7 @@ "Name": "App.Files.Delete", "ParentName": "App.Files", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2223,6 +2469,7 @@ "Name": "App.Files.Widget", "ParentName": "App.Files", "DisplayName": "App.Platform.Widget", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2232,6 +2479,7 @@ "Name": "App.DeveloperKit.CustomEndpoints", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.CustomEndpoints", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2241,6 +2489,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Create", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2250,6 +2499,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Delete", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2259,6 +2509,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Export", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2268,6 +2519,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Import", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2277,6 +2529,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Get", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.StaticLookup.Get", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2286,6 +2539,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Post", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.StaticLookup.Post", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2295,6 +2549,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Put", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.StaticLookup.Put", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2304,6 +2559,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Remove", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "Remove", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2313,6 +2569,7 @@ "Name": "App.DeveloperKit.CustomEndpoints.Update", "ParentName": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.Platform.Update", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2322,6 +2579,7 @@ "Name": "App.DeveloperKit.DynamicServices", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.DynamicServices", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2331,6 +2589,7 @@ "Name": "App.DeveloperKit.DynamicServices.Create", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2340,6 +2599,7 @@ "Name": "App.DeveloperKit.DynamicServices.Update", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2349,6 +2609,7 @@ "Name": "App.DeveloperKit.DynamicServices.Delete", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2358,6 +2619,7 @@ "Name": "App.DeveloperKit.DynamicServices.Manage", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "Manage", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2367,6 +2629,7 @@ "Name": "App.DeveloperKit.DynamicServices.TestCompile", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "TestCompile", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2376,6 +2639,7 @@ "Name": "App.DeveloperKit.DynamicServices.Publish", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "Publish", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2385,6 +2649,7 @@ "Name": "App.DeveloperKit.DynamicServices.ViewCode", "ParentName": "App.DeveloperKit.DynamicServices", "DisplayName": "ViewCode", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2394,6 +2659,7 @@ "Name": "App.DeveloperKit.CustomComponents", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.CustomComponents", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2403,6 +2669,7 @@ "Name": "App.DeveloperKit.CustomComponents.Create", "ParentName": "App.DeveloperKit.CustomComponents", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2412,6 +2679,7 @@ "Name": "App.DeveloperKit.CustomComponents.Update", "ParentName": "App.DeveloperKit.CustomComponents", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2421,6 +2689,7 @@ "Name": "App.DeveloperKit.CustomComponents.Delete", "ParentName": "App.DeveloperKit.CustomComponents", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2430,6 +2699,7 @@ "Name": "App.DeveloperKit.CustomComponents.Export", "ParentName": "App.DeveloperKit.CustomComponents", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2439,6 +2709,7 @@ "Name": "App.DeveloperKit.CustomComponents.Import", "ParentName": "App.DeveloperKit.CustomComponents", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2448,6 +2719,7 @@ "Name": "App.DeveloperKit.IntranetWidgets", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.IntranetWidgets", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2457,6 +2729,7 @@ "Name": "App.DeveloperKit.IntranetWidgets.Create", "ParentName": "App.DeveloperKit.IntranetWidgets", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2466,6 +2739,7 @@ "Name": "App.DeveloperKit.IntranetWidgets.Update", "ParentName": "App.DeveloperKit.IntranetWidgets", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2475,6 +2749,7 @@ "Name": "App.DeveloperKit.IntranetWidgets.Delete", "ParentName": "App.DeveloperKit.IntranetWidgets", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2484,6 +2759,7 @@ "Name": "App.DeveloperKit.IntranetWidgets.Export", "ParentName": "App.DeveloperKit.IntranetWidgets", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2493,6 +2769,7 @@ "Name": "App.DeveloperKit.IntranetWidgets.Import", "ParentName": "App.DeveloperKit.IntranetWidgets", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2502,6 +2779,7 @@ "Name": "App.DeveloperKit.McpServers", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.McpServers", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2511,6 +2789,7 @@ "Name": "App.DeveloperKit.McpServers.Create", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2520,6 +2799,7 @@ "Name": "App.DeveloperKit.McpServers.Update", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2529,6 +2809,7 @@ "Name": "App.DeveloperKit.McpServers.Delete", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2538,6 +2819,7 @@ "Name": "App.DeveloperKit.McpServers.Export", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2547,6 +2829,7 @@ "Name": "App.DeveloperKit.McpServers.Import", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2556,6 +2839,7 @@ "Name": "App.DeveloperKit.McpServers.Invoke", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.DeveloperKit.McpServers.Invoke", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2565,6 +2849,7 @@ "Name": "App.DeveloperKit.McpServers.Write", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.DeveloperKit.McpServers.Write", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2574,6 +2859,7 @@ "Name": "App.DeveloperKit.McpServers.Develop", "ParentName": "App.DeveloperKit.McpServers", "DisplayName": "App.DeveloperKit.McpServers.Develop", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2583,6 +2869,7 @@ "Name": "App.Listforms.Listform", "ParentName": "App.DeveloperKit", "DisplayName": "App.Listforms.Listform", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2592,6 +2879,7 @@ "Name": "App.Listforms.Listform.Create", "ParentName": "App.Listforms.Listform", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2601,6 +2889,7 @@ "Name": "App.Listforms.Listform.Delete", "ParentName": "App.Listforms.Listform", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2610,6 +2899,7 @@ "Name": "App.Listforms.Listform.Export", "ParentName": "App.Listforms.Listform", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2619,6 +2909,7 @@ "Name": "App.Listforms.Listform.Import", "ParentName": "App.Listforms.Listform", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2628,6 +2919,7 @@ "Name": "App.Listforms.Listform.Update", "ParentName": "App.Listforms.Listform", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2637,6 +2929,7 @@ "Name": "App.Listforms.ListformField", "ParentName": "App.Listforms.Listform", "DisplayName": "App.Listforms.ListformField", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2646,6 +2939,7 @@ "Name": "App.Listforms.ListformField.Create", "ParentName": "App.Listforms.ListformField", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2655,6 +2949,7 @@ "Name": "App.Listforms.ListformField.Delete", "ParentName": "App.Listforms.ListformField", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2664,6 +2959,7 @@ "Name": "App.Listforms.ListformField.Update", "ParentName": "App.Listforms.ListformField", "DisplayName": "App.Platform.Update", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2673,6 +2969,7 @@ "Name": "App.Listforms.Wizard", "ParentName": "App.DeveloperKit", "DisplayName": "App.Listforms.Wizard", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2682,6 +2979,7 @@ "Name": "App.Listforms.Wizard.Create", "ParentName": "App.Listforms.Wizard", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2691,6 +2989,7 @@ "Name": "App.Listforms.Wizard.Update", "ParentName": "App.Listforms.Wizard", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2700,6 +2999,7 @@ "Name": "App.Listforms.Wizard.Delete", "ParentName": "App.Listforms.Wizard", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2709,6 +3009,7 @@ "Name": "App.Listforms.Wizard.Export", "ParentName": "App.Listforms.Wizard", "DisplayName": "App.Platform.Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2718,6 +3019,7 @@ "Name": "App.Listforms.Wizard.Import", "ParentName": "App.Listforms.Wizard", "DisplayName": "App.Platform.Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2727,6 +3029,7 @@ "Name": "App.Setup.Migrate", "ParentName": "App.DeveloperKit", "DisplayName": "App.Setup.Migrate", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2736,6 +3039,7 @@ "Name": "App.ForumManagement", "ParentName": null, "DisplayName": "App.ForumManagement", + "Order": 13, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2745,6 +3049,7 @@ "Name": "App.ForumManagement.Categories", "ParentName": "App.ForumManagement", "DisplayName": "App.ForumManagement.Categories", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2754,6 +3059,7 @@ "Name": "App.ForumManagement.Categories.Create", "ParentName": "App.ForumManagement.Categories", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2763,6 +3069,7 @@ "Name": "App.ForumManagement.Categories.Update", "ParentName": "App.ForumManagement.Categories", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2772,6 +3079,7 @@ "Name": "App.ForumManagement.Categories.Delete", "ParentName": "App.ForumManagement.Categories", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2781,6 +3089,7 @@ "Name": "App.ForumManagement.Categories.Export", "ParentName": "App.ForumManagement.Categories", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2790,6 +3099,7 @@ "Name": "App.ForumManagement.Categories.Import", "ParentName": "App.ForumManagement.Categories", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2799,6 +3109,7 @@ "Name": "App.ForumManagement.Topics", "ParentName": "App.ForumManagement", "DisplayName": "App.ForumManagement.Topics", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2808,6 +3119,7 @@ "Name": "App.ForumManagement.Topics.Create", "ParentName": "App.ForumManagement.Topics", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2817,6 +3129,7 @@ "Name": "App.ForumManagement.Topics.Update", "ParentName": "App.ForumManagement.Topics", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2826,6 +3139,7 @@ "Name": "App.ForumManagement.Topics.Delete", "ParentName": "App.ForumManagement.Topics", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2835,6 +3149,7 @@ "Name": "App.ForumManagement.Topics.Export", "ParentName": "App.ForumManagement.Topics", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2844,6 +3159,7 @@ "Name": "App.ForumManagement.Topics.Import", "ParentName": "App.ForumManagement.Topics", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2853,6 +3169,7 @@ "Name": "App.ForumManagement.Posts", "ParentName": "App.ForumManagement", "DisplayName": "App.ForumManagement.Posts", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2862,6 +3179,7 @@ "Name": "App.ForumManagement.Posts.Create", "ParentName": "App.ForumManagement.Posts", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2871,6 +3189,7 @@ "Name": "App.ForumManagement.Posts.Update", "ParentName": "App.ForumManagement.Posts", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2880,6 +3199,7 @@ "Name": "App.ForumManagement.Posts.Delete", "ParentName": "App.ForumManagement.Posts", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2889,6 +3209,7 @@ "Name": "App.ForumManagement.Posts.Export", "ParentName": "App.ForumManagement.Posts", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2898,6 +3219,7 @@ "Name": "App.ForumManagement.Posts.Import", "ParentName": "App.ForumManagement.Posts", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2907,6 +3229,7 @@ "Name": "App.Administration.Definitions", "ParentName": null, "DisplayName": "App.Platform.Definitions", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2916,6 +3239,7 @@ "Name": "App.Definitions.Sector", "ParentName": "App.Administration.Definitions", "DisplayName": "App.Definitions.Sector", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2925,6 +3249,7 @@ "Name": "App.Definitions.Sector.Create", "ParentName": "App.Definitions.Sector", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2934,6 +3259,7 @@ "Name": "App.Definitions.Sector.Update", "ParentName": "App.Definitions.Sector", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2943,6 +3269,7 @@ "Name": "App.Definitions.Sector.Delete", "ParentName": "App.Definitions.Sector", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2952,6 +3279,7 @@ "Name": "App.Definitions.Sector.Export", "ParentName": "App.Definitions.Sector", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2961,6 +3289,7 @@ "Name": "App.Definitions.Sector.Import", "ParentName": "App.Definitions.Sector", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2970,6 +3299,7 @@ "Name": "App.Definitions.Department", "ParentName": "App.Administration.Definitions", "DisplayName": "App.Definitions.Department", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2979,6 +3309,7 @@ "Name": "App.Definitions.Department.Create", "ParentName": "App.Definitions.Department", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2988,6 +3319,7 @@ "Name": "App.Definitions.Department.Update", "ParentName": "App.Definitions.Department", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -2997,6 +3329,7 @@ "Name": "App.Definitions.Department.Delete", "ParentName": "App.Definitions.Department", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3006,6 +3339,7 @@ "Name": "App.Definitions.Department.Export", "ParentName": "App.Definitions.Department", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3015,6 +3349,7 @@ "Name": "App.Definitions.Department.Import", "ParentName": "App.Definitions.Department", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3024,6 +3359,7 @@ "Name": "App.Definitions.JobPosition", "ParentName": "App.Administration.Definitions", "DisplayName": "App.Definitions.JobPosition", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3033,6 +3369,7 @@ "Name": "App.Definitions.JobPosition.Create", "ParentName": "App.Definitions.JobPosition", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3042,6 +3379,7 @@ "Name": "App.Definitions.JobPosition.Update", "ParentName": "App.Definitions.JobPosition", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3051,6 +3389,7 @@ "Name": "App.Definitions.JobPosition.Delete", "ParentName": "App.Definitions.JobPosition", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3060,6 +3399,7 @@ "Name": "App.Definitions.JobPosition.Export", "ParentName": "App.Definitions.JobPosition", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3069,6 +3409,7 @@ "Name": "App.Definitions.JobPosition.Import", "ParentName": "App.Definitions.JobPosition", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3078,6 +3419,7 @@ "Name": "App.Definitions.OrgChart", "ParentName": "App.Administration.Definitions", "DisplayName": "App.Definitions.OrgChart", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3087,6 +3429,7 @@ "Name": "App.Licence", "ParentName": null, "DisplayName": "App.Licence", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3096,6 +3439,7 @@ "Name": "App.Licence.Create", "ParentName": "App.Licence", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3105,6 +3449,7 @@ "Name": "App.Licence.Update", "ParentName": "App.Licence", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3114,6 +3459,7 @@ "Name": "App.Licence.Delete", "ParentName": "App.Licence", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3123,6 +3469,7 @@ "Name": "App.Licence.Export", "ParentName": "App.Licence", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3132,6 +3479,7 @@ "Name": "App.Licence.Import", "ParentName": "App.Licence", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3141,6 +3489,7 @@ "Name": "App.LicenceItem", "ParentName": "App.Licence", "DisplayName": "App.LicenceItem", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3150,6 +3499,7 @@ "Name": "App.LicenceItem.Create", "ParentName": "App.LicenceItem", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3159,6 +3509,7 @@ "Name": "App.LicenceItem.Update", "ParentName": "App.LicenceItem", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3168,6 +3519,7 @@ "Name": "App.LicenceItem.Delete", "ParentName": "App.LicenceItem", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3177,6 +3529,7 @@ "Name": "App.LicenceItem.Export", "ParentName": "App.LicenceItem", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3186,6 +3539,7 @@ "Name": "App.LicenceItem.Import", "ParentName": "App.LicenceItem", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3195,6 +3549,7 @@ "Name": "App.DevRequest", "ParentName": null, "DisplayName": "App.DevRequest", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3204,6 +3559,7 @@ "Name": "App.DevRequest.Create", "ParentName": "App.DevRequest", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3213,6 +3569,7 @@ "Name": "App.DevRequest.Update", "ParentName": "App.DevRequest", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3222,6 +3579,7 @@ "Name": "App.DevRequest.Delete", "ParentName": "App.DevRequest", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3231,6 +3589,7 @@ "Name": "App.DevRequest.Export", "ParentName": "App.DevRequest", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3240,6 +3599,7 @@ "Name": "App.DevRequest.Import", "ParentName": "App.DevRequest", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3249,6 +3609,7 @@ "Name": "App.Agenda", "ParentName": null, "DisplayName": "App.Agenda", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3258,6 +3619,7 @@ "Name": "App.Agenda.Create", "ParentName": "App.Agenda", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3267,6 +3629,7 @@ "Name": "App.Agenda.Update", "ParentName": "App.Agenda", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3276,6 +3639,7 @@ "Name": "App.Agenda.Delete", "ParentName": "App.Agenda", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3285,6 +3649,7 @@ "Name": "App.Agenda.Export", "ParentName": "App.Agenda", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3294,6 +3659,7 @@ "Name": "App.Agenda.Import", "ParentName": "App.Agenda", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3303,6 +3669,7 @@ "Name": "App.Setting", "ParentName": null, "DisplayName": "App.Setting", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3312,6 +3679,7 @@ "Name": "App.SiteManagement", "ParentName": "App.Setting", "DisplayName": "App.SiteManagement", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3321,6 +3689,7 @@ "Name": "Abp.Localization", "ParentName": "App.SiteManagement", "DisplayName": "Abp.Localization", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3330,6 +3699,7 @@ "Name": "App.SiteManagement.Theme", "ParentName": "App.SiteManagement", "DisplayName": "App.SiteManagement.Theme", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3339,6 +3709,7 @@ "Name": "App.SiteManagement.General", "ParentName": "App.SiteManagement", "DisplayName": "App.SiteManagement.General", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3348,6 +3719,7 @@ "Name": "App.Sender", "ParentName": "App.Setting", "DisplayName": "App.Sender", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3357,6 +3729,7 @@ "Name": "App.Sender.Sms", "ParentName": "App.Sender", "DisplayName": "App.Sender.Sms", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3366,6 +3739,7 @@ "Name": "App.Sender.Sms.PostaGuvercini", "ParentName": "App.Sender.Sms", "DisplayName": "App.Sender.Sms.PostaGuvercini", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3375,6 +3749,7 @@ "Name": "App.Sender.WhatsApp", "ParentName": "App.Sender", "DisplayName": "App.Sender.WhatsApp", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3384,6 +3759,7 @@ "Name": "App.Sender.Telegram", "ParentName": "App.Sender", "DisplayName": "App.Sender.Telegram", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3393,6 +3769,7 @@ "Name": "App.Sender.Rocket", "ParentName": "App.Sender", "DisplayName": "App.Sender.Rocket", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3402,6 +3779,7 @@ "Name": "Abp.Mailing", "ParentName": "App.Setting", "DisplayName": "Abp.Mailing", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3411,6 +3789,7 @@ "Name": "Abp.Mailing.Default", "ParentName": "Abp.Mailing", "DisplayName": "Abp.Mailing.Default", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3420,6 +3799,7 @@ "Name": "Abp.Mailing.Smtp", "ParentName": "Abp.Mailing", "DisplayName": "Abp.Mailing.Smtp", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3429,6 +3809,7 @@ "Name": "Abp.Mailing.AWS", "ParentName": "Abp.Mailing", "DisplayName": "Abp.Mailing.AWS", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3438,6 +3819,7 @@ "Name": "Abp.Account", "ParentName": "App.Setting", "DisplayName": "Abp.Account", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3447,6 +3829,7 @@ "Name": "Abp.Account.General", "ParentName": "Abp.Account", "DisplayName": "Abp.Account.General", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3456,6 +3839,7 @@ "Name": "Abp.Account.TwoFactor", "ParentName": "Abp.Account", "DisplayName": "Abp.Account.TwoFactor", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3465,6 +3849,7 @@ "Name": "Abp.Account.Captcha", "ParentName": "Abp.Account", "DisplayName": "Abp.Account.Captcha", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3474,6 +3859,7 @@ "Name": "Abp.Identity", "ParentName": "App.Setting", "DisplayName": "Abp.Identity", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3483,6 +3869,7 @@ "Name": "Abp.Identity.Profile", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.Profile", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3492,6 +3879,7 @@ "Name": "Abp.Identity.Password", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.Password", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3501,6 +3889,7 @@ "Name": "Abp.Identity.SignIn", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.SignIn", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3510,6 +3899,7 @@ "Name": "Abp.Identity.Lockout", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.Lockout", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3519,6 +3909,7 @@ "Name": "Abp.Identity.User", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.User", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -3528,6 +3919,7 @@ "Name": "App.Intranet", "ParentName": null, "DisplayName": "App.Intranet", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3537,6 +3929,7 @@ "Name": "App.Intranet.Document", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Document", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3546,6 +3939,7 @@ "Name": "App.Intranet.Document.Create", "ParentName": "App.Intranet.Document", "DisplayName": "App.Platform.Upload", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3555,6 +3949,7 @@ "Name": "App.Intranet.Document.Update", "ParentName": "App.Intranet.Document", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3564,6 +3959,7 @@ "Name": "App.Intranet.Document.Delete", "ParentName": "App.Intranet.Document", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3573,6 +3969,7 @@ "Name": "App.Intranet.Announcement", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Announcement", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3582,6 +3979,7 @@ "Name": "App.Intranet.Announcement.Create", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3591,6 +3989,7 @@ "Name": "App.Intranet.Announcement.Update", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3600,6 +3999,7 @@ "Name": "App.Intranet.Announcement.Delete", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3609,6 +4009,7 @@ "Name": "App.Intranet.Announcement.Export", "ParentName": "App.Intranet.Announcement", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3618,6 +4019,7 @@ "Name": "App.Intranet.Announcement.Import", "ParentName": "App.Intranet.Announcement", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3627,6 +4029,7 @@ "Name": "App.Intranet.Announcement.Widget", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Platform.Widget", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3636,6 +4039,7 @@ "Name": "App.Intranet.Announcement.Note", "ParentName": "App.Intranet.Announcement", "DisplayName": "Note", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3645,6 +4049,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Intranet.Announcement.AnnouncementLike", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3654,6 +4059,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike.Create", "ParentName": "App.Intranet.Announcement.AnnouncementLike", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3663,6 +4069,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike.Update", "ParentName": "App.Intranet.Announcement.AnnouncementLike", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3672,6 +4079,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike.Delete", "ParentName": "App.Intranet.Announcement.AnnouncementLike", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3681,6 +4089,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike.Export", "ParentName": "App.Intranet.Announcement.AnnouncementLike", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3690,6 +4099,7 @@ "Name": "App.Intranet.Announcement.AnnouncementLike.Import", "ParentName": "App.Intranet.Announcement.AnnouncementLike", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3699,6 +4109,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment", "ParentName": "App.Intranet.Announcement", "DisplayName": "App.Intranet.Announcement.AnnouncementComment", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3708,6 +4119,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment.Create", "ParentName": "App.Intranet.Announcement.AnnouncementComment", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3717,6 +4129,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment.Update", "ParentName": "App.Intranet.Announcement.AnnouncementComment", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3726,6 +4139,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment.Delete", "ParentName": "App.Intranet.Announcement.AnnouncementComment", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3735,6 +4149,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment.Export", "ParentName": "App.Intranet.Announcement.AnnouncementComment", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3744,6 +4159,7 @@ "Name": "App.Intranet.Announcement.AnnouncementComment.Import", "ParentName": "App.Intranet.Announcement.AnnouncementComment", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3753,6 +4169,7 @@ "Name": "App.Intranet.Survey", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Survey", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3762,6 +4179,7 @@ "Name": "App.Intranet.Survey.Create", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3771,6 +4189,7 @@ "Name": "App.Intranet.Survey.Update", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3780,6 +4199,7 @@ "Name": "App.Intranet.Survey.Delete", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3789,6 +4209,7 @@ "Name": "App.Intranet.Survey.Export", "ParentName": "App.Intranet.Survey", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3798,6 +4219,7 @@ "Name": "App.Intranet.Survey.Import", "ParentName": "App.Intranet.Survey", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3807,6 +4229,7 @@ "Name": "App.Intranet.Survey.Note", "ParentName": "App.Intranet.Survey", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3816,6 +4239,7 @@ "Name": "App.Intranet.Survey.Widget", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Platform.Widget", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3825,6 +4249,7 @@ "Name": "App.Intranet.SurveyQuestion", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Intranet.SurveyQuestion", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3834,6 +4259,7 @@ "Name": "App.Intranet.SurveyQuestion.Create", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3843,6 +4269,7 @@ "Name": "App.Intranet.SurveyQuestion.Update", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3852,6 +4279,7 @@ "Name": "App.Intranet.SurveyQuestion.Delete", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3861,6 +4289,7 @@ "Name": "App.Intranet.SurveyQuestion.Export", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3870,6 +4299,7 @@ "Name": "App.Intranet.SurveyQuestion.Import", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3879,6 +4309,7 @@ "Name": "App.Intranet.SurveyQuestion.Note", "ParentName": "App.Intranet.SurveyQuestion", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3888,6 +4319,7 @@ "Name": "App.Intranet.SurveyQuestionOption", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Intranet.SurveyQuestionOption", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3897,6 +4329,7 @@ "Name": "App.Intranet.SurveyQuestionOption.Create", "ParentName": "App.Intranet.SurveyQuestionOption", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3906,6 +4339,7 @@ "Name": "App.Intranet.SurveyQuestionOption.Update", "ParentName": "App.Intranet.SurveyQuestionOption", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3915,6 +4349,7 @@ "Name": "App.Intranet.SurveyQuestionOption.Delete", "ParentName": "App.Intranet.SurveyQuestionOption", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3924,6 +4359,7 @@ "Name": "App.Intranet.SurveyQuestionOption.Export", "ParentName": "App.Intranet.SurveyQuestionOption", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3933,6 +4369,7 @@ "Name": "App.Intranet.SurveyQuestionOption.Import", "ParentName": "App.Intranet.SurveyQuestionOption", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3942,6 +4379,7 @@ "Name": "App.Intranet.SurveyResponse", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Intranet.SurveyResponse", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3951,6 +4389,7 @@ "Name": "App.Intranet.SurveyResponse.Create", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3960,6 +4399,7 @@ "Name": "App.Intranet.SurveyResponse.Update", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3969,6 +4409,7 @@ "Name": "App.Intranet.SurveyResponse.Delete", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3978,6 +4419,7 @@ "Name": "App.Intranet.SurveyResponse.Export", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3987,6 +4429,7 @@ "Name": "App.Intranet.SurveyResponse.Import", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -3996,6 +4439,7 @@ "Name": "App.Intranet.SurveyResponse.Note", "ParentName": "App.Intranet.SurveyResponse", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4005,6 +4449,7 @@ "Name": "App.Intranet.SurveyAnswer", "ParentName": "App.Intranet.Survey", "DisplayName": "App.Intranet.SurveyAnswer", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4014,6 +4459,7 @@ "Name": "App.Intranet.SurveyAnswer.Create", "ParentName": "App.Intranet.SurveyAnswer", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4023,6 +4469,7 @@ "Name": "App.Intranet.SurveyAnswer.Update", "ParentName": "App.Intranet.SurveyAnswer", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4032,6 +4479,7 @@ "Name": "App.Intranet.SurveyAnswer.Delete", "ParentName": "App.Intranet.SurveyAnswer", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4041,6 +4489,7 @@ "Name": "App.Intranet.SurveyAnswer.Export", "ParentName": "App.Intranet.SurveyAnswer", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4050,6 +4499,7 @@ "Name": "App.Intranet.SurveyAnswer.Import", "ParentName": "App.Intranet.SurveyAnswer", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4059,6 +4509,7 @@ "Name": "App.Intranet.SocialPost", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.SocialPost", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4068,6 +4519,7 @@ "Name": "App.Intranet.SocialPost.Create", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4077,6 +4529,7 @@ "Name": "App.Intranet.SocialPost.Update", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4086,6 +4539,7 @@ "Name": "App.Intranet.SocialPost.Delete", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4095,6 +4549,7 @@ "Name": "App.Intranet.SocialPost.Export", "ParentName": "App.Intranet.SocialPost", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4104,6 +4559,7 @@ "Name": "App.Intranet.SocialPost.Import", "ParentName": "App.Intranet.SocialPost", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4113,6 +4569,7 @@ "Name": "App.Intranet.SocialPost.Note", "ParentName": "App.Intranet.SocialPost", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4122,6 +4579,7 @@ "Name": "App.Intranet.SocialPost.Widget", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Platform.Widget", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4131,6 +4589,7 @@ "Name": "App.Intranet.SocialMedia", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Intranet.SocialMedia", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4140,6 +4599,7 @@ "Name": "App.Intranet.SocialMedia.Create", "ParentName": "App.Intranet.SocialMedia", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4149,6 +4609,7 @@ "Name": "App.Intranet.SocialMedia.Update", "ParentName": "App.Intranet.SocialMedia", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4158,6 +4619,7 @@ "Name": "App.Intranet.SocialMedia.Delete", "ParentName": "App.Intranet.SocialMedia", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4167,6 +4629,7 @@ "Name": "App.Intranet.SocialMedia.Export", "ParentName": "App.Intranet.SocialMedia", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4176,6 +4639,7 @@ "Name": "App.Intranet.SocialMedia.Import", "ParentName": "App.Intranet.SocialMedia", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4185,6 +4649,7 @@ "Name": "App.Intranet.SocialPollOption", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Intranet.SocialPollOption", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4194,6 +4659,7 @@ "Name": "App.Intranet.SocialPollOption.Create", "ParentName": "App.Intranet.SocialPollOption", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4203,6 +4669,7 @@ "Name": "App.Intranet.SocialPollOption.Update", "ParentName": "App.Intranet.SocialPollOption", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4212,6 +4679,7 @@ "Name": "App.Intranet.SocialPollOption.Delete", "ParentName": "App.Intranet.SocialPollOption", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4221,6 +4689,7 @@ "Name": "App.Intranet.SocialPollOption.Export", "ParentName": "App.Intranet.SocialPollOption", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4230,6 +4699,7 @@ "Name": "App.Intranet.SocialPollOption.Import", "ParentName": "App.Intranet.SocialPollOption", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4239,6 +4709,7 @@ "Name": "App.Intranet.SocialComment", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Intranet.SocialComment", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4248,6 +4719,7 @@ "Name": "App.Intranet.SocialComment.Create", "ParentName": "App.Intranet.SocialComment", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4257,6 +4729,7 @@ "Name": "App.Intranet.SocialComment.Update", "ParentName": "App.Intranet.SocialComment", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4266,6 +4739,7 @@ "Name": "App.Intranet.SocialComment.Delete", "ParentName": "App.Intranet.SocialComment", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4275,6 +4749,7 @@ "Name": "App.Intranet.SocialComment.Export", "ParentName": "App.Intranet.SocialComment", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4284,6 +4759,7 @@ "Name": "App.Intranet.SocialComment.Import", "ParentName": "App.Intranet.SocialComment", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4293,6 +4769,7 @@ "Name": "App.Intranet.SocialLike", "ParentName": "App.Intranet.SocialPost", "DisplayName": "App.Intranet.SocialLike", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4302,6 +4779,7 @@ "Name": "App.Intranet.SocialLike.Create", "ParentName": "App.Intranet.SocialLike", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4311,6 +4789,7 @@ "Name": "App.Intranet.SocialLike.Update", "ParentName": "App.Intranet.SocialLike", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4320,6 +4799,7 @@ "Name": "App.Intranet.SocialLike.Delete", "ParentName": "App.Intranet.SocialLike", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4329,6 +4809,7 @@ "Name": "App.Intranet.SocialLike.Export", "ParentName": "App.Intranet.SocialLike", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4338,6 +4819,7 @@ "Name": "App.Intranet.SocialLike.Import", "ParentName": "App.Intranet.SocialLike", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4347,6 +4829,7 @@ "Name": "App.Intranet.Events.EventType", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Events.EventType", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4356,6 +4839,7 @@ "Name": "App.Intranet.Events.EventType.Create", "ParentName": "App.Intranet.Events.EventType", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4365,6 +4849,7 @@ "Name": "App.Intranet.Events.EventType.Update", "ParentName": "App.Intranet.Events.EventType", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4374,6 +4859,7 @@ "Name": "App.Intranet.Events.EventType.Delete", "ParentName": "App.Intranet.Events.EventType", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4383,6 +4869,7 @@ "Name": "App.Intranet.Events.EventType.Export", "ParentName": "App.Intranet.Events.EventType", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4392,6 +4879,7 @@ "Name": "App.Intranet.Events.EventType.Import", "ParentName": "App.Intranet.Events.EventType", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4401,6 +4889,7 @@ "Name": "App.Intranet.Events.EventCategory", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Events.EventCategory", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4410,6 +4899,7 @@ "Name": "App.Intranet.Events.EventCategory.Create", "ParentName": "App.Intranet.Events.EventCategory", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4419,6 +4909,7 @@ "Name": "App.Intranet.Events.EventCategory.Update", "ParentName": "App.Intranet.Events.EventCategory", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4428,6 +4919,7 @@ "Name": "App.Intranet.Events.EventCategory.Delete", "ParentName": "App.Intranet.Events.EventCategory", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4437,6 +4929,7 @@ "Name": "App.Intranet.Events.EventCategory.Export", "ParentName": "App.Intranet.Events.EventCategory", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4446,6 +4939,7 @@ "Name": "App.Intranet.Events.EventCategory.Import", "ParentName": "App.Intranet.Events.EventCategory", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4455,6 +4949,7 @@ "Name": "App.Intranet.Events.Event", "ParentName": "App.Intranet", "DisplayName": "App.Intranet.Events.Event", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4464,6 +4959,7 @@ "Name": "App.Intranet.Events.Event.Create", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4473,6 +4969,7 @@ "Name": "App.Intranet.Events.Event.Update", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4482,6 +4979,7 @@ "Name": "App.Intranet.Events.Event.Delete", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4491,6 +4989,7 @@ "Name": "App.Intranet.Events.Event.Export", "ParentName": "App.Intranet.Events.Event", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4500,6 +4999,7 @@ "Name": "App.Intranet.Events.Event.Import", "ParentName": "App.Intranet.Events.Event", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4509,6 +5009,7 @@ "Name": "App.Intranet.Events.Event.Note", "ParentName": "App.Intranet.Events.Event", "DisplayName": "Note", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4518,6 +5019,7 @@ "Name": "App.Intranet.Events.Event.Widget", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Platform.Widget", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4527,6 +5029,7 @@ "Name": "App.Intranet.Events.EventLike", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Intranet.Events.EventLike", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4536,6 +5039,7 @@ "Name": "App.Intranet.Events.EventLike.Create", "ParentName": "App.Intranet.Events.EventLike", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4545,6 +5049,7 @@ "Name": "App.Intranet.Events.EventLike.Update", "ParentName": "App.Intranet.Events.EventLike", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4554,6 +5059,7 @@ "Name": "App.Intranet.Events.EventLike.Delete", "ParentName": "App.Intranet.Events.EventLike", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4563,6 +5069,7 @@ "Name": "App.Intranet.Events.EventLike.Export", "ParentName": "App.Intranet.Events.EventLike", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4572,6 +5079,7 @@ "Name": "App.Intranet.Events.EventLike.Import", "ParentName": "App.Intranet.Events.EventLike", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4581,6 +5089,7 @@ "Name": "App.Intranet.Events.EventComment", "ParentName": "App.Intranet.Events.Event", "DisplayName": "App.Intranet.Events.EventComment", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4590,6 +5099,7 @@ "Name": "App.Intranet.Events.EventComment.Create", "ParentName": "App.Intranet.Events.EventComment", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4599,6 +5109,7 @@ "Name": "App.Intranet.Events.EventComment.Update", "ParentName": "App.Intranet.Events.EventComment", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4608,6 +5119,7 @@ "Name": "App.Intranet.Events.EventComment.Delete", "ParentName": "App.Intranet.Events.EventComment", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4617,6 +5129,7 @@ "Name": "App.Intranet.Events.EventComment.Export", "ParentName": "App.Intranet.Events.EventComment", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4626,6 +5139,7 @@ "Name": "App.Intranet.Events.EventComment.Import", "ParentName": "App.Intranet.Events.EventComment", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4635,6 +5149,7 @@ "Name": "App.Platform.Restrictions", "ParentName": null, "DisplayName": "App.Platform.Restrictions", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4644,6 +5159,7 @@ "Name": "App.Restrictions.WorkHour", "ParentName": "App.Platform.Restrictions", "DisplayName": "App.Restrictions.WorkHour", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4653,6 +5169,7 @@ "Name": "App.Restrictions.WorkHour.Create", "ParentName": "App.Restrictions.WorkHour", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4662,6 +5179,7 @@ "Name": "App.Restrictions.WorkHour.Update", "ParentName": "App.Restrictions.WorkHour", "DisplayName": "App.Platform.Update", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4671,6 +5189,7 @@ "Name": "App.Restrictions.WorkHour.Delete", "ParentName": "App.Restrictions.WorkHour", "DisplayName": "App.Platform.Delete", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4680,6 +5199,7 @@ "Name": "App.Restrictions.WorkHour.Export", "ParentName": "App.Restrictions.WorkHour", "DisplayName": "Export", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4689,6 +5209,7 @@ "Name": "App.Restrictions.WorkHour.Import", "ParentName": "App.Restrictions.WorkHour", "DisplayName": "Import", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4698,6 +5219,7 @@ "Name": "App.Restrictions.IpRestrictions", "ParentName": "App.Platform.Restrictions", "DisplayName": "App.Restrictions.IpRestrictions", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4707,6 +5229,7 @@ "Name": "App.Restrictions.IpRestrictions.Create", "ParentName": "App.Restrictions.IpRestrictions", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4716,6 +5239,7 @@ "Name": "App.Restrictions.IpRestrictions.Delete", "ParentName": "App.Restrictions.IpRestrictions", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4725,6 +5249,7 @@ "Name": "App.Restrictions.IpRestrictions.Export", "ParentName": "App.Restrictions.IpRestrictions", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4734,6 +5259,7 @@ "Name": "App.Restrictions.IpRestrictions.Import", "ParentName": "App.Restrictions.IpRestrictions", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4743,6 +5269,7 @@ "Name": "App.Restrictions.IpRestrictions.Update", "ParentName": "App.Restrictions.IpRestrictions", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4752,6 +5279,7 @@ "Name": "Abp.Identity", "ParentName": null, "DisplayName": "Abp.Identity", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4761,6 +5289,7 @@ "Name": "Abp.Identity.PermissionGroups", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.PermissionGroups", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4770,6 +5299,7 @@ "Name": "Abp.Identity.PermissionGroups.Create", "ParentName": "Abp.Identity.PermissionGroups", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4779,6 +5309,7 @@ "Name": "Abp.Identity.PermissionGroups.Delete", "ParentName": "Abp.Identity.PermissionGroups", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4788,6 +5319,7 @@ "Name": "Abp.Identity.PermissionGroups.Export", "ParentName": "Abp.Identity.PermissionGroups", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4797,6 +5329,7 @@ "Name": "Abp.Identity.PermissionGroups.Import", "ParentName": "Abp.Identity.PermissionGroups", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4806,6 +5339,7 @@ "Name": "Abp.Identity.PermissionGroups.Update", "ParentName": "Abp.Identity.PermissionGroups", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4815,6 +5349,7 @@ "Name": "Abp.Identity.Permissions", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.Permissions", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4824,6 +5359,7 @@ "Name": "Abp.Identity.Permissions.Create", "ParentName": "Abp.Identity.Permissions", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4833,6 +5369,7 @@ "Name": "Abp.Identity.Permissions.Delete", "ParentName": "Abp.Identity.Permissions", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4842,6 +5379,7 @@ "Name": "Abp.Identity.Permissions.Export", "ParentName": "Abp.Identity.Permissions", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4851,6 +5389,7 @@ "Name": "Abp.Identity.Permissions.Import", "ParentName": "Abp.Identity.Permissions", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4860,6 +5399,7 @@ "Name": "Abp.Identity.Permissions.Update", "ParentName": "Abp.Identity.Permissions", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -4869,6 +5409,7 @@ "Name": "App.IdentityManagement.ClaimType", "ParentName": "Abp.Identity", "DisplayName": "App.IdentityManagement.ClaimType", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4878,6 +5419,7 @@ "Name": "App.IdentityManagement.ClaimType.Create", "ParentName": "App.IdentityManagement.ClaimType", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4887,6 +5429,7 @@ "Name": "App.IdentityManagement.ClaimType.Delete", "ParentName": "App.IdentityManagement.ClaimType", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4896,6 +5439,7 @@ "Name": "App.IdentityManagement.ClaimType.Export", "ParentName": "App.IdentityManagement.ClaimType", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4905,6 +5449,7 @@ "Name": "App.IdentityManagement.ClaimType.Import", "ParentName": "App.IdentityManagement.ClaimType", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4914,6 +5459,7 @@ "Name": "App.IdentityManagement.ClaimType.Update", "ParentName": "App.IdentityManagement.ClaimType", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4923,6 +5469,7 @@ "Name": "AbpIdentity.Roles", "ParentName": "Abp.Identity", "DisplayName": "AbpIdentity.Roles", + "Order": 9, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4932,6 +5479,7 @@ "Name": "AbpIdentity.Roles.Create", "ParentName": "AbpIdentity.Roles", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4941,6 +5489,7 @@ "Name": "AbpIdentity.Roles.Delete", "ParentName": "AbpIdentity.Roles", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4950,6 +5499,7 @@ "Name": "AbpIdentity.Roles.Export", "ParentName": "AbpIdentity.Roles", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4959,6 +5509,7 @@ "Name": "AbpIdentity.Roles.Import", "ParentName": "AbpIdentity.Roles", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4968,6 +5519,7 @@ "Name": "AbpIdentity.Roles.ManagePermissions", "ParentName": "AbpIdentity.Roles", "DisplayName": "AbpIdentity.Roles.ManagePermissions", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4977,6 +5529,7 @@ "Name": "AbpIdentity.Roles.Update", "ParentName": "AbpIdentity.Roles", "DisplayName": "App.Platform.Update", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4986,6 +5539,7 @@ "Name": "AbpIdentity.Users", "ParentName": "Abp.Identity", "DisplayName": "AbpIdentity.Users", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -4995,6 +5549,7 @@ "Name": "AbpIdentity.Users.Create", "ParentName": "AbpIdentity.Users", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5004,6 +5559,7 @@ "Name": "AbpIdentity.Users.Delete", "ParentName": "AbpIdentity.Users", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5013,6 +5569,7 @@ "Name": "AbpIdentity.Users.Export", "ParentName": "AbpIdentity.Users", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5022,6 +5579,7 @@ "Name": "AbpIdentity.Users.Import", "ParentName": "AbpIdentity.Users", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5031,6 +5589,7 @@ "Name": "AbpIdentity.Users.ManagePermissions", "ParentName": "AbpIdentity.Users", "DisplayName": "AbpIdentity.Users.ManagePermissions", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5040,6 +5599,7 @@ "Name": "AbpIdentity.Users.Update", "ParentName": "AbpIdentity.Users", "DisplayName": "App.Platform.Update", + "Order": 6, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5049,6 +5609,7 @@ "Name": "AbpIdentity.UserLookup", "ParentName": "AbpIdentity.Users", "DisplayName": "AbpIdentity.UserLookup", + "Order": 7, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5058,6 +5619,7 @@ "Name": "AbpIdentity.Users.Widget", "ParentName": "AbpIdentity.Users", "DisplayName": "App.Platform.Widget", + "Order": 8, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5067,6 +5629,7 @@ "Name": "AbpIdentity.Users.Update.ManageRoles", "ParentName": "AbpIdentity.Users.Update", "DisplayName": "AbpIdentity.Users.Update.ManageRoles", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5076,6 +5639,7 @@ "Name": "Abp.Identity.OrganizationUnits", "ParentName": "Abp.Identity", "DisplayName": "Abp.Identity.OrganizationUnits", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5085,6 +5649,7 @@ "Name": "App.IdentityManagement.AuditLogs", "ParentName": "Abp.Identity", "DisplayName": "App.IdentityManagement.AuditLogs", + "Order": 12, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5094,6 +5659,7 @@ "Name": "App.IdentityManagement.AuditLogs.Create", "ParentName": "App.IdentityManagement.AuditLogs", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5103,6 +5669,7 @@ "Name": "App.IdentityManagement.AuditLogs.Delete", "ParentName": "App.IdentityManagement.AuditLogs", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5112,6 +5679,7 @@ "Name": "App.IdentityManagement.AuditLogs.Export", "ParentName": "App.IdentityManagement.AuditLogs", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5121,6 +5689,7 @@ "Name": "App.IdentityManagement.AuditLogs.Import", "ParentName": "App.IdentityManagement.AuditLogs", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5130,6 +5699,7 @@ "Name": "App.IdentityManagement.AuditLogs.Update", "ParentName": "App.IdentityManagement.AuditLogs", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5139,6 +5709,7 @@ "Name": "App.IdentityManagement.Sessions", "ParentName": "Abp.Identity", "DisplayName": "App.IdentityManagement.Sessions", + "Order": 13, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5148,6 +5719,7 @@ "Name": "App.IdentityManagement.Sessions.Create", "ParentName": "App.IdentityManagement.Sessions", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5157,6 +5729,7 @@ "Name": "App.IdentityManagement.Sessions.Delete", "ParentName": "App.IdentityManagement.Sessions", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5166,6 +5739,7 @@ "Name": "App.IdentityManagement.Sessions.Export", "ParentName": "App.IdentityManagement.Sessions", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5175,6 +5749,7 @@ "Name": "App.IdentityManagement.Sessions.Update", "ParentName": "App.IdentityManagement.Sessions", "DisplayName": "App.Platform.Update", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5184,6 +5759,7 @@ "Name": "App.Reports.Categories", "ParentName": "App.Reports.Management", "DisplayName": "App.Reports.Categories", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5193,6 +5769,7 @@ "Name": "App.Reports.Categories.Create", "ParentName": "App.Reports.Categories", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5202,6 +5779,7 @@ "Name": "App.Reports.Categories.Delete", "ParentName": "App.Reports.Categories", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5211,6 +5789,7 @@ "Name": "App.Reports.Categories.Export", "ParentName": "App.Reports.Categories", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5220,6 +5799,7 @@ "Name": "App.Reports.Categories.Import", "ParentName": "App.Reports.Categories", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5229,6 +5809,7 @@ "Name": "App.Reports.Categories.Update", "ParentName": "App.Reports.Categories", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5238,6 +5819,7 @@ "Name": "App.Reports.ReportTemplates", "ParentName": "App.Reports.Management", "DisplayName": "App.Reports.ReportTemplates", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5247,6 +5829,7 @@ "Name": "App.Reports.ReportTemplates.Create", "ParentName": "App.Reports.ReportTemplates", "DisplayName": "App.Platform.Create", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5256,6 +5839,7 @@ "Name": "App.Reports.ReportTemplates.Delete", "ParentName": "App.Reports.ReportTemplates", "DisplayName": "App.Platform.Delete", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5265,6 +5849,7 @@ "Name": "App.Reports.ReportTemplates.Export", "ParentName": "App.Reports.ReportTemplates", "DisplayName": "Export", + "Order": 3, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5274,6 +5859,7 @@ "Name": "App.Reports.ReportTemplates.Import", "ParentName": "App.Reports.ReportTemplates", "DisplayName": "Import", + "Order": 4, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5283,6 +5869,7 @@ "Name": "App.Reports.ReportTemplates.Update", "ParentName": "App.Reports.ReportTemplates", "DisplayName": "App.Platform.Update", + "Order": 5, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5292,6 +5879,7 @@ "Name": "App.Videoroom", "ParentName": null, "DisplayName": "App.Videoroom", + "Order": 10, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5301,6 +5889,7 @@ "Name": "App.Videoroom.List", "ParentName": "App.Videoroom", "DisplayName": "App.Videoroom.List", + "Order": 1, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5310,6 +5899,7 @@ "Name": "App.Videoroom.Detail", "ParentName": "App.Videoroom", "DisplayName": "App.Videoroom.Detail", + "Order": 2, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" @@ -5319,6 +5909,7 @@ "Name": "App.ForumManagement.Publish", "ParentName": null, "DisplayName": "App.ForumManagement.Publish", + "Order": 11, "IsEnabled": true, "MultiTenancySide": 3, "Modules": "Sas|Adm" diff --git a/configs/seeds/host/data/App.Notifications.NotificationTypes.json b/configs/seeds/host/data/App.Notifications.NotificationTypes.json index edfbed25..4689c0f2 100644 --- a/configs/seeds/host/data/App.Notifications.NotificationTypes.json +++ b/configs/seeds/host/data/App.Notifications.NotificationTypes.json @@ -6,15 +6,18 @@ "Rows": [ { "Id": "095dcdb5-7c4a-5668-81bb-abc7b01b2158", - "Name": "YeniSiparis" + "Name": "YeniSiparis", + "Description": "Yeni bir sipariş oluşturulduğunda tetiklenir. Siparişi karşılayacak ekibin haberdar olması içindir; mesaj metni sipariş numarası ve müşteri adı gibi bilgileri taşır. Tetikleyici genellikle sipariş ekranının kayıt sonrası komutu ya da sipariş tablosunu yazan saklı yordamdır." }, { "Id": "c58237f7-edd6-55a3-9bc9-aab82f63ca3e", - "Name": "SiparisPasla" + "Name": "SiparisPasla", + "Description": "Bir sipariş başka bir kullanıcıya ya da ekibe devredildiğinde tetiklenir. Devralının işi sahiplendiğini bilmesi içindir; mesajda kimin kime devrettiği ve sipariş bilgisi geçer." }, { "Id": "78a4b82b-de80-5826-bf9b-e49a2436cd43", - "Name": "YeniKullanici" + "Name": "YeniKullanici", + "Description": "Sisteme yeni bir kullanıcı eklendiğinde tetiklenir. Yönetici ekibin yetki ve rol atamasını yapması için haberdar olmasını sağlar." } ] } diff --git a/ui/src/components/template/Notification.tsx b/ui/src/components/template/Notification.tsx index 677caf0f..875a50a2 100644 --- a/ui/src/components/template/Notification.tsx +++ b/ui/src/components/template/Notification.tsx @@ -18,6 +18,7 @@ import { updateReadMany, updateSentMany, } from '@/services/notification.service' +import { notificationSignalR } from '@/services/notification.signalr' import { useStoreState } from '@/store' import withHeaderItem from '@/utils/hoc/withHeaderItem' import { useLocalization } from '@/utils/hooks/useLocalization' @@ -53,7 +54,8 @@ type NotificationListItem = { } const notificationHeight = 'h-[26rem]' -const notificationInterval = 120000 // 2 minutes +/** SignalR duyurusu kaybolursa devreye giren yedek yoklama aralığı. */ +const notificationInterval = 300000 // 5 minutes const foregroundPollThrottle = 30000 // 30 seconds /** @@ -146,7 +148,7 @@ const NotificationToggle = ({ ) } -const _Notification = ({ className }: { className?: string }) => { +const NotificationDropdown = ({ className }: { className?: string }) => { const { translate } = useLocalization() const [notificationList, setNotificationList] = useState([]) @@ -165,24 +167,33 @@ const _Notification = ({ className }: { className?: string }) => { const direction = useStoreState((state) => state.theme.direction) /** - * Badge ve dropdown içeriği tek istekten beslenir; böylece rozetteki sayı ile - * listedeki okunmamış kayıt sayısı her zaman aynı kalır. + * Dropdown listesi son `inboxSize` kaydı gösterir; rozetteki sayı ise sunucudan okunan + * toplam okunmamış kayıt sayısıdır. Sayıyı sayfadan hesaplamak 50'den fazla okunmamış + * bildirimde rozeti sessizce tavanlıyordu. Listedeki satırlar aynı mesajın kanal + * kopyalarını birleştirdiği için satır sayısı ile rozet birebir aynı olmak zorunda değildir. */ const getInbox = useCallback(async () => { - const resp = await getList({ - channels: inboxChannels, - // Kanallar arasında UiToast olduğu için servis, geçmişi son 24 saatle - // sınırlayan push filtresini uygulamasın diye liste modunda çağrılır. - isListRequest: true, - sorting: 'CreationTime desc', - maxResultCount: inboxSize, - }) + const [listResponse, unreadResponse] = await Promise.all([ + getList({ + channels: inboxChannels, + // Kanallar arasında UiToast olduğu için servis, geçmişi son 24 saatle + // sınırlayan push filtresini uygulamasın diye liste modunda çağrılır. + isListRequest: true, + sorting: 'CreationTime desc', + maxResultCount: inboxSize, + }), + getList({ + channels: inboxChannels, + isListRequest: true, + isRead: false, + maxResultCount: 1, + }), + ]) - const items = resp.data.items ?? [] - const groups = groupNotifications(items) + const items = listResponse.data.items ?? [] - setNotificationList(groups) - setUnreadNotificationCount(groups.filter((a) => !a.readed).length) + setNotificationList(groupNotifications(items)) + setUnreadNotificationCount(unreadResponse.data.totalCount ?? 0) listLoaded.current = true return items @@ -209,49 +220,75 @@ const _Notification = ({ className }: { className?: string }) => { if (pending.length === 0) { return } - pending.forEach((a) => pushedIds.current.add(a.id)) + + // Yalnızca gerçekten gösterilen kayıt "gönderildi" sayılır. Patlamayı sınırlamak için + // kesilen kuyruk işaretlenseydi o bildirimler hiç görünmeden kaybolurdu; işaretlenmeyen + // kayıtlar bir sonraki yoklamada yeniden denenir. + const shown: string[] = [] for (const notification of pending.slice(0, maxPushBurst)) { - if (notification.notificationChannel === NotificationChannels.UiToast) { - toast.push( - - {notification.message} - , - { placement: 'bottom-end' }, - ) - continue - } + try { + if (notification.notificationChannel === NotificationChannels.UiToast) { + toast.push( + + {notification.message} + , + { placement: 'bottom-end' }, + ) + shown.push(notification.id) + continue + } - const title = notification.notificationType || APP_NAME - const options: NotificationOptions = { - body: notification.message, - dir: 'ltr', - requireInteraction: true, - } + const title = notification.notificationType || APP_NAME + const options: NotificationOptions = { + body: notification.message, + dir: 'ltr', + requireInteraction: true, + // Service worker tıklamada bu adrese götürür; yoksa etkinlik kaydı ekranı açılır. + data: { url: ROUTES_ENUM.protected.admin.activityLog }, + } - if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { - const reg = await navigator.serviceWorker.ready - await reg.showNotification(title, options) - } else { - new window.Notification(title, options) + if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { + const reg = await navigator.serviceWorker.ready + await reg.showNotification(title, options) + } else { + new window.Notification(title, options) + } + shown.push(notification.id) + } catch { + // Gösterim başarısızsa (izin kalkmış, service worker hazır değil) kayıt + // işaretlenmez; sonraki yoklamada tekrar denenir. } } + if (shown.length === 0) { + return + } + + shown.forEach((id) => pushedIds.current.add(id)) + // Yalnızca "gösterildi" işaretlenir; okundu bilgisi kullanıcının kendi // aksiyonuna bırakılır, aksi hâlde mesaj badge'e hiç düşmezdi. - await updateSentMany( - pending.map((a) => a.id), - true, - ) - - await getInbox() - }, [getInbox]) + await updateSentMany(shown, true) + }, []) useEffect(() => { if ('Notification' in window && window.Notification.permission === 'default') { window.Notification.requestPermission() } + // Sıra korunur: önce gösterilmemiş kayıtlar gösterilip "gönderildi" yazılır, sonra + // liste okunur. Eşzamanlı koşsalardı geç dönen istek erken dönenin sonucunu ezer ve + // rozet sunucudaki gerçekle ayrışırdı. Hata kullanıcı aksiyonu değildir; sessizce + // geçilir, bir sonraki tetikleme yeniden dener. + const refresh = () => { + lastPollAt.current = performance.now() + + return getPushNotifications() + .then(() => getInbox()) + .catch(() => undefined) + } + const poll = () => { // Sekme arka plandaysa istek atma. Pencerenin OS odağı yoksa (IDE'ye // geçmek, ikinci ekran, ayrık DevTools) sekme hâlâ görünür sayılır ve @@ -259,11 +296,16 @@ const _Notification = ({ className }: { className?: string }) => { if (document.visibilityState === 'hidden') { return } - lastPollAt.current = performance.now() - getInbox() - getPushNotifications() + refresh() } + // Bildirim anında SignalR ile düşer; yoklama yalnızca bağlantı kurulamadığında ya da + // duyuru kaybolduğunda devreye giren yedektir. + const unsubscribeReceived = notificationSignalR.onReceived(() => { + refresh() + }) + notificationSignalR.start() + poll() const intervalId = setInterval(poll, notificationInterval) @@ -286,6 +328,8 @@ const _Notification = ({ className }: { className?: string }) => { clearInterval(intervalId) document.removeEventListener('visibilitychange', onForeground) window.removeEventListener('focus', onForeground) + unsubscribeReceived() + notificationSignalR.stop() } }, [getInbox, getPushNotifications]) @@ -297,8 +341,17 @@ const _Notification = ({ className }: { className?: string }) => { // Panelde görülen kayıtlar için ayrıca toast/desktop açılmasın. isSent // yalnızca tarayıcı kanallarında UI'nin sorumluluğunda; Mail/Sms gibi // kanalların gönderim durumu backend'e ait olduğu için dokunulmaz. + // Masaüstü bildirimine izin verilmemişse o kayıt gösterilemez; "gönderildi" + // yazılırsa izin sonradan verildiğinde de hiç gösterilmezdi. + const desktopGranted = + 'Notification' in window && window.Notification.permission === 'granted' const unsentIds = items - .filter((a) => !a.isSent && pushChannels.includes(a.notificationChannel)) + .filter( + (a) => + !a.isSent && + pushChannels.includes(a.notificationChannel) && + (desktopGranted || a.notificationChannel !== NotificationChannels.Desktop), + ) .map((a) => a.id) if (unsentIds.length > 0) { unsentIds.forEach((id) => pushedIds.current.add(id)) @@ -309,25 +362,25 @@ const _Notification = ({ className }: { className?: string }) => { } }, [getInbox]) + // Sunucu, kullaniciya henuz iletilmemis (isSent === false) kaydi okundu saymaz; bu yuzden + // liste iyimser guncellenmez, isaretlemeden sonra sunucudan tazelenir. Aksi halde rozet + // sifirlanir ama bir sonraki yoklamada geri gelirdi. const onMarkAllAsRead = useCallback(async () => { await Promise.all(inboxChannels.map((channel) => updateReadAll(channel, true))) + await getInbox() + }, [getInbox]) - setNotificationList((list) => list.map((item) => ({ ...item, readed: true }))) - setUnreadNotificationCount(0) - }, []) + const onMarkAsRead = useCallback( + async (item: NotificationListItem) => { + if (item.readed) { + return + } - const onMarkAsRead = useCallback(async (item: NotificationListItem) => { - if (item.readed) { - return - } - - await updateReadMany(item.ids, true) - - setNotificationList((list) => - list.map((a) => (a.ids[0] === item.ids[0] ? { ...a, readed: true } : a)), - ) - setUnreadNotificationCount((count) => Math.max(0, count - 1)) - }, []) + await updateReadMany(item.ids, true) + await getInbox() + }, + [getInbox], + ) const noResult = !loading && listLoaded.current && notificationList.length === 0 @@ -383,9 +436,9 @@ const _Notification = ({ className }: { className?: string }) => { key={channel} prefix className="text-xs" - prefixClass={NotificationChannelColors[channel]} + prefixClass={NotificationChannelColors[channel] ?? 'bg-gray-400'} > - {channel} + {translate(`::App.NotificationChannel.${channel}`)} ))} @@ -410,8 +463,8 @@ const _Notification = ({ className }: { className?: string }) => { src="/img/others/no-notification.png" alt="no-notification" /> -
No notifications!
-

Please Try again later

+
{translate('::App.Notifications.Empty')}
+

{translate('::App.Notifications.EmptyHint')}

)} @@ -432,6 +485,6 @@ const _Notification = ({ className }: { className?: string }) => { ) } -const Notification = withHeaderItem(_Notification) +const Notification = withHeaderItem(NotificationDropdown) export default Notification diff --git a/ui/src/proxy/notification/models.ts b/ui/src/proxy/notification/models.ts index 3d54d7b4..c7786c01 100644 --- a/ui/src/proxy/notification/models.ts +++ b/ui/src/proxy/notification/models.ts @@ -6,24 +6,43 @@ export interface NotificationDto { notificationType: string message: string isRead: boolean + /** Kullanıcıya iletildi mi; okundu işareti yalnızca iletilmiş kayıtta yazılabilir. */ isSent: boolean - readTime: Date + /** Okundu işaretinin zamanı; okunmamış kayıtta null gelir. */ + readTime?: string | null creationTime: Date creatorFullname?: string creatorId?: string tenantId?: string } +export interface NotificationRuleSummaryDto { + id: string + notificationTypeName: string + channel: string + recipientType: string + recipientId?: string + isActive: boolean +} + +export interface NotificationTypeDto { + id: string + name: string + /** Türün ne işe yaradığı; satır başlığında ipucu olarak gösterilir. */ + description?: string | null +} + export interface NotificationRuleDto { id: string - notificationType: string + /** Tercih eşlemesi ad üzerinden değil id üzerinden yapılır; ad yalnızca ekranda gösterilir. */ + notificationTypeId: string channel: string isActive: boolean isFixed: boolean isCustomized: boolean } -export declare class NotificationFilterRequestDto extends PagedAndSortedResultRequestDto { +export interface NotificationFilterRequestDto extends PagedAndSortedResultRequestDto { channels?: string[] isRead?: boolean isSent?: boolean diff --git a/ui/src/services/notification-rule.service.ts b/ui/src/services/notification-rule.service.ts index fda94c6d..f467f228 100644 --- a/ui/src/services/notification-rule.service.ts +++ b/ui/src/services/notification-rule.service.ts @@ -1,12 +1,22 @@ -import { NotificationRuleDto } from '../proxy/notification/models' +import { + NotificationRuleDto, + NotificationRuleSummaryDto, + NotificationTypeDto, +} from '../proxy/notification/models' import apiService from './api.service' export const getMyNotificationTypes = () => - apiService.fetchData({ + apiService.fetchData({ method: 'GET', url: `/api/app/notification-rule/my-notification-types`, }) +export const getNotificationRuleSummary = (ruleId: string) => + apiService.fetchData({ + method: 'GET', + url: `/api/app/notification-rule/rule-summary/${ruleId}`, + }) + export const getMyNotificationRules = () => apiService.fetchData({ method: 'GET', @@ -15,7 +25,7 @@ export const getMyNotificationRules = () => export const postMyNotificationRule = (data: { channel: string - notificationType: string + notificationTypeId: string isActive: boolean }) => apiService.fetchData({ @@ -24,9 +34,9 @@ export const postMyNotificationRule = (data: { data, }) -export const deleteMyNotificationRules = (notificationType: string) => +export const deleteMyNotificationRules = (notificationTypeId: string) => apiService.fetchData({ method: 'DELETE', url: `/api/app/notification-rule/my-notification-rules`, - params: { notificationType }, + params: { notificationTypeId }, }) diff --git a/ui/src/services/notification.service.ts b/ui/src/services/notification.service.ts index b524839b..507e8f83 100644 --- a/ui/src/services/notification.service.ts +++ b/ui/src/services/notification.service.ts @@ -15,13 +15,6 @@ export const getList = (params: NotificationFilterRequestDto, config?: Partial - apiService.fetchData({ - method: 'PUT', - url: `/api/app/notification/read/${notificationId}`, - params: { isRead }, - }) - export const updateReadAll = (notificationChannel: string, isRead: boolean) => apiService.fetchData({ method: 'PUT', diff --git a/ui/src/services/notification.signalr.ts b/ui/src/services/notification.signalr.ts new file mode 100644 index 00000000..83a2685c --- /dev/null +++ b/ui/src/services/notification.signalr.ts @@ -0,0 +1,65 @@ +import { store } from '@/store/store' +import * as signalR from '@microsoft/signalr' + +/** Sunucunun duyuruda gönderdiği yük; hangi kanallarda kayıt oluştuğunu bildirir. */ +export type NotificationReceivedDto = { + channels: string[] +} + +type ReceivedHandler = (payload: NotificationReceivedDto) => void + +/** + * Bildirimlerin anında düşmesi için kurulan bağlantı. İstemci yalnızca dinler: duyuruyu alınca + * kendi listesini sunucudan okur, böylece okundu/gönderildi bayrakları tek kaynaktan gelir. + * Bağlantı kurulamazsa bildirim kaybolmaz — dropdown'daki yedek yoklama aynı kaydı getirir. + */ +class NotificationSignalRService { + private connection?: signalR.HubConnection + private receivedHandlers = new Set() + + private createConnection() { + this.connection = new signalR.HubConnectionBuilder() + .withUrl(`${import.meta.env.VITE_API_URL}/notificationhub`, { + accessTokenFactory: () => store.getState().auth.session.token || '', + }) + .withAutomaticReconnect() + .configureLogging(signalR.LogLevel.Warning) + .build() + + this.connection.on('NotificationReceived', (payload: NotificationReceivedDto) => { + this.receivedHandlers.forEach((handler) => handler(payload)) + }) + } + + async start() { + if (!store.getState().auth.session.signedIn) return + + if (!this.connection) { + this.createConnection() + } + + if (this.connection?.state !== signalR.HubConnectionState.Disconnected) return + + try { + await this.connection.start() + } catch { + // Bağlantı kurulamadı; yoklama yedeği devrede kalır. + } + } + + async stop() { + if (!this.connection) return + + const connection = this.connection + this.connection = undefined + + await connection.stop().catch(() => undefined) + } + + onReceived(handler: ReceivedHandler) { + this.receivedHandlers.add(handler) + return () => this.receivedHandlers.delete(handler) + } +} + +export const notificationSignalR = new NotificationSignalRService() diff --git a/ui/src/sw.ts b/ui/src/sw.ts index c31c937c..8e4cf2ec 100644 --- a/ui/src/sw.ts +++ b/ui/src/sw.ts @@ -31,7 +31,19 @@ type SwEvent = Event & { source?: { postMessage: (message: unknown) => void } | null } -type SwClient = { id: string; postMessage: (message: unknown) => void } +type SwClient = { + id: string + url?: string + focus?: () => Promise + navigate?: (url: string) => Promise + postMessage: (message: unknown) => void +} + +/** Masaüstü bildirimine tıklandığında gelen olay; `data` gösterim sırasında yazılan yüktür. */ +type SwNotificationEvent = Event & { + waitUntil: (promise: Promise) => void + notification: { close: () => void; data?: { url?: string } | null } +} declare const self: { __WB_MANIFEST: PrecacheEntry[] @@ -44,6 +56,12 @@ declare const self: { matchAll: (options?: { includeUncontrolled?: boolean; type?: string }) => Promise } addEventListener: (type: string, listener: (event: SwEvent) => void) => void + clients_openWindow?: never +} + +declare const selfWithNotifications: { + clients: { openWindow: (url: string) => Promise } + addEventListener: (type: 'notificationclick', listener: (event: SwNotificationEvent) => void) => void } /* ------------------------------------------------------------------ */ @@ -704,3 +722,45 @@ self.addEventListener('fetch', (event) => { event.respondWith(cacheFirst(request, IMAGE_CACHE, IMAGE_LIMIT)) } }) + +/* ------------------------------------------------------------------ */ +/* Bildirim tıklaması */ +/* ------------------------------------------------------------------ */ + +/** + * Masaüstü bildirimine tıklayınca uygulamaya dönülür: açık bir sekme varsa öne alınıp + * hedef adrese götürülür, yoksa yeni sekme açılır. Hedef, bildirimi gösterirken yazılan + * `data.url` değeridir; yoksa etkinlik kaydı ekranı açılır. + */ +const NOTIFICATION_FALLBACK_URL = '/admin/activityLog' + +;(self as unknown as typeof selfWithNotifications).addEventListener( + 'notificationclick', + (event) => { + event.notification.close() + + const target = event.notification.data?.url || NOTIFICATION_FALLBACK_URL + const targetUrl = new URL(target, self.location.origin).href + + event.waitUntil( + (async () => { + const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' }) + + for (const client of clients) { + if (!client.url) continue + if (client.navigate) { + await client.navigate(targetUrl).catch(() => undefined) + } + if (client.focus) { + await client.focus().catch(() => undefined) + } + return + } + + await (self as unknown as typeof selfWithNotifications).clients + .openWindow(targetUrl) + .catch(() => undefined) + })(), + ) + }, +) diff --git a/ui/src/views/admin/activityLog/ActivityLog.tsx b/ui/src/views/admin/activityLog/ActivityLog.tsx index 63497507..bbc8fa8b 100644 --- a/ui/src/views/admin/activityLog/ActivityLog.tsx +++ b/ui/src/views/admin/activityLog/ActivityLog.tsx @@ -4,7 +4,7 @@ import Drawer from '@/components/ui/Drawer' import Button from '@/components/ui/Button' import NotificationChannels from '@/constants/notification-channel.enum' import { NotificationDto } from '@/proxy/notification/models' -import { getList } from '@/services/notification.service' +import { getList, updateReadAll } from '@/services/notification.service' import { useLocalization } from '@/utils/hooks/useLocalization' import dayjs from 'dayjs' import { groupBy } from '@/utils/collection' @@ -26,6 +26,7 @@ const ActivityLog = () => { const [page, setPage] = useState(0) const [hasMore, setHasMore] = useState(false) const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false) + const [markingRead, setMarkingRead] = useState(false) const direction = useStoreState((state) => state.theme.direction) const [filter, setFilter] = useState([ NotificationChannels.Desktop, @@ -59,7 +60,14 @@ const ActivityLog = () => { const next: NotificationGroups = { ...prev } for (const [key, value] of Object.entries(logs)) { - next[key] = [...(next[key] ?? []), ...value] + // Sayfalama canli veri uzerinde calisir; araya yeni bildirim girdiginde ayni + // kayit ikinci sayfada tekrar gelebilir. Id bazli eleme olmadan liste kaydi iki + // kez cizer ve React ayni anahtari iki kez gorur. + const existingIds = new Set((next[key] ?? []).map((item) => item.id)) + next[key] = [ + ...(next[key] ?? []), + ...value.filter((item) => !existingIds.has(item.id)), + ] } return next @@ -76,6 +84,21 @@ const ActivityLog = () => { fetchData(page > 0) }, [fetchData, page]) + /** + * Ekranda görülen kayıtlar okundu işaretlenmeden rozetteki sayı düşmüyordu. İşaretleme + * süzülen kanallarla sınırlıdır; sunucu iletilmemiş kayıtları zaten atlar. + */ + const handleMarkAllAsRead = async () => { + setMarkingRead(true) + try { + await Promise.all(filter.map((channel) => updateReadAll(channel, true))) + setPage(0) + await fetchData() + } finally { + setMarkingRead(false) + } + } + const handleFilterChange = (value: string[]) => { setPage(0) setNotifications({}) @@ -94,14 +117,25 @@ const ActivityLog = () => {

{translate('::Abp.Identity.ActivityLogs')}

- +
+ + +
diff --git a/ui/src/views/admin/activityLog/components/Event.tsx b/ui/src/views/admin/activityLog/components/Event.tsx index 96f5e574..f600faae 100644 --- a/ui/src/views/admin/activityLog/components/Event.tsx +++ b/ui/src/views/admin/activityLog/components/Event.tsx @@ -8,7 +8,6 @@ import { useLocalization } from '@/utils/hooks/useLocalization' type EventProps = { data: NotificationDto - compact?: boolean } const HighlightedText = ({ children, className }: CommonProps) => { @@ -19,51 +18,29 @@ const HighlightedText = ({ children, className }: CommonProps) => { ) } -const Event = ({ data, compact }: EventProps) => { +const Event = ({ data }: EventProps) => { const { translate } = useLocalization() return ( <> - {compact ? ( - <> -
- {data.creatorFullname} - {dayjs(data.creationTime).format('hh:mm A')} -
-
- - {translate('::Abp.Identity.ActivityLogs.SentNotificationOfType', { - type: data.notificationType, - })}{' '} - - - - {data.notificationChannel} - -
- - ) : ( -
-
- {data.notificationType}{' '} - {translate('::Abp.Identity.ActivityLogs.SentNotification')} -
- - - {data.notificationChannel} - - - {dayjs(data.creationTime).format('LT')} +
+
+ {data.notificationType}{' '} + {translate('::Abp.Identity.ActivityLogs.SentNotification')}
- )} + + + {translate(`::App.NotificationChannel.${data.notificationChannel}`)} + + + {data.creatorFullname && {data.creatorFullname}} + + {dayjs(data.creationTime).format('LT')} +
{data.message && ( {data.message} diff --git a/ui/src/views/admin/activityLog/components/Log.tsx b/ui/src/views/admin/activityLog/components/Log.tsx index 9aab464f..28d600fe 100644 --- a/ui/src/views/admin/activityLog/components/Log.tsx +++ b/ui/src/views/admin/activityLog/components/Log.tsx @@ -48,7 +48,7 @@ const Log = ({ } className="dark:bg-gray-800" > - + )) )} diff --git a/ui/src/views/admin/notification/CreateNotification.tsx b/ui/src/views/admin/notification/CreateNotification.tsx index 0fe6a6d2..924549fb 100644 --- a/ui/src/views/admin/notification/CreateNotification.tsx +++ b/ui/src/views/admin/notification/CreateNotification.tsx @@ -5,20 +5,24 @@ import { FormItem, Input, Notification, + Spinner, + Tag, toast, } from '@/components/ui' +import { NotificationChannelColors } from '@/constants/notification-channel.enum' +import { NotificationRuleSummaryDto } from '@/proxy/notification/models' import { postMyNotificationByNotificationRuleId } from '@/services/notification.service' +import { getNotificationRuleSummary } from '@/services/notification-rule.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { Field, Form, Formik, FormikHelpers } from 'formik' +import { useEffect, useState } from 'react' import * as Yup from 'yup' interface CreateNotificationValues { - id: string message: string } const scheme = Yup.object().shape({ - id: Yup.string().required(), message: Yup.string().required(), }) @@ -33,14 +37,43 @@ function CreateNotification({ }) { const { translate } = useLocalization() + // Diyalog yalnızca kural kimliğini alır; kimin, hangi olayı, hangi kanaldan alacağı + // sunucudan okunur — kullanıcı ham GUID'e bakarak karar veremez. + const [summary, setSummary] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let cancelled = false + + const fetchSummary = async () => { + setLoading(true) + try { + const response = await getNotificationRuleSummary(id) + if (!cancelled) { + setSummary(response.data) + } + } finally { + if (!cancelled) { + setLoading(false) + } + } + } + + if (id) { + fetchSummary() + } else { + setLoading(false) + } + + return () => { + cancelled = true + } + }, [id]) + const handleSubmit = async ( values: CreateNotificationValues, { setSubmitting }: FormikHelpers, ) => { - if (!id) { - return - } - try { await postMyNotificationByNotificationRuleId({ id, message: values.message }) toast.push( @@ -64,39 +97,94 @@ function CreateNotification({ return ( -
{id}
- - {({ touched, errors, isSubmitting }) => { - return ( -
- - - - +
+ {summary?.notificationTypeName || translate('::App.Notifications.CreateTitle')} +
+

{translate('::App.Notifications.CreateHint')}

-
- - -
-
-
- ) - }} -
+ {loading ? ( +
+ +
+ ) : ( + <> + {summary && ( +
+
+ + {translate('::App.Listform.ListformField.Channel')}:{' '} + + + {translate(`::App.NotificationChannel.${summary.channel}`)} + +
+
+ + {translate('::App.Listform.ListformField.IsActive')}:{' '} + + {translate(summary.isActive ? '::App.Platform.Yes' : '::App.Platform.No')} +
+
+ + {translate('::App.Listform.ListformField.RecipientType')}:{' '} + + {summary.recipientType} +
+
+ + {translate('::App.Listform.ListformField.RecipientId')}:{' '} + + {summary.recipientId || '-'} +
+
+ )} + + + {({ touched, errors, isSubmitting }) => { + return ( +
+ + + + + +
+ + +
+
+
+ ) + }} +
+ + )}
) } diff --git a/ui/src/views/admin/profile/components/NotificationSettings.tsx b/ui/src/views/admin/profile/components/NotificationSettings.tsx index 16ae2651..7a3ec183 100644 --- a/ui/src/views/admin/profile/components/NotificationSettings.tsx +++ b/ui/src/views/admin/profile/components/NotificationSettings.tsx @@ -1,52 +1,62 @@ import { Button, Checkbox, Spinner, Table } from '@/components/ui' import NotificationChannels from '@/constants/notification-channel.enum' -import { NotificationRuleDto } from '@/proxy/notification/models' +import { NotificationRuleDto, NotificationTypeDto } from '@/proxy/notification/models' import { deleteMyNotificationRules, getMyNotificationRules, getMyNotificationTypes, postMyNotificationRule, } from '@/services/notification-rule.service' +import { useLocalization } from '@/utils/hooks/useLocalization' import { useCallback, useEffect, useState } from 'react' import { FaHistory } from 'react-icons/fa' const { Tr, Th, Td, THead, TBody } = Table +/** Matris sütunları kanal kayıt defterinden gelir; yeni bir kanal eklendiğinde ekran kendiliğinden güncel olur. */ +const channels = Object.values(NotificationChannels) + const NotificationSettings = () => { + const { translate } = useLocalization() + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) const [list, setList] = useState([]) - const [notificationTypes, setNotificationTypes] = useState([]) - const [channels, setChannels] = useState([]) + const [notificationTypes, setNotificationTypes] = useState([]) const fetchData = useCallback(async () => { setLoading(true) - const channels = Object.keys(NotificationChannels) - setChannels(channels) - const response = await getMyNotificationTypes() - const notificationTypes = response.data - setNotificationTypes(response.data) - const responseRules = await getMyNotificationRules() - const rules = responseRules.data - const finalList: NotificationRuleDto[][] = [] - for (const notificationType of notificationTypes) { - const x: NotificationRuleDto[] = [] - for (const channel of channels) { - const rule = rules.find( - (a) => a.channel === channel && a.notificationType === notificationType, - ) - x.push( - rule ?? - ({ - channel, - notificationType, - isActive: false, - } as NotificationRuleDto), - ) - } - finalList.push(x) + try { + const [typesResponse, rulesResponse] = await Promise.all([ + getMyNotificationTypes(), + getMyNotificationRules(), + ]) + + const types = typesResponse.data ?? [] + const rules = rulesResponse.data ?? [] + + setNotificationTypes(types) + setList( + types.map((type) => + channels.map( + (channel) => + rules.find( + (rule) => rule.channel === channel && rule.notificationTypeId === type.id, + ) ?? { + id: '', + notificationTypeId: type.id, + channel, + isActive: false, + isFixed: false, + isCustomized: false, + }, + ), + ), + ) + } finally { + // Hata durumunda da spinner kapanır; aksi hâlde ekran kalıcı olarak yükleniyor görünürdü. + setLoading(false) } - setList(finalList) - setLoading(false) }, []) useEffect(() => { @@ -54,17 +64,29 @@ const NotificationSettings = () => { }, [fetchData]) const onClickCheckbox = async (checked: boolean, rule: NotificationRuleDto) => { - await postMyNotificationRule({ - channel: rule.channel, - notificationType: rule.notificationType, - isActive: checked, - }) - await fetchData() + // Kayıt sürerken kutular kilitlenir; art arda tıklamada son dönen yanıt kazanır ve + // ekran sunucudaki gerçeğin tersine düşerdi. + setSaving(true) + try { + await postMyNotificationRule({ + channel: rule.channel, + notificationTypeId: rule.notificationTypeId, + isActive: checked, + }) + await fetchData() + } finally { + setSaving(false) + } } - const resetMyNotifications = async (notificationType: string) => { - await deleteMyNotificationRules(notificationType) - await fetchData() + const resetMyNotifications = async (notificationTypeId: string) => { + setSaving(true) + try { + await deleteMyNotificationRules(notificationTypeId) + await fetchData() + } finally { + setSaving(false) + } } return ( @@ -80,36 +102,37 @@ const NotificationSettings = () => { - {channels.map((a) => ( - - {a} + {channels.map((channel) => ( + + {translate(`::App.NotificationChannel.${channel}`)} ))} - {notificationTypes.map((a, m) => ( - + {notificationTypes.map((type, m) => ( +