diff --git a/.github/instructions/ai.instructions.md b/.github/instructions/ai.instructions.md index 17aa3754..957e1203 100644 --- a/.github/instructions/ai.instructions.md +++ b/.github/instructions/ai.instructions.md @@ -23,7 +23,7 @@ Companion documents (this file wins on conflict): | `dotnet.instructions.md` | Binding code standard for everything under `api/` | | `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders | | `../../README.md` | Technical map: what exists, where it lives, how it is wired | -| `../../configs/seeds/host/docs/modules/{Module}.md` | **How a generated module works**: discovery answers, approved recipe, screens, formulas, status rules, permissions, FAQ. Read it first when asked how a module/component behaves; write or update it with every module/component change (`lowcode.instructions.md` §0.13). Index: `README.md` §10.5 | +| `../../configs/seeds/docs/modules/{Module}.md` | **How a generated module works**: discovery answers, approved recipe, screens, formulas, status rules, permissions, FAQ. Read it first when asked how a module/component behaves; write or update it with every module/component change (`lowcode.instructions.md` §0.13). Index: `README.md` §10.5 | | `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo | --- @@ -665,7 +665,7 @@ Platform-owned screens, menus, permissions and language texts. Changing them req a deploy. AI must learn and teach implementation flow primarily from these assets: - ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_Hr.cs / ListFormSeeder_{Modul}.cs -- configs/seeds/host/data/App.Languages.{Language,LanguageText}.json — languages and texts (ListForm seed data) +- configs/seeds/data/App.Languages.{Language,LanguageText}.json — languages and texts (ListForm seed data) - WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read surface B below - ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply @@ -678,11 +678,14 @@ a deploy. AI must learn and teach implementation flow primarily from these asset **B. Runtime seeds (written by the running application)** — `configs/seeds/`, resolved through `SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer, the CRUD Endpoint Manager and every ListForm screen with a `SeedFilePath` write here; the -seeders above restore them when the database is recreated. Scope folders mirror the CDN layout: +seeders above restore them when the database is recreated. There is a single folder and no +tenant scope: the host and every tenant with its own database are seeded from the same files +(`SeedFileScope`; a tenant sharing the host database is skipped — the host seed already wrote +there). Files are written only in host context; a tenant's record never reaches a seed file. ``` -configs/seeds/host/{wizard,data,crud,sql/{object,execute},postgres/{object,execute}} -configs/seeds/tenants/{tenantId}/… same layout, tenant scope +configs/seeds/{wizard,data,crud,docs,sql/{object,execute},postgres/{object,execute}} +configs/seeds/{sql,postgres}/host/{object,execute} host database only (e.g. scripts over HangFire) configs/seeds/.imports/{importId}/… wizard import staging + backup (not scanned) ``` @@ -722,7 +725,7 @@ AI must prioritize metadata-based field behavior instead of suggesting custom Re ### 31.3 Step 3 - Add menu and route mapping -From the `configs/seeds/host/data/App.Menus.*.json` patterns, AI must define both: +From the `configs/seeds/data/App.Menus.*.json` patterns, AI must define both: - `App.Menus.Routes.json` row: - `Id`, `Key`, `Path`, `ComponentPath`, `RouteType`, `Authority` @@ -739,7 +742,7 @@ AI must ensure menu URL and route path point to same screen contract. ### 31.4 Step 4 - Add permissions -From the `configs/seeds/host/data/Abp.Identity.Permission*.json` patterns, AI must define: +From the `configs/seeds/data/Abp.Identity.Permission*.json` patterns, AI must define: - Permission group (if missing) - Permission definitions for feature root and actions @@ -759,7 +762,7 @@ AI must never propose menu/route without permission contract. ### 31.5 Step 5 - Add settings/integration dependencies -From the `configs/seeds/host/data/App.SettingDefinitions.json` / `App.Definitions.AiBot.json` patterns, AI must define required settings when feature depends on external services: +From the `configs/seeds/data/App.SettingDefinitions.json` / `App.Definitions.AiBot.json` patterns, AI must define required settings when feature depends on external services: - Sender credentials and endpoints (sms/mail/whatsapp/rocket) - AiBot integration endpoint and activation state @@ -907,8 +910,8 @@ Display names are read from the language key whose name equals the group name. ### 32.5 Wizard export/import is a package, not a file -Exporting a wizard produces a zip whose paths are relative to the scope folder -(`host` / `tenants/{tenantId}`) and which carries every dependency: +Exporting a wizard produces a zip whose paths are relative to the seed folder and which carries +every dependency: The closure is **recursive** (`WizardExportCollector`): a sub-form's wizard file is queued and its own dependencies are collected under the same rules, so the archive stands on its own. diff --git a/.github/instructions/list.instructions.md b/.github/instructions/list.instructions.md index 2fa744a0..f753dee7 100644 --- a/.github/instructions/list.instructions.md +++ b/.github/instructions/list.instructions.md @@ -18,7 +18,7 @@ This document summarizes the rules, standards, and step-by-step instructions for - Table names: `{Modul}_T_{Entity}` (e.g., `Mrp_T_Order`) - Menu/permission keys: `App.{Modul}`, `App.{Modul}.{Liste}` - **Tenant Isolation:** All data, forms, and permissions must be tenant-aware. -- **Localization:** Every menu, list, and field must have a corresponding entry in `configs/seeds/host/data/App.Languages.LanguageText.json`. +- **Localization:** Every menu, list, and field must have a corresponding entry in `configs/seeds/data/App.Languages.LanguageText.json`. - **No Redundant Code:** Avoid duplicating permission grants or seeding logic across modules. - **SeedConsts Rule:** For every new module/entity, add a constant to `SeedConsts` (e.g., `public static class {Modul}` and `public const string {Liste}`) if it does not already exist. Never duplicate existing constants. - **DeleteCommand Rule:** In all `ListFormSeeder_{Modul}.cs` files, the `DeleteCommand` property **must** use the `DefaultDeleteCommand` function, e.g., `DeleteCommand = DefaultDeleteCommand("{TabloAdı}")`. Never use a raw SQL string directly for `DeleteCommand`. @@ -27,10 +27,10 @@ This document summarizes the rules, standards, and step-by-step instructions for ## Special File Handling Rules -- **configs/seeds/host/data/App.Menus.*.json:** +- **configs/seeds/data/App.Menus.*.json:** - Never add, remove or re-path a route in `App.Menus.Routes.json`. The only field set you may edit on an existing row is its `Seo*` columns (lowcode §0.9). Only `App.Menus.Module.json` and `App.Menus.Menu.json` can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to those two files as required by platform design. -- **configs/seeds/host/data/App.Languages.LanguageText.json:** +- **configs/seeds/data/App.Languages.LanguageText.json:** - When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates. - Each key is two rows (`CultureName` `en` and `tr`). `Id` = UUID v5 (URL namespace) of `Sas_H_LanguageText:{ResourceName}:{Key}:{CultureName}`; never a random GUID. @@ -39,11 +39,11 @@ This document summarizes the rules, standards, and step-by-step instructions for ## File-by-File Change Summary -### 1. configs/seeds/host/data/App.Menus.Menu.json +### 1. configs/seeds/data/App.Menus.Menu.json - **{Modul} menu** added as a top-level menu with `ModuleId: {Modul}`; the module row goes into `App.Menus.Module.json`. - **{Liste}** added as a child menu under {Modul}. -### 2. configs/seeds/host/data/Abp.Identity.Permissions.json +### 2. configs/seeds/data/Abp.Identity.Permissions.json - **{Modul} permissions** grouped under `App.{Modul}`. - **{Liste} permissions** nested under {Modul} group. @@ -74,11 +74,11 @@ This document summarizes the rules, standards, and step-by-step instructions for ### 1. Add a New Module (e.g., {Modul}) ``` Yeni bir modül ekle (ör: {Modul}): -- configs/seeds/host/data/App.Menus.Menu.json'a kök menü, App.Menus.Module.json'a modül kaydı olarak ekle -- configs/seeds/host/data/Abp.Identity.PermissionGroups.json'da ayrı bir grup oluştur +- configs/seeds/data/App.Menus.Menu.json'a kök menü, App.Menus.Module.json'a modül kaydı olarak ekle +- configs/seeds/data/Abp.Identity.PermissionGroups.json'da ayrı bir grup oluştur - ListFormSeeder_{Modul}.cs dosyası oluştur ve list-form seed'ini buraya taşı - SqlObjects.sql'de tabloyu {Modul}_T_{Entity} olarak adlandır -- configs/seeds/host/data/App.Languages.LanguageText.json dosyasına menü, liste ve alan çevirilerini ekle +- configs/seeds/data/App.Languages.LanguageText.json dosyasına menü, liste ve alan çevirilerini ekle ``` ### 2. Add a New List Under a Module @@ -87,8 +87,8 @@ Yeni bir liste ekle (ör: {Liste}): - {Modul} ana menüsünün altına ekle - ListFormSeeder_{Modul}.cs dosyasına seed kodunu ekle - SqlObjects.sql'de tabloyu {Modul}_T_{Entity} olarak oluştur -- configs/seeds/host/data/Abp.Identity.Permissions.json'da ilgili izinleri {Modul} grubuna ekle -- configs/seeds/host/data/App.Languages.LanguageText.json dosyasına liste ve alan çevirilerini ekle +- configs/seeds/data/Abp.Identity.Permissions.json'da ilgili izinleri {Modul} grubuna ekle +- configs/seeds/data/App.Languages.LanguageText.json dosyasına liste ve alan çevirilerini ekle ``` ### 3. Enforce Tenant Isolation and Full Audit @@ -101,7 +101,7 @@ Tablo ve list-form için tenant izolasyonu ve full-audit alanlarını ekle: ### 4. Add/Update Localization ``` -Yeni menü, liste veya alan eklediğinde configs/seeds/host/data/App.Languages.LanguageText.json dosyasına (EN + TR satırı) şu şekilde ekle: +Yeni menü, liste veya alan eklediğinde configs/seeds/data/App.Languages.LanguageText.json dosyasına (EN + TR satırı) şu şekilde ekle: - App.{Modul} - App.{Modul}.{Liste} - App.Listform.ListformField.{Alan} diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index 6930919e..8f8c9180 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -13,7 +13,7 @@ sorusunda buraya bak. **Kaynak:** Buradaki her tablo `api/src/Sozsoft.Platform.Application.Contracts/ListForms/**` (DTO), `api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm{,Field}.cs` (entity), `ui/src/views/{list,form}/**` (alanın gerçekten okunup okunmadığı) ve §12 için -`configs/seeds/host/data/App.Menus.*.json` + `ListFormSeeder_*.cs` dosyalarından çıkarılmıştır. +`configs/seeds/data/App.Menus.*.json` + `ListFormSeeder_*.cs` dosyalarından çıkarılmıştır. Çelişki hâlinde **kod esastır**; bu dosya güncellenir. **Ortak kurallar** @@ -224,6 +224,15 @@ eklenen join ve `WHERE` parçası (`SelectQueryManager`; `ServerWhere` token'lar ile çözülür). **Satır bazlı veri kısıtının** kod yazmadan kurulduğu yer burasıdır; ListForm editörünün **Kişiselleştirme** sekmesinden yazılır. +**İstemci filtresi sözleşmesi** (`list-form-select/select` `filter` parametresi; grid, rapor, widget ve MCP +`query_list` aynı yoldan geçer): yalnızca DevExtreme filtre dizisi kabul edilir — `["Alan","op",değer]`, +`["Alan",değer]`, `[koşul,"and"|"or",koşul]`, `["!",koşul]`, iç içe gruplar. Alan ekranın alanı ya da anahtar +alan olmalıdır (değilse koşul `1 = 1` olur), operatör `= <> < <= > >= contains notcontains startswith +endswith` dışındaysa ve JSON bozuksa istek reddedilir; değer her zaman parametredir. Filtre tek grup +olarak paranteze alınır, ardından kiracı/şube/OU koşulları `AND` ile eklenir. Toplam/grup alanları da +ekranın alanlarından, toplam türü `sum avg min max count`'tan seçilir. "Tüm kayıtları sil" +(`createDeleteQuery`) ekranın silme yetkisini ve `AllowAllDeleting`'i birlikte ister. + ### 2.6 Filtre, arama ve gruplama panelleri **`FilterRowJson`** (`GridFilterRowDto`): `Visible` (`false`), `ApplyFilter` (`auto` \| `onClick`), @@ -1263,7 +1272,6 @@ soneklerini alır. | **Public › Sales Orders** | `App.Orders.SalesOrders` (alt form Sales Order Item) | `Sas_H_Order` + `Sas_H_OrderItem` | host | **Onay akışı + alt kalem + toplam + diyalog** birlikte kullanılan tam örnek (§12.7). | | **Public › Blog Category / Posts** | `App.BlogManagement.Category` · `.Posts` | `Sas_H_BlogCategory` · `Sas_H_BlogPost` | host | Çok dilli içerik (`ContentTr`/`ContentEn`), slug, yayın durumu, sayaç alanları; public `/blog` bu tablolardan okur, yayınlanmış yazılar sitemap'e girer. | | **Public › Demos** | `App.Demos` | `Sas_H_Demo` | host | Public `/demo` formundan gelen talepler — public form → tablo akışının örneği. | -| **Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`; detayda `Description` yanında `Analysis` sekmesi (`AnalysisExpr`). MCP geliştirme döngüsünün kuyruğudur (`Backlog` → `Analysis` MCP analiz eder, `Ready to Start`'a kullanıcı alır; `Ready to Start` → `Testing`, `Staging`'e kullanıcı alır; araç çubuğunda DB Migrate butonu vardır — `UiEvalService.ApiDbMigrate()`, önce onay sorar, `App.Setup.Migrate`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, analiz `save_dev_request_analysis` ile yazılır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir — `Description` talebin yazıldığı hâliyle kalır; `lowcode.instructions.md` §0.11–§0.12). | | **Tenants** | `AbpTenantManagement.Tenants` | `AbpTenants` | host | Kiracı kaydı, bağlantı dizesi diyaloğu (`TenantsConnectionString`), alt form olarak şubeler; yazma `list-form-dynamic-api/tenant-*`. Ekranların `IsTenant` davranışının kaynağı. | | **Branches** | `App.Branches` | `Sas_T_Branch` | tenant | Şube listesi. Ekranı `IsBranch = true` yaparsan filtre bu tablodan gelir; `BranchSeed` diyaloğu şube verisini kurar. | | **Setting Definitions** | `App.SettingDefinitions` | `Sas_H_SettingDefinition` | host | Yeni uygulama ayarı: `Code`, `MainGroupKey`/`SubGroupKey`, `DataType`, `SelectOptions` (`List` tipinde değer → etiket; etiket dil anahtarıdır, Ayarlar ekranı `::` önekiyle çevirir, anahtar yoksa metin olduğu gibi görünür), `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. | @@ -1271,6 +1279,7 @@ soneklerini alır. | **Data Source** | `App.DataSource` | `Sas_H_DataSource` | host | `Code`, `DataSourceType`, `ConnectionString`. **İki tür kayıt bir arada:** elle tanımlanan host/harici bağlantılar ve `Code = tenant adı` olan otomatik tenant bağlantıları (Tenants ekranındaki bağlantı diyaloğu yönetir, elle dokunma). Varsayılan kaydı `HostDataSeeder` appsettings'teki bağlantı cümlesinden kurar. Çözüm kuralı `lowcode` §9.3.3. Bağlantı dizesi koda/JSON'a yazılmaz. | | **Background Workers › Recurring Jobs / Jobs** | `App.BackgroundWorkers.RecurringJobs` · `.Jobs` | `Sas_H_BackgroundWorker` · `Sas_H_BackgroundWorker_JobFlow` | host | Cron'lu işler ve çalıştırma geçmişi (`Tenant`, `ExecutionDate`, `Duration`, `Status`, `FailureReason`, `ExceptionDetails`). `WorkerType`/`Scope` değerleri §11'de; her kapsam ayrı bir Hangfire işidir, `Tenant` kolonu hangi kiracıda çalıştığını gösterir. Hazır işler (oturum temizliği, bildirim, yedek, şema sözlüğü) `data/App.BackgroundWorkers.RecurringJobs.json`'dan gelir. Ayrıntı: `lowcode` §9.10, `README.md` §14. | | **Menus › Module / Routes / Menu / Manager** | `App.Menus.Module` · `.Routes` · `.Menu` · `.Manager` (`/admin/menuManager`) | `Sas_H_Module` · `Sas_H_Route` · `Sas_H_Menu` | host | Menü ve route veritabanından gelir; üretilen ekranın menü kaydı buradan açılır. `Menu` ekranı ağaç düzeninin referansıdır. Seed: `data/App.Menus.{Routes,Module,Menu}.json` — `Routes` dosyasına yalnızca var olan satırın `Seo*` alanları için dokunulur. | +| **Developer Kit › Dev Requests** | `App.DevRequests` | `Sas_H_DevRequest` | host | **Kanban/Todo düzeninin** referansı (`DefaultLayout: todo`): `Status`, `Priority`, `SortOrder`, `Tags`, `Assignees`, `SubTasks`, `Timesheets`; detayda `Description` yanında `Analysis` sekmesi (`AnalysisExpr`). MCP geliştirme döngüsünün kuyruğudur (`Backlog` → `Analysis` MCP analiz eder, `Ready to Start`'a kullanıcı alır; `Ready to Start` → `Testing`, `Staging`'e kullanıcı alır; araç çubuğunda DB Migrate butonu vardır — `UiEvalService.ApiDbMigrate()`, önce onay sorar, `App.Setup.Migrate`; yalnızca `start_dev_request` başlatır; açıklamadaki base64 görseller MCP'ye resim içeriği olarak gider, analiz `save_dev_request_analysis` ile yazılır, üretim notu `append_dev_request_note` ile `Analysis`'e eklenir — `Description` talebin yazıldığı hâliyle kalır; `lowcode.instructions.md` §0.11–§0.12). | | **Developer Kit › SQL Query Manager** | `App.SqlQueryManager` · `/admin/sqlQueryManager` | `Sas_H_CrudEndpoint` (üretilen CRUD uçları) | — | View/procedure/function/tablo tasarımı ve çalıştırma, tablodan CRUD endpoint üretimi (`.CrudEndpoints` yetkisi); karar sırasının 2. adımı buradan başlar. | | **Developer Kit › Files** | `App.Files` · `/admin/files` | Blob depolama | tenant | Dosya yöneticisi. Ekranın dosya alanı için ayrı depolama kurma; dosya/görsel editörleri ve Intranet Documents bu altyapıyı kullanır. | | **Developer Kit › Custom Endpoints** | `App.DeveloperKit.CustomEndpoints` | `Sas_H_CustomEndpoint` | host | `Name`, `Url`, `Method`, `DataSourceCode`, `Sql`, `ParametersJson` (`Static`/`Query`/`Path`/`Body`), `PermissionsJson` (`Global`/`Role`/`User`) — parametreli SQL'i yetkili bir HTTP ucuna çevirir. | @@ -1279,7 +1288,7 @@ soneklerini alı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 › Intranet Widgets** | `App.DeveloperKit.IntranetWidgets` | `Sas_T_IntranetWidget` | tenant | Intranet panosunda hangi widget'ın hangi kolonda (`Column`), hangi sırada (`Order`) ve hangi yetkiyle (`PermissionName`) çizileceği; `SourceType` `Builtin` (koddaki `Code`) ya da `Custom` (`ComponentName` ile bir custom component). Seed: `data/App.DeveloperKit.IntranetWidgets.json`. | | **Developer Kit › MCP Servers** | `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), `MaxRows`. Yetkiler: ekran `App.DeveloperKit.McpServers`, erişim ve okuma `.Invoke`, her değişiklik `.Write` (kiracıda satır yazma, host'ta ayrıca tablo/ekran/bileşen geliştirme) — arkasındaki ekran C/U/D, Wizard ve Custom Components yetkileri ayrıca istenir. Kiracı veri okur ve yazar, geliştirme yapamaz (`TenantTools`: şema araçları, `query_list`, `run_select`, `insert_row`/`update_row`/`delete_row`); ham SQL `App.SqlQueryManager` ister; MCP yetki vermez. Kayıtlar `data/App.DeveloperKit.McpServers.json`'a senkronlanır. Ayrıntı: `README.md` §13.9. | -| **Developer Kit › Wizard Manager** | menü kodu `App.Listforms.WizardManager` · yetki `App.Listforms.Wizard` · `/admin/listform/wizardManager` | `configs/seeds/{kapsam}/wizard/*.json` | host | Wizard seed dosyalarının kart/liste görünümü: menü üst düğümü ve URL, **bağımlılıklar**, düzenleme, export (bağımlılık kapanışı) / import ve **silme** (`WizardRecordCleaner`: ekran, veri aynası ve izlenen kayıtları kaldırılır, başka ekran kullanıyorsa engellenir, sonuç raporlanır; onay diyaloğu ekranın tablosunu sorar, seçilirse `WizardTableCleaner` tabloyu scripti ve crud dosyasıyla düşürür — `lowcode` §4.5). Sihirbazın kendisi `/admin/listform/wizard`. | +| **Developer Kit › Wizard Manager** | menü kodu `App.Listforms.WizardManager` · yetki `App.Listforms.Wizard` · `/admin/listform/wizardManager` | `configs/seeds/wizard/*.json` | host | Wizard seed dosyalarının kart/liste görünümü: menü üst düğümü ve URL, **bağımlılıklar**, düzenleme, export (bağımlılık kapanışı) / import ve **silme** (`WizardRecordCleaner`: ekran, veri aynası ve izlenen kayıtları kaldırılır, başka ekran kullanıyorsa engellenir, sonuç raporlanır; onay diyaloğu ekranın tablosunu sorar, seçilirse `WizardTableCleaner` tabloyu scripti ve crud dosyasıyla düşürür — `lowcode` §4.5). Sihirbazın kendisi `/admin/listform/wizard`. | | **Forum Management › Categories / Topics / Posts** | `App.ForumManagement.Categories` · `.Topics` · `.Posts` | `Sas_T_ForumCategory` · `Sas_T_ForumTopic` · `Sas_T_ForumPost` | tenant | Forumun yönetim tarafı: kategori (seed `data/App.ForumManagement.Categories.json`), konu (kategori lookup'ı, sayaçlar, sabitle/kilitle/çözüldü bayrakları), gönderi (konu lookup'ı, kabul edilen yanıt). Kullanıcı tarafı Administration'daki `/admin/forum`. | > Menü kodu ile yetki adı **her zaman aynı değildir**: Wizard Manager'da kod @@ -1310,7 +1319,7 @@ ekranları ABP tablolarıdır, Licence/Dev Request host tablolarını okur. | **Identity › Permission Groups / Permissions** | `Abp.Identity.PermissionGroups` · `Abp.Identity.Permissions` | `AbpPermissionGroups` · `AbpPermissions` | Yetki ağacı (tree düzeni). Yeni ekranın yetkileri burada görünür; `Modules` kolonu yetkiyi modüllere bağlar (pipe ile çoklu), `Order` kardeşler arası sıradır. Seed: `data/Abp.Identity.Permission{Groups,s}.json`. | | **Identity › Claim Type** | `App.IdentityManagement.ClaimType` | `AbpClaimTypes` | Kullanıcıya ek alan (claim) tanımı: `ValueType`, `Regex`, `Required`, `IsStatic`. | | **Identity › Roles** | `AbpIdentity.Roles` | `AbpRoles` | Rol yönetimi + `RolesPermission` diyaloğu; yeni ekranın yetkisi role buradan verilir. Yazma `list-form-dynamic-api/role-*`. | -| **Identity › Users** | `AbpIdentity.Users` · detay `/admin/users/detail/:userId` | `AbpUsers` | Kullanıcı yönetimi + `UsersPermission` diyaloğu, **KPI widget'ları** ve `list-form-dynamic-api/user-*` ile devredilen yazma. Kullanıcı `WorkHour`, `DepartmentId`, `JobPositionId`, `Gender` (`Male`/`Female`, `App.StaticLookup.*`) ve `HomeAddress` alanlarını liste ve formda taşır; kişisel ve kimlik bilgileri (`IdentityNumber`, `BirthDate`, `EducationLevel`, `Nationality`, `SskNo`…) detay ekranındadır. Detay sekmeleri karışmasın diye gruplanmıştır: **General** (iletişim + ikinci e-posta/telefon, ek bilgi, avatar, askerlik `MilitaryStatus`/`MilitaryPostponeEndDate`, engellilik `IsDisabled`/`DisabilityRate`/`DisabilityReport` — rapor base64, 5 MB), **Identity** (nüfus cüzdanı, doğum ve aile, uyruk Türkiye değilse yabancı uyruk `Passport*`/`WorkPermit*`), **Work** (organizasyon `RegistrationNo`/`SskNo`/`ShiftId`/`PdksCardNo`, sözleşme `EmploymentType`/`ContractType`/`ContractEndDate`/`ProbationEndDate`/`SeniorityStartDate`, çıkış `TerminationDate`/`TerminationReasonId`/`TerminationNote`, sürücü belgesi `DrivingLicense*` — sınıflar `|` ile, iletişim kanalları; altında iş deneyimi), **Education & Skills** (en yüksek `EducationLevel` + eğitim/dil/yetenek/belge alt listeleri), **Family / Emergency**, **Financial** (`BankName`, `Iban`, `SalaryType`, `GrossSalary`, `SalaryCurrency` + yan haklar), Health, Permissions, Lockout, Claims. Deneme süresi işe girişten en fazla dört ay sonra biter (`UpdateUserAsync` ve ekran doğrular). Ücret alanları `UserInfoViewModel`'de taşınmaz — o model intranet doğum günü listesine de akar; `GET {id}/financial` / `PUT financial` uçlarından okunur, yetki detayınkiyle aynıdır (`AbpIdentity.Users.Update`). Aynı sebeple `DisabilityReport` yalnızca `GetByIdAsync`'te doldurulur. `ShiftId`, `TerminationReasonId`, `SalaryCurrency` seçenekleri ekranda `list-form-select/lookup` ile bu ListForm'un alan lookup'larından okunur (`useUserFieldLookup`). Detaydaki **Sağlık Kartı** sekmesi `BloodType` ile soy geçmişini (`{Mother,Father,Sibling,Child}HealthStatus` — `Alive`/`Deceased`, `App.StaticLookup.*` — ve `…HealthNote`) ve öz geçmişini (`ChronicDisease`, `TetanusVaccination`, `HepatitisVaccination`, `OtherVaccination`) taşır; sekme dört gruptur (Soy/Öz Geçmişi, Laboratuvar - Fizik Muayene `Lab*`/`Exam*`, Anamnez `Complaint*`/`Diagnosis*` + 3-10. sorular, Sonuç `FitForWork*`/`ShiftWorkFit`/`NightWorkFit`). Sonuçtaki hekim (`FitnessDoctorId`, `ShiftNightDoctorId`) wizard tablosu `Hr_T_Doctor`'dan `LookupQueryValues.TenantDoctorValues` ile seçilir; diploma ve işyeri hekimliği belgesi hekimden gösterilir, kullanıcıya yazılmaz. Bu alanların hepsi listede gizli alandır; arayüz `user-management/HealthCardTab.tsx`. Kullanıcı seçen her lookup bu bilgileri `AbpUsers`'tan okur ve kopya kolon aynı ad/tip/değer kümesiyle açılır (`lowcode.instructions.md` §0.12 madde 5). Yeni bir kullanıcı alanı = `PlatformConsts.AbpIdentity.User` + `AbpIdentityUserExtensions` + EF eşlemesi ve migration + `UserInfoViewModel` (detay/profil) + `CreateUpdateUserInput` ve `user-insert`/`user-update` (liste) + `ListFormSeeder_Administration` alanı. | +| **Identity › Users** | `AbpIdentity.Users` · detay `/admin/users/detail/:userId` | `AbpUsers` | Kullanıcı yönetimi + `UsersPermission` diyaloğu, **KPI widget'ları** ve `list-form-dynamic-api/user-*` ile devredilen yazma. Kullanıcı `WorkHour`, `DepartmentId`, `JobPositionId`, `Gender` (`Male`/`Female`, `App.StaticLookup.*`) ve `HomeAddress` alanlarını liste ve formda taşır; kişisel ve kimlik bilgileri (`IdentityNumber`, `BirthDate`, `EducationLevel`, `Nationality`, `SskNo`…) detay ekranındadır. Detay sekmeleri karışmasın diye gruplanmıştır: **General** (iletişim + ikinci e-posta/telefon, ek bilgi, avatar, askerlik `MilitaryStatus`/`MilitaryPostponeEndDate`, engellilik `IsDisabled`/`DisabilityRate`/`DisabilityReport` — rapor base64, 5 MB), **Identity** (nüfus cüzdanı, doğum ve aile, uyruk Türkiye değilse yabancı uyruk `Passport*`/`WorkPermit*`), **Work** (organizasyon `RegistrationNo`/`SskNo`/`ShiftId`/`PdksCardNo`, sözleşme `EmploymentType`/`ContractType`/`ContractEndDate`/`ProbationEndDate`/`SeniorityStartDate`, çıkış `TerminationDate`/`TerminationReasonId`/`TerminationNote`, sürücü belgesi `DrivingLicense*` — sınıflar `|` ile, iletişim kanalları; altında iş deneyimi), **Education & Skills** (en yüksek `EducationLevel` + eğitim/dil/yetenek/belge alt listeleri), **Family / Emergency**, **Financial** (`BankName`, `Iban`, `SalaryType`, `GrossSalary`, `SalaryCurrency` + yan haklar), Health, Permissions, Lockout, Claims. Deneme süresi işe girişten en fazla dört ay sonra biter (`UpdateUserAsync` ve ekran doğrular). Ücret alanları `UserInfoViewModel`'de taşınmaz — o model intranet doğum günü listesine de akar; `GET {id}/financial` / `PUT financial` uçlarından okunur, yetki detayınkiyle aynıdır (`AbpIdentity.Users.Update`). Aynı sebeple `DisabilityReport` yalnızca `GetByIdAsync`'te doldurulur. `ShiftId`, `TerminationReasonId`, `SalaryCurrency` seçenekleri ekranda `list-form-select/lookup` ile bu ListForm'un alan lookup'larından okunur (`useUserFieldLookup`). Detaydaki **Sağlık Kartı** sekmesi `BloodType` ile soy geçmişini (`{Mother,Father,Sibling,Child}HealthStatus` — `Alive`/`Deceased`, `App.StaticLookup.*` — ve `…HealthNote`) ve öz geçmişini (`ChronicDisease`, `TetanusVaccination`, `HepatitisVaccination`, `OtherVaccination`) taşır; sekme dört gruptur (Soy/Öz Geçmişi, Laboratuvar - Fizik Muayene `Lab*`/`Exam*`, Anamnez `Complaint*`/`Diagnosis*` + 3-10. sorular, Sonuç `FitForWork*`/`ShiftWorkFit`/`NightWorkFit`). Sonuçtaki hekim (`FitnessDoctorId`, `ShiftNightDoctorId`) wizard tablosu `Hr_T_Doctor`'dan `LookupQueryValues.TenantDoctorValues` ile seçilir; diploma ve işyeri hekimliği belgesi hekimden gösterilir, kullanıcıya yazılmaz. Bu alanların hepsi listede gizli alandır; arayüz `user-management/HealthCardTab.tsx`. Kullanıcı seçen her lookup bu bilgileri `AbpUsers`'tan okur ve kopya kolon aynı ad/tip/değer kümesiyle açılır (`lowcode.instructions.md` §0.12 madde 5). Yeni bir kullanıcı alanı = `PlatformConsts.AbpIdentity.User` + `AbpIdentityUserExtensions` + EF eşlemesi ve migration + `UserInfoViewModel` (detay/profil) + `CreateUpdateUserInput` ve `user-insert`/`user-update` (liste) + `ListFormSeeder_Administration` alanı + gerekiyorsa yönetici varsayılanı (`PlatformConsts.AbpIdentity.User.Admin*DefaultValue` + `PlatformIdentityDataSeeder`; host ve tenant Seed sekmesinin yöneticisi buradan oluşur). | | **Identity › Users › Work Experience** | `App.IdentityManagement.UserWorkExperience` (menüsüz alt liste) | `Adm_T_UserWorkExperience` | Kullanıcının daha önce çalıştığı yerler: `CompanyName`, `SectorId` (Sektörler lookup'ı, `LookupQueryValues.TenantSectorValues`), `StartDate`, `EndDate`, `JobDescription` (HtmlEditor). Entity `IMultiTenant`; kullanıcı detayının Work sekmesinde `PlatformViewHost` ile `UserId` filtresiyle gömülüdür, yeni satır `UserId`'yi filtreden alır. Kendi yetkisi yoktur: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme detay sayfasının yetkisi `AbpIdentity.Users.Update`. | | **Identity › Users › Health Card lists** | `App.IdentityManagement.UserPeriodicExamination` · `.UserPatientProtocol` · `.UserSickLeave` (menüsüz alt listeler) | `Adm_T_UserPeriodicExamination` · `Adm_T_UserPatientProtocol` · `Adm_T_UserSickLeave` | Periyodik muayene (hekim `Hr_T_Doctor`'dan, diploma no seçimde `EditorScript.Copy` ile kopyalanır), hasta protokol defteri (`ProtocolType`/`ProtocolResult` statik lookup) ve istirahat listesi (`Days` tarihlerden `EditorScript.Days` ile hesaplanır). Entity'ler `IMultiTenant`; Sağlık Kartı sekmesinde (`HealthCardTab.tsx`) `UserId` filtresiyle gömülüdür. Yetki Work Experience ile aynıdır: okuma `AbpIdentity.Users`, ekleme/güncelleme/silme `AbpIdentity.Users.Update`. | | **Identity › Users › Education & Skills lists** | `App.IdentityManagement.UserEducation` · `.UserLanguage` · `.UserSkill` · `.UserCertificate` (menüsüz alt listeler) | `Adm_T_UserEducation` · `Adm_T_UserLanguage` · `Adm_T_UserSkill` · `Adm_T_UserCertificate` | Eğitim geçmişi (`EducationLevel` kullanıcınınkiyle aynı değerler, `SchoolName`, `Department`, `StartDate`, `EndDate`, `IsGraduated`), yabancı dil (`SkillId` → `Languages` tipindeki `Hr_T_Skill`, `SkillLevelId` dilin tipine ait `Hr_T_SkillLevel`, cascade), yetenek (`SkillId` → `Languages` dışındaki tipler, `Level` 1-5 statik lookup, check constraint) ve yeterlilik belgesi (`CertificateId` → wizard tablosu `Hr_T_Certificate`, EF ilişkisi yok; `CertificateNo`, `IssuedBy`, `IssueDate`, `ExpiryDate`). Lookup'lar `LookupQueryValues.Tenant{LanguageSkill,SkillLevelBySkill,NonLanguageSkill,Certificate}Values`. `EducationSkillsTab.tsx`; yetki Work Experience ile aynıdır. | @@ -1395,9 +1404,9 @@ listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — ` | **Recruitment › Application** | `App.Hr.JobApplication` (alt formlar `App.Hr.Interview`, `App.Hr.JobOffer`) | `Hr_T_JobApplication` | tenant | Aday × ilan başvurusu (çift tekil). `Stage`: `Applied` → `Screening` → `Interview` → `Offer` → `Hired` / `Rejected`; **Todo (kanban) görünümü** aşama sütunlarıyla. Mülakat eklenince `Interview`, teklif eklenince `Offer`, teklif `Accepted` olunca `Hired`'a ilerler (after-command SQL, geri gitmez; insert sonrası komutlar yalnızca eklenen satıra `@Id` ile çalışır). `CandidateName`/`PostingTitle` kopyalanır (kanban başlığı). Ret sebebi yalnızca `Rejected` aşamasında düzenlenir. Alt form olduğu için widget yok. Wizard. | | **Recruitment › Interview** | `App.Hr.Interview` (alt form `App.Hr.InterviewScore`) | `Hr_T_Interview` · `Hr_T_InterviewScore` | tenant | Mülakat planlama: tür, başlangıç/bitiş (**Scheduler** görünümü), mülakatçı, durum, öneri. Kayıt eklenince mülakatçıya `IseAlimMulakatAtandi` bildirimi SQL ile yazılır (tipin `Custom` kuralları kanalı belirler, metin `App.Hr.Interview.Notification.*`). Puan alt formu kriter başına 1–5; `OverallScore` = Σ(puan × ağırlık) / Σ ağırlık, başvurunun `AverageScore`'u mülakatların ortalaması. Wizard. | | **Recruitment › Job Offer** | `App.Hr.JobOffer` | `Hr_T_JobOffer` | tenant | Teklif: `OfferNo` (`TKL-…`), başvuru seçilince pozisyon bilgileri ilandan `copy` ile dolar, brüt ücret + para birimi, yan haklar (`Hr_T_BenefitType` kodları), `Status` (`Draft`/`Sent`/`Accepted`/`Declined`/`Expired`, yanıt tarihi otomatik). İç onay **Grid Workflow** (Kadro Talebi ile aynı akış). Onaylı satırda **Teklif Mektubu** butonu (`DynamicReport`, `Raporlar` kategorisi). Wizard. | -| **Performance Management** | `App.Hr.Performance` dalı (yalnız İK): `PerformancePeriod`, `PerformanceReview`, `PerformanceCalibration`, `DevelopmentPlan`; tanım `App.Hr.PerformanceRating` (İK › Tanımlar). Çalışana ait `MyPerformanceReview`, `TeamPerformanceReview`, `My360Feedback` menüsüzdür; Intranet › Dashboard'daki custom widget'lardan (`MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget`) açılır | `Hr_T_PerformanceRating` · `Hr_T_PerformancePeriod` · `Hr_T_PerformancePeriodSkill` · `Hr_T_PerformanceReview` · `Hr_T_PerformanceGoal` · `Hr_T_PerformanceCompetency` · `Hr_T_PerformanceFeedback` · `Hr_T_PerformanceFeedbackScore` · `Hr_T_DevelopmentPlan` | tenant | Dönem → değerlendirme (hedef KPI/OKR + yetkinlik `Hr_T_Skill`/`Hr_T_SkillLevel` + isteğe bağlı 360) → kalibrasyon → gelişim planı. **Aynı tabloya persona ekranları**: İK (tümü), çalışan (`EmployeeId = '@USERID'`), yönetici (`ManagerId = '@USERID'`), 360 değerlendiricisi (`ReviewerId = '@USERID'`); kilitli alanlar İK ekranının yetkisine bağlıdır (sunucu düşürür), satır sahipliği ve aşama kuralı `Hr_S_PerformanceGuard` (before-command). Hesap ve durum geçişi `Hr_S_PerformanceReviewRecalc` / `Hr_S_PerformanceReviewAfterSave`; tamamlanan değerlendirme `Adm_T_UserSkill`'e yazar. Widget'lar veriyi ekranın kendi sorgusundan (`list-form-select/select`) okur. Çalışma şekli ve kararlar: `configs/seeds/host/docs/modules/HrPerformance.md`. Wizard + Custom Component (pano kutusu). | -| **Leave Management** | `App.Hr.Leave` dalı (yalnız İK): `LeaveRequest` (Grid Workflow: Müdür Onayı `@ManagerUserNames` → İK Onayı; üst pozisyonu olmayan çalışanda müdür adımı `RequiresManagerApproval` karşılaştırmasıyla atlanır), `LeaveBalance` (alt formlar `LeaveBalanceAdjustment`, `LeaveBalanceYear`). Çalışana ait `MyLeaveRequest`, `MyLeaveBalance` ve müdüre ait `TeamLeaveRequest` menüsüzdür; Intranet › Dashboard'daki `MyLeaveWidget` / `TeamLeaveWidget`'tan açılır; kullanıcı detayında **İzinler** sekmesi | `Hr_T_LeaveRequest` · `Hr_T_LeaveBalanceAdjustment` · görünümler `Hr_V_LeaveBalanceYear` · `Hr_V_LeaveSummary` | tenant | Gün sayısı `Hr_F_LeaveWorkDays` (Ayarlar › İnsan Kaynakları › İzin çalışma günleri + `Hr_T_WorkCalendar`); bakiye İş Kanunu md.53 kademeleri (ayarlardan) + devir + düzeltme; müdür = departmanın en üst pozisyonunun sahibi. Doğrulama, müdür çözümü, rapor ↔ `Adm_T_UserSickLeave` eşlemesi ve bildirim `Hr_S_LeaveRequestAfterSave`; satır koruması `Hr_S_LeaveRequestGuard`; Sağlık Kartı tarafı `Hr_S_LeaveSickLeaveSync`. SQL'den ayar okuma `Hr_F_HrSetting` (kiracı → genel → varsayılan). Çalışma şekli: `configs/seeds/host/docs/modules/HrLeave.md`. Wizard + Custom Component + küçük UI/motor eki. | -| **Timesheet & Attendance** | `App.Hr.Attendance` dalı (yalnız İK): `AttendanceLog` (ham okutma; Excel import), `Timesheet` (çalışan × gün; düzenlenen gün `IsManual`), `TimesheetMonthly` (salt okunur aylık özet, alt form `Timesheet` `Id → PeriodKey`), `TimesheetPeriod` (ay yeniden hesap / kapanış). Çalışana ait `MyTimesheet` (+ alt form `MyTimesheetDay`) menüsüzdür; Intranet › Dashboard'daki `MyTimesheetWidget`'tan açılır; kullanıcı detayında **Puantaj** sekmesi | `Hr_T_AttendanceLog` · `Hr_T_Timesheet` · `Hr_T_TimesheetPeriod` · görünümler `Hr_V_AttendanceShift` · `Hr_V_TimesheetMonthly` | tenant | Hesap `Hr_S_TimesheetCalculate` (gün tipi: rapor > ücretsiz izin > `Hr_T_WorkCalendar` tatili > hafta tatili (İzin çalışma günü ayarları) > ücretli izin > okutma > eğitim katılımı (`Training`) > varsayılan çalışma / devamsız; izinli günde `LeaveTypeId`, eğitim gününde `TrainingId`; mola `MAX(vardiya, okutulan)`; geç/erken vardiya toleransı aşılırsa tamamı; fazla mesai haftalık 45 saati aşan kısım + tatil çalışması). Kişi eşlemesi `AbpUsers.PdksCardNo` / `RegistrationNo`; kartı olan çalışan ilk PDKS okutmasından itibaren okutmayla, diğerleri elle (kayıtsız gün `IsAssumed`) takip edilir. Tetikler: hareket kaydı `Hr_S_AttendanceProcess`, PDKS Custom Endpoint `Hr_S_AttendanceImport` (`/hr/attendance/`), dönem `Hr_S_TimesheetPeriodAfterSave`, gece işi `Hr_S_TimesheetNightly`, izin/rapor prosedürleri, `Hr_S_TrainingSync`, `Hr_S_WorkCalendarAfterSave`. Modüller arası çakışma kuralları (raporlu gün devamsız değildir, rapora denk gelen izin günü bakiyeye döner, kapalı aya izin/rapor yazılmaz) `HrAttendance.md` §7.1'dedir. Ayarlar `App.HrSettings.Attendance.*`. Çalışma şekli: `configs/seeds/host/docs/modules/HrAttendance.md`. Wizard + Custom Component + Custom Endpoint + zamanlanmış iş + küçük UI eki. | +| **Performance Management** | `App.Hr.Performance` dalı (yalnız İK): `PerformancePeriod`, `PerformanceReview`, `PerformanceCalibration`, `DevelopmentPlan`; tanım `App.Hr.PerformanceRating` (İK › Tanımlar). Çalışana ait `MyPerformanceReview`, `TeamPerformanceReview`, `My360Feedback` menüsüzdür; Intranet › Dashboard'daki custom widget'lardan (`MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget`) açılır | `Hr_T_PerformanceRating` · `Hr_T_PerformancePeriod` · `Hr_T_PerformancePeriodSkill` · `Hr_T_PerformanceReview` · `Hr_T_PerformanceGoal` · `Hr_T_PerformanceCompetency` · `Hr_T_PerformanceFeedback` · `Hr_T_PerformanceFeedbackScore` · `Hr_T_DevelopmentPlan` | tenant | Dönem → değerlendirme (hedef KPI/OKR + yetkinlik `Hr_T_Skill`/`Hr_T_SkillLevel` + isteğe bağlı 360) → kalibrasyon → gelişim planı. **Aynı tabloya persona ekranları**: İK (tümü), çalışan (`EmployeeId = '@USERID'`), yönetici (`ManagerId = '@USERID'`), 360 değerlendiricisi (`ReviewerId = '@USERID'`); kilitli alanlar İK ekranının yetkisine bağlıdır (sunucu düşürür), satır sahipliği ve aşama kuralı `Hr_S_PerformanceGuard` (before-command). Hesap ve durum geçişi `Hr_S_PerformanceReviewRecalc` / `Hr_S_PerformanceReviewAfterSave`; tamamlanan değerlendirme `Adm_T_UserSkill`'e yazar. Widget'lar veriyi ekranın kendi sorgusundan (`list-form-select/select`) okur. Çalışma şekli ve kararlar: `configs/seeds/docs/modules/HrPerformance.md`. Wizard + Custom Component (pano kutusu). | +| **Leave Management** | `App.Hr.Leave` dalı (yalnız İK): `LeaveRequest` (Grid Workflow: Müdür Onayı `@ManagerUserNames` → İK Onayı; üst pozisyonu olmayan çalışanda müdür adımı `RequiresManagerApproval` karşılaştırmasıyla atlanır), `LeaveBalance` (alt formlar `LeaveBalanceAdjustment`, `LeaveBalanceYear`). Çalışana ait `MyLeaveRequest`, `MyLeaveBalance` ve müdüre ait `TeamLeaveRequest` menüsüzdür; Intranet › Dashboard'daki `MyLeaveWidget` / `TeamLeaveWidget`'tan açılır; kullanıcı detayında **İzinler** sekmesi | `Hr_T_LeaveRequest` · `Hr_T_LeaveBalanceAdjustment` · görünümler `Hr_V_LeaveBalanceYear` · `Hr_V_LeaveSummary` | tenant | Gün sayısı `Hr_F_LeaveWorkDays` (Ayarlar › İnsan Kaynakları › İzin çalışma günleri + `Hr_T_WorkCalendar`); bakiye İş Kanunu md.53 kademeleri (ayarlardan) + devir + düzeltme; müdür = departmanın en üst pozisyonunun sahibi. Doğrulama, müdür çözümü, rapor ↔ `Adm_T_UserSickLeave` eşlemesi ve bildirim `Hr_S_LeaveRequestAfterSave`; satır koruması `Hr_S_LeaveRequestGuard`; Sağlık Kartı tarafı `Hr_S_LeaveSickLeaveSync`. SQL'den ayar okuma `Hr_F_HrSetting` (kiracı → genel → varsayılan). Çalışma şekli: `configs/seeds/docs/modules/HrLeave.md`. Wizard + Custom Component + küçük UI/motor eki. | +| **Timesheet & Attendance** | `App.Hr.Attendance` dalı (yalnız İK): `AttendanceLog` (ham okutma; Excel import), `Timesheet` (çalışan × gün; düzenlenen gün `IsManual`), `TimesheetMonthly` (salt okunur aylık özet, alt form `Timesheet` `Id → PeriodKey`), `TimesheetPeriod` (ay yeniden hesap / kapanış). Çalışana ait `MyTimesheet` (+ alt form `MyTimesheetDay`) menüsüzdür; Intranet › Dashboard'daki `MyTimesheetWidget`'tan açılır; kullanıcı detayında **Puantaj** sekmesi | `Hr_T_AttendanceLog` · `Hr_T_Timesheet` · `Hr_T_TimesheetPeriod` · görünümler `Hr_V_AttendanceShift` · `Hr_V_TimesheetMonthly` | tenant | Hesap `Hr_S_TimesheetCalculate` (gün tipi: rapor > ücretsiz izin > `Hr_T_WorkCalendar` tatili > hafta tatili (İzin çalışma günü ayarları) > ücretli izin > okutma > eğitim katılımı (`Training`) > varsayılan çalışma / devamsız; izinli günde `LeaveTypeId`, eğitim gününde `TrainingId`; mola `MAX(vardiya, okutulan)`; geç/erken vardiya toleransı aşılırsa tamamı; fazla mesai haftalık 45 saati aşan kısım + tatil çalışması). Kişi eşlemesi `AbpUsers.PdksCardNo` / `RegistrationNo`; kartı olan çalışan ilk PDKS okutmasından itibaren okutmayla, diğerleri elle (kayıtsız gün `IsAssumed`) takip edilir. Tetikler: hareket kaydı `Hr_S_AttendanceProcess`, PDKS Custom Endpoint `Hr_S_AttendanceImport` (`/hr/attendance/`), dönem `Hr_S_TimesheetPeriodAfterSave`, gece işi `Hr_S_TimesheetNightly`, izin/rapor prosedürleri, `Hr_S_TrainingSync`, `Hr_S_WorkCalendarAfterSave`. Modüller arası çakışma kuralları (raporlu gün devamsız değildir, rapora denk gelen izin günü bakiyeye döner, kapalı aya izin/rapor yazılmaz) `HrAttendance.md` §7.1'dedir. Ayarlar `App.HrSettings.Attendance.*`. Çalışma şekli: `configs/seeds/docs/modules/HrAttendance.md`. Wizard + Custom Component + Custom Endpoint + zamanlanmış iş + küçük UI eki. | | **Org Chart** | `App.Hr.OrgChart` · `/admin/organization` | `Hr_T_Department` · `Hr_T_JobPosition` | tenant | Departman/ünvan verisinin organizasyon şeması görünümü (özel React sayfası `hr/OrgChart`, veri `api/app/org-chart`). Menü ve route aynı yetkiyi ister. | | **Intranet › Announcement** | `App.Hr.Intranet.Announcement` (alt formlar Like/Comment) | `Hr_T_Announcement` · `Hr_T_AnnouncementLike` · `Hr_T_AnnouncementComment` | tenant | Duyuru + beğeni + yorum: **ana kayıt / alt etkileşim** kalıbının hazır örneği. | | **Intranet › Survey** | `App.Hr.Intranet.Survey` (alt form Question → Option; Response → Answer) | `Hr_T_Survey` · `Hr_T_SurveyQuestion` · `Hr_T_SurveyQuestionOption` · `Hr_T_SurveyResponse` · `Hr_T_SurveyAnswer` | tenant | Anket motoru; soru–seçenek–yanıt zinciri **iki seviyeli alt form** ihtiyacının referansıdır. | @@ -1408,15 +1417,15 @@ listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — ` **Şube bilgisi:** `Department.BranchId` ve `JobPosition.BranchId` (`Guid`, **zorunlu** — kolon `NOT NULL`, formda zorunlu alan; değer `Sas_T_Branch.Id`) şubeyi taşır; ekranlar `IsBranch = false`'tur (satırlar kullanıcının şube atamasına göre süzülmez, şube sütunu veridir). Host varsayılan şubesinin kimliği sabittir -(`PlatformConsts.Branches.BranchIdDefaultValue`; `PlatformIdentityDataSeeder` / -`TenantIdentityDataSeeder` host şubesi için bu kimliği kullanır, kiracı şubeleri yeni GUID alır); +(`PlatformConsts.Branches.BranchIdDefaultValue`; `PlatformIdentityDataSeeder` host şubesi için bu +kimliği kullanır, kiracı şubeleri yeni GUID alır); seed edilen departman ve pozisyon satırları `BranchId` ile bu şubeye bağlanır. Yeni seed satırı eklerken `BranchId` de yazılır. ### 12.5 ListForm olmayan (özel React) ekranlar Bu ekranlar `Sas_H_Route` kayıtlarıyla yönetilir (seed: -`configs/seeds/host/data/App.Menus.Routes.json`). Yenisini eklemek **kod yazmak demektir** — karar +`configs/seeds/data/App.Menus.Routes.json`). Yenisini eklemek **kod yazmak demektir** — karar sırasının son adımıdır ve çerçeve dışıdır (onay ister, `lowcode` §0.8). Rotalı bir sayfa ihtiyacının çerçeve içi karşılığı `RoutePath` taşıyan bir custom component'tir. @@ -1515,9 +1524,9 @@ alanları da doldurur (`lowcode` §0.9). | "izin türü, masraf türü, yan hak türü, vardiya, resmi tatil / çalışma takvimi" | İK › Definitions · İK › Work Calendar · İK › Shift | Var olan `Hr_T_WorkCalendar` / `Hr_T_LeaveType` / `Hr_T_ExpenseType` / `Hr_T_BenefitType` / `Hr_T_Shift` tablosuna lookup bağla; yenisini üretme | | "eğitim / katılımcı, araç / şoför / araç masrafı / araç çıkış-giriş, olay günlüğü, disiplin / tutanak / savunma / ihtar" | İK › Trainings · İK › Vehicles · İK › Event Log · İK › Disciplinary Case | Var olan `Hr_T_Training*` / `Hr_T_Vehicle*` / `Hr_T_Driver*` / `Hr_T_EventLog` / `Hr_T_Disciplinary*` ekranını genişlet ya da lookup bağla; paralel tablo üretme | | "işe alım, kadro talebi, ilan, aday / CV, mülakat, teklif" | İK › Recruitment | Var olan `Hr_T_StaffRequest` / `Hr_T_JobPosting` / `Hr_T_Candidate` / `Hr_T_JobApplication` / `Hr_T_Interview` / `Hr_T_JobOffer` ekranını genişlet; personel kullanıcısını BT elle açar, aday kaydına `UserId` ile bağlanır | -| "performans değerlendirme, hedef / KPI / OKR, yetkinlik değerlendirmesi, 360, kalibrasyon, gelişim planı" | İK › Performance Management | Var olan `Hr_T_Performance*` / `Hr_T_DevelopmentPlan` ekranını genişlet; yeni persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_PerformanceGuard` ile açılır (`configs/seeds/host/docs/modules/HrPerformance.md`) | -| "izin talebi, izin onayı, izin bakiyesi, yıllık izin hakkı, rapor/istirahat izni" | İK › Leave Management | Var olan `Hr_T_LeaveRequest` ekranını genişlet; persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_LeaveRequestGuard` ile açılır, bakiye `Hr_V_LeaveSummary`'den okunur (`configs/seeds/host/docs/modules/HrLeave.md`) | -| "puantaj, devam takibi, PDKS, giriş-çıkış, geç kalma, fazla mesai, devamsızlık" | İK › Timesheet & Attendance | Var olan `Hr_T_AttendanceLog` / `Hr_T_Timesheet` ekranlarını genişlet; hesap `Hr_S_TimesheetCalculate`'te tek yerdedir, kural değişikliği oraya yazılır (`configs/seeds/host/docs/modules/HrAttendance.md`) | +| "performans değerlendirme, hedef / KPI / OKR, yetkinlik değerlendirmesi, 360, kalibrasyon, gelişim planı" | İK › Performance Management | Var olan `Hr_T_Performance*` / `Hr_T_DevelopmentPlan` ekranını genişlet; yeni persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_PerformanceGuard` ile açılır (`configs/seeds/docs/modules/HrPerformance.md`) | +| "izin talebi, izin onayı, izin bakiyesi, yıllık izin hakkı, rapor/istirahat izni" | İK › Leave Management | Var olan `Hr_T_LeaveRequest` ekranını genişlet; persona ekranı aynı tabloya `DefaultFilter` + `Hr_S_LeaveRequestGuard` ile açılır, bakiye `Hr_V_LeaveSummary`'den okunur (`configs/seeds/docs/modules/HrLeave.md`) | +| "puantaj, devam takibi, PDKS, giriş-çıkış, geç kalma, fazla mesai, devamsızlık" | İK › Timesheet & Attendance | Var olan `Hr_T_AttendanceLog` / `Hr_T_Timesheet` ekranlarını genişlet; hesap `Hr_S_TimesheetCalculate`'te tek yerdedir, kural değişikliği oraya yazılır (`configs/seeds/docs/modules/HrAttendance.md`) | | "yapay zekâ cevaplasın" | Saas › Definitions › AiBot | Bot tanımı; `/admin/ai` bu tanımı kullanır | ### 12.7 Canlı referans uygulamalar diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 48c2b35f..cb0aa8a6 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -33,7 +33,7 @@ Bir istek geldiğinde çekirdek koda dokunmak değil, `configs/seeds/` altına u Kullanıcı "şöyle bir ekran/form/süreç istiyorum" dediğinde **uygulamayı kullanıyormuş gibi** davran: Wizard'ın, Custom Components ekranının ve SQL Query Manager'ın diske yazacağı dosyaların -aynılarını `configs/seeds/host/` altına sen yaz. Ekranlara tıklamanın yerine geçen şey budur. +aynılarını `configs/seeds/` altına sen yaz. Ekranlara tıklamanın yerine geçen şey budur. Talep iki biçimde gelir ve ikisi de aynı hatta girer: @@ -47,10 +47,11 @@ ve onay, en son dosyalar.** ### 0.1 Yazılacak klasörler ``` -configs/seeds/host/ +configs/seeds/ ├── sql/object/{Nesne}.sql ← tablo / view / fonksiyon / prosedür (SQL Server) ├── sql/execute/{Ad}.sql ← bir kez çalışacak script (veri dolumu, migrasyon) ├── postgres/object|execute/… ← aynısının PostgreSQL diyalekti (hedef PG ise) +├── {sql|postgres}/host/object|execute/… ← yalnızca host veritabanında çalışan script (ör. HangFire'a dayanan view) ├── crud/{EntityName}.json ← tablonun REST uçları ├── wizard/{WizardName}.json ← ekran + menü + yetki + dil anahtarları (List yolunda sunucu yazar, §4.1) ├── docs/modules/{Modül}.md ← modül dokümanı; iskelet + envanteri deploy yazar (§0.13) @@ -60,7 +61,8 @@ configs/seeds/host/ │ data/App.DeveloperKit.CustomComponents/{Id}.json ``` -Tenant kapsamı isteniyorsa `host/` yerine `tenants/{tenantId}/`; iç düzen aynıdır. +Tenant'a özel seed klasörü yoktur: host ve kendi veritabanı olan her tenant aynı dosyalarla +kurulur; host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (`SeedFileScope`). ### 0.2 Üretim sırası — bu sırayla yaz, bu sırayla çalışır @@ -487,7 +489,7 @@ ya da ziyaretçiye açık yeni bir fiziksel sayfa ürettiğinde. açıklama, paylaşım görseli, Twitter hesabı, diller, robots yönergesi, Google doğrulaması, sosyal hesaplar, varsayılan anahtar kelimeler (`DefaultKeywords`), sitemap anahtarı (`SitemapEnabled`) ve sitemap dışı yollar (`SitemapExcludePaths`) `App.SiteManagement.Seo.*` ayarlarındadır -(`configs/seeds/host/data/App.SettingDefinitions.json`). Bir değer bütün site için geçerliyse +(`configs/seeds/data/App.SettingDefinitions.json`). Bir değer bütün site için geçerliyse oraya yazılır, her yola tekrarlanmaz. Site kökü (`/`) hiçbir kayıt taşımaz; doğrudan bu varsayılanlardan beslenir. @@ -539,8 +541,8 @@ eklenen dosya) bu satırlar ekranla birlikte seed edilir; migrate çalıştığ içerikli satır dosyada varsa (tekrar deploy) yeniden eklenmez. 2. DB Migrate'i bekleme. Migrate wizard seeder ile ekranı kurar, ardından `ListFormSeedDataApplier` dosyadaki satırları ekler (anahtarı olmayanı ekler, var olana dokunmaz). -3. Migrate'ten sonra ekrandan girilen satırlar `ListFormSeedDataSynchronizer` ile aynı dosyaya yazılır - (kiracı bağlamında `tenants/{TenantId}/data/`). +3. Migrate'ten sonra host bağlamında ekrandan girilen satırlar `ListFormSeedDataSynchronizer` ile aynı + dosyaya yazılır; kiracı bağlamındaki kayıt dosyaya yansımaz. Kurallar: - Dosyayı elle yazma; `seedRows` alan adlarını ekranın alanlarına göre doğrular, bilinmeyen alanı reddeder. @@ -805,7 +807,7 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma. ihtiyaç, §0.8, adıyla). Talebi daraltan geçici çözüm önerilmez; "Hayır" taşıyan talep yetenek eklenene kadar `Ready to Start`'a alınmaz. -### 0.13 Modül dokümanı — `configs/seeds/{kapsam}/docs/modules/{Modül}.md` +### 0.13 Modül dokümanı — `configs/seeds/docs/modules/{Modül}.md` Üretilen her modül kendi çalışma şeklini bir md dosyasında taşır; "bu ekran nasıl çalışıyor / şu puan nereden geliyor" sorusu kod ve seed taranmadan bu dosyadan cevaplanır (MCP'de `get_module_doc`). @@ -813,9 +815,8 @@ Doküman **wizard'ın parçasıdır**: Wizard ekranından ya da MCP `deploy_wiza modülde sunucu onu kendisi açar ve güncel tutar; anlatım bölümlerini üreten kişi/AI doldurur. Wizard dışı (entity, özel React) modüller dokümanı elle yazar. -- **Yer:** kapsam seed klasörü — `configs/seeds/host/docs/modules/{Modül}.md` (kiracı kapsamında - `tenants/{tenantId}/docs/modules`). Sunucu yalnızca seed köküne yazabildiği için depo kökünde - değildir. Wizard export zip'i dokümanı taşır, import yazar (`docs/modules/*.md`). +- **Yer:** seed klasörü — `configs/seeds/docs/modules/{Modül}.md`. Sunucu yalnızca seed köküne + yazabildiği için depo kökünde değildir. Wizard export zip'i dokümanı taşır, import yazar (`docs/modules/*.md`). - **Hangi wizard hangi dokümana** (`ModuleDocCatalog`; wizard sözleşmesine alan eklenmez): 1. Dokümanın ilk satırındaki beyan ekranı sahipleniyorsa o doküman: `` (başka dalda @@ -914,8 +915,7 @@ Tutarlılık zorunludur; menü, route, yetki ve dil anahtarı aynı kökten tür | CRUD endpoint seed | `crud/{EntityName}.json` | `Mrp_T_Order.json` | | Liste verisi seed | `data/{ListFormCode}.json` (tek dosya) ya da `data/{ListFormCode}/{anahtar}.json` (klasör) — §9.15 | `App.Definitions.Sequence.json` | -Kapsam klasörü: host bağlamında `configs/seeds/host/…`, tenant bağlamında -`configs/seeds/tenants/{tenantId}/…`. Çözüm her zaman `SeedPathResolver` üzerinden yapılır; +Seed klasörü tektir: `configs/seeds/…`. Çözüm her zaman `SeedPathResolver` üzerinden yapılır; yol elle kurulmaz. --- @@ -1015,7 +1015,7 @@ Her editörün kendine özgü seçenekleri için §4.3.2. ## 4. Artefakt A — ListForm ekranı (Wizard seed dosyası) -Konum: `configs/seeds/{kapsam}/wizard/{WizardName}.json` (dosya adında tarih yoktur) +Konum: `configs/seeds/wizard/{WizardName}.json` (dosya adında tarih yoktur) Okuyan: `WizardDataSeeder` (migrate) · Yazan: `ListFormWizardAppService` (deploy) ve `WizardSeedSynchronizer` (ListForm editöründen kayıt) · Okuma/yazma her zaman `WizardSeedFileDto.FromSeedJson` / `ToSeedJson` ile — düz `JsonSerializer` bloklu bölümleri düşürür. @@ -1046,7 +1046,8 @@ Kurulum **DB Migrate**'tedir (Wizard Manager'daki buton, `POST /api/setup/migrat `WizardDataSeeder` ekrana özgü kayıtları kurar (veri kaynağı, ListForm, alanlar, iş akışı), ardından `ListFormSeedDataApplier` menü/yetki/dil satırlarını ekler (eksik olanı ekler, var olana dokunmaz). **Yetkiler hiçbir role otomatik atanmaz** — admin dahil; ekranı hangi rolün göreceği rol ekranından -seçilir. +seçilir. Tek istisna yeni kiracının yöneticisidir: ilk seed'de, o anda tanımlı olan kiracı tarafı +yetkilerin hepsini alır (`PlatformIdentityDataSeeder`). Deploy'dan migrate'e kadar wizard'ın hiçbir kaydı veritabanında yoktur ve bu beklenen durumdur. Yeni modül, kök/üst menü, izin grubu, yetki ya da dil anahtarı veritabanında bulunmuyorsa @@ -2419,7 +2420,7 @@ ardından `ListFormSeedDataApplier` menü/yetki/dil satırlarını kurar (§4.0) 6. Deploy + tamamlandı Başarıda sihirbaz "Migrate bekliyor" bildirimiyle Wizard Manager'a döner (ekran henüz kurulu -değildir; DB Migrate ile kurulur) ve seed dosyası `configs/seeds/{kapsam}/wizard/` altına yazılmış +değildir; DB Migrate ile kurulur) ve seed dosyası `configs/seeds/wizard/` altına yazılmış olur. **Düzenleme modu** Wizard Manager'dan `EditFileName` ile açılır: aynı adımlar dolu gelir, kaydetmede dosyalar cevaptan yeniden yazılır; önceki sürümün kayıtları `Pending.Replaced`'a alınır, veritabanına dokunulmaz — kaldırma ve yeniden kurulum DB Migrate'tedir (§4.0). @@ -2814,7 +2815,7 @@ ListForm ile ifade edilemeyen ekranın yolu **fiziksel React sayfası değil**, ### 7.1 Seed dosyası -`configs/seeds/{kapsam}/data/App.DeveloperKit.CustomComponents/{Id}.json` — Custom Components +`configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json` — Custom Components ekranının veri aynası, **klasör modunda** (§9.15): her bileşen kendi dosyasındadır, dosya adı satırın `Id`'sidir. Dosya yine liste verisi zarfını taşır; `Rows` tek satırdır. Listeden ve Visual Designer'dan (`CustomComponentAppService`) yapılan her kayıt bu klasöre yansır. @@ -3514,7 +3515,7 @@ tabloları için yalnız `T`/`D` üretir.) 1. Üretilen SQL hedef veri kaynağında çalıştırılır (yeni tabloda `CREATE`, düzenleme modunda **`ALTER` diff'i**). 2. Seed dosyası kaydedilir: her zaman **tam `CREATE TABLE` script'i** (diff değil), - `save-table-script` ucu ile `configs/seeds/{kapsam}/{sql|postgres}/object/{TabloAdı}.sql` + `save-table-script` ucu ile `configs/seeds/{sql|postgres}/object/{TabloAdı}.sql` altına (`SeedPathResolver`). Dosya kaydı başarısız olsa deploy geri alınmaz — log'a düşer. 3. CRUD adımında işaretlendiyse endpoint'ler üretilir (`crud/{Entity}.json`); onay kutusu **varsayılan kapalıdır**, Custom Component'in kullanacağı tabloda açılır. @@ -3575,7 +3576,7 @@ tasarımcıyı kaybettirir. > **Kalıcılık — kritik ayrım:** diske otomatik seed yazan tek tasarımcı **tablo > tasarımcısıdır**. View Designer'ın deploy'u, sorgu editöründen çalıştırılan > `CREATE PROCEDURE`/`CREATE FUNCTION` ve her türlü elle DDL **yalnızca veritabanına** gider. -> Bu nesnelerin `configs/seeds/{kapsam}/{sql|postgres}/object/{Ad}.sql` dosyasını **sen +> Bu nesnelerin `configs/seeds/{sql|postgres}/object/{Ad}.sql` dosyasını **sen > yazarsın** — prompt ile üretimde zaten dosyayı doğrudan yazdığın için bu senin doğal yolun; > uygulama içinden üretim yapıldıysa dosyaya alınmamış nesne veritabanı sıfırlanınca kaybolur. @@ -3642,7 +3643,7 @@ Yeni bir entity/tablo `sql/object/` altına yazılır ve veritabanı `SqlDataSee dosyalardan kurulur. Mekanik: 1. **Yalnızca aktif sağlayıcının klasörü çalışır.** `DefaultDatabaseProvider` SQL Server ise - `{kapsam}/sql/`, PostgreSQL ise `{kapsam}/postgres/`. İki dosya da yazmak taşınabilirlik + `sql/`, PostgreSQL ise `postgres/`. İki dosya da yazmak taşınabilirlik içindir; aynı anda ikisi birden çalışmaz. 2. Sıra: `object/` → `execute/`, her klasörün içinde **dosya adına göre alfabetik**. Bir tablo başka bir tabloya FK ile bağlıysa dosya adları o sırayı vermelidir (ör. `Hr_T_Employee.sql` @@ -3698,9 +3699,15 @@ konfigürasyon ekranlarını (9.9–9.14) ve liste verisi seed dosyasını (9.15 ### 9.1 CRUD Endpoint seed dosyası -Konum: `configs/seeds/{kapsam}/crud/{EntityName}.json` · Okuyan: `CrudEndpointSeedApplier` +Konum: `configs/seeds/crud/{EntityName}.json` · Okuyan: `CrudEndpointSeedApplier` (migrate'te `CrudDataSeeder`, import kapanışında `CompleteImport` üzerinden — kural tek yerdedir) +Çalışma zamanı sınırı (`CrudEndpointAppService` + `DynamicEntityManager`): uçlar **giriş ister** +(anonim/public sayfadan çağrılamaz). Tabloda +`TenantId` varsa okuma, güncelleme ve silme isteği yapanın kiracısıyla sınırlıdır (host için +`TenantId IS NULL`), ekleme `TenantId`'yi sunucudan yazar; istemcinin gönderdiği `Id`/`TenantId` yok sayılır. +`PasswordHash` ve `SecurityStamp` yanıtta dönmez ve yazılamaz. + ```jsonc { "EntityName": "Mrp_T_Order", @@ -3815,7 +3822,7 @@ Endpoint **tanımlarını** düzenleme yetkileri ListForm ekranının `App.DeveloperKit.CustomEndpoints{,.Create,.Update,.Delete}` yetkileridir — çağırma yetkilerinden (`.Get/.Post/.Put/.Remove` + `PermissionsJson`) ayrıdır. -**Seed:** `configs/seeds/{kapsam}/data/App.DeveloperKit.CustomEndpoints.json` (§9.15). Ekranın +**Seed:** `configs/seeds/data/App.DeveloperKit.CustomEndpoints.json` (§9.15). Ekranın `SeedSyncInsert/Update/Delete` bayrakları **açıktır**: ekrandan eklenen/değiştirilen/silinen kayıt dosyaya yansır, migrate'te eksikse geri eklenir. Prompt ile üretimde satırı bu dosyaya yaz (`Id` sabit GUID; `ParametersJson`/`PermissionsJson` kaçışlanmış string). @@ -4027,7 +4034,7 @@ Editör davranışları: Kalıcılık kuralı (§8.2'deki uyarının özeti): **seed dosyasını otomatik yazan tek akış tablo tasarımcısının deploy'udur.** View Designer deploy'u ve editörden çalıştırılan her DDL (view/SP/function) yalnızca veritabanına gider; kalıcı olması için -`configs/seeds/{kapsam}/{sql|postgres}/{object|execute}/{ad}.sql` dosyası ayrıca yazılır. +`configs/seeds/{sql|postgres}/{object|execute}/{ad}.sql` dosyası ayrıca yazılır. Prompt ile üretimde bu ayrım seni etkilemez — dosyayı zaten sen yazarsın (§0.2 sırası) — ama "uygulamadan ürettim, seed'i var mı?" sorusunun cevabı budur. @@ -4630,7 +4637,7 @@ Bu tür isteklerin tamamı aynı iskeleti kullanır. Kullanıcı "avans talep fo üretilecek dosya seti: ``` -configs/seeds/host/ +configs/seeds/ ├── sql/object/Hr_T_AdvanceRequest.sql ← tablo (+ PostgreSQL hedefse postgres/object/…) ├── crud/Hr_T_AdvanceRequest.json ← yalnızca Custom Component yolunda gerekir ├── data/App.DeveloperKit.CustomComponents/{Id}.json ← yalnızca serbest yerleşim isteniyorsa @@ -4698,13 +4705,13 @@ tedarikçi lookup'ı + iki kademeli onay). veri aynası ve `InsertedRecords` içindeki kullanılmayan kayıtlar gider; tablo yalnızca onayda seçilirse (MCP: `dropTables: true`) scripti ve crud dosyasıyla düşer (§4.5). Dosyayı diskten silmek kayıtları temizlemez. -- [ ] Modül dokümanı (`configs/seeds/{kapsam}/docs/modules/{Modül}.md`) yazıldı ya da güncellendi; analiz/kararlar, +- [ ] Modül dokümanı (`configs/seeds/docs/modules/{Modül}.md`) yazıldı ya da güncellendi; analiz/kararlar, ekranlar, hesaplar ve yetkiler üretilen artefaktların gerçek adlarıyla yazılı, README §10.5 dizininde satırı var (§0.13). Prompt ile üretim yaptıysan ek olarak: -- [ ] Dosyalar doğru kapsam klasöründe (`host/` ya da `tenants/{tenantId}/`). +- [ ] Dosyalar `configs/seeds/` altındaki doğru klasörde (§0.1). - [ ] Üretim sırası doğru (§0.2) ve bağımlı wizard'ın `SeededAt` damgası daha büyük. - [ ] Tablo/kolon adları uydurulmadı; ya var olan nesneden okundu ya da `sql/object/` altında üretildi. diff --git a/README.md b/README.md index 36e52aee..da98b268 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,6 @@ sozsoft-platform/ │ │ ├── Sozsoft.Platform.Application/ # Uygulama servisleri (iş mantığı) │ │ ├── Sozsoft.Platform.EntityFrameworkCore/ # DbContext, migration'lar, dinamik veri katmanı │ │ ├── Sozsoft.Platform.HttpApi/ # Controller'lar (conventional) -│ │ ├── Sozsoft.Platform.HttpApi.Client/ # HTTP istemci proxy'leri │ │ ├── Sozsoft.Platform.HttpApi.Host/ # Host: Swagger, auth, Hangfire, SignalR, raporlama │ │ └── Sozsoft.Platform.DbMigrator/ # Migration + seed uygulaması │ ├── modules/ # Bağımsız ABP modülleri @@ -179,9 +178,8 @@ sozsoft-platform/ ├── configs/ │ ├── docker/ # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler │ ├── deployment/ # Dev/Prod compose dosyaları, nginx/redis/turn konfigürasyonu, deploy scriptleri -│ ├── seeds/ # Runtime'da düzenlenebilen seed'ler; CDN ile aynı kapsam düzeni -│ │ # host/{crud,custom,wizard,sql,postgres,docs/modules} -│ │ # tenants/{tenantId}/{crud,custom,wizard,sql,postgres} +│ ├── seeds/ # Runtime'da düzenlenebilen seed'ler; host ve tenant'lar için tek klasör +│ │ # {crud,wizard,data,docs/modules} │ │ # {sql|postgres}/{object,execute} │ └── ai/ # AI entegrasyonu (n8n akış tanımları, Ollama model kurulum notları) ├── .github/instructions/ # ai.instructions.md (bağlayıcı platform kuralları) @@ -244,32 +242,50 @@ host ayarları, ülke/şehir/ilçe verileri ile seed edilmiş ListForm ekranlar (kodla versiyonlanan sabit seed'ler `api/src/Sozsoft.Platform.DbMigrator/Migrations/`, runtime'da düzenlenebilen seed'ler `configs/seeds/` altındadır — bkz. `App:SeedsPath`). -`configs/seeds` altında en üst seviye kapsamdır (CDN klasörleriyle aynı mantık): host -veritabanı için `host/`, her tenant için `tenants/{tenantId}/`. Kapsamın altında içerik -türüne göre `crud/`, `wizard/`, `data/`, `sql/`, `postgres/` klasörleri bulunur: +**Tek tenant seed'i:** Tenants ekranındaki bağlantı diyaloğunun Seed sekmesi +`POST /api/setup/tenants/{id}/seed` ucunu çağırır (`AbpTenantManagement.Tenants.Update`, yalnızca host +kullanıcısı). Uç DbMigrator'ü migrate ile aynı kilit ve SSE akışıyla çalıştırır; `TenantSeed:TenantId`, +`TenantSeed:AdminEmail`, `TenantSeed:AdminPassword` ortam değişkeniyle geçer (`TenantSeedOptions`) ve +`PlatformDbMigrationService` yalnızca o tenant'ın şema + seed + `execute` adımlarını yürütür. Loglar +DB Migrate'teki `DbMigrateLogPanel` ile gösterilir. + +`configs/seeds` tek klasördür; tenant'a özel bir seed kapsamı yoktur. Tek bir `PlatformDbContext` +olduğu için kendi veritabanı olan tenant ekran tanımlarını, menüleri, yetkileri ve wizard +tablolarını kendi veritabanından okur. Bu yüzden host ve kendi veritabanı olan her tenant aynı +dosyalarla kurulur. Host veritabanını paylaşan (bağlantı cümlesi olmayan) tenant'ta dosyalar +yeniden uygulanmaz, çünkü host seed'i onları aynı veritabanına zaten yazmıştır. Karar +`SeedFileScope`'tadır: migrate kararı `DataSeedContext`'e yazar, dosya okuyan her seeder +(`SqlDataSeeder`, `CrudDataSeeder`, `WizardDataSeeder`, `ListFormSeedDataApplier`, `FinalSeeder`) +aynı kararı uygular. İçerik türüne göre klasörler: ``` configs/seeds/ - host/ - crud/ wizard/ data/ - sql/ { object/ execute/ } - postgres/ { object/ execute/ } - tenants/ - {tenantId}/ - crud/ wizard/ data/ - sql/ { object/ execute/ } - postgres/ { object/ execute/ } + crud/ wizard/ data/ docs/ + sql/ { object/ execute/ host/{ object/ execute/ } } + postgres/ { object/ execute/ host/{ object/ execute/ } } ``` +Dosyalar ürünün varsayılanıdır ve **yalnızca host bağlamında yazılır**. Tenant bağlamındaki bir +kayıt veritabanında kalır, seed dosyasına yansımaz; yansısaydı bir tenant'ın verisi bütün +veritabanlarına yayılırdı. Bunu `ListFormSeedDataSynchronizer` ve `WizardSeedSynchronizer` +uygular. Wizard, SQL Query Manager, CRUD Endpoint ve Custom Components yetkileri zaten yalnızca +host tarafındadır. + +Bekleyen wizard kurulumu (`Pending`) dosyaları uygulayan **son** veritabanında kapanır. Tam +migrate'te bu, kendi veritabanı olan son tenant'tır; öyle bir tenant yoksa host'tur. Host'ta +kapansaydı, ekranın eski sürümü kurulu olan tenant ekranı "kurulu" sayıp güncellemezdi. Tek +tenant seed'i (Tenants › Seed) bayrağı kapatmaz. + Aktif veritabanı sağlayıcısına göre `sql/` (SQL Server) ya da `postgres/` okunur. Her `.sql` dosyası amacını belirten bir klasörün altındadır; klasör köklerinde dosya bulunmaz: | Klasör | Ne zaman çalışır | | --- | --- | -| `{sql\|postgres}/object` | Seed sırasında nesneyi oluşturur/günceller. SqlTableDesigner ve SqlQueryManager, deploy ettiği script'i çalıştığı kapsamın bu klasörüne yazar (host bağlamı → `host/`, tenant seçiliyken → `tenants/{tenantId}/`). | +| `{sql\|postgres}/object` | Seed sırasında nesneyi oluşturur/günceller. SqlTableDesigner ve SqlQueryManager, deploy ettiği script'i bu klasöre yazar. | | `{sql\|postgres}/execute` | Nesneyi oluşturur; ayrıca tüm migration ve seed'ler bittikten sonra `FinalSeeder` dosya adından türettiği procedure'ü çalıştırır (dosya adı = procedure adı, `schema.Procedure.sql` de olur). | -| `wizard` | ListForm Wizard'ın ürettiği `.json` konfigürasyonları; `WizardDataSeeder` kendi kapsamındaki dosyaları okur. | -| `crud` | CrudEndpoint konfigürasyonları; `CrudDataSeeder` kendi kapsamındaki dosyaları `ICrudEndpointSeedApplier` üzerinden uygular. Politika **damga karşılaştırmalıdır**: eksik endpoint her zaman eklenir, mevcut bir kayıt yalnızca dosyanın `GeneratedAt` damgası kaydın `LastModificationTime`/`CreationTime` değerinden **yeniyse** güncellenir ya da silinir; aksi halde kayıt korunur ve log'a drift uyarısı düşer. Böylece CRUD Endpoint Manager'dan yapılan bir revizyon depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmaz. Damgası olmayan (eski biçimde yazılmış) dosya hiçbir zaman yeni sayılmaz. | +| `{sql\|postgres}/host/{object\|execute}` | Aynı iki kural, **yalnızca host veritabanında**. Tenant veritabanında karşılığı olmayan nesnelere dayanan script buraya konur — örneğin HangFire tabloları tenant migrate'inde de yalnızca host'a kurulduğu için `Sas_H_BackgroundWorker_JobFlow` view'ı buradadır. Sıra: `object` → `host/object` → `execute` → `host/execute`. SqlTableDesigner ve SqlQueryManager her zaman `object/`'e yazar; yalnızca host'ta çalışması gereken script File Manager'dan buraya taşınır. Wizard export/import bu klasörü de taşır. | +| `wizard` | ListForm Wizard'ın ürettiği `.json` konfigürasyonları; `WizardDataSeeder` okur. | +| `crud` | CrudEndpoint konfigürasyonları; `CrudDataSeeder` dosyaları `ICrudEndpointSeedApplier` üzerinden uygular. Politika **damga karşılaştırmalıdır**: eksik endpoint her zaman eklenir, mevcut bir kayıt yalnızca dosyanın `GeneratedAt` damgası kaydın `LastModificationTime`/`CreationTime` değerinden **yeniyse** güncellenir ya da silinir; aksi halde kayıt korunur ve log'a drift uyarısı düşer. Böylece CRUD Endpoint Manager'dan yapılan bir revizyon depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmaz. Damgası olmayan (eski biçimde yazılmış) dosya hiçbir zaman yeni sayılmaz. | | `data` | Ekranlardan girilen listelerin veri aynası: tek dosya (`{ListFormCode}.json`) ya da satır başına dosya tutan klasör (`{ListFormCode}/{anahtar}.json`; klasör özyinelemeli taranır ve tamamı tek unit of work'te uygulanır). `ListForm.SeedFilePath` dolu olan ekranda `SeedSyncInsert/Update/Delete` bayraklarından işaretli olan işlemler dosyaya yansır (`ListFormSeedDataSynchronizer`); migrate/seed sırasında bütün contributor'lar bittikten sonra `ListFormSeedDataApplier` dosyayı geri uygular: anahtarı veritabanında **olmayan** satırları ekler, var olanlara dokunmaz. Aynı uygulayıcı wizard import'unun kapanışında da (`CompleteImport`) yalnızca o import'ta yazılan dosyalar için, `overwriteExisting` açık olarak çalışır — orada var olan satır dosyadaki değerlerle güncellenir. | Klasör kökünde kalmış eski `.sql` dosyaları geriye dönük uyumluluk için hâlâ işlenir, @@ -294,6 +310,16 @@ Seed edilen host yöneticisi ile `/login` üzerinden giriş yapılır (kullanıc `PlatformIdentityDataSeeder` ile belirlenir). Giriş sonrası `/admin/dashboard` açılır. +Yöneticinin ilk yetkileri, yöneticinin hiç yetkisi yokken bir kez atanır. Host yöneticisi kimlik +yetkileri (rol/kullanıcı) ve `Setup.Migrate` ile başlar. Kiracı yöneticisi ise host'taki tanım +tablosunda (`AbpPermissions`) kiracı tarafına açık bütün yetkileri alır. Sonradan deploy edilen +ekranların yetkileri migrate ile atanmaz. + +Kendi veritabanı olan kiracıda yalnızca `IMultiTenant` varlıklar (kullanıcı, rol, yetki ataması) +o veritabanındadır. ListForm, menü ve yetki tanımı gibi kiracısız varlıkları ABP her zaman host'ta +okur. Kiracı tablolarının satırları, kiracı adıyla kayıtlı veri kaynağından okunup yazılır +(`DataSourceManager`). + --- ## 6. Yapılandırma (Configuration) @@ -304,7 +330,8 @@ Seed edilen host yöneticisi ile `/login` üzerinden giriş yapılır (kullanıc | --- | --- | | `App:SelfUrl` | API'nin kendi adresi (OpenIddict authority ile uyumlu olmalı). | | `App:ClientUrl` | React uygulamasının kök adresi (mail linkleri bu adresi kullanır). | -| `App:CorsOrigins` | Virgülle ayrılmış izinli origin listesi; wildcard subdomain destekli. | +| `App:CorsOrigins` | Virgülle ayrılmış izinli origin listesi; wildcard subdomain destekli (`https://*.alan.com`). | +| `App:BaseDomain` | Production'da ana alan adı ve tüm alt alan adları (yalnızca `https`), geliştirmede `dev.{alan}` CORS'a açılır. Tek politika `PlatformCorsOriginValidator`'dadır: origin bu kurala ya da `App:CorsOrigins`'e uyuyorsa izinlidir; geliştirmede `localhost`/`127.0.0.1`/`[::1]` her zaman açıktır. | | `App:RedirectAllowedUrls` | OAuth yönlendirme için izinli adresler. | | `App:CdnUrl` / `App:CdnPath` | Dosya servisinin adresi ve BLOB kök dizini. | | `App:AttachmentsPath` | Mail kuyruğu eklerinin yazıldığı dizin. | @@ -372,7 +399,7 @@ Ekran davranışı büyük ölçüde JSON kolonlarında saklanır. Öne çıkanl | `ColumnOptionJson`, `PagerOptionJson`, `SelectionJson`, `StateStoringJson` | Sütun, sayfalama, seçim ve durum saklama ayarları. | | `Insert/Update/DeleteCommand` (+ `Before` / `After`) | CRUD komutları ve öncesi/sonrası çalışacak SQL kancaları. | | `Insert/Update/DeleteServiceAddress` | Varsayılan olarak `list-form-data/*`; özel endpoint'e yönlendirilebilir. | -| `SeedFilePath` | Doluysa seed sırasında listenin verisi kapsam klasöründeki bu hedeften geri yüklenir: `.json` ile biten değer tek dosya (`data/{ListFormCode}.json`), uzantısız değer satır başına dosya tutan klasördür (`data/{ListFormCode}/{anahtar}.json`). | +| `SeedFilePath` | Doluysa seed sırasında listenin verisi seed klasöründeki bu hedeften geri yüklenir: `.json` ile biten değer tek dosya (`data/{ListFormCode}.json`), uzantısız değer satır başına dosya tutan klasördür (`data/{ListFormCode}/{anahtar}.json`). | | `SeedSyncInsert` / `SeedSyncUpdate` / `SeedSyncDelete` | Ekranda yapılan hangi işlemin seed dosyasına yansıyacağı; kapalıysa o işlem dosyaya dokunmaz. | | `SubFormsJson` | Ana-detay ilişkisi (ParentFieldName → ChildFieldName eşlemesi). | | `WidgetsJson` | Ekranın üstünde gösterilecek KPI kartları. Alt form olarak üretilen ekrana (menüsüz ya da menülü) yazılmaz; menüsüz wizard'da deploy boşaltır, alt form sekmesinde çizilmez. | @@ -489,7 +516,7 @@ butonlar gösterilebilir. 5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note`) 6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR) 7. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları -8. **Seed dosyası** — `configs/seeds/{host|tenants/{tenantId}}/wizard/{Ad}.json` (wizard cevapları + üretilen `ListForm`/`Fields` sözleşmesi) +8. **Seed dosyası** — `configs/seeds/wizard/{Ad}.json` (wizard cevapları + üretilen `ListForm`/`Fields` sözleşmesi) 9. **Doğrulama ve geri alma** notları **Adım bileşenleri.** Adımlar numaralı dosya adları yerine işlevleriyle adlandırılır ve @@ -586,8 +613,8 @@ kontrol `ListFormWizardAppService` üzerindedir. **Ekran tanımı (wizard seed paketi)** Bir ekranın tanımı, bağımlılıklarıyla birlikte tek bir zip olarak taşınır. Zip içindeki yollar seed -kapsam klasörü (`host` / `tenants/{tenantId}`) kökü baz alınarak yazılır; arşiv başka bir ortamda -aynı klasör düzenine doğrudan açılabilir. +klasörünün kökü baz alınarak yazılır; arşiv başka bir ortamda aynı klasör düzenine doğrudan +açılabilir. Eski arşivlerdeki `host/` öneki import'ta yok sayılır. Toplama **özyinelemelidir** (`WizardExportCollector`): alt formların wizard dosyaları kuyruğa eklenir ve onların bağımlılıkları da aynı kurallarla toplanır. Böylece zip tek başına çalışır. @@ -601,7 +628,7 @@ paylaşılan dosyalar arşive bir kez, birleşik satırlarla girer ve import ar | `data/App.DeveloperKit.CustomComponents/{Id}.json` | Custom yolunda bağlı component ve bağımlılıkları — yalnızca bu bileşenlerin satır dosyaları | | `data/{ListFormCode}.json` ya da `data/{ListFormCode}/{anahtar}.json` | Ekranın `SeedFilePath` değeri doluysa kendi veri aynası (dosya ya da klasör) | | `crud/{entity}.json` | Component'lerin/nesnenin kullandığı CRUD endpoint tanımları | -| `{sql\|postgres}/{object\|execute}/{nesne}.sql` | `SelectCommand`'a karşılık gelen SQL nesnesi, lookup sorgularının (`LookupDto.LookupQuery`) `FROM`/`JOIN` ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar | +| `{sql\|postgres}/[host/]{object\|execute}/{nesne}.sql` | `SelectCommand`'a karşılık gelen SQL nesnesi, lookup sorgularının (`LookupDto.LookupQuery`) `FROM`/`JOIN` ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar | Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan **tahmin edilmez**: kapsam kökündeki script klasörleri (`sql/object`, `postgres/execute` …) diskten taranır ve nesne adıyla eşleşen her @@ -757,7 +784,7 @@ Teknik kullanıcılar için `/admin/developerkit` altında toplanan araçlar: | **Custom Endpoint** | `/admin/list/App.DeveloperKit.CustomEndpoints` | SQL veya servis tanımından REST endpoint üretimi; GET/POST/PUT/DELETE için ayrı yetki kapıları (`App.DeveloperKit.CustomEndpoints.*`) ve kullanıcı/rol/global erişim kuralları. | | **CRUD Endpoint** | SQL Query Manager içinde | Tablo tanımından tam CRUD endpoint kümesi üretimi. Ayrı ekranı yoktur; bkz. aşağıdaki not. | | **Dynamic Service** | `/admin/list/App.DeveloperKit.DynamicServices` | Liste bir ListForm ekranıdır (KPI şeridi, Swagger butonu, kart/ızgara görünümü); kod yazma listeden açılan `DynamicServiceEditor` diyalogundadır: Monaco editörü, Roslyn ile derleme (`TestCompile`), yayınlama (`Publish`) ve çalışan uygulamaya controller olarak kaydetme. Yetkiler: Create/Edit/Delete/Manage/TestCompile/Publish/ViewCode. | -| **Custom Component** | `/admin/list/App.DeveloperKit.CustomComponents` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki **Design** butonu `/admin/developerkit/components/design/{id}` tasarımcısını, **Open URL** butonu bileşenin `RoutePath`'ini açar. Kayıtlar `configs/seeds/host/data/App.DeveloperKit.CustomComponents/{Id}.json` klasörüne senkronlanır. Yetkiler `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`. | +| **Custom Component** | `/admin/list/App.DeveloperKit.CustomComponents` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki **Design** butonu `/admin/developerkit/components/design/{id}` tasarımcısını, **Open URL** butonu bileşenin `RoutePath`'ini açar. Kayıtlar `configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json` klasörüne senkronlanır. Yetkiler `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`. | | **Visual Designer** | `/admin/developerkit/components/design/:id` | Custom Component listesindeki **Design** butonundan açılır; sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). | | **ListForm** | `/admin/list/App.Listforms.Listform` | Var olan ekranların ham tanım listesi; buradan `/admin/listform/edit/{kod}` sekmeli editörüne geçilir. | | **Wizard Manager** | `/admin/listform/wizardManager` | Üretilmiş wizard seed dosyalarının listesi/kartı, düzenleme, silme, DB migrate, export/import. Menüsüz alt formlar onları sekme olarak açan ekranın altında iç içe görünür; kendi menüsü olan ekran başka bir ekranın sekmesi olsa da kökte kalır (export kapanışı onu yine taşır). Wizard'ı üreten talep listede görünür ve kaydına bağlanır; talebin bütün wizard'ları tek arşivde export edilir (`export-dev-request`) ya da bağımlılık sırasıyla tek işlemde silinir (`delete-dev-request`; alt formlar önce, talebin ekranları birbirini kullanım saymaz, tablolar en sonda — `lowcode.instructions.md` §4.5). Yetki `App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}`. | @@ -799,7 +826,7 @@ düşer ve kullanıcı Query Editor'dan devam eder (`onOpenInEditor`). PostgreSQ > açılabilir. Hepsi `CrudEndpointDialog` bileşenini kullanır. Yetki `App.SqlQueryManager` altındaki > `App.SqlQueryManager.CrudEndpoints`'tir; izin verilmemişse butonlar görünmez ve > `crud-endpoint-generate` uçlarının tamamı (base CRUD metotları dahil) reddedilir. -> Üretilen endpoint'ler `configs/seeds/{host|tenants/{tenantId}}/crud/{EntityName}.json` olarak +> Üretilen endpoint'ler `configs/seeds/crud/{EntityName}.json` olarak > seed dosyasına yazılır; `CrudDataSeeder` aynı dosyaları okuyup endpoint'leri geri yükler. > **Tablo tasarımı varsayılanı:** Yeni tablo oluştururken tenant ve tam denetim (audit) kolonları @@ -817,7 +844,7 @@ tasarımcı dokümanı geri okunabilir. `sourceMode` alanı `visual` ya da `code geçilen bir bileşen kanvasa geri dönmez. **Seed senkronizasyonu.** Bileşenlerin seed kaynağı Custom Components ekranının veri klasörüdür: -`configs/seeds/{host|tenants/{tenantId}}/data/App.DeveloperKit.CustomComponents/{Id}.json` — her +`configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json` — her bileşen kendi dosyasındadır; bileşen kodu satırın içinde durduğu için tek dosya hızla büyürdü. Bileşene yazan **iki yol** da bu klasöre işlenir (`ListFormSeedDataSynchronizer`): listeden yapılan ekleme/güncelleme/silme ve Visual Designer'ın kaydı (`CustomComponentAppService`). Veritabanı @@ -882,7 +909,7 @@ tıklamak seçim yapar, sayfayı değiştirmez — tıklama yakalama evresinde d **Diğer notlar.** `PlatformIcon` tasarımcı dokümanındaki ikon adını çözer; `selectComponents.ts` `Select.componentAs` için saklanan adı (`ReactSelect`, `CreatableSelect`, `AsyncSelect`) gerçek bileşene çevirir — kanvas ve üretilen bileşenin aynı adı aynı şekilde yorumlamasını sağlayan tek -nokta budur. Yeni bileşen `configs/seeds/host/data/App.DeveloperKit.CustomComponents/` klasöründe +nokta budur. Yeni bileşen `configs/seeds/data/App.DeveloperKit.CustomComponents/` klasöründe kendi `{Id}.json` satır dosyası olarak başlar. **Dinamik servis yaşam döngüsü:** `DynamicServiceCompiler` kodu derler → @@ -946,15 +973,15 @@ sebebi ve değerlendirme kriteri tanımları İK › Tanımlar (`App.Hr.Definiti kullanıcı (personel) kaydını BT elle açar; İK aday kaydını `UserId` ile o kullanıcıya bağlar. Ayrıntı: `lowcode-reference.instructions.md` §12.4. **Performans Yönetimi** (`App.Hr.Performance`) wizard + custom component ile üretilmiştir: İK menüsü şartları ve takibi taşır, çalışana ait listeler menüsüzdür ve -Intranet panosundaki widget'lardan açılır; çalışma şekli [HrPerformance.md](configs/seeds/host/docs/modules/HrPerformance.md). +Intranet panosundaki widget'lardan açılır; çalışma şekli [HrPerformance.md](configs/seeds/docs/modules/HrPerformance.md). **İzin Yönetimi** (`App.Hr.Leave`) aynı kalıptadır (talep, Müdür → İK Grid Workflow onayı, İş Kanunu md.53 bakiyesi, -rapor ↔ Sağlık Kartı eşlemesi, kullanıcı detayında İzinler sekmesi); çalışma şekli [HrLeave.md](configs/seeds/host/docs/modules/HrLeave.md). +rapor ↔ Sağlık Kartı eşlemesi, kullanıcı detayında İzinler sekmesi); çalışma şekli [HrLeave.md](configs/seeds/docs/modules/HrLeave.md). **Puantaj ve Devam Takibi** (`App.Hr.Attendance`) hareketlerden (PDKS ucu, Excel, elle) günlük ve aylık puantaj kurar; izin, rapor, çalışma takvimi ve vardiyadan beslenir, kullanıcı detayında Puantaj sekmesi ve Intranet'te Puantajım widget'ı vardır; -çalışma şekli [HrAttendance.md](configs/seeds/host/docs/modules/HrAttendance.md). +çalışma şekli [HrAttendance.md](configs/seeds/docs/modules/HrAttendance.md). **Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve -`configs/seeds/host/data/Abp.Identity.Permissions.json` ile seed edilir (gruplar: +`configs/seeds/data/Abp.Identity.Permissions.json` ile seed edilir (gruplar: `Abp.Identity.PermissionGroups.json` — `App.Saas` (1), `App.Administration` (2), `App.Hr` (3)). Ana yetki kökleri: `App.Saas`, `App.Branches`, `App.Definitions.*`, `App.Hr.*` (Intranet dahil, `App.Hr.Intranet.*`), `App.Restrictions.*`, `App.Languages.*`, `App.Listforms.*`, @@ -1012,7 +1039,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer | Modül | Kapsam | | --- | --- | -| **Sozsoft.Languages** | Dil tanımları, dil anahtarları, çeviri metinleri; tenant bazlı lokalizasyon (`TenantLocalizationMiddleware`). | +| **Sozsoft.Languages** | Dil tanımları, dil anahtarları, çeviri metinleri; veritabanı tabanlı lokalizasyon (`DatabaseResourceLocalizer`). Çeviri okuma (public sayfalar dahil) ABP'nin `application-localization` ucundandır ve yetki istemez; dil/anahtar/metin yönetim uçları ile önbellek temizleme `App.Languages.Language*` / `App.Languages.LanguageText*` yetkilerine bağlıdır (`LanguagesPermissions`). Önbellek temizleme yalnızca uygulamanın anahtarlarını siler (`DistributedCacheCleaner`). | | **Sozsoft.Settings** | Ayar tanımları, sağlayıcılar, şifreleme bayrakları ve ayar ekranı metadata'sı. | | **Sozsoft.Notifications** | Bildirim tipleri, kuralları ve kullanıcı bildirimleri; kanal bazlı yönlendirme. | | **Sozsoft.MailQueue** | Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme. | @@ -1041,7 +1068,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer | Aktivite ve denetim kaydı | `/admin/activityLog`, `/admin/changeLog` | Kullanıcı hareketleri ve ABP audit log detayı. | | Bildirimler | `/admin/profile/notification-settings`, bildirim ekranları | Kanal tercihleri ve bildirim oluşturma. | | Forum | `/admin/forum` | Forum kullanımı; yönetim `App.ForumManagement` altındaki ListForm ekranlarıyla yapılır. | -| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik; ayrıca İK modüllerinin çalışana ait özetleri (Performans Yönetimi: Performansım, Ekibimin Performansı, 360 Taleplerim — custom component). Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Sas_T_IntranetWidget` kaydından gelir; kayıtlar `configs/seeds/{kapsam}/data/App.DeveloperKit.IntranetWidgets.json` ile senkron tutulur. | +| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik; ayrıca İK modüllerinin çalışana ait özetleri (Performans Yönetimi: Performansım, Ekibimin Performansı, 360 Taleplerim — custom component). Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Sas_T_IntranetWidget` kaydından gelir; kayıtlar `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json` ile senkron tutulur. | | Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. | | Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. `Raporlar` kategorisindeki şablon: `/admin/reports/DynamicReport/view/{ŞablonId}[/{ListFormCode}?recordId=...]` (§15). | | AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. | @@ -1064,7 +1091,7 @@ react-icons adı, `Order` sıra, `IsDisabled` yayın anahtarı, `DisplayName` di `[Authorize]` olduğu için menüler uygulama konfigürasyonunun `publicMenus` alanıyla taşınır (`PlatformApplicationConfigurationContributor`), istemcide `usePublicMenu` okur. `Pub` modülü yönetim navigasyonuna girmez (`abpConfig.model.ts` menüyü kurarken bu modülü eler; Menü Yönetimi -ekranı elemez, menüler oradan düzenlenir). Seed: `configs/seeds/host/data/App.Menus.Module.json` ve +ekranı elemez, menüler oradan düzenlenir). Seed: `configs/seeds/data/App.Menus.Module.json` ve `App.Menus.Menu.json`. **Sayfa.** Her public sayfa bir Custom Component'tir: `RoutePath` `/admin/` ile başlamadığı sürece @@ -1072,7 +1099,7 @@ bileşen otomatik olarak public route olur ve `PublicLayout` altında yayınlan DeveloperKit > Custom Components (Visual Designer) üzerinden değişir. Aynı yolu bir fiziksel route ile paylaşan custom component kazanır; karşılığı olan dosyası bulunmayan rota kaydı ise hiç yüklenmez (`hasPhysicalComponent`) — seeder yalnızca eklediği için eski kayıtlar veritabanında -kalsa bile ekran patlamaz. Seed: `configs/seeds/host/data/App.DeveloperKit.CustomComponents/{Id}.json`; +kalsa bile ekran patlamaz. Seed: `configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json`; ekran klasör modundadır, her bileşen kendi satır dosyasındadır (dosya adı satırın `Id`'si). Runtime kapsamı sınırlıdır: bileşen kodundaki `import` satırları derlemede temizlenir, @@ -1203,7 +1230,7 @@ Get/Save uçları ve DTO'ları, `TenantData.json` + `TenantDataSeeder`, `App.{Home,About,Services,Contact}` yetkileri ve `.Design` alt yetkileri, `/admin/public/*/designer` rota ve menüleri. -### 10.5. Modül dokümanları (`configs/seeds/host/docs/modules/`) +### 10.5. Modül dokümanları (`configs/seeds/docs/modules/`) Konfigürasyonla üretilen her modülün çalışma şekli, keşif analizi ve kararları kendi dosyasındadır; "bu ekran nasıl çalışıyor?" sorusunun cevabı oradadır (MCP'de `get_module_doc`). Dokümanlar seed @@ -1216,21 +1243,21 @@ başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını g | Modül | Menü | Doküman | | --- | --- | --- | -| Performans Yönetimi | İK › Performans Yönetimi (`App.Hr.Performance`) | [HrPerformance.md](configs/seeds/host/docs/modules/HrPerformance.md) | -| İşe Alım | İK › İşe Alım (`App.Hr.Recruitment`) | [HrRecruitment.md](configs/seeds/host/docs/modules/HrRecruitment.md) | -| İzin Yönetimi | İK › İzin Yönetimi (`App.Hr.Leave`) | [HrLeave.md](configs/seeds/host/docs/modules/HrLeave.md) | -| Puantaj ve Devam Takibi | İK › Puantaj ve Devam Takibi (`App.Hr.Attendance`) | [HrAttendance.md](configs/seeds/host/docs/modules/HrAttendance.md) | -| Eğitimler | İK › Eğitimler (`App.Hr.Trainings`) | [HrTrainings.md](configs/seeds/host/docs/modules/HrTrainings.md) | -| Araçlar | İK › Araçlar (`App.Hr.Vehicles`) | [HrVehicles.md](configs/seeds/host/docs/modules/HrVehicles.md) | -| Disiplin Dosyaları | İK › Disiplin Dosyaları (`App.Hr.DisciplinaryCase`) | [HrDisciplinaryCase.md](configs/seeds/host/docs/modules/HrDisciplinaryCase.md) | -| Tanımlamalar (dizin + genel tanımlar) | İK › Tanımlamalar (`App.Hr.Definitions`) | [HrDefinitions.md](configs/seeds/host/docs/modules/HrDefinitions.md) | -| Organizasyon ve Yetkinlik | İK › Departmanlar, Pozisyonlar, Yetenek Tipleri, Organizasyon Şeması | [HrOrganization.md](configs/seeds/host/docs/modules/HrOrganization.md) | -| Çalışma Takvimi ve Vardiyalar | İK › Çalışma Takvimi, Vardiyalar | [HrWorkCalendar.md](configs/seeds/host/docs/modules/HrWorkCalendar.md) | -| Sağlık Kartları | İK › Sağlık Kartları (`App.Hr.HealthCard`) | [HrHealthCard.md](configs/seeds/host/docs/modules/HrHealthCard.md) | -| Ziyaretçiler | İK › Ziyaretçiler (`App.Hr.Visitor`) | [HrVisitor.md](configs/seeds/host/docs/modules/HrVisitor.md) | -| Olay Günlüğü | İK › Olay Günlüğü (`App.Hr.EventLog`) | [HrEventLog.md](configs/seeds/host/docs/modules/HrEventLog.md) | -| Personel Kaydı (özlük, sözleşme, eğitim, aile, mali bilgiler) | Kimlik › Kullanıcılar › detay | [HrPersonnel.md](configs/seeds/host/docs/modules/HrPersonnel.md) | -| Intranet (pano, duyuru, anket, sosyal duvar, etkinlik, doküman, mesajlaşma) | İK › Intranet (`App.Hr.Intranet`) | [HrIntranet.md](configs/seeds/host/docs/modules/HrIntranet.md) | +| Performans Yönetimi | İK › Performans Yönetimi (`App.Hr.Performance`) | [HrPerformance.md](configs/seeds/docs/modules/HrPerformance.md) | +| İşe Alım | İK › İşe Alım (`App.Hr.Recruitment`) | [HrRecruitment.md](configs/seeds/docs/modules/HrRecruitment.md) | +| İzin Yönetimi | İK › İzin Yönetimi (`App.Hr.Leave`) | [HrLeave.md](configs/seeds/docs/modules/HrLeave.md) | +| Puantaj ve Devam Takibi | İK › Puantaj ve Devam Takibi (`App.Hr.Attendance`) | [HrAttendance.md](configs/seeds/docs/modules/HrAttendance.md) | +| Eğitimler | İK › Eğitimler (`App.Hr.Trainings`) | [HrTrainings.md](configs/seeds/docs/modules/HrTrainings.md) | +| Araçlar | İK › Araçlar (`App.Hr.Vehicles`) | [HrVehicles.md](configs/seeds/docs/modules/HrVehicles.md) | +| Disiplin Dosyaları | İK › Disiplin Dosyaları (`App.Hr.DisciplinaryCase`) | [HrDisciplinaryCase.md](configs/seeds/docs/modules/HrDisciplinaryCase.md) | +| Tanımlamalar (dizin + genel tanımlar) | İK › Tanımlamalar (`App.Hr.Definitions`) | [HrDefinitions.md](configs/seeds/docs/modules/HrDefinitions.md) | +| Organizasyon ve Yetkinlik | İK › Departmanlar, Pozisyonlar, Yetenek Tipleri, Organizasyon Şeması | [HrOrganization.md](configs/seeds/docs/modules/HrOrganization.md) | +| Çalışma Takvimi ve Vardiyalar | İK › Çalışma Takvimi, Vardiyalar | [HrWorkCalendar.md](configs/seeds/docs/modules/HrWorkCalendar.md) | +| Sağlık Kartları | İK › Sağlık Kartları (`App.Hr.HealthCard`) | [HrHealthCard.md](configs/seeds/docs/modules/HrHealthCard.md) | +| Ziyaretçiler | İK › Ziyaretçiler (`App.Hr.Visitor`) | [HrVisitor.md](configs/seeds/docs/modules/HrVisitor.md) | +| Olay Günlüğü | İK › Olay Günlüğü (`App.Hr.EventLog`) | [HrEventLog.md](configs/seeds/docs/modules/HrEventLog.md) | +| Personel Kaydı (özlük, sözleşme, eğitim, aile, mali bilgiler) | Kimlik › Kullanıcılar › detay | [HrPersonnel.md](configs/seeds/docs/modules/HrPersonnel.md) | +| Intranet (pano, duyuru, anket, sosyal duvar, etkinlik, doküman, mesajlaşma) | İK › Intranet (`App.Hr.Intranet`) | [HrIntranet.md](configs/seeds/docs/modules/HrIntranet.md) | --- @@ -1287,7 +1314,10 @@ başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını g - **Şube (Branch):** Tenant altındaki fiziksel/örgütsel kırılım; kullanıcı–şube eşlemesi `BranchUsers` ile tutulur, lisans limiti girişte kontrol edilir. - **Organizasyon birimi:** ABP `OrganizationUnit` ağacı; ListForm'lar OU bazlı filtrelenebilir. -- Tenant bazlı lokalizasyon: `TenantLocalizationInitializer` + `TenantLocalizationMiddleware`. +- Lokalizasyon: isteğin kültürü (metin, tarih, sayı) `UseAbpRequestLocalization` ile UI'ın gönderdiği + `Accept-Language`'tan kurulur. `TenantLocalizationMiddleware` + `TenantLocalizationInitializer` istek dışı + iş parçacıklarının (Hangfire, worker) varsayılan kültürünü ve `IClock` tipini global `Abp.Localization.DefaultLanguage` + / `Abp.Timing.TimeZone` ayarlarından tutar; değerler dakikada en fazla bir kez okunur ve yalnızca değişince yazılır. --- @@ -1386,11 +1416,12 @@ Agenda'nın üstündedir; Saas altında bildirim ekranı **yoktur**. Tablolar kiracı kapsamındadır (`Adm_T_NotificationTemplate`, `Adm_T_NotificationType`, `Adm_T_NotificationRule`, `Adm_T_Notification`; dördü de `IMultiTenant`). Her kiracı kendi türünü, kuralını ve kuyruğunu görür; host kaydı (`TenantId = null`) yalnızca host oturumunda görünür. Yeni bir kiracı -oluşturulduğunda varsayılan tür ve kural kümesi o kiracıya kopyalanır -(`NotificationTenantDefaultsSeedContributor`): kaynak host kapsamındaki +oluşturulduğunda varsayılan tür ve kural kümesi o kiracıya kopyalanır. Kendi veritabanı olan +kiracı kümeyi seed dosyasından alır (`ListFormSeedDataApplier`). Host veritabanını paylaşan +kiracıya ise `NotificationTenantDefaultsSeedContributor` kopyalar: kaynak `data/App.Notifications.Notification{Types,Rules}.json` dosyalarıdır, satırlar **yeni kimliklerle** yazılır ve eşleşme ad üzerinden yapıldığı için ikinci çalıştırma bir şey eklemez; -şablonlar kopyalanmaz. +şablonlar kopyalanmaz. İki yol aynı kiracıda çalışmaz, yoksa türler iki kez yazılırdı. **Bildirimler** ekranı (`App.Notifications.Notification`) hem kuyruk günlüğü hem gönderim ekranıdır: liste kural motorunun ürettiklerini de gösterir, ekleme formu ise elle gönderimdir. @@ -1513,7 +1544,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır. | `run_query` | Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler `list_queries` ile aynı. | | `insert_row` / `update_row` / `delete_row` | ListForm ekranının insert/update/delete sözleşmesinden geçer. | | `get_dev_guide` | Üretim sözleşmesi: `lowcode` / `reference` belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. `4.2`, ya da başlık metniyle). Bölüm önce istenen, sonra diğer belgede aranır; başlıkla eşleşmeyen terimde (örn. `dxImageUpload`) terimin geçtiği bölümler (en çok 15, `DevGuideSearchMatches`), hiç geçmiyorsa başlık listesi döner — hata atılmaz. Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner. | -| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `{kapsam}/sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Write` + `App.SqlQueryManager` ister. | +| `create_table` | Yeni tabloyu SQL Query Manager tablo tasarımcısıyla aynı script biçimiyle kurar (`McpTableCreator`): `Id` + `TenantId` + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. Script `sql/object/{Tablo}.sql` dosyasına yazılır. Yalnızca oluşturur — tablo varsa reddeder; yalnızca SQL Server. `.Write` + `App.SqlQueryManager` ister. | | `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. | | `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromSeedJson`'dan geçer. Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur ve `SeedSync*` bayrakları açılır — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. | | `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). | @@ -1524,7 +1555,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır. | `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. | | `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. | | `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. | -| `get_module_doc` / `save_module_doc` | Modül dokümanı (`configs/seeds/{kapsam}/docs/modules/{Modül}.md`, `IModuleDocAppService`). Ad yerine ekran kodu ya da wizard dosya adı verilebilir. `save_module_doc` elle yazılan bölümleri kaydeder, "Ek — Ekran envanteri"ni sunucu üretir. Talebi `update_row` ile `Testing`'e almak, talebin wizard'larının dokümanı tamamlanmadan (dosya var, `module-doc:todo` kalmamış) reddedilir. Okuma `App.Listforms.Wizard`, yazma ayrıca `Wizards.Update` ister. | +| `get_module_doc` / `save_module_doc` | Modül dokümanı (`configs/seeds/docs/modules/{Modül}.md`, `IModuleDocAppService`). Ad yerine ekran kodu ya da wizard dosya adı verilebilir. `save_module_doc` elle yazılan bölümleri kaydeder, "Ek — Ekran envanteri"ni sunucu üretir. Talebi `update_row` ile `Testing`'e almak, talebin wizard'larının dokümanı tamamlanmadan (dosya var, `module-doc:todo` kalmamış) reddedilir. Okuma `App.Listforms.Wizard`, yazma ayrıca `Wizards.Update` ister. | | `list_report_templates` / `get_report_template` / `save_report_category` / `save_report_template` / `delete_report_template` | Rapor kategorileri ve `DynamicReport` şablonları (`McpReportAppService`). Okuma depodan, ekranın okuma yetkisiyle yapılır. Yazma `App.Reports.Categories` / `App.Reports.ReportTemplates` ekranlarının C/U/D sözleşmesinden (`IListFormDataAppService`) geçer; seed senkronu aynen çalışır. Kayıt ada göre ekler/günceller. `listFormCode` verilirse HTML'deki `{{AlanAdi}}` yer tutucuları o ekranın alanlarına göre doğrulanır ve cevapta satır butonu (`rowButton`) döner (§15). | **Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`): @@ -1858,17 +1889,20 @@ konfigürasyonu ile üretilir. ### 18.4. PWA ve sürüm yönetimi -- Kurulum iki kademelidir: **kabuk** (index.html + entry JS/CSS + eager vendor chunk'ları) - workbox precache manifest'inden gelir ve zorunludur; **bileşenler** (tüm lazy chunk'lar, - build'de üretilen `dist/warmup.json`) güncelleme penceresinde aynı ilerleme çubuğunda arka - planda indirilir. İkinci kademe best-effort'tur (inemeyen dosya kurulumu düşürmez, süre - bütçesi aşılırsa kalanlar runtime cache'e kalır); böylece deploy sonrası ilk gezinmede chunk - beklenmez ama yavaş bağlantıda kurulum timeout'a düşmez. DevExtreme temaları (~33 MB), font - ve görseller runtime cache'tedir. +- Service worker kurulumu yalnızca **kabuğu** indirir (index.html + entry JS/CSS + eager vendor + chunk'ları, workbox precache manifest'i); kabuk zorunludur. **Bileşen ısıtması** kurulumdan + ayrıdır ve yalnızca oturum açık kullanıcı içindir: store, application-configuration her + geldiğinde `protected` fiziksel rotalardan kullanıcının yetkili olduklarını + (`PermissionGuard` ile aynı ölçüt) `setWarmupRoutes` ile bildirir, uygulama `ENSURE_WARMUP` + mesajıyla bu rotaları worker'a yollar. Worker build'de üretilen `dist/warmup.json`'daki + `routes` eşlemesinden (view modülü → chunk + statik import zinciri + CSS) yalnızca o dosyaları + indirir. Ziyaretçi, public sayfa ve giriş ekranı hiçbir bileşen indirmez; ekranın içinden lazy + açılan parçalar (diyalog, editör) ve runtime bileşenleri ilk kullanımda runtime cache'e + alınır. Isıtma best-effort'tur (inemeyen dosya atlanır, süre bütçesi aşılırsa kalanlar + runtime cache'e kalır) ve her açılışta eksikleri kaldığı yerden tamamlar. DevExtreme temaları + (~33 MB), font ve görseller runtime cache'tedir. - `registerType: 'prompt'` — yeni sürüm indirilir, indirme bitince otomatik devreye alınır (`views/version/swRegistration.ts`, `sw.ts`, `AppUpdateDialog.tsx`). -- Isıtma yalnızca kuruluma bağlı değildir: uygulama açılışta `ENSURE_WARMUP` mesajı yollar, - service worker eksik kalan bileşenleri kaldığı yerden tamamlar. - **Sürüm bilgisi backend'dedir.** `Sas_H_ChangeLog` tablosu tek kaynaktır; istemci sürüm numarasını ve son sürümün notlarını `application-configuration` yanıtındaki `extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`), @@ -2068,7 +2102,7 @@ Yeni ABP modülü eklemek için: İki ayrı seed yüzeyi vardır ve karıştırılmamalıdır. **1. Depo içi (kod ile taşınan) seed'ler** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`. -Platformun kendi ekranları buradadır (dil metinleri `configs/seeds/host/data/App.Languages.*.json`); değişiklikleri +Platformun kendi ekranları buradadır (dil metinleri `configs/seeds/data/App.Languages.*.json`); değişiklikleri derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyalar: - `ListFormSeeder_Saas.cs`, `ListFormSeeder_Administration.cs`, `ListFormSeeder_Hr.cs` — menü @@ -2081,29 +2115,31 @@ derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek içi **2. Runtime'da üretilen seed'ler** — `configs/seeds/`. Wizard, Custom Components ekranı/Visual Designer, CRUD Endpoint Manager ve `SeedFilePath` tanımlı ListForm ekranları çalışma zamanında burayı yazar; veritabanı silinip yeniden oluşturulduğunda aynı dosyalar -geri yüklenir. Kapsam klasörü CDN düzeniyle aynıdır ve `SeedPathResolver` üzerinden çözülür: +geri yüklenir. Klasör tektir (host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur) +ve `SeedPathResolver` üzerinden çözülür: ``` configs/seeds/ -├── host/ # host kapsamı -│ ├── wizard/ # {Ad}.json → WizardDataSeeder -│ ├── crud/ # {EntityName}.json → CrudEndpointSeedApplier -│ ├── data/ # {ListFormCode}.json ya da {ListFormCode}/{anahtar}.json → ListFormSeedDataApplier -│ │ # App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı -│ │ # Abp.Identity.Permission{Groups,s}.json — yetki tanımları -│ │ # App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri -│ │ # App.Definitions.{Currency,ContactTitle,AiBot,TerminationReason}.json — para birimi, hitap, AI bot, SGK çıkış kodu -│ │ # App.Hr.{SkillType,SkillLevel,Skill}.json — İK yetkinlik tanımları -│ │ # App.Hr.{Department,JobPosition}.json — İK departman → pozisyon (BranchId ile şubeye bağlı) -│ │ # App.Hr.Intranet.*.json — İK › Intranet (duyuru, anket, sosyal akış, etkinlik) -│ │ # App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json — İK › Tanımlar altındaki işe alım tanımları -│ │ # App.Hr.PerformanceRating.json — İK › Tanımlar › Performans Dereceleri (5 kademeli ölçek) -│ │ # App.Notifications.Notification{Templates,Types,Rules}.json — bildirim şablonu, tipi → kuralı -│ │ # App.Languages.{Language,LanguageText}.json — dil → metin (eksik LanguageKey metinle üretilir) -│ │ # App.SettingDefinitions.json, App.BackgroundWorkers.RecurringJobs.json — ayar tanımları, zamanlanmış işler -│ ├── sql/{object,execute}/ # .sql (SQL Server) -│ └── postgres/{object,execute}/ -├── tenants/{tenantId}/… # aynı düzen, tenant kapsamı +├── wizard/ # {Ad}.json → WizardDataSeeder +├── crud/ # {EntityName}.json → CrudEndpointSeedApplier +├── docs/modules/ # {Modül}.md → ModuleDocManager (seed edilmez) +├── data/ # {ListFormCode}.json ya da {ListFormCode}/{anahtar}.json → ListFormSeedDataApplier +│ # App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı +│ # Abp.Identity.Permission{Groups,s}.json — yetki tanımları +│ # App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri +│ # App.Definitions.{Currency,ContactTitle,AiBot,TerminationReason}.json — para birimi, hitap, AI bot, SGK çıkış kodu +│ # App.Hr.{SkillType,SkillLevel,Skill}.json — İK yetkinlik tanımları +│ # App.Hr.{Department,JobPosition}.json — İK departman → pozisyon (BranchId ile şubeye bağlı) +│ # App.Hr.Intranet.*.json — İK › Intranet (duyuru, anket, sosyal akış, etkinlik) +│ # App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json — İK › Tanımlar altındaki işe alım tanımları +│ # App.Hr.PerformanceRating.json — İK › Tanımlar › Performans Dereceleri (5 kademeli ölçek) +│ # App.Notifications.Notification{Templates,Types,Rules}.json — bildirim şablonu, tipi → kuralı +│ # App.Languages.{Language,LanguageText}.json — dil → metin (eksik LanguageKey metinle üretilir) +│ # App.SettingDefinitions.json, App.BackgroundWorkers.RecurringJobs.json — ayar tanımları, zamanlanmış işler +├── sql/{object,execute}/ # .sql (SQL Server) +│ └── host/{object,execute}/ # yalnızca host veritabanında çalışır +├── postgres/{object,execute}/ +│ └── host/{object,execute}/ └── .imports/{importId}/ # wizard import staging + backup (seeder taramalarına girmez) ``` diff --git a/api/Sozsoft.Platform.HttpApi.Host.Dockerfile b/api/Sozsoft.Platform.HttpApi.Host.Dockerfile index ebd945a4..08b3e76c 100644 --- a/api/Sozsoft.Platform.HttpApi.Host.Dockerfile +++ b/api/Sozsoft.Platform.HttpApi.Host.Dockerfile @@ -55,7 +55,6 @@ COPY "src/Sozsoft.Platform.Domain/Sozsoft.Platform.Domain.csproj" "src/Sozsoft.P COPY "src/Sozsoft.Platform.Domain.Shared/Sozsoft.Platform.Domain.Shared.csproj" "src/Sozsoft.Platform.Domain.Shared/" COPY "src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj" "src/Sozsoft.Platform.EntityFrameworkCore/" COPY "src/Sozsoft.Platform.HttpApi/Sozsoft.Platform.HttpApi.csproj" "src/Sozsoft.Platform.HttpApi/" -COPY "src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj" "src/Sozsoft.Platform.HttpApi.Client/" COPY "src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj" "src/Sozsoft.Platform.HttpApi.Host/" COPY "src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj" "src/Sozsoft.Platform.DbMigrator/" COPY "test/Sozsoft.Platform.EntityFrameworkCore.Tests/Sozsoft.Platform.EntityFrameworkCore.Tests.csproj" "test/Sozsoft.Platform.EntityFrameworkCore.Tests/" diff --git a/api/Sozsoft.Platform.sln b/api/Sozsoft.Platform.sln index ec6225ab..7525a5fa 100644 --- a/api/Sozsoft.Platform.sln +++ b/api/Sozsoft.Platform.sln @@ -17,8 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sozsoft.Platform.Applicatio EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sozsoft.Platform.HttpApi", "src\Sozsoft.Platform.HttpApi\Sozsoft.Platform.HttpApi.csproj", "{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sozsoft.Platform.HttpApi.Client", "src\Sozsoft.Platform.HttpApi.Client\Sozsoft.Platform.HttpApi.Client.csproj", "{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sozsoft.Platform.DbMigrator", "src\Sozsoft.Platform.DbMigrator\Sozsoft.Platform.DbMigrator.csproj", "{AA94D832-1CCC-4715-95A9-A483F23A1A5D}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sozsoft.Platform.HttpApi.Host", "src\Sozsoft.Platform.HttpApi.Host\Sozsoft.Platform.HttpApi.Host.csproj", "{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}" @@ -186,18 +184,6 @@ Global {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x64.Build.0 = Release|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.ActiveCfg = Release|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.Build.0 = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.ActiveCfg = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.Build.0 = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.ActiveCfg = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.Build.0 = Debug|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.Build.0 = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.ActiveCfg = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.Build.0 = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.ActiveCfg = Release|Any CPU - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.Build.0 = Release|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -593,7 +579,6 @@ Global {42F719ED-8413-4895-B5B4-5AB56079BC66} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {520659C8-C734-4298-A3DA-B539DB9DFC0B} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {4164BDF7-F527-4E85-9CE6-E3C2D7426A27} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} - {3B5A0094-670D-4BB1-BFDD-61B88A8773DC} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {AA94D832-1CCC-4715-95A9-A483F23A1A5D} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D} = {CA9AC87F-097E-4F15-8393-4BC07735A5B0} {4D886EEF-D4CF-4879-A10D-249D762E4ED9} = {03E1C8DA-035E-4882-AF81-F392139FCF38} diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/ILanguageKeyIntegrationService.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/ILanguageKeyIntegrationService.cs deleted file mode 100644 index 3e5a7aa6..00000000 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/ILanguageKeyIntegrationService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp; - -namespace Sozsoft.Languages; - -[IntegrationService] -public interface ILanguageKeyIntegrationService -{ - Task CreateKeysForSettingsAsync( - string NameKey, - string DescriptionKey, - string MainGroupKey, - string SubGroupKey - ); - - Task DeleteKeysForSettingsAsync( - string NameKey, - string DescriptionKey, - string MainGroupKey, - string SubGroupKey); -} - diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/Permissions/LanguagesPermissions.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/Permissions/LanguagesPermissions.cs index 28459bac..972f8400 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/Permissions/LanguagesPermissions.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application.Contracts/Permissions/LanguagesPermissions.cs @@ -6,6 +6,25 @@ public class LanguagesPermissions { public const string GroupName = "Languages"; + // Yetki tanımları seed'dedir (data/Abp.Identity.Permissions.json); burada yalnızca adları durur. + // Değerler PlatformConsts.AppCodes.Languages ile aynıdır — modül platforma referans vermez. + public static class Language + { + public const string Default = "App.Languages.Language"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + + /// Dil anahtarları ayrı bir ekranda değil, dil metinleriyle birlikte yönetilir. + public static class LanguageText + { + public const string Default = "App.Languages.LanguageText"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + public static string[] GetAll() { return ReflectionHelper.GetPublicConstantsRecursively(typeof(LanguagesPermissions)); diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageAppService.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageAppService.cs index 557246cd..7ed88f09 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageAppService.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageAppService.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Localization; +using Sozsoft.Languages.Permissions; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Entities; @@ -12,6 +14,7 @@ using Volo.Abp.Validation; namespace Sozsoft.Languages; +[Authorize] public class LanguageAppService : CrudAppService< Language, LanguageDto, @@ -23,6 +26,11 @@ public class LanguageAppService : CrudAppService< { LocalizationResource = typeof(LanguagesResource); ObjectMapperContext = typeof(LanguagesApplicationModule); + GetPolicyName = LanguagesPermissions.Language.Default; + GetListPolicyName = LanguagesPermissions.Language.Default; + CreatePolicyName = LanguagesPermissions.Language.Create; + UpdatePolicyName = LanguagesPermissions.Language.Update; + DeletePolicyName = LanguagesPermissions.Language.Delete; } public override async Task CreateAsync(LanguageDto input) @@ -40,6 +48,7 @@ public class LanguageAppService : CrudAppService< return await base.CreateAsync(input); } + [Authorize(LanguagesPermissions.Language.Default)] public async Task GetLanguageByCultureNameAsync(string CultureName) { var item = await Repository.FirstOrDefaultAsync(a => a.CultureName == CultureName); diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyAppService.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyAppService.cs index 4400b9de..201ec831 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyAppService.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyAppService.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Localization; +using Sozsoft.Languages.Permissions; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; @@ -11,6 +13,7 @@ using Volo.Abp.Validation; namespace Sozsoft.Languages; +[Authorize] public class LanguageKeyAppService : CrudAppService< LanguageKey, LanguageKeyDto, @@ -26,6 +29,11 @@ public class LanguageKeyAppService : CrudAppService< { LocalizationResource = typeof(LanguagesResource); ObjectMapperContext = typeof(LanguagesApplicationModule); + GetPolicyName = LanguagesPermissions.LanguageText.Default; + GetListPolicyName = LanguagesPermissions.LanguageText.Default; + CreatePolicyName = LanguagesPermissions.LanguageText.Create; + UpdatePolicyName = LanguagesPermissions.LanguageText.Update; + DeletePolicyName = LanguagesPermissions.LanguageText.Delete; _repository = repository; } diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyIntegrationService.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyIntegrationService.cs deleted file mode 100644 index 3af4e89e..00000000 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageKeyIntegrationService.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Threading.Tasks; -using Sozsoft.Languages.Entities; -using Volo.Abp; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; - -namespace Sozsoft.Languages; - -[IntegrationService] -public class LanguageKeyIntegrationService : ILanguageKeyIntegrationService, ITransientDependency -{ - private readonly IRepository _repository; - - public LanguageKeyIntegrationService(IRepository repository) - { - _repository = repository; - } - - public async Task CreateKeysForSettingsAsync( - string NameKey, - string DescriptionKey, - string MainGroupKey, - string SubGroupKey - ) - { - //NameKey - if (!await _repository.AnyAsync(a => a.Key == NameKey && a.ResourceName == LanguagesConsts.AppName)) - { - await _repository.InsertAsync(new LanguageKey { Key = NameKey, ResourceName = LanguagesConsts.AppName }); - } - - //DescriptionKey - if (!await _repository.AnyAsync(a => a.Key == DescriptionKey && a.ResourceName == LanguagesConsts.AppName)) - { - await _repository.InsertAsync(new LanguageKey { Key = DescriptionKey, ResourceName = LanguagesConsts.AppName }); - } - - //MainGroupKey - if (!await _repository.AnyAsync(a => a.Key == MainGroupKey && a.ResourceName == LanguagesConsts.AppName)) - { - await _repository.InsertAsync(new LanguageKey { Key = MainGroupKey, ResourceName = LanguagesConsts.AppName }); - } - - //SubGroupKey - if (!await _repository.AnyAsync(a => a.Key == SubGroupKey && a.ResourceName == LanguagesConsts.AppName)) - { - await _repository.InsertAsync(new LanguageKey { Key = SubGroupKey, ResourceName = LanguagesConsts.AppName }); - } - } - - public async Task DeleteKeysForSettingsAsync( - string NameKey, - string DescriptionKey, - string MainGroupKey, - string SubGroupKey) - { - await _repository.DeleteAsync(a => a.Key == NameKey && a.ResourceName == LanguagesConsts.AppName); - await _repository.DeleteAsync(a => a.Key == DescriptionKey && a.ResourceName == LanguagesConsts.AppName); - await _repository.DeleteAsync(a => a.Key == MainGroupKey && a.ResourceName == LanguagesConsts.AppName); - await _repository.DeleteAsync(a => a.Key == SubGroupKey && a.ResourceName == LanguagesConsts.AppName); - } -} - diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageTextAppService.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageTextAppService.cs index 2f72b2bc..5d940950 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageTextAppService.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguageTextAppService.cs @@ -4,11 +4,12 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Linq.Dynamic.Core; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; +using Sozsoft.Languages.Caching; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Localization; -using StackExchange.Redis; +using Sozsoft.Languages.Permissions; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -19,6 +20,11 @@ using Volo.Abp.Validation; namespace Sozsoft.Languages; +/// +/// Dil metinlerinin yönetim uçları. Çeviri okuma bu servisten geçmez — public sayfalar dahil bütün +/// istemciler metinleri ABP'nin application-localization ucundan (DatabaseResourceLocalizer) alır. +/// +[Authorize] public class LanguageTextAppService : CrudAppService< LanguageText, LanguageTextDto, @@ -29,20 +35,25 @@ public class LanguageTextAppService : CrudAppService< { private readonly IRepository _repositoryText; private readonly IRepository _repositoryKey; - private readonly IConfiguration _configuration; + private readonly DistributedCacheCleaner _cacheCleaner; public LanguageTextAppService( IRepository repositoryText, IRepository repositoryKey, - IConfiguration configuration + DistributedCacheCleaner cacheCleaner ) : base(repositoryText) { LocalizationResource = typeof(LanguagesResource); ObjectMapperContext = typeof(LanguagesApplicationModule); + GetPolicyName = LanguagesPermissions.LanguageText.Default; + GetListPolicyName = LanguagesPermissions.LanguageText.Default; + CreatePolicyName = LanguagesPermissions.LanguageText.Create; + UpdatePolicyName = LanguagesPermissions.LanguageText.Update; + DeletePolicyName = LanguagesPermissions.LanguageText.Delete; _repositoryText = repositoryText; _repositoryKey = repositoryKey; - _configuration = configuration; + _cacheCleaner = cacheCleaner; } public override async Task CreateAsync(LanguageTextCreateUpdateDto input) @@ -63,6 +74,7 @@ public class LanguageTextAppService : CrudAppService< return await base.CreateAsync(input); } + [Authorize(LanguagesPermissions.LanguageText.Update)] public async Task UpdateLanguageTextAsync(LanguageTextCreateUpdateDto input) { var entity = await _repositoryText.GetAsync( @@ -77,6 +89,7 @@ public class LanguageTextAppService : CrudAppService< } } + [Authorize(LanguagesPermissions.LanguageText.Default)] public async Task> GetLanguageTextTranslatedAsync(LanguageTextTranslatedRequestDto input) { var query = await _repositoryKey.GetQueryableAsync(); @@ -109,6 +122,7 @@ public class LanguageTextAppService : CrudAppService< return new PagedResultDto(totalCount, dtoLanguageTextTranslate); } + [Authorize(LanguagesPermissions.LanguageText.Default)] public async Task GetLanguageTextAsync(LanguageTextFilteredDto input) { var entity = await _repositoryText.FirstOrDefaultAsync(a => @@ -119,6 +133,7 @@ public class LanguageTextAppService : CrudAppService< return ObjectMapper.Map(entity); } + [Authorize(LanguagesPermissions.LanguageText.Default)] public async Task> GetLanguageTextByCultureNameAsync(string CultureName) { var item = await _repositoryText.GetListAsync(a => a.CultureName == CultureName); @@ -130,11 +145,11 @@ public class LanguageTextAppService : CrudAppService< return ObjectMapper.Map, List>(item); } + // Dil Metinleri listesindeki "Redis önbelleğini temizle" butonu da bu yetkiyle görünür. + [Authorize(LanguagesPermissions.LanguageText.Default)] public async Task ClearRedisCacheAsync() { - var redis = ConnectionMultiplexer.Connect(_configuration["Redis:Configuration"]!); - var db = redis.GetDatabase(); - await db.ExecuteAsync("FLUSHALL"); + await _cacheCleaner.ClearAsync(); } protected virtual IQueryable ApplySorting(IQueryable query, LanguageTextTranslatedRequestDto input) diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Caching/DistributedCacheCleaner.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Caching/DistributedCacheCleaner.cs new file mode 100644 index 00000000..d47ef78a --- /dev/null +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Caching/DistributedCacheCleaner.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using StackExchange.Redis; +using Sozsoft.Languages.Localization; +using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; + +namespace Sozsoft.Languages.Caching; + +/// +/// Uygulamanın dağıtık önbelleğini boşaltır. FLUSHALL yerine yalnızca bu uygulamanın anahtarlarını +/// siler: Redis başka bir uygulama ya da ortamla paylaşılıyorsa onların verisine dokunulmaz. +/// Bağlantı süreç başına tektir; her çağrıda yeni bağlantı açmak bağlantı sızdırıyordu. +/// +public class DistributedCacheCleaner : ISingletonDependency, IDisposable +{ + private const int BatchSize = 500; + + private readonly Lazy _connection; + private readonly string[] _keyPatterns; + private readonly IDatabaseResourceLocalizer _localizer; + + public DistributedCacheCleaner( + IConfiguration configuration, + IOptions cacheOptions, + IDatabaseResourceLocalizer localizer) + { + _localizer = localizer; + _connection = new Lazy( + () => ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!)); + + // ABP iki biçimde yazar: IDistributedCache anahtarı [t:{kiracı},]c:{önbellek},k:{KeyPrefix}{anahtar}, + // bellek içi tanım önbelleklerinin damgası ise doğrudan {KeyPrefix}_Abp...Stamp. Host "Platform", + // DbMigrator "Platform:" öneki kullanır; ':' atılınca desenler iki sürecin anahtarlarını da kapsar. + var prefix = (cacheOptions.Value.KeyPrefix ?? string.Empty).TrimEnd(':'); + _keyPatterns = [$"{prefix}*", $"*,k:{prefix}*"]; + } + + /// Silinen anahtar sayısı. + public async Task ClearAsync() + { + var connection = _connection.Value; + var database = connection.GetDatabase(); + long removed = 0; + + foreach (var endpoint in connection.GetEndPoints()) + { + var server = connection.GetServer(endpoint); + if (server.IsReplica || !server.IsConnected) + { + continue; + } + + foreach (var pattern in _keyPatterns) + { + var batch = new List(BatchSize); + await foreach (var key in server.KeysAsync(database.Database, pattern, BatchSize)) + { + batch.Add(key); + if (batch.Count == BatchSize) + { + removed += await DeleteAsync(database, batch); + batch.Clear(); + } + } + + removed += await DeleteAsync(database, batch); + } + } + + // Dil sözlüğünün süreç içi kopyası Redis'te değildir; temizlik onu da kapsar. + _localizer.ClearLocalCache(); + + return removed; + } + + // Anahtarlar tek tek silinir: çok anahtarlı DEL, cluster'da farklı slot'lara düşen anahtarları reddeder. + private static async Task DeleteAsync(IDatabase database, List keys) + { + var results = await Task.WhenAll(keys.Select(key => database.KeyDeleteAsync(key))); + return results.Count(deleted => deleted); + } + + public void Dispose() + { + if (_connection.IsValueCreated) + { + _connection.Value.Dispose(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidator.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidator.cs index 4b4d906d..25bf894f 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidator.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidator.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Sozsoft.Languages.Entities; +using Sozsoft.Languages.Localization; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities.Events; @@ -12,10 +13,12 @@ public class LanguageTextCacheItemInvalidator : ITransientDependency { private readonly IDistributedCache cache; + private readonly IDatabaseResourceLocalizer localizer; - public LanguageTextCacheItemInvalidator(IDistributedCache cache) + public LanguageTextCacheItemInvalidator(IDistributedCache cache, IDatabaseResourceLocalizer localizer) { this.cache = cache; + this.localizer = localizer; } public async Task HandleEventAsync(EntityChangedEventData eventData) @@ -23,7 +26,7 @@ public class LanguageTextCacheItemInvalidator : //distributedCacheOptions.Value.KeyPrefix var key = LanguageTextCacheItem.CalculateCacheKey(eventData.Entity.ResourceName, eventData.Entity.CultureName); await cache.RemoveAsync(key); - return; + localizer.ClearLocalCache(); } } diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidatorDistributed.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidatorDistributed.cs index bd96d945..87fc3363 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidatorDistributed.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Languages/LanguageTextCacheItemInvalidatorDistributed.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Sozsoft.Languages.Localization; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities.Events.Distributed; @@ -13,11 +14,14 @@ public class LanguageTextCacheItemInvalidatorDistributed : ITransientDependency { private readonly IDistributedCache cache; + private readonly IDatabaseResourceLocalizer localizer; public LanguageTextCacheItemInvalidatorDistributed( - IDistributedCache cache) + IDistributedCache cache, + IDatabaseResourceLocalizer localizer) { this.cache = cache; + this.localizer = localizer; } public async Task HandleEventAsync(EntityCreatedEto eventData) @@ -39,6 +43,7 @@ public class LanguageTextCacheItemInvalidatorDistributed : { var key = LanguageTextCacheItem.CalculateCacheKey(eventData.ResourceName, eventData.CultureName); await cache.RemoveAsync(key); + localizer.ClearLocalCache(); } } diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/DatabaseResourceLocalizer.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/DatabaseResourceLocalizer.cs index 888b913e..53ca6a05 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/DatabaseResourceLocalizer.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/DatabaseResourceLocalizer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -14,8 +15,14 @@ namespace Sozsoft.Languages.Localization; public class DatabaseResourceLocalizer : IDatabaseResourceLocalizer, ISingletonDependency { + // Her metin aramasında sözlüğün tamamı Redis'ten çekilip çözülüyordu. Süreç içi katman bunu önler. + // Kısa ömürlüdür: metinlerin bir kısmı ham SQL ile yazılır (olay üretmez) ve diğer API örnekleri ile + // DbMigrator ayrı süreçtir; onların temizliği buraya en geç bu süre sonunda yansır. + private static readonly long LocalCacheDurationMs = (long)TimeSpan.FromSeconds(30).TotalMilliseconds; + private readonly IRepository languageTextRepository; private readonly IDistributedCache cache; + private readonly ConcurrentDictionary localCache = new(); public DatabaseResourceLocalizer( IRepository languageTextRepository, @@ -27,27 +34,52 @@ public class DatabaseResourceLocalizer : IDatabaseResourceLocalizer, ISingletonD public LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name) { - var item = GetCacheItem(resource, cultureName); - var value = item.Dictionary?.GetOrDefault(name); - if (item == null || value == null) - { - return null; - } - return new LocalizedString(name, value); + var value = GetCacheItem(resource, cultureName)?.Dictionary?.GetOrDefault(name); + return value == null ? null : new LocalizedString(name, value); } + /// Süreç içi katmanı boşaltır; dağıtık önbellek temizlendiğinde ve metin değiştiğinde çağrılır. + public void ClearLocalCache() => localCache.Clear(); + protected LanguageTextCacheItem GetCacheItem(LocalizationResourceBase resource, string cultureName) { // culture, resource, key var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); - return cache.GetOrAdd(key, () => CreateCacheItem(resource, cultureName)); + if (TryGetLocal(key, out var local)) + { + return local; + } + + return SetLocal(key, cache.GetOrAdd(key, () => CreateCacheItem(resource, cultureName))); + } + + private bool TryGetLocal(string key, out LanguageTextCacheItem item) + { + if (localCache.TryGetValue(key, out var entry) && entry.ExpiresAt > Environment.TickCount64) + { + item = entry.Item; + return true; + } + + item = null; + return false; + } + + private LanguageTextCacheItem SetLocal(string key, LanguageTextCacheItem item) + { + if (item != null) + { + localCache[key] = (item, Environment.TickCount64 + LocalCacheDurationMs); + } + + return item; } public void Fill(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) { var item = GetCacheItem(resource, cultureName); - foreach (var kvp in item.Dictionary) + foreach (var kvp in item?.Dictionary ?? []) { dictionary[kvp.Key] = new LocalizedString(kvp.Key, kvp.Value); } @@ -67,9 +99,12 @@ public class DatabaseResourceLocalizer : IDatabaseResourceLocalizer, ISingletonD public async Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary) { var key = LanguageTextCacheItem.CalculateCacheKey(resource.ResourceName, cultureName); - var item = await cache.GetOrAddAsync(key, () => CreateCacheItemAsync(resource, cultureName)); + if (!TryGetLocal(key, out var item)) + { + item = SetLocal(key, await cache.GetOrAddAsync(key, () => CreateCacheItemAsync(resource, cultureName))); + } - foreach (var kvp in item.Dictionary) + foreach (var kvp in item?.Dictionary ?? []) { dictionary[kvp.Key] = new LocalizedString(kvp.Key, kvp.Value); } diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/IDatabaseResourceLocalizer.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/IDatabaseResourceLocalizer.cs index 47afbd8d..27037e75 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/IDatabaseResourceLocalizer.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Localization/IDatabaseResourceLocalizer.cs @@ -10,5 +10,6 @@ public interface IDatabaseResourceLocalizer void Fill(LocalizationResourceBase resource, string cultureName, Dictionary dictionary); Task FillAsync(LocalizationResourceBase resource, string cultureName, Dictionary dictionary); LocalizedString GetOrNull(LocalizationResourceBase resource, string cultureName, string name); + void ClearLocalCache(); } diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs index 45c2d4e6..00b28c8f 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpDevelopAppService.cs @@ -329,7 +329,7 @@ public class McpDevelopAppService( if (listForm == null || string.IsNullOrWhiteSpace(relativePath) || SeedPathResolver.IsSeedFolderPath(relativePath)) throw new UserFriendlyException(L[McpErrorCodes.SeedRowsNotAllowed]); - var path = SeedPathResolver.GetScopePath(configuration, CurrentTenant.Id, relativePath.Split('/')); + var path = SeedPathResolver.GetPath(configuration, relativePath.Split('/')); var seedFile = File.Exists(path) ? JsonSerializer.Deserialize(await File.ReadAllTextAsync(path), ListFormSeedDataFileDto.JsonOptions) : null; diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpDomainSharedModule.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpDomainSharedModule.cs index 6e1aeab4..d6bb2208 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpDomainSharedModule.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpDomainSharedModule.cs @@ -6,7 +6,7 @@ namespace Sozsoft.Mcp; /// /// Modulun paylasilan sozlesmesi. Kullaniciya gorunen metinler platformun kendi -/// PlatformResource kaynagindan (configs/seeds/host/data/App.Languages.LanguageText.json) okunur; modul ikinci bir dil +/// PlatformResource kaynagindan (configs/seeds/data/App.Languages.LanguageText.json) okunur; modul ikinci bir dil /// kaynagi acmaz. /// [DependsOn( diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs index d82fcf37..9689952a 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/McpErrorCodes.cs @@ -1,7 +1,7 @@ namespace Sozsoft.Mcp; /// -/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun configs/seeds/host/data/App.Languages.LanguageText.json +/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun configs/seeds/data/App.Languages.LanguageText.json /// dosyasinda EN + TR olarak durur; koda gomulu metin kullanilmaz. /// public static class McpErrorCodes diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs index 39013a2b..68f2ad40 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs @@ -265,14 +265,13 @@ public class McpTableCreator( } /// - /// Tasarimcinin SaveTableScriptAsync ile yazdigi yol: kapsam istegin kiracisini izler. + /// Tasarimcinin SaveTableScriptAsync ile yazdigi yol. /// Dosyasiz tablo veritabani sifirlandiginda geri gelmezdi; yazilamazsa islem hata verir. /// private string SaveSeedScript(string tableName, string script) { - var outputPath = SeedPathResolver.GetScopePath( + var outputPath = SeedPathResolver.GetPath( configuration, - CurrentTenant.Id, SeedPathResolver.GetProviderFolderName(isPostgreSql: false), SeedPathResolver.ObjectFolder); @@ -281,7 +280,7 @@ public class McpTableCreator( var filePath = Path.Combine(outputPath, $"{tableName}.sql"); File.WriteAllText(filePath, script); - return $"{SeedPathResolver.GetScopeFolderName(CurrentTenant.Id)}/{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{tableName}.sql"; + return $"{SeedPathResolver.SqlFolder}/{SeedPathResolver.ObjectFolder}/{tableName}.sql"; } private void EnsureSafeIdentifier(string identifier) diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs index 114c568d..cbbc668b 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs @@ -118,44 +118,5 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService } } } - - /* - public override async Task CreateAsync(SettingDefinitionDto input) - { - await languageKeyIntegrationService.CreateKeysForSettingsAsync( - input.NameKey, - input.DescriptionKey, - input.MainGroupKey, - input.SubGroupKey); - - return await base.CreateAsync(input); - } - - public override async Task UpdateAsync(Guid id, SettingDefinitionDto input) - { - await languageKeyIntegrationService.CreateKeysForSettingsAsync( - input.NameKey, - input.DescriptionKey, - input.MainGroupKey, - input.SubGroupKey); - - return await base.UpdateAsync(id, input); - } - - public override async Task DeleteAsync(Guid id) - { - var key = await _repositorySettingDefinition.GetAsync(id); - if (key != null) - { - await languageKeyIntegrationService.DeleteKeysForSettingsAsync( - key.NameKey, - key.DescriptionKey, - key.MainGroupKey, - key.SubGroupKey); - } - - await base.DeleteAsync(id); - } - */ } diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs index aaf75270..718771fd 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs @@ -30,8 +30,8 @@ public interface ISqlObjectManagerAppService : IApplicationService Task GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName); /// - /// Saves the T-SQL script to {SeedsRoot}/{host|tenants/{tenantId}}/{sql|postgres}/object/{fileName}.sql - /// (repo: configs/seeds). The scope folder follows the current tenant: host context writes to host/. + /// Saves the T-SQL script to {SeedsRoot}/{sql|postgres}/object/{fileName}.sql + /// (repo: configs/seeds). The same file is seeded into the host and every tenant that has its own database. /// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded. /// Task SaveTableScriptAsync(SaveTableScriptDto input); diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/SqlExecutionDto.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/SqlExecutionDto.cs index 58b4198d..67b844ed 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/SqlExecutionDto.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application.Contracts/SqlExecutionDto.cs @@ -21,7 +21,7 @@ public class SqlQueryExecutionResultDto /// /// Input for saving a T-SQL script file to the seeds folder -/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object). +/// ({App:SeedsPath}/{sql|postgres}/object). /// public class SaveTableScriptDto { @@ -38,7 +38,7 @@ public class SaveTableScriptDto /// /// Input for deleting seed files from the seeds folder -/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object). +/// ({App:SeedsPath}/{sql|postgres}/object). /// public class DeleteSqlDataFilesDto { diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs index 636167e9..ec2cea8c 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs @@ -960,23 +960,15 @@ FROM ( /// /// Script dosyalarinin yazilacagi klasor. SqlDataSeeder ile ayni cozumleyiciyi kullanir /// (App:SeedsPath -> configs/seeds), aksi halde deploy edilen nesne seed olarak geri okunmaz. - /// - /// Yol, islemin yapildigi veritabani kapsamini izler: host (varsayilan) baglaminda - /// host/{sql|postgres}/object, bir tenant secilmisken - /// tenants/{tenantId}/{sql|postgres}/object. Boylece nesne, seed sirasinda - /// olusturuldugu veritabaninda yeniden olusturulur. - /// + /// Dosya {sql|postgres}/object altindadir; nesne host'ta ve kendi veritabani olan her + /// tenant'ta seed sirasinda yeniden olusturulur. /// private string ResolveSqlDataOutputPath() { var providerDirectoryName = SeedPathResolver.GetProviderFolderName( SettingsConsts.DefaultDatabaseProvider == SettingsConsts.DatabaseProvider.PostgreSql); - return SeedPathResolver.GetScopePath( - _configuration, - _currentTenant.Id, - providerDirectoryName, - SeedPathResolver.ObjectFolder); + return SeedPathResolver.GetPath(_configuration, providerDirectoryName, SeedPathResolver.ObjectFolder); } } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateTenantInput.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateTenantInput.cs index 5e9e6c1e..34d852c9 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateTenantInput.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/DynamicApi/CreateUpdateTenantInput.cs @@ -9,8 +9,6 @@ public class CreateUpdateTenantInput public bool IsActive { get; set; } public Guid? Id { get; set; } - public string AdminEmail { get; set; } - public string AdminPassword { get; set; } public string ConnectionString { get; set; } public string OrganizationName { get; set; } public string Founder { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsEditDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsEditDto.cs index f7863fbd..06394cbf 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsEditDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/GridOptionsDto/GridOptionsEditDto.cs @@ -32,7 +32,7 @@ public class GridOptionsEditDto : GridOptionsDto set { SelectFieldsDefaultValueJson = JsonSerializer.Serialize(value); } } - /// Kapsam klasorune gore goreli seed dosyasi yolu (ornegin data/{ListFormCode}.json). + /// Seed klasorune gore goreli seed dosyasi yolu (ornegin data/{ListFormCode}.json). /// Dolu ise ekrandan yapilan kayit islemleri dosyaya yansitilir. public string SeedFilePath { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs index cb30511c..0c96f650 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ModuleDocs/IModuleDocAppService.cs @@ -39,7 +39,7 @@ public class ModuleDocDto /// Dosya adi, uzantisiz (HrPerformance). public string Name { get; set; } = string.Empty; - /// Kapsam klasorune gore goreli yol (docs/modules/HrPerformance.md). + /// Seed klasorune gore goreli yol (docs/modules/HrPerformance.md). public string Path { get; set; } = string.Empty; public bool Exists { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs index f25f3c1a..954bac8f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/ListFormWizardDto.cs @@ -85,7 +85,7 @@ public class ListFormWizardDto public bool ConfirmDelete { get; set; } /// - /// Ekranin verisi kapsam klasorundeki data/{ListFormCode}.json dosyasina aynalanir + /// Ekranin verisi seed klasorundeki data/{ListFormCode}.json dosyasina aynalanir /// (SeedFilePath + ekle/guncelle/sil senkronizasyonu). Istekle gelen ornek/demo satirlar /// ekrandan girildiginde dosyaya yazilir ve migrate ile geri yuklenir. /// diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardExportDtos.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardExportDtos.cs index 06a10b00..de6798df 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardExportDtos.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardExportDtos.cs @@ -5,7 +5,7 @@ namespace Sozsoft.Platform.ListForms; /// Export zip'ine girecek tek bir seed dosyasi. public class WizardExportEntryDto { - /// Kapsam klasorune goreli yol, ornegin sql/object/Prj_T_Task.sql. + /// Seed klasorune goreli yol, ornegin sql/object/Prj_T_Task.sql. public string Path { get; set; } /// Dosyanin arsive neden girdigi (wizard'in kendisi, alt form, lookup, veri...). @@ -27,9 +27,6 @@ public class WizardExportAnalysisDto /// Indirilecek arsivin adi. public string ArchiveName { get; set; } - /// Kapsam klasoru (host ya da tenants/{tenantId}). - public string ScopeFolder { get; set; } - public List Entries { get; set; } = []; /// Bagimlilik olarak bulunan ama diskte karsiligi olmayan dosyalar. diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardImportDtos.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardImportDtos.cs index 72edda20..6a7390d2 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardImportDtos.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardImportDtos.cs @@ -5,11 +5,11 @@ namespace Sozsoft.Platform.ListForms; /// /// Import edilen zip icindeki tek bir seed dosyasinin analiz sonucu. -/// Yol her zaman kapsam klasorune (host / tenants/{tenantId}) goreli tutulur. +/// Yol her zaman seed klasorune goreli tutulur. /// public class WizardImportEntryDto { - /// Kapsam klasorune goreli yol, ornegin crud/Orders.json. + /// Seed klasorune goreli yol, ornegin crud/Orders.json. public string Path { get; set; } /// Yolun ilk segmenti; listede gruplama icin kullanilir (wizard, crud, data, sql, postgres). @@ -36,9 +36,6 @@ public class WizardImportAnalysisDto /// Staging klasorunu ve rollback kaydini tanimlayan oturum kimligi. public string ImportId { get; set; } - /// Dosyalarin yazilacagi kapsam klasoru (host ya da tenants/{tenantId}). - public string ScopeFolder { get; set; } - public List Entries { get; set; } = []; /// Guvenlik/format nedeniyle atlanan zip girdileri icin aciklamalar. @@ -166,7 +163,7 @@ public class WizardImportCompleteResultDto /// Import kapanisindaki tek bir adimin sonucu (SQL scripti ya da CRUD dosyasi). public class WizardImportStepResultDto { - /// Kapsam klasorune goreli yol. + /// Seed klasorune goreli yol. public string Path { get; set; } /// Adim basariliysa yapilan isi ozetleyen kisa metin. @@ -179,7 +176,7 @@ public class WizardImportStepResultDto /// Tek bir veri seed dosyasinin veritabanina uygulanma sonucu. public class WizardImportSeedApplyResultDto { - /// Kapsam klasorune goreli yol. + /// Seed klasorune goreli yol. public string Path { get; set; } /// Veritabanina eklenen satir sayisi. diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs index 6aacdb52..49ca1205 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs @@ -11,7 +11,7 @@ namespace Sozsoft.Platform.ListForms; /// /// Wizard seed dosyası formatı. -/// WizardAppService tarafından Seeds/{host|tenants/{tenantId}}/wizard/{wizardName}.json olarak kaydedilir. +/// WizardAppService tarafından Seeds/wizard/{wizardName}.json olarak kaydedilir. /// WizardDataSeeder (DbMigrator) bu dosyaları okuyarak veritabanını yeniden oluşturur. /// public class WizardSeedFileDto @@ -511,7 +511,7 @@ public class WizardDeleteTableDto public WizardDeleteReasonEnum Reason { get; set; } public List UsedBy { get; set; } = []; - /// Tabloyla birlikte silinecek seed dosyalari, kapsam klasorune gore goreli. + /// Tabloyla birlikte silinecek seed dosyalari, seed klasorune gore goreli. public List Files { get; set; } = []; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Permissions/PlatformPermissionDefinitionProvider.cs b/api/src/Sozsoft.Platform.Application.Contracts/Permissions/PlatformPermissionDefinitionProvider.cs index 7bfaa101..308d7def 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Permissions/PlatformPermissionDefinitionProvider.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Permissions/PlatformPermissionDefinitionProvider.cs @@ -3,7 +3,7 @@ namespace Sozsoft.Platform.Permissions; /// -/// Uygulama yetkileri kod içinde tanımlanmaz; configs/seeds/host/data/Abp.Identity.Permissions.json +/// Uygulama yetkileri kod içinde tanımlanmaz; configs/seeds/data/Abp.Identity.Permissions.json /// dosyasından ListFormSeedDataApplier ile veritabanına seed edilir ve /// oradan dinamik olarak yönetilir. Bu sağlayıcı yalnızca kullanılmayan /// ABP modül gruplarını kaldırmak için vardır. diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Tenants/IPlatformTenantAppService.cs b/api/src/Sozsoft.Platform.Application.Contracts/Tenants/IPlatformTenantAppService.cs deleted file mode 100644 index 273fce30..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Tenants/IPlatformTenantAppService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using Sozsoft.Platform.ListForms.DynamicApi; - -namespace Sozsoft.Platform.Tenants; - -public interface IPlatformTenantAppService -{ - Task PostSeedTenantDataAsync(CreateUpdateTenantInput input); -} - diff --git a/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs b/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs index 972721d8..57868ff3 100644 --- a/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Sozsoft.Platform; using Sozsoft.Platform.Branchs; using Sozsoft.Platform.Entities; @@ -18,10 +19,13 @@ public class BranchAppService : PlatformAppService _branchSeedManager = branchSeedManager; } + // Şubeler listesindeki BranchSeed komutu da bu yetkiyle görünür (ListFormSeeder_Saas). + [Authorize(PlatformConsts.AppCodes.Branches + ".Update")] public async Task PostSeedAsync(Guid branchId) { + // Branch kiracı filtresi taşımaz; kiracı kullanıcısı yalnızca kendi şubesini seed edebilir, host hepsini. var branch = await _branchRepository.FirstOrDefaultAsync(x => x.Id == branchId); - if (branch == null) + if (branch == null || (CurrentTenant.Id.HasValue && branch.TenantId != CurrentTenant.Id)) return new BranchSeedResultDto { Success = false, Message = "Branch not found." }; using (CurrentTenant.Change(branch.TenantId)) diff --git a/api/src/Sozsoft.Platform.Application/DataSource/DataSourceAppService.cs b/api/src/Sozsoft.Platform.Application/DataSource/DataSourceAppService.cs index e9fd3295..1a0d36b4 100644 --- a/api/src/Sozsoft.Platform.Application/DataSource/DataSourceAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DataSource/DataSourceAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Sozsoft.Platform.Entities; using Volo.Abp.Application.Dtos; @@ -16,6 +17,23 @@ public class DataSourceAppService : CrudAppService< { public DataSourceAppService(IRepository repo) : base(repo) { + // Okuma açık kalır: ListForm, Wizard ve SQL Query Manager açılır listeleri kodu ve türü okur. + CreatePolicyName = PlatformConsts.AppCodes.DataSource + ".Create"; + UpdatePolicyName = PlatformConsts.AppCodes.DataSource + ".Update"; + DeletePolicyName = PlatformConsts.AppCodes.DataSource + ".Delete"; + } + + protected override async Task MapToGetOutputDtoAsync(DataSource entity) + => WithoutSecret(await base.MapToGetOutputDtoAsync(entity)); + + protected override async Task MapToGetListOutputDtoAsync(DataSource entity) + => WithoutSecret(await base.MapToGetListOutputDtoAsync(entity)); + + // Bağlantı cümlesi veritabanı kimlik bilgisi taşır; hiçbir istemci ekranı onu okumaz. + private static DataSourceDto WithoutSecret(DataSourceDto dto) + { + dto.ConnectionString = null; + return dto; } } diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointAppService.cs index 1c83d58b..5bb6250c 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointAppService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Sozsoft.Platform.Domain.DeveloperKit; @@ -12,6 +13,11 @@ using Volo.Abp.Domain.Repositories; namespace Sozsoft.Platform.Application; +/// +/// Üretilmiş CRUD uçları ham tabloya yazar; bu yüzden giriş zorunludur ve veri isteği yapanın +/// kiracısıyla sınırlıdır (). +/// +[Authorize] [RemoteService] [Route("api/app/crudendpoint")] public class CrudEndpointAppService : ApplicationService diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs index 176db496..a7fe954f 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CrudEndpointGenerateAppService.cs @@ -203,7 +203,7 @@ public class CrudEndpointGenerateAppService : CrudAppService< } /// - /// Entity'nin guncel endpoint'lerini {host|tenants/{tenantId}}/crud/{EntityName}.json + /// Entity'nin guncel endpoint'lerini crud/{EntityName}.json /// dosyasina yazar; endpoint kalmamissa dosyayi siler. Dosya islemi hatasi CRUD islemini /// engellemez, yalnizca loglanir. /// @@ -261,12 +261,12 @@ public class CrudEndpointGenerateAppService : CrudAppService< } /// - /// CRUD seed dosyalarinin yazilacagi klasor; kapsam aktif tenant'i izler - /// (host baglaminda host/crud, tenant baglaminda tenants/{tenantId}/crud). + /// CRUD seed dosyalarinin yazilacagi klasor (crud/); host ve kendi veritabani olan her + /// tenant ayni dosyalarla kurulur. /// private string ResolveCrudSeedOutputPath() { - return SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id, SeedPathResolver.CrudFolder); + return SeedPathResolver.GetPath(_configuration, SeedPathResolver.CrudFolder); } /// Entity adindan guvenli bir dosya adi uretir; path traversal denemelerini reddeder. diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs index 2c991593..9d503ee2 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs @@ -10,14 +10,13 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.Logging; using Sozsoft.Platform.DeveloperKit; -using Volo.Abp.DependencyInjection; namespace Sozsoft.Platform.DynamicServices; /// /// Dynamic C# kod derleme servisi /// -public class DynamicServiceCompiler : ITransientDependency +public class DynamicServiceCompiler { private readonly ILogger _logger; diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs index 3ad70b48..622be341 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using OpenIddict.Abstractions; using Sozsoft.Platform.BlobStoring; +using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Extensions; using Sozsoft.Platform.Identity.Dto; @@ -79,6 +80,8 @@ public class PlatformIdentityAppService : ApplicationService this.guidGenerator = guidGenerator; } + // Kullanıcı detay ekranı (Users listesindeki Detail komutu) bu yetkiyle açılır. + [Authorize(IdentityPermissions.Users.Update)] public async Task GetByIdAsync(Guid UserId) { var user = await identityUserRepository.GetAsync(UserId); @@ -187,6 +190,7 @@ public class PlatformIdentityAppService : ApplicationService }.MapIdentityExtraProperties(user); } + [Authorize(IdentityPermissions.Users.ManageRoles)] public async Task UpdatePermissionAsync(UserInfoViewModel UserInfo) { var roles = UserInfo.Roles.Where(r => r.IsAssigned).Select(r => r.Name).ToArray(); @@ -202,10 +206,15 @@ public class PlatformIdentityAppService : ApplicationService } // 2. Yeni atamaları ekle - var assignedBranchIds = UserInfo.Branches + // Branch kiracı filtresi taşımaz: istemciden gelen kimlikler kiracının kendi şubelerine süzülür. + var requestedBranchIds = UserInfo.Branches .Where(b => b.IsAssigned) .Select(b => b.Id) .ToList(); + var assignedBranchIds = (await branchRepository.GetListAsync( + b => requestedBranchIds.Contains(b.Id) && b.TenantId == CurrentTenant.Id)) + .Select(b => b.Id) + .ToList(); foreach (var branchId in assignedBranchIds) { @@ -218,6 +227,7 @@ public class PlatformIdentityAppService : ApplicationService } } + [Authorize(IdentityPermissions.Users.Update)] public async Task UpdateLockoutAsync(UserInfoViewModel UserInfo) { var user = await UserManager.GetByIdAsync(UserInfo.Id); @@ -255,6 +265,7 @@ public class PlatformIdentityAppService : ApplicationService await UserManager.UpdateAsync(user); } + [Authorize(IdentityPermissions.Users.Update)] public async Task UpdateUserAsync(UserInfoViewModel UserInfo) { // 4857 s. Is Kanunu md. 15: deneme suresi en fazla iki ay, toplu sozlesmeyle dort ay. @@ -464,6 +475,8 @@ public class PlatformIdentityAppService : ApplicationService return [.. list.OrderBy(p => p.Name)]; } + // Listelerdeki "Yetkili kullanıcılar" menüsü de bu yetkiyle görünür (useFilters). + [Authorize(PlatformConsts.AppCodes.Listforms.Listform + ".Update")] public async Task> GetPermissionGrantedUsersAsync(string permissionName) { if (string.IsNullOrWhiteSpace(permissionName)) @@ -576,6 +589,7 @@ public class PlatformIdentityAppService : ApplicationService }).ToList(); } + [Authorize(IdentityPermissions.Users.Update)] public async Task CreateClaimUserAsync(UserClaimModel input) { var user = await identityUserRepository.GetAsync(input.UserId); @@ -583,6 +597,7 @@ public class PlatformIdentityAppService : ApplicationService user.AddClaim(guidGenerator, new Claim(input.ClaimType, input.ClaimValue)); } + [Authorize(IdentityPermissions.Users.Update)] public async Task DeleteClaimUser(Guid id, Guid userId) { var user = await identityUserRepository.GetAsync(userId); @@ -591,8 +606,12 @@ public class PlatformIdentityAppService : ApplicationService user.Claims.Remove(claim); } + [Authorize(SeedConsts.AppCodes.IdentityManagement.Sessions)] public async Task KickUserAsync(Guid userId) { + // OpenIddict token'ları kiracıya göre süzülmez; kullanıcının bu kiracıda olduğu önce doğrulanır. + _ = await identityUserRepository.GetAsync(userId, includeDetails: false); + using (CurrentTenant.Change(CurrentTenant.Id)) { // 1. AbpSessions temizle diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs index aff71374..9f4de55b 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSeedDataSynchronizer.cs @@ -62,8 +62,9 @@ public enum SeedSyncOperation } /// -/// ListForm.SeedFilePath dolu olan ekranlarin verisi kapsam klasorunde aynalanir; veritabani -/// sifirlandiginda ListFormSeedDataApplier ayni yerden geri yukler. +/// ListForm.SeedFilePath dolu olan ekranlarin verisi seed klasorunde aynalanir; veritabani +/// sifirlandiginda ListFormSeedDataApplier ayni yerden geri yukler. Yalnizca host baglamindaki +/// kayitlar aynalanir; tenant baglamindaki kayit veritabaninda kalir. /// Senkronizasyon satir bazlidir: yalnizca islem goren satir okunup karsiligina yazilir, /// boylece her kayitta listenin tamami sorgulanmaz ve dosyadaki satir sirasi korunur. /// @@ -403,6 +404,11 @@ public class ListFormSeedDataSynchronizer( Func action, bool createTarget = true) { + // Seed dosyalari urunun varsayilanidir ve host ile kendi veritabani olan her tenant'a kurulur; + // bir tenant'in kaydi dosyaya yazilsaydi o tenant'in verisi butun veritabanlarina yayilirdi. + if (currentTenant.Id != null) + return; + try { var listForm = await listFormRepository.FirstOrDefaultAsync(a => a.ListFormCode == listFormCode); @@ -411,10 +417,7 @@ public class ListFormSeedDataSynchronizer( var isFolder = SeedPathResolver.IsSeedFolderPath(listForm.SeedFilePath); - var fullPath = SeedPathResolver.GetScopePath( - configuration, - currentTenant.Id, - listForm.SeedFilePath.Replace('\\', '/').Trim('/').Split('/')); + var fullPath = SeedPathResolver.GetPath(configuration, listForm.SeedFilePath.Replace('\\', '/').Trim('/').Split('/')); // Klasor ilk kayitta olusur; ekran, hedefi diskte hazir bulmak zorunda degildir. if (createTarget) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSelectAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSelectAppService.cs index 7022d6ba..fd2cca84 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormSelectAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormSelectAppService.cs @@ -20,6 +20,8 @@ namespace Sozsoft.Platform.ListForms.Select; [Authorize] public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppService { + private static readonly JsonSerializerOptions CaseInsensitiveJson = new() { PropertyNameCaseInsensitive = true }; + private readonly IRepository listFormRepository; private readonly IRepository listFormFieldRepository; private readonly IRepository listFormWorkflowRepository; @@ -148,6 +150,22 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe //kullaniciya ait ListForm verisini al var listForm = await listFormManager.GetUserListForm(input.ListFormCode); + // "Tüm kayıtları sil" filtreye uyan satırları kalıcı siler: okuma yetkisi yetmez, ekranın + // silme yetkisi ve tasarımda açılmış AllowAllDeleting ayarı birlikte aranır. + if (input.CreateDeleteQuery) + { + var editingJson = listForm.EditingOptionJson; + var editing = string.IsNullOrWhiteSpace(editingJson) + ? null + : JsonSerializer.Deserialize(editingJson, CaseInsensitiveJson); + + if (editing?.AllowAllDeleting != true + || !await authManager.CanAccess(input.ListFormCode, AuthorizationTypeEnum.Delete)) + { + throw new Volo.Abp.UserFriendlyException(L[PlatformConsts.AppErrorCodes.NoAuth]); + } + } + //kullaniciya ait ListFormField verilerini al var fields = await listFormFieldManager.GetUserListFormFields(input.ListFormCode); fields = fields.Where(a => a.CanRead).ToList(); diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index 64a6aaf6..be180ec2 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Sozsoft.Languages; +using Sozsoft.Languages.Caching; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Languages; using Sozsoft.Platform.Data.Seeds; @@ -51,7 +51,7 @@ public class ListFormWizardAppService( IRepository repoModule, IRepository repoDevRequest, IConfiguration configuration, - LanguageTextAppService languageTextAppService, + DistributedCacheCleaner cacheCleaner, IDynamicDataManager dynamicDataManager, IListFormSeedDataApplier seedDataApplier, ICrudEndpointSeedApplier crudEndpointSeedApplier, @@ -72,7 +72,7 @@ public class ListFormWizardAppService( private readonly IRepository repoModule = repoModule; private readonly IRepository repoDevRequest = repoDevRequest; private readonly IConfiguration _configuration = configuration; - private readonly LanguageTextAppService _languageTextAppService = languageTextAppService; + private readonly DistributedCacheCleaner _cacheCleaner = cacheCleaner; private readonly IDynamicDataManager _dynamicDataManager = dynamicDataManager; private readonly IListFormSeedDataApplier _seedDataApplier = seedDataApplier; private readonly ICrudEndpointSeedApplier _crudEndpointSeedApplier = crudEndpointSeedApplier; @@ -1014,7 +1014,7 @@ public class ListFormWizardAppService( } var componentDependencies = fileByComponent.Count > 0 - ? await new WizardExportCollector(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)) + ? await new WizardExportCollector(SeedPathResolver.GetRootPath(_configuration)) .ReadCustomComponentDependenciesAsync() : []; @@ -1356,7 +1356,7 @@ public class ListFormWizardAppService( // Bekleyen wizard'da veritabani degismedigi icin onbellekte gecersiz kalan bir sey yoktur. if (!isPending) - await _languageTextAppService.ClearRedisCacheAsync(); + await _cacheCleaner.ClearAsync(); await _moduleDocManager.RefreshAllAsync(); @@ -1560,26 +1560,25 @@ public class ListFormWizardAppService( } } - /// Aktif kapsamin seed klasoru (host ya da tenants/{tenantId}). + /// Seed klasorunun tam yolu. private string ResolveScopeRoot() { - return Path.GetFullPath(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + return Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); } /// - /// Wizard seed dosyalarinin yazilacagi wizard dizinini dondurur; kapsam aktif tenant'i izler - /// (host baglaminda host/wizard, tenant baglaminda tenants/{tenantId}/wizard). + /// Wizard seed dosyalarinin yazilacagi wizard dizinini (wizard/) dondurur. /// Yol cozumu uzerinden seeder'lar ve File Manager ile ortaktir. /// private string ResolveWizardSeedOutputPath() { - return SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id, SeedPathResolver.WizardFolder); + return SeedPathResolver.GetPath(_configuration, SeedPathResolver.WizardFolder); } /// /// Wizard seed dosyasini ve tasinabilir olmasi icin gereken butun seed dosyalarini tek bir zip - /// icinde dondurur. Zip icindeki yollar seed kapsam klasoru (host / tenants/{tenantId}) koku baz - /// alinarak yazilir; dolayisiyla arsiv baska bir ortamda ayni klasor duzenine dogrudan acilabilir. + /// icinde dondurur. Zip icindeki yollar seed klasorunun koku baz alinarak + /// yazilir; dolayisiyla arsiv baska bir ortamda ayni klasor duzenine dogrudan acilabilir. /// /// Bagimlilik kapanisi ozyinelemelidir (alt formlar, lookup nesneleri, ekran verisi, custom /// component zinciri) — ayrinti icin . Cozulemeyen bir @@ -1678,7 +1677,6 @@ public class ListFormWizardAppService( FileName = fileName, WizardFiles = wizardFiles, ArchiveName = archiveName, - ScopeFolder = SeedPathResolver.GetScopeFolderName(CurrentTenant.Id), Entries = [.. items.Select(x => new WizardExportEntryDto { Path = x.Path, @@ -1695,7 +1693,7 @@ public class ListFormWizardAppService( var seed = WizardSeedFileDto.FromSeedJson(await File.ReadAllTextAsync(filePath)) ?? throw new UserFriendlyException("Dosya okunamadı."); - var collector = new WizardExportCollector(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var collector = new WizardExportCollector(SeedPathResolver.GetRootPath(_configuration)); var items = await collector.CollectAsync([filePath]); return (items, collector.Warnings, ArchiveName(seed.Wizard?.WizardName ?? Path.GetFileNameWithoutExtension(fileName), "Wizard")); @@ -1713,7 +1711,7 @@ public class ListFormWizardAppService( var title = devRequest?.Title ?? files[0].Seed!.DevRequest.Title; var fileNames = files.Select(f => f.FileName).ToList(); - var collector = new WizardExportCollector(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var collector = new WizardExportCollector(SeedPathResolver.GetRootPath(_configuration)); var items = await collector.CollectAsync([.. fileNames.Select(ResolveWizardSeedFilePath)]); return (items, collector.Warnings, ArchiveName(title, "DevRequest"), fileNames); @@ -1757,7 +1755,7 @@ public class ListFormWizardAppService( private const int ImportMaxEntryCount = 500; - /// Kapsam klasorunde import edilmesine izin verilen kok klasorler. + /// Seed klasorunde import edilmesine izin verilen kok klasorler. private static readonly string[] ImportAllowedRootFolders = [ SeedPathResolver.WizardFolder, @@ -1811,12 +1809,11 @@ public class ListFormWizardAppService( var importId = GuidGenerator.Create().ToString("N"); var sessionPath = GetImportSessionPath(importId); var stagedRoot = Path.Combine(sessionPath, ImportStagedFolder); - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); var result = new WizardImportAnalysisDto { ImportId = importId, - ScopeFolder = SeedPathResolver.GetScopeFolderName(CurrentTenant.Id), DataSourceCode = SeedConsts.DataSources.DefaultCode, }; @@ -2165,7 +2162,7 @@ public class ListFormWizardAppService( if (!File.Exists(stagedPath)) throw new UserFriendlyException($"Import oturumunda dosya bulunamadı: {relativePath}"); - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); var targetPath = Path.GetFullPath(Path.Combine(scopeRoot, systemPath)); if (!targetPath.StartsWith(scopeRoot + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) throw new UserFriendlyException("Geçersiz dosya yolu."); @@ -2247,7 +2244,7 @@ public class ListFormWizardAppService( // burada da denetlenmezse bu uc dogrudan cagrilarak analiz karari atlanabilirdi. EnsureImportNotBlocked(sessionPath); - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); var result = new WizardImportCompleteResultDto(); var providerFolder = SeedPathResolver.GetProviderFolderName(DefaultDatabaseProvider == DatabaseProvider.PostgreSql); @@ -2419,7 +2416,7 @@ public class ListFormWizardAppService( } /// - /// Kapsam klasorundeki bir seed scriptini calistirir. Script'ler varlik kontrollu yazilir + /// Seed klasorundeki bir seed scriptini calistirir. Script'ler varlik kontrollu yazilir /// (nesne varsa olusturma atlanir), bu yuzden yeniden calistirilmalari guvenlidir. Hata tek /// script ile sinirli kalir; kalan adimlar yurumeye devam eder ve sonuc kullaniciya raporlanir. /// @@ -2533,7 +2530,7 @@ public class ListFormWizardAppService( { var sessionPath = GetExistingImportSessionPath(importId); var logPath = Path.Combine(sessionPath, ImportAppliedLogFile); - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(_configuration, CurrentTenant.Id)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); if (File.Exists(logPath)) { @@ -2725,9 +2722,10 @@ public class ListFormWizardAppService( } /// - /// Zip girdisini kapsam klasorune goreli yola cevirir. Zip, kapsam klasorunun icinden - /// (wizard/x.json) ya da depo kokunden (configs/seeds/host/wizard/x.json) - /// alinmis olabilir; her iki durumda da izin verilen ilk kok klasorden itibaren okunur. + /// Zip girdisini seed klasorune goreli yola cevirir. Zip, seed klasorunun icinden + /// (wizard/x.json) ya da depo kokunden (configs/seeds/wizard/x.json) alinmis + /// olabilir; eski arsivler host/ onekini de tasir. Her durumda izin verilen ilk kok + /// klasorden itibaren okunur. /// Kural disi bir yol icin null doner. /// private static string? NormalizeImportEntryPath(string? entryPath) @@ -2750,7 +2748,8 @@ public class ListFormWizardAppService( var isDocsFolder = segments[0].Equals(SeedPathResolver.DocsFolder, StringComparison.OrdinalIgnoreCase); - // sql/postgres altinda object|execute klasoru zorunludur; digerlerinde alt klasor yoktur. + // sql/postgres altinda object|execute klasoru zorunludur (yalnizca host'ta calisanlar icin + // host/object|execute); digerlerinde alt klasor yoktur. if (isDocsFolder) { // Yalnizca modul dokumanlari: docs/modules/{Modul}.md. @@ -2759,8 +2758,15 @@ public class ListFormWizardAppService( } else if (isScriptFolder) { - if (segments.Length != 3 || !ImportAllowedScriptFolders.Contains(segments[1], StringComparer.OrdinalIgnoreCase)) + var isHostOnly = segments.Length == 4 + && segments[1].Equals(SeedPathResolver.HostOnlyFolder, StringComparison.OrdinalIgnoreCase); + var scriptFolder = isHostOnly ? segments[2] : segments[1]; + + if (segments.Length != (isHostOnly ? 4 : 3) + || !ImportAllowedScriptFolders.Contains(scriptFolder, StringComparer.OrdinalIgnoreCase)) + { return null; + } } else if (isDataFolder) { @@ -2856,7 +2862,7 @@ public class ListFormWizardAppService( } /// - /// Kapsam klasorune goreli yolu var olan bir dosyanin tam yoluna cevirir; kapsam disina cikan + /// Seed klasorune goreli yolu var olan bir dosyanin tam yoluna cevirir; kapsam disina cikan /// ya da bulunmayan bir yol icin null doner. /// private static string? ResolveScopeFilePath(string scopeRoot, string relativePath) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocCatalog.cs b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocCatalog.cs index 41eedcca..d03304a4 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocCatalog.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocCatalog.cs @@ -10,7 +10,7 @@ using Sozsoft.Platform.Data.Seeds; namespace Sozsoft.Platform.ListForms; /// -/// Bir kapsam klasorundeki wizard dosyalarini ve modul dokumanlarini okur; her wizard'in hangi +/// Bir seed klasorundeki wizard dosyalarini ve modul dokumanlarini okur; her wizard'in hangi /// dokumana ait oldugunu cozer. Kural, wizard sozlesmesine alan eklemeden menu agacindan cikar: /// /// Bir dokuman ekranin kodunu basligindaki beyanla sahiplenmisse (module-doc: claims=…) o dokuman. diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs index 785ba675..9a9b5207 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging; using Sozsoft.Platform.Data.Seeds; using Volo.Abp; using Volo.Abp.DependencyInjection; -using Volo.Abp.MultiTenancy; namespace Sozsoft.Platform.ListForms; @@ -20,12 +19,11 @@ namespace Sozsoft.Platform.ListForms; /// public class ModuleDocManager( IConfiguration configuration, - ICurrentTenant currentTenant, ILogger logger) : ITransientDependency { private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); - private string ScopeRoot => Path.GetFullPath(SeedPathResolver.GetScopePath(configuration, currentTenant.Id)); + private string ScopeRoot => Path.GetFullPath(SeedPathResolver.GetRootPath(configuration)); public Task LoadCatalogAsync() => ModuleDocCatalog.LoadAsync(ScopeRoot); diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs index ec532ad2..fc05569e 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs @@ -461,7 +461,7 @@ public sealed class WizardExportCollector(string scopeRoot) } /// - /// Kapsamdaki script klasorlerinin kapsam-goreli yollari (sql/object, postgres/execute …), + /// Script klasorlerinin seed-goreli yollari (sql/object, postgres/execute, sql/host/object …), /// diskte gercekten var olanlar. Bir kez taranir; kapsam kokunun disina cikilmaz. /// private IReadOnlyList ScriptFolders => scriptFolders ??= ScanScriptFolders(); @@ -486,7 +486,21 @@ public sealed class WizardExportCollector(string scopeRoot) } foreach (var scriptPath in SafeEnumerateDirectories(providerPath)) - folders.Add($"{provider}/{System.IO.Path.GetFileName(scriptPath)}"); + { + var scriptFolder = System.IO.Path.GetFileName(scriptPath); + + // {sql|postgres}/host yalnizca host'ta calisan scriptlerin kabidir; scriptler bir + // seviye altta, kendi object/execute klasorlerindedir. + if (scriptFolder.Equals(SeedPathResolver.HostOnlyFolder, StringComparison.OrdinalIgnoreCase)) + { + foreach (var hostScriptPath in SafeEnumerateDirectories(scriptPath)) + folders.Add($"{provider}/{scriptFolder}/{System.IO.Path.GetFileName(hostScriptPath)}"); + + continue; + } + + folders.Add($"{provider}/{scriptFolder}"); + } } return folders; diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs index 0995ccaf..a0c74386 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardRecordCleaner.cs @@ -12,7 +12,6 @@ using Sozsoft.Platform.Enums; using Sozsoft.Platform.Extensions; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; -using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement; using Volo.Abp.TenantManagement; @@ -58,7 +57,6 @@ public class WizardRecordCleaner( ITenantRepository tenantRepository, IListFormSeedDataSynchronizer seedDataSynchronizer, IConfiguration configuration, - ICurrentTenant currentTenant, ILogger logger) : ITransientDependency { /// Raporda bir kaydi kullananlarin en fazla bu kadari listelenir. @@ -192,7 +190,7 @@ public class WizardRecordCleaner( return; } - var path = SeedPathResolver.GetScopePath(configuration, currentTenant.Id, relativePath.Split('/')); + var path = SeedPathResolver.GetPath(configuration, relativePath.Split('/')); if (SeedPathResolver.IsSeedFolderPath(relativePath)) { if (!Directory.Exists(path)) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs index e1b9af9c..f6f4fb3e 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs @@ -28,7 +28,7 @@ public sealed class WizardSeedRowStore public static readonly string PermissionFile = SeedConsts.AbpIdentity.Permissions.Default; public static readonly string LanguageTextFile = PlatformConsts.AppCodes.Languages.LanguageText; - // Sira, kapsam klasorundeki mevcut dosyalarla aynidir: once tanim, sonra ona referans veren satir. + // Sira, seed klasorundeki mevcut dosyalarla aynidir: once tanim, sonra ona referans veren satir. private static readonly FileSpec[] Specs = [ new(ModuleFile, "Id", 2), @@ -48,7 +48,7 @@ public sealed class WizardSeedRowStore { } - /// Kapsam klasorundeki dosyalari okur; olmayan dosya bos olarak baslar ve ilk yazimda olusur. + /// Seed klasorundeki dosyalari okur; olmayan dosya bos olarak baslar ve ilk yazimda olusur. public static async Task LoadAsync(string scopeRoot) { var store = new WizardSeedRowStore(); diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedSynchronizer.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedSynchronizer.cs index fb04abc3..9f7b646b 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedSynchronizer.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedSynchronizer.cs @@ -44,7 +44,9 @@ public class WizardSeedSynchronizer( public async Task SyncAsync(string listFormCode) { - if (string.IsNullOrWhiteSpace(listFormCode)) + // Wizard dosyasi host ile kendi veritabani olan her tenant'a kurulur; tenant'in ekran + // duzenlemesi dosyaya yazilsaydi butun veritabanlarina yayilirdi. + if (string.IsNullOrWhiteSpace(listFormCode) || currentTenant.Id != null) return; try @@ -89,13 +91,13 @@ public class WizardSeedSynchronizer( } /// - /// Kapsam klasorundeki wizard dosyalari icinde bu ListForm koduna ait olani bulur. Dosya adi + /// Seed klasorundeki wizard dosyalari icinde bu ListForm koduna ait olani bulur. Dosya adi /// wizard adiyla ayni oldugu icin once dogrudan ad denenir; eski ya da elle adlandirilmis /// dosyalar icin klasor taranir. /// private async Task<(string FilePath, WizardSeedFileDto Seed)?> FindSeedFileAsync(string listFormCode) { - var wizardPath = SeedPathResolver.GetScopePath(configuration, currentTenant.Id, SeedPathResolver.WizardFolder); + var wizardPath = SeedPathResolver.GetPath(configuration, SeedPathResolver.WizardFolder); if (!Directory.Exists(wizardPath)) return null; diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs index 6be25802..1af44803 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs @@ -12,7 +12,6 @@ using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; -using Volo.Abp.MultiTenancy; namespace Sozsoft.Platform.ListForms; @@ -38,7 +37,6 @@ public class WizardTableCleaner( IRepository repoCrudEndpoint, IDynamicDataManager dynamicDataManager, IConfiguration configuration, - ICurrentTenant currentTenant, ILogger logger) : ITransientDependency { private const int MaxUsedBy = 20; @@ -353,7 +351,7 @@ public class WizardTableCleaner( // ── Yardimcilar ──────────────────────────────────────────────────────────────── private string ScopeFile(string relativePath) => - SeedPathResolver.GetScopePath(configuration, currentTenant.Id, relativePath.Split('/')); + SeedPathResolver.GetPath(configuration, relativePath.Split('/')); /// Sema yazilmamissa null doner; varsayilan sema saglayiciya gore 'ta atanir. private static (string? SchemaName, string TableName) ParseTableName(string selectCommand) diff --git a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs index 0420b1f1..22848cd9 100644 --- a/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Menu/MenuAppService.cs @@ -5,7 +5,7 @@ using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; -using Sozsoft.Languages; +using Sozsoft.Languages.Caching; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Languages; using Sozsoft.Platform.Entities; @@ -32,7 +32,7 @@ public class MenuAppService : CrudAppService< private readonly IRepository _repositoryText; private readonly ITenantRepository _tenantRepository; private readonly IPermissionDefinitionRecordRepository _permissionRepository; - private readonly LanguageTextAppService _languageTextAppService; + private readonly DistributedCacheCleaner _cacheCleaner; private readonly IRepository _moduleRepository; private readonly IListFormSeedDataSynchronizer _seedDataSynchronizer; @@ -42,7 +42,7 @@ public class MenuAppService : CrudAppService< IRepository languageTextRepository, ITenantRepository tenantRepository, IPermissionDefinitionRecordRepository permissionRepository, - LanguageTextAppService languageTextAppService, + DistributedCacheCleaner cacheCleaner, IRepository moduleRepository, IListFormSeedDataSynchronizer seedDataSynchronizer ) : base(menuRepository) @@ -52,7 +52,7 @@ public class MenuAppService : CrudAppService< _repositoryText = languageTextRepository; _tenantRepository = tenantRepository; _permissionRepository = permissionRepository; - _languageTextAppService = languageTextAppService; + _cacheCleaner = cacheCleaner; _moduleRepository = moduleRepository; _seedDataSynchronizer = seedDataSynchronizer; @@ -379,7 +379,7 @@ public class MenuAppService : CrudAppService< } } - await _languageTextAppService.ClearRedisCacheAsync(); + await _cacheCleaner.ClearAsync(); // Dil metinleri de kendi ekraninin seed dosyasinda yasar; menu adini runtime'da // degistirmek DB sifirlandiginda geri gelmeli. diff --git a/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs b/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs index e2b8fcfa..ed0928ad 100644 --- a/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Tenants/PlatformTenantAppService.cs @@ -2,27 +2,21 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Sozsoft.Platform.Extensions; -using Sozsoft.Platform.ListForms.DynamicApi; using Sozsoft.Platform.Localization; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Entities; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; -using Volo.Abp.Identity; using Volo.Abp.TenantManagement; namespace Sozsoft.Platform.Tenants; [Authorize(TenantManagementPermissions.Tenants.Default)] [Dependency(ReplaceServices = true)] -public class PlatformTenantAppService : TenantAppService, IPlatformTenantAppService +public class PlatformTenantAppService : TenantAppService { - private readonly ITenantRepository tenantRepository; - private readonly IDistributedEventBus distributedEventBus; - public PlatformTenantAppService( ITenantRepository tenantRepository, ITenantManager tenantManager, @@ -30,9 +24,6 @@ public class PlatformTenantAppService : TenantAppService, IPlatformTenantAppServ IDistributedEventBus distributedEventBus, ILocalEventBus localEventBus) : base(tenantRepository, tenantManager, dataSeeder, distributedEventBus, localEventBus) { - this.tenantRepository = tenantRepository; - this.distributedEventBus = distributedEventBus; - LocalizationResource = typeof(PlatformResource); } @@ -93,23 +84,5 @@ public class PlatformTenantAppService : TenantAppService, IPlatformTenantAppServ return dto; } - - [Authorize(TenantManagementPermissions.Tenants.Update)] - public async Task PostSeedTenantDataAsync(CreateUpdateTenantInput input) - { - var tenant = (input.Id.HasValue ? await tenantRepository.FindAsync(input.Id.Value) : null) ?? throw new EntityNotFoundException(); - await distributedEventBus.PublishAsync( - new ApplyDatabaseMigrationsEto - { - TenantId = tenant.Id, - DatabaseName = tenant.Name, - Properties = - { - { IdentityDataSeedContributor.AdminEmailPropertyName, input.AdminEmail }, - { IdentityDataSeedContributor.AdminPasswordPropertyName, input.AdminPassword }, - { IdentityDataSeedContributor.AdminUserNamePropertyName, input.AdminEmail } - } - }); - } } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/CrudDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/CrudDataSeeder.cs index 0eb258ae..204c2214 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/CrudDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/CrudDataSeeder.cs @@ -6,7 +6,7 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// SqlQueryManager uzerinden uretilen CRUD endpoint'lerini -/// {host|tenants/{tenantId}}/crud/*.json dosyalarindan okuyarak veritabanina uygular. +/// crud/*.json dosyalarindan okuyarak veritabanina uygular. /// /// tablolari olusturduktan hemen sonra calisir; boylece endpoint'ler /// her zaman dayandiklari tablolar hazir olduktan sonra yazilir. ABP'nin contributor sirasi diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/FinalSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/FinalSeeder.cs index bcd27628..3b003cd2 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/FinalSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/FinalSeeder.cs @@ -10,18 +10,19 @@ using Microsoft.Extensions.Logging; using Sozsoft.Platform.EntityFrameworkCore; using Volo.Abp.DependencyInjection; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.MultiTenancy; using Volo.Abp.Uow; using static Sozsoft.Settings.SettingsConsts; namespace Sozsoft.Platform.Data.Seeds; /// -/// Seeds/{host|tenants/{tenantId}}/{sql|postgres}/execute klasöründeki .sql dosyaları -/// iki aşamada işlenir: -/// 1) Migration/seed sırasında tarafından, kapsam klasörüyle -/// (host/tenants/{tenantId}) aynı bağlamda çalıştırılır (nesne oluşturulur/güncellenir). +/// Seeds/{sql|postgres}/execute klasöründeki .sql dosyaları iki aşamada işlenir: +/// 1) Migration/seed sırasında tarafından çalıştırılır (nesne +/// oluşturulur/güncellenir). /// 2) Tüm migration ve seed işlemleri bittikten sonra bu sınıf, dosya adından türetilen stored -/// procedure'ü ilgili bağlamda (host ya da tenant) varlığı doğrulanarak çalıştırır. +/// procedure'ü çağıranın seçtiği bağlamda (host ya da kendi veritabanı olan tenant) varlığı +/// doğrulanarak çalıştırır. {sql|postgres}/host/execute yalnızca host bağlamında işlenir. /// Dosya adı = procedure adı. İsteğe bağlı olarak "schema.Procedure.sql" formatı da desteklenir. /// public class FinalSeeder : IFinalSeeder, ITransientDependency @@ -39,44 +40,39 @@ public class FinalSeeder : IFinalSeeder, ITransientDependency private readonly IDbContextProvider _dbContextProvider; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConfiguration _configuration; + private readonly ICurrentTenant _currentTenant; private readonly ILogger _logger; public FinalSeeder( IDbContextProvider dbContextProvider, IUnitOfWorkManager unitOfWorkManager, IConfiguration configuration, + ICurrentTenant currentTenant, ILogger logger) { _dbContextProvider = dbContextProvider; _unitOfWorkManager = unitOfWorkManager; _configuration = configuration; + _currentTenant = currentTenant; _logger = logger; } - public async Task ExecuteAsync(Guid? tenantId = null) + public async Task ExecuteAsync() { - var directoryPath = GetScriptDirectoryPath(tenantId); - - if (!Directory.Exists(directoryPath)) - { - _logger.LogInformation("{Directory} directory not found, skipping FinalSeeder.", directoryPath); - return; - } - - var sqlFiles = Directory.GetFiles(directoryPath, "*.sql") - .OrderBy(f => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase) + // Ortak procedure'ler once, host'a ozgu olanlar (host/execute) yalnizca host baglaminda sonra. + var sqlFiles = GetScriptDirectoryPaths() + .Where(Directory.Exists) + .SelectMany(path => Directory.GetFiles(path, "*.sql") + .OrderBy(f => Path.GetFileName(f), StringComparer.OrdinalIgnoreCase)) .ToArray(); if (sqlFiles.Length == 0) { - _logger.LogInformation("No .sql files found in {Directory}, skipping FinalSeeder.", directoryPath); + _logger.LogInformation("No execute script found, skipping FinalSeeder."); return; } - _logger.LogInformation( - "FinalSeeder started for {Scope}. {Count} procedure(s) to be executed.", - tenantId == null ? "Host" : $"Tenant {tenantId}", - sqlFiles.Length); + _logger.LogInformation("FinalSeeder started. {Count} procedure(s) to be executed.", sqlFiles.Length); var failed = new List(); @@ -133,12 +129,15 @@ public class FinalSeeder : IFinalSeeder, ITransientDependency } } - private string GetScriptDirectoryPath(Guid? tenantId) + private IEnumerable GetScriptDirectoryPaths() { var providerDirectoryName = SeedPathResolver.GetProviderFolderName( DefaultDatabaseProvider == DatabaseProvider.PostgreSql); - return SeedPathResolver.GetScopePath(_configuration, tenantId, providerDirectoryName, DirectoryName); + yield return SeedPathResolver.GetPath(_configuration, providerDirectoryName, DirectoryName); + + if (_currentTenant.Id == null) + yield return SeedPathResolver.GetPath(_configuration, providerDirectoryName, SeedPathResolver.HostOnlyFolder, DirectoryName); } /// diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostDataSeeder.cs index ee835a6a..2e773928 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostDataSeeder.cs @@ -14,7 +14,7 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// Varsayilan veri kaynagini olusturur. Baglanti cumlesi ortamin appsettings dosyasindan gelir; -/// bu yuzden diger host verisi gibi configs/seeds/host/data altina yazilamaz. +/// bu yuzden diger platform verisi gibi configs/seeds/data altina yazilamaz. /// public class HostDataSeeder : IDataSeedContributor, ITransientDependency { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs index 8f547288..bf610f02 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs @@ -40,7 +40,6 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep { var configurationSection = _configuration.GetSection("OpenIddict:Applications"); var swaggerRootUrl = configurationSection["Platform_Swagger:RootUrl"]?.TrimEnd('/'); - var utils = new ListFormSeeder_Utils(_listFormRepository, _listFormFieldRepository); var listFormName = String.Empty; #region Permission Group diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs index e2cdf305..4efb4a8e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs @@ -5,14 +5,12 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Localization; using Sozsoft.Languages.Languages; using Sozsoft.Mcp; using Sozsoft.Platform.Editors; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms; -using Sozsoft.Platform.Localization; using Sozsoft.Platform.Permissions; using Sozsoft.Platform.Queries; using Volo.Abp.Data; @@ -31,28 +29,24 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency private readonly IRepository _listFormFieldRepository; private readonly IRepository _listFormWorkflowRepository; private readonly IConfiguration _configuration; - protected IStringLocalizer _localizer { get; } public ListFormSeeder_Saas( IRepository listFormRepository, IRepository listFormFieldRepository, IRepository listFormWorkflowRepository, - IConfiguration configuration, - IStringLocalizer localizer + IConfiguration configuration ) { _listFormRepository = listFormRepository; _listFormFieldRepository = listFormFieldRepository; _listFormWorkflowRepository = listFormWorkflowRepository; _configuration = configuration; - _localizer = localizer; } public async Task SeedAsync(DataSeedContext context) { var configurationSection = _configuration.GetSection("OpenIddict:Applications"); var swaggerRootUrl = configurationSection["Platform_Swagger:RootUrl"]?.TrimEnd('/'); - var utils = new ListFormSeeder_Utils(_listFormRepository, _listFormFieldRepository); var listFormName = String.Empty; #region Tenant diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Utils.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Utils.cs deleted file mode 100644 index 75136872..00000000 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Utils.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Sozsoft.Platform.Entities; -using Sozsoft.Platform.Enums; -using Sozsoft.Platform.ListForms; -using Volo.Abp.Domain.Repositories; - -namespace Sozsoft.Platform.Data.Seeds; - -public class ListFormSeeder_Utils -{ - private readonly IRepository _listFormRepository; - private readonly IRepository _listFormFieldRepository; - - public ListFormSeeder_Utils( - IRepository listFormRepository, - IRepository listFormFieldRepository) - { - _listFormRepository = listFormRepository; - _listFormFieldRepository = listFormFieldRepository; - } - - public static string GetDefaultFormCodes(string listFormCode) => "form-" + listFormCode; - - public async Task CloneFormLayoutAsync(string listFormCode, string subFormsJson) - { - var newListFormCode = GetDefaultFormCodes(listFormCode); - var cloneForm = await CloneListFormWithFieldsAsync(listFormCode, newListFormCode); - - if (cloneForm != null) - { - await SetSubFormsJsonAsync(cloneForm, subFormsJson); - } - else - { - Console.WriteLine($"ListForm with code {listFormCode} not found!"); - } - } - - public async Task CloneListFormWithFieldsAsync(string listFormCode, string newListFormCode, bool copyFormFields = true) - { - var listForm = await _listFormRepository.FirstOrDefaultAsync(f => f.ListFormCode == listFormCode); - if (listForm == null) - { - Console.WriteLine($"ListForm with code {listFormCode} not found!"); - - return null; - } - - var targetForm = await _listFormRepository.FirstOrDefaultAsync(f => f.ListFormCode == newListFormCode); - if (targetForm == null) - { - var insertForm = new ListForm - { - ListFormCode = newListFormCode, - SubFormsJson = listForm.SubFormsJson, - WidgetsJson = listForm.WidgetsJson, - WorkflowJson = listForm.WorkflowJson, - ListFormType = listForm.ListFormType, - IsSubForm = listForm.IsSubForm, - ShowNote = listForm.ShowNote, - LayoutJson = listForm.LayoutJson, - CultureName = listForm.CultureName, - Name = listForm.Name, - Title = listForm.Title, - DataSourceCode = listForm.DataSourceCode, - IsTenant = listForm.IsTenant, - IsBranch = listForm.IsBranch, - IsOrganizationUnit = listForm.IsOrganizationUnit, - Description = listForm.Description, - SelectCommandType = listForm.SelectCommandType, - SelectCommand = listForm.SelectCommand, - KeyFieldName = listForm.KeyFieldName, - KeyFieldDbSourceType = listForm.KeyFieldDbSourceType, - DefaultFilter = listForm.DefaultFilter, - SortMode = listForm.SortMode, - PermissionJson = listForm.PermissionJson, - DeleteCommand = listForm.DeleteCommand, - DeleteFieldsDefaultValueJson = listForm.DeleteFieldsDefaultValueJson, - EditingOptionJson = listForm.EditingOptionJson, - EditingFormJson = listForm.EditingFormJson, - InsertFieldsDefaultValueJson = listForm.InsertFieldsDefaultValueJson, - FormFieldsDefaultValueJson = listForm.FormFieldsDefaultValueJson, - CommandColumnJson = listForm.CommandColumnJson, - InsertServiceAddress = listForm.InsertServiceAddress, - UpdateServiceAddress = listForm.UpdateServiceAddress, - FilterRowJson = listForm.FilterRowJson, - HeaderFilterJson = listForm.HeaderFilterJson, - SearchPanelJson = listForm.SearchPanelJson, - GroupPanelJson = listForm.GroupPanelJson, - SelectionJson = listForm.SelectionJson, - ColumnOptionJson = listForm.ColumnOptionJson, - PagerOptionJson = listForm.PagerOptionJson, - }; - - // insert cloned form - targetForm = await _listFormRepository.InsertAsync(insertForm, autoSave: true); - } - - if (copyFormFields) - { - var targetFormFields = await _listFormFieldRepository.GetListAsync(f => f.ListFormCode == listFormCode); - if (targetFormFields != null && targetFormFields.Count > 0) - { - var clonedFields = new List(); - foreach (var f in targetFormFields) - { - var newField = new ListFormField - { - ListFormCode = newListFormCode, - CultureName = f.CultureName, - SourceDbType = f.SourceDbType, - FieldName = f.FieldName, - Width = f.Width, - ListOrderNo = f.ListOrderNo, - Visible = f.Visible, - IsActive = f.IsActive, - AllowSearch = f.AllowSearch, - LookupJson = f.LookupJson, - ColumnCustomizationJson = f.ColumnCustomizationJson, - PermissionJson = f.PermissionJson, - PivotSettingsJson = f.PivotSettingsJson, - ValidationRuleJson = f.ValidationRuleJson, - EditorOptions = f.EditorOptions, - SortIndex = f.SortIndex, - SortDirection = f.SortDirection, - }; - clonedFields.Add(newField); - } - - await _listFormFieldRepository.InsertManyAsync(clonedFields, autoSave: true); - } - } - - return targetForm; - } - - private async Task SetSubFormsJsonAsync(ListForm listForm, string subFormsJson) - { - listForm.ListFormType = ListFormTypeEnum.Form; - listForm.SubFormsJson = subFormsJson; - await _listFormRepository.UpdateAsync(listForm, autoSave: true); - - //Eksik olan form alanlarını ekle - var subForms = JsonSerializer.Deserialize>(subFormsJson); - if (subForms != null) - { - foreach (var subForm in subForms) - { - foreach (var sf in subForm.Relation) - { - var parentFieldName = sf.ParentFieldName; - var dbType = sf.DbType; - - await InsertListFormFieldAsync( - listForm.ListFormCode, - parentFieldName, - dbType - ); - - var childListFormCode = subForm.Code; - var childFieldName = sf.ChildFieldName; - - await InsertListFormFieldAsync( - childListFormCode, - childFieldName, - dbType - ); - } - } - } - } - - private async Task InsertListFormFieldAsync(string listFormCode, string fieldName, DbType dbType) - { - var form = await _listFormRepository.FirstOrDefaultAsync(f => f.ListFormCode == listFormCode); - var fieldQuery = await _listFormFieldRepository.GetQueryableAsync(); - - var formField = await fieldQuery.FirstOrDefaultAsync(f => f.ListFormCode == listFormCode && f.FieldName == fieldName); - if (formField == null) - { - var listOrderNo = await fieldQuery.Where(f => f.ListFormCode == listFormCode).MaxAsync(a => a.ListOrderNo) ?? 0; - var field = new ListFormField - { - CultureName = PlatformConsts.DefaultLanguage, - ListFormCode = listFormCode, - SourceDbType = dbType, - FieldName = fieldName, - ListOrderNo = listOrderNo + 1, - Visible = false, - IsActive = true, - SortIndex = 0, - PermissionJson = JsonSerializer.Serialize(new ListFormFieldPermissionDto - { - C = form.Name + ".Create", - R = form.Name, - U = form.Name + ".Update", - E = true, - I = false, - Deny = false - }), - }; - - await _listFormFieldRepository.InsertAsync(field, autoSave: true); - } - } -} diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PlatformIdentityDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PlatformIdentityDataSeeder.cs index 44a45891..f757ff5b 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PlatformIdentityDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PlatformIdentityDataSeeder.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; @@ -7,6 +8,7 @@ using Sozsoft.Platform.Entities; using Sozsoft.Platform.Extensions; using Sozsoft.Platform.Permissions; using Volo.Abp; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; using Volo.Abp.Guids; @@ -14,6 +16,7 @@ using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.PermissionManagement; using Volo.Abp.TenantManagement; +using Volo.Abp.Uow; using IdentityRole = Volo.Abp.Identity.IdentityRole; using IdentityUser = Volo.Abp.Identity.IdentityUser; @@ -23,6 +26,8 @@ namespace Sozsoft.Platform.Data.Seeds; public class PlatformIdentityDataSeeder : IdentityDataSeeder { private readonly IPermissionGrantRepository _permissionGrantRepository; + private readonly IPermissionDefinitionRecordRepository _permissionDefinitionRecordRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly ITenantRepository _tenantRepository; private readonly IRepository _branchRepository; private readonly IRepository _branchUsersRepository; @@ -38,6 +43,8 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder IdentityUserManager userManager, IdentityRoleManager roleManager, IPermissionGrantRepository permissionGrantRepository, + IPermissionDefinitionRecordRepository permissionDefinitionRecordRepository, + IUnitOfWorkManager unitOfWorkManager, ICurrentTenant currentTenant, ITenantRepository tenantRepository, IConfiguration configuration, @@ -45,6 +52,8 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder ) : base(guidGenerator, roleRepository, userRepository, lookupNormalizer, userManager, roleManager, currentTenant, identityOptions) { this._permissionGrantRepository = permissionGrantRepository; + this._permissionDefinitionRecordRepository = permissionDefinitionRecordRepository; + this._unitOfWorkManager = unitOfWorkManager; this._tenantRepository = tenantRepository; this._branchRepository = branchRepository; this._branchUsersRepository = branchUsersRepository; @@ -159,6 +168,13 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder adminUser.SetAvatar(GetAvatar(tenantId?.ToString(), adminUser.Id.ToString())); adminUser.SetUserType(PlatformConsts.AbpIdentity.User.InternalUserTypeDefaultValue); adminUser.SetGender(PlatformConsts.AbpIdentity.User.AdminGenderDefaultValue); + adminUser.SetMilitaryStatus(PlatformConsts.AbpIdentity.User.AdminMilitaryStatusDefaultValue); + adminUser.SetEmploymentType(PlatformConsts.AbpIdentity.User.AdminEmploymentTypeDefaultValue); + adminUser.SetContractType(PlatformConsts.AbpIdentity.User.AdminContractTypeDefaultValue); + adminUser.SetSalaryType(PlatformConsts.AbpIdentity.User.AdminSalaryTypeDefaultValue); + // İzin hakkı ve kıdem hesabı bu iki tarihten yürür; yönetici kurulum günü işe başlamış sayılır. + adminUser.SetHireDate(DateTime.Today); + adminUser.SetSeniorityStartDate(DateTime.Today); (await UserManager.CreateAsync(adminUser, adminPassword, validatePassword: false)).CheckErrors(); result.CreatedAdminUser = true; @@ -180,7 +196,11 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder /*admin rolüne ait yetkiler otomatik atanıyor*/ var permissions = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName); - if (permissions.Count == 0) + if (permissions.Count == 0 && tenantId != null) + { + await GrantTenantPermissionsAsync(tenantId.Value); + } + else if (permissions.Count == 0) { await _permissionGrantRepository.InsertManyAsync( [ @@ -229,6 +249,43 @@ public class PlatformIdentityDataSeeder : IdentityDataSeeder } } + /// + /// Yeni kiracının yöneticisi kiracı tarafına açık bütün yetkilerle başlar; yoksa menüdeki hiçbir + /// ekranı göremez ve rol ekranından yetki verecek kimse kalmaz. Yetkilerin çoğu seed dosyasından + /// gelen dinamik tanımlardır ve DbMigrator'ın yetki tanımı yöneticisinde yoktur + /// (IsDynamicPermissionStoreEnabled yalnızca API'dedir); bu yüzden ABP'nin + /// PermissionDataSeedContributor'ı onları atayamaz, liste host'taki tanım tablosundan okunur. + /// Yalnızca yöneticinin hiç yetkisi yokken çalışır: sonradan deploy edilen ekranların yetkileri + /// migrate ile kendiliğinden atanmaz, rol ekranından verilir. + /// + private async Task GrantTenantPermissionsAsync(Guid tenantId) + { + string[] permissionNames; + + // Tanım tablosu host'tadır. Okuma, kiracının atamalarıyla aynı unit of work'te yapıldığında + // atamalar tenant veritabanı yerine host'a yazıldı; okuma bu yüzden kendi unit of work'ündedir. + using (CurrentTenant.Change(null)) + using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + var records = await _permissionDefinitionRecordRepository.GetListAsync(); + permissionNames = [.. records + .Where(a => a.MultiTenancySide.HasFlag(MultiTenancySides.Tenant)) + .Where(a => string.IsNullOrWhiteSpace(a.Providers) + || a.Providers.Split(',', StringSplitOptions.TrimEntries).Contains(RolePermissionValueProvider.ProviderName)) + .Select(a => a.Name)]; + await uow.CompleteAsync(); + } + + await _permissionGrantRepository.InsertManyAsync( + permissionNames.Select(name => new PermissionGrant( + GuidGenerator.Create(), + name, + RolePermissionValueProvider.ProviderName, + PlatformConsts.AbpIdentity.User.AdminRoleName, + tenantId)), + autoSave: true); + } + private string GetAvatar(string? tenantId, string? userId) { var tenantPart = !string.IsNullOrEmpty(tenantId) diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/SqlDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/SqlDataSeeder.cs index 94f8de12..e367a9db 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/SqlDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/SqlDataSeeder.cs @@ -17,23 +17,19 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// SqlTableDesigner ve SqlQueryManager üzerinden deploy edilen nesneleri -/// {host|tenants/{tenantId}}/{sql|postgres} klasöründeki .sql dosyalarından okuyarak -/// veritabanına uygular. +/// {sql|postgres} klasöründeki .sql dosyalarından okuyarak veritabanına uygular; host'ta ve +/// kendi veritabanı olan her tenant'ta aynı dosyalar çalışır (). /// Her dosya, tek bir nesneye (ve ilgili ALTER/INDEX script'lerine) ait varlık kontrolü içeren /// batch'lerden oluşmalıdır. Veritabanı silinip yeniden oluşturulduğunda tüm scriptler yeniden çalışır. /// /// Klasör düzeni — her .sql dosyası amacını belirten bir klasörün altındadır, kökte dosya durmaz: /// -/// host/ → host (varsayılan) veritabanı kapsamı -/// {sql|postgres}/ -/// object/ → nesne oluşturulur/güncellenir. SqlTableDesigner ve -/// SqlQueryManager, host bağlamındayken buraya yazar. -/// execute/ → nesne oluşturulur, ayrıca tüm migration'lar bittikten sonra +/// {sql|postgres}/ +/// object/ → nesne oluşturulur/güncellenir. SqlTableDesigner ve +/// SqlQueryManager buraya yazar. +/// execute/ → nesne oluşturulur, ayrıca tüm migration'lar bittikten sonra /// tarafından çalıştırılır. -/// tenants/{tenantId}/ → ilgili tenant veritabanı kapsamı -/// {sql|postgres}/ -/// object/ -/// execute/ +/// host/{object,execute}/ → aynı kurallar, yalnızca host veritabanında. /// /// Geriye dönük uyumluluk: klasör köklerinde kalmış eski .sql dosyaları da işlenir, ancak /// uyarı ile loglanır — taşınmaları beklenir. @@ -68,21 +64,24 @@ public class SqlDataSeeder : IDataSeedContributor, ITransientDependency /// public async Task SeedAsync(DataSeedContext context) { + if (!SeedFileScope.IsEnabled(context)) + return; + await SeedSqlObjectsAsync(context); await _crudDataSeeder.SeedAsync(context); } private async Task SeedSqlObjectsAsync(DataSeedContext context) { - var dataDirectoryName = $"{SeedPathResolver.GetScopeFolderName(context.TenantId)}/{GetProviderDirectoryName()}"; - var sqlDataPath = SeedPathResolver.GetScopePath(_configuration, context.TenantId, GetProviderDirectoryName()); + var dataDirectoryName = GetProviderDirectoryName(); + var sqlDataPath = SeedPathResolver.GetPath(_configuration, dataDirectoryName); if (!Directory.Exists(sqlDataPath)) { _logger.LogInformation("Seeds/{DirectoryName} directory not found, skipping SqlDataSeeder.", dataDirectoryName); return; } - var sqlFiles = GetSqlFiles(sqlDataPath); + var sqlFiles = GetSqlFiles(sqlDataPath, includeHostOnly: context.TenantId == null); if (sqlFiles.Length == 0) { @@ -151,20 +150,32 @@ public class SqlDataSeeder : IDataSeedContributor, ITransientDependency return SeedPathResolver.GetProviderFolderName(DefaultDatabaseProvider == DatabaseProvider.PostgreSql); } - private string[] GetSqlFiles(string providerDirectoryPath) + /// + /// Sira: ortak nesneler, host'a ozgu nesneler, ardindan execute scriptleri. Host'a ozgu script + /// ortak tablolara dayanabilir; execute scriptleri de nesnelerin kurulmus olmasini bekler. + /// + private string[] GetSqlFiles(string providerDirectoryPath, bool includeHostOnly) { var sqlFiles = new List(); + var hostOnlyPath = Path.Combine(providerDirectoryPath, SeedPathResolver.HostOnlyFolder); // Klasor kokunde dosya beklenmez; eskiden kalanlar uyari ile islenir. sqlFiles.AddRange(GetLegacyRootSqlFiles(providerDirectoryPath)); sqlFiles.AddRange(GetOrderedSqlFiles(Path.Combine(providerDirectoryPath, ObjectDirectoryName))); + // {sql|postgres}/host: tenant veritabaninda karsiligi olmayan nesnelere (HangFire) dayanir. + if (includeHostOnly) + sqlFiles.AddRange(GetOrderedSqlFiles(Path.Combine(hostOnlyPath, ObjectDirectoryName))); + // {sql|postgres}/execute: nesneler burada da oluşturulur, ayrıca tüm migration // bittikten sonra FinalSeeder dosya adı üzerinden çalıştırır. sqlFiles.AddRange(GetOrderedSqlFiles( Path.Combine(providerDirectoryPath, FinalSeeder.DirectoryName))); + if (includeHostOnly) + sqlFiles.AddRange(GetOrderedSqlFiles(Path.Combine(hostOnlyPath, FinalSeeder.DirectoryName))); + return sqlFiles.ToArray(); } diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs index 766f3939..cc57903f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/WizardDataSeeder.cs @@ -18,8 +18,8 @@ using Volo.Abp.PermissionManagement; namespace Sozsoft.Platform.Data.Seeds; /// -/// Wizard ile uretilen ekranlari Seeds/{host|tenants/{tenantId}}/wizard/*.json dosyalarindan kurar -/// (kapsam, seed edilen veritabanini izler). +/// Wizard ile uretilen ekranlari Seeds/wizard/*.json dosyalarindan kurar; host'ta ve kendi +/// veritabani olan her tenant'ta ayni dosyalar kullanilir (). /// /// Deploy veritabanina yazmaz; bu seeder wizard'in kurulumunun tek yeridir. Dosya yalnizca ekrana /// ozgu kayitlari tasir: veri kaynagi, ListForm, alanlar ve is akisi. Yetkiler hicbir role atanmaz; @@ -53,18 +53,20 @@ public class WizardDataSeeder( public async Task SeedAsync(DataSeedContext context) { - var scopeFolderName = SeedPathResolver.GetScopeFolderName(context.TenantId); - var wizardDataPath = SeedPathResolver.GetScopePath(configuration, context.TenantId, SeedPathResolver.WizardFolder); + if (!SeedFileScope.IsEnabled(context)) + return; + + var wizardDataPath = SeedPathResolver.GetPath(configuration, SeedPathResolver.WizardFolder); if (!Directory.Exists(wizardDataPath)) { - logger.LogInformation("Seeds/{Scope}/wizard directory not found, skipping.", scopeFolderName); + logger.LogInformation("Seeds/wizard directory not found, skipping."); return; } var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json"); if (jsonFiles.Length == 0) { - logger.LogInformation("No JSON files found in Seeds/{Scope}/wizard directory, skipping.", scopeFolderName); + logger.LogInformation("No JSON files found in Seeds/wizard directory, skipping."); return; } @@ -126,7 +128,7 @@ public class WizardDataSeeder( await InstallAsync(seedFile); - if (seedFile.Pending != null) + if (seedFile.Pending != null && SeedFileScope.CompletesPending(context)) { seedFile.Pending = null; await File.WriteAllTextAsync(filePath, seedFile.ToSeedJson()); diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/WizardImportEntryStatusEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/WizardImportEntryStatusEnum.cs index 8be1bcfe..f0b5b66f 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/WizardImportEntryStatusEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/WizardImportEntryStatusEnum.cs @@ -1,6 +1,6 @@ namespace Sozsoft.Platform.Enums; -/// Import edilen zip girdisinin hedef kapsam klasorundeki dosyayla karsilastirma sonucu. +/// Import edilen zip girdisinin hedef seed klasorundeki dosyayla karsilastirma sonucu. public enum WizardImportEntryStatusEnum { /// Hedefte ayni yolda dosya yok; dogrudan kopyalanir. diff --git a/api/src/Sozsoft.Platform.Domain.Shared/ListForms/IListFormArtifact.cs b/api/src/Sozsoft.Platform.Domain.Shared/ListForms/IListFormArtifact.cs index 5b3d1889..64bd42cf 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/ListForms/IListFormArtifact.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/ListForms/IListFormArtifact.cs @@ -51,7 +51,7 @@ public interface IListFormArtifact string DeleteBeforeCommand { get; set; } string DeleteAfterCommand { get; set; } - /// Kapsam klasorune gore goreli seed dosyasi yolu; dolu ise liste verisi bu dosyayla senkron tutulur. + /// Seed klasorune gore goreli seed dosyasi yolu; dolu ise liste verisi bu dosyayla senkron tutulur. string SeedFilePath { get; set; } /// Ekrandan eklenen kayit seed dosyasina da eklenir. diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 8efd67f3..660f85a4 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -289,7 +289,12 @@ public static class PlatformConsts public static readonly DateTime AdminBirthDateDefaultValue = new(1981, 8, 27); public const string AdminBirthPlaceDefaultValue = "Kadıköy, İstanbul"; public const string AdminMaritalStatusDefaultValue = "Evli"; - public const string AdminGenderDefaultValue = "Male"; + public const string AdminGenderDefaultValue = GenderMale; + // Değerler Users ekranındaki statik lookup anahtarlarıdır (ListFormSeeder_Administration). + public const string AdminMilitaryStatusDefaultValue = "Done"; + public const string AdminEmploymentTypeDefaultValue = "FullTime"; + public const string AdminContractTypeDefaultValue = "Indefinite"; + public const string AdminSalaryTypeDefaultValue = "Monthly"; // Cinsiyet dilden bagimsiz anahtarla saklanir; ekranda App.StaticLookup.* ile cevrilir. public const string GenderMale = "Male"; diff --git a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs index 53eb0e75..c6c7c49b 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs @@ -24,7 +24,7 @@ public static class WizardConsts /// Liste verisinin aynalandigi seed klasoru; SeedPathResolver ile ayni deger. public const string SeedDataFolder = "data"; - /// Ekranin varsayilan veri seed dosyasi, kapsam klasorune gore goreli. + /// Ekranin varsayilan veri seed dosyasi, seed klasorune gore goreli. public static string SeedDataFilePath(string listFormCode) => $"{SeedDataFolder}/{listFormCode}.json"; public static string MenuUrl(string code) => $"/admin/list/{code}"; diff --git a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs index f9cd07c8..0bd2a751 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs @@ -18,15 +18,15 @@ namespace Sozsoft.Platform.Data.Seeds; public record CrudEndpointSeedApplyResult(string Path, int Inserted, int Updated, int Deleted, int Skipped, string Error = null); /// -/// {host|tenants/{tenantId}}/crud/{EntityName}.json dosyalarini veritabanina uygular. +/// crud/{EntityName}.json dosyalarini verilen tenant baglamindaki veritabanina uygular. /// public interface ICrudEndpointSeedApplier { - /// Kapsamdaki butun CRUD seed dosyalarini uygular (migrate/seed akisi). + /// Butun CRUD seed dosyalarini uygular (migrate/seed akisi). Task> ApplyScopeAsync(Guid? tenantId); /// - /// Kapsam klasorune goreli belirli dosyalari uygular; import gibi dosya bazli akislar butun + /// Seed klasorune goreli belirli dosyalari uygular; import gibi dosya bazli akislar butun /// klasoru taramak yerine yalnizca yazilan dosyalari uygular. /// Task> ApplyFilesAsync(Guid? tenantId, IReadOnlyCollection relativePaths); @@ -58,23 +58,22 @@ public class CrudEndpointSeedApplier( public async Task> ApplyScopeAsync(Guid? tenantId) { - var scopeFolderName = SeedPathResolver.GetScopeFolderName(tenantId); - var crudPath = SeedPathResolver.GetScopePath(configuration, tenantId, SeedPathResolver.CrudFolder); + var crudPath = SeedPathResolver.GetPath(configuration, SeedPathResolver.CrudFolder); if (!Directory.Exists(crudPath)) { - logger.LogInformation("Seeds/{Scope}/crud directory not found, skipping CrudEndpointSeedApplier.", scopeFolderName); + logger.LogInformation("Seeds/crud directory not found, skipping CrudEndpointSeedApplier."); return []; } var jsonFiles = Directory.GetFiles(crudPath, "*.json").OrderBy(Path.GetFileName, StringComparer.OrdinalIgnoreCase).ToArray(); if (jsonFiles.Length == 0) { - logger.LogInformation("No JSON files found in Seeds/{Scope}/crud directory, skipping CrudEndpointSeedApplier.", scopeFolderName); + logger.LogInformation("No JSON files found in Seeds/crud directory, skipping CrudEndpointSeedApplier."); return []; } - logger.LogInformation("CrudEndpointSeedApplier started for {Scope}. {Count} file(s) to be processed.", scopeFolderName, jsonFiles.Length); + logger.LogInformation("CrudEndpointSeedApplier started. {Count} file(s) to be processed.", jsonFiles.Length); return await ApplyAsync(tenantId, jsonFiles); } @@ -84,7 +83,7 @@ public class CrudEndpointSeedApplier( if (relativePaths == null || relativePaths.Count == 0) return []; - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(configuration, tenantId)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(configuration)); var results = new List(); var filePaths = new List(); diff --git a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs index cf77afb8..82b5d1da 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs @@ -5,7 +5,7 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// Bir entity icin uretilen CRUD endpoint'lerinin seed dosyasi. -/// SqlQueryManager endpoint uretince {host|tenants/{tenantId}}/crud/{EntityName}.json +/// SqlQueryManager endpoint uretince crud/{EntityName}.json /// olarak yazilir; veritabani silinip yeniden olusturuldugunda /// CrudDataSeeder ayni dosyalari okuyarak endpoint'leri geri yukler. /// diff --git a/api/src/Sozsoft.Platform.Domain/Data/CustomComponentSeedDataSource.cs b/api/src/Sozsoft.Platform.Domain/Data/CustomComponentSeedDataSource.cs index ba1db17c..823357f7 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CustomComponentSeedDataSource.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CustomComponentSeedDataSource.cs @@ -3,7 +3,7 @@ /// /// Component'in kullandigi tek bir API. dolu ise endpoint /// CRUD Endpoint Manager tarafindan uretilmistir ve tanimi -/// (kapsam klasorune gore crud/{EntityName}.json) dosyasindadir. +/// (seed klasorune gore crud/{EntityName}.json) dosyasindadir. /// public class CustomComponentSeedDataSourceDto { @@ -23,6 +23,6 @@ public class CustomComponentSeedDataSourceDto /// CRUD endpoint'inin islem turu (GetAll, GetById, Create, Update, Delete). public string? OperationType { get; set; } - /// Endpoint tanimini tasiyan seed dosyasinin kapsam klasorune gore yolu. + /// Endpoint tanimini tasiyan seed dosyasinin seed klasorune gore yolu. public string? SeedFile { get; set; } } diff --git a/api/src/Sozsoft.Platform.Domain/Data/IFinalSeeder.cs b/api/src/Sozsoft.Platform.Domain/Data/IFinalSeeder.cs index ffbe1c7a..03dd6ff2 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/IFinalSeeder.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/IFinalSeeder.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Sozsoft.Platform.Data; @@ -9,8 +8,8 @@ namespace Sozsoft.Platform.Data; /// public interface IFinalSeeder { - /// - /// null ise host/, aksi halde tenants/{tenantId}/ kapsamındaki {sql|postgres}/execute klasörü işlenir. - /// - Task ExecuteAsync(Guid? tenantId = null); + /// + /// {sql|postgres}/execute klasörünü geçerli bağlamın (host ya da tenant) veritabanında işler. + /// + Task ExecuteAsync(); } diff --git a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs index 42eeded4..7126f5bd 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataApplier.cs @@ -23,7 +23,7 @@ using Volo.Abp.Uow; namespace Sozsoft.Platform.Data.Seeds; /// -/// Kapsam klasorundeki data/*.json dosyalarini okuyup satirlari ilgili ListForm'un +/// Seed klasorundeki data/*.json dosyalarini okuyup satirlari ilgili ListForm'un /// tablosuna yazar. /// public interface IListFormSeedDataApplier @@ -32,7 +32,7 @@ public interface IListFormSeedDataApplier Task ApplyAsync(Guid? tenantId); /// - /// Kapsam klasorune goreli belirli veri seed dosyalarini uygular; import gibi dosya bazli + /// Seed klasorune goreli belirli veri seed dosyalarini uygular; import gibi dosya bazli /// akislar butun klasoru taramak yerine yalnizca yazilan dosyalari uygular. Dosyalar /// ApplyAsync ile ayni kurala gore siralanir. /// @@ -105,7 +105,7 @@ public class ListFormSeedDataApplier( if (relativePaths == null || relativePaths.Count == 0) return []; - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(configuration, tenantId)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(configuration)); var results = new List(); var filePaths = new List(); @@ -143,12 +143,11 @@ public class ListFormSeedDataApplier( public async Task ApplyAsync(Guid? tenantId) { - var scopeFolderName = SeedPathResolver.GetScopeFolderName(tenantId); - var dataPath = SeedPathResolver.GetScopePath(configuration, tenantId, SeedPathResolver.DataFolder); + var dataPath = SeedPathResolver.GetPath(configuration, SeedPathResolver.DataFolder); if (!Directory.Exists(dataPath)) { - logger.LogInformation("Seeds/{Scope}/data directory not found, skipping ListFormSeedDataApplier.", scopeFolderName); + logger.LogInformation("Seeds/data directory not found, skipping ListFormSeedDataApplier."); return; } @@ -159,13 +158,13 @@ public class ListFormSeedDataApplier( var jsonFiles = Directory.GetFiles(dataPath, "*.json", SearchOption.AllDirectories); if (jsonFiles.Length == 0) { - logger.LogInformation("No JSON files found in Seeds/{Scope}/data directory, skipping ListFormSeedDataApplier.", scopeFolderName); + logger.LogInformation("No JSON files found in Seeds/data directory, skipping ListFormSeedDataApplier."); return; } - logger.LogInformation("ListFormSeedDataApplier started for {Scope}. {Count} file(s) to be processed.", scopeFolderName, jsonFiles.Length); + logger.LogInformation("ListFormSeedDataApplier started. {Count} file(s) to be processed.", jsonFiles.Length); - var scopeRoot = Path.GetFullPath(SeedPathResolver.GetScopePath(configuration, tenantId)); + var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(configuration)); using (currentTenant.Change(tenantId)) { @@ -179,7 +178,7 @@ public class ListFormSeedDataApplier( } } - logger.LogInformation("ListFormSeedDataApplier completed for {Scope}.", scopeFolderName); + logger.LogInformation("ListFormSeedDataApplier completed."); } /// @@ -249,7 +248,7 @@ public class ListFormSeedDataApplier( /// /// Tek seferde uygulanan bir ekranin dosyalari: tek dosya ya da satir klasorundeki butun satir - /// dosyalari. kapsam klasorune goreli hedeftir (dosya ya da klasor) ve + /// dosyalari. seed klasorune goreli hedeftir (dosya ya da klasor) ve /// sonuclarda, loglarda bu gorunur. /// private sealed record SeedDataTarget(string Path, string[] FilePaths); diff --git a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs index 346d67c0..4b10362c 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs @@ -7,7 +7,7 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// Bir ListForm ekranindan (Grid, Tree, TodoBoard vb.) girilen verinin seed dosyasi: -/// {host|tenants/{tenantId}}/data/{ListFormCode}.json. +/// data/{ListFormCode}.json. /// /// Ekrandaki her insert/update/delete sonrasi ilgili satir dosyaya islenir; veritabani /// sifirlandiginda ListFormSeedDataApplier ayni dosyayi okuyup satirlari geri yazar. diff --git a/api/src/Sozsoft.Platform.Domain/Data/NotificationTenantDefaultsSeedContributor.cs b/api/src/Sozsoft.Platform.Domain/Data/NotificationTenantDefaultsSeedContributor.cs index e70f859d..857f25ee 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/NotificationTenantDefaultsSeedContributor.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/NotificationTenantDefaultsSeedContributor.cs @@ -15,8 +15,9 @@ namespace Sozsoft.Platform.Data.Seeds; /// /// Bildirim tipleri ve varsayilan kurallari kiraci kapsamindadir: her kiraci kendi turunu ve -/// kuralini duzenler. Host kapsamindaki seed dosyalari varsayilan kumeyi tanimlar, bu contributor -/// da yeni kiraciya o kumenin bir kopyasini uretir. +/// kuralini duzenler. Seed dosyalari varsayilan kumeyi tanimlar; host ve kendi veritabani olan +/// kiraci onu dosyadan alir, bu contributor da host veritabanini paylasan yeni kiraciya o kumenin +/// bir kopyasini uretir. /// /// Kopya satirlar yeni kimliklerle yazilir; dosyadaki kimlik host kaydinindir ve tum kiracilarda /// tekrar edilemez. Eslesme ad uzerinden yapilir, bu yuzden ayni kiraci icin ikinci kez @@ -34,9 +35,10 @@ public class NotificationTenantDefaultsSeedContributor( public async Task SeedAsync(DataSeedContext context) { - // Host kapsami kendi dosyalarini ListFormSeedDataApplier ile uygular; burada yalnizca - // kiraci kopyasi uretilir. - if (context.TenantId == null) + // Seed dosyalarinin uygulandigi veritabani (host ve kendi veritabani olan kiraci) bu satirlari + // ListFormSeedDataApplier ile alir; burada yalnizca host veritabanini paylasan kiracinin kopyasi + // uretilir. Iki yol birden calissaydi ayri veritabanli kiracida tipler iki kez yazilirdi. + if (SeedFileScope.IsEnabled(context)) { return; } @@ -120,14 +122,13 @@ public class NotificationTenantDefaultsSeedContributor( } /// - /// Varsayilan kumeyi host kapsamindaki veri seed dosyasindan okur; ikinci bir varsayilan + /// Varsayilan kumeyi veri seed dosyasindan okur; ikinci bir varsayilan /// listesi kodda tutulmaz. Dosya yoksa kiraci bos baslar, bu bir hata degildir. /// private List ReadRows(string listFormCode) { - var path = SeedPathResolver.GetScopePath( + var path = SeedPathResolver.GetPath( configuration, - null, SeedPathResolver.GetDataFilePath(listFormCode).Split('/')); if (!File.Exists(path)) diff --git a/api/src/Sozsoft.Platform.Domain/Data/PlatformDbMigrationService.cs b/api/src/Sozsoft.Platform.Domain/Data/PlatformDbMigrationService.cs index 7310a178..669abdc6 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/PlatformDbMigrationService.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/PlatformDbMigrationService.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Sozsoft.Languages.Caching; using Sozsoft.Platform.Data.Seeds; -using StackExchange.Redis; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; @@ -29,6 +26,7 @@ public class PlatformDbMigrationService : ITransientDependency private readonly IConfiguration _configuration; private readonly IEnumerable _finalSeeders; private readonly IListFormSeedDataApplier _listFormSeedDataApplier; + private readonly DistributedCacheCleaner _cacheCleaner; public PlatformDbMigrationService( IDataSeeder dataSeeder, @@ -37,7 +35,8 @@ public class PlatformDbMigrationService : ITransientDependency ICurrentTenant currentTenant, IConfiguration configuration, IEnumerable finalSeeders, - IListFormSeedDataApplier listFormSeedDataApplier) + IListFormSeedDataApplier listFormSeedDataApplier, + DistributedCacheCleaner cacheCleaner) { _dataSeeder = dataSeeder; _dbSchemaMigrators = dbSchemaMigrators; @@ -46,6 +45,7 @@ public class PlatformDbMigrationService : ITransientDependency _configuration = configuration; _finalSeeders = finalSeeders; _listFormSeedDataApplier = listFormSeedDataApplier; + _cacheCleaner = cacheCleaner; Logger = NullLogger.Instance; } @@ -55,27 +55,32 @@ public class PlatformDbMigrationService : ITransientDependency var seed = _configuration.GetValue("Seed", false); Logger.LogInformation($"Migrating database. Seed: {seed}. Env: {Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}"); - ClearCache(); + await ClearCacheAsync(); - var initialMigrationAdded = AddInitialMigrationIfNotExist(); - - if (initialMigrationAdded) + var tenantSeedId = _configuration.GetValue(TenantSeedOptions.TenantIdKey); + if (tenantSeedId.HasValue) { + await SeedSingleTenantAsync(tenantSeedId.Value); return; } Logger.LogInformation("Started database migrations..."); await MigrateDatabaseSchemaAsync(); + + var tenants = await _tenantRepository.GetListAsync(includeDetails: true); + + // Bekleyen wizard kurulumu dosyaları uygulayan son veritabanında kapanır; host'ta kapansaydı + // ekranın eski sürümü kurulu olan ayrı veritabanlı tenant onu "kurulu" sayıp güncellemezdi. + var lastSeedFileTenant = tenants.LastOrDefault(SeedFileScope.AppliesTo); + if (seed) { - await SeedDataAsync(); + await SeedDataAsync(completesPending: lastSeedFileTenant == null); } Logger.LogInformation($"Successfully completed host database migrations."); - var tenants = await _tenantRepository.GetListAsync(includeDetails: true); - var migratedDatabaseSchemas = new HashSet(); foreach (var tenant in tenants) { @@ -96,7 +101,7 @@ public class PlatformDbMigrationService : ITransientDependency } if (seed) { - await SeedDataAsync(tenant); + await SeedDataAsync(tenant, completesPending: tenant == lastSeedFileTenant); } } @@ -105,29 +110,68 @@ public class PlatformDbMigrationService : ITransientDependency Logger.LogInformation("Successfully completed all database migrations."); - await ExecuteFinalSeedersAsync(tenants); + await ExecuteFinalSeedersAsync(tenants, includeHost: true); // Seed; menu, yetki ve dil metni ekler. Calisan uygulama bu arada eski halleri onbellege // almis olabilir (ornegin wizard'in yeni menusu); migrate sonrasinda da temizlenir. if (seed) - ClearCache(); + await ClearCacheAsync(); Logger.LogInformation("You can safely end this process..."); } - private void ClearCache() + /// + /// Tenants ekranının "Seed" sekmesi: yalnızca verilen tenant'ın şemasını ve seed'ini + /// tam migrate'le aynı adımlarla çalıştırır. Yönetici bilgisi bu tenant için ekrandan gelir; + /// tam migrate'teki varsayılan yönetici kullanılmaz. + /// + private async Task SeedSingleTenantAsync(Guid tenantId) { - using var redis = ConnectionMultiplexer.Connect(_configuration["Redis:Configuration"]!); - redis.GetDatabase().Execute("FLUSHALL"); + var tenant = await _tenantRepository.FindAsync(tenantId, includeDetails: true) + ?? throw new InvalidOperationException($"Tenant not found: {tenantId}"); + + var adminEmail = _configuration[TenantSeedOptions.AdminEmailKey]; + var adminPassword = _configuration[TenantSeedOptions.AdminPasswordKey]; + if (string.IsNullOrWhiteSpace(adminEmail) || string.IsNullOrWhiteSpace(adminPassword)) + { + throw new InvalidOperationException("Admin email and password are required for tenant seed."); + } + + Logger.LogInformation("Started {TenantName} tenant migration and seed...", tenant.Name); + + using (_currentTenant.Change(tenant.Id)) + { + if (tenant.ConnectionStrings.Any()) + { + await MigrateDatabaseSchemaAsync(tenant); + } + + // Host ve diğer tenant'lar bu çalışmada kurulmadığı için bekleyen wizard kurulumu açık kalır. + await SeedDataAsync(tenant, completesPending: false, adminEmail, adminPassword); + } + + Logger.LogInformation("Successfully completed {TenantName} tenant database migrations.", tenant.Name); + + await ExecuteFinalSeedersAsync([tenant], includeHost: false); + + await ClearCacheAsync(); + + Logger.LogInformation("You can safely end this process..."); + } + + private async Task ClearCacheAsync() + { + var removed = await _cacheCleaner.ClearAsync(); + Logger.LogInformation("Distributed cache cleared. {Count} key(s) removed.", removed); } /// /// Tüm host ve tenant migration/seed adımları bittikten sonra, - /// {host|tenants/{tenantId}}/{sql|postgres}/execute klasöründeki script'lerin dosya adından - /// türeyen procedure'lerini ilgili bağlamda çalıştırır. İlgili tenant klasörü yoksa - /// o tenant adımı sessizce atlanır. + /// {sql|postgres}/execute klasöründeki script'lerin dosya adından türeyen procedure'lerini + /// host'ta ve kendi veritabanı olan her tenant'ta çalıştırır. Host veritabanını paylaşan tenant + /// atlanır; procedure o veritabanında host adımında zaten çalışmıştır. /// - private async Task ExecuteFinalSeedersAsync(List tenants) + private async Task ExecuteFinalSeedersAsync(List tenants, bool includeHost) { if (!_finalSeeders.Any()) { @@ -136,20 +180,24 @@ public class PlatformDbMigrationService : ITransientDependency } Logger.LogInformation( - "Executing SQL scripts for host and {TenantCount} tenant(s)...", + "Executing SQL scripts for {Host}{TenantCount} tenant(s)...", + includeHost ? "host and " : string.Empty, tenants.Count); - using (_currentTenant.Change(null)) + if (includeHost) { - foreach (var executor in _finalSeeders) + using (_currentTenant.Change(null)) { - Logger.LogInformation("Running SQL scripts for host..."); - await executor.ExecuteAsync(); - Logger.LogInformation("Completed SQL scripts for host."); + foreach (var executor in _finalSeeders) + { + Logger.LogInformation("Running SQL scripts for host..."); + await executor.ExecuteAsync(); + Logger.LogInformation("Completed SQL scripts for host."); + } } } - foreach (var tenant in tenants) + foreach (var tenant in tenants.Where(SeedFileScope.AppliesTo)) { using (_currentTenant.Change(tenant.Id)) { @@ -158,7 +206,7 @@ public class PlatformDbMigrationService : ITransientDependency Logger.LogInformation( "Running SQL scripts for {TenantName} tenant...", tenant.Name); - await executor.ExecuteAsync(tenant.Id); + await executor.ExecuteAsync(); Logger.LogInformation( "Completed SQL scripts for {TenantName} tenant.", tenant.Name); @@ -180,129 +228,34 @@ public class PlatformDbMigrationService : ITransientDependency } } - private async Task SeedDataAsync(Tenant? tenant = null) + private async Task SeedDataAsync( + Tenant? tenant = null, + bool completesPending = true, + string adminEmail = PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue, + string adminPassword = PlatformConsts.AbpIdentity.User.AdminPasswordDefaultValue) { Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); + var applySeedFiles = SeedFileScope.AppliesTo(tenant); + if (!applySeedFiles) + { + Logger.LogInformation("{TenantName} tenant shares the host database; seed files are already applied there.", tenant!.Name); + } + await _dataSeeder.SeedAsync(new DataSeedContext(tenant?.Id) - .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue) - .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, PlatformConsts.AbpIdentity.User.AdminPasswordDefaultValue) - .WithProperty(IdentityDataSeedContributor.AdminUserNamePropertyName, PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue) + .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, adminEmail) + .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, adminPassword) + .WithProperty(IdentityDataSeedContributor.AdminUserNamePropertyName, adminEmail) + .WithProperty(SeedFileScope.PropertyName, applySeedFiles) + .WithProperty(SeedFileScope.CompletesPendingPropertyName, completesPending) ); // Veri seed dosyalari ListForm tanimlarina ve tablolara dayandigi icin butun // contributor'lar bittikten sonra uygulanir; ABP contributor sirasi garanti degildir. - await _listFormSeedDataApplier.ApplyAsync(tenant?.Id); - } - - private bool AddInitialMigrationIfNotExist() - { - try + if (applySeedFiles) { - if (!DbMigrationsProjectExists()) - { - return false; - } + await _listFormSeedDataApplier.ApplyAsync(tenant?.Id); } - catch (Exception) - { - return false; - } - - try - { - if (!MigrationsFolderExists()) - { - AddInitialMigration(); - return true; - } - else - { - return false; - } - } - catch (Exception e) - { - Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message); - return false; - } - } - - private bool DbMigrationsProjectExists() - { - var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath(); - - return dbMigrationsProjectFolder != null; - } - - private bool MigrationsFolderExists() - { - var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath(); - return dbMigrationsProjectFolder != null && Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations")); - } - - private void AddInitialMigration() - { - Logger.LogInformation("Creating initial migration..."); - - string argumentPrefix; - string fileName; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - argumentPrefix = "-c"; - fileName = "/bin/bash"; - } - else - { - argumentPrefix = "/C"; - fileName = "cmd.exe"; - } - - var procStartInfo = new ProcessStartInfo(fileName, - $"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\"\"" - ); - - try - { - Process.Start(procStartInfo); - } - catch (Exception) - { - throw new Exception("Couldn't run ABP CLI..."); - } - } - - private string? GetEntityFrameworkCoreProjectFolderPath() - { - var slnDirectoryPath = GetSolutionDirectoryPath(); - - if (slnDirectoryPath == null) - { - throw new Exception("Solution folder not found!"); - } - - var srcDirectoryPath = Path.Combine(slnDirectoryPath, "src"); - - return Directory.GetDirectories(srcDirectoryPath) - .FirstOrDefault(d => d.EndsWith(".EntityFrameworkCore")); - } - - private string? GetSolutionDirectoryPath() - { - var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); - - while (currentDirectory != null && Directory.GetParent(currentDirectory.FullName) != null) - { - currentDirectory = Directory.GetParent(currentDirectory.FullName); - - if (currentDirectory != null && Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null) - { - return currentDirectory.FullName; - } - } - - return null; } } diff --git a/api/src/Sozsoft.Platform.Domain/Data/SeedFileScope.cs b/api/src/Sozsoft.Platform.Domain/Data/SeedFileScope.cs new file mode 100644 index 00000000..2f214966 --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain/Data/SeedFileScope.cs @@ -0,0 +1,40 @@ +using System.Linq; +using Volo.Abp.Data; +using Volo.Abp.TenantManagement; + +namespace Sozsoft.Platform.Data.Seeds; + +/// +/// Seed klasorundeki dosyalarin (sql, crud, wizard, data, execute) bir seed calismasinda uygulanip +/// uygulanmayacagi. Host her zaman uygular; tenant yalnizca kendi veritabani varsa uygular. Baglanti +/// cumlesi olmayan tenant host veritabanini paylasir ve dosyalar o veritabanina host seed'inde zaten +/// yazilmistir; tekrar uygulamak toplu procedure'leri bosuna calistirir ve anahtarsiz satirlari +/// tenant kimligiyle kopyalar. +/// +public static class SeedFileScope +{ + /// uzerinde kararin tasindigi ozellik. + public const string PropertyName = "Sozsoft.ApplySeedFiles"; + + /// Tenant icin karar: kendi veritabanini gosteren bir baglanti cumlesi var mi. + public static bool AppliesTo(Tenant? tenant) => + tenant == null || tenant.ConnectionStrings.Any(a => !string.IsNullOrWhiteSpace(a.Value)); + + /// + /// Seed contributor'lari karari baglamdan okur. Ozellik yoksa (ornegin tenant olusturulurken + /// uygulamanin calistirdigi seed) yalnizca host uygular. + /// + public static bool IsEnabled(DataSeedContext context) => + context.TenantId == null || context[PropertyName] is true; + + /// + /// Bekleyen wizard kurulumunun (Pending) bu calismada dosyadan silinip silinmeyecegini + /// tasiyan ozellik. Ayni dosyalar birden cok veritabanina kuruldugu icin bayrak, kurulumu bekleyen + /// son veritabani da kurulduktan sonra kalkar. + /// + public const string CompletesPendingPropertyName = "Sozsoft.CompletesPendingWizards"; + + /// Ozellik yoksa tek veritabanli calisma sayilir ve bayrak silinir. + public static bool CompletesPending(DataSeedContext context) => + context[CompletesPendingPropertyName] is not false; +} diff --git a/api/src/Sozsoft.Platform.Domain/Data/SeedPathResolver.cs b/api/src/Sozsoft.Platform.Domain/Data/SeedPathResolver.cs index 1c9df61f..11cfa56d 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/SeedPathResolver.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/SeedPathResolver.cs @@ -18,22 +18,25 @@ namespace Sozsoft.Platform.Data.Seeds; /// uzerinden de duzenlenebildigi icin yapilan degisiklik dogrudan host diskinde kalici olur. /// /// -/// Klasor duzeni CDN ile ayni kapsam mantigini izler — once kapsam, sonra icerik turu: +/// Tek klasor vardir; host ve kendi veritabani olan her tenant ayni dosyalarla kurulur. Tek bir +/// PlatformDbContext oldugu icin ayri veritabanli tenant ekran tanimlarini, menuleri ve yetkileri +/// kendi veritabanindan okur; tenant'a ozel bir seed kapsami yoktur. Paylasilan (host) veritabanindaki +/// tenant dosyalari yeniden uygulamaz (). Dosyalar urunun varsayilanidir; +/// yalnizca host baglaminda yazilir, tenant'in verisi dosyaya yansimaz. /// /// seeds/ -/// host/ → host (varsayilan) veritabani kapsami -/// crud/ -/// wizard/ -/// data/ -/// sql/ → SQL Server saglayicisi -/// object/ → nesne olusturulur/guncellenir -/// execute/ → nesne olusturulur, ayrica tum migration'lar bitince calistirilir -/// postgres/ → PostgreSQL saglayicisi -/// object/ -/// execute/ -/// tenants/ -/// {tenantId}/ → her tenant kendi klasorunde -/// crud/ wizard/ data/ sql/{object,execute}/ postgres/{object,execute}/ +/// crud/ +/// wizard/ +/// data/ +/// docs/ +/// sql/ → SQL Server saglayicisi +/// object/ → nesne olusturulur/guncellenir +/// execute/ → nesne olusturulur, ayrica tum migration'lar bitince calistirilir +/// host/{object,execute}/ → ayni kurallar, yalnizca host veritabaninda +/// postgres/ → PostgreSQL saglayicisi +/// object/ +/// execute/ +/// host/{object,execute}/ /// /// /// @@ -47,12 +50,6 @@ public static class SeedPathResolver { public const string ConfigurationKey = "App:SeedsPath"; - /// Host veritabani kapsamindaki seed'lerin kok klasoru. - public const string HostFolder = "host"; - - /// Tenant kapsamindaki seed'lerin kok klasoru; altinda tenant Id'si ile klasorler bulunur. - public const string TenantsFolder = "tenants"; - /// SQL Server saglayicisinin script klasoru. public const string SqlFolder = "sql"; @@ -65,6 +62,13 @@ public static class SeedPathResolver /// Olusturulan, ayrica tum migration'lar bittikten sonra calistirilan scriptler. public const string ExecuteFolder = "execute"; + /// + /// Saglayici klasorunun altinda yalnizca host veritabaninda calisan scriptler + /// ({sql|postgres}/host/{object,execute}). Host'a ozgu nesnelere dayanan script buraya + /// yazilir; ornegin HangFire tablolari tenant migrate'inde de yalnizca host'a kurulur. + /// + public const string HostOnlyFolder = "host"; + /// Wizard'in urettigi konfigurasyon dosyalari. public const string WizardFolder = "wizard"; @@ -75,7 +79,7 @@ public static class SeedPathResolver public const string DataFolder = WizardConsts.SeedDataFolder; /// - /// Kapsam klasorundeki dokumanlar. Modul dokumanlari docs/modules/{Modul}.md altindadir; + /// Seed klasorundeki dokumanlar. Modul dokumanlari docs/modules/{Modul}.md altindadir; /// sunucu (wizard deploy, MCP) yalnizca seed kokune yazabildigi icin depo kokunde degil buradadir. /// public const string DocsFolder = "docs"; @@ -84,7 +88,7 @@ public static class SeedPathResolver public const string ModuleDocsFolder = "modules"; /// - /// Bir ListForm icin varsayilan veri seed dosyasinin kapsam klasorune gore goreli yolu. Bicim + /// Bir ListForm icin varsayilan veri seed dosyasinin seed klasorune gore goreli yolu. Bicim /// Domain.Shared'dedir; wizard'in urettigi ekran (Application.Contracts) ayni yolu yazar. /// public static string GetDataFilePath(string listFormCode) @@ -93,7 +97,7 @@ public static class SeedPathResolver } /// - /// Bir ListForm icin satir basina dosya tutan veri seed klasorunun kapsam klasorune gore goreli + /// Bir ListForm icin satir basina dosya tutan veri seed klasorunun seed klasorune gore goreli /// yolu. Cok satirli ya da satiri buyuk (bilesen kodu gibi) ekranlarda tek dosya her kayitta /// bastan yazilir; klasor modunda yalnizca islem goren satirin dosyasina dokunulur. /// @@ -168,38 +172,12 @@ public static class SeedPathResolver return path; } - /// - /// Kapsam klasorunun (host ya da ilgili tenant) altindaki yolu dondurur. - /// null ise host/, aksi halde tenants/{tenantId}/ kullanilir. - /// - public static string GetScopePath(IConfiguration? configuration, Guid? tenantId, params string[] subPaths) - { - return GetPath(configuration, BuildScopeSubPaths(tenantId, subPaths)); - } - - /// Kapsam klasorunun seed koküne gore goreli adi (host ya da tenants/{tenantId}). - public static string GetScopeFolderName(Guid? tenantId) - { - return tenantId == null - ? HostFolder - : $"{TenantsFolder}/{tenantId.Value}"; - } - /// Aktif veritabani saglayicisina karsilik gelen script klasorunun adi. public static string GetProviderFolderName(bool isPostgreSql) { return isPostgreSql ? PostgresFolder : SqlFolder; } - private static string[] BuildScopeSubPaths(Guid? tenantId, string[] subPaths) - { - var scopeSegments = tenantId == null - ? new[] { HostFolder } - : new[] { TenantsFolder, tenantId.Value.ToString() }; - - return scopeSegments.Concat(subPaths ?? Array.Empty()).ToArray(); - } - /// Calisma dizininden yukari dogru cikarak depodaki configs/seeds klasorunu arar. private static string? FindRepositorySeedsPath() { diff --git a/api/src/Sozsoft.Platform.Domain/Data/TenantSeedOptions.cs b/api/src/Sozsoft.Platform.Domain/Data/TenantSeedOptions.cs new file mode 100644 index 00000000..7fec199d --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain/Data/TenantSeedOptions.cs @@ -0,0 +1,15 @@ +namespace Sozsoft.Platform.Data; + +/// +/// Tek tenant seed'inin DbMigrator alt sürecine taşıdığı yapılandırma anahtarları. +/// Değerler komut satırı yerine ortam değişkeniyle geçer; yönetici parolası süreç listesinde görünmez. +/// +public static class TenantSeedOptions +{ + public const string TenantIdKey = "TenantSeed:TenantId"; + public const string AdminEmailKey = "TenantSeed:AdminEmail"; + public const string AdminPasswordKey = "TenantSeed:AdminPassword"; + + /// Yapılandırma anahtarının ortam değişkeni karşılığı (: → __). + public static string ToEnvironmentVariable(string key) => key.Replace(":", "__"); +} diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs index d5849edb..db280d3c 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm.cs @@ -98,7 +98,7 @@ public class ListForm : Entity, IListFormArtifact /// public string DeleteServiceAddress { get; set; } = "list-form-data/delete"; - /// Bu listenin verisinin aynalandigi seed dosyasinin kapsam klasorune gore goreli yolu + /// Bu listenin verisinin aynalandigi seed dosyasinin seed klasorune gore goreli yolu /// (ornegin data/App.DeveloperKit.IntranetWidgets.json). Dolu ise ekrandan yapilan /// seed sirasinda dosyadaki satirlar tabloya geri yuklenir. Ekranda yapilan islemlerin dosyaya /// yansimasi asagidaki bayraklara baglidir. diff --git a/api/src/Sozsoft.Platform.Domain/Extensions/StringExtensions.cs b/api/src/Sozsoft.Platform.Domain/Extensions/StringExtensions.cs index 820cec64..59b49bd8 100644 --- a/api/src/Sozsoft.Platform.Domain/Extensions/StringExtensions.cs +++ b/api/src/Sozsoft.Platform.Domain/Extensions/StringExtensions.cs @@ -4,11 +4,6 @@ namespace Sozsoft.Platform.Extensions; public static class StringExtensions { - public static string RemoveParentheses(this string str) - { - return str?.Replace("(", "").Replace(")", ""); - } - public static string GetSegment(this string str, char segment, int index) { var start = str.NthIndexOf(segment, index); diff --git a/api/src/Sozsoft.Platform.Domain/Queries/DataSourceManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/DataSourceManager.cs index b0fcdcb7..e2d6e712 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/DataSourceManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/DataSourceManager.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Threading.Tasks; using Sozsoft.Platform.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Services; +using Volo.Abp.MultiTenancy; namespace Sozsoft.Platform.Queries; @@ -15,10 +16,12 @@ public interface IDataSourceManager public class DataSourceManager : DomainService, IDataSourceManager { private readonly IRepository repoDataSource; + private readonly ITenantStore tenantStore; - public DataSourceManager(IRepository repoDataSource) + public DataSourceManager(IRepository repoDataSource, ITenantStore tenantStore) { this.repoDataSource = repoDataSource; + this.tenantStore = tenantStore; } public async Task GetConnectionStringAsync(bool isTenant, string dataSourceCode) @@ -30,10 +33,23 @@ public class DataSourceManager : DomainService, IDataSourceManager public async Task GetDataSourceAsync(bool isTenant, string dataSourceCode) { - var code = isTenant && CurrentTenant.Name is not null ? CurrentTenant.Name : dataSourceCode; + var tenantName = isTenant ? await GetTenantNameAsync() : null; + var code = tenantName ?? dataSourceCode; var dataSource = await repoDataSource.FirstOrDefaultAsync(a => a.Code == code); dataSource ??= await repoDataSource.FirstOrDefaultAsync(a => a.Code == dataSourceCode); return dataSource; } -} + /// + /// Kiracının veri kaynağı kiracı adıyla kayıtlıdır. İstek dışında (DbMigrator, seed applier, + /// arka plan işi) kiracı yalnızca kimliğiyle değiştirilir ve ad boş kalır; ad o zaman kiracı + /// kaydından okunur, yoksa kiracının satırları host veritabanına yazılırdı. + /// + private async Task GetTenantNameAsync() + { + if (CurrentTenant.Name is not null || CurrentTenant.Id is not { } tenantId) + return CurrentTenant.Name; + + return (await tenantStore.FindAsync(tenantId))?.Name; + } +} diff --git a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs index bf33028a..c211d77b 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/QueryManager.cs @@ -212,28 +212,20 @@ public class QueryManager : PlatformDomainService, IQueryManager } else if (op == OperationEnum.Delete) { - // todo: softdelete - string where = string.Empty; - if (parameters.Any()) - { - where = string.Join( - " AND ", - parameters.Select(a => BuildWhereCondition(a.Key, a.Value, dataSourceType)).ToList()); - } - else + // Silme her zaman anahtarla sınırlanır; diğer parametreler (TenantId gibi) yalnızca ek koşuldur. + // Önceden parametre varsa anahtar hiç eklenmiyordu: silme varsayılanı tanımsız bir ekranda sorgu + // "WHERE TenantId = @TenantId" olarak kalıp kiracının bütün satırlarını siliyordu. + if (!parameters.ContainsKey(listForm.KeyFieldName)) { var val = QueryHelper.GetFormattedValue(listForm.KeyFieldDbSourceType, keys); parameters.Add( listForm.KeyFieldName, val.GetType().IsArray ? val : new object[] { val }); - - where = dataSourceType switch - { - DataSourceTypeEnum.Mssql => $"\"{listForm.KeyFieldName}\" IN @{listForm.KeyFieldName}", - DataSourceTypeEnum.Postgresql => $"\"{listForm.KeyFieldName}\" = ANY(@{listForm.KeyFieldName})", - _ => "1 = 0", - }; } + + var where = string.Join( + " AND ", + parameters.Select(a => BuildWhereCondition(a.Key, a.Value, dataSourceType))); sql = $"DELETE FROM \"{listForm.SelectCommand}\" WHERE {where}"; } } diff --git a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs index f9980ae4..929a341f 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs @@ -12,6 +12,7 @@ using Sozsoft.Platform.Extensions; using Sozsoft.Platform.ListForms; using Sozsoft.Platform.Localization; using Sozsoft.Platform.OrganizationUnits; +using Volo.Abp; using Volo.Abp.Domain.Repositories; using Volo.Abp.ObjectMapping; using Volo.Abp.Settings; @@ -205,10 +206,15 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager for (int i = 0; i < summaries.Length; i++) { var summary = summaries[i].Split(' '); - var fieldName = summary[0]; - var summmaryType = summary[1]; // asc, desc - fieldName = ConvertAliasToFieldName(fieldName, listFormFields); - if (fieldName == "undefined") + // Toplam türü SQL fonksiyon adı olarak yazılır; yalnızca bilinen beş tür kabul edilir. + if (summary.Length != 2 || !SummaryTypes.Contains(summary[1])) + { + continue; + } + + var summmaryType = summary[1]; + var fieldName = ConvertAliasToFieldName(summary[0], listFormFields); + if (fieldName == UnknownFieldName) { continue; } @@ -294,9 +300,22 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager } } + // Ad istemciden gelir (toplam, grup, filtre); ekranda olmayan bir ad SQL'e hiç yazılmaz. + if (filterName != KeyFieldName && !listFormFields.Any(a => a.FieldName == filterName)) + { + return UnknownFieldName; + } + return string.Format(format, $"\"{TableName}\".\"{filterName}\""); } + private const string UnknownFieldName = "undefined"; + + private static readonly HashSet SummaryTypes = new(StringComparer.OrdinalIgnoreCase) + { + "sum", "avg", "min", "max", "count", + }; + private string GetSelectString() { return $"SELECT {string.Join(',', SelectFields.Select(c => $"{c.SelectFieldName}"))} FROM {From} AS \"{TableName}\""; @@ -353,55 +372,16 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager if (!string.IsNullOrEmpty(queryParams?.Filter)) { - var filters = queryParams.Filter.Replace('[', '(') - .Replace(']', ')') - .Replace("\"\"", "''") // (empty string) "" -> '' - .Replace("\"", "") - .Replace(",\"or\",", " or ") - .Replace(",\"and\",", " and "); + SelectQueryParameters = new Dictionary(); + filterParameterIndex = 0; - var filterItems = filters.Split(",").ToList(); - if (filterItems - .Where(a => !a.Contains('!') && a != "and" && a != "or") - .ToList().Count % 3 == 0) //Eğer gelen veri 'and', 'or' veya '!' dışında kalan item adedi 3 ün katıysa + var clientFilter = BuildClientFilter(queryParams.Filter, listFormFields); + if (clientFilter != null) { - SelectQueryParameters = new Dictionary(); - - var forDeger = 3; - - for (int i = 0; i < filterItems.Count; i += forDeger) - { - if (filterItems[i].Contains('!')) - { - forDeger = 1; - - whereParts.Add(filterItems[i].Replace("!", "NOT")); - IsAppliedGridFilter = true; - } - else if (filterItems[i] == "and" || filterItems[i] == "or") - { - forDeger = 1; - - whereParts.Add(filterItems[i]); - IsAppliedGridFilter = true; - } - else - { - forDeger = 3; - - var fieldName = filterItems[i]; - var op = filterItems[i + 1]; - var fieldValue = filterItems[i + 2]; - - var filter = ParseFilter(fieldName, op, fieldValue, listFormFields, i); - - if (filter != "") - { - whereParts.Add(filter); - IsAppliedGridFilter = true; - } - } - } + // İstemci filtresi tek grup olarak paranteze alınır; aksi hâlde içindeki "or", + // ardından eklenen kiracı/şube/OU koşullarını devre dışı bırakırdı. + whereParts.Add($"({clientFilter})"); + IsAppliedGridFilter = true; } } @@ -527,42 +507,188 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager return (value ?? string.Empty).Replace("'", "''"); } - /// FieldName operator Id seklinde olan filtreleri ayirir - /// - /// - /// - private string ParseFilter( - string fieldName, - string op, - string fieldValue, - List listFormFields, - int customKey) + private const int MaxFilterDepth = 32; + + private static readonly HashSet FilterOperators = new(StringComparer.OrdinalIgnoreCase) { - string sqlPart = string.Empty; - if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(op)) + "=", "<>", "<", "<=", ">", ">=", "contains", "notcontains", "startswith", "endswith", + }; + + private int filterParameterIndex; + + /// + /// DevExtreme filtre dizisini (["Alan","=",değer], [koşul,"and",koşul], ["!",koşul]) + /// ağaç olarak çözer. Alan adı ekranın alanlarından, operatör beyaz listeden gelir; değer her zaman + /// parametredir. Metin olarak işlenmez: önceki ayrıştırıcı virgül ve parantezle böldüğü için değerin bir + /// kısmı ham SQL olarak sorguya ekleniyordu. + /// + private string BuildClientFilter(string filterJson, List listFormFields) + { + JsonElement root; + try { - return sqlPart; + using var document = JsonDocument.Parse(filterJson); + root = document.RootElement.Clone(); + } + catch (JsonException) + { + throw new UserFriendlyException("Invalid filter."); } + if (root.ValueKind != JsonValueKind.Array) + { + throw new UserFriendlyException("Invalid filter."); + } + + return BuildFilterNode(root, listFormFields, 0); + } + + private string BuildFilterNode(JsonElement node, List listFormFields, int depth) + { + if (depth > MaxFilterDepth) + { + throw new UserFriendlyException("Invalid filter."); + } + + var items = node.EnumerateArray().ToList(); + if (items.Count == 0) + { + return null; + } + + if (items.Count == 2 && IsFilterToken(items[0], "!") && items[1].ValueKind == JsonValueKind.Array) + { + var negated = BuildFilterNode(items[1], listFormFields, depth + 1); + return negated == null ? null : $"NOT ({negated})"; + } + + if (IsFilterCondition(items)) + { + return items.Count == 2 + ? BuildFilterCondition(items[0].GetString(), "=", items[1], listFormFields) + : BuildFilterCondition(items[0].GetString(), items[1].GetString(), items[2], listFormFields); + } + + // Grup: terimler ve aralarındaki bağlaçlar. Bağlaçsız ardışık terimler "and" sayılır (DevExtreme + // kuralı). UI bazen koşulu gruba düz ekler (["A","=",1,"and",[...]]); bu üçlü de koşul olarak okunur. + var parts = new List(); + string connector = null; + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + string term; + + if (IsFilterToken(item, "and") || IsFilterToken(item, "or")) + { + connector = item.GetString().ToUpperInvariant(); + continue; + } + + if (IsFilterToken(item, "!") && i + 1 < items.Count && items[i + 1].ValueKind == JsonValueKind.Array) + { + var negated = BuildFilterNode(items[++i], listFormFields, depth + 1); + term = negated == null ? null : $"NOT ({negated})"; + } + else if (item.ValueKind == JsonValueKind.Array) + { + var group = BuildFilterNode(item, listFormFields, depth + 1); + term = group == null ? null : $"({group})"; + } + else if (item.ValueKind == JsonValueKind.String + && i + 2 < items.Count + && items[i + 1].ValueKind == JsonValueKind.String) + { + term = BuildFilterCondition(item.GetString(), items[i + 1].GetString(), items[i + 2], listFormFields); + i += 2; + } + else + { + throw new UserFriendlyException("Invalid filter."); + } + + if (term == null) + { + continue; + } + + if (parts.Count > 0) + { + parts.Add(connector ?? "AND"); + } + + parts.Add(term); + connector = null; + } + + return parts.Count == 0 ? null : string.Join(" ", parts); + } + + private static bool IsFilterToken(JsonElement item, string token) + { + return item.ValueKind == JsonValueKind.String + && string.Equals(item.GetString(), token, StringComparison.OrdinalIgnoreCase); + } + + /// ["Alan","op",değer] ya da kısa biçim ["Alan",değer]. + private static bool IsFilterCondition(List items) + { + if (items[0].ValueKind != JsonValueKind.String + || IsFilterToken(items[0], "!") + || IsFilterToken(items[0], "and") + || IsFilterToken(items[0], "or")) + { + return false; + } + + return items.Count switch + { + 2 => items[1].ValueKind != JsonValueKind.Array, + 3 => items[1].ValueKind == JsonValueKind.String + && !IsFilterToken(items[1], "and") + && !IsFilterToken(items[1], "or"), + _ => false, + }; + } + + /// Tek koşulun SQL'i. Alan ekranda yoksa koşul etkisiz (1 = 1) yazılır. + private string BuildFilterCondition(string fieldName, string op, JsonElement value, List listFormFields) + { + if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(op)) + { + return null; + } + + var fieldValue = value.ValueKind switch + { + JsonValueKind.String => value.GetString(), + JsonValueKind.Number => value.GetRawText(), + JsonValueKind.True => "true", + JsonValueKind.False => "false", + JsonValueKind.Null or JsonValueKind.Undefined => "null", + _ => throw new UserFriendlyException("Invalid filter."), + }; + var fieldFilter = fieldName.Split('.'); - var filterFieldName = fieldFilter.First().RemoveParentheses(); + var filterFieldName = fieldFilter.First(); var filterGroupInterval = fieldFilter.Length > 1 ? fieldFilter.Last().ToLower() : null; - var filterParamKey = $"@{filterFieldName}{customKey}"; - var filterParamKeyLower = filterParamKey; var listFormField = listFormFields.FirstOrDefault(c => c.FieldName == filterFieldName); // Anahtar alan her sorguya eklenir ama alan listesinde olmayabilir; grid satira odaklanirken // filtreye anahtar kosulu yazar. var isKeyField = listFormField == null && filterFieldName == KeyFieldName; if (listFormField == null && !isKeyField) { - // Kosul dusurulurse etrafindaki baglac ve parantezler yetim kalir (WHERE and (...))); - // yerine parantezleri koruyan etkisiz bir kosul yazilir. - var leading = fieldName[..^fieldName.TrimStart('(').Length]; - var trailing = fieldValue[fieldValue.TrimEnd(')').Length..]; - return $"{leading}1 = 1{trailing}"; + return "1 = 1"; } - var filterOperator = op.RemoveParentheses(); + var filterOperator = op; + if (!FilterOperators.Contains(filterOperator)) + { + throw new UserFriendlyException($"'{op}' is not a supported filter operator."); + } + + var filterParamKey = $"@{filterFieldName}{filterParameterIndex++}"; + var filterParamKeyLower = filterParamKey; + // filterGroupInterval sayı olduğu için (örn OrderDate.Month), eğer bu gelirse sayıya parse ediyoruz, // diğer durumlarda field tipine parse ediyoruz DbType dbType = filterGroupInterval switch @@ -571,13 +697,12 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager _ => isKeyField ? KeyFieldDbSourceType : listFormField.SourceDbType }; - var filterValue = fieldValue.RemoveParentheses(); + var filterValue = fieldValue; if (filterValue == "null") { if (filterOperator == "=") filterOperator = "isblank"; if (filterOperator == "<>") filterOperator = "isnotblank"; } - var lastCharacters = fieldValue.Replace(filterValue, string.Empty); // Sadece parantezleri almak için var filterSqlFieldName = ConvertAliasToFieldName(filterFieldName, listFormFields, filterGroupInterval); if (filterOperator == "=" && DataSourceType == DataSourceTypeEnum.Postgresql && ( @@ -588,7 +713,6 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager filterParamKeyLower = $"lower({filterParamKeyLower})"; filterValue = filterValue.Replace('I', 'ı'); } - filterSqlFieldName = fieldFilter.First().Replace(filterFieldName, filterSqlFieldName); var sqlOperator = ""; string formatSql = "{0} {1} {2}"; @@ -615,11 +739,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager else if (filterOperator == "isblank") formatSql = "{0} IS NULL"; else if (filterOperator == "isnotblank") formatSql = "{0} IS NOT NULL"; - sqlPart = string.Format(formatSql, filterSqlFieldName, sqlOperator, filterParamKeyLower); - if (lastCharacters.Any()) - { - sqlPart += lastCharacters; - } + var sqlPart = string.Format(formatSql, filterSqlFieldName, sqlOperator, filterParamKeyLower); if (formatSql.Contains("{2}")) { @@ -743,9 +863,18 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager var isExpanded = items.Length > 2 && items[2] == "true"; var groupInterval = items.Length > 3 ? validGroupIntervals.Contains(items[3]) ? items[3] : null : null; var fieldName = ConvertAliasToFieldName(field, listFormFields, groupInterval);// Alias li bir alan ise gercek alani bul + if (fieldName == UnknownFieldName) + { + continue; + } GroupTuples.Add((i, fieldName, $"Group_{i}", sort, isExpanded, groupInterval)); } + if (GroupTuples.Count == 0) + { + return null; + } + // gruplanan bir alan icin grup satirinda summary bilgisinin gorunmesi icin gerekli veri yapisidir, // grup sorgusu ile birlikte calistirilir. // Gelen veri yapisi: FieldName SummaryType|FieldName SummaryType @@ -768,6 +897,10 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager continue; } var fieldName = ConvertAliasToFieldName(field, listFormFields);// Alias li bir alan ise gercek alani bul + if (fieldName == UnknownFieldName) + { + continue; + } GroupSummaryTuples.Add((i, fieldName, $"Summary_{i}", summaryType)); } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DatabaseMigrationEventHandlerBase.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DatabaseMigrationEventHandlerBase.cs deleted file mode 100644 index 1c7c209c..00000000 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DatabaseMigrationEventHandlerBase.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Sozsoft.Platform.EntityFrameworkCore; -using Volo.Abp; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.DistributedLocking; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.EventBus.Distributed; -using Volo.Abp.Guids; -using Volo.Abp.Identity; -using Volo.Abp.MultiTenancy; -using Volo.Abp.PermissionManagement; -using Volo.Abp.TenantManagement; -using Volo.Abp.Uow; -using static Sozsoft.Settings.SettingsConsts; - -namespace Sozsoft.Platform.Migrations; - -public class DatabaseMigrationEventHandler : - IDistributedEventHandler, - ITransientDependency -{ - protected string DatabaseName { get; } - public ICurrentTenant CurrentTenant { get; } - public IUnitOfWorkManager UnitOfWorkManager { get; } - public ITenantStore TenantStore { get; } - public IAbpDistributedLock DistributedLock { get; } - public IDataSeeder DataSeeder { get; } - public IOptions IdentityOptions { get; } - public IIdentityUserRepository UserRepository { get; } - public IIdentityRoleRepository RoleRepository { get; } - public ILookupNormalizer LookupNormalizer { get; } - public IGuidGenerator GuidGenerator { get; } - public IdentityUserManager UserManager { get; } - public IdentityRoleManager RoleManager { get; } - public IPermissionGrantRepository PermissionGrantRepository { get; } - public ITenantRepository TenantRepository { get; } - - protected ILogger Logger { get; } - - public DatabaseMigrationEventHandler( - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - ITenantStore tenantStore, - IAbpDistributedLock distributedLock, - IDistributedEventBus distributedEventBus, - ILoggerFactory loggerFactory, - IDataSeeder dataSeeder, - IOptions identityOptions, - IIdentityUserRepository userRepository, - IIdentityRoleRepository roleRepository, - ILookupNormalizer lookupNormalizer, - IGuidGenerator guidGenerator, - IdentityUserManager userManager, - IdentityRoleManager roleManager, - IPermissionGrantRepository permissionGrantRepository, - ITenantRepository tenantRepository) - { - DatabaseName = DefaultDatabaseProvider; - - CurrentTenant = currentTenant; - UnitOfWorkManager = unitOfWorkManager; - TenantStore = tenantStore; - DistributedLock = distributedLock; - DataSeeder = dataSeeder; - IdentityOptions = identityOptions; - UserRepository = userRepository; - RoleRepository = roleRepository; - LookupNormalizer = lookupNormalizer; - GuidGenerator = guidGenerator; - UserManager = userManager; - RoleManager = roleManager; - PermissionGrantRepository = permissionGrantRepository; - TenantRepository = tenantRepository; - - Logger = loggerFactory.CreateLogger(); - - } - - public async Task HandleEventAsync(ApplyDatabaseMigrationsEto eventData) - { - try - { - var schemaMigrated = await MigrateDatabaseSchemaAsync(eventData.TenantId); - if (schemaMigrated) - { - await SeedTenantDataAsync(eventData); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error while seeding tenant data"); - throw; - - //await HandleErrorOnApplyDatabaseMigrationAsync(eventData, ex); - } - } - - private async Task MigrateDatabaseSchemaAsync(Guid? tenantId) - { - var result = false; - - using (CurrentTenant.Change(tenantId)) - { - using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) - { - async Task WaitForDistributedLockAsync(PlatformDbContext dbContext) - { - var distributedLockHandle = await DistributedLock.TryAcquireAsync( - "DatabaseMigrationEventHandler_" + - dbContext.Database.GetConnectionString()!.ToUpperInvariant().ToMd5(), - TimeSpan.FromSeconds(60) - ); - - if (distributedLockHandle == null) - { - throw new AbpException($"Distributed lock could not be acquired for database migration event handler: {DatabaseName}."); - } - - return distributedLockHandle; - } - - async Task MigrateDatabaseSchemaWithDbContextAsync() - { - var dbContext = await uow.ServiceProvider - .GetRequiredService>() - .GetDbContextAsync(); - - await using (await WaitForDistributedLockAsync(dbContext)) - { - if ((await dbContext.Database.GetPendingMigrationsAsync()).Any()) - { - await dbContext.Database.MigrateAsync(); - return true; - } - } - - return false; - } - - if (tenantId == null) - { - //Migrating the host database - Logger.LogInformation($"Migrating database of host. Database Name = {DatabaseName}"); - result = await MigrateDatabaseSchemaWithDbContextAsync(); - } - else - { - var tenantConfiguration = await TenantStore.FindAsync(tenantId.Value); - if (!tenantConfiguration!.ConnectionStrings!.Default.IsNullOrWhiteSpace() || - !tenantConfiguration.ConnectionStrings.GetOrDefault(DatabaseName).IsNullOrWhiteSpace()) - { - //Migrating the tenant database (only if tenant has a separate database) - Logger.LogInformation($"Migrating separate database of tenant. Database Name = {DatabaseName}, TenantId = {tenantId}"); - result = await MigrateDatabaseSchemaWithDbContextAsync(); - } - else - { - result = true; - } - } - - await uow.CompleteAsync(); - } - } - - return result; - } - - private async Task SeedTenantDataAsync(ApplyDatabaseMigrationsEto eventData) - { - using (CurrentTenant.Change(eventData.TenantId)) - { - await DataSeeder.SeedAsync( - new DataSeedContext(eventData.TenantId) - .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, eventData.Properties[IdentityDataSeedContributor.AdminEmailPropertyName]) - .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, eventData.Properties[IdentityDataSeedContributor.AdminPasswordPropertyName]) - .WithProperty(IdentityDataSeedContributor.AdminUserNamePropertyName, eventData.Properties[IdentityDataSeedContributor.AdminUserNamePropertyName]) - ); - } - } -} - - diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs index 2f173d16..972df54a 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs @@ -9,15 +9,31 @@ using Sozsoft.Platform.Domain.DeveloperKit; using Sozsoft.Platform.EntityFrameworkCore; using Volo.Abp; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.MultiTenancy; public class DynamicEntityManager : IDynamicEntityManager { + private const string TenantIdColumn = "TenantId"; + + /// + /// Kimlik bilgisi taşıyan kolonlar: istemciye hiç dönmez ve istemciden yazılamaz. + /// Parola ve güvenlik damgası yalnızca kimlik yöneticisinden (UserManager) geçer. + /// + private static readonly HashSet SecretColumns = new(StringComparer.OrdinalIgnoreCase) + { + "PasswordHash", + "SecurityStamp", + }; + private readonly IDbContextProvider _dbContextProvider; + private readonly ICurrentTenant _currentTenant; public DynamicEntityManager( - IDbContextProvider dbContextProvider) + IDbContextProvider dbContextProvider, + ICurrentTenant currentTenant) { _dbContextProvider = dbContextProvider; + _currentTenant = currentTenant; } public async Task?> GetEntityListAsync( @@ -35,6 +51,10 @@ public class DynamicEntityManager : IDynamicEntityManager conditions.Add($"({idDeletedColumn} = {FalseLiteral(isPostgreSql)} OR {idDeletedColumn} IS NULL)"); var parameters = new List(); + var tenantCondition = await BuildTenantConditionAsync(entityName, isPostgreSql, parameters); + if (tenantCondition != null) + conditions.Add(tenantCondition); + if (filters is { Count: > 0 }) { var columns = await GetColumnNamesAsync(entityName); @@ -49,7 +69,7 @@ public class DynamicEntityManager : IDynamicEntityManager var query = conditions.Count > 0 ? $"SELECT * FROM {tableName} WHERE {string.Join(" AND ", conditions)}" : $"SELECT * FROM {tableName}"; - return await ExecuteRawQueryAsync(query, parameters); + return RemoveSecretColumns(await ExecuteRawQueryAsync(query, parameters)); } /// @@ -196,11 +216,18 @@ public class DynamicEntityManager : IDynamicEntityManager var idColumn = QuoteIdentifier("Id", isPostgreSql); var isDeletedColumn = QuoteIdentifier("IsDeleted", isPostgreSql); var hasIsDeleted = await ColumnExistsAsync(entityName, "IsDeleted"); - var query = hasIsDeleted - ? $"SELECT * FROM {tableName} WHERE {idColumn} = '{id}' AND ({isDeletedColumn} = {FalseLiteral(isPostgreSql)} OR {isDeletedColumn} IS NULL)" - : $"SELECT * FROM {tableName} WHERE {idColumn} = '{id}'"; - var result = await ExecuteRawQueryAsync(query); - return result?.FirstOrDefault(); + + var parameters = new List(); + var conditions = new List { $"{idColumn} = {AddParameter(parameters, id)}" }; + if (hasIsDeleted) + conditions.Add($"({isDeletedColumn} = {FalseLiteral(isPostgreSql)} OR {isDeletedColumn} IS NULL)"); + + var tenantCondition = await BuildTenantConditionAsync(entityName, isPostgreSql, parameters); + if (tenantCondition != null) + conditions.Add(tenantCondition); + + var query = $"SELECT * FROM {tableName} WHERE {string.Join(" AND ", conditions)}"; + return RemoveSecretColumns(await ExecuteRawQueryAsync(query, parameters)).FirstOrDefault(); } public async Task CreateEntityAsync(string entityName, JsonElement data) @@ -218,9 +245,16 @@ public class DynamicEntityManager : IDynamicEntityManager if (hasCreationTime) { columns.Add(QuoteIdentifier("CreationTime", isPostgreSql)); values.Add(UtcNowExpression(isPostgreSql)); } if (hasIsDeleted) { columns.Add(QuoteIdentifier("IsDeleted", isPostgreSql)); values.Add(FalseLiteral(isPostgreSql)); } + // Kayıt her zaman isteği yapanın kiracısına yazılır; istemcinin gönderdiği TenantId yok sayılır. + if (_currentTenant.Id.HasValue && await ColumnExistsAsync(entityName, TenantIdColumn)) + { + columns.Add(QuoteIdentifier(TenantIdColumn, isPostgreSql)); + values.Add(AddParameter(parameters, _currentTenant.Id.Value)); + } + foreach (var prop in data.EnumerateObject()) { - if (prop.NameEquals("id") || prop.NameEquals("Id")) + if (IsProtectedColumn(prop.Name)) continue; columns.Add(QuoteIdentifier(prop.Name, isPostgreSql)); @@ -251,7 +285,7 @@ public class DynamicEntityManager : IDynamicEntityManager foreach (var prop in data.EnumerateObject()) { - if (prop.NameEquals("id") || prop.NameEquals("Id")) + if (IsProtectedColumn(prop.Name)) continue; setParts.Add($"{QuoteIdentifier(prop.Name, isPostgreSql)} = {AddParameter(parameters, ToSqlParameterValue(prop.Value))}"); @@ -300,6 +334,44 @@ public class DynamicEntityManager : IDynamicEntityManager } } + /// + /// Kiracı kolonu olan tabloda yalnızca isteği yapanın kiracısının satırları görünür (host için + /// TenantId IS NULL). Güncelleme ve silme önce ile satırı + /// bulduğu için aynı sınır onlara da uygulanır. + /// + private async Task BuildTenantConditionAsync(string entityName, bool isPostgreSql, List parameters) + { + if (!await ColumnExistsAsync(entityName, TenantIdColumn)) + return null; + + var column = QuoteIdentifier(TenantIdColumn, isPostgreSql); + return _currentTenant.Id.HasValue + ? $"{column} = {AddParameter(parameters, _currentTenant.Id.Value)}" + : $"{column} IS NULL"; + } + + /// Anahtar, kiracı ve kimlik bilgisi kolonları istemci verisinden yazılmaz. + private static bool IsProtectedColumn(string columnName) + { + return columnName.Equals("Id", StringComparison.OrdinalIgnoreCase) + || columnName.Equals(TenantIdColumn, StringComparison.OrdinalIgnoreCase) + || SecretColumns.Contains(columnName); + } + + private static List RemoveSecretColumns(List rows) + { + foreach (var row in rows) + { + if (row is not Dictionary item) + continue; + + foreach (var key in item.Keys.Where(SecretColumns.Contains).ToList()) + item.Remove(key); + } + + return rows; + } + private async Task ColumnExistsAsync(string tableName, string columnName) { var dbContext = await _dbContextProvider.GetDbContextAsync(); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs deleted file mode 100644 index f26aac00..00000000 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantIdentityDataSeeder.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; -using Sozsoft.Platform.Entities; -using Sozsoft.Platform.Extensions; -using Volo.Abp; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; -using Volo.Abp.Identity; -using Volo.Abp.MultiTenancy; -using Volo.Abp.TenantManagement; -using IdentityRole = Volo.Abp.Identity.IdentityRole; -using IdentityUser = Volo.Abp.Identity.IdentityUser; - -namespace Sozsoft.Platform.Data.Seeds; - -[Dependency(ReplaceServices = true)] -public class TenantIdentityDataSeeder : IdentityDataSeeder -{ - private readonly IRepository _branchRepository; - private readonly IRepository _branchUsersRepository; - private readonly ITenantRepository _tenantRepository; - private readonly IConfiguration _configuration; - - public TenantIdentityDataSeeder( - IGuidGenerator guidGenerator, - IIdentityRoleRepository roleRepository, - IIdentityUserRepository userRepository, - IRepository branchRepository, - IRepository branchUsersRepository, - ILookupNormalizer lookupNormalizer, - IdentityUserManager userManager, - IdentityRoleManager roleManager, - ICurrentTenant currentTenant, - ITenantRepository tenantRepository, - IConfiguration configuration, - IOptions identityOptions - ) : base(guidGenerator, roleRepository, userRepository, lookupNormalizer, userManager, roleManager, currentTenant, identityOptions) - { - this._branchRepository = branchRepository; - this._branchUsersRepository = branchUsersRepository; - this._tenantRepository = tenantRepository; - this._configuration = configuration; - } - - public override async Task SeedAsync(string adminEmail, string adminPassword, Guid? tenantId = null, string adminUserName = null) - { - Check.NotNullOrWhiteSpace(adminEmail, nameof(adminEmail)); - Check.NotNullOrWhiteSpace(adminPassword, nameof(adminPassword)); - - using (CurrentTenant.Change(tenantId)) - { - await IdentityOptions.SetAsync(); - - var result = new IdentityDataSeedResult(); - - var branchCode = ""; - var branchName = ""; - - if (tenantId == null && !CurrentTenant.IsAvailable) - { - branchCode = PlatformConsts.Branches.BranchCode; - branchName = PlatformConsts.Branches.BranchName; - } - else - { - var tenant = await _tenantRepository.FindAsync(tenantId ?? CurrentTenant.Id.Value); - if (tenant != null) - { - branchCode = tenant.Name; - branchName = tenant.GetOrganizationName(); - } - } - - //Default Branch otomatik olarak oluşturuluyor. - var defaultBranch = await _branchRepository.FirstOrDefaultAsync(b => b.Code == branchCode); - if (defaultBranch == null) - { - var branchId = tenantId == null && !CurrentTenant.IsAvailable - ? PlatformConsts.Branches.BranchIdDefaultValue - : GuidGenerator.Create(); - defaultBranch = await _branchRepository.InsertAsync(new Branch(branchId) - { - Code = branchCode, - Name = branchName, - VknTckn = PlatformConsts.Branches.BranchVknTckn, - TaxOffice = PlatformConsts.Branches.BranchTaxOffice, - MobileNumber = PlatformConsts.Branches.BranchMobileNumber, - Country = PlatformConsts.Branches.BranchCountry, - City = PlatformConsts.Branches.BranchCity, - District = PlatformConsts.Branches.BranchDistrict, - Township = PlatformConsts.Branches.BranchTownship, - Email = PlatformConsts.Branches.BranchEmail, - Website = PlatformConsts.Branches.BranchWebsite, - IsActive = PlatformConsts.Branches.BranchIsActive, - TenantId = tenantId, - }, autoSave: true); - } - - var adminRole = await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.NormalizeName(PlatformConsts.AbpIdentity.User.AdminRoleName)); - if (adminRole is null) - { - adminRole = new IdentityRole( - GuidGenerator.Create(), - PlatformConsts.AbpIdentity.User.AdminRoleName, - tenantId - ) - { - IsStatic = true, - IsPublic = true - }; - - (await RoleManager.CreateAsync(adminRole)).CheckErrors(); - result.CreatedAdminRole = true; - } - - adminUserName ??= PlatformConsts.AbpIdentity.User.AdminEmailDefaultValue; - var adminUser = await UserRepository.FindByNormalizedUserNameAsync( - LookupNormalizer.NormalizeName(adminUserName) - ); - - if (adminUser is null) - { - adminUser = new IdentityUser( - GuidGenerator.Create(), - adminUserName, - adminEmail, - tenantId - ) - { - Name = PlatformConsts.AbpIdentity.User.AdminNameDefaultValue, - Surname = PlatformConsts.AbpIdentity.User.AdminSurNameDefaultValue, - }; - - adminUser.SetIsVerified(true); - adminUser.SetEmailConfirmed(true); - adminUser.SetRocketUsername(PlatformConsts.AbpIdentity.User.AdminRocketUsernameDefaultValue); - adminUser.SetPhoneNumber(PlatformConsts.AbpIdentity.User.AdminPhoneNumberDefaultValue, adminUser.PhoneNumberConfirmed); - adminUser.SetWorkHour(PlatformConsts.AbpIdentity.User.AdminWorkHourDefaultValue); - adminUser.SetNationality(PlatformConsts.AbpIdentity.User.AdminNationalityDefaultValue); - adminUser.SetBloodType(PlatformConsts.AbpIdentity.User.AdminBloodTypeDefaultValue); - adminUser.SetEducationLevel(PlatformConsts.AbpIdentity.User.AdminEducationLevelDefaultValue); - adminUser.SetHomeAddress(PlatformConsts.AbpIdentity.User.AdminHomeAddressDefaultValue); - adminUser.SetBirthDate(PlatformConsts.AbpIdentity.User.AdminBirthDateDefaultValue); - adminUser.SetBirthPlace(PlatformConsts.AbpIdentity.User.AdminBirthPlaceDefaultValue); - adminUser.SetMaritalStatus(PlatformConsts.AbpIdentity.User.AdminMaritalStatusDefaultValue); - adminUser.SetDepartmentId(PlatformConsts.AbpIdentity.User.AdminDepartmentIdDefaultValue); - adminUser.SetJobPositionId(PlatformConsts.AbpIdentity.User.AdminJobPositionIdDefaultValue); - adminUser.SetAvatar(GetAvatar(tenantId?.ToString(), adminUser.Id.ToString())); - adminUser.SetUserType(PlatformConsts.AbpIdentity.User.InternalUserTypeDefaultValue); - adminUser.SetGender(PlatformConsts.AbpIdentity.User.AdminGenderDefaultValue); - - (await UserManager.CreateAsync(adminUser, adminPassword, validatePassword: false)).CheckErrors(); - result.CreatedAdminUser = true; - - (await UserManager.AddToRoleAsync(adminUser, PlatformConsts.AbpIdentity.User.AdminRoleName)).CheckErrors(); - - // BranchUsers tablosuna admin kullanicisi ekleniyor. - var defaultBranchUsers = await _branchUsersRepository.FirstOrDefaultAsync(bu => bu.UserId == adminUser.Id && bu.BranchId == defaultBranch.Id); - if (defaultBranchUsers is null) - { - await _branchUsersRepository.InsertAsync(new BranchUsers - { - BranchId = defaultBranch.Id, - UserId = adminUser.Id, - TenantId = tenantId - }, autoSave: true); - } - } - - // Grant all permission kısmı Abp tarafından otomatik çalıştırılıyor. - // Ayrıca bende çalıştırınca hata veriyor. - // Hem DbMigrator hem de burada IdentityDataSeeder var. - // Aynı Assembly'de birden fazla IdentityDataSeeder olabilir - // DbMigrator çalışınca kendi IdentityDataSeeder'ını çalıştırıyor. - // Tenant seed edilince bu IdentityDataSeeder çalışıyor. - // See: Volo.Abp.PermissionManagement.PermissionDataSeedContributor - - return result; - } - } - - private string GetAvatar(string? tenantId, string? userId) - { - var tenantPart = !string.IsNullOrEmpty(tenantId) - ? $"tenants/{tenantId}" - : "host"; - - var baseUrl = _configuration["App:CdnUrl"]?.TrimEnd('/'); - - return $"{baseUrl}/{tenantPart}/avatar/{userId ?? "default"}.jpg"; - } -} - diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj index 5a0d94f5..f37760bf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj @@ -8,7 +8,6 @@ - @@ -18,7 +17,6 @@ - diff --git a/api/src/Sozsoft.Platform.HttpApi.Client/PlatformHttpApiClientModule.cs b/api/src/Sozsoft.Platform.HttpApi.Client/PlatformHttpApiClientModule.cs deleted file mode 100644 index 7b585765..00000000 --- a/api/src/Sozsoft.Platform.HttpApi.Client/PlatformHttpApiClientModule.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Account; -using Volo.Abp.FeatureManagement; -using Volo.Abp.Identity; -using Volo.Abp.Modularity; -using Volo.Abp.PermissionManagement; -using Volo.Abp.SettingManagement; -using Volo.Abp.TenantManagement; -using Volo.Abp.VirtualFileSystem; - -namespace Sozsoft.Platform; - -[DependsOn( - typeof(PlatformApplicationContractsModule), - typeof(AbpAccountHttpApiClientModule), - typeof(AbpIdentityHttpApiClientModule), - typeof(AbpPermissionManagementHttpApiClientModule), - typeof(AbpTenantManagementHttpApiClientModule), - typeof(AbpFeatureManagementHttpApiClientModule), - typeof(AbpSettingManagementHttpApiClientModule) -)] -public class PlatformHttpApiClientModule : AbpModule -{ - public const string RemoteServiceName = "Default"; - - public override void ConfigureServices(ServiceConfigurationContext context) - { - context.Services.AddHttpClientProxies( - typeof(PlatformApplicationContractsModule).Assembly, - RemoteServiceName - ); - - Configure(options => - { - options.FileSets.AddEmbedded(); - }); - } -} - diff --git a/api/src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj b/api/src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj deleted file mode 100644 index 1293a136..00000000 --- a/api/src/Sozsoft.Platform.HttpApi.Client/Sozsoft.Platform.HttpApi.Client.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - net10.0 - Sozsoft.Platform - - - - - - - - - - - - - - - - - - - - - - diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs index 6f627644..b07355b2 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,12 +11,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Sozsoft.Platform.Data; using Sozsoft.Platform.DbStartup; using Sozsoft.Platform.EntityFrameworkCore; using Sozsoft.Platform.Permissions; using Volo.Abp.DistributedLocking; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.MultiTenancy; +using Volo.Abp.TenantManagement; using Volo.Abp.Uow; namespace Sozsoft.Platform.Controllers; @@ -46,6 +49,7 @@ public class SetupController : ControllerBase private readonly ICurrentTenant _currentTenant; private readonly IAbpDistributedLock _distributedLock; private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly ITenantRepository _tenantRepository; private readonly BackgroundWorkerInitializer _backgroundWorkerInitializer; private readonly AbpBackgroundWorkerRecurringJobInitializer _abpBackgroundWorkerRecurringJobInitializer; private readonly ILogger _logger; @@ -58,6 +62,7 @@ public class SetupController : ControllerBase ICurrentTenant currentTenant, IAbpDistributedLock distributedLock, IUnitOfWorkManager unitOfWorkManager, + ITenantRepository tenantRepository, BackgroundWorkerInitializer backgroundWorkerInitializer, AbpBackgroundWorkerRecurringJobInitializer abpBackgroundWorkerRecurringJobInitializer, ILogger logger) @@ -69,6 +74,7 @@ public class SetupController : ControllerBase _currentTenant = currentTenant; _distributedLock = distributedLock; _unitOfWorkManager = unitOfWorkManager; + _tenantRepository = tenantRepository; _backgroundWorkerInitializer = backgroundWorkerInitializer; _abpBackgroundWorkerRecurringJobInitializer = abpBackgroundWorkerRecurringJobInitializer; _logger = logger; @@ -108,12 +114,65 @@ public class SetupController : ControllerBase if (!authorized) { _logger.LogWarning("Database migration request denied: {Reason}", denialReason); - Response.StatusCode = StatusCodes.Status403Forbidden; - Response.ContentType = "text/plain; charset=utf-8"; - await Response.WriteAsync(denialReason); + await WritePlainTextAsync(StatusCodes.Status403Forbidden, denialReason); return; } + _logger.LogWarning( + "Database migration triggered via /api/setup/migrate by user {UserId}.", + User?.Identity?.Name ?? "(setup password)"); + + await RunMigratorAsync(environment: null, reloadWorkers: true); + } + + /// + /// Tenants ekranının Seed sekmesi: yalnızca bu tenant için migration + seed çalıştırır ve + /// logları ile aynı SSE biçiminde akıtır. Yönetici e-posta/parolası + /// DbMigrator'a ortam değişkeniyle geçer (). + /// + [HttpPost("tenants/{id:guid}/seed")] + public async Task SeedTenantAsync(Guid id, [FromBody] SeedTenantInput input) + { + var (authorized, denialReason) = await AuthorizeTenantSeedRequestAsync(); + if (!authorized) + { + _logger.LogWarning("Tenant seed request denied: {Reason}", denialReason); + await WritePlainTextAsync(StatusCodes.Status403Forbidden, denialReason); + return; + } + + if (string.IsNullOrWhiteSpace(input.AdminEmail) || string.IsNullOrWhiteSpace(input.AdminPassword)) + { + await WritePlainTextAsync(StatusCodes.Status400BadRequest, "Admin email and password are required."); + return; + } + + var tenant = await _tenantRepository.FindAsync(id, includeDetails: false); + if (tenant is null) + { + await WritePlainTextAsync(StatusCodes.Status404NotFound, $"Tenant not found: {id}"); + return; + } + + _logger.LogWarning( + "Tenant seed triggered for {TenantName} ({TenantId}) by user {UserId}.", + tenant.Name, tenant.Id, User?.Identity?.Name); + + await RunMigratorAsync(new Dictionary + { + [TenantSeedOptions.ToEnvironmentVariable(TenantSeedOptions.TenantIdKey)] = tenant.Id.ToString(), + [TenantSeedOptions.ToEnvironmentVariable(TenantSeedOptions.AdminEmailKey)] = input.AdminEmail.Trim(), + [TenantSeedOptions.ToEnvironmentVariable(TenantSeedOptions.AdminPasswordKey)] = input.AdminPassword, + }, reloadWorkers: false); + } + + /// + /// DbMigrator'ü tek kilit altında çalıştırır; migrate ile tenant seed aynı anda yürümez. + /// Worker tanımları host verisidir; tek tenant seed'i onlara dokunmadığı için + /// yalnızca tam migrate'te açıktır. + /// + private async Task RunMigratorAsync(IReadOnlyDictionary? environment, bool reloadWorkers) + { var stream = new SetupEventStream(Response); await stream.StartAsync(); @@ -130,13 +189,9 @@ public class SetupController : ControllerBase return; } - _logger.LogWarning( - "Database migration triggered via /api/setup/migrate by user {UserId}.", - User?.Identity?.Name ?? "(setup password)"); - // ApplicationStopping kullanılıyor: istemci sekmesini kapatsa da migration tamamlanır. var outcome = await SetupMigrationRunner.ExecuteAsync( - _configuration, _env.ContentRootPath, stream, _lifetime.ApplicationStopping); + _configuration, _env.ContentRootPath, stream, _lifetime.ApplicationStopping, environment); if (outcome != MigrationOutcome.Succeeded) { @@ -144,6 +199,13 @@ public class SetupController : ControllerBase return; } + if (!reloadWorkers) + { + await stream.SuccessAsync("Migration and seed completed successfully."); + await stream.DoneAsync("Completed."); + return; + } + await stream.InfoAsync("Migration and seed completed successfully."); // Yeni/değişen tanımlar Hangfire'a yansısın diye worker'lar tazelenir. @@ -165,6 +227,13 @@ public class SetupController : ControllerBase await stream.DoneAsync("Completed."); } + private async Task WritePlainTextAsync(int statusCode, string message) + { + Response.StatusCode = statusCode; + Response.ContentType = "text/plain; charset=utf-8"; + await Response.WriteAsync(message); + } + // Yetkilendirme /// @@ -215,6 +284,27 @@ public class SetupController : ControllerBase : (false, $"The '{PlatformPermissions.Setup.Migrate}' permission is required."); } + /// + /// Tenant seed'i tenant yönetimi işidir: host kullanıcısı + tenant güncelleme yetkisi. + /// Setup parolası bu uçta geçerli değildir; veritabanı ayakta olmadan tenant da yoktur. + /// + private async Task<(bool Authorized, string Reason)> AuthorizeTenantSeedRequestAsync() + { + if (User?.Identity?.IsAuthenticated != true) + { + return (false, "Authentication is required to seed tenant data."); + } + + if (_currentTenant.Id is not null) + { + return (false, "Tenant data can only be seeded by a host user."); + } + + return (await _authorizationService.AuthorizeAsync(User, TenantManagementPermissions.Tenants.Update)).Succeeded + ? (true, string.Empty) + : (false, $"The '{TenantManagementPermissions.Tenants.Update}' permission is required."); + } + private async Task CanMigrateAsync() { if (User?.Identity?.IsAuthenticated != true) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SeedTenantInput.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SeedTenantInput.cs new file mode 100644 index 00000000..27f66fe7 --- /dev/null +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SeedTenantInput.cs @@ -0,0 +1,11 @@ +namespace Sozsoft.Platform.DbStartup; + +/// +/// /api/setup/tenants/{id}/seed isteği: tenant'ın yönetici hesabı seed sırasında bu bilgilerle kurulur. +/// +public sealed class SeedTenantInput +{ + public string AdminEmail { get; init; } = string.Empty; + + public string AdminPassword { get; init; } = string.Empty; +} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs index 5638dd33..23ec339b 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupMigrationRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; @@ -42,11 +43,15 @@ public static class SetupMigrationRunner /// uygulamanın ApplicationStopping token'ı olmalıdır. Aksi halde kullanıcı sekmeyi /// kapattığında migration yarıda kesilir ve veritabanı tutarsız kalır. /// + /// + /// Alt sürece ek ortam değişkenleri (ör. tek tenant seed'i). Gizli değer taşıyabilir; loglanmaz. + /// public static async Task ExecuteAsync( IConfiguration configuration, string contentRootPath, SetupEventStream stream, - CancellationToken processCancellationToken) + CancellationToken processCancellationToken, + IReadOnlyDictionary? environment = null) { if (!await ExecutionGate.WaitAsync(0, CancellationToken.None)) { @@ -57,7 +62,7 @@ public static class SetupMigrationRunner try { - return await ExecuteCoreAsync(configuration, contentRootPath, stream, processCancellationToken); + return await ExecuteCoreAsync(configuration, contentRootPath, stream, processCancellationToken, environment); } finally { @@ -69,7 +74,8 @@ public static class SetupMigrationRunner IConfiguration configuration, string contentRootPath, SetupEventStream stream, - CancellationToken processCancellationToken) + CancellationToken processCancellationToken, + IReadOnlyDictionary? environment) { await stream.InfoAsync("Database migration and seeding are being initiated..."); @@ -112,6 +118,14 @@ public static class SetupMigrationRunner process.StartInfo.Environment[SeedsPathEnvironmentVariable] = SeedPathResolver.GetRootPath(configuration); + if (environment is not null) + { + foreach (var (name, value) in environment) + { + process.StartInfo.Environment[name] = value; + } + } + process.Start(); async Task PumpAsync(StreamReader reader, string level) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs index 8aecff2b..4c1f2af8 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs @@ -10,14 +10,13 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.DependencyInjection; namespace Sozsoft.Platform.DynamicServices; /// /// Dinamik assembly'leri runtime'da DI ve MVC sistemine kaydeden background service /// -public class DynamicAssemblyRegistrationService : BackgroundService, ITransientDependency +public class DynamicAssemblyRegistrationService : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs index d833b3c9..af9e7a08 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/PlatformApplicationConfigurationContributor.cs @@ -9,14 +9,14 @@ using Sozsoft.Platform.Identity; using Sozsoft.Platform.Public; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; +using Volo.Abp.Linq; using Volo.Abp.Identity; using Volo.Abp.Users; namespace Sozsoft.Platform.Extensions; -public class PlatformApplicationConfigurationContributor : IApplicationConfigurationContributor, ITransientDependency +public class PlatformApplicationConfigurationContributor : IApplicationConfigurationContributor { public async Task ContributeAsync(ApplicationConfigurationContributorContext context) { @@ -30,13 +30,13 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura // version.json isteği yoktur). Yalnızca EN SON sürüm taşınır; tam liste // ChangeLog ekranının kendi endpoint'inden (/api/app/change-log) gelir, // aksi halde her config isteğinde bütün changelog gönderilirdi. + // Her sayfa açılışında çalışır; yayınlanmış bütün sürüm notları yerine yalnızca en yenisi okunur. var changeLogRepository = context.ServiceProvider.GetRequiredService>(); - var changeLogs = await changeLogRepository.GetListAsync(x => x.IsPublished); - - var latest = changeLogs + var asyncExecuter = context.ServiceProvider.GetRequiredService(); + var latest = await asyncExecuter.FirstOrDefaultAsync((await changeLogRepository.GetQueryableAsync()) + .Where(note => note.IsPublished) .OrderByDescending(note => note.ReleaseDate) - .ThenByDescending(note => note.Version) - .FirstOrDefault(); + .ThenByDescending(note => note.Version)); context.ApplicationConfiguration.SetProperty("changeLogs", new { @@ -85,11 +85,16 @@ public class PlatformApplicationConfigurationContributor : IApplicationConfigura if (currentUser.Id.HasValue) { var sessionRepository = context.ServiceProvider.GetRequiredService(); - var sessions = await sessionRepository.GetListAsync(userId: currentUser.Id.Value); var claimSessionId = currentUser.FindClaim(PlatformClaimTypes.SessionId.Key)?.Value; - var sessionId = sessions.Exists(session => session.SessionId == claimSessionId) + + // Olağan yol token'daki oturumun hâlâ var olmasıdır; kullanıcının bütün oturumları yalnızca + // o bulunamazsa (en son açılanı seçmek için) okunur. + var claimSession = claimSessionId == null ? null : await sessionRepository.FindAsync(claimSessionId); + var sessionId = claimSession?.UserId == currentUser.Id.Value ? claimSessionId - : sessions.OrderByDescending(session => session.SignedIn).FirstOrDefault()?.SessionId; + : (await sessionRepository.GetListAsync(userId: currentUser.Id.Value)) + .OrderByDescending(session => session.SignedIn) + .FirstOrDefault()?.SessionId; context.ApplicationConfiguration.CurrentUser.SessionId = sessionId; context.ApplicationConfiguration.SetProperty("currentUserSessionId", sessionId); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformCorsOriginValidator.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformCorsOriginValidator.cs new file mode 100644 index 00000000..57b049d6 --- /dev/null +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformCorsOriginValidator.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; + +namespace Sozsoft.Platform; + +/// +/// Uygulamanın tek CORS politikasının origin kararı. Önceden iki ayrı politika (Program.cs'teki +/// "Dynamic" ve modüldeki varsayılan) art arda çalışıyordu; bir origin ikisinden birine uyuyorsa +/// izinlidir, böylece iki kural kümesi de aynen geçerli kalır. +/// +public sealed class PlatformCorsOriginValidator +{ + private static readonly string[] LoopbackHosts = ["localhost", "127.0.0.1", "[::1]"]; + + private readonly bool _isProduction; + private readonly string? _baseDomain; + private readonly string[] _configuredOrigins; + + /// App:CorsOrigins: virgülle ayrılmış, https://*.alan.com joker alt alan adı destekli. + public PlatformCorsOriginValidator(bool isProduction, string? baseDomain, string? corsOrigins) + { + _isProduction = isProduction; + _baseDomain = string.IsNullOrWhiteSpace(baseDomain) ? null : baseDomain.Trim().ToLowerInvariant(); + _configuredOrigins = (corsOrigins ?? string.Empty) + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(o => o.TrimEnd('/')) + .ToArray(); + } + + public bool IsAllowed(string origin) + { + if (!Uri.TryCreate(origin, UriKind.Absolute, out var uri)) + { + return false; + } + + return IsAllowedByEnvironmentRule(uri) || IsAllowedByConfiguredOrigins(origin, uri); + } + + /// Eski "Dynamic" politikası: ortama göre ana alan adı, geliştirme makinesi ve listedeki host'lar. + private bool IsAllowedByEnvironmentRule(Uri uri) + { + var scheme = uri.Scheme.ToLowerInvariant(); + var host = uri.Host.ToLowerInvariant(); + + if (_isProduction) + { + if (scheme != Uri.UriSchemeHttps) + { + return false; + } + + if (_baseDomain is not null && (host == _baseDomain || host.EndsWith("." + _baseDomain, StringComparison.Ordinal))) + { + return true; + } + + return _configuredOrigins.Any(o => + Uri.TryCreate(o, UriKind.Absolute, out var allowed) + && allowed.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) + && allowed.Host.Equals(host, StringComparison.OrdinalIgnoreCase)); + } + + if ((scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp) && LoopbackHosts.Contains(host)) + { + return true; + } + + if (_baseDomain is not null && host == $"dev.{_baseDomain}") + { + return true; + } + + return _configuredOrigins.Any(o => + Uri.TryCreate(o, UriKind.Absolute, out var allowed) + && allowed.Host.Equals(host, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Eski varsayılan politika: WithOrigins(App:CorsOrigins) + + /// SetIsOriginAllowedToAllowWildcardSubdomains. Joker eşleşme ASP.NET Core'un kuralıyla + /// aynıdır: şema ve port aynı, host .alan ile biter. + /// + private bool IsAllowedByConfiguredOrigins(string origin, Uri uri) + { + foreach (var allowed in _configuredOrigins) + { + if (allowed.Contains("*.", StringComparison.Ordinal)) + { + if (Uri.TryCreate(allowed.Replace("*.", string.Empty, StringComparison.Ordinal), UriKind.Absolute, out var domain) + && uri.Scheme == domain.Scheme + && uri.Port == domain.Port + && uri.Host.EndsWith("." + domain.Host, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + else if (allowed.Equals(origin, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } +} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index e6e364dd..bd01f5b9 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -134,7 +133,6 @@ public class PlatformHttpApiHostModule : AbpModule //Tenant bazında lokalizasyon ayarları için //TenantLocalization Middleware kaydı context.Services.AddTransient(); - context.Services.AddTransient(); ConfigureAuthentication(context); ConfigureBundles(); @@ -258,18 +256,19 @@ public class PlatformHttpApiHostModule : AbpModule private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) { + var originValidator = new PlatformCorsOriginValidator( + context.Services.GetHostingEnvironment().IsProduction(), + configuration["App:BaseDomain"], + configuration["App:CorsOrigins"]); + context.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder - .WithOrigins(configuration["App:CorsOrigins"]? - .Split(",", StringSplitOptions.RemoveEmptyEntries) - .Select(o => o.RemovePostFix("/")) - .ToArray() ?? Array.Empty()) + .SetIsOriginAllowed(originValidator.IsAllowed) .WithAbpExposedHeaders() .WithExposedHeaders(["X-Correlation-Id"]) - .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); @@ -335,9 +334,6 @@ public class PlatformHttpApiHostModule : AbpModule { options.KeyPrefix = PlatformConsts.AppName; }); - Configure(options => - { - }); } private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration) @@ -483,6 +479,10 @@ public class PlatformHttpApiHostModule : AbpModule // Setup delegate for dynamic service unregistration DynamicServiceCompiler.NotifyAssemblyUnregistration = DynamicAssemblyRegistrationService.RequestAssemblyUnregistration; + // Preflight'ı ve hata yanıtlarını da kapsasın diye boru hattının en başında; endpoint'e özel + // [EnableCors] kullanılmadığı için routing'i beklemesi gerekmez. + app.UseCors(); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -500,7 +500,6 @@ public class PlatformHttpApiHostModule : AbpModule app.UseResponseCompression(); app.MapAbpStaticAssets(); app.UseRouting(); - app.UseCors(); // Not: /api/setup/application-status artık SetupController tarafından karşılanıyor. // Buradaki kopya middleware kaldırıldı — durum mantığı tek yerde (DatabaseReadinessChecker). diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs index 2da9fcd8..6117d5e0 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Program.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.Hosting; using Serilog; using Serilog.Sinks.PostgreSQL; using Sozsoft.Platform.DbStartup; -using Sozsoft.Platform.DynamicServices; using Sozsoft.Platform.Enums; using static Sozsoft.Settings.SettingsConsts; @@ -111,78 +110,9 @@ public class Program .UseSerilog(); await builder.AddApplicationAsync(); - var baseDomain = builder.Configuration["App:BaseDomain"]?.Trim(); - var extraOrigins = (builder.Configuration["App:CorsOrigins"] ?? "") - .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - - builder.Services.AddCors(options => - { - options.AddPolicy("Dynamic", policy => - { - policy - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials() - .SetIsOriginAllowed(origin => - { - if (!Uri.TryCreate(origin, UriKind.Absolute, out var uri)) - return false; - - var scheme = uri.Scheme.ToLowerInvariant(); - var host = uri.Host.ToLowerInvariant(); - - if (builder.Environment.IsProduction()) //IsProduction - { - if (scheme != "https") return false; - - if (!string.IsNullOrWhiteSpace(baseDomain)) - { - var bd = baseDomain.ToLowerInvariant(); - if (host == bd || host.EndsWith("." + bd)) - return true; - } - - foreach (var o in extraOrigins) - { - if (Uri.TryCreate(o, UriKind.Absolute, out var eo) - && eo.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) - && eo.Host.Equals(host, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - else //IsDevelopment - { - if (scheme == "https" && (host == "localhost" || host == "127.0.0.1" || host == "[::1]") - || (scheme == "http" && (host == "localhost" || host == "127.0.0.1" || host == "[::1]")) - || (!string.IsNullOrWhiteSpace(baseDomain) && (host == $"dev.{baseDomain.ToLowerInvariant()}"))) - return true; - - foreach (var o in extraOrigins) - { - if (Uri.TryCreate(o, UriKind.Absolute, out var eo) - && eo.Host.Equals(host, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - }); - }); - }); - var app = builder.Build(); app.UseDevExpressControls(); - app.UseCors("Dynamic"); - // Dynamic Assembly Registration Delegate Setup - DynamicServiceCompiler.NotifyAssemblyRegistration = DynamicAssemblyRegistrationService.RequestAssemblyRegistration; - DynamicServiceCompiler.NotifyAssemblyUnregistration = DynamicAssemblyRegistrationService.RequestAssemblyUnregistration; await app.InitializeApplicationAsync(); await app.RunAsync(); return 0; diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj index 64b33d50..359df5ae 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj @@ -27,7 +27,6 @@ - diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationInitializer.cs b/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationInitializer.cs index efa731b3..ead30807 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationInitializer.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationInitializer.cs @@ -1,55 +1,93 @@ -using System; +using System; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; -using Volo.Abp.Settings; +using Volo.Abp.Localization; +using Volo.Abp.SettingManagement; using Volo.Abp.Timing; namespace Sozsoft.Platform.Localization; +/// +/// Sürecin varsayılan kültürünü ve tipini dil/saat dilimi ayarlarına göre tutar. +/// +/// İsteğin kültürü (tarih, sayı, metin) burada belirlenmez: UseAbpRequestLocalization onu her +/// istekte UI'ın gönderdiği Accept-Language'tan kurar. Buradaki değerler istek dışında çalışan +/// iş parçacıkları (Hangfire işleri, worker'lar) içindir ve süreç genelinde tektir; bu yüzden istekteki +/// kiracı ya da kullanıcı değil, global ayar okunur. Aksi hâlde eşzamanlı istekler birbirinin değerini ezerdi. +/// public class TenantLocalizationInitializer : ITransientDependency { - private readonly ISettingProvider _settingProvider; + // Ayar değişikliği yeniden başlatma gerektirmesin ama her istek ayar okumasın. + private static readonly long RefreshIntervalMs = (long)TimeSpan.FromMinutes(1).TotalMilliseconds; + private static long _nextRefreshAt; + + private readonly ISettingManager _settingManager; private readonly IOptions _clockOptions; public TenantLocalizationInitializer( - ISettingProvider settingProvider, + ISettingManager settingManager, IOptions clockOptions) { - _settingProvider = settingProvider; + _settingManager = settingManager; _clockOptions = clockOptions; } public async Task ApplyTenantSettingsAsync() { - // Dil ayarı - var lang = await _settingProvider.GetOrNullAsync("Abp.Localization.DefaultLanguage") ?? "en"; + var now = Environment.TickCount64; + var due = Interlocked.Read(ref _nextRefreshAt); + if (now < due || Interlocked.CompareExchange(ref _nextRefreshAt, now + RefreshIntervalMs, due) != due) + { + return; + } + + var language = await _settingManager.GetOrNullGlobalAsync(LocalizationSettingNames.DefaultLanguage) ?? "en"; + var timeZoneId = await _settingManager.GetOrNullGlobalAsync(TimingSettingNames.TimeZone) ?? "UTC"; + + ApplyCulture(language); + ApplyClockKind(timeZoneId); + } + + private static void ApplyCulture(string language) + { + CultureInfo culture; try { - var culture = new CultureInfo(lang); + culture = new CultureInfo(language); + } + catch (CultureNotFoundException) + { + culture = CultureInfo.InvariantCulture; + } + + if (CultureInfo.DefaultThreadCurrentCulture?.Name != culture.Name) + { CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture; } - catch - { - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; - } + } - // Saat tipi (UTC / Local) - var timeZoneId = await _settingProvider.GetOrNullAsync("Abp.Timing.TimeZone") ?? "UTC"; + private void ApplyClockKind(string timeZoneId) + { + DateTimeKind kind; try { - var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); - _clockOptions.Value.Kind = tz.Id.Contains("UTC", StringComparison.OrdinalIgnoreCase) + var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId); + kind = timeZone.Id.Contains("UTC", StringComparison.OrdinalIgnoreCase) ? DateTimeKind.Utc : DateTimeKind.Local; } - catch + catch (Exception ex) when (ex is TimeZoneNotFoundException or InvalidTimeZoneException) { - _clockOptions.Value.Kind = DateTimeKind.Utc; + kind = DateTimeKind.Utc; + } + + if (_clockOptions.Value.Kind != kind) + { + _clockOptions.Value.Kind = kind; } } } - diff --git a/api/test/Sozsoft.Platform.EntityFrameworkCore.Tests/DynamicDataRepository_Tests.cs b/api/test/Sozsoft.Platform.EntityFrameworkCore.Tests/DynamicDataRepository_Tests.cs index 6483d708..089c0d08 100644 --- a/api/test/Sozsoft.Platform.EntityFrameworkCore.Tests/DynamicDataRepository_Tests.cs +++ b/api/test/Sozsoft.Platform.EntityFrameworkCore.Tests/DynamicDataRepository_Tests.cs @@ -93,14 +93,13 @@ public class DynamicDataRepository_Tests : PlatformEntityFrameworkCoreTestBase } catch (Exception e) when (e.Message == "Invalid object name 'Test'.") { - await unitOfWorkManager.Current.RollbackAsync(); + // WithUnitOfWorkAsync içinde her zaman açık bir unit of work vardır. + if (unitOfWorkManager.Current is { } unitOfWork) + { + await unitOfWork.RollbackAsync(); + } } }); - - //var user = await repo.QueryAsync("select Name from Test where Id = 3", ConnectionString); - //var user2 = await repo.QueryAsync("select Name from Test2 where Id = 3", ConnectionString2); - //user.FirstOrDefault().ShouldBeNull(); - //user2.FirstOrDefault().ShouldBeNull(); } public override void Dispose() diff --git a/claude.md b/claude.md index 8be2100e..3ce39a5f 100644 --- a/claude.md +++ b/claude.md @@ -111,7 +111,7 @@ These sections encode expectations the user will **not** repeat in every prompt. | `api/` kod standardı (sürüm, nullable, ölü kod, modern C#, ABP katmanları) | `.github/instructions/dotnet.instructions.md` | | Modül/liste ekleme prosedürü, seeder dosya sorumlulukları | `.github/instructions/list.instructions.md` | | Ne nerede yaşıyor, hangi ekran ne yapıyor | `README.md` | -| Bir modülün nasıl çalıştığı, analizi ve kararları | `configs/seeds/host/docs/modules/{Modül}.md` (dizin: `README.md` §10.5) | +| Bir modülün nasıl çalıştığı, analizi ve kararları | `configs/seeds/docs/modules/{Modül}.md` (dizin: `README.md` §10.5) | | Yetki kodları | `PlatformConsts.AppCodes` (backend) + `constants/permission.constant.ts` (UI) | | Seed yolları | `SeedPathResolver` | @@ -184,7 +184,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → Alt form ekranının menü/yetki/düzenleme kuralları (menüsüz alt formun yetkisi ana ekranın altında, `Form` sekmesi tek kayıt + `AllowAdding`/`AllowUpdating` açık): `lowcode.instructions.md` §4.6. 14. **Modül dokümanı** → MCP ya da wizard ile oluşturulan her modül çalışma şeklini - `configs/seeds/{kapsam}/docs/modules/{Modül}.md` dosyasında taşır: keşif cevapları ve onaylanan + `configs/seeds/docs/modules/{Modül}.md` dosyasında taşır: keşif cevapları ve onaylanan tarif (analiz), ekranlar, veri modeli, hesaplar/durum geçişleri, yetki dağılımı, entegrasyonlar, sık sorulanlar. Dosyayı ve "Ek — Ekran envanteri"ni deploy kendisi açar/yeniler; anlatım bölümlerini (`module-doc:todo`) üreten doldurur — MCP'de `save_module_doc`, Claude Code'da dosyaya @@ -226,7 +226,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → sözleşmesi düzdür, dönüşüm `WizardSeedFileDto.ToSeedJson` / `FromSeedJson` içindedir. **Wizard seed dosyası her zaman bu iki metotla okunup yazılır**; düz `JsonSerializer` çağrısı bloklu bölümleri sessizce düşürür. -- **Wizard ile üretilen ekran** → seed dosyası (`configs/seeds/{kapsam}/wizard/{WizardName}.json`) +- **Wizard ile üretilen ekran** → seed dosyası (`configs/seeds/wizard/{WizardName}.json`) wizard cevaplarının yanında ekranın `GridOptionsEditDto` (`ListForm`) ve alanların `ColumnFormatEditDto` (`Fields`) sözleşmesini taşır. ListForm detayından yapılan her kayıt (`ListFormsAppService`, `ListFormFieldsAppService`, `ListFormJsonRowAppService`) bu bölümleri @@ -291,7 +291,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → - **MCP ile demo veri** → geliştirme isteği örnek satır taşıyorsa (prompt'taki tablo/liste, ekran görüntüsü) ekran `deploy_wizard`'a `SeedData: true` ile gider (`SeedFilePath = data/{ListFormCode}.json` + `SeedSync*` açık, `ListFormWizardDto.BuildListForm`) ve satırlar aynı çağrıda `seedRows` ile - `configs/seeds/host/data/{ListFormCode}.json` dosyasına yazılır (`McpDevelopAppService`; alan adları + `configs/seeds/data/{ListFormCode}.json` dosyasına yazılır (`McpDevelopAppService`; alan adları ekrana göre doğrulanır, `Guid` anahtarı sunucu üretir). Migrate ekranı kurar, ardından satırları ekler; MCP migrate'i beklemez. Talep döngüsü `Testing`'de biter, `Staging`'e kullanıcı alır. Dosya elle yazılmaz, görselden okunamayan değer uydurulmaz. `SeedData` wizard ekranında sorulmaz ama düzenlemede korunur (`RefreshFrom` + UI modeli). @@ -340,8 +340,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → Administration › Bildirimler altındadır. Ayrı bir manuel gönderim tablosu yoktur: **Bildirimler** ekranının ekleme formu elle gönderimdir, uç alıcı × kanal açılımını kuyruğa yazar (`list-form-dynamic-api/notification-insert`). Yeni kiracıya varsayılan tür/kural kümesi - kopyalanır (`NotificationTenantDefaultsSeedContributor`); kaynak host kapsamındaki seed - dosyalarıdır, satırlar yeni kimliklerle yazılır. Kiracı verisine dokunan zamanlanmış işin + gelir: kendi veritabanı olan kiracı seed dosyasından alır, host veritabanını paylaşana + `NotificationTenantDefaultsSeedContributor` aynı dosyalardan yeni kimliklerle kopyalar; iki yol + aynı kiracıda çalışmaz. Kiracı verisine dokunan zamanlanmış işin `Scope` alanı `Tenant` ya da `Host ve Kiracılar` olmalıdır — `Host` kapsamında çalışan bir iş kiracı satırlarını göremez (`WorkerScopeEnum`, `PlatformBackgroundWorker`). - **Bildirim bayrakları** → sıra değişmez: `IsSent` (iletildi) olmadan `IsRead` yazılmaz, `IsRead` @@ -365,7 +366,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → yazıları) `SeoAppService` içindedir; yeni bir public kaynak türü eklenirse toplayıcıya da eklenir, yoksa sayfa sitemap'te görünmez. Yeni bir meta alanı = iki entity + `SeoRouteDto` + `SeoFields` (seeder) + `SeoHead` birlikte. -- **Yeni bir dil anahtarı** → `configs/seeds/host/data/App.Languages.LanguageText.json` (EN + TR iki satır; +- **Yeni bir dil anahtarı** → `configs/seeds/data/App.Languages.LanguageText.json` (EN + TR iki satır; `Id` = `Sas_H_LanguageText:{ResourceName}:{Key}:{CultureName}` adından URL isim alanlı v5 GUID); 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 @@ -419,8 +420,9 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → olmasıdır: tabloya dair bir bulgu üretirken `TableName` (+ tablo hedefte yoksa `TableMissing`) doldurulur, istemci tablo tasarımcısını buna göre açar. - **`ListForm.SeedFilePath` dolu bir ekran** → `SeedSyncInsert/SeedSyncUpdate/SeedSyncDelete` - bayraklarından işaretli olan işlemler kapsam klasöründeki hedefe yansır - (`ListFormSeedDataSynchronizer`). Hedef iki biçimdedir ve ayrım uzantıdadır: `.json` ile biten + bayraklarından işaretli olan işlemler seed klasöründeki hedefe yansır + (`ListFormSeedDataSynchronizer`); yalnızca host bağlamındaki kayıt yansır, kiracının kaydı + veritabanında kalır. Hedef iki biçimdedir ve ayrım uzantıdadır: `.json` ile biten değer **tek dosyadır** (`data/{ListFormCode}.json`, bütün satırlar orada), uzantısız değer **klasördür** (`data/{ListFormCode}/{anahtar}.json`, her satır kendi dosyasında) ve klasör yoksa ilk kayıtta oluşturulur. Klasör modu satırı büyük ya da sık yazılan ekranlar içindir @@ -434,6 +436,21 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → eklenir, varsa dokunulmaz; import'ta `overwriteExisting` açıktır: var olan kayıt dosyadaki değerlerle güncellenir ve soft delete izleri (`IsDeleted`/`DeletionTime`/`DeleterId`) temizlenir. Satırı veritabanından okuyan senkronizasyon açık UoW varsa commit sonrasında kendi UoW'sinde çalışır (EF ile yazan çağıran, ör. `MenuAppService`, aksi hâlde eski değeri dosyaya yazardı); yalnızca dosyaya dokunan `RemoveRowsByFieldAsync` hemen çalışır. Yeni bir kayıt yolu (yeni endpoint, toplu işlem, tipli AppService) eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar. +- **Seed kapsamı** → `configs/seeds/` tek klasördür; `host/` ya da `tenants/{tenantId}/` yoktur. + Host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur, host veritabanını paylaşan + tenant'ta dosyalar yeniden uygulanmaz (`SeedFileScope`; migrate kararı `DataSeedContext`'e yazar). + Dosyayı okuyan yeni bir seeder `SeedFileScope.IsEnabled` ile başlar. Dosyalar yalnızca host + bağlamında yazılır; dosyaya yazan yeni bir yol tenant bağlamında yazmaz. Bekleyen wizard kurulumu + (`Pending`) dosyaları uygulayan son veritabanında kapanır (`SeedFileScope.CompletesPending`). + Tek istisna `{sql|postgres}/host/{object,execute}`: yalnızca host'ta çalışır; tenant veritabanında + karşılığı olmayan nesneye (HangFire tabloları) dayanan script buraya konur, tablo önekine bakılmaz. + Kendi veritabanı olan tenant'ta EF ile yalnızca `IMultiTenant` varlıklar o veritabanına gider; + ListForm, menü ve yetki tanımını ABP her zaman host'ta okur. Kiracı tablosunun satırları kiracı + adıyla kayıtlı `DataSource` üzerinden gider. Kiracı yalnızca kimliğiyle değiştirildiğinde ad boş + kalır; bu durumda adı `DataSourceManager` kiracı kaydından okur. Yeni tenant yöneticisi ilk + seed'de kiracı tarafı bütün yetkileri alır (`PlatformIdentityDataSeeder`). Bunun nedeni, + DbMigrator'da dinamik yetki deposunun kapalı olmasıdır: ABP'nin yetki seed'i seed dosyasından + gelen yetkileri göremez. - **Seed dosyası yazmak** → veri dosyası `ListFormSeedDataFileDto.Serialize`, wizard dosyası `WizardSeedFileDto.ToSeedJson` ile yazılır; düz `JsonSerializer.Serialize` + `File.WriteAllText` kullanılmaz. System.Text.Json emojiyi her zaman vekil çift kaçışına çevirir; dosya baştan @@ -453,7 +470,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → satırları + route `Authority`. Yetki `Modules` değeri: Saas/Administration `Sas|Adm`, İK `Sas|Hr` (`Sas` host, `Adm`/`Hr` kiracı görünürlüğü). İK departman/pozisyon `BranchId` zorunludur; seed satırı host şubesine (`PlatformConsts.Branches.BranchIdDefaultValue`) bağlanır. -- **Menü ağacı ve yetkiler** → `configs/seeds/host/data/App.Menus.{Routes,Module,Menu}.json` ve +- **Menü ağacı ve yetkiler** → `configs/seeds/data/App.Menus.{Routes,Module,Menu}.json` ve `Abp.Identity.Permission{Groups,s}.json`; platformun kendi menü/yetki kayıtları da diğer liste verisiyle aynı mekanizmadan (`ListFormSeedDataApplier`) geçer, ayrı bir seeder yoktur. `Order` alanı bağımlılık sırasını kurar: route → modül → menü, yetki grubu → yetki, ülke grubu → ülke → @@ -497,7 +514,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → tipler düz metin aramasında referanssız görünür. - Yorum satırına alınmış kod bloğu bırakılmaz. - Modül/ekran/bileşen ekleyen ya da davranışını değiştiren iş modül dokümanını - (`configs/seeds/{kapsam}/docs/modules/{Modül}.md`) yazar veya günceller; liste ekranının açıklaması + (`configs/seeds/docs/modules/{Modül}.md`) yazar veya günceller; liste ekranının açıklaması ayrıntılıdır (standing default 14–15). - `api/` değişikliğinden sonra: `dotnet build -p:EnforceCodeStyleInBuild=true` — 0 hata, yeni `IDE00xx` yok. diff --git a/configs/seeds/host/crud/AbpRoles.json b/configs/seeds/crud/AbpRoles.json similarity index 100% rename from configs/seeds/host/crud/AbpRoles.json rename to configs/seeds/crud/AbpRoles.json diff --git a/configs/seeds/host/crud/AbpUserRoles.json b/configs/seeds/crud/AbpUserRoles.json similarity index 100% rename from configs/seeds/host/crud/AbpUserRoles.json rename to configs/seeds/crud/AbpUserRoles.json diff --git a/configs/seeds/host/crud/AbpUsers.json b/configs/seeds/crud/AbpUsers.json similarity index 100% rename from configs/seeds/host/crud/AbpUsers.json rename to configs/seeds/crud/AbpUsers.json diff --git a/configs/seeds/host/data/Abp.Identity.PermissionGroups.json b/configs/seeds/data/Abp.Identity.PermissionGroups.json similarity index 100% rename from configs/seeds/host/data/Abp.Identity.PermissionGroups.json rename to configs/seeds/data/Abp.Identity.PermissionGroups.json diff --git a/configs/seeds/host/data/Abp.Identity.Permissions.json b/configs/seeds/data/Abp.Identity.Permissions.json similarity index 99% rename from configs/seeds/host/data/Abp.Identity.Permissions.json rename to configs/seeds/data/Abp.Identity.Permissions.json index db171f5a..82b4bcc6 100644 --- a/configs/seeds/host/data/Abp.Identity.Permissions.json +++ b/configs/seeds/data/Abp.Identity.Permissions.json @@ -1077,9 +1077,9 @@ { "GroupName": "App.Saas", "Name": "App.DevRequests", - "ParentName": null, + "ParentName": "App.DeveloperKit", "DisplayName": "App.DevRequests", - "Order": 3, + "Order": 1, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1149,7 +1149,7 @@ "Name": "AbpTenantManagement.Tenants", "ParentName": null, "DisplayName": "AbpTenantManagement.Tenants", - "Order": 4, + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1239,7 +1239,7 @@ "Name": "App.Branches", "ParentName": null, "DisplayName": "App.Branches", - "Order": 5, + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1299,7 +1299,7 @@ "Name": "App.SettingDefinitions", "ParentName": null, "DisplayName": "App.SettingDefinitions", - "Order": 6, + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1359,7 +1359,7 @@ "Name": "App.Languages", "ParentName": null, "DisplayName": "App.Languages", - "Order": 7, + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1499,7 +1499,7 @@ "Name": "App.DataSource", "ParentName": null, "DisplayName": "App.DataSource", - "Order": 8, + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1559,7 +1559,7 @@ "Name": "App.BackgroundWorkers", "ParentName": null, "DisplayName": "App.BackgroundWorkers", - "Order": 10, + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1689,7 +1689,7 @@ "Name": "App.Menus", "ParentName": null, "DisplayName": "App.Menus", - "Order": 11, + "Order": 10, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1889,7 +1889,7 @@ "Name": "App.DeveloperKit", "ParentName": null, "DisplayName": "App.DeveloperKit", - "Order": 12, + "Order": 11, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1899,7 +1899,7 @@ "Name": "App.SqlQueryManager", "ParentName": "App.DeveloperKit", "DisplayName": "App.SqlQueryManager", - "Order": 1, + "Order": 2, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1919,7 +1919,7 @@ "Name": "App.Files", "ParentName": "App.DeveloperKit", "DisplayName": "App.Files", - "Order": 2, + "Order": 3, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -1969,7 +1969,7 @@ "Name": "App.DeveloperKit.CustomEndpoints", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.CustomEndpoints", - "Order": 3, + "Order": 4, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2069,7 +2069,7 @@ "Name": "App.DeveloperKit.DynamicServices", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.DynamicServices", - "Order": 4, + "Order": 5, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2149,7 +2149,7 @@ "Name": "App.DeveloperKit.CustomComponents", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.CustomComponents", - "Order": 5, + "Order": 6, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2209,7 +2209,7 @@ "Name": "App.Listforms.Listform", "ParentName": "App.DeveloperKit", "DisplayName": "App.Listforms.Listform", - "Order": 6, + "Order": 7, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2309,7 +2309,7 @@ "Name": "App.DeveloperKit.IntranetWidgets", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.IntranetWidgets", - "Order": 7, + "Order": 8, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2369,7 +2369,7 @@ "Name": "App.DeveloperKit.McpServers", "ParentName": "App.DeveloperKit", "DisplayName": "App.DeveloperKit.McpServers", - "Order": 8, + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2449,7 +2449,7 @@ "Name": "App.Listforms.Wizard", "ParentName": "App.DeveloperKit", "DisplayName": "App.Listforms.Wizard", - "Order": 8, + "Order": 9, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2509,7 +2509,7 @@ "Name": "App.Setup.Migrate", "ParentName": "App.DeveloperKit", "DisplayName": "App.Setup.Migrate", - "Order": 9, + "Order": 10, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" @@ -2519,7 +2519,7 @@ "Name": "App.ForumManagement", "ParentName": null, "DisplayName": "App.ForumManagement", - "Order": 13, + "Order": 12, "IsEnabled": true, "MultiTenancySide": 2, "Modules": "Sas|Adm" diff --git a/configs/seeds/host/data/App.BackgroundWorkers.RecurringJobs.json b/configs/seeds/data/App.BackgroundWorkers.RecurringJobs.json similarity index 100% rename from configs/seeds/host/data/App.BackgroundWorkers.RecurringJobs.json rename to configs/seeds/data/App.BackgroundWorkers.RecurringJobs.json diff --git a/configs/seeds/host/data/App.BlogManagement.Category.json b/configs/seeds/data/App.BlogManagement.Category.json similarity index 100% rename from configs/seeds/host/data/App.BlogManagement.Category.json rename to configs/seeds/data/App.BlogManagement.Category.json diff --git a/configs/seeds/host/data/App.BlogManagement.Posts.json b/configs/seeds/data/App.BlogManagement.Posts.json similarity index 100% rename from configs/seeds/host/data/App.BlogManagement.Posts.json rename to configs/seeds/data/App.BlogManagement.Posts.json diff --git a/configs/seeds/host/data/App.Definitions.AiBot.json b/configs/seeds/data/App.Definitions.AiBot.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.AiBot.json rename to configs/seeds/data/App.Definitions.AiBot.json diff --git a/configs/seeds/host/data/App.Definitions.City.json b/configs/seeds/data/App.Definitions.City.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.City.json rename to configs/seeds/data/App.Definitions.City.json diff --git a/configs/seeds/host/data/App.Definitions.ContactTitle.json b/configs/seeds/data/App.Definitions.ContactTitle.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.ContactTitle.json rename to configs/seeds/data/App.Definitions.ContactTitle.json diff --git a/configs/seeds/host/data/App.Definitions.Country.json b/configs/seeds/data/App.Definitions.Country.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.Country.json rename to configs/seeds/data/App.Definitions.Country.json diff --git a/configs/seeds/host/data/App.Definitions.CountryGroup.json b/configs/seeds/data/App.Definitions.CountryGroup.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.CountryGroup.json rename to configs/seeds/data/App.Definitions.CountryGroup.json diff --git a/configs/seeds/host/data/App.Definitions.Currency.json b/configs/seeds/data/App.Definitions.Currency.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.Currency.json rename to configs/seeds/data/App.Definitions.Currency.json diff --git a/configs/seeds/host/data/App.Definitions.District.json b/configs/seeds/data/App.Definitions.District.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.District.json rename to configs/seeds/data/App.Definitions.District.json diff --git a/configs/seeds/host/data/App.Definitions.GlobalSearch.json b/configs/seeds/data/App.Definitions.GlobalSearch.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.GlobalSearch.json rename to configs/seeds/data/App.Definitions.GlobalSearch.json diff --git a/configs/seeds/host/data/App.Definitions.Sector.json b/configs/seeds/data/App.Definitions.Sector.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.Sector.json rename to configs/seeds/data/App.Definitions.Sector.json diff --git a/configs/seeds/host/data/App.Definitions.Sequence.json b/configs/seeds/data/App.Definitions.Sequence.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.Sequence.json rename to configs/seeds/data/App.Definitions.Sequence.json diff --git a/configs/seeds/host/data/App.Definitions.TerminationReason.json b/configs/seeds/data/App.Definitions.TerminationReason.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.TerminationReason.json rename to configs/seeds/data/App.Definitions.TerminationReason.json diff --git a/configs/seeds/host/data/App.Definitions.Uom.json b/configs/seeds/data/App.Definitions.Uom.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.Uom.json rename to configs/seeds/data/App.Definitions.Uom.json diff --git a/configs/seeds/host/data/App.Definitions.UomCategory.json b/configs/seeds/data/App.Definitions.UomCategory.json similarity index 100% rename from configs/seeds/host/data/App.Definitions.UomCategory.json rename to configs/seeds/data/App.Definitions.UomCategory.json diff --git a/configs/seeds/host/data/App.DevRequests/2e84f333-5344-4f28-8cc0-178b46d7c0bc.json b/configs/seeds/data/App.DevRequests/2e84f333-5344-4f28-8cc0-178b46d7c0bc.json similarity index 100% rename from configs/seeds/host/data/App.DevRequests/2e84f333-5344-4f28-8cc0-178b46d7c0bc.json rename to configs/seeds/data/App.DevRequests/2e84f333-5344-4f28-8cc0-178b46d7c0bc.json diff --git a/configs/seeds/host/data/App.DevRequests/31f9867d-f243-4e29-9819-bcd9dee19d5a.json b/configs/seeds/data/App.DevRequests/31f9867d-f243-4e29-9819-bcd9dee19d5a.json similarity index 100% rename from configs/seeds/host/data/App.DevRequests/31f9867d-f243-4e29-9819-bcd9dee19d5a.json rename to configs/seeds/data/App.DevRequests/31f9867d-f243-4e29-9819-bcd9dee19d5a.json diff --git a/configs/seeds/host/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json b/configs/seeds/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json similarity index 99% rename from configs/seeds/host/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json rename to configs/seeds/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json index e8854eb3..0f4ef9b3 100644 --- a/configs/seeds/host/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json +++ b/configs/seeds/data/App.DevRequests/5853d035-ecd7-41a3-878d-e3756b3110ee.json @@ -9,15 +9,15 @@ "TenantId": null, "UserName": "system@sozsoft.com", "RequestDate": null, - "Status": "Testing", + "Status": "Staging", "Title": "Human Resources -> Olay Günlüğü", "Description": "

Olay Günlüğü : Vardiya Amiri, Güvenlik Odası, Gözlem Odası gibi kısımların gün içerisinde önemli buldukları olayları kaydettiği listedir. Burada Vardiya Amiri denilen kişi yerine Kullanıcı listesinde olayı yaşayan kişi seçilebilir. Önem Durumu 5 seçenekli yıldızlı olabilir ve Vardiya seçilebilmeli. Ancak sistemde Vardiya tanımlama listesi yok. Ayrıca Vardiya tanımlama listesi eklenmesi gerekiyor.


Vardiya listesi Human Resources menüsündeki Definitions altına eklenmelidir. Yani şu anda Human Resources -> Definitions alt menüsü zaten var. Onun altına eklenecek. Aynı şekilde Role -> Permission kısımlarıda bu şekilde yönetilmeli.


Olay Günlüğü listesi Human Resources en altına eklenecektir.


", "DueDate": "2026-10-02T11:06:00", "Tags": "HR", "Assignees": "system@sozsoft.com", "Priority": "5", - "Completed": false, - "SortOrder": 0, + "Completed": true, + "SortOrder": 5, "Timesheets": "[{\"id\":\"3bae274a-3436-f9bc-830a-3a240ead359b\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"Talep analiz edildi\",\"duration\":0.0167},{\"id\":\"ebf2867f-f582-b7ae-3d47-3a240eadda23\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"Hr_T_Shift tablosu olu\\u015Fturuldu\",\"duration\":0.0167},{\"id\":\"e6022fc8-351b-fc03-900f-3a240eadece6\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"Hr_T_EventLog tablosu olu\\u015Fturuldu\",\"duration\":0.0167},{\"id\":\"5eb27418-4a1b-4f2f-8ad3-3a240eae2c7c\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"Shift.json wizard\\u0027\\u0131 deploy edildi (3 demo sat\\u0131r)\",\"duration\":0.0167},{\"id\":\"69d6ba44-63ea-e8a8-0063-3a240eaea22d\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"EventLog.json wizard\\u0027\\u0131 deploy edildi (0 demo sat\\u0131r)\",\"duration\":0.0167},{\"id\":\"93bb0982-d82a-186d-9bd5-3a240eaec698\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"4 tamamlanm\\u0131\\u015F alt g\\u00F6rev eklendi\",\"duration\":0.0167},{\"id\":\"8b8d12de-3242-4e40-b129-3a240eaeef11\",\"date\":\"2026-10-02\",\"userName\":\"system@sozsoft.com\",\"description\":\"Analize \\u00FCretim notu eklendi\",\"duration\":0.0167}]", "SubTasks": "[{\"id\":\"ffd91461-edf6-bbaf-bee9-3a240eaec685\",\"title\":\"create_table: Hr_T_Shift\",\"assignee\":\"system@sozsoft.com\",\"dueDate\":\"2026-10-02\",\"completed\":true},{\"id\":\"c039e7d5-b13a-2b8b-b78c-3a240eaec685\",\"title\":\"create_table: Hr_T_EventLog\",\"assignee\":\"system@sozsoft.com\",\"dueDate\":\"2026-10-02\",\"completed\":true},{\"id\":\"349095f7-bed6-e5c3-1cf0-3a240eaec685\",\"title\":\"deploy_wizard: Shift.json\",\"assignee\":\"system@sozsoft.com\",\"dueDate\":\"2026-10-02\",\"completed\":true},{\"id\":\"d9cfdc40-d467-856e-1d8b-3a240eaec685\",\"title\":\"deploy_wizard: EventLog.json\",\"assignee\":\"system@sozsoft.com\",\"dueDate\":\"2026-10-02\",\"completed\":true}]", "Analysis": "

Amaç

\n

Vardiya Amiri, Güvenlik Odası, Gözlem Odası gibi birimlerin gün içinde önemli buldukları olayları; olayı yaşayan kişi, tarih-saat, vardiya ve önem derecesiyle tek bir günlükte kaydetmesi. Bunun için gereken Vardiya tanım listesinin de eklenmesi.

\n\n

Görsel analizi

\n
    \n
  • [image-1] Vardiya Amiri Olay Günlüğü — liste + popup form. Üstte üç sekme var: Vardiya Amiri / Güvenlik / Gözlem Odası Olay Günlüğü (aynı yapıda üç günlük).
  • \n
  • Liste kolonları: KayıtID, Şube Adı, Kayıt Tarihi, Tarih, Saat, Vardiya, Vardiya Ustası, Önem Durumu, Açıklama, Kullanıcı Adı.
  • \n
  • Form alanları: Vardiya Amiri (kişi lookup), Olay Tarihi (bugün), Olay Saati (şimdi), Önem Durumu (görselde Rutin/Önemli/Çok Önemli), Vardiya, Açıklama (çok satırlı).
  • \n
  • Salt okunur alanlar: Kayıt Tarihi, Şube Adı, Kayıt (kaydı giren kullanıcı).
  • \n
  • Görselde satır yok, demo veri yok.
  • \n
\n\n

Çözüm yolu

\n

İki Wizard List ekranı: Vardiyalar (tanım) ve Olay Günlüğü. Görseldeki üç günlük tek tabloda tutulur, ayrımı LogSource alanı yapar. Önem Durumu 1–5 yıldızlı statik lookup'tır; platformda yıldız editörü yok. Custom component gerekmez.

\n\n

Ortamın durumu (keşif)

\n
    \n
  • Hr_T_Shift ve Hr_T_EventLog tabloları ile Shift.json ve EventLog.json wizard'ları yok. Üretim sıfırdan yapılır.
  • \n
  • Menü tablosunda App.Hr.Shift, App.Hr.EventLog ve App.Platform.Definitions kayıtları yok; kalıntı yok.
  • \n
  • Yetki tablosunda da bu üç kayıt yok; kalıntı yok.
  • \n
  • Hedef menü App.Hr.Definitions var (App.Hr altında).
  • \n
  • Hedef yetki App.Hr.Definitions var. App.Hr grubunda kök seviyede; Department, JobPosition, SkillType, Skill, SkillLevel, Doctor, Certificate, Hospital, HospitalDepartment ve DocumentType yetkileri bunun altında.
  • \n
\n\n

Menü yeri

\n\n\n\n\n\n\n
SeviyeKodAd (TR / EN)İkonDurum
KökApp.Hrİnsan Kaynakları / Human Resources—Var
AraApp.Hr.DefinitionsTanımlamalar / DefinitionsFcFilingCabinetVar
EkranApp.Hr.ShiftVardiyalar / ShiftsFcClockYeni — App.Hr.Definitions altında, kardeşlerin sonunda
EkranApp.Hr.EventLogOlay Günlüğü / Event LogFcViewDetailsYeni — App.Hr'nin doğrudan altında, en sonda
\n

ModuleId: Hr · İzin grubu: App.Hr · Route: /admin/list/{ListFormCode}.

\n
    \n
  • Vardiyalar: MenuParentCode App.Hr.Definitions, MenuParentPath [App.Hr].
  • \n
  • Olay Günlüğü: MenuParentCode App.Hr.
  • \n
\n\n

Yetki yeri (Rol › Yetkiler)

\n

Talebe göre yetki ağacı menüyle aynı yapıda olur.

\n
    \n
  • App.Hr (grup) › App.Hr.Definitions (var) › App.Hr.Shift › .Create .Update .Delete .Export .Import .Note
  • \n
  • App.Hr (grup) › App.Hr.EventLog (kök) › .Create .Update .Delete .Export .Import .Note
  • \n
\n

Yeni ara yetki açılmaz. Rol yetki penceresinde Human Resources altında tek bir \"Definitions\" kalır; Vardiyalar onun içinde, Olay Günlüğü en altta görünür. Deploy sonrası App.Hr.Shift yetkisinin ParentName değerinin App.Hr.Definitions olduğu yetki tablosunda doğrulanır.

\n\n

Veri modeli

\n

Tablolar yeni; kiracı, audit ve soft delete standart.

\n

Hr_T_Shift — Tanım tablosu: Evet. Alanlar:

\n
    \n
  • Name nvarchar(64), zorunlu
  • \n
  • StartTime nvarchar(5) (SS:dd)
  • \n
  • EndTime nvarchar(5) (SS:dd)
  • \n
  • IsActive bit = 1, zorunlu
  • \n
\n

Hr_T_EventLog — Tanım: Hayır. Alanlar:

\n
    \n
  • BranchId uniqueidentifier (Sas_T_Branch)
  • \n
  • LogSource nvarchar(32), zorunlu: ShiftSupervisor (Vardiya Amiri), SecurityRoom (Güvenlik Odası), ObservationRoom (Gözlem Odası)
  • \n
  • UserId uniqueidentifier, zorunlu (AbpUsers)
  • \n
  • EventDate date, zorunlu
  • \n
  • EventTime nvarchar(5), zorunlu
  • \n
  • Importance int, zorunlu, varsayılan 1
  • \n
  • ShiftId uniqueidentifier (Hr_T_Shift)
  • \n
  • Description nvarchar(MAX)
  • \n
\n

Kayıt Tarihi ve Kayıt için standart CreationTime ve CreatorId salt okunur gösterilir.

\n\n

Etkilenen kısımlar

\n
    \n
  • Var olan App.Hr.Definitions menü ve yetki dalına, ayrıca App.Hr köküne birer ekran eklenir. Yeni ara menü ya da ara yetki açılmaz.
  • \n
  • İki ekranın C/R/U/D/E/I/N yetkileri ve dil anahtarları.
  • \n
  • Var olan ekranlara dokunulmaz.
  • \n
\n\n

Görünümler ve davranış

\n
    \n
  • Grid/Card/Pivot/Chart açık, varsayılan grid. Olay Günlüğü popup formda açılır.
  • \n
  • Vardiyalar widget'ları: Toplam / Aktif / Pasif.
  • \n
  • Olay Günlüğü widget'ları: Toplam / Bugün / Önemli (Önem ≥ 4).
  • \n
  • Önem: 1 ★ Rutin, 2 ★★ Düşük, 3 ★★★ Önemli, 4 ★★★★ Çok Önemli, 5 ★★★★★ Kritik.
  • \n
  • Kaynak (LogSource) seçilir, listede filtrelenir; görseldeki üç sekmenin yerini alır.
  • \n
  • Açılışta EventDate = bugün, EventTime = şimdi.
  • \n
  • Kullanıcı ve Vardiya lookup'ları yalnız aktif kayıtları listeler.
  • \n
  • Şube alanı seçilebilir lookup'tır; ekran IsBranch: true.
  • \n
\n\n

Kararlar

\n
    \n
  • \"Human Resources › Definitions\", hem menüde hem yetki ağacında App.Hr.Definitions kodlu var olan daldır (iki tabloda da doğrulandı). Görünen adının dil anahtarı App.Platform.Definitions'tır; kod olarak kullanılmaz.
  • \n
  • Üç günlük tek ekranda birleşir, LogSource ile ayrılır (onay).
  • \n
  • Önemdeki 2 \"Düşük\" ve 5 \"Kritik\" analiz kararıdır; görseldeki üç seçenek 1, 3, 4'e eşlenir.
  • \n
  • Saatler SS:dd; saniye tutulmaz.
  • \n
  • Vardiya demo verisi: Sabah 08:00–16:00, Akşam 16:00–00:00, Gece 00:00–08:00 (onay).
  • \n
\n\n

Üretim planı

\n
    \n
  1. create_table: Hr_T_Shift
  2. \n
  3. create_table: Hr_T_EventLog
  4. \n
  5. deploy_wizard: Shift.json
  6. \n
  7. deploy_wizard: EventLog.json
  8. \n
\n\n

Üretime hazır

\n

Evet

MCP

\n

Tablolar: Hr_T_Shift, Hr_T_EventLog.

\n

Ekranlar ve yerleri:

\n
    \n
  • Vardiyalar (App.Hr.Shift): menüde ve yetki ağacında App.Hr › App.Hr.Definitions altında. Deploy menuPath'i App.Hr › App.Hr.Definitions olarak doğruladı.
  • \n
  • Olay Günlüğü (App.Hr.EventLog): App.Hr'nin doğrudan altında, en sonda.
  • \n
\n

Demo veri: Vardiyalar için 3 satır (Sabah, Akşam, Gece).

\n

Widget'lar: Vardiyalar: Toplam / Aktif / Pasif. Olay Günlüğü: Toplam / Bugün / Önemli (Önem ≥ 4).

\n

Yetki doğrulaması: Yetkiler DB Migrate ile kurulur. Deploy sonrası yetki tablosunda App.Hr.Shift, App.Hr.EventLog ve App.Platform.Definitions kayıtları yok (beklenen: migrate öncesi). Migrate sonrası App.Hr.Shift yetkisinin ParentName değerinin App.Hr.Definitions olduğu kontrol edilmeli.

\n

DB Migrate bekleyen wizard'lar: Shift.json, EventLog.json.

" diff --git a/configs/seeds/host/data/App.DevRequests/b0b200d4-f3f6-4fe3-9f11-5bff3040a110.json b/configs/seeds/data/App.DevRequests/b0b200d4-f3f6-4fe3-9f11-5bff3040a110.json similarity index 100% rename from configs/seeds/host/data/App.DevRequests/b0b200d4-f3f6-4fe3-9f11-5bff3040a110.json rename to configs/seeds/data/App.DevRequests/b0b200d4-f3f6-4fe3-9f11-5bff3040a110.json diff --git a/configs/seeds/host/data/App.DevRequests/c8017276-eef2-4ede-b060-ec86339fee18.json b/configs/seeds/data/App.DevRequests/c8017276-eef2-4ede-b060-ec86339fee18.json similarity index 100% rename from configs/seeds/host/data/App.DevRequests/c8017276-eef2-4ede-b060-ec86339fee18.json rename to configs/seeds/data/App.DevRequests/c8017276-eef2-4ede-b060-ec86339fee18.json diff --git a/configs/seeds/host/data/App.DevRequests/ec7d68c2-2dae-466a-8f8b-f28f6042746a.json b/configs/seeds/data/App.DevRequests/ec7d68c2-2dae-466a-8f8b-f28f6042746a.json similarity index 100% rename from configs/seeds/host/data/App.DevRequests/ec7d68c2-2dae-466a-8f8b-f28f6042746a.json rename to configs/seeds/data/App.DevRequests/ec7d68c2-2dae-466a-8f8b-f28f6042746a.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/1acc9af1-6d5e-2940-160e-3a237eea9c0a.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/1acc9af1-6d5e-2940-160e-3a237eea9c0a.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/1acc9af1-6d5e-2940-160e-3a237eea9c0a.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/1acc9af1-6d5e-2940-160e-3a237eea9c0a.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/23744edc-eda9-512e-8079-fe2e8e5fd2a8.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/23744edc-eda9-512e-8079-fe2e8e5fd2a8.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/23744edc-eda9-512e-8079-fe2e8e5fd2a8.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/23744edc-eda9-512e-8079-fe2e8e5fd2a8.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/3b7b16c6-aa96-5765-8e19-fe5e727feb52.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/3b7b16c6-aa96-5765-8e19-fe5e727feb52.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/3b7b16c6-aa96-5765-8e19-fe5e727feb52.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/3b7b16c6-aa96-5765-8e19-fe5e727feb52.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/514a142f-8aaa-685f-fd13-3a237eea9c27.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/514a142f-8aaa-685f-fd13-3a237eea9c27.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/514a142f-8aaa-685f-fd13-3a237eea9c27.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/514a142f-8aaa-685f-fd13-3a237eea9c27.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8389ccbc-3e7b-738d-35aa-3a237eea9b9d.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8389ccbc-3e7b-738d-35aa-3a237eea9b9d.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8389ccbc-3e7b-738d-35aa-3a237eea9b9d.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8389ccbc-3e7b-738d-35aa-3a237eea9b9d.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000101.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000101.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000101.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000101.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000102.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000102.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000102.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000102.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000103.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000103.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000103.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000103.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000104.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000104.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000104.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000104.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000105.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000105.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000105.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/8f1c2d40-0000-4000-8000-000000000105.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/932daf62-9f6c-0c8c-08c0-3a237eea9c19.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/932daf62-9f6c-0c8c-08c0-3a237eea9c19.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/932daf62-9f6c-0c8c-08c0-3a237eea9c19.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/932daf62-9f6c-0c8c-08c0-3a237eea9c19.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/b2d9fa60-b052-5039-a0fc-146f4e5b2e8c.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/b2d9fa60-b052-5039-a0fc-146f4e5b2e8c.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/b2d9fa60-b052-5039-a0fc-146f4e5b2e8c.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/b2d9fa60-b052-5039-a0fc-146f4e5b2e8c.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomComponents/d669129d-52fe-56e1-972d-d20fe1b3360e.json b/configs/seeds/data/App.DeveloperKit.CustomComponents/d669129d-52fe-56e1-972d-d20fe1b3360e.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomComponents/d669129d-52fe-56e1-972d-d20fe1b3360e.json rename to configs/seeds/data/App.DeveloperKit.CustomComponents/d669129d-52fe-56e1-972d-d20fe1b3360e.json diff --git a/configs/seeds/host/data/App.DeveloperKit.CustomEndpoints.json b/configs/seeds/data/App.DeveloperKit.CustomEndpoints.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.CustomEndpoints.json rename to configs/seeds/data/App.DeveloperKit.CustomEndpoints.json diff --git a/configs/seeds/host/data/App.DeveloperKit.IntranetWidgets.json b/configs/seeds/data/App.DeveloperKit.IntranetWidgets.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.IntranetWidgets.json rename to configs/seeds/data/App.DeveloperKit.IntranetWidgets.json diff --git a/configs/seeds/host/data/App.DeveloperKit.McpServers.json b/configs/seeds/data/App.DeveloperKit.McpServers.json similarity index 100% rename from configs/seeds/host/data/App.DeveloperKit.McpServers.json rename to configs/seeds/data/App.DeveloperKit.McpServers.json diff --git a/configs/seeds/host/data/App.ForumManagement.Categories.json b/configs/seeds/data/App.ForumManagement.Categories.json similarity index 100% rename from configs/seeds/host/data/App.ForumManagement.Categories.json rename to configs/seeds/data/App.ForumManagement.Categories.json diff --git a/configs/seeds/host/data/App.Hr.BenefitType.json b/configs/seeds/data/App.Hr.BenefitType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.BenefitType.json rename to configs/seeds/data/App.Hr.BenefitType.json diff --git a/configs/seeds/host/data/App.Hr.CandidateSource.json b/configs/seeds/data/App.Hr.CandidateSource.json similarity index 100% rename from configs/seeds/host/data/App.Hr.CandidateSource.json rename to configs/seeds/data/App.Hr.CandidateSource.json diff --git a/configs/seeds/host/data/App.Hr.Certificate.json b/configs/seeds/data/App.Hr.Certificate.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Certificate.json rename to configs/seeds/data/App.Hr.Certificate.json diff --git a/configs/seeds/host/data/App.Hr.Department.json b/configs/seeds/data/App.Hr.Department.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Department.json rename to configs/seeds/data/App.Hr.Department.json diff --git a/configs/seeds/host/data/App.Hr.DisciplinaryPenaltyType.json b/configs/seeds/data/App.Hr.DisciplinaryPenaltyType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.DisciplinaryPenaltyType.json rename to configs/seeds/data/App.Hr.DisciplinaryPenaltyType.json diff --git a/configs/seeds/host/data/App.Hr.Doctor.json b/configs/seeds/data/App.Hr.Doctor.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Doctor.json rename to configs/seeds/data/App.Hr.Doctor.json diff --git a/configs/seeds/host/data/App.Hr.DocumentType.json b/configs/seeds/data/App.Hr.DocumentType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.DocumentType.json rename to configs/seeds/data/App.Hr.DocumentType.json diff --git a/configs/seeds/host/data/App.Hr.EvaluationCriterion.json b/configs/seeds/data/App.Hr.EvaluationCriterion.json similarity index 100% rename from configs/seeds/host/data/App.Hr.EvaluationCriterion.json rename to configs/seeds/data/App.Hr.EvaluationCriterion.json diff --git a/configs/seeds/host/data/App.Hr.ExpenseType.json b/configs/seeds/data/App.Hr.ExpenseType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.ExpenseType.json rename to configs/seeds/data/App.Hr.ExpenseType.json diff --git a/configs/seeds/host/data/App.Hr.Hospital.json b/configs/seeds/data/App.Hr.Hospital.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Hospital.json rename to configs/seeds/data/App.Hr.Hospital.json diff --git a/configs/seeds/host/data/App.Hr.HospitalDepartment.json b/configs/seeds/data/App.Hr.HospitalDepartment.json similarity index 100% rename from configs/seeds/host/data/App.Hr.HospitalDepartment.json rename to configs/seeds/data/App.Hr.HospitalDepartment.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Announcement.json b/configs/seeds/data/App.Hr.Intranet.Announcement.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Announcement.json rename to configs/seeds/data/App.Hr.Intranet.Announcement.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Events.Event.json b/configs/seeds/data/App.Hr.Intranet.Events.Event.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Events.Event.json rename to configs/seeds/data/App.Hr.Intranet.Events.Event.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Events.EventCategory.json b/configs/seeds/data/App.Hr.Intranet.Events.EventCategory.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Events.EventCategory.json rename to configs/seeds/data/App.Hr.Intranet.Events.EventCategory.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Events.EventComment.json b/configs/seeds/data/App.Hr.Intranet.Events.EventComment.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Events.EventComment.json rename to configs/seeds/data/App.Hr.Intranet.Events.EventComment.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Events.EventType.json b/configs/seeds/data/App.Hr.Intranet.Events.EventType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Events.EventType.json rename to configs/seeds/data/App.Hr.Intranet.Events.EventType.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SocialComment.json b/configs/seeds/data/App.Hr.Intranet.SocialComment.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SocialComment.json rename to configs/seeds/data/App.Hr.Intranet.SocialComment.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SocialLike.json b/configs/seeds/data/App.Hr.Intranet.SocialLike.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SocialLike.json rename to configs/seeds/data/App.Hr.Intranet.SocialLike.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SocialMedia.json b/configs/seeds/data/App.Hr.Intranet.SocialMedia.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SocialMedia.json rename to configs/seeds/data/App.Hr.Intranet.SocialMedia.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SocialPollOption.json b/configs/seeds/data/App.Hr.Intranet.SocialPollOption.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SocialPollOption.json rename to configs/seeds/data/App.Hr.Intranet.SocialPollOption.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SocialPost.json b/configs/seeds/data/App.Hr.Intranet.SocialPost.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SocialPost.json rename to configs/seeds/data/App.Hr.Intranet.SocialPost.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.Survey.json b/configs/seeds/data/App.Hr.Intranet.Survey.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.Survey.json rename to configs/seeds/data/App.Hr.Intranet.Survey.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SurveyQuestion.json b/configs/seeds/data/App.Hr.Intranet.SurveyQuestion.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SurveyQuestion.json rename to configs/seeds/data/App.Hr.Intranet.SurveyQuestion.json diff --git a/configs/seeds/host/data/App.Hr.Intranet.SurveyQuestionOption.json b/configs/seeds/data/App.Hr.Intranet.SurveyQuestionOption.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Intranet.SurveyQuestionOption.json rename to configs/seeds/data/App.Hr.Intranet.SurveyQuestionOption.json diff --git a/configs/seeds/host/data/App.Hr.JobPosition.json b/configs/seeds/data/App.Hr.JobPosition.json similarity index 100% rename from configs/seeds/host/data/App.Hr.JobPosition.json rename to configs/seeds/data/App.Hr.JobPosition.json diff --git a/configs/seeds/host/data/App.Hr.LeaveType.json b/configs/seeds/data/App.Hr.LeaveType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.LeaveType.json rename to configs/seeds/data/App.Hr.LeaveType.json diff --git a/configs/seeds/host/data/App.Hr.PerformanceRating.json b/configs/seeds/data/App.Hr.PerformanceRating.json similarity index 100% rename from configs/seeds/host/data/App.Hr.PerformanceRating.json rename to configs/seeds/data/App.Hr.PerformanceRating.json diff --git a/configs/seeds/host/data/App.Hr.RejectionReason.json b/configs/seeds/data/App.Hr.RejectionReason.json similarity index 100% rename from configs/seeds/host/data/App.Hr.RejectionReason.json rename to configs/seeds/data/App.Hr.RejectionReason.json diff --git a/configs/seeds/host/data/App.Hr.Shift.json b/configs/seeds/data/App.Hr.Shift.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Shift.json rename to configs/seeds/data/App.Hr.Shift.json diff --git a/configs/seeds/host/data/App.Hr.Skill.json b/configs/seeds/data/App.Hr.Skill.json similarity index 100% rename from configs/seeds/host/data/App.Hr.Skill.json rename to configs/seeds/data/App.Hr.Skill.json diff --git a/configs/seeds/host/data/App.Hr.SkillLevel.json b/configs/seeds/data/App.Hr.SkillLevel.json similarity index 100% rename from configs/seeds/host/data/App.Hr.SkillLevel.json rename to configs/seeds/data/App.Hr.SkillLevel.json diff --git a/configs/seeds/host/data/App.Hr.SkillType.json b/configs/seeds/data/App.Hr.SkillType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.SkillType.json rename to configs/seeds/data/App.Hr.SkillType.json diff --git a/configs/seeds/host/data/App.Hr.TrainingSubject.json b/configs/seeds/data/App.Hr.TrainingSubject.json similarity index 100% rename from configs/seeds/host/data/App.Hr.TrainingSubject.json rename to configs/seeds/data/App.Hr.TrainingSubject.json diff --git a/configs/seeds/host/data/App.Hr.TrainingType.json b/configs/seeds/data/App.Hr.TrainingType.json similarity index 100% rename from configs/seeds/host/data/App.Hr.TrainingType.json rename to configs/seeds/data/App.Hr.TrainingType.json diff --git a/configs/seeds/host/data/App.Hr.VehicleDocument.json b/configs/seeds/data/App.Hr.VehicleDocument.json similarity index 100% rename from configs/seeds/host/data/App.Hr.VehicleDocument.json rename to configs/seeds/data/App.Hr.VehicleDocument.json diff --git a/configs/seeds/host/data/App.Hr.VehicleExpenseDefinition.json b/configs/seeds/data/App.Hr.VehicleExpenseDefinition.json similarity index 100% rename from configs/seeds/host/data/App.Hr.VehicleExpenseDefinition.json rename to configs/seeds/data/App.Hr.VehicleExpenseDefinition.json diff --git a/configs/seeds/host/data/App.Hr.WorkCalendar.json b/configs/seeds/data/App.Hr.WorkCalendar.json similarity index 100% rename from configs/seeds/host/data/App.Hr.WorkCalendar.json rename to configs/seeds/data/App.Hr.WorkCalendar.json diff --git a/configs/seeds/host/data/App.Languages.Language.json b/configs/seeds/data/App.Languages.Language.json similarity index 100% rename from configs/seeds/host/data/App.Languages.Language.json rename to configs/seeds/data/App.Languages.Language.json diff --git a/configs/seeds/host/data/App.Languages.LanguageText.json b/configs/seeds/data/App.Languages.LanguageText.json similarity index 99% rename from configs/seeds/host/data/App.Languages.LanguageText.json rename to configs/seeds/data/App.Languages.LanguageText.json index 3403060f..f61a1cf9 100644 --- a/configs/seeds/host/data/App.Languages.LanguageText.json +++ b/configs/seeds/data/App.Languages.LanguageText.json @@ -3224,6 +3224,20 @@ "Key": "AbpTenantManagement.Tenants.DatabaseSeeded", "Value": "Veritabanı oluşturuldu." }, + { + "Id": "821df335-46e8-501c-8991-460c55961578", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "AbpTenantManagement.Tenants.DatabaseSeedLogs", + "Value": "Database Seed Logs" + }, + { + "Id": "43d9e9d9-2152-59a2-ba64-3bc116724974", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "AbpTenantManagement.Tenants.DatabaseSeedLogs", + "Value": "Veritabanı Oluşturma Logları" + }, { "Id": "d9865a1e-0950-5e7d-afd8-c505a3b8d1a3", "CultureName": "en", @@ -67377,42 +67391,42 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "App.Definitions.Country.Description", - "Value": "Country — country definitions and their address/phone rules. Table: Sas_H_Country · host scope · list screen.\nUsage: besides being an address lookup, this table is the source of validation rules. Read these fields when validating an address or phone field instead of writing country rules into the screen.\nFields:\n• Name, GroupName → country name and the country group it belongs to\n• Currency → the country's default currency; the lookup comes from the Currency screen\n• PhoneCode → international dialling code (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → phone validation and display mask; the mask of a phone field on a screen is taken from here\n• ZipRequired, StateRequired → is the postal code / state field mandatory in this country\n• TaxLabel → what the tax number is called in that country (VKN, VAT No, EIN); the caption of the tax field is written from this value\nChain: Country Group › Country › City › District. On a screen the city lookup is filtered by the selected country; the ready pattern is in this screen's own field definitions.\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.Country.json — a country deleted by hand comes back at the next seed run." + "Value": "Country — country definitions and their address/phone rules. Table: Sas_H_Country · host scope · list screen.\nUsage: besides being an address lookup, this table is the source of validation rules. Read these fields when validating an address or phone field instead of writing country rules into the screen.\nFields:\n• Name, GroupName → country name and the country group it belongs to\n• Currency → the country's default currency; the lookup comes from the Currency screen\n• PhoneCode → international dialling code (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → phone validation and display mask; the mask of a phone field on a screen is taken from here\n• ZipRequired, StateRequired → is the postal code / state field mandatory in this country\n• TaxLabel → what the tax number is called in that country (VKN, VAT No, EIN); the caption of the tax field is written from this value\nChain: Country Group › Country › City › District. On a screen the city lookup is filtered by the selected country; the ready pattern is in this screen's own field definitions.\nCaution: initial data is loaded from configs/seeds/data/App.Definitions.Country.json — a country deleted by hand comes back at the next seed run." }, { "Id": "e1d8760d-19a4-598a-8069-ca207e62c01e", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Definitions.Country.Description", - "Value": "Ülke — ülke tanımları ve adres/telefon kuralları. Tablo: Sas_H_Country · host kapsamlı · liste ekranı.\nKullanım: bu tablo yalnızca adres lookup'ı değil, doğrulama kurallarının da kaynağıdır. Adres veya telefon alanı doğrulanırken ülke kurallarını ekrana gömmek yerine bu alanlar okunur.\nAlanlar:\n• Name, GroupName → ülke adı ve bağlı olduğu ülke grubu\n• Currency → ülkenin varsayılan para birimi; lookup Para Birimi ekranından gelir\n• PhoneCode → uluslararası arama kodu (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → telefon doğrulaması ve gösterim maskesi; ekrandaki telefon alanının maskesi buradan alınır\n• ZipRequired, StateRequired → o ülkede posta kodu / eyalet alanı zorunlu mu\n• TaxLabel → vergi numarasının o ülkedeki adı (VKN, VAT No, EIN); vergi alanının başlığı bu değerden yazılır\nZincir: Ülke Grubu › Ülke › Şehir › İlçe. Ekranda şehir lookup'ı seçili ülkeye göre süzülür; hazır kalıp bu ekranın kendi alan tanımlarındadır.\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.Country.json dosyasından yüklenir — elle silinen ülke, sonraki seed çalışmasında geri gelir." + "Value": "Ülke — ülke tanımları ve adres/telefon kuralları. Tablo: Sas_H_Country · host kapsamlı · liste ekranı.\nKullanım: bu tablo yalnızca adres lookup'ı değil, doğrulama kurallarının da kaynağıdır. Adres veya telefon alanı doğrulanırken ülke kurallarını ekrana gömmek yerine bu alanlar okunur.\nAlanlar:\n• Name, GroupName → ülke adı ve bağlı olduğu ülke grubu\n• Currency → ülkenin varsayılan para birimi; lookup Para Birimi ekranından gelir\n• PhoneCode → uluslararası arama kodu (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → telefon doğrulaması ve gösterim maskesi; ekrandaki telefon alanının maskesi buradan alınır\n• ZipRequired, StateRequired → o ülkede posta kodu / eyalet alanı zorunlu mu\n• TaxLabel → vergi numarasının o ülkedeki adı (VKN, VAT No, EIN); vergi alanının başlığı bu değerden yazılır\nZincir: Ülke Grubu › Ülke › Şehir › İlçe. Ekranda şehir lookup'ı seçili ülkeye göre süzülür; hazır kalıp bu ekranın kendi alan tanımlarındadır.\nDikkat: başlangıç verisi configs/seeds/data/App.Definitions.Country.json dosyasından yüklenir — elle silinen ülke, sonraki seed çalışmasında geri gelir." }, { "Id": "994a1278-5e7f-5d0c-8f6c-9a18b0e7f93c", "CultureName": "en", "ResourceName": "Platform", "Key": "App.Definitions.City.Description", - "Value": "City — city/province definitions. Table: Sas_H_City · host scope · list screen.\nUsage: the third step of the address chain. Bind the city field of an address screen here with a lookup and filter it by the selected country; do not maintain a separate city list.\nFields:\n• Country → the country the city belongs to; the cascade filter runs over this field\n• Name → city/province name\n• PlateCode → the province's numeric code (in Turkey the plate number); used for sorting, for the region key in reports and for integration matching\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.City.json. When a city is added by hand, its districts also have to be added — otherwise the district lookup stays empty on the address screen." + "Value": "City — city/province definitions. Table: Sas_H_City · host scope · list screen.\nUsage: the third step of the address chain. Bind the city field of an address screen here with a lookup and filter it by the selected country; do not maintain a separate city list.\nFields:\n• Country → the country the city belongs to; the cascade filter runs over this field\n• Name → city/province name\n• PlateCode → the province's numeric code (in Turkey the plate number); used for sorting, for the region key in reports and for integration matching\nCaution: initial data is loaded from configs/seeds/data/App.Definitions.City.json. When a city is added by hand, its districts also have to be added — otherwise the district lookup stays empty on the address screen." }, { "Id": "9d0dd649-3ed4-5023-84f3-80063bab9093", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Definitions.City.Description", - "Value": "Şehir — şehir/il tanımları. Tablo: Sas_H_City · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin üçüncü adımı. Adres ekranının şehir alanı buraya lookup ile bağlanır ve seçili ülkeye göre süzülür; ayrı bir şehir listesi tutulmaz.\nAlanlar:\n• Country → şehrin bağlı olduğu ülke; cascade filtresi bu alan üzerinden çalışır\n• Name → şehir/il adı\n• PlateCode → ilin sayısal kodu (Türkiye'de plaka numarası); sıralama, raporlarda bölge anahtarı ve entegrasyon eşleşmesi için kullanılır\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.City.json dosyasından yüklenir. Elle şehir eklendiğinde ilçelerinin de eklenmesi gerekir — aksi hâlde adres ekranında ilçe lookup'ı boş kalır." + "Value": "Şehir — şehir/il tanımları. Tablo: Sas_H_City · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin üçüncü adımı. Adres ekranının şehir alanı buraya lookup ile bağlanır ve seçili ülkeye göre süzülür; ayrı bir şehir listesi tutulmaz.\nAlanlar:\n• Country → şehrin bağlı olduğu ülke; cascade filtresi bu alan üzerinden çalışır\n• Name → şehir/il adı\n• PlateCode → ilin sayısal kodu (Türkiye'de plaka numarası); sıralama, raporlarda bölge anahtarı ve entegrasyon eşleşmesi için kullanılır\nDikkat: başlangıç verisi configs/seeds/data/App.Definitions.City.json dosyasından yüklenir. Elle şehir eklendiğinde ilçelerinin de eklenmesi gerekir — aksi hâlde adres ekranında ilçe lookup'ı boş kalır." }, { "Id": "038d0e56-d3af-50a3-90db-eed5ce6792a7", "CultureName": "en", "ResourceName": "Platform", "Key": "App.Definitions.District.Description", - "Value": "District — district definitions. Table: Sas_H_District · host scope · list screen.\nUsage: the last step of the address chain. On a screen the district lookup is filtered by the selected city; the postal code can be filled automatically from the selected district.\nFields:\n• Country, City → the chain the district is bound to; the cascade filter runs over the City field\n• Name → district name\n• Township → sub-district/neighbourhood level where it exists\n• PostalCode → the district's postal code; used as the default value of the postal code field on an address screen\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.District.json. When one district has several postal codes, keep the most used one here and let the user change it on the screen." + "Value": "District — district definitions. Table: Sas_H_District · host scope · list screen.\nUsage: the last step of the address chain. On a screen the district lookup is filtered by the selected city; the postal code can be filled automatically from the selected district.\nFields:\n• Country, City → the chain the district is bound to; the cascade filter runs over the City field\n• Name → district name\n• Township → sub-district/neighbourhood level where it exists\n• PostalCode → the district's postal code; used as the default value of the postal code field on an address screen\nCaution: initial data is loaded from configs/seeds/data/App.Definitions.District.json. When one district has several postal codes, keep the most used one here and let the user change it on the screen." }, { "Id": "f3fcfe19-a395-5e40-9543-d71bc1eaaaa8", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Definitions.District.Description", - "Value": "İlçe — ilçe tanımları. Tablo: Sas_H_District · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin son adımı. Ekranda ilçe lookup'ı seçili şehre göre süzülür; posta kodu seçilen ilçeden otomatik doldurulabilir.\nAlanlar:\n• Country, City → ilçenin bağlı olduğu zincir; cascade filtresi City alanı üzerinden çalışır\n• Name → ilçe adı\n• Township → varsa belde/mahalle seviyesi\n• PostalCode → ilçenin posta kodu; adres ekranındaki posta kodu alanının varsayılan değeri olarak kullanılır\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.District.json dosyasından yüklenir. Bir ilçenin birden çok posta kodu varsa en çok kullanılanı burada tut, kullanıcıya ekranda değiştirme imkânı bırak." + "Value": "İlçe — ilçe tanımları. Tablo: Sas_H_District · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin son adımı. Ekranda ilçe lookup'ı seçili şehre göre süzülür; posta kodu seçilen ilçeden otomatik doldurulabilir.\nAlanlar:\n• Country, City → ilçenin bağlı olduğu zincir; cascade filtresi City alanı üzerinden çalışır\n• Name → ilçe adı\n• Township → varsa belde/mahalle seviyesi\n• PostalCode → ilçenin posta kodu; adres ekranındaki posta kodu alanının varsayılan değeri olarak kullanılır\nDikkat: başlangıç verisi configs/seeds/data/App.Definitions.District.json dosyasından yüklenir. Bir ilçenin birden çok posta kodu varsa en çok kullanılanı burada tut, kullanıcıya ekranda değiştirme imkânı bırak." }, { "Id": "0a636068-04e1-5628-b4ee-a73304232cd8", @@ -67503,14 +67517,14 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "App.SettingDefinitions.Description", - "Value": "Setting Definitions — the definition screen of application settings. Table: Sas_H_SettingDefinition · host scope · list screen. This screen defines the setting; the values are entered on Administration › Settings (/admin/settings).\nUsage: every value that must be configurable (url, token, limit, mode, default) is defined here instead of being hard-coded. The settings screen is produced from these rows: the grouping, the editor type and the permission all come from this record.\nFields:\n• Code → unique setting key; read from code with ISettingProvider.GetOrNullAsync(Code). Example: App.Sender.Telegram.BotToken\n• NameKey, DescriptionKey → language keys. The text is not written here but on Languages › Language Text\n• DefaultValue → the value used when no level holds one\n• DataType → the editor type on the settings screen: Text, List …\n• SelectOptions → key-value pairs for the List type; the value is again a language key\n• Providers → which levels may hold a value, pipe separated: D = Default, G = Global, T = Tenant, U = User. T|G|D means the tenant may override the global value\n• IsEncrypted → the value is stored encrypted and decrypted automatically while reading. Turn it on for every secret (token, password, key)\n• IsVisibleToClients → is it sent to the browser. Never turn it on together with IsEncrypted\n• IsInherited → does the tenant inherit the global value when it has none of its own\n• MainGroupKey, SubGroupKey → the heading and sub-heading on the settings screen; both are language keys\n• RequiredPermissionName → who may see and change the setting\n• Order → the ordering of the settings screen\nCaution: a definition added only from this screen is lost on a database reset — the same record is also written into configs/seeds/host/data/App.SettingDefinitions.json. Never encrypt or decrypt an encrypted value yourself; ISettingManager encrypts while writing and ISettingProvider decrypts while reading." + "Value": "Setting Definitions — the definition screen of application settings. Table: Sas_H_SettingDefinition · host scope · list screen. This screen defines the setting; the values are entered on Administration › Settings (/admin/settings).\nUsage: every value that must be configurable (url, token, limit, mode, default) is defined here instead of being hard-coded. The settings screen is produced from these rows: the grouping, the editor type and the permission all come from this record.\nFields:\n• Code → unique setting key; read from code with ISettingProvider.GetOrNullAsync(Code). Example: App.Sender.Telegram.BotToken\n• NameKey, DescriptionKey → language keys. The text is not written here but on Languages › Language Text\n• DefaultValue → the value used when no level holds one\n• DataType → the editor type on the settings screen: Text, List …\n• SelectOptions → key-value pairs for the List type; the value is again a language key\n• Providers → which levels may hold a value, pipe separated: D = Default, G = Global, T = Tenant, U = User. T|G|D means the tenant may override the global value\n• IsEncrypted → the value is stored encrypted and decrypted automatically while reading. Turn it on for every secret (token, password, key)\n• IsVisibleToClients → is it sent to the browser. Never turn it on together with IsEncrypted\n• IsInherited → does the tenant inherit the global value when it has none of its own\n• MainGroupKey, SubGroupKey → the heading and sub-heading on the settings screen; both are language keys\n• RequiredPermissionName → who may see and change the setting\n• Order → the ordering of the settings screen\nCaution: a definition added only from this screen is lost on a database reset — the same record is also written into configs/seeds/data/App.SettingDefinitions.json. Never encrypt or decrypt an encrypted value yourself; ISettingManager encrypts while writing and ISettingProvider decrypts while reading." }, { "Id": "118d90da-214e-5a14-9413-27cc00903826", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.SettingDefinitions.Description", - "Value": "Ayar Tanımları — uygulama ayarlarının tanım ekranı. Tablo: Sas_H_SettingDefinition · host kapsamlı · liste ekranı. Bu ekran ayarı tanımlar; değerler Administration › Ayarlar (/admin/settings) ekranından girilir.\nKullanım: ayarlanabilir olması gereken her değer (url, token, limit, mod, varsayılan) koda gömülmez, burada tanımlanır. Ayar ekranı bu satırlardan üretilir: gruplama, editör tipi ve yetki bu kayıttan gelir.\nAlanlar:\n• Code → benzersiz ayar anahtarı; koddan ISettingProvider.GetOrNullAsync(Code) ile okunur. Örnek: App.Sender.Telegram.BotToken\n• NameKey, DescriptionKey → dil anahtarları. Metin buraya değil Languages › Language Text ekranına yazılır\n• DefaultValue → hiçbir seviyede değer yoksa kullanılan değer\n• DataType → ayar ekranındaki editör tipi: Text, List …\n• SelectOptions → List tipi için anahtar-değer çiftleri; değer yine dil anahtarıdır\n• Providers → değerin hangi seviyede tutulabileceği, dikey çizgi ile: D = Default, G = Global, T = Tenant, U = User. T|G|D, kiracının global değeri ezebileceği anlamına gelir\n• IsEncrypted → değer şifreli saklanır ve okunurken otomatik çözülür. Gizli olan her değerde (token, parola, anahtar) açılır\n• IsVisibleToClients → tarayıcıya gönderilir mi. IsEncrypted ile birlikte asla açılmaz\n• IsInherited → kiracının kendi değeri yoksa global değeri devralır mı\n• MainGroupKey, SubGroupKey → ayar ekranındaki başlık ve alt başlık; ikisi de dil anahtarıdır\n• RequiredPermissionName → ayarı kimin görüp değiştirebileceği\n• Order → ayar ekranının sıralaması\nDikkat: yalnızca bu ekrandan eklenen tanım veritabanı sıfırlandığında kaybolur — aynı kayıt configs/seeds/host/data/App.SettingDefinitions.json dosyasına da yazılır. Şifreli değeri elle şifreleme/çözme; yazarken ISettingManager şifreler, okurken ISettingProvider çözer." + "Value": "Ayar Tanımları — uygulama ayarlarının tanım ekranı. Tablo: Sas_H_SettingDefinition · host kapsamlı · liste ekranı. Bu ekran ayarı tanımlar; değerler Administration › Ayarlar (/admin/settings) ekranından girilir.\nKullanım: ayarlanabilir olması gereken her değer (url, token, limit, mod, varsayılan) koda gömülmez, burada tanımlanır. Ayar ekranı bu satırlardan üretilir: gruplama, editör tipi ve yetki bu kayıttan gelir.\nAlanlar:\n• Code → benzersiz ayar anahtarı; koddan ISettingProvider.GetOrNullAsync(Code) ile okunur. Örnek: App.Sender.Telegram.BotToken\n• NameKey, DescriptionKey → dil anahtarları. Metin buraya değil Languages › Language Text ekranına yazılır\n• DefaultValue → hiçbir seviyede değer yoksa kullanılan değer\n• DataType → ayar ekranındaki editör tipi: Text, List …\n• SelectOptions → List tipi için anahtar-değer çiftleri; değer yine dil anahtarıdır\n• Providers → değerin hangi seviyede tutulabileceği, dikey çizgi ile: D = Default, G = Global, T = Tenant, U = User. T|G|D, kiracının global değeri ezebileceği anlamına gelir\n• IsEncrypted → değer şifreli saklanır ve okunurken otomatik çözülür. Gizli olan her değerde (token, parola, anahtar) açılır\n• IsVisibleToClients → tarayıcıya gönderilir mi. IsEncrypted ile birlikte asla açılmaz\n• IsInherited → kiracının kendi değeri yoksa global değeri devralır mı\n• MainGroupKey, SubGroupKey → ayar ekranındaki başlık ve alt başlık; ikisi de dil anahtarıdır\n• RequiredPermissionName → ayarı kimin görüp değiştirebileceği\n• Order → ayar ekranının sıralaması\nDikkat: yalnızca bu ekrandan eklenen tanım veritabanı sıfırlandığında kaybolur — aynı kayıt configs/seeds/data/App.SettingDefinitions.json dosyasına da yazılır. Şifreli değeri elle şifreleme/çözme; yazarken ISettingManager şifreler, okurken ISettingProvider çözer." }, { "Id": "bc6b1578-622e-5d9a-80a5-6ae37b3cc523", @@ -67643,14 +67657,14 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "App.Menus.Routes.Description", - "Value": "Routes — the route records of custom React screens. Table: Sas_H_Route · host scope · list screen.\nUsage: needed only when a page written in code is added — this is the last step of the decision order. Screens produced with a ListForm do not get a route record; they use the ready dynamic routes /admin/list/{code}, /admin/form/{code}, /admin/form/{code}/{id}, /admin/chart/{code}.\nFields:\n• Key → the unique key of the route; the menu record binds to the page over this value\n• Path → the url; parameters are written in the /admin/foo/:id form\n• ComponentType → how the component is resolved (normal)\n• ComponentPath → the component path, e.g. @/views/admin/foo/Foo. The file must exist in the repository\n• RouteType → who may open it: public (no login), authenticated (any logged in user), protected (permission required)\n• Authority → the permissions that may open it; on a protected route this field is mandatory\nCaution: a route whose ComponentPath does not exist breaks the whole menu load, not just that page. The seed file configs/seeds/host/data/App.Menus.Routes.json is managed by the platform — while adding a menu only App.Menus.Module.json and App.Menus.Menu.json are touched." + "Value": "Routes — the route records of custom React screens. Table: Sas_H_Route · host scope · list screen.\nUsage: needed only when a page written in code is added — this is the last step of the decision order. Screens produced with a ListForm do not get a route record; they use the ready dynamic routes /admin/list/{code}, /admin/form/{code}, /admin/form/{code}/{id}, /admin/chart/{code}.\nFields:\n• Key → the unique key of the route; the menu record binds to the page over this value\n• Path → the url; parameters are written in the /admin/foo/:id form\n• ComponentType → how the component is resolved (normal)\n• ComponentPath → the component path, e.g. @/views/admin/foo/Foo. The file must exist in the repository\n• RouteType → who may open it: public (no login), authenticated (any logged in user), protected (permission required)\n• Authority → the permissions that may open it; on a protected route this field is mandatory\nCaution: a route whose ComponentPath does not exist breaks the whole menu load, not just that page. The seed file configs/seeds/data/App.Menus.Routes.json is managed by the platform — while adding a menu only App.Menus.Module.json and App.Menus.Menu.json are touched." }, { "Id": "4ae6cc6a-37be-5942-a709-552357cee2ad", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Menus.Routes.Description", - "Value": "Route'lar — özel React ekranlarının route kayıtları. Tablo: Sas_H_Route · host kapsamlı · liste ekranı.\nKullanım: yalnızca kod ile yazılmış bir sayfa eklenirken gerekir — karar sırasının son adımıdır. ListForm ile üretilen ekranlara route kaydı açılmaz; hazır dinamik rotalar kullanılır: /admin/list/{kod}, /admin/form/{kod}, /admin/form/{kod}/{id}, /admin/chart/{kod}.\nAlanlar:\n• Key → route'un benzersiz anahtarı; menü kaydı sayfaya bu değerle bağlanır\n• Path → url; parametreler /admin/foo/:id biçiminde yazılır\n• ComponentType → bileşenin nasıl çözüleceği (normal)\n• ComponentPath → bileşen yolu, örn. @/views/admin/foo/Foo. Dosyanın depoda bulunması zorunludur\n• RouteType → kimin açabileceği: public (oturumsuz), authenticated (oturum açmış herkes), protected (yetki gerekir)\n• Authority → açabilecek yetkiler; protected route'ta bu alan zorunludur\nDikkat: ComponentPath karşılığı olmayan route yalnızca o sayfayı değil menünün tamamının yüklenmesini bozar. configs/seeds/host/data/App.Menus.Routes.json seed dosyası platform tarafından yönetilir — menü eklerken yalnızca App.Menus.Module.json ve App.Menus.Menu.json dosyalarına dokunulur." + "Value": "Route'lar — özel React ekranlarının route kayıtları. Tablo: Sas_H_Route · host kapsamlı · liste ekranı.\nKullanım: yalnızca kod ile yazılmış bir sayfa eklenirken gerekir — karar sırasının son adımıdır. ListForm ile üretilen ekranlara route kaydı açılmaz; hazır dinamik rotalar kullanılır: /admin/list/{kod}, /admin/form/{kod}, /admin/form/{kod}/{id}, /admin/chart/{kod}.\nAlanlar:\n• Key → route'un benzersiz anahtarı; menü kaydı sayfaya bu değerle bağlanır\n• Path → url; parametreler /admin/foo/:id biçiminde yazılır\n• ComponentType → bileşenin nasıl çözüleceği (normal)\n• ComponentPath → bileşen yolu, örn. @/views/admin/foo/Foo. Dosyanın depoda bulunması zorunludur\n• RouteType → kimin açabileceği: public (oturumsuz), authenticated (oturum açmış herkes), protected (yetki gerekir)\n• Authority → açabilecek yetkiler; protected route'ta bu alan zorunludur\nDikkat: ComponentPath karşılığı olmayan route yalnızca o sayfayı değil menünün tamamının yüklenmesini bozar. configs/seeds/data/App.Menus.Routes.json seed dosyası platform tarafından yönetilir — menü eklerken yalnızca App.Menus.Module.json ve App.Menus.Menu.json dosyalarına dokunulur." }, { "Id": "22dfb054-72d4-5e46-8bdc-bf8aefc5c3a5", @@ -67671,14 +67685,14 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "App.Menus.Menu.Description", - "Value": "Menu — menu items. Table: Sas_H_Menu · host scope · tree layout. The menu comes from the database, not from code.\nUsage: the tree of the left menu is built here. The produced screen becomes visible to the user only after its menu record is opened.\nFields:\n• Code → the unique code of the menu item; the child items reference it through ParentCode\n• ParentCode → the parent menu; the tree structure is formed over this field\n• DisplayName → the language key of the displayed name; the text is written on Language Text\n• ModuleId → the module the root menu belongs to; it references the Module record and the module cannot be deleted while a menu points at it\n• Url → the address it opens; for a produced screen /admin/list/{ListFormCode}\n• Icon, CssClass → the icon and additional style\n• Order → the ordering among siblings\n• Target → does it open in the same tab or a new one\n• ElementId → the dom id used for tests and guided tours\n• RequiredPermissionName → a menu whose permission the user does not hold is not rendered at all\n• IsDisabled → a disabled item does not appear in the menu; the record is preserved\nRule: the menu item, the route it opens and the permission must show the same contract. A visible menu leading to a screen the user cannot open is a design error.\nCaution: a menu added only from the interface disappears on a database reset; the same record is also written into configs/seeds/host/data/App.Menus.Menu.json (and the module row into App.Menus.Module.json)." + "Value": "Menu — menu items. Table: Sas_H_Menu · host scope · tree layout. The menu comes from the database, not from code.\nUsage: the tree of the left menu is built here. The produced screen becomes visible to the user only after its menu record is opened.\nFields:\n• Code → the unique code of the menu item; the child items reference it through ParentCode\n• ParentCode → the parent menu; the tree structure is formed over this field\n• DisplayName → the language key of the displayed name; the text is written on Language Text\n• ModuleId → the module the root menu belongs to; it references the Module record and the module cannot be deleted while a menu points at it\n• Url → the address it opens; for a produced screen /admin/list/{ListFormCode}\n• Icon, CssClass → the icon and additional style\n• Order → the ordering among siblings\n• Target → does it open in the same tab or a new one\n• ElementId → the dom id used for tests and guided tours\n• RequiredPermissionName → a menu whose permission the user does not hold is not rendered at all\n• IsDisabled → a disabled item does not appear in the menu; the record is preserved\nRule: the menu item, the route it opens and the permission must show the same contract. A visible menu leading to a screen the user cannot open is a design error.\nCaution: a menu added only from the interface disappears on a database reset; the same record is also written into configs/seeds/data/App.Menus.Menu.json (and the module row into App.Menus.Module.json)." }, { "Id": "c98c9b0a-1188-5f80-95dd-ad63d3ebc1f7", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Menus.Menu.Description", - "Value": "Menü — menü öğeleri. Tablo: Sas_H_Menu · host kapsamlı · ağaç düzeni. Menü koddan değil veritabanından gelir.\nKullanım: sol menünün ağacı burada kurulur. Üretilen ekran, menü kaydı açılmadan kullanıcıya görünmez.\nAlanlar:\n• Code → menü öğesinin benzersiz kodu; alt öğeler ParentCode ile buna referans verir\n• ParentCode → üst menü; ağaç yapısı bu alan üzerinden kurulur\n• DisplayName → görünen adın dil anahtarı; metin Language Text ekranına yazılır\n• ModuleId → kök menünün bağlı olduğu modül; Modül kaydına referans verir, menü bağlıyken modül silinemez\n• Url → açtığı adres; üretilen ekran için /admin/list/{ListFormCode}\n• Icon, CssClass → ikon ve ek stil\n• Order → kardeşleri arasındaki sıra\n• Target → aynı sekmede mi yeni sekmede mi açılacağı\n• ElementId → test ve rehberli tur için kullanılan dom kimliği\n• RequiredPermissionName → kullanıcının yetkisi olmayan menü hiç basılmaz\n• IsDisabled → pasif öğe menüde görünmez, kaydı korunur\nKural: menü öğesi, açtığı route ve yetki aynı sözleşmeyi göstermelidir. Görünen ama açılamayan ekrana götüren menü tasarım hatasıdır.\nDikkat: yalnızca arayüzden eklenen menü veritabanı sıfırlandığında kaybolur; aynı kayıt configs/seeds/host/data/App.Menus.Menu.json dosyasına da yazılır (modül satırı App.Menus.Module.json içine)." + "Value": "Menü — menü öğeleri. Tablo: Sas_H_Menu · host kapsamlı · ağaç düzeni. Menü koddan değil veritabanından gelir.\nKullanım: sol menünün ağacı burada kurulur. Üretilen ekran, menü kaydı açılmadan kullanıcıya görünmez.\nAlanlar:\n• Code → menü öğesinin benzersiz kodu; alt öğeler ParentCode ile buna referans verir\n• ParentCode → üst menü; ağaç yapısı bu alan üzerinden kurulur\n• DisplayName → görünen adın dil anahtarı; metin Language Text ekranına yazılır\n• ModuleId → kök menünün bağlı olduğu modül; Modül kaydına referans verir, menü bağlıyken modül silinemez\n• Url → açtığı adres; üretilen ekran için /admin/list/{ListFormCode}\n• Icon, CssClass → ikon ve ek stil\n• Order → kardeşleri arasındaki sıra\n• Target → aynı sekmede mi yeni sekmede mi açılacağı\n• ElementId → test ve rehberli tur için kullanılan dom kimliği\n• RequiredPermissionName → kullanıcının yetkisi olmayan menü hiç basılmaz\n• IsDisabled → pasif öğe menüde görünmez, kaydı korunur\nKural: menü öğesi, açtığı route ve yetki aynı sözleşmeyi göstermelidir. Görünen ama açılamayan ekrana götüren menü tasarım hatasıdır.\nDikkat: yalnızca arayüzden eklenen menü veritabanı sıfırlandığında kaybolur; aynı kayıt configs/seeds/data/App.Menus.Menu.json dosyasına da yazılır (modül satırı App.Menus.Module.json içine)." }, { "Id": "3728bc05-56e7-5279-9172-745291c04b37", @@ -67825,28 +67839,28 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "Abp.Identity.PermissionGroups.Description", - "Value": "Permission Groups — the top level of the permission tree. Table: AbpPermissionGroups · list screen.\nUsage: every permission belongs to a group and the group determines the heading under which the permission appears in the role and user permission dialogs. The groups also carry the module division: App.Saas, App.Administration, a project module.\nFields:\n• Name → the group code; permission rows reference it through GroupName\n• DisplayName → the language key of the displayed name\n• ExtraProperties → additional information kept by ABP on the record\nCaution: permissions and their groups are produced by the platform — by the Wizard, by the seeder and by the configs/seeds/host/data/Abp.Identity.Permission*.json seed files. Do not open a group by hand for a screen you produce; the wizard creates the group and the permissions together and keeps the codes consistent." + "Value": "Permission Groups — the top level of the permission tree. Table: AbpPermissionGroups · list screen.\nUsage: every permission belongs to a group and the group determines the heading under which the permission appears in the role and user permission dialogs. The groups also carry the module division: App.Saas, App.Administration, a project module.\nFields:\n• Name → the group code; permission rows reference it through GroupName\n• DisplayName → the language key of the displayed name\n• ExtraProperties → additional information kept by ABP on the record\nCaution: permissions and their groups are produced by the platform — by the Wizard, by the seeder and by the configs/seeds/data/Abp.Identity.Permission*.json seed files. Do not open a group by hand for a screen you produce; the wizard creates the group and the permissions together and keeps the codes consistent." }, { "Id": "3b9bde77-307e-5589-9eca-7c846e93bf5d", "CultureName": "tr", "ResourceName": "Platform", "Key": "Abp.Identity.PermissionGroups.Description", - "Value": "Yetki Grupları — yetki ağacının en üst seviyesi. Tablo: AbpPermissionGroups · liste ekranı.\nKullanım: her yetki bir gruba bağlıdır ve grup, yetkinin rol ve kullanıcı yetki diyaloglarında hangi başlık altında görüneceğini belirler. Gruplar aynı zamanda modül ayrımını taşır: App.Saas, App.Administration, proje modülü.\nAlanlar:\n• Name → grup kodu; yetki satırları GroupName alanıyla buna referans verir\n• DisplayName → görünen adın dil anahtarı\n• ExtraProperties → ABP'nin kayıt üzerinde tuttuğu ek bilgi\nDikkat: yetkiler ve grupları platform tarafından üretilir — Wizard, seeder ve configs/seeds/host/data/Abp.Identity.Permission*.json seed dosyaları. Ürettiğin ekran için elle grup açma; sihirbaz grubu ve yetkileri birlikte oluşturur, kodları tutarlı tutar." + "Value": "Yetki Grupları — yetki ağacının en üst seviyesi. Tablo: AbpPermissionGroups · liste ekranı.\nKullanım: her yetki bir gruba bağlıdır ve grup, yetkinin rol ve kullanıcı yetki diyaloglarında hangi başlık altında görüneceğini belirler. Gruplar aynı zamanda modül ayrımını taşır: App.Saas, App.Administration, proje modülü.\nAlanlar:\n• Name → grup kodu; yetki satırları GroupName alanıyla buna referans verir\n• DisplayName → görünen adın dil anahtarı\n• ExtraProperties → ABP'nin kayıt üzerinde tuttuğu ek bilgi\nDikkat: yetkiler ve grupları platform tarafından üretilir — Wizard, seeder ve configs/seeds/data/Abp.Identity.Permission*.json seed dosyaları. Ürettiğin ekran için elle grup açma; sihirbaz grubu ve yetkileri birlikte oluşturur, kodları tutarlı tutar." }, { "Id": "f92ec83f-0343-5f52-9d98-f8f6b9ea93dd", "CultureName": "en", "ResourceName": "Platform", "Key": "Abp.Identity.Permissions.Description", - "Value": "Permissions — the permission definitions of the application. Table: AbpPermissions · tree layout.\nUsage: the permission contract of every screen is visible here. A list screen normally has a root permission plus the .Create, .Update, .Delete, .Export, .Import and .Note sub-permissions; the ListForm PermissionJson maps these codes onto the buttons.\nFields:\n• Name → the permission code, e.g. App.Definitions.Currency.Create. It is the same value referenced from the code (PlatformConsts.AppCodes) and from the UI\n• ParentName → the parent permission; the tree is formed over this field and granting a parent does not automatically grant the children\n• GroupName → the permission group it belongs to\n• DisplayName → the language key of the displayed name\n• MultiTenancySide → on which side it is valid: host, tenant or both. A permission defined only for host is not offered to a tenant role\n• Modules → which modules the permission belongs to; it drives the module based filtering of the permission dialogs\n• IsEnabled → a disabled permission is not offered to anyone; it is the fastest way to close a capability across the system\nCaution: a permission added only from the interface is lost on a database reset. The same record is also written into PlatformConsts.AppCodes, configs/seeds/host/data/Abp.Identity.Permissions.json, LanguagesData.json and, if it is used in the interface, into permission.constant.ts." + "Value": "Permissions — the permission definitions of the application. Table: AbpPermissions · tree layout.\nUsage: the permission contract of every screen is visible here. A list screen normally has a root permission plus the .Create, .Update, .Delete, .Export, .Import and .Note sub-permissions; the ListForm PermissionJson maps these codes onto the buttons.\nFields:\n• Name → the permission code, e.g. App.Definitions.Currency.Create. It is the same value referenced from the code (PlatformConsts.AppCodes) and from the UI\n• ParentName → the parent permission; the tree is formed over this field and granting a parent does not automatically grant the children\n• GroupName → the permission group it belongs to\n• DisplayName → the language key of the displayed name\n• MultiTenancySide → on which side it is valid: host, tenant or both. A permission defined only for host is not offered to a tenant role\n• Modules → which modules the permission belongs to; it drives the module based filtering of the permission dialogs\n• IsEnabled → a disabled permission is not offered to anyone; it is the fastest way to close a capability across the system\nCaution: a permission added only from the interface is lost on a database reset. The same record is also written into PlatformConsts.AppCodes, configs/seeds/data/Abp.Identity.Permissions.json, LanguagesData.json and, if it is used in the interface, into permission.constant.ts." }, { "Id": "16ef5bcf-72c6-5ea6-a6f7-8b93affe0efc", "CultureName": "tr", "ResourceName": "Platform", "Key": "Abp.Identity.Permissions.Description", - "Value": "Yetkiler — uygulamanın yetki tanımları. Tablo: AbpPermissions · ağaç düzeni.\nKullanım: her ekranın yetki sözleşmesi burada görünür. Liste ekranı normalde bir kök yetki ile birlikte .Create, .Update, .Delete, .Export, .Import ve .Note alt yetkilerini taşır; ListForm PermissionJson bu kodları butonlara eşler.\nAlanlar:\n• Name → yetki kodu, örn. App.Definitions.Currency.Create. Koddan (PlatformConsts.AppCodes) ve arayüzden referans verilen değerle aynıdır\n• ParentName → üst yetki; ağaç bu alan üzerinden kurulur ve üst yetkiyi vermek alt yetkileri otomatik vermez\n• GroupName → bağlı olduğu yetki grubu\n• DisplayName → görünen adın dil anahtarı\n• MultiTenancySide → hangi tarafta geçerli olduğu: host, tenant veya ikisi. Yalnızca host için tanımlı yetki kiracı rolüne sunulmaz\n• Modules → yetkinin hangi modüllere ait olduğu; yetki diyaloglarının modül bazlı süzülmesini sağlar\n• IsEnabled → pasif yetki kimseye sunulmaz; bir yeteneği sistem genelinde kapatmanın en hızlı yoludur\nDikkat: yalnızca arayüzden eklenen yetki veritabanı sıfırlandığında kaybolur. Aynı kayıt PlatformConsts.AppCodes, configs/seeds/host/data/Abp.Identity.Permissions.json, LanguagesData.json ve arayüzde kullanılacaksa permission.constant.ts dosyalarına da yazılır." + "Value": "Yetkiler — uygulamanın yetki tanımları. Tablo: AbpPermissions · ağaç düzeni.\nKullanım: her ekranın yetki sözleşmesi burada görünür. Liste ekranı normalde bir kök yetki ile birlikte .Create, .Update, .Delete, .Export, .Import ve .Note alt yetkilerini taşır; ListForm PermissionJson bu kodları butonlara eşler.\nAlanlar:\n• Name → yetki kodu, örn. App.Definitions.Currency.Create. Koddan (PlatformConsts.AppCodes) ve arayüzden referans verilen değerle aynıdır\n• ParentName → üst yetki; ağaç bu alan üzerinden kurulur ve üst yetkiyi vermek alt yetkileri otomatik vermez\n• GroupName → bağlı olduğu yetki grubu\n• DisplayName → görünen adın dil anahtarı\n• MultiTenancySide → hangi tarafta geçerli olduğu: host, tenant veya ikisi. Yalnızca host için tanımlı yetki kiracı rolüne sunulmaz\n• Modules → yetkinin hangi modüllere ait olduğu; yetki diyaloglarının modül bazlı süzülmesini sağlar\n• IsEnabled → pasif yetki kimseye sunulmaz; bir yeteneği sistem genelinde kapatmanın en hızlı yoludur\nDikkat: yalnızca arayüzden eklenen yetki veritabanı sıfırlandığında kaybolur. Aynı kayıt PlatformConsts.AppCodes, configs/seeds/data/Abp.Identity.Permissions.json, LanguagesData.json ve arayüzde kullanılacaksa permission.constant.ts dosyalarına da yazılır." }, { "Id": "edad4467-88fe-570e-818c-b253b9446113", diff --git a/configs/seeds/host/data/App.Menus.Menu.json b/configs/seeds/data/App.Menus.Menu.json similarity index 99% rename from configs/seeds/host/data/App.Menus.Menu.json rename to configs/seeds/data/App.Menus.Menu.json index 46aac262..95038f35 100644 --- a/configs/seeds/host/data/App.Menus.Menu.json +++ b/configs/seeds/data/App.Menus.Menu.json @@ -398,10 +398,10 @@ "Id": "6f77c3ac-1f0a-4f22-8ef2-b5aa4a01614a", "Code": "App.DevRequests", "DisplayName": "App.DevRequests", - "Order": 3, + "Order": 1, "Url": "/admin/list/App.DevRequests", "Icon": "FcIdea", - "ParentCode": "App.Saas", + "ParentCode": "App.DeveloperKit", "RequiredPermissionName": "App.DevRequests", "CssClass": null, "Target": null, @@ -413,7 +413,7 @@ "Id": "d55dca3e-a14f-4b59-8541-d2a5691e4276", "Code": "AbpTenantManagement.Tenants", "DisplayName": "AbpTenantManagement.Tenants", - "Order": 4, + "Order": 3, "Url": "/admin/list/AbpTenantManagement.Tenants", "Icon": "FcDepartment", "ParentCode": "App.Saas", @@ -428,7 +428,7 @@ "Id": "16d8ca66-d74a-4b86-aabd-8ade147de92f", "Code": "App.Branches", "DisplayName": "App.Branches", - "Order": 5, + "Order": 4, "Url": "/admin/list/App.Branches", "Icon": "FcTreeStructure", "ParentCode": "App.Saas", @@ -443,7 +443,7 @@ "Id": "661ab09d-78f0-4e82-ab17-a1eefe73c4bb", "Code": "App.SettingDefinitions", "DisplayName": "App.SettingDefinitions", - "Order": 6, + "Order": 5, "Url": "/admin/list/App.SettingDefinitions", "Icon": "FcSupport", "ParentCode": "App.Saas", @@ -458,7 +458,7 @@ "Id": "da20a94c-e468-45fe-b91b-b5690d3370a2", "Code": "App.Languages", "DisplayName": "App.Languages", - "Order": 7, + "Order": 6, "Url": null, "Icon": "FcGlobe", "ParentCode": "App.Saas", @@ -503,7 +503,7 @@ "Id": "9328cf64-1c67-4d25-94a1-d9d7d3bb0da0", "Code": "App.DataSource", "DisplayName": "App.DataSource", - "Order": 8, + "Order": 7, "Url": "/admin/list/App.DataSource", "Icon": "FcAcceptDatabase", "ParentCode": "App.Saas", @@ -518,7 +518,7 @@ "Id": "405588d2-3185-4ee1-884a-57d4758d5eba", "Code": "App.BackgroundWorkers", "DisplayName": "App.BackgroundWorkers", - "Order": 9, + "Order": 8, "Url": null, "Icon": "FcSynchronize", "ParentCode": "App.Saas", @@ -563,7 +563,7 @@ "Id": "ac77496b-c2a8-44f4-927c-7b56faae8453", "Code": "App.Menus", "DisplayName": "App.Menus", - "Order": 10, + "Order": 9, "Url": null, "Icon": "FcParallelTasks", "ParentCode": "App.Saas", @@ -638,7 +638,7 @@ "Id": "e141c2ae-3364-4022-bf76-c745266c86ff", "Code": "App.DeveloperKit", "DisplayName": "App.DeveloperKit", - "Order": 11, + "Order": 10, "Url": null, "Icon": "FcAndroidOs", "ParentCode": "App.Saas", @@ -653,7 +653,7 @@ "Id": "88ed3fe6-7937-45d1-aad7-a9d8c2150e1e", "Code": "App.SqlQueryManager", "DisplayName": "App.SqlQueryManager", - "Order": 1, + "Order": 2, "Url": "/admin/sqlQueryManager", "Icon": "FaDatabase", "ParentCode": "App.DeveloperKit", @@ -668,7 +668,7 @@ "Id": "0375ca77-8dfc-46ea-aadf-6989d0749e02", "Code": "App.Files", "DisplayName": "App.Files", - "Order": 2, + "Order": 3, "Url": "/admin/files", "Icon": "FcFolder", "ParentCode": "App.DeveloperKit", @@ -683,7 +683,7 @@ "Id": "df0592bf-469c-4ee1-a981-846c277c092f", "Code": "App.DeveloperKit.CustomEndpoints", "DisplayName": "App.DeveloperKit.CustomEndpoints", - "Order": 3, + "Order": 4, "Url": "/admin/list/App.DeveloperKit.CustomEndpoints", "Icon": "FcMindMap", "ParentCode": "App.DeveloperKit", @@ -698,7 +698,7 @@ "Id": "b75af2e9-4a36-4973-bde7-c30c76bd4551", "Code": "App.DeveloperKit.DynamicServices", "DisplayName": "App.DeveloperKit.DynamicServices", - "Order": 4, + "Order": 5, "Url": "/admin/list/App.DeveloperKit.DynamicServices", "Icon": "FcCommandLine", "ParentCode": "App.DeveloperKit", @@ -713,7 +713,7 @@ "Id": "322dbb78-c53b-433b-b387-1faf9f3960c6", "Code": "App.DeveloperKit.CustomComponents", "DisplayName": "App.DeveloperKit.CustomComponents", - "Order": 5, + "Order": 6, "Url": "/admin/list/App.DeveloperKit.CustomComponents", "Icon": "FcBiohazard", "ParentCode": "App.DeveloperKit", @@ -728,7 +728,7 @@ "Id": "16a859ee-bd46-4db0-a48b-e2923c520c69", "Code": "App.Listforms.Listform", "DisplayName": "App.Listforms.Listform", - "Order": 6, + "Order": 7, "Url": "/admin/list/App.Listforms.Listform", "Icon": "FcDataSheet", "ParentCode": "App.DeveloperKit", @@ -743,7 +743,7 @@ "Id": "0ef9ffe5-a083-46f1-aa0b-c4c0b61f09bc", "Code": "App.DeveloperKit.IntranetWidgets", "DisplayName": "App.DeveloperKit.IntranetWidgets", - "Order": 7, + "Order": 8, "Url": "/admin/list/App.DeveloperKit.IntranetWidgets", "Icon": "FcAdvertising", "ParentCode": "App.DeveloperKit", @@ -758,7 +758,7 @@ "Id": "b6474d04-4787-4476-989e-1833ba85ceff", "Code": "App.DeveloperKit.McpServers", "DisplayName": "App.DeveloperKit.McpServers", - "Order": 8, + "Order": 9, "Url": "/admin/list/App.DeveloperKit.McpServers", "Icon": "FcElectroDevices", "ParentCode": "App.DeveloperKit", @@ -773,7 +773,7 @@ "Id": "10672210-74c4-4ae5-95ce-0f20e2e93fcd", "Code": "App.Listforms.WizardManager", "DisplayName": "App.Listforms.WizardManager", - "Order": 9, + "Order": 10, "Url": "/admin/listform/wizardManager", "Icon": "FcFlashAuto", "ParentCode": "App.DeveloperKit", @@ -788,7 +788,7 @@ "Id": "5a3637ab-11fd-4048-bed4-828cd35c91c4", "Code": "App.ForumManagement", "DisplayName": "App.ForumManagement", - "Order": 12, + "Order": 11, "Url": null, "Icon": "FcReading", "ParentCode": "App.Saas", diff --git a/configs/seeds/host/data/App.Menus.Module.json b/configs/seeds/data/App.Menus.Module.json similarity index 100% rename from configs/seeds/host/data/App.Menus.Module.json rename to configs/seeds/data/App.Menus.Module.json diff --git a/configs/seeds/host/data/App.Menus.Routes.json b/configs/seeds/data/App.Menus.Routes.json similarity index 100% rename from configs/seeds/host/data/App.Menus.Routes.json rename to configs/seeds/data/App.Menus.Routes.json diff --git a/configs/seeds/host/data/App.Notifications.NotificationRules.json b/configs/seeds/data/App.Notifications.NotificationRules.json similarity index 100% rename from configs/seeds/host/data/App.Notifications.NotificationRules.json rename to configs/seeds/data/App.Notifications.NotificationRules.json diff --git a/configs/seeds/host/data/App.Notifications.NotificationTemplates.json b/configs/seeds/data/App.Notifications.NotificationTemplates.json similarity index 100% rename from configs/seeds/host/data/App.Notifications.NotificationTemplates.json rename to configs/seeds/data/App.Notifications.NotificationTemplates.json diff --git a/configs/seeds/host/data/App.Notifications.NotificationTypes.json b/configs/seeds/data/App.Notifications.NotificationTypes.json similarity index 100% rename from configs/seeds/host/data/App.Notifications.NotificationTypes.json rename to configs/seeds/data/App.Notifications.NotificationTypes.json diff --git a/configs/seeds/host/data/App.Orders.InstallmentOptions.json b/configs/seeds/data/App.Orders.InstallmentOptions.json similarity index 100% rename from configs/seeds/host/data/App.Orders.InstallmentOptions.json rename to configs/seeds/data/App.Orders.InstallmentOptions.json diff --git a/configs/seeds/host/data/App.Orders.PaymentMethods.json b/configs/seeds/data/App.Orders.PaymentMethods.json similarity index 100% rename from configs/seeds/host/data/App.Orders.PaymentMethods.json rename to configs/seeds/data/App.Orders.PaymentMethods.json diff --git a/configs/seeds/host/data/App.Orders.Products.json b/configs/seeds/data/App.Orders.Products.json similarity index 100% rename from configs/seeds/host/data/App.Orders.Products.json rename to configs/seeds/data/App.Orders.Products.json diff --git a/configs/seeds/host/data/App.Reports.Categories.json b/configs/seeds/data/App.Reports.Categories.json similarity index 100% rename from configs/seeds/host/data/App.Reports.Categories.json rename to configs/seeds/data/App.Reports.Categories.json diff --git a/configs/seeds/host/data/App.Reports.ReportTemplates.json b/configs/seeds/data/App.Reports.ReportTemplates.json similarity index 100% rename from configs/seeds/host/data/App.Reports.ReportTemplates.json rename to configs/seeds/data/App.Reports.ReportTemplates.json diff --git a/configs/seeds/host/data/App.Restrictions.WorkHour.json b/configs/seeds/data/App.Restrictions.WorkHour.json similarity index 100% rename from configs/seeds/host/data/App.Restrictions.WorkHour.json rename to configs/seeds/data/App.Restrictions.WorkHour.json diff --git a/configs/seeds/host/data/App.SettingDefinitions.json b/configs/seeds/data/App.SettingDefinitions.json similarity index 100% rename from configs/seeds/host/data/App.SettingDefinitions.json rename to configs/seeds/data/App.SettingDefinitions.json diff --git a/configs/seeds/host/docs/modules/HrAttendance.md b/configs/seeds/docs/modules/HrAttendance.md similarity index 95% rename from configs/seeds/host/docs/modules/HrAttendance.md rename to configs/seeds/docs/modules/HrAttendance.md index af2b4fb9..370d198b 100644 --- a/configs/seeds/host/docs/modules/HrAttendance.md +++ b/configs/seeds/docs/modules/HrAttendance.md @@ -305,23 +305,23 @@ Custom Endpoints'ten aynı SQL ve parametrelerle eklenir. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{AttendanceLog,Timesheet,TimesheetPeriod}.sql` | Tablolar | -| `configs/seeds/host/sql/object/Hr_T_Shift.sql` | Vardiya toleransı kolonları | -| `configs/seeds/host/sql/object/Hr_V_{AttendanceShift,TimesheetMonthly}.sql` | View'lar | -| `configs/seeds/host/sql/object/Hr_F_{WorkCalendarDayType,TimesheetPeriodClosed,LeaveNetDays}.sql` | Takvim günü türü, kapalı dönem, rapordan arınmış izin günü | -| `configs/seeds/host/sql/object/Hr_S_{TimesheetCalculate,AttendanceProcess,AttendanceImport,TimesheetPeriodAfterSave,TimesheetNightly}.sql` | Hesap ve tetikler | -| `configs/seeds/host/sql/object/Hr_S_{LeaveRequestAfterSave,LeaveRequestGuard,LeaveSickLeaveSync,LeaveDaysRefresh}.sql` | İzin/rapor → puantaj tetiği, kapalı dönem guard'ı, rapor → izin günü | -| `configs/seeds/host/sql/object/Hr_S_{TrainingSync,WorkCalendarAfterSave}.sql` | Eğitim ve takvim → puantaj tetiği | -| `configs/seeds/host/wizard/{LeaveRequest,MyLeaveRequest,HealthCardSickLeave,Training,TrainingParticipant,WorkCalendar}.json` | Tetik komutları (silme dahil) | +| `configs/seeds/sql/object/Hr_T_{AttendanceLog,Timesheet,TimesheetPeriod}.sql` | Tablolar | +| `configs/seeds/sql/object/Hr_T_Shift.sql` | Vardiya toleransı kolonları | +| `configs/seeds/sql/object/Hr_V_{AttendanceShift,TimesheetMonthly}.sql` | View'lar | +| `configs/seeds/sql/object/Hr_F_{WorkCalendarDayType,TimesheetPeriodClosed,LeaveNetDays}.sql` | Takvim günü türü, kapalı dönem, rapordan arınmış izin günü | +| `configs/seeds/sql/object/Hr_S_{TimesheetCalculate,AttendanceProcess,AttendanceImport,TimesheetPeriodAfterSave,TimesheetNightly}.sql` | Hesap ve tetikler | +| `configs/seeds/sql/object/Hr_S_{LeaveRequestAfterSave,LeaveRequestGuard,LeaveSickLeaveSync,LeaveDaysRefresh}.sql` | İzin/rapor → puantaj tetiği, kapalı dönem guard'ı, rapor → izin günü | +| `configs/seeds/sql/object/Hr_S_{TrainingSync,WorkCalendarAfterSave}.sql` | Eğitim ve takvim → puantaj tetiği | +| `configs/seeds/wizard/{LeaveRequest,MyLeaveRequest,HealthCardSickLeave,Training,TrainingParticipant,WorkCalendar}.json` | Tetik komutları (silme dahil) | | `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs` (`UserSickLeave`) | Kullanıcı detayındaki istirahat listesinin silme tetiği | -| `configs/seeds/host/wizard/{AttendanceLog,Timesheet,TimesheetMonthly,TimesheetPeriod,MyTimesheet,MyTimesheetDay}.json` | 6 wizard | -| `configs/seeds/host/wizard/Shift.json` | Tolerans alanları | -| `configs/seeds/host/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json` | `MyTimesheetWidget` | -| `configs/seeds/host/data/App.DeveloperKit.IntranetWidgets.json` | `my-timesheet` | -| `configs/seeds/host/data/App.DeveloperKit.CustomEndpoints.json` | PDKS ucu | -| `configs/seeds/host/data/App.BackgroundWorkers.RecurringJobs.json` | `Timesheet Nightly` | -| `configs/seeds/host/data/App.SettingDefinitions.json` | `App.HrSettings.Attendance.*` | -| `configs/seeds/host/data/{App.Menus.Menu,Abp.Identity.Permissions,App.Languages.LanguageText}.json` | Menü, yetki, dil | +| `configs/seeds/wizard/{AttendanceLog,Timesheet,TimesheetMonthly,TimesheetPeriod,MyTimesheet,MyTimesheetDay}.json` | 6 wizard | +| `configs/seeds/wizard/Shift.json` | Tolerans alanları | +| `configs/seeds/data/App.DeveloperKit.CustomComponents/1bca19aa-e144-5e4c-b23b-9eddd4dafd19.json` | `MyTimesheetWidget` | +| `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json` | `my-timesheet` | +| `configs/seeds/data/App.DeveloperKit.CustomEndpoints.json` | PDKS ucu | +| `configs/seeds/data/App.BackgroundWorkers.RecurringJobs.json` | `Timesheet Nightly` | +| `configs/seeds/data/App.SettingDefinitions.json` | `App.HrSettings.Attendance.*` | +| `configs/seeds/data/{App.Menus.Menu,Abp.Identity.Permissions,App.Languages.LanguageText}.json` | Menü, yetki, dil | | `ui/src/views/admin/user-management/{TimesheetTab,Details}.tsx` | Kullanıcı detayı › Puantaj | Geri alma: Wizard Manager'dan altı ekran silinir; widget, endpoint, iş ve ayar satırları kendi diff --git a/configs/seeds/host/docs/modules/HrDefinitions.md b/configs/seeds/docs/modules/HrDefinitions.md similarity index 97% rename from configs/seeds/host/docs/modules/HrDefinitions.md rename to configs/seeds/docs/modules/HrDefinitions.md index 9e843df9..9e397390 100644 --- a/configs/seeds/host/docs/modules/HrDefinitions.md +++ b/configs/seeds/docs/modules/HrDefinitions.md @@ -90,7 +90,7 @@ kısıt yoktur. ## 5. İş kuralları Hesap yoktur; yalnız form varsayılanları (§3). Seed dosyası aynalandığı için tanımlar ekrandan -değiştirildiğinde `configs/seeds/host/data/App.Hr.*.json` dosyası da değişir — bu dosyalar commit'lenmelidir. +değiştirildiğinde `configs/seeds/data/App.Hr.*.json` dosyası da değişir — bu dosyalar commit'lenmelidir. --- @@ -137,9 +137,9 @@ lookup olarak okunmaları ekran yetkisi gerektirmez. Hiçbir yetki role otomatik | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.sql` | Tablolar | -| `configs/seeds/host/wizard/{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | 8 wizard | -| `configs/seeds/host/data/App.Hr.{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | Aynalanan tanım verisi | +| `configs/seeds/sql/object/Hr_T_{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.sql` | Tablolar | +| `configs/seeds/wizard/{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | 8 wizard | +| `configs/seeds/data/App.Hr.{Doctor,Certificate,Hospital,HospitalDepartment,DocumentType,LeaveType,ExpenseType,BenefitType}.json` | Aynalanan tanım verisi | ## Ek — Ekran envanteri (otomatik) diff --git a/configs/seeds/host/docs/modules/HrDisciplinaryCase.md b/configs/seeds/docs/modules/HrDisciplinaryCase.md similarity index 97% rename from configs/seeds/host/docs/modules/HrDisciplinaryCase.md rename to configs/seeds/docs/modules/HrDisciplinaryCase.md index 28ad16f5..58081f27 100644 --- a/configs/seeds/host/docs/modules/HrDisciplinaryCase.md +++ b/configs/seeds/docs/modules/HrDisciplinaryCase.md @@ -151,10 +151,10 @@ Bildirim, onay akışı, rapor şablonu ya da bordro bağlantısı yoktur. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_Disciplinary{PenaltyType,Case,Defense,Decision,File}.sql` | Tablolar | -| `configs/seeds/host/wizard/Disciplinary{PenaltyType,Case,Defense,Decision,File}.json` | 5 wizard | -| `configs/seeds/host/data/App.Hr.DisciplinaryPenaltyType.json` | Ceza türleri | -| `configs/seeds/host/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | +| `configs/seeds/sql/object/Hr_T_Disciplinary{PenaltyType,Case,Defense,Decision,File}.sql` | Tablolar | +| `configs/seeds/wizard/Disciplinary{PenaltyType,Case,Defense,Decision,File}.json` | 5 wizard | +| `configs/seeds/data/App.Hr.DisciplinaryPenaltyType.json` | Ceza türleri | +| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar (`DisciplinaryDefense`, `DisciplinaryDecision`, `DisciplinaryFile`) önce, sonra `DisciplinaryCase` ve `DisciplinaryPenaltyType` silinir; tablolar diff --git a/configs/seeds/host/docs/modules/HrEventLog.md b/configs/seeds/docs/modules/HrEventLog.md similarity index 97% rename from configs/seeds/host/docs/modules/HrEventLog.md rename to configs/seeds/docs/modules/HrEventLog.md index 89165c95..c99dc091 100644 --- a/configs/seeds/host/docs/modules/HrEventLog.md +++ b/configs/seeds/docs/modules/HrEventLog.md @@ -102,8 +102,8 @@ Hiçbir yetki role otomatik atanmaz. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_EventLog.sql` | Tablo | -| `configs/seeds/host/wizard/EventLog.json` | Wizard | +| `configs/seeds/sql/object/Hr_T_EventLog.sql` | Tablo | +| `configs/seeds/wizard/EventLog.json` | Wizard | Geri alma: Wizard Manager'dan silinir; tablo yalnızca onayda seçilirse düşer. diff --git a/configs/seeds/host/docs/modules/HrHealthCard.md b/configs/seeds/docs/modules/HrHealthCard.md similarity index 98% rename from configs/seeds/host/docs/modules/HrHealthCard.md rename to configs/seeds/docs/modules/HrHealthCard.md index a7f0d9a0..c6ca6f86 100644 --- a/configs/seeds/host/docs/modules/HrHealthCard.md +++ b/configs/seeds/docs/modules/HrHealthCard.md @@ -155,8 +155,8 @@ serbest metindir). Bildirim, periyodik muayene hatırlatması ya da rapor şablo | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/wizard/HealthCard{,History,LabPhysical,Anamnesis,Result,PeriodicExamination,PatientProtocol,SickLeave}.json` | 8 wizard | -| `configs/seeds/host/wizard/{Doctor,Hospital,HospitalDepartment}.json`, `data/App.Hr.{Doctor,Hospital,HospitalDepartment}.json` | Tanım ekranları ve verisi | +| `configs/seeds/wizard/HealthCard{,History,LabPhysical,Anamnesis,Result,PeriodicExamination,PatientProtocol,SickLeave}.json` | 8 wizard | +| `configs/seeds/wizard/{Doctor,Hospital,HospitalDepartment}.json`, `data/App.Hr.{Doctor,Hospital,HospitalDepartment}.json` | Tanım ekranları ve verisi | | `api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/User{PeriodicExamination,PatientProtocol,SickLeave}.cs` | Çok kayıtlı sekmelerin entity'leri (migration'lı) | | `ui/src/views/admin/user-management/HealthCardTab.tsx` | Kullanıcı detayındaki sekme | diff --git a/configs/seeds/host/docs/modules/HrIntranet.md b/configs/seeds/docs/modules/HrIntranet.md similarity index 99% rename from configs/seeds/host/docs/modules/HrIntranet.md rename to configs/seeds/docs/modules/HrIntranet.md index 8fcddb81..c2ecfc54 100644 --- a/configs/seeds/host/docs/modules/HrIntranet.md +++ b/configs/seeds/docs/modules/HrIntranet.md @@ -210,7 +210,7 @@ Kontrol edilmeli (koddan tespit edilenler): | `api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs`, `Messenger/MessengerAppService.cs` | Servisler | | `ui/src/views/intranet/Dashboard.tsx`, `widgets/*`, `SocialWall/*`, `documents/IntranetDocuments.tsx` | Pano, kutular, sosyal duvar, doküman sayfası | | `ui/src/components/template/MessengerWidget.tsx` | Mesajlaşma penceresi | -| `configs/seeds/host/data/App.Hr.Intranet.*.json`, `App.DeveloperKit.IntranetWidgets.json` | Örnek veri ve pano kutuları | +| `configs/seeds/data/App.Hr.Intranet.*.json`, `App.DeveloperKit.IntranetWidgets.json` | Örnek veri ve pano kutuları | Örnek veri: - 5 duyuru (3'ü sabitlenmiş). diff --git a/configs/seeds/host/docs/modules/HrLeave.md b/configs/seeds/docs/modules/HrLeave.md similarity index 97% rename from configs/seeds/host/docs/modules/HrLeave.md rename to configs/seeds/docs/modules/HrLeave.md index 829542ab..000352ba 100644 --- a/configs/seeds/host/docs/modules/HrLeave.md +++ b/configs/seeds/docs/modules/HrLeave.md @@ -341,13 +341,13 @@ Widget'lar liste okuma yetkisiyle görünür (İzinlerim `App.Hr.MyLeaveRequest` | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_LeaveRequest.sql`, `Hr_T_LeaveBalanceAdjustment.sql`, `Hr_T_LeaveType.sql` (`BalanceExceedAction`) | Tablolar | +| `configs/seeds/sql/object/Hr_T_LeaveRequest.sql`, `Hr_T_LeaveBalanceAdjustment.sql`, `Hr_T_LeaveType.sql` (`BalanceExceedAction`) | Tablolar | | `…/Hr_F_HrSetting.sql`, `Hr_F_LeaveWorkDays.sql` | Ayar okuma, gün sayısı | | `…/Hr_V_LeaveBalanceYear.sql`, `Hr_V_LeaveSummary.sql` | Bakiye görünümleri | | `…/Hr_S_LeaveRequestAfterSave.sql`, `Hr_S_LeaveRequestGuard.sql`, `Hr_S_LeaveNotify.sql`, `Hr_S_LeaveSickLeaveSync.sql` | Prosedürler | -| `configs/seeds/host/wizard/{LeaveRequest,MyLeaveRequest,TeamLeaveRequest,LeaveBalance,LeaveBalanceAdjustment,LeaveBalanceYear,MyLeaveBalance}.json` | 7 wizard; `LeaveType.json` (alan), `HealthCardSickLeave.json` (kancalar) | -| `configs/seeds/host/data/App.DeveloperKit.CustomComponents/{23744edc-eda9-512e-8079-fe2e8e5fd2a8,3b7b16c6-aa96-5765-8e19-fe5e727feb52}.json` | `MyLeaveWidget`, `TeamLeaveWidget` | -| `configs/seeds/host/data/App.DeveloperKit.IntranetWidgets.json`, `App.SettingDefinitions.json`, `App.Notifications.Notification{Types,Rules}.json`, `App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json`, `App.Hr.LeaveType.json` | Seed satırları | +| `configs/seeds/wizard/{LeaveRequest,MyLeaveRequest,TeamLeaveRequest,LeaveBalance,LeaveBalanceAdjustment,LeaveBalanceYear,MyLeaveBalance}.json` | 7 wizard; `LeaveType.json` (alan), `HealthCardSickLeave.json` (kancalar) | +| `configs/seeds/data/App.DeveloperKit.CustomComponents/{23744edc-eda9-512e-8079-fe2e8e5fd2a8,3b7b16c6-aa96-5765-8e19-fe5e727feb52}.json` | `MyLeaveWidget`, `TeamLeaveWidget` | +| `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json`, `App.SettingDefinitions.json`, `App.Notifications.Notification{Types,Rules}.json`, `App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json`, `App.Hr.LeaveType.json` | Seed satırları | | `api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs` | `@Kolon` onaycı | | `api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Administration.cs` | Kullanıcı detayı istirahat kancaları | | `ui/src/views/admin/listForm/workflow/WorkflowCriteria.tsx` | Tasarımcıda alan onaycısı seçeneği | diff --git a/configs/seeds/host/docs/modules/HrOrganization.md b/configs/seeds/docs/modules/HrOrganization.md similarity index 98% rename from configs/seeds/host/docs/modules/HrOrganization.md rename to configs/seeds/docs/modules/HrOrganization.md index c487b9c8..2f9be711 100644 --- a/configs/seeds/host/docs/modules/HrOrganization.md +++ b/configs/seeds/docs/modules/HrOrganization.md @@ -89,7 +89,7 @@ Hr_T_SkillType ──< Hr_T_SkillLevel (Progress, IsDefault) Entity'ler: `Entities/Tenant/Hr/Definitions/{SkillType,Skill,SkillLevel,Department,JobPosition}.cs` (`FullAuditedEntity, IMultiTenant`); EF yapılandırması `PlatformDbContext.cs`. -Seed (`configs/seeds/host/data/`): +Seed (`configs/seeds/data/`): | Dosya | İçerik | | --- | --- | @@ -181,7 +181,7 @@ Kontrol edilmeli (koddan tespit edilen tutarsızlıklar): | `ui/src/views/admin/hr/OrgChart.tsx`, `ui/src/services/orgChart.service.ts` | Şema sayfası | | `ui/src/views/admin/user-management/Details.tsx`, `EducationSkillsTab.tsx` | Kullanıcı iş bilgileri ve yetkinlik sekmeleri | | `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` | Şube/departman/pozisyon/yetenek lookup'ları | -| `configs/seeds/host/data/App.Hr.{Department,JobPosition,SkillType,Skill,SkillLevel}.json` | Seed | -| `configs/seeds/host/data/App.Menus.Routes.json` (`admin.hr.organization`) | Şema rotası | +| `configs/seeds/data/App.Hr.{Department,JobPosition,SkillType,Skill,SkillLevel}.json` | Seed | +| `configs/seeds/data/App.Menus.Routes.json` (`admin.hr.organization`) | Şema rotası | Bu tablolar migration'lıdır; ekranlar seeder'dan kurulduğu için Wizard Manager'dan silinmez. diff --git a/configs/seeds/host/docs/modules/HrPerformance.md b/configs/seeds/docs/modules/HrPerformance.md similarity index 97% rename from configs/seeds/host/docs/modules/HrPerformance.md rename to configs/seeds/docs/modules/HrPerformance.md index 6a28e227..407874b0 100644 --- a/configs/seeds/host/docs/modules/HrPerformance.md +++ b/configs/seeds/docs/modules/HrPerformance.md @@ -371,14 +371,14 @@ yönetici (Ekip Gelişim Planı) ya da İK açar. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_Performance{Rating,Period,PeriodSkill,Review,Goal,Competency,Feedback,FeedbackScore}.sql`, `Hr_T_DevelopmentPlan.sql` | Tablolar, CHECK ve tekil indeksler | -| `configs/seeds/host/sql/object/Hr_S_Performance{Notify,ReviewRecalc,PeriodActivate,Guard,ReviewAfterSave,FeedbackOpen}.sql`, `Hr_S_DevelopmentPlanSync.sql` | Prosedürler | -| `configs/seeds/host/wizard/{PerformanceRating,PerformancePeriodSkill,PerformancePeriod,PerformanceGoal,PerformanceCompetency,PerformanceFeedbackScore,PerformanceFeedback,ReviewDevelopmentPlan,PerformanceReview,MyPerformanceGoal,MyPerformanceCompetency,MyDevelopmentPlan,MyPerformanceReview,TeamPerformanceGoal,TeamPerformanceCompetency,TeamDevelopmentPlan,TeamPerformanceReview,My360FeedbackScore,My360Feedback,PerformanceCalibration,DevelopmentPlan}.json` | 21 wizard (List) | -| `configs/seeds/host/data/App.DeveloperKit.CustomComponents/{b2d9fa60-b052-5039-a0fc-146f4e5b2e8c,98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba,d669129d-52fe-56e1-972d-d20fe1b3360e}.json` | `MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget` (code modu, rotasız) | -| `configs/seeds/host/data/App.DeveloperKit.IntranetWidgets.json` | `my-performance`, `team-performance`, `my-360-feedback` pano kayıtları | -| `configs/seeds/host/data/App.Hr.PerformanceRating.json` | 5 kademeli derece ölçeği | -| `configs/seeds/host/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları (wizard'lara ait) | -| `configs/seeds/host/data/App.Notifications.Notification{Types,Rules}.json` | 2 tip, 4 kural | +| `configs/seeds/sql/object/Hr_T_Performance{Rating,Period,PeriodSkill,Review,Goal,Competency,Feedback,FeedbackScore}.sql`, `Hr_T_DevelopmentPlan.sql` | Tablolar, CHECK ve tekil indeksler | +| `configs/seeds/sql/object/Hr_S_Performance{Notify,ReviewRecalc,PeriodActivate,Guard,ReviewAfterSave,FeedbackOpen}.sql`, `Hr_S_DevelopmentPlanSync.sql` | Prosedürler | +| `configs/seeds/wizard/{PerformanceRating,PerformancePeriodSkill,PerformancePeriod,PerformanceGoal,PerformanceCompetency,PerformanceFeedbackScore,PerformanceFeedback,ReviewDevelopmentPlan,PerformanceReview,MyPerformanceGoal,MyPerformanceCompetency,MyDevelopmentPlan,MyPerformanceReview,TeamPerformanceGoal,TeamPerformanceCompetency,TeamDevelopmentPlan,TeamPerformanceReview,My360FeedbackScore,My360Feedback,PerformanceCalibration,DevelopmentPlan}.json` | 21 wizard (List) | +| `configs/seeds/data/App.DeveloperKit.CustomComponents/{b2d9fa60-b052-5039-a0fc-146f4e5b2e8c,98a5c0b9-c8d6-5e83-ae76-d9ccb5b70cba,d669129d-52fe-56e1-972d-d20fe1b3360e}.json` | `MyPerformanceWidget`, `TeamPerformanceWidget`, `My360FeedbackWidget` (code modu, rotasız) | +| `configs/seeds/data/App.DeveloperKit.IntranetWidgets.json` | `my-performance`, `team-performance`, `my-360-feedback` pano kayıtları | +| `configs/seeds/data/App.Hr.PerformanceRating.json` | 5 kademeli derece ölçeği | +| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları (wizard'lara ait) | +| `configs/seeds/data/App.Notifications.Notification{Types,Rules}.json` | 2 tip, 4 kural | | `ui/safelist.txt` | Widget bileşenlerinin Tailwind sınıfları (`twSafelistGenerator` derlemede yeniler) | Kurulum: DB Migrate (tablolar/prosedürler `sql/object`, ekranlar `WizardDataSeeder`, satırlar diff --git a/configs/seeds/host/docs/modules/HrPersonnel.md b/configs/seeds/docs/modules/HrPersonnel.md similarity index 100% rename from configs/seeds/host/docs/modules/HrPersonnel.md rename to configs/seeds/docs/modules/HrPersonnel.md diff --git a/configs/seeds/host/docs/modules/HrRecruitment.md b/configs/seeds/docs/modules/HrRecruitment.md similarity index 97% rename from configs/seeds/host/docs/modules/HrRecruitment.md rename to configs/seeds/docs/modules/HrRecruitment.md index 452abf8c..12d3e152 100644 --- a/configs/seeds/host/docs/modules/HrRecruitment.md +++ b/configs/seeds/docs/modules/HrRecruitment.md @@ -216,12 +216,12 @@ KVKK: aday verisi kişisel veridir; Adaylar ekranının yetkisi yalnızca işe a | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.sql` | Tablolar, CHECK ve tekil indeksler | -| `configs/seeds/host/wizard/{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.json` | 10 wizard | -| `configs/seeds/host/data/App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json` | Tanım verisi | -| `configs/seeds/host/data/App.Reports.ReportTemplates.json` | KVKK formu ve teklif mektubu şablonları | -| `configs/seeds/host/data/App.Notifications.Notification{Types,Rules}.json` | `IseAlimMulakatAtandi` tipi ve 2 kuralı | -| `configs/seeds/host/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | +| `configs/seeds/sql/object/Hr_T_{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.sql` | Tablolar, CHECK ve tekil indeksler | +| `configs/seeds/wizard/{StaffRequest,JobPosting,Candidate,JobApplication,Interview,InterviewScore,JobOffer,CandidateSource,RejectionReason,EvaluationCriterion}.json` | 10 wizard | +| `configs/seeds/data/App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json` | Tanım verisi | +| `configs/seeds/data/App.Reports.ReportTemplates.json` | KVKK formu ve teklif mektubu şablonları | +| `configs/seeds/data/App.Notifications.Notification{Types,Rules}.json` | `IseAlimMulakatAtandi` tipi ve 2 kuralı | +| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | Kurulum: DB Migrate. Geri alma: Wizard Manager'dan wizard'lar silinir (alt formlar önce: `InterviewScore` → `Interview`/`JobOffer` → `JobApplication` → diğerleri); tablolar yalnızca onayda seçilirse düşer. diff --git a/configs/seeds/host/docs/modules/HrTrainings.md b/configs/seeds/docs/modules/HrTrainings.md similarity index 96% rename from configs/seeds/host/docs/modules/HrTrainings.md rename to configs/seeds/docs/modules/HrTrainings.md index e98c7c36..3dca1f4c 100644 --- a/configs/seeds/host/docs/modules/HrTrainings.md +++ b/configs/seeds/docs/modules/HrTrainings.md @@ -159,11 +159,11 @@ altında üç ekranın kökü + `.Create/.Update/.Delete/.Export/.Import/.Note`; | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.sql` | Tablolar | -| `configs/seeds/host/wizard/{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.json` | 5 wizard | -| `configs/seeds/host/data/App.Hr.{TrainingType,TrainingSubject}.json` | Tanım verisi | -| `configs/seeds/host/data/App.Reports.ReportTemplates.json` | Katılım Belgesi şablonu | -| `configs/seeds/host/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | +| `configs/seeds/sql/object/Hr_T_{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.sql` | Tablolar | +| `configs/seeds/wizard/{TrainingType,TrainingSubject,Training,TrainingParticipant,TrainingFile}.json` | 5 wizard | +| `configs/seeds/data/App.Hr.{TrainingType,TrainingSubject}.json` | Tanım verisi | +| `configs/seeds/data/App.Reports.ReportTemplates.json` | Katılım Belgesi şablonu | +| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar (`TrainingParticipant`, `TrainingFile`) önce, sonra `Training`, `TrainingSubject`, `TrainingType` silinir; tablolar yalnızca onayda seçilirse diff --git a/configs/seeds/host/docs/modules/HrVehicles.md b/configs/seeds/docs/modules/HrVehicles.md similarity index 97% rename from configs/seeds/host/docs/modules/HrVehicles.md rename to configs/seeds/docs/modules/HrVehicles.md index 282862bf..cd1b9b2e 100644 --- a/configs/seeds/host/docs/modules/HrVehicles.md +++ b/configs/seeds/docs/modules/HrVehicles.md @@ -202,10 +202,10 @@ temizlenmeden kaydedilen bir çıkış kaydı aracın km'sini 0'a çekebilir. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.sql` | Tablolar | -| `configs/seeds/host/wizard/{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.json` | 12 wizard | -| `configs/seeds/host/data/App.Hr.{VehicleDocument,VehicleExpenseDefinition}.json` | Tanım verisi | -| `configs/seeds/host/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | +| `configs/seeds/sql/object/Hr_T_{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.sql` | Tablolar | +| `configs/seeds/wizard/{VehicleDocument,Vehicle,VehicleFile,VehicleDestination,Driver,DriverDocument,DriverPenalty,DriverAccident,VehicleExpenseDefinition,VehicleExpense,VehicleExpenseLine,VehicleMovement}.json` | 12 wizard | +| `configs/seeds/data/App.Hr.{VehicleDocument,VehicleExpenseDefinition}.json` | Tanım verisi | +| `configs/seeds/data/App.Menus.Menu.json`, `Abp.Identity.Permissions.json`, `App.Languages.LanguageText.json` | Menü, yetki, dil satırları | Kurulum: DB Migrate. Geri alma: Wizard Manager'dan alt formlar önce, ana ekranlar sonra silinir; tablolar yalnızca onayda seçilirse düşer. diff --git a/configs/seeds/host/docs/modules/HrVisitor.md b/configs/seeds/docs/modules/HrVisitor.md similarity index 98% rename from configs/seeds/host/docs/modules/HrVisitor.md rename to configs/seeds/docs/modules/HrVisitor.md index b7ebb10f..9b80526e 100644 --- a/configs/seeds/host/docs/modules/HrVisitor.md +++ b/configs/seeds/docs/modules/HrVisitor.md @@ -108,8 +108,8 @@ veridir; saklama süresi için periyodik temizlik yoktur. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_Visitor.sql` | Tablo | -| `configs/seeds/host/wizard/Visitor.json` | Wizard | +| `configs/seeds/sql/object/Hr_T_Visitor.sql` | Tablo | +| `configs/seeds/wizard/Visitor.json` | Wizard | Geri alma: Wizard Manager'dan silinir; tablo yalnızca onayda seçilirse düşer. diff --git a/configs/seeds/host/docs/modules/HrWorkCalendar.md b/configs/seeds/docs/modules/HrWorkCalendar.md similarity index 97% rename from configs/seeds/host/docs/modules/HrWorkCalendar.md rename to configs/seeds/docs/modules/HrWorkCalendar.md index 29afea25..778a870d 100644 --- a/configs/seeds/host/docs/modules/HrWorkCalendar.md +++ b/configs/seeds/docs/modules/HrWorkCalendar.md @@ -132,9 +132,9 @@ yetki ağacı aynı yeri gösterir. | Dosya | İçerik | | --- | --- | -| `configs/seeds/host/sql/object/Hr_T_{WorkCalendar,Shift}.sql` | Tablolar | -| `configs/seeds/host/wizard/{WorkCalendar,Shift}.json` | 2 wizard | -| `configs/seeds/host/data/App.Hr.{WorkCalendar,Shift}.json` | Takvim ve vardiya verisi (ekran kayıtları dosyaya aynalanır) | +| `configs/seeds/sql/object/Hr_T_{WorkCalendar,Shift}.sql` | Tablolar | +| `configs/seeds/wizard/{WorkCalendar,Shift}.json` | 2 wizard | +| `configs/seeds/data/App.Hr.{WorkCalendar,Shift}.json` | Takvim ve vardiya verisi (ekran kayıtları dosyaya aynalanır) | | `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` (`TenantShiftValues`) | Kullanıcı ekranındaki vardiya lookup'ı | Geri alma: Wizard Manager'dan silinir; vardiya tablosu kullanıcı ekranının lookup'ı tarafından okunduğu için düşürülmemelidir. diff --git a/configs/seeds/host/postgres/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql b/configs/seeds/postgres/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql similarity index 100% rename from configs/seeds/host/postgres/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql rename to configs/seeds/postgres/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql diff --git a/configs/seeds/host/postgres/object/Sas_H_BackgroundWorker_JobFlow.sql b/configs/seeds/postgres/host/object/Sas_H_BackgroundWorker_JobFlow.sql similarity index 100% rename from configs/seeds/host/postgres/object/Sas_H_BackgroundWorker_JobFlow.sql rename to configs/seeds/postgres/host/object/Sas_H_BackgroundWorker_JobFlow.sql diff --git a/configs/seeds/host/postgres/object/Adm_T_DatabaseBackupAll.sql b/configs/seeds/postgres/object/Adm_T_DatabaseBackupAll.sql similarity index 100% rename from configs/seeds/host/postgres/object/Adm_T_DatabaseBackupAll.sql rename to configs/seeds/postgres/object/Adm_T_DatabaseBackupAll.sql diff --git a/configs/seeds/host/postgres/object/Adm_T_DatabaseBackupFilesDeleteAll.sql b/configs/seeds/postgres/object/Adm_T_DatabaseBackupFilesDeleteAll.sql similarity index 100% rename from configs/seeds/host/postgres/object/Adm_T_DatabaseBackupFilesDeleteAll.sql rename to configs/seeds/postgres/object/Adm_T_DatabaseBackupFilesDeleteAll.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_Customer.sql b/configs/seeds/postgres/object/Prj_T_Customer.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_Customer.sql rename to configs/seeds/postgres/object/Prj_T_Customer.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_Milestone.sql b/configs/seeds/postgres/object/Prj_T_Milestone.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_Milestone.sql rename to configs/seeds/postgres/object/Prj_T_Milestone.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_Tag.sql b/configs/seeds/postgres/object/Prj_T_Tag.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_Tag.sql rename to configs/seeds/postgres/object/Prj_T_Tag.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_Task.sql b/configs/seeds/postgres/object/Prj_T_Task.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_Task.sql rename to configs/seeds/postgres/object/Prj_T_Task.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_TaskDependency.sql b/configs/seeds/postgres/object/Prj_T_TaskDependency.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_TaskDependency.sql rename to configs/seeds/postgres/object/Prj_T_TaskDependency.sql diff --git a/configs/seeds/host/postgres/object/Prj_T_Timesheet.sql b/configs/seeds/postgres/object/Prj_T_Timesheet.sql similarity index 100% rename from configs/seeds/host/postgres/object/Prj_T_Timesheet.sql rename to configs/seeds/postgres/object/Prj_T_Timesheet.sql diff --git a/configs/seeds/host/postgres/object/Sas_H_ListFormFieldsDictionaryJson.sql b/configs/seeds/postgres/object/Sas_H_ListFormFieldsDictionaryJson.sql similarity index 100% rename from configs/seeds/host/postgres/object/Sas_H_ListFormFieldsDictionaryJson.sql rename to configs/seeds/postgres/object/Sas_H_ListFormFieldsDictionaryJson.sql diff --git a/configs/seeds/host/postgres/object/Sas_H_OrderItem_ByTenantId.sql b/configs/seeds/postgres/object/Sas_H_OrderItem_ByTenantId.sql similarity index 100% rename from configs/seeds/host/postgres/object/Sas_H_OrderItem_ByTenantId.sql rename to configs/seeds/postgres/object/Sas_H_OrderItem_ByTenantId.sql diff --git a/configs/seeds/host/sql/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql b/configs/seeds/sql/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql similarity index 100% rename from configs/seeds/host/sql/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql rename to configs/seeds/sql/execute/Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns.sql diff --git a/configs/seeds/host/sql/object/Sas_H_BackgroundWorker_JobFlow.sql b/configs/seeds/sql/host/object/Sas_H_BackgroundWorker_JobFlow.sql similarity index 100% rename from configs/seeds/host/sql/object/Sas_H_BackgroundWorker_JobFlow.sql rename to configs/seeds/sql/host/object/Sas_H_BackgroundWorker_JobFlow.sql diff --git a/configs/seeds/host/sql/object/Adm_T_DatabaseBackupAll.sql b/configs/seeds/sql/object/Adm_T_DatabaseBackupAll.sql similarity index 100% rename from configs/seeds/host/sql/object/Adm_T_DatabaseBackupAll.sql rename to configs/seeds/sql/object/Adm_T_DatabaseBackupAll.sql diff --git a/configs/seeds/host/sql/object/Adm_T_DatabaseBackupFilesDeleteAll.sql b/configs/seeds/sql/object/Adm_T_DatabaseBackupFilesDeleteAll.sql similarity index 100% rename from configs/seeds/host/sql/object/Adm_T_DatabaseBackupFilesDeleteAll.sql rename to configs/seeds/sql/object/Adm_T_DatabaseBackupFilesDeleteAll.sql diff --git a/configs/seeds/host/sql/object/Hr_F_HrSetting.sql b/configs/seeds/sql/object/Hr_F_HrSetting.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_F_HrSetting.sql rename to configs/seeds/sql/object/Hr_F_HrSetting.sql diff --git a/configs/seeds/host/sql/object/Hr_F_LeaveNetDays.sql b/configs/seeds/sql/object/Hr_F_LeaveNetDays.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_F_LeaveNetDays.sql rename to configs/seeds/sql/object/Hr_F_LeaveNetDays.sql diff --git a/configs/seeds/host/sql/object/Hr_F_LeaveWorkDays.sql b/configs/seeds/sql/object/Hr_F_LeaveWorkDays.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_F_LeaveWorkDays.sql rename to configs/seeds/sql/object/Hr_F_LeaveWorkDays.sql diff --git a/configs/seeds/host/sql/object/Hr_F_TimesheetPeriodClosed.sql b/configs/seeds/sql/object/Hr_F_TimesheetPeriodClosed.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_F_TimesheetPeriodClosed.sql rename to configs/seeds/sql/object/Hr_F_TimesheetPeriodClosed.sql diff --git a/configs/seeds/host/sql/object/Hr_F_WorkCalendarDayType.sql b/configs/seeds/sql/object/Hr_F_WorkCalendarDayType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_F_WorkCalendarDayType.sql rename to configs/seeds/sql/object/Hr_F_WorkCalendarDayType.sql diff --git a/configs/seeds/host/sql/object/Hr_S_AttendanceImport.sql b/configs/seeds/sql/object/Hr_S_AttendanceImport.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_AttendanceImport.sql rename to configs/seeds/sql/object/Hr_S_AttendanceImport.sql diff --git a/configs/seeds/host/sql/object/Hr_S_AttendanceProcess.sql b/configs/seeds/sql/object/Hr_S_AttendanceProcess.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_AttendanceProcess.sql rename to configs/seeds/sql/object/Hr_S_AttendanceProcess.sql diff --git a/configs/seeds/host/sql/object/Hr_S_DevelopmentPlanSync.sql b/configs/seeds/sql/object/Hr_S_DevelopmentPlanSync.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_DevelopmentPlanSync.sql rename to configs/seeds/sql/object/Hr_S_DevelopmentPlanSync.sql diff --git a/configs/seeds/host/sql/object/Hr_S_LeaveDaysRefresh.sql b/configs/seeds/sql/object/Hr_S_LeaveDaysRefresh.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_LeaveDaysRefresh.sql rename to configs/seeds/sql/object/Hr_S_LeaveDaysRefresh.sql diff --git a/configs/seeds/host/sql/object/Hr_S_LeaveNotify.sql b/configs/seeds/sql/object/Hr_S_LeaveNotify.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_LeaveNotify.sql rename to configs/seeds/sql/object/Hr_S_LeaveNotify.sql diff --git a/configs/seeds/host/sql/object/Hr_S_LeaveRequestAfterSave.sql b/configs/seeds/sql/object/Hr_S_LeaveRequestAfterSave.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_LeaveRequestAfterSave.sql rename to configs/seeds/sql/object/Hr_S_LeaveRequestAfterSave.sql diff --git a/configs/seeds/host/sql/object/Hr_S_LeaveRequestGuard.sql b/configs/seeds/sql/object/Hr_S_LeaveRequestGuard.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_LeaveRequestGuard.sql rename to configs/seeds/sql/object/Hr_S_LeaveRequestGuard.sql diff --git a/configs/seeds/host/sql/object/Hr_S_LeaveSickLeaveSync.sql b/configs/seeds/sql/object/Hr_S_LeaveSickLeaveSync.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_LeaveSickLeaveSync.sql rename to configs/seeds/sql/object/Hr_S_LeaveSickLeaveSync.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformanceFeedbackOpen.sql b/configs/seeds/sql/object/Hr_S_PerformanceFeedbackOpen.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformanceFeedbackOpen.sql rename to configs/seeds/sql/object/Hr_S_PerformanceFeedbackOpen.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformanceGuard.sql b/configs/seeds/sql/object/Hr_S_PerformanceGuard.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformanceGuard.sql rename to configs/seeds/sql/object/Hr_S_PerformanceGuard.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformanceNotify.sql b/configs/seeds/sql/object/Hr_S_PerformanceNotify.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformanceNotify.sql rename to configs/seeds/sql/object/Hr_S_PerformanceNotify.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformancePeriodActivate.sql b/configs/seeds/sql/object/Hr_S_PerformancePeriodActivate.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformancePeriodActivate.sql rename to configs/seeds/sql/object/Hr_S_PerformancePeriodActivate.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformanceReviewAfterSave.sql b/configs/seeds/sql/object/Hr_S_PerformanceReviewAfterSave.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformanceReviewAfterSave.sql rename to configs/seeds/sql/object/Hr_S_PerformanceReviewAfterSave.sql diff --git a/configs/seeds/host/sql/object/Hr_S_PerformanceReviewRecalc.sql b/configs/seeds/sql/object/Hr_S_PerformanceReviewRecalc.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_PerformanceReviewRecalc.sql rename to configs/seeds/sql/object/Hr_S_PerformanceReviewRecalc.sql diff --git a/configs/seeds/host/sql/object/Hr_S_TimesheetCalculate.sql b/configs/seeds/sql/object/Hr_S_TimesheetCalculate.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_TimesheetCalculate.sql rename to configs/seeds/sql/object/Hr_S_TimesheetCalculate.sql diff --git a/configs/seeds/host/sql/object/Hr_S_TimesheetNightly.sql b/configs/seeds/sql/object/Hr_S_TimesheetNightly.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_TimesheetNightly.sql rename to configs/seeds/sql/object/Hr_S_TimesheetNightly.sql diff --git a/configs/seeds/host/sql/object/Hr_S_TimesheetPeriodAfterSave.sql b/configs/seeds/sql/object/Hr_S_TimesheetPeriodAfterSave.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_TimesheetPeriodAfterSave.sql rename to configs/seeds/sql/object/Hr_S_TimesheetPeriodAfterSave.sql diff --git a/configs/seeds/host/sql/object/Hr_S_TrainingSync.sql b/configs/seeds/sql/object/Hr_S_TrainingSync.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_TrainingSync.sql rename to configs/seeds/sql/object/Hr_S_TrainingSync.sql diff --git a/configs/seeds/host/sql/object/Hr_S_WorkCalendarAfterSave.sql b/configs/seeds/sql/object/Hr_S_WorkCalendarAfterSave.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_S_WorkCalendarAfterSave.sql rename to configs/seeds/sql/object/Hr_S_WorkCalendarAfterSave.sql diff --git a/configs/seeds/host/sql/object/Hr_T_AttendanceLog.sql b/configs/seeds/sql/object/Hr_T_AttendanceLog.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_AttendanceLog.sql rename to configs/seeds/sql/object/Hr_T_AttendanceLog.sql diff --git a/configs/seeds/host/sql/object/Hr_T_BenefitType.sql b/configs/seeds/sql/object/Hr_T_BenefitType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_BenefitType.sql rename to configs/seeds/sql/object/Hr_T_BenefitType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Candidate.sql b/configs/seeds/sql/object/Hr_T_Candidate.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Candidate.sql rename to configs/seeds/sql/object/Hr_T_Candidate.sql diff --git a/configs/seeds/host/sql/object/Hr_T_CandidateSource.sql b/configs/seeds/sql/object/Hr_T_CandidateSource.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_CandidateSource.sql rename to configs/seeds/sql/object/Hr_T_CandidateSource.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Certificate.sql b/configs/seeds/sql/object/Hr_T_Certificate.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Certificate.sql rename to configs/seeds/sql/object/Hr_T_Certificate.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DevelopmentPlan.sql b/configs/seeds/sql/object/Hr_T_DevelopmentPlan.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DevelopmentPlan.sql rename to configs/seeds/sql/object/Hr_T_DevelopmentPlan.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DisciplinaryCase.sql b/configs/seeds/sql/object/Hr_T_DisciplinaryCase.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DisciplinaryCase.sql rename to configs/seeds/sql/object/Hr_T_DisciplinaryCase.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DisciplinaryDecision.sql b/configs/seeds/sql/object/Hr_T_DisciplinaryDecision.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DisciplinaryDecision.sql rename to configs/seeds/sql/object/Hr_T_DisciplinaryDecision.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DisciplinaryDefense.sql b/configs/seeds/sql/object/Hr_T_DisciplinaryDefense.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DisciplinaryDefense.sql rename to configs/seeds/sql/object/Hr_T_DisciplinaryDefense.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DisciplinaryFile.sql b/configs/seeds/sql/object/Hr_T_DisciplinaryFile.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DisciplinaryFile.sql rename to configs/seeds/sql/object/Hr_T_DisciplinaryFile.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DisciplinaryPenaltyType.sql b/configs/seeds/sql/object/Hr_T_DisciplinaryPenaltyType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DisciplinaryPenaltyType.sql rename to configs/seeds/sql/object/Hr_T_DisciplinaryPenaltyType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Doctor.sql b/configs/seeds/sql/object/Hr_T_Doctor.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Doctor.sql rename to configs/seeds/sql/object/Hr_T_Doctor.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DocumentType.sql b/configs/seeds/sql/object/Hr_T_DocumentType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DocumentType.sql rename to configs/seeds/sql/object/Hr_T_DocumentType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Driver.sql b/configs/seeds/sql/object/Hr_T_Driver.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Driver.sql rename to configs/seeds/sql/object/Hr_T_Driver.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DriverAccident.sql b/configs/seeds/sql/object/Hr_T_DriverAccident.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DriverAccident.sql rename to configs/seeds/sql/object/Hr_T_DriverAccident.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DriverDocument.sql b/configs/seeds/sql/object/Hr_T_DriverDocument.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DriverDocument.sql rename to configs/seeds/sql/object/Hr_T_DriverDocument.sql diff --git a/configs/seeds/host/sql/object/Hr_T_DriverPenalty.sql b/configs/seeds/sql/object/Hr_T_DriverPenalty.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_DriverPenalty.sql rename to configs/seeds/sql/object/Hr_T_DriverPenalty.sql diff --git a/configs/seeds/host/sql/object/Hr_T_EvaluationCriterion.sql b/configs/seeds/sql/object/Hr_T_EvaluationCriterion.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_EvaluationCriterion.sql rename to configs/seeds/sql/object/Hr_T_EvaluationCriterion.sql diff --git a/configs/seeds/host/sql/object/Hr_T_EventLog.sql b/configs/seeds/sql/object/Hr_T_EventLog.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_EventLog.sql rename to configs/seeds/sql/object/Hr_T_EventLog.sql diff --git a/configs/seeds/host/sql/object/Hr_T_ExpenseType.sql b/configs/seeds/sql/object/Hr_T_ExpenseType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_ExpenseType.sql rename to configs/seeds/sql/object/Hr_T_ExpenseType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Hospital.sql b/configs/seeds/sql/object/Hr_T_Hospital.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Hospital.sql rename to configs/seeds/sql/object/Hr_T_Hospital.sql diff --git a/configs/seeds/host/sql/object/Hr_T_HospitalDepartment.sql b/configs/seeds/sql/object/Hr_T_HospitalDepartment.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_HospitalDepartment.sql rename to configs/seeds/sql/object/Hr_T_HospitalDepartment.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Interview.sql b/configs/seeds/sql/object/Hr_T_Interview.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Interview.sql rename to configs/seeds/sql/object/Hr_T_Interview.sql diff --git a/configs/seeds/host/sql/object/Hr_T_InterviewScore.sql b/configs/seeds/sql/object/Hr_T_InterviewScore.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_InterviewScore.sql rename to configs/seeds/sql/object/Hr_T_InterviewScore.sql diff --git a/configs/seeds/host/sql/object/Hr_T_JobApplication.sql b/configs/seeds/sql/object/Hr_T_JobApplication.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_JobApplication.sql rename to configs/seeds/sql/object/Hr_T_JobApplication.sql diff --git a/configs/seeds/host/sql/object/Hr_T_JobOffer.sql b/configs/seeds/sql/object/Hr_T_JobOffer.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_JobOffer.sql rename to configs/seeds/sql/object/Hr_T_JobOffer.sql diff --git a/configs/seeds/host/sql/object/Hr_T_JobPosting.sql b/configs/seeds/sql/object/Hr_T_JobPosting.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_JobPosting.sql rename to configs/seeds/sql/object/Hr_T_JobPosting.sql diff --git a/configs/seeds/host/sql/object/Hr_T_LeaveBalanceAdjustment.sql b/configs/seeds/sql/object/Hr_T_LeaveBalanceAdjustment.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_LeaveBalanceAdjustment.sql rename to configs/seeds/sql/object/Hr_T_LeaveBalanceAdjustment.sql diff --git a/configs/seeds/host/sql/object/Hr_T_LeaveRequest.sql b/configs/seeds/sql/object/Hr_T_LeaveRequest.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_LeaveRequest.sql rename to configs/seeds/sql/object/Hr_T_LeaveRequest.sql diff --git a/configs/seeds/host/sql/object/Hr_T_LeaveType.sql b/configs/seeds/sql/object/Hr_T_LeaveType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_LeaveType.sql rename to configs/seeds/sql/object/Hr_T_LeaveType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceCompetency.sql b/configs/seeds/sql/object/Hr_T_PerformanceCompetency.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceCompetency.sql rename to configs/seeds/sql/object/Hr_T_PerformanceCompetency.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceFeedback.sql b/configs/seeds/sql/object/Hr_T_PerformanceFeedback.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceFeedback.sql rename to configs/seeds/sql/object/Hr_T_PerformanceFeedback.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceFeedbackScore.sql b/configs/seeds/sql/object/Hr_T_PerformanceFeedbackScore.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceFeedbackScore.sql rename to configs/seeds/sql/object/Hr_T_PerformanceFeedbackScore.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceGoal.sql b/configs/seeds/sql/object/Hr_T_PerformanceGoal.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceGoal.sql rename to configs/seeds/sql/object/Hr_T_PerformanceGoal.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformancePeriod.sql b/configs/seeds/sql/object/Hr_T_PerformancePeriod.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformancePeriod.sql rename to configs/seeds/sql/object/Hr_T_PerformancePeriod.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformancePeriodSkill.sql b/configs/seeds/sql/object/Hr_T_PerformancePeriodSkill.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformancePeriodSkill.sql rename to configs/seeds/sql/object/Hr_T_PerformancePeriodSkill.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceRating.sql b/configs/seeds/sql/object/Hr_T_PerformanceRating.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceRating.sql rename to configs/seeds/sql/object/Hr_T_PerformanceRating.sql diff --git a/configs/seeds/host/sql/object/Hr_T_PerformanceReview.sql b/configs/seeds/sql/object/Hr_T_PerformanceReview.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_PerformanceReview.sql rename to configs/seeds/sql/object/Hr_T_PerformanceReview.sql diff --git a/configs/seeds/host/sql/object/Hr_T_RejectionReason.sql b/configs/seeds/sql/object/Hr_T_RejectionReason.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_RejectionReason.sql rename to configs/seeds/sql/object/Hr_T_RejectionReason.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Shift.sql b/configs/seeds/sql/object/Hr_T_Shift.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Shift.sql rename to configs/seeds/sql/object/Hr_T_Shift.sql diff --git a/configs/seeds/host/sql/object/Hr_T_StaffRequest.sql b/configs/seeds/sql/object/Hr_T_StaffRequest.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_StaffRequest.sql rename to configs/seeds/sql/object/Hr_T_StaffRequest.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Timesheet.sql b/configs/seeds/sql/object/Hr_T_Timesheet.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Timesheet.sql rename to configs/seeds/sql/object/Hr_T_Timesheet.sql diff --git a/configs/seeds/host/sql/object/Hr_T_TimesheetPeriod.sql b/configs/seeds/sql/object/Hr_T_TimesheetPeriod.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_TimesheetPeriod.sql rename to configs/seeds/sql/object/Hr_T_TimesheetPeriod.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Training.sql b/configs/seeds/sql/object/Hr_T_Training.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Training.sql rename to configs/seeds/sql/object/Hr_T_Training.sql diff --git a/configs/seeds/host/sql/object/Hr_T_TrainingFile.sql b/configs/seeds/sql/object/Hr_T_TrainingFile.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_TrainingFile.sql rename to configs/seeds/sql/object/Hr_T_TrainingFile.sql diff --git a/configs/seeds/host/sql/object/Hr_T_TrainingParticipant.sql b/configs/seeds/sql/object/Hr_T_TrainingParticipant.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_TrainingParticipant.sql rename to configs/seeds/sql/object/Hr_T_TrainingParticipant.sql diff --git a/configs/seeds/host/sql/object/Hr_T_TrainingSubject.sql b/configs/seeds/sql/object/Hr_T_TrainingSubject.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_TrainingSubject.sql rename to configs/seeds/sql/object/Hr_T_TrainingSubject.sql diff --git a/configs/seeds/host/sql/object/Hr_T_TrainingType.sql b/configs/seeds/sql/object/Hr_T_TrainingType.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_TrainingType.sql rename to configs/seeds/sql/object/Hr_T_TrainingType.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Vehicle.sql b/configs/seeds/sql/object/Hr_T_Vehicle.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Vehicle.sql rename to configs/seeds/sql/object/Hr_T_Vehicle.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleDestination.sql b/configs/seeds/sql/object/Hr_T_VehicleDestination.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleDestination.sql rename to configs/seeds/sql/object/Hr_T_VehicleDestination.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleDocument.sql b/configs/seeds/sql/object/Hr_T_VehicleDocument.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleDocument.sql rename to configs/seeds/sql/object/Hr_T_VehicleDocument.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleExpense.sql b/configs/seeds/sql/object/Hr_T_VehicleExpense.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleExpense.sql rename to configs/seeds/sql/object/Hr_T_VehicleExpense.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleExpenseDefinition.sql b/configs/seeds/sql/object/Hr_T_VehicleExpenseDefinition.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleExpenseDefinition.sql rename to configs/seeds/sql/object/Hr_T_VehicleExpenseDefinition.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleExpenseLine.sql b/configs/seeds/sql/object/Hr_T_VehicleExpenseLine.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleExpenseLine.sql rename to configs/seeds/sql/object/Hr_T_VehicleExpenseLine.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleFile.sql b/configs/seeds/sql/object/Hr_T_VehicleFile.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleFile.sql rename to configs/seeds/sql/object/Hr_T_VehicleFile.sql diff --git a/configs/seeds/host/sql/object/Hr_T_VehicleMovement.sql b/configs/seeds/sql/object/Hr_T_VehicleMovement.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_VehicleMovement.sql rename to configs/seeds/sql/object/Hr_T_VehicleMovement.sql diff --git a/configs/seeds/host/sql/object/Hr_T_Visitor.sql b/configs/seeds/sql/object/Hr_T_Visitor.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_Visitor.sql rename to configs/seeds/sql/object/Hr_T_Visitor.sql diff --git a/configs/seeds/host/sql/object/Hr_T_WorkCalendar.sql b/configs/seeds/sql/object/Hr_T_WorkCalendar.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_T_WorkCalendar.sql rename to configs/seeds/sql/object/Hr_T_WorkCalendar.sql diff --git a/configs/seeds/host/sql/object/Hr_V_AttendanceShift.sql b/configs/seeds/sql/object/Hr_V_AttendanceShift.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_V_AttendanceShift.sql rename to configs/seeds/sql/object/Hr_V_AttendanceShift.sql diff --git a/configs/seeds/host/sql/object/Hr_V_LeaveBalanceYear.sql b/configs/seeds/sql/object/Hr_V_LeaveBalanceYear.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_V_LeaveBalanceYear.sql rename to configs/seeds/sql/object/Hr_V_LeaveBalanceYear.sql diff --git a/configs/seeds/host/sql/object/Hr_V_LeaveSummary.sql b/configs/seeds/sql/object/Hr_V_LeaveSummary.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_V_LeaveSummary.sql rename to configs/seeds/sql/object/Hr_V_LeaveSummary.sql diff --git a/configs/seeds/host/sql/object/Hr_V_TimesheetMonthly.sql b/configs/seeds/sql/object/Hr_V_TimesheetMonthly.sql similarity index 100% rename from configs/seeds/host/sql/object/Hr_V_TimesheetMonthly.sql rename to configs/seeds/sql/object/Hr_V_TimesheetMonthly.sql diff --git a/configs/seeds/host/sql/object/Sas_H_ListFormFieldsDictionaryJson.sql b/configs/seeds/sql/object/Sas_H_ListFormFieldsDictionaryJson.sql similarity index 100% rename from configs/seeds/host/sql/object/Sas_H_ListFormFieldsDictionaryJson.sql rename to configs/seeds/sql/object/Sas_H_ListFormFieldsDictionaryJson.sql diff --git a/configs/seeds/host/sql/object/Sas_H_OrderItem_ByTenantId.sql b/configs/seeds/sql/object/Sas_H_OrderItem_ByTenantId.sql similarity index 100% rename from configs/seeds/host/sql/object/Sas_H_OrderItem_ByTenantId.sql rename to configs/seeds/sql/object/Sas_H_OrderItem_ByTenantId.sql diff --git a/configs/seeds/host/wizard/AttendanceLog.json b/configs/seeds/wizard/AttendanceLog.json similarity index 100% rename from configs/seeds/host/wizard/AttendanceLog.json rename to configs/seeds/wizard/AttendanceLog.json diff --git a/configs/seeds/host/wizard/BenefitType.json b/configs/seeds/wizard/BenefitType.json similarity index 100% rename from configs/seeds/host/wizard/BenefitType.json rename to configs/seeds/wizard/BenefitType.json diff --git a/configs/seeds/host/wizard/Candidate.json b/configs/seeds/wizard/Candidate.json similarity index 100% rename from configs/seeds/host/wizard/Candidate.json rename to configs/seeds/wizard/Candidate.json diff --git a/configs/seeds/host/wizard/CandidateSource.json b/configs/seeds/wizard/CandidateSource.json similarity index 100% rename from configs/seeds/host/wizard/CandidateSource.json rename to configs/seeds/wizard/CandidateSource.json diff --git a/configs/seeds/host/wizard/Certificate.json b/configs/seeds/wizard/Certificate.json similarity index 100% rename from configs/seeds/host/wizard/Certificate.json rename to configs/seeds/wizard/Certificate.json diff --git a/configs/seeds/host/wizard/DevelopmentPlan.json b/configs/seeds/wizard/DevelopmentPlan.json similarity index 100% rename from configs/seeds/host/wizard/DevelopmentPlan.json rename to configs/seeds/wizard/DevelopmentPlan.json diff --git a/configs/seeds/host/wizard/DisciplinaryCase.json b/configs/seeds/wizard/DisciplinaryCase.json similarity index 100% rename from configs/seeds/host/wizard/DisciplinaryCase.json rename to configs/seeds/wizard/DisciplinaryCase.json diff --git a/configs/seeds/host/wizard/DisciplinaryDecision.json b/configs/seeds/wizard/DisciplinaryDecision.json similarity index 100% rename from configs/seeds/host/wizard/DisciplinaryDecision.json rename to configs/seeds/wizard/DisciplinaryDecision.json diff --git a/configs/seeds/host/wizard/DisciplinaryDefense.json b/configs/seeds/wizard/DisciplinaryDefense.json similarity index 100% rename from configs/seeds/host/wizard/DisciplinaryDefense.json rename to configs/seeds/wizard/DisciplinaryDefense.json diff --git a/configs/seeds/host/wizard/DisciplinaryFile.json b/configs/seeds/wizard/DisciplinaryFile.json similarity index 100% rename from configs/seeds/host/wizard/DisciplinaryFile.json rename to configs/seeds/wizard/DisciplinaryFile.json diff --git a/configs/seeds/host/wizard/DisciplinaryPenaltyType.json b/configs/seeds/wizard/DisciplinaryPenaltyType.json similarity index 100% rename from configs/seeds/host/wizard/DisciplinaryPenaltyType.json rename to configs/seeds/wizard/DisciplinaryPenaltyType.json diff --git a/configs/seeds/host/wizard/Doctor.json b/configs/seeds/wizard/Doctor.json similarity index 100% rename from configs/seeds/host/wizard/Doctor.json rename to configs/seeds/wizard/Doctor.json diff --git a/configs/seeds/host/wizard/DocumentType.json b/configs/seeds/wizard/DocumentType.json similarity index 100% rename from configs/seeds/host/wizard/DocumentType.json rename to configs/seeds/wizard/DocumentType.json diff --git a/configs/seeds/host/wizard/Driver.json b/configs/seeds/wizard/Driver.json similarity index 100% rename from configs/seeds/host/wizard/Driver.json rename to configs/seeds/wizard/Driver.json diff --git a/configs/seeds/host/wizard/DriverAccident.json b/configs/seeds/wizard/DriverAccident.json similarity index 100% rename from configs/seeds/host/wizard/DriverAccident.json rename to configs/seeds/wizard/DriverAccident.json diff --git a/configs/seeds/host/wizard/DriverDocument.json b/configs/seeds/wizard/DriverDocument.json similarity index 100% rename from configs/seeds/host/wizard/DriverDocument.json rename to configs/seeds/wizard/DriverDocument.json diff --git a/configs/seeds/host/wizard/DriverPenalty.json b/configs/seeds/wizard/DriverPenalty.json similarity index 100% rename from configs/seeds/host/wizard/DriverPenalty.json rename to configs/seeds/wizard/DriverPenalty.json diff --git a/configs/seeds/host/wizard/EvaluationCriterion.json b/configs/seeds/wizard/EvaluationCriterion.json similarity index 100% rename from configs/seeds/host/wizard/EvaluationCriterion.json rename to configs/seeds/wizard/EvaluationCriterion.json diff --git a/configs/seeds/host/wizard/EventLog.json b/configs/seeds/wizard/EventLog.json similarity index 100% rename from configs/seeds/host/wizard/EventLog.json rename to configs/seeds/wizard/EventLog.json diff --git a/configs/seeds/host/wizard/ExpenseType.json b/configs/seeds/wizard/ExpenseType.json similarity index 100% rename from configs/seeds/host/wizard/ExpenseType.json rename to configs/seeds/wizard/ExpenseType.json diff --git a/configs/seeds/host/wizard/HealthCard.json b/configs/seeds/wizard/HealthCard.json similarity index 100% rename from configs/seeds/host/wizard/HealthCard.json rename to configs/seeds/wizard/HealthCard.json diff --git a/configs/seeds/host/wizard/HealthCardAnamnesis.json b/configs/seeds/wizard/HealthCardAnamnesis.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardAnamnesis.json rename to configs/seeds/wizard/HealthCardAnamnesis.json diff --git a/configs/seeds/host/wizard/HealthCardHistory.json b/configs/seeds/wizard/HealthCardHistory.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardHistory.json rename to configs/seeds/wizard/HealthCardHistory.json diff --git a/configs/seeds/host/wizard/HealthCardLabPhysical.json b/configs/seeds/wizard/HealthCardLabPhysical.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardLabPhysical.json rename to configs/seeds/wizard/HealthCardLabPhysical.json diff --git a/configs/seeds/host/wizard/HealthCardPatientProtocol.json b/configs/seeds/wizard/HealthCardPatientProtocol.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardPatientProtocol.json rename to configs/seeds/wizard/HealthCardPatientProtocol.json diff --git a/configs/seeds/host/wizard/HealthCardPeriodicExamination.json b/configs/seeds/wizard/HealthCardPeriodicExamination.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardPeriodicExamination.json rename to configs/seeds/wizard/HealthCardPeriodicExamination.json diff --git a/configs/seeds/host/wizard/HealthCardResult.json b/configs/seeds/wizard/HealthCardResult.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardResult.json rename to configs/seeds/wizard/HealthCardResult.json diff --git a/configs/seeds/host/wizard/HealthCardSickLeave.json b/configs/seeds/wizard/HealthCardSickLeave.json similarity index 100% rename from configs/seeds/host/wizard/HealthCardSickLeave.json rename to configs/seeds/wizard/HealthCardSickLeave.json diff --git a/configs/seeds/host/wizard/Hospital.json b/configs/seeds/wizard/Hospital.json similarity index 100% rename from configs/seeds/host/wizard/Hospital.json rename to configs/seeds/wizard/Hospital.json diff --git a/configs/seeds/host/wizard/HospitalDepartment.json b/configs/seeds/wizard/HospitalDepartment.json similarity index 100% rename from configs/seeds/host/wizard/HospitalDepartment.json rename to configs/seeds/wizard/HospitalDepartment.json diff --git a/configs/seeds/host/wizard/Interview.json b/configs/seeds/wizard/Interview.json similarity index 100% rename from configs/seeds/host/wizard/Interview.json rename to configs/seeds/wizard/Interview.json diff --git a/configs/seeds/host/wizard/InterviewScore.json b/configs/seeds/wizard/InterviewScore.json similarity index 100% rename from configs/seeds/host/wizard/InterviewScore.json rename to configs/seeds/wizard/InterviewScore.json diff --git a/configs/seeds/host/wizard/JobApplication.json b/configs/seeds/wizard/JobApplication.json similarity index 100% rename from configs/seeds/host/wizard/JobApplication.json rename to configs/seeds/wizard/JobApplication.json diff --git a/configs/seeds/host/wizard/JobOffer.json b/configs/seeds/wizard/JobOffer.json similarity index 100% rename from configs/seeds/host/wizard/JobOffer.json rename to configs/seeds/wizard/JobOffer.json diff --git a/configs/seeds/host/wizard/JobPosting.json b/configs/seeds/wizard/JobPosting.json similarity index 100% rename from configs/seeds/host/wizard/JobPosting.json rename to configs/seeds/wizard/JobPosting.json diff --git a/configs/seeds/host/wizard/LeaveBalance.json b/configs/seeds/wizard/LeaveBalance.json similarity index 100% rename from configs/seeds/host/wizard/LeaveBalance.json rename to configs/seeds/wizard/LeaveBalance.json diff --git a/configs/seeds/host/wizard/LeaveBalanceAdjustment.json b/configs/seeds/wizard/LeaveBalanceAdjustment.json similarity index 100% rename from configs/seeds/host/wizard/LeaveBalanceAdjustment.json rename to configs/seeds/wizard/LeaveBalanceAdjustment.json diff --git a/configs/seeds/host/wizard/LeaveBalanceYear.json b/configs/seeds/wizard/LeaveBalanceYear.json similarity index 100% rename from configs/seeds/host/wizard/LeaveBalanceYear.json rename to configs/seeds/wizard/LeaveBalanceYear.json diff --git a/configs/seeds/host/wizard/LeaveRequest.json b/configs/seeds/wizard/LeaveRequest.json similarity index 100% rename from configs/seeds/host/wizard/LeaveRequest.json rename to configs/seeds/wizard/LeaveRequest.json diff --git a/configs/seeds/host/wizard/LeaveType.json b/configs/seeds/wizard/LeaveType.json similarity index 100% rename from configs/seeds/host/wizard/LeaveType.json rename to configs/seeds/wizard/LeaveType.json diff --git a/configs/seeds/host/wizard/My360Feedback.json b/configs/seeds/wizard/My360Feedback.json similarity index 100% rename from configs/seeds/host/wizard/My360Feedback.json rename to configs/seeds/wizard/My360Feedback.json diff --git a/configs/seeds/host/wizard/My360FeedbackScore.json b/configs/seeds/wizard/My360FeedbackScore.json similarity index 100% rename from configs/seeds/host/wizard/My360FeedbackScore.json rename to configs/seeds/wizard/My360FeedbackScore.json diff --git a/configs/seeds/host/wizard/MyDevelopmentPlan.json b/configs/seeds/wizard/MyDevelopmentPlan.json similarity index 100% rename from configs/seeds/host/wizard/MyDevelopmentPlan.json rename to configs/seeds/wizard/MyDevelopmentPlan.json diff --git a/configs/seeds/host/wizard/MyLeaveBalance.json b/configs/seeds/wizard/MyLeaveBalance.json similarity index 100% rename from configs/seeds/host/wizard/MyLeaveBalance.json rename to configs/seeds/wizard/MyLeaveBalance.json diff --git a/configs/seeds/host/wizard/MyLeaveRequest.json b/configs/seeds/wizard/MyLeaveRequest.json similarity index 100% rename from configs/seeds/host/wizard/MyLeaveRequest.json rename to configs/seeds/wizard/MyLeaveRequest.json diff --git a/configs/seeds/host/wizard/MyPerformanceCompetency.json b/configs/seeds/wizard/MyPerformanceCompetency.json similarity index 100% rename from configs/seeds/host/wizard/MyPerformanceCompetency.json rename to configs/seeds/wizard/MyPerformanceCompetency.json diff --git a/configs/seeds/host/wizard/MyPerformanceGoal.json b/configs/seeds/wizard/MyPerformanceGoal.json similarity index 100% rename from configs/seeds/host/wizard/MyPerformanceGoal.json rename to configs/seeds/wizard/MyPerformanceGoal.json diff --git a/configs/seeds/host/wizard/MyPerformanceReview.json b/configs/seeds/wizard/MyPerformanceReview.json similarity index 100% rename from configs/seeds/host/wizard/MyPerformanceReview.json rename to configs/seeds/wizard/MyPerformanceReview.json diff --git a/configs/seeds/host/wizard/MyTimesheet.json b/configs/seeds/wizard/MyTimesheet.json similarity index 100% rename from configs/seeds/host/wizard/MyTimesheet.json rename to configs/seeds/wizard/MyTimesheet.json diff --git a/configs/seeds/host/wizard/MyTimesheetDay.json b/configs/seeds/wizard/MyTimesheetDay.json similarity index 100% rename from configs/seeds/host/wizard/MyTimesheetDay.json rename to configs/seeds/wizard/MyTimesheetDay.json diff --git a/configs/seeds/host/wizard/PerformanceCalibration.json b/configs/seeds/wizard/PerformanceCalibration.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceCalibration.json rename to configs/seeds/wizard/PerformanceCalibration.json diff --git a/configs/seeds/host/wizard/PerformanceCompetency.json b/configs/seeds/wizard/PerformanceCompetency.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceCompetency.json rename to configs/seeds/wizard/PerformanceCompetency.json diff --git a/configs/seeds/host/wizard/PerformanceFeedback.json b/configs/seeds/wizard/PerformanceFeedback.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceFeedback.json rename to configs/seeds/wizard/PerformanceFeedback.json diff --git a/configs/seeds/host/wizard/PerformanceFeedbackScore.json b/configs/seeds/wizard/PerformanceFeedbackScore.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceFeedbackScore.json rename to configs/seeds/wizard/PerformanceFeedbackScore.json diff --git a/configs/seeds/host/wizard/PerformanceGoal.json b/configs/seeds/wizard/PerformanceGoal.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceGoal.json rename to configs/seeds/wizard/PerformanceGoal.json diff --git a/configs/seeds/host/wizard/PerformancePeriod.json b/configs/seeds/wizard/PerformancePeriod.json similarity index 100% rename from configs/seeds/host/wizard/PerformancePeriod.json rename to configs/seeds/wizard/PerformancePeriod.json diff --git a/configs/seeds/host/wizard/PerformancePeriodSkill.json b/configs/seeds/wizard/PerformancePeriodSkill.json similarity index 100% rename from configs/seeds/host/wizard/PerformancePeriodSkill.json rename to configs/seeds/wizard/PerformancePeriodSkill.json diff --git a/configs/seeds/host/wizard/PerformanceRating.json b/configs/seeds/wizard/PerformanceRating.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceRating.json rename to configs/seeds/wizard/PerformanceRating.json diff --git a/configs/seeds/host/wizard/PerformanceReview.json b/configs/seeds/wizard/PerformanceReview.json similarity index 100% rename from configs/seeds/host/wizard/PerformanceReview.json rename to configs/seeds/wizard/PerformanceReview.json diff --git a/configs/seeds/host/wizard/RejectionReason.json b/configs/seeds/wizard/RejectionReason.json similarity index 100% rename from configs/seeds/host/wizard/RejectionReason.json rename to configs/seeds/wizard/RejectionReason.json diff --git a/configs/seeds/host/wizard/ReviewDevelopmentPlan.json b/configs/seeds/wizard/ReviewDevelopmentPlan.json similarity index 100% rename from configs/seeds/host/wizard/ReviewDevelopmentPlan.json rename to configs/seeds/wizard/ReviewDevelopmentPlan.json diff --git a/configs/seeds/host/wizard/Shift.json b/configs/seeds/wizard/Shift.json similarity index 100% rename from configs/seeds/host/wizard/Shift.json rename to configs/seeds/wizard/Shift.json diff --git a/configs/seeds/host/wizard/StaffRequest.json b/configs/seeds/wizard/StaffRequest.json similarity index 100% rename from configs/seeds/host/wizard/StaffRequest.json rename to configs/seeds/wizard/StaffRequest.json diff --git a/configs/seeds/host/wizard/TeamDevelopmentPlan.json b/configs/seeds/wizard/TeamDevelopmentPlan.json similarity index 100% rename from configs/seeds/host/wizard/TeamDevelopmentPlan.json rename to configs/seeds/wizard/TeamDevelopmentPlan.json diff --git a/configs/seeds/host/wizard/TeamLeaveRequest.json b/configs/seeds/wizard/TeamLeaveRequest.json similarity index 100% rename from configs/seeds/host/wizard/TeamLeaveRequest.json rename to configs/seeds/wizard/TeamLeaveRequest.json diff --git a/configs/seeds/host/wizard/TeamPerformanceCompetency.json b/configs/seeds/wizard/TeamPerformanceCompetency.json similarity index 100% rename from configs/seeds/host/wizard/TeamPerformanceCompetency.json rename to configs/seeds/wizard/TeamPerformanceCompetency.json diff --git a/configs/seeds/host/wizard/TeamPerformanceGoal.json b/configs/seeds/wizard/TeamPerformanceGoal.json similarity index 100% rename from configs/seeds/host/wizard/TeamPerformanceGoal.json rename to configs/seeds/wizard/TeamPerformanceGoal.json diff --git a/configs/seeds/host/wizard/TeamPerformanceReview.json b/configs/seeds/wizard/TeamPerformanceReview.json similarity index 100% rename from configs/seeds/host/wizard/TeamPerformanceReview.json rename to configs/seeds/wizard/TeamPerformanceReview.json diff --git a/configs/seeds/host/wizard/Timesheet.json b/configs/seeds/wizard/Timesheet.json similarity index 100% rename from configs/seeds/host/wizard/Timesheet.json rename to configs/seeds/wizard/Timesheet.json diff --git a/configs/seeds/host/wizard/TimesheetMonthly.json b/configs/seeds/wizard/TimesheetMonthly.json similarity index 100% rename from configs/seeds/host/wizard/TimesheetMonthly.json rename to configs/seeds/wizard/TimesheetMonthly.json diff --git a/configs/seeds/host/wizard/TimesheetPeriod.json b/configs/seeds/wizard/TimesheetPeriod.json similarity index 100% rename from configs/seeds/host/wizard/TimesheetPeriod.json rename to configs/seeds/wizard/TimesheetPeriod.json diff --git a/configs/seeds/host/wizard/Training.json b/configs/seeds/wizard/Training.json similarity index 100% rename from configs/seeds/host/wizard/Training.json rename to configs/seeds/wizard/Training.json diff --git a/configs/seeds/host/wizard/TrainingFile.json b/configs/seeds/wizard/TrainingFile.json similarity index 100% rename from configs/seeds/host/wizard/TrainingFile.json rename to configs/seeds/wizard/TrainingFile.json diff --git a/configs/seeds/host/wizard/TrainingParticipant.json b/configs/seeds/wizard/TrainingParticipant.json similarity index 100% rename from configs/seeds/host/wizard/TrainingParticipant.json rename to configs/seeds/wizard/TrainingParticipant.json diff --git a/configs/seeds/host/wizard/TrainingSubject.json b/configs/seeds/wizard/TrainingSubject.json similarity index 100% rename from configs/seeds/host/wizard/TrainingSubject.json rename to configs/seeds/wizard/TrainingSubject.json diff --git a/configs/seeds/host/wizard/TrainingType.json b/configs/seeds/wizard/TrainingType.json similarity index 100% rename from configs/seeds/host/wizard/TrainingType.json rename to configs/seeds/wizard/TrainingType.json diff --git a/configs/seeds/host/wizard/Vehicle.json b/configs/seeds/wizard/Vehicle.json similarity index 100% rename from configs/seeds/host/wizard/Vehicle.json rename to configs/seeds/wizard/Vehicle.json diff --git a/configs/seeds/host/wizard/VehicleDestination.json b/configs/seeds/wizard/VehicleDestination.json similarity index 100% rename from configs/seeds/host/wizard/VehicleDestination.json rename to configs/seeds/wizard/VehicleDestination.json diff --git a/configs/seeds/host/wizard/VehicleDocument.json b/configs/seeds/wizard/VehicleDocument.json similarity index 100% rename from configs/seeds/host/wizard/VehicleDocument.json rename to configs/seeds/wizard/VehicleDocument.json diff --git a/configs/seeds/host/wizard/VehicleExpense.json b/configs/seeds/wizard/VehicleExpense.json similarity index 100% rename from configs/seeds/host/wizard/VehicleExpense.json rename to configs/seeds/wizard/VehicleExpense.json diff --git a/configs/seeds/host/wizard/VehicleExpenseDefinition.json b/configs/seeds/wizard/VehicleExpenseDefinition.json similarity index 100% rename from configs/seeds/host/wizard/VehicleExpenseDefinition.json rename to configs/seeds/wizard/VehicleExpenseDefinition.json diff --git a/configs/seeds/host/wizard/VehicleExpenseLine.json b/configs/seeds/wizard/VehicleExpenseLine.json similarity index 100% rename from configs/seeds/host/wizard/VehicleExpenseLine.json rename to configs/seeds/wizard/VehicleExpenseLine.json diff --git a/configs/seeds/host/wizard/VehicleFile.json b/configs/seeds/wizard/VehicleFile.json similarity index 100% rename from configs/seeds/host/wizard/VehicleFile.json rename to configs/seeds/wizard/VehicleFile.json diff --git a/configs/seeds/host/wizard/VehicleMovement.json b/configs/seeds/wizard/VehicleMovement.json similarity index 100% rename from configs/seeds/host/wizard/VehicleMovement.json rename to configs/seeds/wizard/VehicleMovement.json diff --git a/configs/seeds/host/wizard/Visitor.json b/configs/seeds/wizard/Visitor.json similarity index 100% rename from configs/seeds/host/wizard/Visitor.json rename to configs/seeds/wizard/Visitor.json diff --git a/configs/seeds/host/wizard/WorkCalendar.json b/configs/seeds/wizard/WorkCalendar.json similarity index 100% rename from configs/seeds/host/wizard/WorkCalendar.json rename to configs/seeds/wizard/WorkCalendar.json diff --git a/ui/package-lock.json b/ui/package-lock.json index 3675d70a..8940ab0c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -22,6 +22,7 @@ "devextreme": "26.1.4", "devextreme-exceljs-fork": "^4.4.13", "devextreme-react": "26.1.4", + "dompurify": "3.4.13", "easy-peasy": "^6.1.1", "emoji-picker-react": "^4.19.1", "file-saver": "^2.0.5", diff --git a/ui/package.json b/ui/package.json index 12101153..15cf348b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -40,6 +40,7 @@ "devextreme": "26.1.4", "devextreme-exceljs-fork": "^4.4.13", "devextreme-react": "26.1.4", + "dompurify": "3.4.13", "easy-peasy": "^6.1.1", "emoji-picker-react": "^4.19.1", "file-saver": "^2.0.5", diff --git a/ui/src/assets/styles/vendors/_react-quill.css b/ui/src/assets/styles/vendors/_react-quill.css deleted file mode 100644 index 8823c036..00000000 --- a/ui/src/assets/styles/vendors/_react-quill.css +++ /dev/null @@ -1,107 +0,0 @@ -.rich-text-editor { - .ql-toolbar.ql-snow { - @apply border border-gray-300 dark:border-gray-600 rounded-tl-lg rounded-tr-lg; - } - - .ql-container.ql-snow { - @apply border border-gray-300 dark:border-gray-600 rounded-bl-lg rounded-br-lg; - } - - .ql-editor { - @apply min-h-[120px] p-6; - } - - .ql-snow.ql-toolbar button, - .ql-snow .ql-toolbar button { - } - - .ql-snow .ql-stroke { - @apply stroke-gray-600 dark:stroke-gray-100; - } - - .ql-snow .ql-picker { - @apply text-gray-600 dark:text-gray-100; - } - - .ql-snow.ql-toolbar button, - .ql-snow .ql-toolbar button { - @apply rounded; - } - - .ql-snow.ql-toolbar button:hover, - .ql-snow .ql-toolbar button:hover, - .ql-snow.ql-toolbar button:focus, - .ql-snow .ql-toolbar button:focus, - .ql-snow.ql-toolbar button.ql-active, - .ql-snow .ql-toolbar button.ql-active, - .ql-snow.ql-toolbar .ql-picker-label:hover, - .ql-snow .ql-toolbar .ql-picker-label:hover, - .ql-snow.ql-toolbar .ql-picker-label.ql-active, - .ql-snow .ql-toolbar .ql-picker-label.ql-active, - .ql-snow.ql-toolbar .ql-picker-item:hover, - .ql-snow .ql-toolbar .ql-picker-item:hover, - .ql-snow.ql-toolbar .ql-picker-item.ql-selected, - .ql-snow .ql-toolbar .ql-picker-item.ql-selected { - @apply bg-gray-100 dark:bg-gray-500; - } - - .ql-snow.ql-toolbar button:hover .ql-stroke, - .ql-snow .ql-toolbar button:hover .ql-stroke, - .ql-snow.ql-toolbar button:focus .ql-stroke, - .ql-snow .ql-toolbar button:focus .ql-stroke, - .ql-snow.ql-toolbar button.ql-active .ql-stroke, - .ql-snow .ql-toolbar button.ql-active .ql-stroke, - .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, - .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, - .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, - .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, - .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, - .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, - .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, - .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, - .ql-snow.ql-toolbar button:hover .ql-stroke-miter, - .ql-snow .ql-toolbar button:hover .ql-stroke-miter, - .ql-snow.ql-toolbar button:focus .ql-stroke-miter, - .ql-snow .ql-toolbar button:focus .ql-stroke-miter, - .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, - .ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, - .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, - .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, - .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, - .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, - .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, - .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, - .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, - .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter { - @apply stroke-gray-900 dark:stroke-gray-100; - } - - .ql-snow .ql-picker-options .ql-picker-item { - @apply px-2 rounded-lg; - } - - .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options { - @apply border-gray-300 dark:border-gray-600; - } - - .ql-snow .ql-picker-options { - @apply bg-white dark:bg-gray-700 rounded-lg; - } - - .ql-snow.ql-toolbar button:hover, - .ql-snow .ql-toolbar button:hover, - .ql-snow.ql-toolbar button:focus, - .ql-snow .ql-toolbar button:focus, - .ql-snow.ql-toolbar button.ql-active, - .ql-snow .ql-toolbar button.ql-active, - .ql-snow.ql-toolbar .ql-picker-label:hover, - .ql-snow .ql-toolbar .ql-picker-label:hover, - .ql-snow.ql-toolbar .ql-picker-label.ql-active, - .ql-snow .ql-toolbar .ql-picker-label.ql-active, - .ql-snow.ql-toolbar .ql-picker-item:hover, - .ql-snow .ql-toolbar .ql-picker-item:hover, - .ql-snow.ql-toolbar .ql-picker-item.ql-selected, - .ql-snow .ql-toolbar .ql-picker-item.ql-selected { - @apply text-gray-900 dark:text-gray-100; - } -} diff --git a/ui/src/components/layouts/Layouts.tsx b/ui/src/components/layouts/Layouts.tsx index 574919e3..c49fc7a9 100644 --- a/ui/src/components/layouts/Layouts.tsx +++ b/ui/src/components/layouts/Layouts.tsx @@ -16,7 +16,6 @@ import { useDynamicRoutes } from '@/routes/dynamicRoutesContext' import { useLocation } from 'react-router-dom' import { hasSubdomain } from '@/utils/subdomain' import { ROUTES_ENUM } from '@/routes/route.constant' -import ServiceUnavailable from '@/views/ServiceUnavailable' import SeoHead from '@/components/shared/SeoHead' import SkipToContent from '@/components/shared/SkipToContent' @@ -38,6 +37,8 @@ const layouts = { } const AuthLayout = lazy(() => import('./AuthLayout')) +// Yalnızca açılış başarısız olunca gösterilir; ana pakette taşınması gerekmez. +const ServiceUnavailable = lazy(() => import('@/views/ServiceUnavailable')) const PublicLayout = lazy(() => import('./PublicLayout')) class LayoutChunkErrorBoundary extends Component<{ children: ReactNode }, { failed: boolean }> { @@ -135,7 +136,11 @@ const Layout = () => { // burada, layout seçilmeden önce kesiyoruz. /setup dışarıda bırakılır ki yönetici // müdahale edebilsin. if (bootstrapFailed && currentPath !== ROUTES_ENUM.setup) { - return + return ( + + + + ) } if (loading) { diff --git a/ui/src/components/shared/AuthorityCheck.tsx b/ui/src/components/shared/AuthorityCheck.tsx deleted file mode 100644 index 08811d7b..00000000 --- a/ui/src/components/shared/AuthorityCheck.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import useAuthority from '@/utils/hooks/useAuthority' -import type { CommonProps } from '@/proxy/common' - -interface AuthorityCheckProps extends CommonProps { - userAuthority: string[] - authority: string[] -} - -const AuthorityCheck = (props: AuthorityCheckProps) => { - const { userAuthority = [], authority = [], children } = props - - const roleMatched = useAuthority(userAuthority, authority) - - return <>{roleMatched ? children : null} -} - -export default AuthorityCheck diff --git a/ui/src/components/shared/DbMigrateLogPanel.tsx b/ui/src/components/shared/DbMigrateLogPanel.tsx index 031c372d..b8d8a01b 100644 --- a/ui/src/components/shared/DbMigrateLogPanel.tsx +++ b/ui/src/components/shared/DbMigrateLogPanel.tsx @@ -4,6 +4,7 @@ import { createRoot } from 'react-dom/client' import { Button } from '@/components/ui' interface DbMigrateLogPanelProps { + title: string onClose: () => void } @@ -21,7 +22,7 @@ function levelColor(level: string): string { } } -function DbMigrateLogPanel({ onClose }: DbMigrateLogPanelProps) { +function DbMigrateLogPanel({ title, onClose }: DbMigrateLogPanelProps) { const [logs, setLogs] = useState([]) const [done, setDone] = useState(false) const bottomRef = useRef(null) @@ -48,7 +49,7 @@ function DbMigrateLogPanel({ onClose }: DbMigrateLogPanelProps) {
- DB Migration Logs + {title} {/* Panel her iki temada da koyu; buton varsayilan olarak acik temayi izledigi icin renkler burada sabitlenir (aksi halde hover'da beyaz @@ -88,7 +89,7 @@ function DbMigrateLogPanel({ onClose }: DbMigrateLogPanelProps) { let panelRoot: ReturnType | null = null let panelContainer: HTMLElement | null = null -export function openDbMigrateLogPanel() { +export function openDbMigrateLogPanel(title = 'DB Migration Logs') { if (panelContainer) return panelContainer = document.createElement('div') document.body.appendChild(panelContainer) @@ -101,7 +102,7 @@ export function openDbMigrateLogPanel() { panelContainer = null } - panelRoot.render() + panelRoot.render() } export function dispatchMigrateLog(entry: MigrateLogEntry) { diff --git a/ui/src/components/shared/EllipsisButton.tsx b/ui/src/components/shared/EllipsisButton.tsx deleted file mode 100644 index 75a6f0c4..00000000 --- a/ui/src/components/shared/EllipsisButton.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Button from '@/components/ui/Button' -import { FaEllipsisH } from 'react-icons/fa' -import type { ButtonProps } from '@/components/ui/Button' - -type EllipsisButtonProps = ButtonProps - -const EllipsisButton = (props: EllipsisButtonProps) => { - const { shape = 'circle', variant = 'plain', size = 'xs' } = props - - return ( - - - - - - - - - - - - {roleList.map((role) => ( - - - - - ))} - -
Role
-
{ - setActiveRoleName(role.name) - setPermissionDialogOpen(true) - }} - > - {translate('::App.Platform.Permission')} -
-
{role.name}
-
-
- - {activeRoleName && ( - setPermissionDialogOpen(false)} - > - )} - - ) -} - -export default Roles diff --git a/ui/src/views/admin/tenant-management/Tenants.tsx b/ui/src/views/admin/tenant-management/Tenants.tsx deleted file mode 100644 index 64ca0f75..00000000 --- a/ui/src/views/admin/tenant-management/Tenants.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Loading } from '@/components/shared' -import AdaptableCard from '@/components/shared/AdaptableCard' -import Container from '@/components/shared/Container' -import { Table } from '@/components/ui' -import { TenantDto } from '@/proxy/config/models' -import { useLocalization } from '@/utils/hooks/useLocalization' -import useThemeClass from '@/utils/hooks/useThemeClass' -import { isEmpty } from '@/utils/collection' -import { useEffect, useState } from 'react' -import PageTitle from '@/components/shared/PageTitle' -import TenantsConnectionString from './TenantsConnectionString' -import { getTenants } from '@/services/tenant.service' - -const { Tr, Th, Td, THead, TBody } = Table - -const Tenants = () => { - const { textTheme } = useThemeClass() - const [loading, setLoading] = useState(true) - const [tenantList, setTenantList] = useState([]) - const [activeTenant, setActiveTenant] = useState() - - const { translate } = useLocalization() - - const fetchData = async () => { - setLoading(true) - const response = await getTenants() - if (response.data?.items) { - setTenantList(response.data.items) - } - setLoading(false) - } - - useEffect(() => { - if (isEmpty(tenantList)) { - fetchData() - } - }, []) - - const dialogOpen = (tenant: TenantDto) => { - setActiveTenant(tenant) - } - - return ( - <> - - - - - - - - - - - - - {tenantList.map((tenant) => ( - - - - - ))} - -
Tenant
-
dialogOpen(tenant)} - > - {translate('::AbpTenantManagement.Tenants.ManageConnectionStrings')} -
-
{tenant.name}
-
-
-
- {activeTenant && ( - setActiveTenant(undefined)} - > - )} - - ) -} - -export default Tenants diff --git a/ui/src/views/admin/tenant-management/TenantsConnectionString.tsx b/ui/src/views/admin/tenant-management/TenantsConnectionString.tsx index 596fbd69..264dcc4d 100644 --- a/ui/src/views/admin/tenant-management/TenantsConnectionString.tsx +++ b/ui/src/views/admin/tenant-management/TenantsConnectionString.tsx @@ -11,13 +11,17 @@ import { } from '@/components/ui' import TabList from '@/components/ui/Tabs/TabList' import TabNav from '@/components/ui/Tabs/TabNav' -import { SeedTenantDataInput } from '@/proxy/admin/tenant/models' import { deleteTenantDefaultConnectionString, getTenantDefaultConnectionString, - postSeedTenantData, putTenantDefaultConnectionString, } from '@/services/tenant.service' +import { SeedTenantInput, streamTenantSeed } from '@/services/setup.service' +import { + dispatchMigrateDone, + dispatchMigrateLog, + openDbMigrateLogPanel, +} from '@/components/shared/DbMigrateLogPanel' import { useLocalization } from '@/utils/hooks/useLocalization' import { Field, FieldProps, Form, Formik, FormikHelpers } from 'formik' import { useCallback, useEffect, useState, type MouseEvent } from 'react' @@ -42,7 +46,6 @@ const schemaCs = Yup.object().shape({ }) const schemaSeed = Yup.object().shape({ - id: Yup.string().required(), adminEmail: Yup.string().required(), adminPassword: Yup.string().required(), }) @@ -161,45 +164,50 @@ function TenantConnectionString({ ) const handleSubmitSeed = async ( - values: SeedTenantDataInput, - { setSubmitting }: FormikHelpers, + values: SeedTenantInput, + { setSubmitting }: FormikHelpers, ) => { if (!id) { return } setSubmitting(true) + openDbMigrateLogPanel(`${name} — ${translate('::AbpTenantManagement.Tenants.DatabaseSeedLogs')}`) - try { - await postSeedTenantData(values) - toast.push( - - {translate('::AbpTenantManagement.Tenants.DatabaseSeeded')} - , - { - placement: 'bottom-end', - }, - ) + let failed = false + let lastLoggedError: string | undefined - onDialogClose() - } catch (error) { - const errorMessage = - (error as any)?.response?.data?.error?.message ?? - (error as any)?.response?.data?.message ?? - (error as any)?.message ?? - translate('::App.Platform.IslemBasarisiz') + await streamTenantSeed(id, values, { + onLog: (entry) => { + if (entry.level === 'error') lastLoggedError = entry.message + dispatchMigrateLog(entry) + }, + onError: (error) => { + failed = true + // Akış öncesi ret (yetki, doğrulama, bağlantı) panelde de görünsün; akıştaki hata zaten loglandı. + if (error.message !== lastLoggedError) { + dispatchMigrateLog({ level: 'error', message: error.message }) + } + toast.push( + + {error.message || translate('::App.Platform.IslemBasarisiz')} + , + { placement: 'bottom-end' }, + ) + }, + onFinished: dispatchMigrateDone, + }) - toast.push( - - {errorMessage} - , - { - placement: 'bottom-end', - }, - ) - } finally { - setSubmitting(false) - } + setSubmitting(false) + if (failed) return + + toast.push( + + {translate('::AbpTenantManagement.Tenants.DatabaseSeeded')} + , + { placement: 'bottom-end' }, + ) + onDialogClose() } if (!connectionString) { @@ -337,7 +345,7 @@ function TenantConnectionString({ )} {currentTab === 'seed' && ( diff --git a/ui/src/views/admin/user-management/Users.tsx b/ui/src/views/admin/user-management/Users.tsx deleted file mode 100644 index b051f3a6..00000000 --- a/ui/src/views/admin/user-management/Users.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import AdaptableCard from '@/components/shared/AdaptableCard' -import Container from '@/components/shared/Container' -import { Table } from '@/components/ui' -import { getUsers } from '@/services/identity.service' -import { useLocalization } from '@/utils/hooks/useLocalization' -import useThemeClass from '@/utils/hooks/useThemeClass' -import { isEmpty } from '@/utils/collection' -import { useEffect, useState } from 'react' -import PageTitle from '@/components/shared/PageTitle' -import UsersPermission from './UsersPermission' -import { IdentityUserDto } from '@/proxy/admin/models' -import { ROUTES_ENUM } from '@/routes/route.constant' -import ActionLink from '@/components/shared/ActionLink' - -const { Tr, Th, Td, THead, TBody } = Table - -const Users = () => { - const { textTheme } = useThemeClass() - const [userList, setUserList] = useState([]) - const [activeUser, setActiveUser] = useState() - const [permissionDialogOpen, setPermissionDialogOpen] = useState(false) - - const { translate } = useLocalization() - - const fetchData = async () => { - const response = await getUsers() - if (response.data?.items) { - setUserList(response.data.items) - } - } - - useEffect(() => { - if (isEmpty(userList)) { - fetchData() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - const openPermissionDialog = (user: IdentityUserDto) => { - setActiveUser(user) - setPermissionDialogOpen(true) - } - - return ( - <> - - - - - - - - - - - - - - - {userList.map((user) => ( - - - - - - - - ))} - -
{translate('::App.Platform.FullName')}E-PostaTelefon{translate('::App.Listform.ListformField.Status')}
-
openPermissionDialog(user)} - > - {translate('::App.Platform.Permission')} -
-
- - {user.name} {user.surname} - - {user.email}{user.phoneNumber}{user.isActive ? 'Aktif' : 'Pasif'}
-
-
- {activeUser?.id && ( - setPermissionDialogOpen(false)} - name={ - `${activeUser.name ?? ''} ${activeUser.surname ?? ''}`.trim() || - activeUser.email || - activeUser.id - } - id={activeUser.id} - > - )} - - ) -} - -export default Users diff --git a/ui/src/views/public/NotFound.tsx b/ui/src/views/public/NotFound.tsx deleted file mode 100644 index 8475f933..00000000 --- a/ui/src/views/public/NotFound.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import { Link } from 'react-router-dom' -import { FaFrown } from 'react-icons/fa' -import { ROUTES_ENUM } from '@/routes/route.constant' -import { useLocalization } from '@/utils/hooks/useLocalization' - -const NotFound: React.FC = () => { - const { translate } = useLocalization() - - return ( - - ) -} - -export default NotFound diff --git a/ui/src/views/shared/DialogContext/DialogShowComponent.tsx b/ui/src/views/shared/DialogContext/DialogShowComponent.tsx index 2a9b5fec..d0ec4194 100644 --- a/ui/src/views/shared/DialogContext/DialogShowComponent.tsx +++ b/ui/src/views/shared/DialogContext/DialogShowComponent.tsx @@ -1,13 +1,23 @@ -import TenantsConnectionString from '@/views/admin/tenant-management/TenantsConnectionString' -import CreateTenantFromOrderDialog from '@/views/admin/tenant-management/CreateTenantFromOrderDialog' import { useDialogContext } from './DialogProvider' -import CreateNotification from '@/views/admin/notification/CreateNotification' -import AuditLogDetail from '@/views/admin/auditLog/AuditLogDetail' -import RolesPermission from '@/views/admin/role-management/RolesPermission' -import UsersPermission from '@/views/admin/user-management/UsersPermission' -import BranchSeed from '@/views/branch/BranchSeed' -import DynamicServiceEditor from '@/views/developerKit/DynamicServiceEditor' -import { ReactElement } from 'react' +import { ComponentType, lazy, ReactElement, Suspense } from 'react' + +// Diyaloglar her layout'ta (public dahil) çizilen Views'tan açılır; statik import edilince monaco, +// formik gibi bağımlılıkları da ilk yüklemeye giriyordu. Kod yalnızca diyalog açılınca indirilir. +// Props diyalog yapılandırmasından (config.props) tipsiz gelir; her diyalog kendi sözleşmesini tanımlar. +const dialogs: Record> = { + RolesPermission: lazy(() => import('@/views/admin/role-management/RolesPermission')), + UsersPermission: lazy(() => import('@/views/admin/user-management/UsersPermission')), + TenantsConnectionString: lazy( + () => import('@/views/admin/tenant-management/TenantsConnectionString'), + ), + CreateTenantFromOrderDialog: lazy( + () => import('@/views/admin/tenant-management/CreateTenantFromOrderDialog'), + ), + CreateNotification: lazy(() => import('@/views/admin/notification/CreateNotification')), + AuditLogDetail: lazy(() => import('@/views/admin/auditLog/AuditLogDetail')), + DynamicServiceEditor: lazy(() => import('@/views/developerKit/DynamicServiceEditor')), + BranchSeed: lazy(() => import('@/views/branch/BranchSeed')), +} const DialogShowComponent = (): ReactElement => { const dialogContext: any = useDialogContext() @@ -19,78 +29,16 @@ const DialogShowComponent = (): ReactElement => { dialogContext.setConfig({}) } - const getComponent = (param: string) => { - switch (param) { - case 'RolesPermission': - return ( - - ) - case 'UsersPermission': - return ( - - ) - case 'TenantsConnectionString': - return ( - - ) - case 'CreateTenantFromOrderDialog': - return ( - - ) - case 'CreateNotification': - return ( - - ) - case 'AuditLogDetail': - return ( - - ) - case 'DynamicServiceEditor': - return ( - - ) - case 'BranchSeed': - return ( - - ) - default: - return <> - } + const Dialog = dialogs[dialogContext.config?.component] + if (!Dialog) { + return <> } - return getComponent(dialogContext.config?.component) + return ( + + + + ) } export default DialogShowComponent diff --git a/ui/src/views/version/swRegistration.ts b/ui/src/views/version/swRegistration.ts index f486615d..8936b72b 100644 --- a/ui/src/views/version/swRegistration.ts +++ b/ui/src/views/version/swRegistration.ts @@ -672,6 +672,8 @@ function handleWorkerMessage(event: MessageEvent) { case 'SW_PRECACHE_DONE': // Geçişsiz kurulum bittiğinde ısıtma yeniden söz sahibi olabilir. if (!sessionTransitions) { + // İlk kurulum sırasında gelen ısıtma isteği beklemişti; şimdi gönderilir. + if (installSessionActive && data.phase === 'install') scheduleWarmup() installSessionActive = false activePhase = undefined } @@ -802,30 +804,52 @@ function requestPrecacheState(reg: ServiceWorkerRegistration) { } /** - * Eksik bileşenlerin tamamlanmasını ister. + * Isıtılacak view modülleri (`views/...`); oturum açılmadıysa boştur. * - * Isıtmayı yalnızca kuruluma bağlamak yetmiyor: süre bütçesi dolduğunda, sekme - * kapandığında ya da bir dosya hata verdiğinde kalan chunk'lar bir sonraki - * deploy'a kadar indirilmiyor ve kullanıcı ekran açtıkça chunk bekliyordu. - * Worker eksik bir şey yoksa hiçbir mesaj yollamaz, pencere de açılmaz. + * Bileşen ısıtması kurulumun parçası değildir: kurulum her ziyaretçide çalışır ve + * bütün chunk'ları oraya bağlamak public sayfa ile giriş ekranında da ~20 MB + * indirmek demekti. Liste store'dan gelir (`setWarmupRoutes`). */ -function requestWarmup(reg: ServiceWorkerRegistration) { - // Kurulum sürüyor ya da geçişi bekliyorsa ikinci bir oturum başlatma: iki - // oturum hem bant genişliği için yarışır hem de pencerede iç içe geçer. +let warmupRoutes: string[] = [] + +/** + * Yetkili rotaların eksik bileşenlerinin tamamlanmasını ister. + * + * Isıtma her açılışta istenir: süre bütçesi dolduğunda, sekme kapandığında ya da + * bir dosya hata verdiğinde kalan chunk'lar sonraki açılışta tamamlanır. Worker + * eksik bir şey yoksa hiçbir mesaj yollamaz, pencere de açılmaz. + */ +async function requestWarmup() { + // Kurulum sürüyorsa ikinci bir oturum başlatma: iki oturum hem bant genişliği + // için yarışır hem de pencerede iç içe geçer. Kurulum bitince yeniden istenir. + if (!isServiceWorkerEnabled || !('serviceWorker' in navigator)) return + if (warmupRoutes.length === 0 || installSessionActive) return + // İlk kurulumda worker henüz aktif değildir; `ready` aktive olunca çözülür. + const reg = await navigator.serviceWorker.ready if (reg.installing || reg.waiting || installSessionActive) return - const worker = reg.active ?? navigator.serviceWorker.controller - worker?.postMessage({ type: 'ENSURE_WARMUP' }) + reg.active?.postMessage({ type: 'ENSURE_WARMUP', routes: warmupRoutes }) } /** İlk boyama ve açılış istekleri bitsin diye ısıtma isteği boşta gönderilir. */ -function scheduleWarmup(reg: ServiceWorkerRegistration) { +function scheduleWarmup() { const idle = (window as unknown as { requestIdleCallback?: (cb: () => void, o?: object) => void }) .requestIdleCallback - const send = () => requestWarmup(reg) + const send = () => void requestWarmup() if (idle) idle.call(window, send, { timeout: 5_000 }) else window.setTimeout(send, 3_000) } +/** + * Oturum açmış kullanıcının yetkili olduğu fiziksel rotaların view modülleri + * (`@/views/...` → `views/...`). Boş liste ısıtmayı kapatır. + */ +export const setWarmupRoutes = (routes: string[]) => { + const next = [...new Set(routes)].sort() + if (next.join('|') === warmupRoutes.join('|')) return + warmupRoutes = next + if (next.length > 0) scheduleWarmup() +} + function trackInstallingWorker(reg: ServiceWorkerRegistration) { const worker = reg.installing if (!worker || watchedWorkers.has(worker)) return @@ -972,7 +996,7 @@ export const registerServiceWorker = () => { // Kurulumu tamamlanmış, aktif olandan farklı bir worker bekliyorsa // doğrudan devreye al. if (reg.waiting && navigator.serviceWorker.controller) activateWaitingWorker() - else scheduleWarmup(reg) + else scheduleWarmup() } settle() }, diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts index ba98daed..f935ab39 100644 --- a/ui/tailwind.config.ts +++ b/ui/tailwind.config.ts @@ -17,8 +17,8 @@ export default { // kokunu gormedigin de — docker, ayri checkout — bu glob bos doner, safelist ise `ui/` icinde // oldugu icin her ortamda taranir. // Bilesenler satir basina dosya tutan klasorde durur; tek dosya bicimi eski kurulumlarda kalir. - '../configs/seeds/*/data/App.DeveloperKit.CustomComponents/*.json', - '../configs/seeds/*/data/App.DeveloperKit.CustomComponents.json', + '../configs/seeds/data/App.DeveloperKit.CustomComponents/*.json', + '../configs/seeds/data/App.DeveloperKit.CustomComponents.json', ], safelist: [ { diff --git a/ui/tsconfig.node.tsbuildinfo b/ui/tsconfig.node.tsbuildinfo index efced989..05f97bb2 100644 --- a/ui/tsconfig.node.tsbuildinfo +++ b/ui/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["./node_modules/typescript/lib/lib.d.ts","./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/round-robin-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/buffer/index.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/rolldown/dist/shared/logging-xuho4may.d.mts","./node_modules/@oxc-project/types/types.d.ts","./node_modules/rolldown/dist/shared/binding-cvtkjvyl.d.mts","./node_modules/@rolldown/pluginutils/dist/filter/index.d.mts","./node_modules/@rolldown/pluginutils/dist/index.d.mts","./node_modules/rolldown/dist/shared/define-config-dvsxpnau.d.mts","./node_modules/rolldown/dist/index.d.mts","./node_modules/rolldown/dist/parse-ast-index.d.mts","./node_modules/vite/types/internal/rolluptypecompat.d.ts","./node_modules/rolldown/dist/shared/constructors-xaibwpwj.d.mts","./node_modules/rolldown/dist/plugins-index.d.mts","./node_modules/rolldown/dist/shared/transform-wmu_ci8e.d.mts","./node_modules/rolldown/dist/utils-index.d.mts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/module-runner.d.ts","./node_modules/vite/types/internal/esbuildoptions.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts","./node_modules/@jridgewell/trace-mapping/types/types.d.mts","./node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts","./node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts","./node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts","./node_modules/@jridgewell/gen-mapping/types/types.d.mts","./node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts","./node_modules/@jridgewell/source-map/types/source-map.d.mts","./node_modules/terser/tools/terser.d.ts","./node_modules/vite/types/internal/terseroptions.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.d.ts","./node_modules/vite/types/internal/lightningcssoptions.d.ts","./node_modules/vite/types/internal/csspreprocessoroptions.d.ts","./node_modules/rolldown/dist/filter-index.d.mts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@vitejs/plugin-react/types/optionaltypes.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/rollup/dist/rollup.d.ts","./node_modules/workbox-build/build/lib/copy-workbox-libraries.d.ts","./node_modules/type-fest/source/primitive.d.ts","./node_modules/type-fest/source/find-global-type.d.ts","./node_modules/type-fest/source/typed-array.d.ts","./node_modules/type-fest/source/basic.d.ts","./node_modules/type-fest/source/json-value.d.ts","./node_modules/type-fest/source/characters.d.ts","./node_modules/type-fest/source/union-to-intersection.d.ts","./node_modules/type-fest/source/keys-of-union.d.ts","./node_modules/type-fest/source/distributed-omit.d.ts","./node_modules/type-fest/source/distributed-pick.d.ts","./node_modules/type-fest/source/empty-object.d.ts","./node_modules/type-fest/source/if-empty-object.d.ts","./node_modules/type-fest/source/is-any.d.ts","./node_modules/type-fest/source/is-optional-key-of.d.ts","./node_modules/type-fest/source/optional-keys-of.d.ts","./node_modules/type-fest/source/required-keys-of.d.ts","./node_modules/type-fest/source/has-required-keys.d.ts","./node_modules/type-fest/source/is-never.d.ts","./node_modules/type-fest/source/if.d.ts","./node_modules/type-fest/source/unknown-array.d.ts","./node_modules/type-fest/source/internal/type.d.ts","./node_modules/type-fest/source/internal/array.d.ts","./node_modules/type-fest/source/internal/characters.d.ts","./node_modules/type-fest/source/simplify.d.ts","./node_modules/type-fest/source/is-equal.d.ts","./node_modules/type-fest/source/omit-index-signature.d.ts","./node_modules/type-fest/source/pick-index-signature.d.ts","./node_modules/type-fest/source/merge.d.ts","./node_modules/type-fest/source/is-float.d.ts","./node_modules/type-fest/source/is-integer.d.ts","./node_modules/type-fest/source/numeric.d.ts","./node_modules/type-fest/source/string-to-number.d.ts","./node_modules/type-fest/source/tuple-of.d.ts","./node_modules/type-fest/source/trim.d.ts","./node_modules/type-fest/source/some-extend.d.ts","./node_modules/type-fest/source/or-all.d.ts","./node_modules/type-fest/source/or.d.ts","./node_modules/type-fest/source/string-to-array.d.ts","./node_modules/type-fest/source/string-length.d.ts","./node_modules/type-fest/source/internal/string.d.ts","./node_modules/type-fest/source/internal/object.d.ts","./node_modules/type-fest/source/is-string-literal.d.ts","./node_modules/type-fest/source/is-numeric-literal.d.ts","./node_modules/type-fest/source/is-boolean-literal.d.ts","./node_modules/type-fest/source/is-symbol-literal.d.ts","./node_modules/type-fest/source/is-literal.d.ts","./node_modules/type-fest/source/is-null.d.ts","./node_modules/type-fest/source/is-unknown.d.ts","./node_modules/type-fest/source/internal/keys.d.ts","./node_modules/type-fest/source/internal/numeric.d.ts","./node_modules/type-fest/source/all-extend.d.ts","./node_modules/type-fest/source/and-all.d.ts","./node_modules/type-fest/source/and.d.ts","./node_modules/type-fest/source/absolute.d.ts","./node_modules/type-fest/source/greater-than.d.ts","./node_modules/type-fest/source/greater-than-or-equal.d.ts","./node_modules/type-fest/source/less-than.d.ts","./node_modules/type-fest/source/internal/tuple.d.ts","./node_modules/type-fest/source/internal/enforce-optional.d.ts","./node_modules/type-fest/source/internal/index.d.ts","./node_modules/type-fest/source/except.d.ts","./node_modules/type-fest/source/require-at-least-one.d.ts","./node_modules/type-fest/source/non-empty-object.d.ts","./node_modules/type-fest/source/non-empty-string.d.ts","./node_modules/type-fest/source/unknown-record.d.ts","./node_modules/type-fest/source/unknown-set.d.ts","./node_modules/type-fest/source/unknown-map.d.ts","./node_modules/type-fest/source/tagged-union.d.ts","./node_modules/type-fest/source/writable.d.ts","./node_modules/type-fest/source/writable-deep.d.ts","./node_modules/type-fest/source/object-merge.d.ts","./node_modules/type-fest/source/conditional-simplify-deep.d.ts","./node_modules/type-fest/source/non-empty-tuple.d.ts","./node_modules/type-fest/source/array-tail.d.ts","./node_modules/type-fest/source/simplify-deep.d.ts","./node_modules/type-fest/source/merge-deep.d.ts","./node_modules/type-fest/source/merge-exclusive.d.ts","./node_modules/type-fest/source/require-exactly-one.d.ts","./node_modules/type-fest/source/require-all-or-none.d.ts","./node_modules/type-fest/source/require-one-or-none.d.ts","./node_modules/type-fest/source/is-union.d.ts","./node_modules/type-fest/source/single-key-object.d.ts","./node_modules/type-fest/source/partial-deep.d.ts","./node_modules/type-fest/source/unwrap-partial.d.ts","./node_modules/type-fest/source/unwrap-required.d.ts","./node_modules/type-fest/source/required-deep.d.ts","./node_modules/type-fest/source/subtract.d.ts","./node_modules/type-fest/source/sum.d.ts","./node_modules/type-fest/source/paths.d.ts","./node_modules/type-fest/source/pick-deep.d.ts","./node_modules/type-fest/source/array-splice.d.ts","./node_modules/type-fest/source/literal-union.d.ts","./node_modules/type-fest/source/exclude-exactly.d.ts","./node_modules/type-fest/source/union-member.d.ts","./node_modules/type-fest/source/union-to-tuple.d.ts","./node_modules/type-fest/source/omit-deep.d.ts","./node_modules/type-fest/source/partial-on-undefined-deep.d.ts","./node_modules/type-fest/source/undefined-on-partial-deep.d.ts","./node_modules/type-fest/source/readonly-deep.d.ts","./node_modules/type-fest/source/promisable.d.ts","./node_modules/type-fest/source/arrayable.d.ts","./node_modules/type-fest/source/optional.d.ts","./node_modules/tagged-tag/index.d.ts","./node_modules/type-fest/source/tagged.d.ts","./node_modules/type-fest/source/invariant-of.d.ts","./node_modules/type-fest/source/set-optional.d.ts","./node_modules/type-fest/source/set-readonly.d.ts","./node_modules/type-fest/source/set-required.d.ts","./node_modules/type-fest/source/set-required-deep.d.ts","./node_modules/type-fest/source/set-non-nullable.d.ts","./node_modules/type-fest/source/non-nullable-deep.d.ts","./node_modules/type-fest/source/set-non-nullable-deep.d.ts","./node_modules/type-fest/source/value-of.d.ts","./node_modules/type-fest/source/async-return-type.d.ts","./node_modules/type-fest/source/extends-strict.d.ts","./node_modules/type-fest/source/is-tuple.d.ts","./node_modules/type-fest/source/tuple-to-object.d.ts","./node_modules/type-fest/source/conditional-keys.d.ts","./node_modules/type-fest/source/conditional-except.d.ts","./node_modules/type-fest/source/conditional-pick.d.ts","./node_modules/type-fest/source/conditional-pick-deep.d.ts","./node_modules/type-fest/source/stringified.d.ts","./node_modules/type-fest/source/join.d.ts","./node_modules/type-fest/source/less-than-or-equal.d.ts","./node_modules/type-fest/source/array-slice.d.ts","./node_modules/type-fest/source/string-slice.d.ts","./node_modules/type-fest/source/fixed-length-array.d.ts","./node_modules/type-fest/source/multidimensional-array.d.ts","./node_modules/type-fest/source/multidimensional-readonly-array.d.ts","./node_modules/type-fest/source/iterable-element.d.ts","./node_modules/type-fest/source/entry.d.ts","./node_modules/type-fest/source/entries.d.ts","./node_modules/type-fest/source/set-return-type.d.ts","./node_modules/type-fest/source/set-parameter-type.d.ts","./node_modules/type-fest/source/asyncify.d.ts","./node_modules/type-fest/source/jsonify.d.ts","./node_modules/type-fest/source/jsonifiable.d.ts","./node_modules/type-fest/source/structured-cloneable.d.ts","./node_modules/type-fest/source/schema.d.ts","./node_modules/type-fest/source/literal-to-primitive.d.ts","./node_modules/type-fest/source/literal-to-primitive-deep.d.ts","./node_modules/type-fest/source/union-max.d.ts","./node_modules/type-fest/source/key-as-string.d.ts","./node_modules/type-fest/source/array-element.d.ts","./node_modules/type-fest/source/exact.d.ts","./node_modules/type-fest/source/readonly-tuple.d.ts","./node_modules/type-fest/source/override-properties.d.ts","./node_modules/type-fest/source/is-readonly-key-of.d.ts","./node_modules/type-fest/source/readonly-keys-of.d.ts","./node_modules/type-fest/source/rename-keys.d.ts","./node_modules/type-fest/source/has-optional-keys.d.ts","./node_modules/type-fest/source/is-required-key-of.d.ts","./node_modules/type-fest/source/has-readonly-keys.d.ts","./node_modules/type-fest/source/writable-keys-of.d.ts","./node_modules/type-fest/source/is-writable-key-of.d.ts","./node_modules/type-fest/source/has-writable-keys.d.ts","./node_modules/type-fest/source/spread.d.ts","./node_modules/type-fest/source/split-on-rest-element.d.ts","./node_modules/type-fest/source/extract-rest-element.d.ts","./node_modules/type-fest/source/exclude-rest-element.d.ts","./node_modules/type-fest/source/tuple-to-union.d.ts","./node_modules/type-fest/source/int-range.d.ts","./node_modules/type-fest/source/int-closed-range.d.ts","./node_modules/type-fest/source/if-any.d.ts","./node_modules/type-fest/source/if-never.d.ts","./node_modules/type-fest/source/if-unknown.d.ts","./node_modules/type-fest/source/array-indices.d.ts","./node_modules/type-fest/source/array-values.d.ts","./node_modules/type-fest/source/array-length.d.ts","./node_modules/type-fest/source/set-field-type.d.ts","./node_modules/type-fest/source/shared-union-fields.d.ts","./node_modules/type-fest/source/all-union-fields.d.ts","./node_modules/type-fest/source/shared-union-fields-deep.d.ts","./node_modules/type-fest/source/if-null.d.ts","./node_modules/type-fest/source/is-undefined.d.ts","./node_modules/type-fest/source/xor.d.ts","./node_modules/type-fest/source/is-lowercase.d.ts","./node_modules/type-fest/source/is-uppercase.d.ts","./node_modules/type-fest/source/is-optional.d.ts","./node_modules/type-fest/source/is-nullable.d.ts","./node_modules/type-fest/source/exclusify-union.d.ts","./node_modules/type-fest/source/array-reverse.d.ts","./node_modules/type-fest/source/union-length.d.ts","./node_modules/type-fest/source/words.d.ts","./node_modules/type-fest/source/camel-case.d.ts","./node_modules/type-fest/source/camel-cased-properties.d.ts","./node_modules/type-fest/source/camel-cased-properties-deep.d.ts","./node_modules/type-fest/source/delimiter-case.d.ts","./node_modules/type-fest/source/kebab-case.d.ts","./node_modules/type-fest/source/delimiter-cased-properties.d.ts","./node_modules/type-fest/source/kebab-cased-properties.d.ts","./node_modules/type-fest/source/delimiter-cased-properties-deep.d.ts","./node_modules/type-fest/source/kebab-cased-properties-deep.d.ts","./node_modules/type-fest/source/pascal-case.d.ts","./node_modules/type-fest/source/pascal-cased-properties.d.ts","./node_modules/type-fest/source/pascal-cased-properties-deep.d.ts","./node_modules/type-fest/source/snake-case.d.ts","./node_modules/type-fest/source/snake-cased-properties.d.ts","./node_modules/type-fest/source/snake-cased-properties-deep.d.ts","./node_modules/type-fest/source/screaming-snake-case.d.ts","./node_modules/type-fest/source/split.d.ts","./node_modules/type-fest/source/replace.d.ts","./node_modules/type-fest/source/string-repeat.d.ts","./node_modules/type-fest/source/includes.d.ts","./node_modules/type-fest/source/get.d.ts","./node_modules/type-fest/source/last-array-element.d.ts","./node_modules/type-fest/source/conditional-simplify.d.ts","./node_modules/type-fest/source/remove-prefix.d.ts","./node_modules/type-fest/source/remove-suffix.d.ts","./node_modules/type-fest/source/global-this.d.ts","./node_modules/type-fest/source/package-json.d.ts","./node_modules/type-fest/source/tsconfig-json.d.ts","./node_modules/type-fest/source/extract-strict.d.ts","./node_modules/type-fest/source/exclude-strict.d.ts","./node_modules/type-fest/source/extract-exactly.d.ts","./node_modules/type-fest/index.d.ts","./node_modules/workbox-core/_version.d.ts","./node_modules/workbox-core/types.d.ts","./node_modules/workbox-broadcast-update/_version.d.ts","./node_modules/workbox-broadcast-update/broadcastcacheupdate.d.ts","./node_modules/workbox-google-analytics/_version.d.ts","./node_modules/workbox-google-analytics/initialize.d.ts","./node_modules/workbox-routing/_version.d.ts","./node_modules/workbox-routing/utils/constants.d.ts","./node_modules/workbox-background-sync/_version.d.ts","./node_modules/workbox-background-sync/queue.d.ts","./node_modules/workbox-cacheable-response/_version.d.ts","./node_modules/workbox-cacheable-response/cacheableresponse.d.ts","./node_modules/workbox-expiration/_version.d.ts","./node_modules/workbox-expiration/expirationplugin.d.ts","./node_modules/workbox-build/build/types.d.ts","./node_modules/workbox-build/build/lib/cdn-utils.d.ts","./node_modules/workbox-build/build/generate-sw.d.ts","./node_modules/workbox-build/build/get-manifest.d.ts","./node_modules/workbox-build/build/inject-manifest.d.ts","./node_modules/workbox-build/build/index.d.ts","./node_modules/vite-plugin-pwa/dist/index.d.ts","./vite.config.ts","./node_modules/@types/emscripten/index.d.ts","./node_modules/@types/file-saver/index.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/@types/hoist-non-react-statics/index.d.ts","./node_modules/@types/jquery/jquerystatic.d.ts","./node_modules/@types/jquery/jquery.d.ts","./node_modules/@types/jquery/misc.d.ts","./node_modules/@types/jquery/legacy.d.ts","./node_modules/@types/sizzle/index.d.ts","./node_modules/@types/jquery/index.d.ts","./node_modules/@types/json-schema/index.d.ts","./node_modules/@types/json5/index.d.ts","./node_modules/@types/pako/index.d.ts","./node_modules/@types/parse-json/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/raf/index.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-modal/index.d.ts","./node_modules/@types/react-transition-group/config.d.ts","./node_modules/@types/react-transition-group/transition.d.ts","./node_modules/@types/react-transition-group/csstransition.d.ts","./node_modules/@types/react-transition-group/switchtransition.d.ts","./node_modules/@types/react-transition-group/transitiongroup.d.ts","./node_modules/@types/react-transition-group/index.d.ts","./node_modules/@types/resolve/index.d.ts","./node_modules/@types/trusted-types/lib/index.d.ts","./node_modules/@types/trusted-types/index.d.ts","./node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@rolldown/plugin-babel/dist/index.d.mts","./node_modules/@types/babel__core/index.d.ts","./node_modules/@types/babel__generator/index.d.ts","./node_modules/@types/babel__standalone/index.d.ts","./node_modules/@types/babel__template/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/lodash/common/array.d.ts","./node_modules/@types/lodash/common/collection.d.ts","./node_modules/@types/lodash/common/common.d.ts","./node_modules/@types/lodash/common/date.d.ts","./node_modules/@types/lodash/common/function.d.ts","./node_modules/@types/lodash/common/lang.d.ts","./node_modules/@types/lodash/common/math.d.ts","./node_modules/@types/lodash/common/number.d.ts","./node_modules/@types/lodash/common/object.d.ts","./node_modules/@types/lodash/common/seq.d.ts","./node_modules/@types/lodash/common/string.d.ts","./node_modules/@types/lodash/common/util.d.ts","./node_modules/@types/lodash/index.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/react-helmet/index.d.ts","./node_modules/@types/react-highlight-words/index.d.ts","./node_modules/esbuild/lib/main.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/filereader.d.ts"],"fileIdsList":[[54,109,126,127,184,186],[54,109,126,127],[54,109,126,127,185],[54,109,126,127,184,187],[54,109,126,127,182,184],[54,109,126,127,181,182,183],[54,109,126,127,181,184],[54,109,126,127,167],[54,109,126,127,466],[54,109,126,127,468,469,470,471,472],[54,106,107,109,126,127],[54,108,109,126,127],[109,126,127],[54,109,114,126,127,145],[54,109,110,115,120,126,127,129,142,153],[54,109,110,111,120,126,127,129],[54,109,112,126,127,154],[54,109,113,114,121,126,127,131],[54,109,114,126,127,142,150],[54,109,115,117,120,126,127,129],[54,108,109,116,126,127],[54,109,117,118,126,127],[54,109,119,120,126,127],[54,108,109,120,126,127],[54,109,120,121,122,126,127,142,153],[54,109,120,121,122,126,127,137,142,145],[54,101,109,117,120,123,126,127,129,142,153],[54,109,120,121,123,124,126,127,129,142,150,153],[54,109,123,125,126,127,142,150,153],[52,53,54,55,56,57,58,59,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],[54,109,120,126,127],[54,109,126,127,128,153],[54,109,117,120,126,127,129,142],[54,109,126,127,131],[54,109,126,127,132],[54,108,109,126,127,133],[54,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],[54,109,126,127,135],[54,109,126,127,136],[54,109,120,126,127,137,138],[54,109,126,127,137,139,154,156],[54,109,121,126,127],[54,109,120,126,127,142,143,145],[54,109,126,127,144,145],[54,109,126,127,142,143],[54,109,126,127,145],[54,109,126,127,146],[54,106,109,126,127,142,147,153],[54,109,120,126,127,148,149],[54,109,126,127,148,149],[54,109,114,126,127,129,142,150],[54,109,126,127,151],[54,109,126,127,129,152],[54,109,123,126,127,136,153],[54,109,114,126,127,154],[54,109,126,127,142,155],[54,109,126,127,128,156],[54,109,126,127,157],[54,109,114,126,127],[54,101,109,126,127],[54,109,126,127,158],[54,101,109,120,122,126,127,133,142,145,153,155,156,158],[54,109,126,127,142,159],[54,109,126,127,466,483],[54,109,126,127,482,483,484,485,486],[54,109,126,127,464,465],[54,109,126,127,489],[54,109,126,127,218,219],[54,109,126,127,211,212],[54,109,126,127,206],[54,109,126,127,204,206],[54,109,126,127,195,203,204,205,207,209],[54,109,126,127,193],[54,109,126,127,196,201,206,209],[54,109,126,127,192,209],[54,109,126,127,196,197,200,201,202,209],[54,109,126,127,196,197,198,200,201,209],[54,109,126,127,193,194,195,196,197,201,202,203,205,206,207,209],[54,109,126,127,209],[54,109,126,127,191,193,194,195,196,197,198,200,201,202,203,204,205,206,207,208],[54,109,126,127,191,209],[54,109,126,127,196,198,199,201,202,209],[54,109,126,127,200,209],[54,109,126,127,201,202,206,209],[54,109,126,127,194,204],[54,109,126,127,169],[54,109,126,127,164,166,169],[54,109,126,127,165,166],[54,109,126,127,166,169,173],[54,109,126,127,165],[54,109,126,127,166,169],[54,109,126,127,164,165,166,168],[54,109,126,127,164,166],[54,109,126,127,165,166,175],[54,109,126,127,221,222],[54,109,126,127,188],[54,109,126,127,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,265,266,267,268,269,270,271,274,275,276,277,278,279,280,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438],[54,109,126,127,255],[54,109,126,127,236,241,243,244,245,260,264],[54,109,126,127,231,243,247,283,394],[54,109,126,127,274],[54,109,126,127,275],[54,109,126,127,243],[54,109,126,127,242,243,244,245],[54,109,126,127,241,248,254,276,278,279,283,311,314,347],[54,109,126,127,243,256,279,283,310],[54,109,126,127,242,243,283],[54,109,126,127,356],[54,109,126,127,283,407],[54,109,126,127,243,283,408],[54,109,126,127,283,408],[54,109,126,127,284,341],[54,109,126,127,243,244,338,340],[54,109,126,127,234,248,283,288,295,342],[54,109,126,127,241,341],[54,109,126,127,251,265,283,407],[54,109,126,127,243,283,407,411],[54,109,126,127,283,407,411],[54,109,126,127,231],[54,109,126,127,354],[54,109,126,127,224,231,248,271,283,367],[54,109,126,127,248,283],[54,109,126,127,236,241,242,244,248],[54,109,126,127,243,244,245,381],[54,109,126,127,231,242,244,247,271],[54,109,126,127,236,241,259,260,264,271,276],[54,109,126,127,236,242,244,248],[54,109,126,127,243,381],[54,109,126,127,256,284],[54,109,126,127,229,283,312,315,366,424],[54,109,126,127,278],[54,109,126,127,248,254,260,276,277,283],[54,109,126,127,238],[54,109,126,127,372],[54,109,126,127,239],[54,109,126,127,377],[54,109,126,127,236],[54,109,126,127,234],[54,109,126,127,241],[54,109,126,127,270],[54,109,126,127,271],[54,109,126,127,248],[54,109,126,127,311,385],[54,109,126,127,243,254,256,277,283,310],[54,109,126,127,241,242,243,244],[54,109,126,127,247],[54,109,126,127,244,245,246,263,264,272,273,281,282],[54,109,126,127,236,263,269,271],[54,109,126,127,243,244,254],[54,109,126,127,224,231,236,239,241,242,244,247,248,251,255,263,272],[54,109,126,127,246,256,257,262],[54,109,126,127,243,254,278,280],[54,109,126,127,224,236,241,243],[54,109,126,127,244,264],[54,109,126,127,252,254,283],[54,109,126,127,241,244,264,265,266,267,268],[54,109,126,127,236,248],[54,109,126,127,236,237,244],[54,109,126,127,236,241,242,243,283],[54,109,126,127,241,248],[54,109,126,127,236,244,371],[54,109,126,127,228],[54,109,126,127,226,228,234,236,241,243,254,271,283],[54,109,126,127,283,407,411,415],[54,109,126,127,283,407,411,413],[54,109,126,127,230],[54,109,126,127,242,243,244,381],[54,109,126,127,279],[54,109,126,127,249,363],[54,109,126,127,224],[54,109,126,127,243,249,250,251,283,288,295,296,297,298],[54,109,126,127,242,247,248,249,250],[54,109,126,127,248,310],[54,109,126,127,240,285],[54,109,126,127,241,244,247],[54,109,126,127,252,253],[54,109,126,127,237,239,241,242,244,247,249,250,264],[54,109,126,127,241,243,248,283,298,312,314,315,318],[54,109,126,127,237],[54,109,126,127,258],[54,109,126,127,259],[54,109,126,127,251],[54,109,126,127,228,315],[54,109,126,127,241,283],[54,109,126,127,242,251,271,283],[54,109,126,127,283,408,417],[54,109,126,127,236,241,243,276,278,283,311],[54,109,126,127,230,241,243,247,256,283,298,312],[54,109,126,127,283],[54,109,126,127,371],[54,109,126,127,256],[54,109,126,127,241,242,244,260,264,265],[54,109,126,127,238,239,244,249,269,330,331,372],[54,109,126,127,236,241,242,283],[54,109,126,127,236,241,242,283,284],[54,109,126,127,236,241,242,283,301],[54,109,126,127,241,247,283],[54,109,126,127,236,238,243,244,247,264,271],[54,109,126,127,283,407,411,420],[54,109,126,127,247,283],[54,109,126,127,236,243,247,255,283,312,318,333,334],[54,109,126,127,247,283,284],[54,109,126,127,243,271,283],[54,109,126,127,236,243,255,283,298,309,312,318,331],[54,109,126,127,242,243,247,283,284],[54,109,126,127,241,243,283,365],[54,109,126,127,241,243,247,283,304],[54,109,126,127,243,283,295],[54,109,126,127,234,242,304],[54,109,126,127,242,243,244,245,264],[54,109,126,127,260,265,276,283],[54,109,126,127,239,247],[54,109,126,127,261],[54,109,126,127,229,254,266],[54,109,126,127,261,346,348],[54,109,126,127,244,260,264,265],[54,109,126,127,244,254],[54,109,126,127,225,226],[54,109,126,127,254,256,277,280,283],[54,109,126,127,254,256,277,283,310],[54,109,126,127,326],[54,109,126,127,229,242,243,244,254],[54,109,126,127,236,242,243,339],[54,109,126,127,225],[54,109,126,127,318],[54,109,126,127,241,243,244,254],[54,109,126,127,230,241],[54,109,126,127,241,243,316,317],[54,109,126,127,244],[54,109,126,127,283,400,401],[54,109,126,127,247,284],[54,109,126,127,244,260,276],[54,66,69,72,73,109,126,127,153],[54,69,109,126,127,142,153],[54,69,73,109,126,127,153],[54,109,126,127,142],[54,63,109,126,127],[54,67,109,126,127],[54,65,66,69,109,126,127,153],[54,109,126,127,129,150],[54,109,126,127,160],[54,63,109,126,127,160],[54,65,69,109,126,127,129,153],[54,60,61,62,64,68,109,120,126,127,142,153],[54,69,78,86,109,126,127],[54,61,67,109,126,127],[54,69,95,96,109,126,127],[54,61,64,69,109,126,127,145,153,160],[54,69,109,126,127],[54,65,69,109,126,127,153],[54,60,109,126,127],[54,63,64,65,67,68,69,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,96,97,98,99,100,109,126,127],[54,69,88,91,109,117,126,127],[54,69,78,79,80,109,126,127],[54,67,69,79,81,109,126,127],[54,68,109,126,127],[54,61,63,69,109,126,127],[54,69,73,79,81,109,126,127],[54,73,109,126,127],[54,67,69,72,109,126,127,153],[54,61,65,69,78,109,126,127],[54,69,88,109,126,127],[54,81,109,126,127],[54,63,69,95,109,126,127,145,158,160],[54,109,126,127,218,222,459],[54,109,126,127,161],[54,109,120,121,123,124,125,126,127,129,142,150,153,159,160,161,162,163,170,171,172,174,176,178,179,180,190,210,214,215,216,217,218],[54,109,126,127,161,162,163,177],[54,109,126,127,163],[54,109,126,127,213],[54,109,126,127,170,180,218],[54,109,126,127,189],[54,109,126,127,170,218],[54,109,126,127,441],[54,109,126,127,454],[54,109,126,127,223,454,455,456,457,458],[54,109,126,127,439,441,443,445,447,449,451,453],[54,109,126,127,132,218,220,460]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","signature":false,"impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","signature":false,"impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","signature":false,"impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","signature":false,"impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","signature":false,"impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","signature":false,"impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","signature":false,"impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","signature":false,"impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","signature":false,"impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","signature":false,"impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","signature":false,"impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","signature":false,"impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","signature":false,"impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","signature":false,"impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","signature":false,"impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","signature":false,"impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","signature":false,"impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","signature":false,"impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","signature":false,"impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","signature":false,"impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","signature":false,"impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","signature":false,"impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","signature":false,"impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","signature":false,"impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","signature":false,"impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","signature":false,"impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","signature":false,"impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","signature":false,"impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","signature":false,"impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","signature":false,"impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","signature":false,"impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","signature":false,"impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","signature":false,"impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","signature":false,"impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","signature":false,"impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","signature":false,"impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","signature":false,"impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","signature":false,"impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","signature":false,"impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","signature":false,"impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","signature":false,"impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","signature":false,"impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","signature":false,"impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","signature":false,"impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","signature":false,"impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","signature":false,"impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","signature":false,"impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","signature":false,"impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","signature":false,"impliedFormat":1},{"version":"f724236417941ea77ec8d38c6b7021f5fb7f8521c7f8c1538e87661f2c6a0774","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","signature":false,"impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","signature":false,"impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","signature":false,"impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c06b2652ffeb89afd0f1c52c165ced77032f9cd09bc481153fbd6b5504c69494","signature":false,"impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","signature":false,"impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","signature":false,"impliedFormat":1},{"version":"612422d5ba6b4a5c4537f423e9199645468ad80a689801da63ab7edb43f7b835","signature":false,"impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","signature":false,"impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","signature":false,"impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","signature":false,"impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","signature":false,"impliedFormat":1},{"version":"1cbdcdef62bf42688e5d825898c3a59458466c48ad8a4ef3f88d8a1cd04310d6","signature":false,"impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"dc8c6f5322961b56d9906601b20798725df60baeab45ec014fba9f795d5596fd","signature":false,"impliedFormat":1},{"version":"67e435fa530778903f5525904e6645b7368eb5ac622055febd19bb399e328ccd","signature":false,"impliedFormat":1},{"version":"060d305fe4494d8cb2b99d620928d369d1ee55c1645f5e729a2aca07d0f108cb","signature":false,"impliedFormat":1},{"version":"5b22bf96be06fca1e134e3a85351ffe7aa2edf0c9a5bd175b7239ee48186dbf5","signature":false,"impliedFormat":1},{"version":"0c50296ee73dae94efc3f0da4936b1146ca6ce2217acfabb44c19c9a33fa30e5","signature":false,"impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","signature":false,"impliedFormat":1},{"version":"5b48cc670da1dfe926036deaf349085caf822def460a6d50de23f1cc12a5f8cd","signature":false,"impliedFormat":1},{"version":"418f34087d52d001e2688a6282a5de9bf583ff771ed5546af0ae36e4f60a458b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"036c9a03b01281f99f8251fdc7088e894566be44b3f139a9d48501a978b611df","signature":false,"impliedFormat":1},{"version":"4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","signature":false,"impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","signature":false,"impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","signature":false,"impliedFormat":1},{"version":"2c9adcc85574b002c9a6311ff2141055769e0071856ec979d92ff989042b1f1b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1569ad5969ae0de6ad0bdfaaab0dabcd8878bc3147ab096206eed2019bb84e7f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","signature":false,"impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","signature":false,"impliedFormat":1},{"version":"42a350ce4f6f291198dc89833afcc4fd078248b4e1fdc9d05c846c66a670f710","signature":false,"impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","signature":false,"impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","signature":false,"impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","signature":false,"impliedFormat":1},{"version":"b03754211d839cdc48979996525a32eb62130682c2f9801dfc2f25533ce7238d","signature":false,"impliedFormat":1},{"version":"3910dab597c40e173bf0e0d419d3ce9682c54ebf6ae84849f9b829b1451a17ec","signature":false,"impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","signature":false,"impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","signature":false,"impliedFormat":1},{"version":"486c074a5c0f2254345c0d1c9540380f5463999e42d7e1a159305ea823d3c4b1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","signature":false,"impliedFormat":1},{"version":"f4d99ac08c4097a3cc202bec2c7198c19dcdd80d4ec6c62d1356b10d03b119d5","signature":false,"impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","signature":false,"impliedFormat":1},{"version":"ca77e83162eccec633513723d178d4203bdd5465297267696be08013bab0aa1d","signature":false,"impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","signature":false,"impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","signature":false,"impliedFormat":1},{"version":"f61c153d5abbe4381a1fba0d41be0babdd5de51c626ee7ff14cede14dedc33f1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"7803171d745ab18e77f16bf82e742268ff72b7f7382ab191e4277527580c2d89","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0e9ba65cd3d6d194bdda7343f9f2917f7144ead96a924ca58d1a14919992dc20","signature":false,"impliedFormat":1},{"version":"255d948f87f24ffd57bcb2fdf95792fd418a2e1f712a98cf2cce88744d75085c","signature":false,"impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","signature":false,"impliedFormat":1},{"version":"0b0dfb2d3e8420ead06f6a11acfb02ddcec42257e6ce391993e895ba0af86de4","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"bfd3b3c21a56104693183942e221c1896ee23bcb8f8d91ab0b941f7b32985411","signature":false,"impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","signature":false,"impliedFormat":1},{"version":"64ccfe7dcbcad9b8a661a042ecb2309e41b9ab957f7b8cd070b05583a7821a13","signature":false,"impliedFormat":99},{"version":"c911b3bd083dade8fdb7775261aa25feae695d562a3b11209650b47be90043fc","signature":false,"impliedFormat":99},{"version":"98580d6ed68c9cee7f956685abbb6502d70bd92f48175ac807ae6c5e254fadc6","signature":false,"impliedFormat":99},{"version":"fc5ad2ec8b130a59612c5cfcc2cdca9bcdd80851adb8acdb004aaeff00cc978d","signature":false,"impliedFormat":99},{"version":"5c62f97efe6b4b10e8597cb6252fcf772eeea3d3e9b93e97d1ce3e24980042da","signature":false,"impliedFormat":99},{"version":"aacf8a6955a6e97c902e3ad9644afc21544795c0cd35327e063814a564f4d4b4","signature":false,"impliedFormat":99},{"version":"638a22f8e16b31c37297483fa38cfc760ed309b16d4c606a7b4532f4394c9c0c","signature":false,"impliedFormat":99},{"version":"9394183f4c37b8591156f32e41223c9e0dd211eefe7499155d4cdf15268eaea8","signature":false,"impliedFormat":99},{"version":"17e7af5056ebd299c68c82bb69f9cbcf0a1a0cfddcdaead376bb7f3fb630f134","signature":false,"impliedFormat":99},{"version":"f3816d074e78163d7972a8782fc9eb5ce51ee5c0bd44d1faf646827e39ae3288","signature":false,"impliedFormat":99},{"version":"c0042ca8c8cbda9e978c4558933c47c812ac018e51d64ae8ff51e641672bed46","signature":false,"impliedFormat":99},{"version":"7d3e062a778b8f5ea4f0cac7e925e31f88e6739812ebc5f827474324a4048f14","signature":false,"impliedFormat":99},{"version":"7c5bafb0d9096c4de8488335d05920b35e993f698fb9101cb78956aa956f9648","signature":false,"impliedFormat":99},{"version":"670f2ad250e21d8e3a9cf0bd2c2352f59c132bb6be106e6955b610223b9fbe9d","signature":false,"impliedFormat":99},{"version":"08cad5153da753ed9c463b0e42a86bd8bc2b443368ce6a02a2a70e46adf7a4e0","signature":false,"impliedFormat":99},{"version":"df5baf8af95129eecb1ef1b02158664fc9c8bd0573e6011571b6c6bc33d2f570","signature":false,"impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","signature":false,"impliedFormat":99},{"version":"d4ec151557b64ff001e06b5878e66eb98640961647b1581fe2a0a1af05f408fb","signature":false,"impliedFormat":99},{"version":"e0864480ea083087d705f9405bd6bf59b795e8474c3447f0d6413b2bce535a09","signature":false,"impliedFormat":99},{"version":"e67cbea16f1994af89efd700542dbf3828a46a52b29e4d67e801bd7869dc103c","signature":false,"impliedFormat":99},{"version":"971f12a5fc236419ced0b7b9f23a53c1758233713f565635bbf4b85e2b23f55a","signature":false,"impliedFormat":99},{"version":"76de3321ce519928f1ff7d7a30391c0dc7374af20f81d9167919f038895b5cb0","signature":false,"impliedFormat":99},{"version":"094b9210da23b8711709b0535c59841186267bf6b83c1609aa9b515f830ab274","signature":false,"impliedFormat":99},{"version":"fbfbb4e99c6259ff5ccc4a5a62b3b63c0c8cae6e84737786c4a4c761c9a9de91","signature":false,"impliedFormat":99},{"version":"604887bbd5b0a93234ce882543a465f008636185c52e0f0353330e2bc38b03b6","signature":false,"impliedFormat":99},{"version":"32bf912173e8a9533631f9e9d8dc90a2ac7b52c2355611ddd886beab24dfd182","signature":false,"impliedFormat":99},{"version":"82695324abf7f3278b6d9f0582f4a544e8f7055c8cbe1065ab5cbacde1719c4c","signature":false,"impliedFormat":99},{"version":"43bba542e50e19241ec64bc13cfc0d9273e6198f36563cecad1f4e4b78ad47f3","signature":false,"impliedFormat":99},{"version":"b8cb3b69c0e8114f758bb8ef8efeef1cc80f8911bfd21126def73d2174ce479e","signature":false,"impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","signature":false,"impliedFormat":99},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","signature":false,"impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","signature":false,"impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","signature":false,"impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","signature":false,"impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","signature":false,"impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","signature":false,"impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","signature":false,"impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","signature":false,"impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","signature":false,"impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","signature":false,"impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","signature":false,"impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","signature":false,"impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","signature":false,"impliedFormat":1},{"version":"3cc8a3d123b6b232d48d34b51b785f9da8d193f5b5817fa521fcd2f3b9315c55","signature":false,"impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","signature":false,"impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","signature":false,"impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","signature":false,"impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","signature":false,"impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","signature":false,"impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","signature":false,"impliedFormat":99},{"version":"f3fe8197217cb6fd6e07c0eaa0966e4ece77cd779cbfc55767a93704cc692e04","signature":false,"impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","signature":false,"impliedFormat":1},{"version":"617112682fcb55805f0a0e16450397edc016881962eee4298f4f4e3ef795aee8","signature":false,"impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","signature":false,"impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","signature":false,"affectsGlobalScope":true,"impliedFormat":99},{"version":"7ef3a45742bae4cb8d88e473a04e14d35998ec9e8465d594b1bceb8d71d0023d","signature":false,"impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","signature":false,"affectsGlobalScope":true,"impliedFormat":99},{"version":"343badbae019a9bc81f44b0942ab08f0702521429be21dba24a70238dba35645","signature":false,"impliedFormat":99},{"version":"2faebfa830ae4cfbfb58e48b0ec20a2a63882d776f0ca36ec7155d45cf1b7f2d","signature":false,"impliedFormat":99},{"version":"9b98d6982d3a305952855f00b660b60d0d625f0965281f1692d5df8a401fca00","signature":false,"impliedFormat":99},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","signature":false,"impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cd21651ff2dc71a2d2386cecd16eca9eed55064b792564c2ff09e9465f974521","signature":false,"impliedFormat":1},{"version":"fad635d25316340230cfbe1f879f00fcee7eb405afc038bd5986c0cecc72ad01","signature":false,"impliedFormat":99},{"version":"d4c1041683629f6239e6984e36689c93ca47a10875627eac4ce458359ff05403","signature":false,"impliedFormat":99},{"version":"635e0f9fb75313bc0a606164c18d7edb63d898cc1fa7d64bf1ca2424cb0ed641","signature":false,"impliedFormat":99},{"version":"04dc33f1dbf2f463c6480d431da59a772140be86c438f9445a0f2181b2893bfc","signature":false,"impliedFormat":99},{"version":"ecd444940f4a7726b83d004060652c1ea7dba9e79384f9e14c7c893dd35ee5a6","signature":false,"impliedFormat":99},{"version":"e415a5f905d75b3f182c691edfce1d9f1edb386f77af750c6891e1d4ca8b288c","signature":false,"impliedFormat":99},{"version":"5b59e760d0240891c2a037ffd823d3b29a81ebbdb692f3f6f1e6a84c75bdf93f","signature":false,"impliedFormat":99},{"version":"6df1fe718a1b394790d02d005a955ed1f50803c0fb988068af707db60515eb2b","signature":false,"impliedFormat":99},{"version":"21854b6c78fc2c0533d10595abc9f2adc8e366836f8443b4311b5455c9c61211","signature":false,"impliedFormat":99},{"version":"9ff4fa57a4e960c92e3683b84fac659e046c9dc9ea71fa19fd0238f389a37abc","signature":false,"impliedFormat":99},{"version":"81590fb2a4a06190dd982ba296e376309d6cc9a799c943b8bc2eb0fe7e826b83","signature":false,"impliedFormat":99},{"version":"bead1f5a73e14e9425a757dce08aa2551c029cd679a1b20f4ac94cfb2d26af60","signature":false,"impliedFormat":99},{"version":"c13be7c697ba7c5497070a9899e3eb5b6a1bd8e873d54ef4c0cd3292a0c9d80b","signature":false,"impliedFormat":99},{"version":"f45b6b08aaa4c86dd4b909a6ef6120d0d25f006031ce3677dea240040d79cd7e","signature":false,"impliedFormat":99},{"version":"2da759c66256885ffe262664a03c99c84f66c12160124bc9c54bbe4626a98b34","signature":false,"impliedFormat":99},{"version":"b539fdc12a246bce588611a2d2f471128f99cbe4feb982b84e292a7c185c19b1","signature":false,"impliedFormat":99},{"version":"f2a848ebb283dcc3bfa8b644e162eeb658ed4f9b134196a37c4263c37907b510","signature":false,"impliedFormat":99},{"version":"0eea552d03c4a36195a94973748ebde3ac30619c0c266253390e550823477470","signature":false,"impliedFormat":99},{"version":"d80a35ec56d6a8cee40e1bf7496551a57d41e194a0bc602a7433b52aad88e2a7","signature":false,"impliedFormat":99},{"version":"575430260af36058251660050741d9d5ddf2e267ff10a2595099ff8199f4cfbb","signature":false,"impliedFormat":99},{"version":"186f7fb9debf5a3b4847c572fcdb79140e17e112429b3086425a13ee825235d1","signature":false,"impliedFormat":99},{"version":"5992ed84626768c64bc863e32483d3e6a35b69918b3c8e529e6fe9b3b4230068","signature":false,"impliedFormat":99},{"version":"7fcf64c42e0bf1cebc851540ab03cf87600b4b5c7741b853c953aa0a88b9bbb5","signature":false,"impliedFormat":99},{"version":"c9ca5bce1637322c8fe4e3759a32102d6bfe37e437e784d193019984516236f8","signature":false,"impliedFormat":99},{"version":"9a38144039551dd1f8247d7bf778cb2f57f31e67fb5445e3d2918e6e3b71f1ff","signature":false,"impliedFormat":99},{"version":"a813c75b26a9094cf1364da70a459bced14258f78d767c6f63adb9aa08994955","signature":false,"impliedFormat":99},{"version":"3db294c132bd83df923a78cc7125bab8bc4253280296dcccc7e823c964e73696","signature":false,"impliedFormat":99},{"version":"e66f0def9554c696cb0693998a90876006ae0b72dafccc3f34dfc1b01c649ba5","signature":false,"impliedFormat":99},{"version":"b25e6c44ae3ec126eb11223357c4cb372ea57a6b719cd2b1f7efcdf5050fc5b5","signature":false,"impliedFormat":99},{"version":"cbb264fe574f7c974c0c7f041d5d09f58598c00398c2e6db4bb6d77848108459","signature":false,"impliedFormat":99},{"version":"707da604e89a3507db40a7b7283c9d062f00a3e8c1bb949437e4d23d3f8c83a2","signature":false,"impliedFormat":99},{"version":"8e2abba1230fb6e546aa985c11f416a4e2bc338c970f7ade1f7d7b1bd3703939","signature":false,"impliedFormat":99},{"version":"116174984d0b919c97430ff968fa69c52e282d7347cc92f4dabd5ed41a1c7d13","signature":false,"impliedFormat":99},{"version":"4163e075fce2172b06114df8c5f612164fe8338fcd0d554fd4194f9160f4925e","signature":false,"impliedFormat":99},{"version":"003b12a82db026304ca5bf817c6c32e88562e2fb1c6deab1e3319cbe63448919","signature":false,"impliedFormat":99},{"version":"316972caac7df38c384308f37116d3b5a44a597ad00aee135680efc456590dd1","signature":false,"impliedFormat":99},{"version":"f9f7181330aed071e41b2dff5eae74649c4e7efccab970aab94744c85e231fa7","signature":false,"impliedFormat":99},{"version":"1cae5a8af6f5206a48ce239d979e42d87d97e65b2b7c8e74a47b0c268bcff69a","signature":false,"impliedFormat":99},{"version":"d8c93138a8baac14311c5164ef070ed9e2213067d4e51d705ff7460f8c30aac8","signature":false,"impliedFormat":99},{"version":"c844f57c72b7ca3f5841da0859fd6e8708047ef76acb435936e933c5a1035e65","signature":false,"impliedFormat":99},{"version":"34572df2b002f36030be31c255702e99b4e9fde525468c62909909ed61aa085d","signature":false,"impliedFormat":99},{"version":"5fa712431619a8ebf7a63c1c0d27fd9feaae12b95c7cb2940ff813648a504865","signature":false,"impliedFormat":99},{"version":"7e154d458cf3d067917d13bc85488b11d023a75666997074f649c7ffa259c8a2","signature":false,"impliedFormat":99},{"version":"723697a7974bb5912a12872fb7bd70fbbaeca62f425b160189e13135c145f69b","signature":false,"impliedFormat":99},{"version":"c3ff3ac71fca07b3e392c42b2f6f8496434617cb677fd754039e4eb12b10a570","signature":false,"impliedFormat":99},{"version":"e405715e19f006c750521411aa0e21f5f65714b0cfc06a1ef827ca7164a78a20","signature":false,"impliedFormat":99},{"version":"ac224c8d6f0fb529961be77e07cc57b3de3a0e2d9e07431c984bbf85f864535c","signature":false,"impliedFormat":99},{"version":"34b73aa54e4a54baf65412868f362b45c2aec8d55e93d37c9abc193d9a5042f3","signature":false,"impliedFormat":99},{"version":"3389ec1b78a7947e393b927a43e31020f71c791085f67efde86d24fb3805d2f0","signature":false,"impliedFormat":99},{"version":"21cea8230195dbf1fcf1b11c35b01bfcd035dd4d0de736e4daf5f0f76e08f42c","signature":false,"impliedFormat":99},{"version":"161bda0d64fefa744228c5317b39853a5671eb936bf543a1ed5ca1c2756fe78f","signature":false,"impliedFormat":99},{"version":"0c7905fb84c0678fdf4ca3c5d7f0e3ff1d6f096749a32d18d3f516e40c3b8cb7","signature":false,"impliedFormat":99},{"version":"ce9a92fa30beb6aa039c8e6b3ced2261a2fea149fa70046a543531bd4d767975","signature":false,"impliedFormat":99},{"version":"5378a15bc21328ce0801339bf5b6da02d62d822a6e635738f1fc1ba399d04b74","signature":false,"impliedFormat":99},{"version":"c6b6e8731ebce7e8343ddb353376aa4e7fd522ad22cc19732d9633981dd00e0e","signature":false,"impliedFormat":99},{"version":"2d7b937e1d74c7b7f360b8dc29896f1f1dbf6bea34661c44ba061b94f1faed38","signature":false,"impliedFormat":99},{"version":"eae3dff34f426311c6b091deaa0d0c5702533460f8a4a26299d294ee4e3c2655","signature":false,"impliedFormat":99},{"version":"c35ec6abf02ebd1ff7467e5fbd660ea8fef0c156e3d3c4aa702f44cff1aed391","signature":false,"impliedFormat":99},{"version":"72fa28b198e60cb3f6fc7e85847a808b1aaa93d6362419bb350363b77056c061","signature":false,"impliedFormat":99},{"version":"2dcd17e5dea10ba55d976e3c304d185aabaa4a7589efaeebba61679b18b7108b","signature":false,"impliedFormat":99},{"version":"abd4b15f7263ead3b01616086a13683d3e7bfe8bc93f79cffde7dcf6c6c622ed","signature":false,"impliedFormat":99},{"version":"925dfc37731b7011e1886c707db60c9658b48fd02b6a261a732bc31d26d4f976","signature":false,"impliedFormat":99},{"version":"f9c434fb4157a0aa57b651ac5d18d47a8e1585779a805507d6015422e36cf95e","signature":false,"impliedFormat":99},{"version":"e89c048b7e1597471f692c49a5df23c43d67e9b4037a2b1ca28094c49bcc5868","signature":false,"impliedFormat":99},{"version":"c424b932553eec696c94ea77aece8460fd898e6349a499b1e96a05f561f809bf","signature":false,"impliedFormat":99},{"version":"a3e55e768780c3422867fd252c593444166c1b4aab83f711d4cafe018c151bcc","signature":false,"impliedFormat":99},{"version":"2f0fad277179e920e6e1e3a1f404663ef44e7d6daad888c7e7c1d5e4ac35660f","signature":false,"impliedFormat":99},{"version":"8dee13aa32d22ed4803116c8ec86779d47755c077cd91df1f5f8c92f004a974e","signature":false,"impliedFormat":99},{"version":"8dab0cefaf005a0843afb026ef38e5aed8607bfbb737f9c7fc50f785b78ecf16","signature":false,"impliedFormat":99},{"version":"d3ad0bc42dcc11b07c57f2252f153a61c35c356964700e4dc8de47dcc93e4f7e","signature":false,"impliedFormat":99},{"version":"060db9875e0cfcbbdbb9033306d86ff29211539422873cb938a83292dd15cff6","signature":false,"impliedFormat":99},{"version":"95a86c187c914030d637fcd6aed8b2ffc97253c23ee64672f5a048f3b51aa6c9","signature":false,"impliedFormat":99},{"version":"8e631b7142be8de16b2664f1b7a0ba19328f972056e878ac70fcdac1248a349b","signature":false,"impliedFormat":99},{"version":"a7991a0b4ef29c09d5e3f2c3470c3767cfb1729496ce5b6b2ca30c83b0ee2017","signature":false,"impliedFormat":99},{"version":"cdfdb403e5559c77775dfa1cbcd40e27664369fc4b4c1fa42619935fd9262bee","signature":false,"impliedFormat":99},{"version":"34b180219def210a4a69436bbffd46ec68014539ee985b97e4708430d4d8e1ff","signature":false,"impliedFormat":99},{"version":"b0ea9f55415f1f81eaa0ae0873cd218d247ffa8a3d1816debc9e72ae031301df","signature":false,"impliedFormat":99},{"version":"955c70837aa87fca583694d68fd02e43c78ceddae2f1fccfa67e314919e9923a","signature":false,"impliedFormat":99},{"version":"8de93f492e39bba6d3dd9497a9536fd7ef1d0c024646dea563ba599ba41b42b7","signature":false,"impliedFormat":99},{"version":"ff6cae9c99bf41a88bd777cff4603b2dd8df284df08bc1c70b39e1b831114587","signature":false,"impliedFormat":99},{"version":"42b7f20621b9ca12ac20e0ecf067b709921a823306bc46a7566d6001d30d5987","signature":false,"impliedFormat":99},{"version":"9821bd794030358a4ca9b693d3b2604baaa9a439fd8bd31b03c6bdc35e5c4d52","signature":false,"impliedFormat":99},{"version":"1644a14874faafb317449298893c34c3864f1128416b401fb502a9b0ff5fb68c","signature":false,"impliedFormat":99},{"version":"24e70403180bb155cc23f6128a1b242f62ec5a7b3bb2c312c0d591f10bc5450c","signature":false,"impliedFormat":99},{"version":"b3d32b6765eb89b0256725bdc76ee73e63c56fcedfeeef37721615bd363eda77","signature":false,"impliedFormat":99},{"version":"a78a1bca636b4b69775c6c5f548b34fc7a9abbe74c2887d7f1fdfb293e37f61b","signature":false,"impliedFormat":99},{"version":"b26fb95946488eaeed9ad6d170ff90d22202547f1ef52bc7d6f91c77c8516540","signature":false,"impliedFormat":99},{"version":"0bba43d978e1d320d14ef2e02f1f02d7474071a5592d0b545e969002d55a49fa","signature":false,"impliedFormat":99},{"version":"558df86d4f17ae1c7e732fb2ece8fb6390529496c77b3cb90a4c38008008088a","signature":false,"impliedFormat":99},{"version":"ecf7bd6a5e86562c39e559a0ba41a09058bf1f187a260cd78c381feade95f8c6","signature":false,"impliedFormat":99},{"version":"6e1435c1c4300a30ee53a812a002e9e37f5ca02a5d6c7cbccad7ec667d02aacf","signature":false,"impliedFormat":99},{"version":"9937002dcab0b1d0b3cda4709e3f36986cc8ddb8177fc50b7995f26a0e642983","signature":false,"impliedFormat":99},{"version":"324ca2d3dd0efd96187834009ae257ea293ecb27d425c87aa415ab4d1242f2f4","signature":false,"impliedFormat":99},{"version":"f4563e9a0e5ec762cca8101e1d4435d96490041f2a1f416973f23cba3cf985b1","signature":false,"impliedFormat":99},{"version":"d8122c9207978093ee0ff6574da42deba33e6b8a24b93b60e9cb4d004a89d9c1","signature":false,"impliedFormat":99},{"version":"c2ccc7c4bf4ec0827ab24087d3d28d936003bcf364b4e5df8abb135adf29f9c0","signature":false,"impliedFormat":99},{"version":"e4e2459598e499579a64881664c0413c8caac0134b6044696246345dbc3fd287","signature":false,"impliedFormat":99},{"version":"4a88bd904cef785320a507d51e358e082624bbd2bdae93f47be7f8ddd2c1b60d","signature":false,"impliedFormat":99},{"version":"3068fc8e6a14e20bac8feae3bd72965d44e4ef372d612708d322663907b5cd3c","signature":false,"impliedFormat":99},{"version":"9681b978f529c62ea3b7feb6e6c16979f97de629a9de0c9bccd697d642937b05","signature":false,"impliedFormat":99},{"version":"4b8b7b1100b1f9a92ce02a3efdf9c4b588ba71bdd3d426217cf13722a60f5295","signature":false,"impliedFormat":99},{"version":"00e2828dbc375686b78f04fae08f8b76e2c87876ddf0fce4403d41c18181cd9d","signature":false,"impliedFormat":99},{"version":"4af74a8e255575812dfeb09390e0cebc8a6aefaec59a7985478106f8e4a3fb1d","signature":false,"impliedFormat":99},{"version":"99bad9b8bf0c579cac0a1dcd86d8d5e47b5eb902eb0f16b553c1d3c12204a94a","signature":false,"impliedFormat":99},{"version":"e6af1e7c1b560b80c9291bc861168d1656a6ef8d3fffd3a83f07b61c82e9d0cf","signature":false,"impliedFormat":99},{"version":"d85a8538091baab20f6cf05faf6ff5b97e9dffea52bdd29381d9a4b5e0aa60d1","signature":false,"impliedFormat":99},{"version":"df28899221284aaf08640b6ad3e819ab9f4112f4f7cffb4b90179e53476ea495","signature":false,"impliedFormat":99},{"version":"2741ed6b4cebf245949141e47c139b82f6a640a4a4d9db4f097885f443d7d40b","signature":false,"impliedFormat":99},{"version":"0d3d9d02030248338b908179592022d15619d04881db91d00d1a484eb88a0052","signature":false,"impliedFormat":99},{"version":"2b2432c899a2e6652e212a8127fe507a5eb18e054f18a06ebbc29a74c46bfefb","signature":false,"impliedFormat":99},{"version":"f85981bbd8ffd17e39d83e4baf150e87b4b6560d73fa021f3bdf7f15abf4fe7b","signature":false,"impliedFormat":99},{"version":"dc74ed49e914b4cd2f8f028fc3263b24ab66ace68bdbf2a9119c28a698262b3f","signature":false,"impliedFormat":99},{"version":"4fb21c3ddd96225cc0be2224cf62c108972303bf373a9da867f5fca6da848313","signature":false,"impliedFormat":99},{"version":"34a1307870b94bd744d6a651f8ec27f3e3654ed1a3203901660111227b761fe3","signature":false,"impliedFormat":99},{"version":"bf47479ef063e921d82b060b0e1a96904677df4f273a50e08b1df2bd136e2925","signature":false,"impliedFormat":99},{"version":"4ca4f879206579f227372ce8beec213f932d1eaba371d5b3ec0851e3a571d203","signature":false,"impliedFormat":99},{"version":"344e635bf3cad4394e0d4d4d834607e9d819fa8df446d9507a78778ee1012fa6","signature":false,"impliedFormat":99},{"version":"6b528ab3b824af82e43266a979dff47f62d921992e0bdfb6a9043e5396082fc5","signature":false,"impliedFormat":99},{"version":"1558bcadf70f6b363eaeb655cc2cae7360397804de1d9bd585d2feda385192f6","signature":false,"impliedFormat":99},{"version":"90d8434d2a655295f7c8dd900ea86ead98ffe066c59d7aebefe08ffd61e38ef4","signature":false,"impliedFormat":99},{"version":"e9c1381fc978572529d41990a03a53429e5a652e8b1774b70303348c57bb1b31","signature":false,"impliedFormat":99},{"version":"11669d8c5e86ff67b984f8bee64d1faab863ec1c8ff89c262c27e75c063ebdff","signature":false,"impliedFormat":99},{"version":"cfff369fdf1d2afd4c6642a6ebf7c8d727393d532146f42be42283bdbbc5377b","signature":false,"impliedFormat":99},{"version":"ae3de7ae9d29e5038cadb1fe4a2d655e3b01b702dd42fb1d274dbb550f6df698","signature":false,"impliedFormat":99},{"version":"70acecbbb46d43b76ee7e3b4e6f2b0539eede73fe747491caaa093d61f29e4db","signature":false,"impliedFormat":99},{"version":"d6eb3370299b3183336618711ffb27ad6ce9763d96efc80cf6139ec2758ea48f","signature":false,"impliedFormat":99},{"version":"867e3e9ac667faef8df6bd9ec169a2c3f58f71ed0ed36e57b08668c214727eb4","signature":false,"impliedFormat":99},{"version":"5b88f3495fff05e3d31c820935f5a2e9c2bb70fea138d9a152a0fcea46589b40","signature":false,"impliedFormat":99},{"version":"71e44bf474e08d33b49ab3f403d2d79ef2f0d9aadc2f9929330bef87a1909ad2","signature":false,"impliedFormat":99},{"version":"c1fe0d3291fab30900480f68f72cb052f34854441f9786a0cd424857e2de8757","signature":false,"impliedFormat":99},{"version":"503fcc822afc39a884a56823971d420086076e7f8822e93cefba27fe065fff41","signature":false,"impliedFormat":99},{"version":"6d8b3da7d530016f4bc28fc091d796938cd5ac43ab1cb95e65d4c7292a82ca19","signature":false,"impliedFormat":99},{"version":"ee1fe3070063aa57050425ee1cb0d52cc2aa4c356e79cc840c8914c107a4164e","signature":false,"impliedFormat":99},{"version":"6303a23a5cfbd0c2caefc4e3781f759a580aa15565be734406b9637bb5465307","signature":false,"impliedFormat":99},{"version":"98aef5c1cf67ba83fd511cf763045a0906f4192b9a963ca31d66fe99a085d255","signature":false,"impliedFormat":99},{"version":"10adf4d23d463963a359f301db2f98daa8bde4df0690b3c4154758455c71ee5f","signature":false,"impliedFormat":99},{"version":"c47cea8fe761c7646e9df73f2bfe308cccb838e56821208b1a58277d6745d7e5","signature":false,"impliedFormat":99},{"version":"c8a8b70029abc4ee30637b5c9a3d80ea0762f9ee99f5ed6c537fc08ca710be81","signature":false,"impliedFormat":99},{"version":"1c7783e32cd7d5637e47ccbd215e2566f465ac2ba5206f331b91072482dd018f","signature":false,"impliedFormat":99},{"version":"5664dfe415f5c29f87c7c3bf43ea2c1ad2990521b310aa10e8a41abd14f32f27","signature":false,"impliedFormat":99},{"version":"7b839a06f4fe489e14e4420fa43e29901b726de6c8e827708805846f7ae8cafc","signature":false,"impliedFormat":99},{"version":"953fa50ab31648e14301ced3e42b8ccfce6be9a2311424bffe0718b7c9683d96","signature":false,"impliedFormat":99},{"version":"f198f05243b8cc697166824d1b1c8380e9a963145b53afd36a854aaba7cf1899","signature":false,"impliedFormat":99},{"version":"a23b404fbd4eeb4dbe9a16c3cb6c77cdb6969d433c0b723b0f5aa4de5f7b7953","signature":false,"impliedFormat":99},{"version":"9674cbc030a7c5506e8757cc5f092b75e8baf8ba55323a814064bece3df0432d","signature":false,"impliedFormat":99},{"version":"c57d07957ac426ef87ed28d9785a824824d8234e2776e1bdbf098b3ce55a8daa","signature":false,"impliedFormat":99},{"version":"f586a88e367e59a3e1f1b34a1228e52a5155c106b9cb2f8db36fed6d9eb9efb3","signature":false,"impliedFormat":99},{"version":"e6749b3c1e0a0f0541269a9058386ccdce75fa33daad48134cc710ab68a25833","signature":false,"impliedFormat":99},{"version":"e2bc4ead8e63387393a8f7c7b618cd8d2b5595f27e3b3997b664c63841f6df84","signature":false,"impliedFormat":99},{"version":"b9d47ab0b8f1295fff20fda80af86215624d99c96e03dd4486736b0202422aa9","signature":false,"impliedFormat":99},{"version":"db0c5d28711f45008be0afbf2d55a7911688dccb702eeffe3532953460598bd8","signature":false,"impliedFormat":99},{"version":"a620d02ba9bdb7a894cc1bdb911f0123ce0647264fc2de6913d1cae0421128df","signature":false,"impliedFormat":99},{"version":"ed999c1469cce636526c84b819dac6b369fc37541723f6a4b56dfaa1525e0992","signature":false,"impliedFormat":99},{"version":"78f7166e49230242132f49fe71ad3e31fa8c2f95f53e4960d35e5cb797c7d820","signature":false,"impliedFormat":99},{"version":"93f5ab10abfa1761b9f90954f50256ca01ee9e657e6a5f9995a2714ae11c516a","signature":false,"impliedFormat":99},{"version":"be9077c9bb010ba677b101fc40bef4f9839a6ace99568ff57b4c02f4b237db4a","signature":false,"impliedFormat":99},{"version":"0529c4cf5f1a3cb17efba2ba1ffbe6bef8383b4523a03334056105d9e1720fe7","signature":false,"impliedFormat":99},{"version":"7fcb4b818f60e4fe51511aa4e53a51b6d3ec031deafe9b678e47397fb787b42b","signature":false,"impliedFormat":99},{"version":"34767bf6796c20eff1bce8ccb895d7598e9c01f560147a8ab211fa51f5a8ed68","signature":false,"impliedFormat":99},{"version":"7b6863bd3701c0ed87293b617e9e0a91055351825e858a62e7d6e884ca4dae8d","signature":false,"impliedFormat":99},{"version":"5a2109c8a388c03d6f6153fe98ddaa4d0a9aa058dfc0b9d98b7ce45baf85ef1e","signature":false,"impliedFormat":99},{"version":"44386ffe4d68242e8cbfab18cf68f52e345fce6ce9b0fe456ce8f448a89c5ff0","signature":false,"impliedFormat":99},{"version":"d2adb57b7578ff022f341a779e91f718422ef03f4f1b22697b921e49bdbdd500","signature":false,"impliedFormat":99},{"version":"d1344da8b59b8190baca55ee1631f2cdcf9107646e98847588a3210bb4ae0ed7","signature":false,"impliedFormat":99},{"version":"d1ff278bedcd43322c961d38d8619d58070b55e0347d49ad6757849af4332dbc","signature":false,"impliedFormat":99},{"version":"40a38756055ab3ab7f359dd22c55c11b4651b75b273c6ed76d1a6620fd4fcf47","signature":false,"impliedFormat":99},{"version":"6124185e0daf9151bb72f01273bc892e6e2c853c5e8acd7fef17c286838a902b","signature":false,"impliedFormat":99},{"version":"5f17ae4cbfc34d8a68e5e6199884eb9dc9dfc931d526ead14a02ab49177efabd","signature":false,"impliedFormat":99},{"version":"766b9454208585ac1c3525ce9d58c209162bea386487f18445b7f7e706c23d5c","signature":false,"impliedFormat":99},{"version":"0896a5e1c7bb06ee49e7205c66c6b1c0c4f0b3d1ed157826302350d14d3183e0","signature":false,"impliedFormat":99},{"version":"c9a57b1ee3b66e6e227d20598366ab73968778dab609e5139c418c83310ad174","signature":false,"impliedFormat":99},{"version":"4869351067bfe590c999008942fcacec26e4af771ec27ed1b27d1dfc86c2c1a5","signature":false,"impliedFormat":99},{"version":"a6f04472fcf1eafdeed678ac628a23aaa7d027d56e1fbe2c1140ad13b95a5a5f","signature":false,"impliedFormat":99},{"version":"6e0fecd3f3e7ac0ec8d06971ddd01dfe7aa95da8aa65c56db375a28c15134fd9","signature":false,"impliedFormat":99},{"version":"3f65ad2a5d60095fae96c2d46e2fc61c66d1fb65afdc50c04452aa68422aa933","signature":false,"impliedFormat":99},{"version":"0ee692791f05b017c39fe78367768ceb2bd7e5c0ac0fea2298f666b45d190395","signature":false,"impliedFormat":99},{"version":"1ba1caf4981cb5344a7d34d69a9a6c7a0b6fac89cf36e93a6a88b0c53aad951d","signature":false,"impliedFormat":99},{"version":"4682eefaf56f525acfbb3780761496edf9e6671c134ebacc755ee8a046515a98","signature":false,"impliedFormat":99},{"version":"0c32d0c4a2605c3cfee63b60ade3722d6211d0bd7151d5dcb7d19aa185f92e20","signature":false,"impliedFormat":99},{"version":"5a79fc998e1c6841498cd9769ad4b8d212af1fa3ce1be575e244614048fa56f4","signature":false,"impliedFormat":99},{"version":"91049bee4d52e0d6ec9ecec57f9fd444067c11b2d18bfa369a51a78e63565cb2","signature":false,"impliedFormat":99},{"version":"07098c38d1f2300c6d2b62070e3ae3b75ecd3dfe940f88a0019d82e099f96ec5","signature":false,"impliedFormat":99},{"version":"e80d9f821bf1abe950318634b06840e8dad4fc0db46e97427d4a02195b23fbea","signature":false,"impliedFormat":99},{"version":"b3c78b5b1878641bd763116532eb6b03bd32f1d46f349287b87a942ec835c02a","signature":false,"impliedFormat":99},{"version":"399a3acb5e52325ee91ae74c95666f5071e9cdcce29c28b4e6f68f6038972f32","signature":false,"impliedFormat":99},{"version":"99318cb9deeef5b45a497640d4ce5dfa1c06a46d52f69196fccf2f89f7dad92c","signature":false,"impliedFormat":99},{"version":"9ee5f4da6229111e6b48ac5483dae7bd8577a49fd4c25dd7efe76a69dc9ae641","signature":false,"impliedFormat":99},{"version":"f6f27861d673d84dfc9809ab2eeb18d0b40a3997a837928edd7078160a12660c","signature":false,"impliedFormat":99},{"version":"2b4298489804ebe4f90ba2ce7d2f8e4d9b2ed326233bc6cd8fa2d5195d7d1a80","signature":false,"impliedFormat":99},{"version":"cdb35bdd7124a83909c366a75877d899eaeeefbb851c4cbd68b24de3201128c3","signature":false,"impliedFormat":99},{"version":"830123659fac74408548220a59ce6f82a37391db61a22f73d2ee321f9c4bf505","signature":false,"impliedFormat":99},{"version":"adf1fb98edd8782904c6b35e3fc0de9c0814296cf58230a985eaae26e107ed38","signature":false,"impliedFormat":99},{"version":"aee498086404181340c23a2c7737c3f5d3ed2a7f0316da4b94d912ef227fa3a8","signature":false,"impliedFormat":99},{"version":"157b2797f3fdbe4380ceabcf7a6a4cae523c9002256a3ce1cdaa56bb7ae0156e","signature":false,"impliedFormat":99},{"version":"7ff0326ef2a190b9b788b782a9a57112e424c015cf9329266cefba043fb95b65","signature":false,"impliedFormat":99},{"version":"dcb345b68d9e5e49e34f0b6c7439e10eafe626cb440299548684460806db9053","signature":false,"impliedFormat":99},{"version":"424aece36fa94e024a60d4c82771d0657b6ab028949c3b6fdfdd4986a62ef141","signature":false,"impliedFormat":99},{"version":"547f7a5cfbd3b141cce4a784a8cf11f77b1fa0482905cad6c053c6ebed5759d8","signature":false,"impliedFormat":99},{"version":"4de20271e7318991ee1e0f69fff6ce7b5a560f8409f5d4f244e74b044e194ab0","signature":false,"impliedFormat":99},{"version":"0646a2a0d8f7cb8c261e8fdaa3cd52760774f2bf09fad57eb6d322c49e53a791","signature":false,"impliedFormat":99},{"version":"464998e95d1ebe90aa71effabec6a42a77eb8d66a68bb53c8f02f0b0a34fe5ac","signature":false,"impliedFormat":99},{"version":"a0941e0644f91115b011166ef28ca1345cd931ac4b2e92e8314c7ddcef2d8c12","signature":false,"impliedFormat":99},{"version":"2af00fa0c37d8c3856e424b841f0e1bb4431ed0ac4c9e44934bc0cb350037aa8","signature":false,"impliedFormat":99},{"version":"a981ad35e7f6bfe68e26317ada681bc51d09f3abe24603f61f5ff4dd9bcd1d39","signature":false,"impliedFormat":99},{"version":"be2c243908a547a2d35351af3900c9a765ebc40cf8fa97cbcb5bb2d850150991","signature":false,"impliedFormat":99},{"version":"832e04fa9c6949cef6cbf1ffa5ff92f859827533d98f7199810c3654c6a7c82b","signature":false,"impliedFormat":99},{"version":"2a6d709681fbf73106c1daa64360eb0f7563a7f6d7527666d25b22ac5ec3d89b","signature":false,"impliedFormat":99},{"version":"12015d4754cec0afbc26cb1514cfbe246d3836aad746492a9fcc0b571fdff767","signature":false,"impliedFormat":99},{"version":"4a25d16f71ef609569ccbff00768e87325786df27b160f87ab44ddf7e7f603ac","signature":false,"impliedFormat":99},{"version":"a5f9bc96e9c35a8c4afbbd326f46f4ce42def2d053ca65d32b261bfd3462d112","signature":false,"impliedFormat":99},{"version":"c7223f7b2be8859bdd4b5d553c5e02b5d38345c3c828be14701863d1c5c46aa9","signature":false,"impliedFormat":99},{"version":"a778e8948ff336336b4a1b89e38fbc462044b395dc4adad8f7d539ebe51db61d","signature":false,"impliedFormat":99},{"version":"668192f288841913bf9afe0d34afde995d7c8bc63fb4d5e2b584db305cea0aa9","signature":false,"impliedFormat":99},{"version":"68c13200f8a1b994c2911340cee34fe972c121e06ae9e7111b220a387771ad85","signature":false,"impliedFormat":99},{"version":"d21b2c2b65be4881f879d5d97b88353e1e76663bebb58ba5ee9b5565a90e87b6","signature":false,"impliedFormat":99},{"version":"6745194a7f4ae219550989bb9663e04766eac8901e1b7013668bec1f0005f4b7","signature":false,"impliedFormat":99},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"f0ae1ac99c66a4827469b8942101642ae65971e36db438afe67d4985caa31222","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"46b907ed13bd5023adeb5446ad96e9680b1a40d4e4288344d0d0e31d9034d20a","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"ea689c41691ac977c4cf2cfe7fc7de5136851730c9d4dbc97d76eb65df8ee461","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"8d0f0aa989374cc6c7bc141649a9ca7d76b221a39375c8b98b844c3ad8c9b090","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"72c62b406af19eca8080ea63f90f4c907ee5b8348152b75ba106395cd7514f54","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"be3d53a4a6cc2e67e4b4b09c46bffce6282585fe504f77839863c53cb378a47f","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"3199d552cbbbac5a3c6e1499c09acf672ae8c8c8687cf2a3dbfa7c8902cc7054","signature":false,"impliedFormat":1},{"version":"c82377c9ebc4d9788c743b6fba0661d1c34f39a7955476ac780039e120dc41fd","signature":false,"impliedFormat":1},{"version":"e3bf0a5aa199a4fc9f478808c7ffc2aa01411944594c2b305a43ede96e4a521d","signature":false,"impliedFormat":1},{"version":"3b0951ca295694b8d7b8139c1d69c1e6c2085e65fd86c8968eae8224f3bf5bfe","signature":false,"impliedFormat":1},{"version":"f2393e9e894511d174544b3319d5ed107753cc76548e590454024ccf2dedc881","signature":false,"impliedFormat":1},{"version":"83af0534774218e8d8205fb55df878c77e2471708a9d1435778aa69dabc24839","signature":false,"impliedFormat":1},{"version":"0013a72eaf0d971739705e72d2334e90973516c348f3b42a070ea5ec5563f502","signature":false,"impliedFormat":1},{"version":"e7990a14be2e5d20f7c5f3437f77733352c21c6665ad6fb57bf1a02611b88190","signature":false,"impliedFormat":99},{"version":"1ff59a6b0a95f3f5586b17ae4e89faf359e7cb939006a29692af5a9f5f60c345","signature":false},{"version":"ad633a1a426cdbcf6e6e0a4af203552e0ffe544d4e690470baca6a8bc2188204","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ba854883a418fca4343b51cb93718d481770f3b81e978bbf6378a2385264e55c","signature":false,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","signature":false,"impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","signature":false,"impliedFormat":1},{"version":"b2d0630483bf337ef9dac326c3334a245aa4946e9f60f12baf7da5be44beafbb","signature":false,"impliedFormat":1},{"version":"68c0f599345d45a3f72fe7b5a89da23053f17d9c2cd5b2321acabe6e6f7b23b3","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"fd6f0bb5bd5f176b689915806a974cdb12a467bdaa414dc107a62d462eb7ddd5","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"770b54f6069a7364c1095e6ed782cf33e404c53ea2a4d8f58615403328fbd8fc","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a1819d8e80fbf3e8d7acb1deafe67401ccad93d59d6a2416bdfc1a1e74ee7c2b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"6537313aa6bd137e046dd52f48698d4075dd511886f17ee70bc1dda1b9fe1066","signature":false,"impliedFormat":1},{"version":"8ac576b6d6707b07707fd5f7ec7089f768a599a39317ba08c423b8b55e76ca16","signature":false,"impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","signature":false,"impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","signature":false,"impliedFormat":1},{"version":"4ba01987c4b2c4b94a24c0a05bffbd75ab64d9fe8335dc63e9955d6c51a63468","signature":false,"impliedFormat":1},{"version":"916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","signature":false,"impliedFormat":1},{"version":"ed6b820c54de95b2510bb673490d61c7f2187f532a339d8d04981645a918961f","signature":false,"impliedFormat":1},{"version":"4cf58cd73f135e59d2268b4b792623bd8cc7ea887d96498f2a64d550beb930bb","signature":false,"impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","signature":false,"impliedFormat":1},{"version":"003ea9cf8d32a1df04a447de1bf59756bfa366841c1ecc91ad1a6a43da1b9718","signature":false,"impliedFormat":1},{"version":"960a68ced7820108787135bdae5265d2cc4b511b7dcfd5b8f213432a8483daf1","signature":false,"impliedFormat":1},{"version":"7c52a6d05a6e68269e63bc63fad6e869368a141ad23a20e2350c831dc499c5f2","signature":false,"impliedFormat":1},{"version":"2e7ebdc7d8af978c263890bbde991e88d6aa31cc29d46735c9c5f45f0a41243b","signature":false,"impliedFormat":1},{"version":"b57fd1c0a680d220e714b76d83eff51a08670f56efcc5d68abc82f5a2684f0c0","signature":false,"impliedFormat":1},{"version":"8cf121e98669f724256d06bebafec912b92bb042a06d4944f7fb27a56c545109","signature":false,"impliedFormat":1},{"version":"1084565c68b2aed5d6d5cea394799bd688afdf4dc99f4e3615957857c15bb231","signature":false,"impliedFormat":1},{"version":"8baa5d0febc68db886c40bf341e5c90dc215a90cd64552e47e8184be6b7e3358","signature":false,"impliedFormat":1},{"version":"15fe687c59d62741b4494d5e623d497d55eb38966ecf5bea7f36e48fc3fbe15e","signature":false,"impliedFormat":1},{"version":"2c3b8be03577c98530ef9cb1a76e2c812636a871f367e9edf4c5f3ce702b77f8","signature":false,"affectsGlobalScope":true,"impliedFormat":1}],"root":[461],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[187,1],[185,2],[186,3],[188,4],[183,5],[181,2],[184,6],[182,7],[165,2],[167,2],[168,8],[462,2],[221,2],[463,2],[467,9],[473,10],[469,2],[468,2],[471,2],[470,2],[474,2],[475,2],[106,11],[107,11],[108,12],[54,13],[109,14],[110,15],[111,16],[52,2],[112,17],[113,18],[114,19],[115,20],[116,21],[117,22],[118,22],[119,23],[120,24],[121,25],[122,26],[55,2],[53,2],[123,27],[124,28],[125,29],[160,30],[126,31],[127,2],[128,32],[129,33],[131,34],[132,35],[133,36],[134,37],[135,38],[136,39],[137,40],[138,40],[139,41],[140,2],[141,42],[142,43],[144,44],[143,45],[145,46],[146,47],[147,48],[148,49],[149,50],[150,51],[151,52],[152,53],[153,54],[154,55],[155,56],[156,57],[157,58],[56,2],[57,59],[58,2],[59,2],[102,60],[103,61],[104,2],[105,46],[158,62],[159,63],[476,2],[477,2],[478,2],[479,2],[480,9],[481,9],[482,2],[484,64],[487,65],[485,9],[483,9],[486,64],[464,2],[466,66],[488,2],[472,2],[490,67],[489,2],[220,68],[219,2],[130,2],[465,2],[211,2],[213,69],[212,2],[207,70],[205,71],[206,72],[194,73],[195,71],[202,74],[193,75],[198,76],[208,2],[199,77],[204,78],[210,79],[209,80],[192,81],[200,82],[201,83],[196,84],[203,70],[197,85],[216,86],[170,87],[171,88],[174,89],[166,90],[173,91],[169,92],[164,2],[175,93],[176,94],[222,95],[191,2],[326,2],[189,96],[439,97],[277,98],[274,99],[395,100],[275,101],[276,102],[367,103],[390,2],[392,2],[405,104],[348,105],[314,106],[297,107],[391,2],[324,2],[337,2],[358,108],[227,2],[408,109],[410,110],[409,111],[229,2],[342,112],[341,113],[344,114],[343,115],[295,2],[430,2],[411,116],[415,117],[413,118],[232,119],[233,119],[234,2],[355,120],[354,2],[368,121],[284,122],[316,123],[383,124],[437,2],[404,125],[338,126],[438,127],[382,128],[436,2],[225,2],[350,129],[428,130],[433,2],[279,131],[278,132],[374,133],[376,134],[240,135],[379,136],[387,137],[235,138],[388,139],[397,140],[389,141],[242,139],[427,142],[386,143],[385,144],[245,145],[246,2],[282,146],[283,147],[272,148],[273,149],[264,150],[263,151],[281,152],[244,153],[328,2],[236,2],[267,154],[248,2],[252,2],[253,155],[269,156],[400,101],[241,2],[270,2],[403,137],[266,154],[237,137],[402,137],[371,157],[375,158],[265,154],[268,154],[339,159],[398,2],[304,160],[271,140],[401,101],[378,161],[353,2],[346,2],[228,2],[360,162],[359,163],[412,118],[416,164],[414,165],[366,2],[231,166],[429,167],[347,131],[280,168],[364,169],[363,2],[315,170],[299,171],[300,2],[251,172],[351,173],[352,173],[286,174],[287,2],[296,2],[334,175],[254,176],[294,177],[319,178],[249,2],[238,179],[325,2],[259,180],[260,181],[370,182],[434,183],[306,184],[320,185],[417,111],[419,186],[418,186],[312,187],[313,188],[250,2],[224,2],[323,2],[322,189],[372,190],[369,191],[431,192],[432,192],[373,193],[425,189],[302,194],[285,195],[301,194],[303,196],[309,197],[239,133],[362,198],[423,199],[393,200],[335,201],[333,2],[329,202],[357,203],[330,202],[332,204],[331,205],[356,141],[396,206],[394,207],[298,208],[247,2],[305,209],[420,118],[422,164],[421,165],[258,99],[381,210],[424,211],[380,212],[262,213],[426,214],[349,215],[261,216],[255,217],[345,2],[361,218],[310,219],[311,220],[291,2],[327,221],[257,189],[435,2],[256,222],[340,223],[384,2],[226,224],[321,189],[406,225],[365,226],[317,227],[230,2],[318,228],[243,2],[290,2],[288,2],[289,2],[307,2],[308,229],[336,2],[407,230],[293,189],[377,134],[292,231],[399,232],[1,2],[50,2],[51,2],[9,2],[13,2],[12,2],[3,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2],[4,2],[22,2],[23,2],[5,2],[24,2],[28,2],[25,2],[26,2],[27,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[39,2],[36,2],[37,2],[38,2],[40,2],[8,2],[41,2],[46,2],[47,2],[42,2],[43,2],[44,2],[45,2],[2,2],[48,2],[49,2],[11,2],[10,2],[78,233],[90,234],[75,235],[91,236],[100,237],[66,238],[67,239],[65,240],[99,241],[94,242],[98,243],[69,244],[87,245],[68,246],[97,247],[63,248],[64,242],[70,249],[71,2],[77,250],[74,249],[61,251],[101,252],[92,253],[81,254],[80,249],[82,255],[85,256],[79,257],[83,258],[95,241],[72,259],[73,260],[86,261],[62,236],[89,262],[88,249],[76,260],[84,263],[93,2],[60,2],[96,264],[460,265],[162,266],[218,267],[178,268],[163,266],[161,2],[177,269],[217,2],[215,2],[179,2],[214,270],[172,271],[190,272],[180,273],[448,2],[449,2],[442,2],[443,274],[456,275],[457,275],[459,276],[458,275],[455,275],[223,2],[454,277],[450,2],[451,2],[440,2],[441,2],[452,2],[453,274],[444,2],[445,2],[446,2],[447,2],[461,278]],"changeFileSet":[491,492,187,185,186,188,183,181,184,182,165,493,167,168,494,495,496,497,498,462,221,463,467,473,469,468,471,470,474,475,499,500,501,502,503,504,505,506,507,508,509,510,511,106,107,108,54,109,110,111,512,513,514,52,112,113,114,115,116,117,118,119,120,121,122,55,53,123,124,125,160,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,144,143,145,146,147,148,149,150,151,152,153,154,155,156,157,56,57,58,59,102,103,104,105,158,159,476,477,478,479,480,515,516,481,482,484,487,485,483,486,464,466,488,472,490,489,220,219,130,465,517,211,213,212,207,205,206,194,195,202,193,198,208,199,204,210,209,192,200,201,196,203,197,216,170,171,174,166,173,169,164,175,176,222,191,326,189,439,277,274,395,275,276,367,390,392,405,348,314,297,391,324,337,358,227,408,410,409,229,342,341,344,343,295,430,411,415,413,232,233,234,355,354,368,284,316,383,437,404,338,438,382,436,225,350,428,433,279,278,374,376,240,379,387,235,388,397,389,242,427,386,385,245,246,282,283,272,273,264,263,281,244,328,236,267,248,252,253,269,400,241,270,403,266,237,402,371,375,265,268,339,398,304,271,401,378,353,346,228,360,359,412,416,414,366,231,429,347,280,364,363,315,299,300,251,351,352,286,287,296,334,254,294,319,249,238,325,259,260,370,434,306,320,417,419,418,312,313,250,224,323,322,372,369,431,432,373,425,302,285,301,303,309,239,362,423,393,335,333,329,357,330,332,331,356,396,394,298,247,305,420,422,421,258,381,424,380,262,426,349,261,255,345,361,310,311,291,327,257,435,256,340,384,226,321,406,365,317,230,318,243,290,288,289,307,308,336,407,293,377,292,399,1,50,51,9,13,12,3,14,15,16,17,18,19,20,21,4,22,23,5,24,28,25,26,27,29,30,31,6,32,33,34,35,7,39,36,37,38,40,8,41,46,47,42,43,44,45,2,48,49,11,10,78,90,75,91,100,66,67,65,99,94,98,69,87,68,97,63,518,519,64,70,71,77,74,61,101,92,81,80,82,85,79,83,95,72,73,86,62,89,88,76,84,93,60,96,460,162,218,178,163,161,177,217,215,179,214,172,190,180,448,449,442,443,456,457,459,458,455,223,454,450,451,440,441,452,453,444,445,446,447,461],"version":"5.9.3"} \ No newline at end of file +{"fileNames":["./node_modules/typescript/lib/lib.d.ts","./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.float16.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/round-robin-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/buffer/index.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/rolldown/dist/shared/logging-xuho4may.d.mts","./node_modules/@oxc-project/types/types.d.ts","./node_modules/rolldown/dist/shared/binding-cvtkjvyl.d.mts","./node_modules/@rolldown/pluginutils/dist/filter/index.d.mts","./node_modules/@rolldown/pluginutils/dist/index.d.mts","./node_modules/rolldown/dist/shared/define-config-dvsxpnau.d.mts","./node_modules/rolldown/dist/index.d.mts","./node_modules/rolldown/dist/parse-ast-index.d.mts","./node_modules/vite/types/internal/rolluptypecompat.d.ts","./node_modules/rolldown/dist/shared/constructors-xaibwpwj.d.mts","./node_modules/rolldown/dist/plugins-index.d.mts","./node_modules/rolldown/dist/shared/transform-wmu_ci8e.d.mts","./node_modules/rolldown/dist/utils-index.d.mts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/module-runner.d.ts","./node_modules/vite/types/internal/esbuildoptions.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts","./node_modules/@jridgewell/trace-mapping/types/types.d.mts","./node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts","./node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts","./node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts","./node_modules/@jridgewell/gen-mapping/types/types.d.mts","./node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts","./node_modules/@jridgewell/source-map/types/source-map.d.mts","./node_modules/terser/tools/terser.d.ts","./node_modules/vite/types/internal/terseroptions.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.d.ts","./node_modules/vite/types/internal/lightningcssoptions.d.ts","./node_modules/vite/types/internal/csspreprocessoroptions.d.ts","./node_modules/rolldown/dist/filter-index.d.mts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@vitejs/plugin-react/types/optionaltypes.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/rollup/dist/rollup.d.ts","./node_modules/workbox-build/build/lib/copy-workbox-libraries.d.ts","./node_modules/type-fest/source/primitive.d.ts","./node_modules/type-fest/source/find-global-type.d.ts","./node_modules/type-fest/source/typed-array.d.ts","./node_modules/type-fest/source/basic.d.ts","./node_modules/type-fest/source/json-value.d.ts","./node_modules/type-fest/source/characters.d.ts","./node_modules/type-fest/source/union-to-intersection.d.ts","./node_modules/type-fest/source/keys-of-union.d.ts","./node_modules/type-fest/source/distributed-omit.d.ts","./node_modules/type-fest/source/distributed-pick.d.ts","./node_modules/type-fest/source/empty-object.d.ts","./node_modules/type-fest/source/if-empty-object.d.ts","./node_modules/type-fest/source/is-any.d.ts","./node_modules/type-fest/source/is-optional-key-of.d.ts","./node_modules/type-fest/source/optional-keys-of.d.ts","./node_modules/type-fest/source/required-keys-of.d.ts","./node_modules/type-fest/source/has-required-keys.d.ts","./node_modules/type-fest/source/is-never.d.ts","./node_modules/type-fest/source/if.d.ts","./node_modules/type-fest/source/unknown-array.d.ts","./node_modules/type-fest/source/internal/type.d.ts","./node_modules/type-fest/source/internal/array.d.ts","./node_modules/type-fest/source/internal/characters.d.ts","./node_modules/type-fest/source/simplify.d.ts","./node_modules/type-fest/source/is-equal.d.ts","./node_modules/type-fest/source/omit-index-signature.d.ts","./node_modules/type-fest/source/pick-index-signature.d.ts","./node_modules/type-fest/source/merge.d.ts","./node_modules/type-fest/source/is-float.d.ts","./node_modules/type-fest/source/is-integer.d.ts","./node_modules/type-fest/source/numeric.d.ts","./node_modules/type-fest/source/string-to-number.d.ts","./node_modules/type-fest/source/tuple-of.d.ts","./node_modules/type-fest/source/trim.d.ts","./node_modules/type-fest/source/some-extend.d.ts","./node_modules/type-fest/source/or-all.d.ts","./node_modules/type-fest/source/or.d.ts","./node_modules/type-fest/source/string-to-array.d.ts","./node_modules/type-fest/source/string-length.d.ts","./node_modules/type-fest/source/internal/string.d.ts","./node_modules/type-fest/source/internal/object.d.ts","./node_modules/type-fest/source/is-string-literal.d.ts","./node_modules/type-fest/source/is-numeric-literal.d.ts","./node_modules/type-fest/source/is-boolean-literal.d.ts","./node_modules/type-fest/source/is-symbol-literal.d.ts","./node_modules/type-fest/source/is-literal.d.ts","./node_modules/type-fest/source/is-null.d.ts","./node_modules/type-fest/source/is-unknown.d.ts","./node_modules/type-fest/source/internal/keys.d.ts","./node_modules/type-fest/source/internal/numeric.d.ts","./node_modules/type-fest/source/all-extend.d.ts","./node_modules/type-fest/source/and-all.d.ts","./node_modules/type-fest/source/and.d.ts","./node_modules/type-fest/source/absolute.d.ts","./node_modules/type-fest/source/greater-than.d.ts","./node_modules/type-fest/source/greater-than-or-equal.d.ts","./node_modules/type-fest/source/less-than.d.ts","./node_modules/type-fest/source/internal/tuple.d.ts","./node_modules/type-fest/source/internal/enforce-optional.d.ts","./node_modules/type-fest/source/internal/index.d.ts","./node_modules/type-fest/source/except.d.ts","./node_modules/type-fest/source/require-at-least-one.d.ts","./node_modules/type-fest/source/non-empty-object.d.ts","./node_modules/type-fest/source/non-empty-string.d.ts","./node_modules/type-fest/source/unknown-record.d.ts","./node_modules/type-fest/source/unknown-set.d.ts","./node_modules/type-fest/source/unknown-map.d.ts","./node_modules/type-fest/source/tagged-union.d.ts","./node_modules/type-fest/source/writable.d.ts","./node_modules/type-fest/source/writable-deep.d.ts","./node_modules/type-fest/source/object-merge.d.ts","./node_modules/type-fest/source/conditional-simplify-deep.d.ts","./node_modules/type-fest/source/non-empty-tuple.d.ts","./node_modules/type-fest/source/array-tail.d.ts","./node_modules/type-fest/source/simplify-deep.d.ts","./node_modules/type-fest/source/merge-deep.d.ts","./node_modules/type-fest/source/merge-exclusive.d.ts","./node_modules/type-fest/source/require-exactly-one.d.ts","./node_modules/type-fest/source/require-all-or-none.d.ts","./node_modules/type-fest/source/require-one-or-none.d.ts","./node_modules/type-fest/source/is-union.d.ts","./node_modules/type-fest/source/single-key-object.d.ts","./node_modules/type-fest/source/partial-deep.d.ts","./node_modules/type-fest/source/unwrap-partial.d.ts","./node_modules/type-fest/source/unwrap-required.d.ts","./node_modules/type-fest/source/required-deep.d.ts","./node_modules/type-fest/source/subtract.d.ts","./node_modules/type-fest/source/sum.d.ts","./node_modules/type-fest/source/paths.d.ts","./node_modules/type-fest/source/pick-deep.d.ts","./node_modules/type-fest/source/array-splice.d.ts","./node_modules/type-fest/source/literal-union.d.ts","./node_modules/type-fest/source/exclude-exactly.d.ts","./node_modules/type-fest/source/union-member.d.ts","./node_modules/type-fest/source/union-to-tuple.d.ts","./node_modules/type-fest/source/omit-deep.d.ts","./node_modules/type-fest/source/partial-on-undefined-deep.d.ts","./node_modules/type-fest/source/undefined-on-partial-deep.d.ts","./node_modules/type-fest/source/readonly-deep.d.ts","./node_modules/type-fest/source/promisable.d.ts","./node_modules/type-fest/source/arrayable.d.ts","./node_modules/type-fest/source/optional.d.ts","./node_modules/tagged-tag/index.d.ts","./node_modules/type-fest/source/tagged.d.ts","./node_modules/type-fest/source/invariant-of.d.ts","./node_modules/type-fest/source/set-optional.d.ts","./node_modules/type-fest/source/set-readonly.d.ts","./node_modules/type-fest/source/set-required.d.ts","./node_modules/type-fest/source/set-required-deep.d.ts","./node_modules/type-fest/source/set-non-nullable.d.ts","./node_modules/type-fest/source/non-nullable-deep.d.ts","./node_modules/type-fest/source/set-non-nullable-deep.d.ts","./node_modules/type-fest/source/value-of.d.ts","./node_modules/type-fest/source/async-return-type.d.ts","./node_modules/type-fest/source/extends-strict.d.ts","./node_modules/type-fest/source/is-tuple.d.ts","./node_modules/type-fest/source/tuple-to-object.d.ts","./node_modules/type-fest/source/conditional-keys.d.ts","./node_modules/type-fest/source/conditional-except.d.ts","./node_modules/type-fest/source/conditional-pick.d.ts","./node_modules/type-fest/source/conditional-pick-deep.d.ts","./node_modules/type-fest/source/stringified.d.ts","./node_modules/type-fest/source/join.d.ts","./node_modules/type-fest/source/less-than-or-equal.d.ts","./node_modules/type-fest/source/array-slice.d.ts","./node_modules/type-fest/source/string-slice.d.ts","./node_modules/type-fest/source/fixed-length-array.d.ts","./node_modules/type-fest/source/multidimensional-array.d.ts","./node_modules/type-fest/source/multidimensional-readonly-array.d.ts","./node_modules/type-fest/source/iterable-element.d.ts","./node_modules/type-fest/source/entry.d.ts","./node_modules/type-fest/source/entries.d.ts","./node_modules/type-fest/source/set-return-type.d.ts","./node_modules/type-fest/source/set-parameter-type.d.ts","./node_modules/type-fest/source/asyncify.d.ts","./node_modules/type-fest/source/jsonify.d.ts","./node_modules/type-fest/source/jsonifiable.d.ts","./node_modules/type-fest/source/structured-cloneable.d.ts","./node_modules/type-fest/source/schema.d.ts","./node_modules/type-fest/source/literal-to-primitive.d.ts","./node_modules/type-fest/source/literal-to-primitive-deep.d.ts","./node_modules/type-fest/source/union-max.d.ts","./node_modules/type-fest/source/key-as-string.d.ts","./node_modules/type-fest/source/array-element.d.ts","./node_modules/type-fest/source/exact.d.ts","./node_modules/type-fest/source/readonly-tuple.d.ts","./node_modules/type-fest/source/override-properties.d.ts","./node_modules/type-fest/source/is-readonly-key-of.d.ts","./node_modules/type-fest/source/readonly-keys-of.d.ts","./node_modules/type-fest/source/rename-keys.d.ts","./node_modules/type-fest/source/has-optional-keys.d.ts","./node_modules/type-fest/source/is-required-key-of.d.ts","./node_modules/type-fest/source/has-readonly-keys.d.ts","./node_modules/type-fest/source/writable-keys-of.d.ts","./node_modules/type-fest/source/is-writable-key-of.d.ts","./node_modules/type-fest/source/has-writable-keys.d.ts","./node_modules/type-fest/source/spread.d.ts","./node_modules/type-fest/source/split-on-rest-element.d.ts","./node_modules/type-fest/source/extract-rest-element.d.ts","./node_modules/type-fest/source/exclude-rest-element.d.ts","./node_modules/type-fest/source/tuple-to-union.d.ts","./node_modules/type-fest/source/int-range.d.ts","./node_modules/type-fest/source/int-closed-range.d.ts","./node_modules/type-fest/source/if-any.d.ts","./node_modules/type-fest/source/if-never.d.ts","./node_modules/type-fest/source/if-unknown.d.ts","./node_modules/type-fest/source/array-indices.d.ts","./node_modules/type-fest/source/array-values.d.ts","./node_modules/type-fest/source/array-length.d.ts","./node_modules/type-fest/source/set-field-type.d.ts","./node_modules/type-fest/source/shared-union-fields.d.ts","./node_modules/type-fest/source/all-union-fields.d.ts","./node_modules/type-fest/source/shared-union-fields-deep.d.ts","./node_modules/type-fest/source/if-null.d.ts","./node_modules/type-fest/source/is-undefined.d.ts","./node_modules/type-fest/source/xor.d.ts","./node_modules/type-fest/source/is-lowercase.d.ts","./node_modules/type-fest/source/is-uppercase.d.ts","./node_modules/type-fest/source/is-optional.d.ts","./node_modules/type-fest/source/is-nullable.d.ts","./node_modules/type-fest/source/exclusify-union.d.ts","./node_modules/type-fest/source/array-reverse.d.ts","./node_modules/type-fest/source/union-length.d.ts","./node_modules/type-fest/source/words.d.ts","./node_modules/type-fest/source/camel-case.d.ts","./node_modules/type-fest/source/camel-cased-properties.d.ts","./node_modules/type-fest/source/camel-cased-properties-deep.d.ts","./node_modules/type-fest/source/delimiter-case.d.ts","./node_modules/type-fest/source/kebab-case.d.ts","./node_modules/type-fest/source/delimiter-cased-properties.d.ts","./node_modules/type-fest/source/kebab-cased-properties.d.ts","./node_modules/type-fest/source/delimiter-cased-properties-deep.d.ts","./node_modules/type-fest/source/kebab-cased-properties-deep.d.ts","./node_modules/type-fest/source/pascal-case.d.ts","./node_modules/type-fest/source/pascal-cased-properties.d.ts","./node_modules/type-fest/source/pascal-cased-properties-deep.d.ts","./node_modules/type-fest/source/snake-case.d.ts","./node_modules/type-fest/source/snake-cased-properties.d.ts","./node_modules/type-fest/source/snake-cased-properties-deep.d.ts","./node_modules/type-fest/source/screaming-snake-case.d.ts","./node_modules/type-fest/source/split.d.ts","./node_modules/type-fest/source/replace.d.ts","./node_modules/type-fest/source/string-repeat.d.ts","./node_modules/type-fest/source/includes.d.ts","./node_modules/type-fest/source/get.d.ts","./node_modules/type-fest/source/last-array-element.d.ts","./node_modules/type-fest/source/conditional-simplify.d.ts","./node_modules/type-fest/source/remove-prefix.d.ts","./node_modules/type-fest/source/remove-suffix.d.ts","./node_modules/type-fest/source/global-this.d.ts","./node_modules/type-fest/source/package-json.d.ts","./node_modules/type-fest/source/tsconfig-json.d.ts","./node_modules/type-fest/source/extract-strict.d.ts","./node_modules/type-fest/source/exclude-strict.d.ts","./node_modules/type-fest/source/extract-exactly.d.ts","./node_modules/type-fest/index.d.ts","./node_modules/workbox-core/_version.d.ts","./node_modules/workbox-core/types.d.ts","./node_modules/workbox-broadcast-update/_version.d.ts","./node_modules/workbox-broadcast-update/broadcastcacheupdate.d.ts","./node_modules/workbox-google-analytics/_version.d.ts","./node_modules/workbox-google-analytics/initialize.d.ts","./node_modules/workbox-routing/_version.d.ts","./node_modules/workbox-routing/utils/constants.d.ts","./node_modules/workbox-background-sync/_version.d.ts","./node_modules/workbox-background-sync/queue.d.ts","./node_modules/workbox-cacheable-response/_version.d.ts","./node_modules/workbox-cacheable-response/cacheableresponse.d.ts","./node_modules/workbox-expiration/_version.d.ts","./node_modules/workbox-expiration/expirationplugin.d.ts","./node_modules/workbox-build/build/types.d.ts","./node_modules/workbox-build/build/lib/cdn-utils.d.ts","./node_modules/workbox-build/build/generate-sw.d.ts","./node_modules/workbox-build/build/get-manifest.d.ts","./node_modules/workbox-build/build/inject-manifest.d.ts","./node_modules/workbox-build/build/index.d.ts","./node_modules/vite-plugin-pwa/dist/index.d.ts","./vite.config.ts","./node_modules/@types/emscripten/index.d.ts","./node_modules/@types/file-saver/index.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/@types/hoist-non-react-statics/index.d.ts","./node_modules/@types/jquery/jquerystatic.d.ts","./node_modules/@types/jquery/jquery.d.ts","./node_modules/@types/jquery/misc.d.ts","./node_modules/@types/jquery/legacy.d.ts","./node_modules/@types/sizzle/index.d.ts","./node_modules/@types/jquery/index.d.ts","./node_modules/@types/json-schema/index.d.ts","./node_modules/@types/json5/index.d.ts","./node_modules/@types/pako/index.d.ts","./node_modules/@types/parse-json/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/raf/index.d.ts","./node_modules/@types/react-dom/index.d.ts","./node_modules/@types/react-modal/index.d.ts","./node_modules/@types/react-transition-group/config.d.ts","./node_modules/@types/react-transition-group/transition.d.ts","./node_modules/@types/react-transition-group/csstransition.d.ts","./node_modules/@types/react-transition-group/switchtransition.d.ts","./node_modules/@types/react-transition-group/transitiongroup.d.ts","./node_modules/@types/react-transition-group/index.d.ts","./node_modules/@types/resolve/index.d.ts","./node_modules/@types/trusted-types/lib/index.d.ts","./node_modules/@types/trusted-types/index.d.ts","./node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@rolldown/plugin-babel/dist/index.d.mts","./node_modules/@types/babel__core/index.d.ts","./node_modules/@types/babel__generator/index.d.ts","./node_modules/@types/babel__standalone/index.d.ts","./node_modules/@types/babel__template/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/lodash/common/array.d.ts","./node_modules/@types/lodash/common/collection.d.ts","./node_modules/@types/lodash/common/common.d.ts","./node_modules/@types/lodash/common/date.d.ts","./node_modules/@types/lodash/common/function.d.ts","./node_modules/@types/lodash/common/lang.d.ts","./node_modules/@types/lodash/common/math.d.ts","./node_modules/@types/lodash/common/number.d.ts","./node_modules/@types/lodash/common/object.d.ts","./node_modules/@types/lodash/common/seq.d.ts","./node_modules/@types/lodash/common/string.d.ts","./node_modules/@types/lodash/common/util.d.ts","./node_modules/@types/lodash/index.d.ts","./node_modules/@types/node/compatibility/disposable.d.ts","./node_modules/@types/node/compatibility/index.d.ts","./node_modules/@types/node/compatibility/indexable.d.ts","./node_modules/@types/react-helmet/index.d.ts","./node_modules/@types/react-highlight-words/index.d.ts","./node_modules/esbuild/lib/main.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/filereader.d.ts"],"fileIdsList":[[54,109,126,127,184,186],[54,109,126,127],[54,109,126,127,185],[54,109,126,127,184,187],[54,109,126,127,182,184],[54,109,126,127,181,182,183],[54,109,126,127,181,184],[54,109,126,127,167],[54,109,126,127,466],[54,109,126,127,468,469,470,471,472],[54,106,107,109,126,127],[54,108,109,126,127],[109,126,127],[54,109,114,126,127,145],[54,109,110,115,120,126,127,129,142,153],[54,109,110,111,120,126,127,129],[54,109,112,126,127,154],[54,109,113,114,121,126,127,131],[54,109,114,126,127,142,150],[54,109,115,117,120,126,127,129],[54,108,109,116,126,127],[54,109,117,118,126,127],[54,109,119,120,126,127],[54,108,109,120,126,127],[54,109,120,121,122,126,127,142,153],[54,109,120,121,122,126,127,137,142,145],[54,101,109,117,120,123,126,127,129,142,153],[54,109,120,121,123,124,126,127,129,142,150,153],[54,109,123,125,126,127,142,150,153],[52,53,54,55,56,57,58,59,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],[54,109,120,126,127],[54,109,126,127,128,153],[54,109,117,120,126,127,129,142],[54,109,126,127,131],[54,109,126,127,132],[54,108,109,126,127,133],[54,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159],[54,109,126,127,135],[54,109,126,127,136],[54,109,120,126,127,137,138],[54,109,126,127,137,139,154,156],[54,109,121,126,127],[54,109,120,126,127,142,143,145],[54,109,126,127,144,145],[54,109,126,127,142,143],[54,109,126,127,145],[54,109,126,127,146],[54,106,109,126,127,142,147,153],[54,109,120,126,127,148,149],[54,109,126,127,148,149],[54,109,114,126,127,129,142,150],[54,109,126,127,151],[54,109,126,127,129,152],[54,109,123,126,127,136,153],[54,109,114,126,127,154],[54,109,126,127,142,155],[54,109,126,127,128,156],[54,109,126,127,157],[54,109,114,126,127],[54,101,109,126,127],[54,109,126,127,158],[54,101,109,120,122,126,127,133,142,145,153,155,156,158],[54,109,126,127,142,159],[54,109,126,127,466,483],[54,109,126,127,482,483,484,485,486],[54,109,126,127,464,465],[54,109,126,127,489],[54,109,126,127,218,219],[54,109,126,127,211,212],[54,109,126,127,206],[54,109,126,127,204,206],[54,109,126,127,195,203,204,205,207,209],[54,109,126,127,193],[54,109,126,127,196,201,206,209],[54,109,126,127,192,209],[54,109,126,127,196,197,200,201,202,209],[54,109,126,127,196,197,198,200,201,209],[54,109,126,127,193,194,195,196,197,201,202,203,205,206,207,209],[54,109,126,127,209],[54,109,126,127,191,193,194,195,196,197,198,200,201,202,203,204,205,206,207,208],[54,109,126,127,191,209],[54,109,126,127,196,198,199,201,202,209],[54,109,126,127,200,209],[54,109,126,127,201,202,206,209],[54,109,126,127,194,204],[54,109,126,127,169],[54,109,126,127,164,166,169],[54,109,126,127,165,166],[54,109,126,127,166,169,173],[54,109,126,127,165],[54,109,126,127,166,169],[54,109,126,127,164,165,166,168],[54,109,126,127,164,166],[54,109,126,127,165,166,175],[54,109,126,127,221,222],[54,109,126,127,188],[54,109,126,127,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,265,266,267,268,269,270,271,274,275,276,277,278,279,280,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438],[54,109,126,127,255],[54,109,126,127,236,241,243,244,245,260,264],[54,109,126,127,231,243,247,283,394],[54,109,126,127,274],[54,109,126,127,275],[54,109,126,127,243],[54,109,126,127,242,243,244,245],[54,109,126,127,241,248,254,276,278,279,283,311,314,347],[54,109,126,127,243,256,279,283,310],[54,109,126,127,242,243,283],[54,109,126,127,356],[54,109,126,127,283,407],[54,109,126,127,243,283,408],[54,109,126,127,283,408],[54,109,126,127,284,341],[54,109,126,127,243,244,338,340],[54,109,126,127,234,248,283,288,295,342],[54,109,126,127,241,341],[54,109,126,127,251,265,283,407],[54,109,126,127,243,283,407,411],[54,109,126,127,283,407,411],[54,109,126,127,231],[54,109,126,127,354],[54,109,126,127,224,231,248,271,283,367],[54,109,126,127,248,283],[54,109,126,127,236,241,242,244,248],[54,109,126,127,243,244,245,381],[54,109,126,127,231,242,244,247,271],[54,109,126,127,236,241,259,260,264,271,276],[54,109,126,127,236,242,244,248],[54,109,126,127,243,381],[54,109,126,127,256,284],[54,109,126,127,229,283,312,315,366,424],[54,109,126,127,278],[54,109,126,127,248,254,260,276,277,283],[54,109,126,127,238],[54,109,126,127,372],[54,109,126,127,239],[54,109,126,127,377],[54,109,126,127,236],[54,109,126,127,234],[54,109,126,127,241],[54,109,126,127,270],[54,109,126,127,271],[54,109,126,127,248],[54,109,126,127,311,385],[54,109,126,127,243,254,256,277,283,310],[54,109,126,127,241,242,243,244],[54,109,126,127,247],[54,109,126,127,244,245,246,263,264,272,273,281,282],[54,109,126,127,236,263,269,271],[54,109,126,127,243,244,254],[54,109,126,127,224,231,236,239,241,242,244,247,248,251,255,263,272],[54,109,126,127,246,256,257,262],[54,109,126,127,243,254,278,280],[54,109,126,127,224,236,241,243],[54,109,126,127,244,264],[54,109,126,127,252,254,283],[54,109,126,127,241,244,264,265,266,267,268],[54,109,126,127,236,248],[54,109,126,127,236,237,244],[54,109,126,127,236,241,242,243,283],[54,109,126,127,241,248],[54,109,126,127,236,244,371],[54,109,126,127,228],[54,109,126,127,226,228,234,236,241,243,254,271,283],[54,109,126,127,283,407,411,415],[54,109,126,127,283,407,411,413],[54,109,126,127,230],[54,109,126,127,242,243,244,381],[54,109,126,127,279],[54,109,126,127,249,363],[54,109,126,127,224],[54,109,126,127,243,249,250,251,283,288,295,296,297,298],[54,109,126,127,242,247,248,249,250],[54,109,126,127,248,310],[54,109,126,127,240,285],[54,109,126,127,241,244,247],[54,109,126,127,252,253],[54,109,126,127,237,239,241,242,244,247,249,250,264],[54,109,126,127,241,243,248,283,298,312,314,315,318],[54,109,126,127,237],[54,109,126,127,258],[54,109,126,127,259],[54,109,126,127,251],[54,109,126,127,228,315],[54,109,126,127,241,283],[54,109,126,127,242,251,271,283],[54,109,126,127,283,408,417],[54,109,126,127,236,241,243,276,278,283,311],[54,109,126,127,230,241,243,247,256,283,298,312],[54,109,126,127,283],[54,109,126,127,371],[54,109,126,127,256],[54,109,126,127,241,242,244,260,264,265],[54,109,126,127,238,239,244,249,269,330,331,372],[54,109,126,127,236,241,242,283],[54,109,126,127,236,241,242,283,284],[54,109,126,127,236,241,242,283,301],[54,109,126,127,241,247,283],[54,109,126,127,236,238,243,244,247,264,271],[54,109,126,127,283,407,411,420],[54,109,126,127,247,283],[54,109,126,127,236,243,247,255,283,312,318,333,334],[54,109,126,127,247,283,284],[54,109,126,127,243,271,283],[54,109,126,127,236,243,255,283,298,309,312,318,331],[54,109,126,127,242,243,247,283,284],[54,109,126,127,241,243,283,365],[54,109,126,127,241,243,247,283,304],[54,109,126,127,243,283,295],[54,109,126,127,234,242,304],[54,109,126,127,242,243,244,245,264],[54,109,126,127,260,265,276,283],[54,109,126,127,239,247],[54,109,126,127,261],[54,109,126,127,229,254,266],[54,109,126,127,261,346,348],[54,109,126,127,244,260,264,265],[54,109,126,127,244,254],[54,109,126,127,225,226],[54,109,126,127,254,256,277,280,283],[54,109,126,127,254,256,277,283,310],[54,109,126,127,326],[54,109,126,127,229,242,243,244,254],[54,109,126,127,236,242,243,339],[54,109,126,127,225],[54,109,126,127,318],[54,109,126,127,241,243,244,254],[54,109,126,127,230,241],[54,109,126,127,241,243,316,317],[54,109,126,127,244],[54,109,126,127,283,400,401],[54,109,126,127,247,284],[54,109,126,127,244,260,276],[54,66,69,72,73,109,126,127,153],[54,69,109,126,127,142,153],[54,69,73,109,126,127,153],[54,109,126,127,142],[54,63,109,126,127],[54,67,109,126,127],[54,65,66,69,109,126,127,153],[54,109,126,127,129,150],[54,109,126,127,160],[54,63,109,126,127,160],[54,65,69,109,126,127,129,153],[54,60,61,62,64,68,109,120,126,127,142,153],[54,69,78,86,109,126,127],[54,61,67,109,126,127],[54,69,95,96,109,126,127],[54,61,64,69,109,126,127,145,153,160],[54,69,109,126,127],[54,65,69,109,126,127,153],[54,60,109,126,127],[54,63,64,65,67,68,69,70,71,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,96,97,98,99,100,109,126,127],[54,69,88,91,109,117,126,127],[54,69,78,79,80,109,126,127],[54,67,69,79,81,109,126,127],[54,68,109,126,127],[54,61,63,69,109,126,127],[54,69,73,79,81,109,126,127],[54,73,109,126,127],[54,67,69,72,109,126,127,153],[54,61,65,69,78,109,126,127],[54,69,88,109,126,127],[54,81,109,126,127],[54,63,69,95,109,126,127,145,158,160],[54,109,126,127,218,222,459],[54,109,126,127,161],[54,109,120,121,123,124,125,126,127,129,142,150,153,159,160,161,162,163,170,171,172,174,176,178,179,180,190,210,214,215,216,217,218],[54,109,126,127,161,162,163,177],[54,109,126,127,163],[54,109,126,127,213],[54,109,126,127,170,180,218],[54,109,126,127,189],[54,109,126,127,170,218],[54,109,126,127,441],[54,109,126,127,454],[54,109,126,127,223,454,455,456,457,458],[54,109,126,127,439,441,443,445,447,449,451,453],[54,109,126,127,132,218,220,460]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","signature":false,"impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","signature":false,"impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","signature":false,"impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","signature":false,"impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","signature":false,"impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","signature":false,"impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","signature":false,"impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"196cb558a13d4533a5163286f30b0509ce0210e4b316c56c38d4c0fd2fb38405","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","signature":false,"impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","signature":false,"impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","signature":false,"impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","signature":false,"impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","signature":false,"impliedFormat":1},{"version":"074de5b2fdead0165a2757e3aaef20f27a6347b1c36adea27d51456795b37682","signature":false,"impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","signature":false,"impliedFormat":1},{"version":"24371e69a38fc33e268d4a8716dbcda430d6c2c414a99ff9669239c4b8f40dea","signature":false,"impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","signature":false,"impliedFormat":1},{"version":"3e11fce78ad8c0e1d1db4ba5f0652285509be3acdd519529bc8fcef85f7dafd9","signature":false,"impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","signature":false,"impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","signature":false,"impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","signature":false,"impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","signature":false,"impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","signature":false,"impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","signature":false,"impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","signature":false,"impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","signature":false,"impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","signature":false,"impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","signature":false,"impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","signature":false,"impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","signature":false,"impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","signature":false,"impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","signature":false,"impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","signature":false,"impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","signature":false,"impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","signature":false,"impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","signature":false,"impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","signature":false,"impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","signature":false,"impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","signature":false,"impliedFormat":1},{"version":"c63b9ada8c72f95aac5db92aea07e5e87ec810353cdf63b2d78f49a58662cf6c","signature":false,"impliedFormat":1},{"version":"1cc2a09e1a61a5222d4174ab358a9f9de5e906afe79dbf7363d871a7edda3955","signature":false,"impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","signature":false,"impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","signature":false,"impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","signature":false,"impliedFormat":1},{"version":"b64d4d1c5f877f9c666e98e833f0205edb9384acc46e98a1fef344f64d6aba44","signature":false,"impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","signature":false,"impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","signature":false,"impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","signature":false,"impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","signature":false,"impliedFormat":1},{"version":"c9381908473a1c92cb8c516b184e75f4d226dad95c3a85a5af35f670064d9a2f","signature":false,"impliedFormat":1},{"version":"f724236417941ea77ec8d38c6b7021f5fb7f8521c7f8c1538e87661f2c6a0774","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","signature":false,"impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","signature":false,"impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","signature":false,"impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c06b2652ffeb89afd0f1c52c165ced77032f9cd09bc481153fbd6b5504c69494","signature":false,"impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","signature":false,"impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","signature":false,"impliedFormat":1},{"version":"612422d5ba6b4a5c4537f423e9199645468ad80a689801da63ab7edb43f7b835","signature":false,"impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","signature":false,"impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","signature":false,"impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","signature":false,"impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","signature":false,"impliedFormat":1},{"version":"1cbdcdef62bf42688e5d825898c3a59458466c48ad8a4ef3f88d8a1cd04310d6","signature":false,"impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"dc8c6f5322961b56d9906601b20798725df60baeab45ec014fba9f795d5596fd","signature":false,"impliedFormat":1},{"version":"67e435fa530778903f5525904e6645b7368eb5ac622055febd19bb399e328ccd","signature":false,"impliedFormat":1},{"version":"060d305fe4494d8cb2b99d620928d369d1ee55c1645f5e729a2aca07d0f108cb","signature":false,"impliedFormat":1},{"version":"5b22bf96be06fca1e134e3a85351ffe7aa2edf0c9a5bd175b7239ee48186dbf5","signature":false,"impliedFormat":1},{"version":"0c50296ee73dae94efc3f0da4936b1146ca6ce2217acfabb44c19c9a33fa30e5","signature":false,"impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","signature":false,"impliedFormat":1},{"version":"5b48cc670da1dfe926036deaf349085caf822def460a6d50de23f1cc12a5f8cd","signature":false,"impliedFormat":1},{"version":"418f34087d52d001e2688a6282a5de9bf583ff771ed5546af0ae36e4f60a458b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"036c9a03b01281f99f8251fdc7088e894566be44b3f139a9d48501a978b611df","signature":false,"impliedFormat":1},{"version":"4967529644e391115ca5592184d4b63980569adf60ee685f968fd59ab1557188","signature":false,"impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","signature":false,"impliedFormat":1},{"version":"04bf1aa481d1adfb16d93d76e44ce71c51c8ef68039d849926551199489637f6","signature":false,"impliedFormat":1},{"version":"2c9adcc85574b002c9a6311ff2141055769e0071856ec979d92ff989042b1f1b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"1569ad5969ae0de6ad0bdfaaab0dabcd8878bc3147ab096206eed2019bb84e7f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","signature":false,"impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","signature":false,"impliedFormat":1},{"version":"42a350ce4f6f291198dc89833afcc4fd078248b4e1fdc9d05c846c66a670f710","signature":false,"impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","signature":false,"impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","signature":false,"impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","signature":false,"impliedFormat":1},{"version":"b03754211d839cdc48979996525a32eb62130682c2f9801dfc2f25533ce7238d","signature":false,"impliedFormat":1},{"version":"3910dab597c40e173bf0e0d419d3ce9682c54ebf6ae84849f9b829b1451a17ec","signature":false,"impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","signature":false,"impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","signature":false,"impliedFormat":1},{"version":"486c074a5c0f2254345c0d1c9540380f5463999e42d7e1a159305ea823d3c4b1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","signature":false,"impliedFormat":1},{"version":"f4d99ac08c4097a3cc202bec2c7198c19dcdd80d4ec6c62d1356b10d03b119d5","signature":false,"impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","signature":false,"impliedFormat":1},{"version":"ca77e83162eccec633513723d178d4203bdd5465297267696be08013bab0aa1d","signature":false,"impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","signature":false,"impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","signature":false,"impliedFormat":1},{"version":"f61c153d5abbe4381a1fba0d41be0babdd5de51c626ee7ff14cede14dedc33f1","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"7803171d745ab18e77f16bf82e742268ff72b7f7382ab191e4277527580c2d89","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"0e9ba65cd3d6d194bdda7343f9f2917f7144ead96a924ca58d1a14919992dc20","signature":false,"impliedFormat":1},{"version":"255d948f87f24ffd57bcb2fdf95792fd418a2e1f712a98cf2cce88744d75085c","signature":false,"impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","signature":false,"impliedFormat":1},{"version":"0b0dfb2d3e8420ead06f6a11acfb02ddcec42257e6ce391993e895ba0af86de4","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"bfd3b3c21a56104693183942e221c1896ee23bcb8f8d91ab0b941f7b32985411","signature":false,"impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","signature":false,"impliedFormat":1},{"version":"64ccfe7dcbcad9b8a661a042ecb2309e41b9ab957f7b8cd070b05583a7821a13","signature":false,"impliedFormat":99},{"version":"c911b3bd083dade8fdb7775261aa25feae695d562a3b11209650b47be90043fc","signature":false,"impliedFormat":99},{"version":"98580d6ed68c9cee7f956685abbb6502d70bd92f48175ac807ae6c5e254fadc6","signature":false,"impliedFormat":99},{"version":"fc5ad2ec8b130a59612c5cfcc2cdca9bcdd80851adb8acdb004aaeff00cc978d","signature":false,"impliedFormat":99},{"version":"5c62f97efe6b4b10e8597cb6252fcf772eeea3d3e9b93e97d1ce3e24980042da","signature":false,"impliedFormat":99},{"version":"aacf8a6955a6e97c902e3ad9644afc21544795c0cd35327e063814a564f4d4b4","signature":false,"impliedFormat":99},{"version":"638a22f8e16b31c37297483fa38cfc760ed309b16d4c606a7b4532f4394c9c0c","signature":false,"impliedFormat":99},{"version":"9394183f4c37b8591156f32e41223c9e0dd211eefe7499155d4cdf15268eaea8","signature":false,"impliedFormat":99},{"version":"17e7af5056ebd299c68c82bb69f9cbcf0a1a0cfddcdaead376bb7f3fb630f134","signature":false,"impliedFormat":99},{"version":"f3816d074e78163d7972a8782fc9eb5ce51ee5c0bd44d1faf646827e39ae3288","signature":false,"impliedFormat":99},{"version":"c0042ca8c8cbda9e978c4558933c47c812ac018e51d64ae8ff51e641672bed46","signature":false,"impliedFormat":99},{"version":"7d3e062a778b8f5ea4f0cac7e925e31f88e6739812ebc5f827474324a4048f14","signature":false,"impliedFormat":99},{"version":"7c5bafb0d9096c4de8488335d05920b35e993f698fb9101cb78956aa956f9648","signature":false,"impliedFormat":99},{"version":"670f2ad250e21d8e3a9cf0bd2c2352f59c132bb6be106e6955b610223b9fbe9d","signature":false,"impliedFormat":99},{"version":"08cad5153da753ed9c463b0e42a86bd8bc2b443368ce6a02a2a70e46adf7a4e0","signature":false,"impliedFormat":99},{"version":"df5baf8af95129eecb1ef1b02158664fc9c8bd0573e6011571b6c6bc33d2f570","signature":false,"impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","signature":false,"impliedFormat":99},{"version":"d4ec151557b64ff001e06b5878e66eb98640961647b1581fe2a0a1af05f408fb","signature":false,"impliedFormat":99},{"version":"e0864480ea083087d705f9405bd6bf59b795e8474c3447f0d6413b2bce535a09","signature":false,"impliedFormat":99},{"version":"e67cbea16f1994af89efd700542dbf3828a46a52b29e4d67e801bd7869dc103c","signature":false,"impliedFormat":99},{"version":"971f12a5fc236419ced0b7b9f23a53c1758233713f565635bbf4b85e2b23f55a","signature":false,"impliedFormat":99},{"version":"76de3321ce519928f1ff7d7a30391c0dc7374af20f81d9167919f038895b5cb0","signature":false,"impliedFormat":99},{"version":"094b9210da23b8711709b0535c59841186267bf6b83c1609aa9b515f830ab274","signature":false,"impliedFormat":99},{"version":"fbfbb4e99c6259ff5ccc4a5a62b3b63c0c8cae6e84737786c4a4c761c9a9de91","signature":false,"impliedFormat":99},{"version":"604887bbd5b0a93234ce882543a465f008636185c52e0f0353330e2bc38b03b6","signature":false,"impliedFormat":99},{"version":"32bf912173e8a9533631f9e9d8dc90a2ac7b52c2355611ddd886beab24dfd182","signature":false,"impliedFormat":99},{"version":"82695324abf7f3278b6d9f0582f4a544e8f7055c8cbe1065ab5cbacde1719c4c","signature":false,"impliedFormat":99},{"version":"43bba542e50e19241ec64bc13cfc0d9273e6198f36563cecad1f4e4b78ad47f3","signature":false,"impliedFormat":99},{"version":"b8cb3b69c0e8114f758bb8ef8efeef1cc80f8911bfd21126def73d2174ce479e","signature":false,"impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","signature":false,"impliedFormat":99},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","signature":false,"impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","signature":false,"impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","signature":false,"impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","signature":false,"impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","signature":false,"impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","signature":false,"impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","signature":false,"impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","signature":false,"impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","signature":false,"impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","signature":false,"impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","signature":false,"impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","signature":false,"impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","signature":false,"impliedFormat":1},{"version":"3cc8a3d123b6b232d48d34b51b785f9da8d193f5b5817fa521fcd2f3b9315c55","signature":false,"impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","signature":false,"impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","signature":false,"impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","signature":false,"impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","signature":false,"impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","signature":false,"impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","signature":false,"impliedFormat":99},{"version":"f3fe8197217cb6fd6e07c0eaa0966e4ece77cd779cbfc55767a93704cc692e04","signature":false,"impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","signature":false,"impliedFormat":1},{"version":"617112682fcb55805f0a0e16450397edc016881962eee4298f4f4e3ef795aee8","signature":false,"impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","signature":false,"impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","signature":false,"affectsGlobalScope":true,"impliedFormat":99},{"version":"7ef3a45742bae4cb8d88e473a04e14d35998ec9e8465d594b1bceb8d71d0023d","signature":false,"impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","signature":false,"affectsGlobalScope":true,"impliedFormat":99},{"version":"343badbae019a9bc81f44b0942ab08f0702521429be21dba24a70238dba35645","signature":false,"impliedFormat":99},{"version":"2faebfa830ae4cfbfb58e48b0ec20a2a63882d776f0ca36ec7155d45cf1b7f2d","signature":false,"impliedFormat":99},{"version":"9b98d6982d3a305952855f00b660b60d0d625f0965281f1692d5df8a401fca00","signature":false,"impliedFormat":99},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","signature":false,"impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"cd21651ff2dc71a2d2386cecd16eca9eed55064b792564c2ff09e9465f974521","signature":false,"impliedFormat":1},{"version":"fad635d25316340230cfbe1f879f00fcee7eb405afc038bd5986c0cecc72ad01","signature":false,"impliedFormat":99},{"version":"d4c1041683629f6239e6984e36689c93ca47a10875627eac4ce458359ff05403","signature":false,"impliedFormat":99},{"version":"635e0f9fb75313bc0a606164c18d7edb63d898cc1fa7d64bf1ca2424cb0ed641","signature":false,"impliedFormat":99},{"version":"04dc33f1dbf2f463c6480d431da59a772140be86c438f9445a0f2181b2893bfc","signature":false,"impliedFormat":99},{"version":"ecd444940f4a7726b83d004060652c1ea7dba9e79384f9e14c7c893dd35ee5a6","signature":false,"impliedFormat":99},{"version":"e415a5f905d75b3f182c691edfce1d9f1edb386f77af750c6891e1d4ca8b288c","signature":false,"impliedFormat":99},{"version":"5b59e760d0240891c2a037ffd823d3b29a81ebbdb692f3f6f1e6a84c75bdf93f","signature":false,"impliedFormat":99},{"version":"6df1fe718a1b394790d02d005a955ed1f50803c0fb988068af707db60515eb2b","signature":false,"impliedFormat":99},{"version":"21854b6c78fc2c0533d10595abc9f2adc8e366836f8443b4311b5455c9c61211","signature":false,"impliedFormat":99},{"version":"9ff4fa57a4e960c92e3683b84fac659e046c9dc9ea71fa19fd0238f389a37abc","signature":false,"impliedFormat":99},{"version":"81590fb2a4a06190dd982ba296e376309d6cc9a799c943b8bc2eb0fe7e826b83","signature":false,"impliedFormat":99},{"version":"bead1f5a73e14e9425a757dce08aa2551c029cd679a1b20f4ac94cfb2d26af60","signature":false,"impliedFormat":99},{"version":"c13be7c697ba7c5497070a9899e3eb5b6a1bd8e873d54ef4c0cd3292a0c9d80b","signature":false,"impliedFormat":99},{"version":"f45b6b08aaa4c86dd4b909a6ef6120d0d25f006031ce3677dea240040d79cd7e","signature":false,"impliedFormat":99},{"version":"2da759c66256885ffe262664a03c99c84f66c12160124bc9c54bbe4626a98b34","signature":false,"impliedFormat":99},{"version":"b539fdc12a246bce588611a2d2f471128f99cbe4feb982b84e292a7c185c19b1","signature":false,"impliedFormat":99},{"version":"f2a848ebb283dcc3bfa8b644e162eeb658ed4f9b134196a37c4263c37907b510","signature":false,"impliedFormat":99},{"version":"0eea552d03c4a36195a94973748ebde3ac30619c0c266253390e550823477470","signature":false,"impliedFormat":99},{"version":"d80a35ec56d6a8cee40e1bf7496551a57d41e194a0bc602a7433b52aad88e2a7","signature":false,"impliedFormat":99},{"version":"575430260af36058251660050741d9d5ddf2e267ff10a2595099ff8199f4cfbb","signature":false,"impliedFormat":99},{"version":"186f7fb9debf5a3b4847c572fcdb79140e17e112429b3086425a13ee825235d1","signature":false,"impliedFormat":99},{"version":"5992ed84626768c64bc863e32483d3e6a35b69918b3c8e529e6fe9b3b4230068","signature":false,"impliedFormat":99},{"version":"7fcf64c42e0bf1cebc851540ab03cf87600b4b5c7741b853c953aa0a88b9bbb5","signature":false,"impliedFormat":99},{"version":"c9ca5bce1637322c8fe4e3759a32102d6bfe37e437e784d193019984516236f8","signature":false,"impliedFormat":99},{"version":"9a38144039551dd1f8247d7bf778cb2f57f31e67fb5445e3d2918e6e3b71f1ff","signature":false,"impliedFormat":99},{"version":"a813c75b26a9094cf1364da70a459bced14258f78d767c6f63adb9aa08994955","signature":false,"impliedFormat":99},{"version":"3db294c132bd83df923a78cc7125bab8bc4253280296dcccc7e823c964e73696","signature":false,"impliedFormat":99},{"version":"e66f0def9554c696cb0693998a90876006ae0b72dafccc3f34dfc1b01c649ba5","signature":false,"impliedFormat":99},{"version":"b25e6c44ae3ec126eb11223357c4cb372ea57a6b719cd2b1f7efcdf5050fc5b5","signature":false,"impliedFormat":99},{"version":"cbb264fe574f7c974c0c7f041d5d09f58598c00398c2e6db4bb6d77848108459","signature":false,"impliedFormat":99},{"version":"707da604e89a3507db40a7b7283c9d062f00a3e8c1bb949437e4d23d3f8c83a2","signature":false,"impliedFormat":99},{"version":"8e2abba1230fb6e546aa985c11f416a4e2bc338c970f7ade1f7d7b1bd3703939","signature":false,"impliedFormat":99},{"version":"116174984d0b919c97430ff968fa69c52e282d7347cc92f4dabd5ed41a1c7d13","signature":false,"impliedFormat":99},{"version":"4163e075fce2172b06114df8c5f612164fe8338fcd0d554fd4194f9160f4925e","signature":false,"impliedFormat":99},{"version":"003b12a82db026304ca5bf817c6c32e88562e2fb1c6deab1e3319cbe63448919","signature":false,"impliedFormat":99},{"version":"316972caac7df38c384308f37116d3b5a44a597ad00aee135680efc456590dd1","signature":false,"impliedFormat":99},{"version":"f9f7181330aed071e41b2dff5eae74649c4e7efccab970aab94744c85e231fa7","signature":false,"impliedFormat":99},{"version":"1cae5a8af6f5206a48ce239d979e42d87d97e65b2b7c8e74a47b0c268bcff69a","signature":false,"impliedFormat":99},{"version":"d8c93138a8baac14311c5164ef070ed9e2213067d4e51d705ff7460f8c30aac8","signature":false,"impliedFormat":99},{"version":"c844f57c72b7ca3f5841da0859fd6e8708047ef76acb435936e933c5a1035e65","signature":false,"impliedFormat":99},{"version":"34572df2b002f36030be31c255702e99b4e9fde525468c62909909ed61aa085d","signature":false,"impliedFormat":99},{"version":"5fa712431619a8ebf7a63c1c0d27fd9feaae12b95c7cb2940ff813648a504865","signature":false,"impliedFormat":99},{"version":"7e154d458cf3d067917d13bc85488b11d023a75666997074f649c7ffa259c8a2","signature":false,"impliedFormat":99},{"version":"723697a7974bb5912a12872fb7bd70fbbaeca62f425b160189e13135c145f69b","signature":false,"impliedFormat":99},{"version":"c3ff3ac71fca07b3e392c42b2f6f8496434617cb677fd754039e4eb12b10a570","signature":false,"impliedFormat":99},{"version":"e405715e19f006c750521411aa0e21f5f65714b0cfc06a1ef827ca7164a78a20","signature":false,"impliedFormat":99},{"version":"ac224c8d6f0fb529961be77e07cc57b3de3a0e2d9e07431c984bbf85f864535c","signature":false,"impliedFormat":99},{"version":"34b73aa54e4a54baf65412868f362b45c2aec8d55e93d37c9abc193d9a5042f3","signature":false,"impliedFormat":99},{"version":"3389ec1b78a7947e393b927a43e31020f71c791085f67efde86d24fb3805d2f0","signature":false,"impliedFormat":99},{"version":"21cea8230195dbf1fcf1b11c35b01bfcd035dd4d0de736e4daf5f0f76e08f42c","signature":false,"impliedFormat":99},{"version":"161bda0d64fefa744228c5317b39853a5671eb936bf543a1ed5ca1c2756fe78f","signature":false,"impliedFormat":99},{"version":"0c7905fb84c0678fdf4ca3c5d7f0e3ff1d6f096749a32d18d3f516e40c3b8cb7","signature":false,"impliedFormat":99},{"version":"ce9a92fa30beb6aa039c8e6b3ced2261a2fea149fa70046a543531bd4d767975","signature":false,"impliedFormat":99},{"version":"5378a15bc21328ce0801339bf5b6da02d62d822a6e635738f1fc1ba399d04b74","signature":false,"impliedFormat":99},{"version":"c6b6e8731ebce7e8343ddb353376aa4e7fd522ad22cc19732d9633981dd00e0e","signature":false,"impliedFormat":99},{"version":"2d7b937e1d74c7b7f360b8dc29896f1f1dbf6bea34661c44ba061b94f1faed38","signature":false,"impliedFormat":99},{"version":"eae3dff34f426311c6b091deaa0d0c5702533460f8a4a26299d294ee4e3c2655","signature":false,"impliedFormat":99},{"version":"c35ec6abf02ebd1ff7467e5fbd660ea8fef0c156e3d3c4aa702f44cff1aed391","signature":false,"impliedFormat":99},{"version":"72fa28b198e60cb3f6fc7e85847a808b1aaa93d6362419bb350363b77056c061","signature":false,"impliedFormat":99},{"version":"2dcd17e5dea10ba55d976e3c304d185aabaa4a7589efaeebba61679b18b7108b","signature":false,"impliedFormat":99},{"version":"abd4b15f7263ead3b01616086a13683d3e7bfe8bc93f79cffde7dcf6c6c622ed","signature":false,"impliedFormat":99},{"version":"925dfc37731b7011e1886c707db60c9658b48fd02b6a261a732bc31d26d4f976","signature":false,"impliedFormat":99},{"version":"f9c434fb4157a0aa57b651ac5d18d47a8e1585779a805507d6015422e36cf95e","signature":false,"impliedFormat":99},{"version":"e89c048b7e1597471f692c49a5df23c43d67e9b4037a2b1ca28094c49bcc5868","signature":false,"impliedFormat":99},{"version":"c424b932553eec696c94ea77aece8460fd898e6349a499b1e96a05f561f809bf","signature":false,"impliedFormat":99},{"version":"a3e55e768780c3422867fd252c593444166c1b4aab83f711d4cafe018c151bcc","signature":false,"impliedFormat":99},{"version":"2f0fad277179e920e6e1e3a1f404663ef44e7d6daad888c7e7c1d5e4ac35660f","signature":false,"impliedFormat":99},{"version":"8dee13aa32d22ed4803116c8ec86779d47755c077cd91df1f5f8c92f004a974e","signature":false,"impliedFormat":99},{"version":"8dab0cefaf005a0843afb026ef38e5aed8607bfbb737f9c7fc50f785b78ecf16","signature":false,"impliedFormat":99},{"version":"d3ad0bc42dcc11b07c57f2252f153a61c35c356964700e4dc8de47dcc93e4f7e","signature":false,"impliedFormat":99},{"version":"060db9875e0cfcbbdbb9033306d86ff29211539422873cb938a83292dd15cff6","signature":false,"impliedFormat":99},{"version":"95a86c187c914030d637fcd6aed8b2ffc97253c23ee64672f5a048f3b51aa6c9","signature":false,"impliedFormat":99},{"version":"8e631b7142be8de16b2664f1b7a0ba19328f972056e878ac70fcdac1248a349b","signature":false,"impliedFormat":99},{"version":"a7991a0b4ef29c09d5e3f2c3470c3767cfb1729496ce5b6b2ca30c83b0ee2017","signature":false,"impliedFormat":99},{"version":"cdfdb403e5559c77775dfa1cbcd40e27664369fc4b4c1fa42619935fd9262bee","signature":false,"impliedFormat":99},{"version":"34b180219def210a4a69436bbffd46ec68014539ee985b97e4708430d4d8e1ff","signature":false,"impliedFormat":99},{"version":"b0ea9f55415f1f81eaa0ae0873cd218d247ffa8a3d1816debc9e72ae031301df","signature":false,"impliedFormat":99},{"version":"955c70837aa87fca583694d68fd02e43c78ceddae2f1fccfa67e314919e9923a","signature":false,"impliedFormat":99},{"version":"8de93f492e39bba6d3dd9497a9536fd7ef1d0c024646dea563ba599ba41b42b7","signature":false,"impliedFormat":99},{"version":"ff6cae9c99bf41a88bd777cff4603b2dd8df284df08bc1c70b39e1b831114587","signature":false,"impliedFormat":99},{"version":"42b7f20621b9ca12ac20e0ecf067b709921a823306bc46a7566d6001d30d5987","signature":false,"impliedFormat":99},{"version":"9821bd794030358a4ca9b693d3b2604baaa9a439fd8bd31b03c6bdc35e5c4d52","signature":false,"impliedFormat":99},{"version":"1644a14874faafb317449298893c34c3864f1128416b401fb502a9b0ff5fb68c","signature":false,"impliedFormat":99},{"version":"24e70403180bb155cc23f6128a1b242f62ec5a7b3bb2c312c0d591f10bc5450c","signature":false,"impliedFormat":99},{"version":"b3d32b6765eb89b0256725bdc76ee73e63c56fcedfeeef37721615bd363eda77","signature":false,"impliedFormat":99},{"version":"a78a1bca636b4b69775c6c5f548b34fc7a9abbe74c2887d7f1fdfb293e37f61b","signature":false,"impliedFormat":99},{"version":"b26fb95946488eaeed9ad6d170ff90d22202547f1ef52bc7d6f91c77c8516540","signature":false,"impliedFormat":99},{"version":"0bba43d978e1d320d14ef2e02f1f02d7474071a5592d0b545e969002d55a49fa","signature":false,"impliedFormat":99},{"version":"558df86d4f17ae1c7e732fb2ece8fb6390529496c77b3cb90a4c38008008088a","signature":false,"impliedFormat":99},{"version":"ecf7bd6a5e86562c39e559a0ba41a09058bf1f187a260cd78c381feade95f8c6","signature":false,"impliedFormat":99},{"version":"6e1435c1c4300a30ee53a812a002e9e37f5ca02a5d6c7cbccad7ec667d02aacf","signature":false,"impliedFormat":99},{"version":"9937002dcab0b1d0b3cda4709e3f36986cc8ddb8177fc50b7995f26a0e642983","signature":false,"impliedFormat":99},{"version":"324ca2d3dd0efd96187834009ae257ea293ecb27d425c87aa415ab4d1242f2f4","signature":false,"impliedFormat":99},{"version":"f4563e9a0e5ec762cca8101e1d4435d96490041f2a1f416973f23cba3cf985b1","signature":false,"impliedFormat":99},{"version":"d8122c9207978093ee0ff6574da42deba33e6b8a24b93b60e9cb4d004a89d9c1","signature":false,"impliedFormat":99},{"version":"c2ccc7c4bf4ec0827ab24087d3d28d936003bcf364b4e5df8abb135adf29f9c0","signature":false,"impliedFormat":99},{"version":"e4e2459598e499579a64881664c0413c8caac0134b6044696246345dbc3fd287","signature":false,"impliedFormat":99},{"version":"4a88bd904cef785320a507d51e358e082624bbd2bdae93f47be7f8ddd2c1b60d","signature":false,"impliedFormat":99},{"version":"3068fc8e6a14e20bac8feae3bd72965d44e4ef372d612708d322663907b5cd3c","signature":false,"impliedFormat":99},{"version":"9681b978f529c62ea3b7feb6e6c16979f97de629a9de0c9bccd697d642937b05","signature":false,"impliedFormat":99},{"version":"4b8b7b1100b1f9a92ce02a3efdf9c4b588ba71bdd3d426217cf13722a60f5295","signature":false,"impliedFormat":99},{"version":"00e2828dbc375686b78f04fae08f8b76e2c87876ddf0fce4403d41c18181cd9d","signature":false,"impliedFormat":99},{"version":"4af74a8e255575812dfeb09390e0cebc8a6aefaec59a7985478106f8e4a3fb1d","signature":false,"impliedFormat":99},{"version":"99bad9b8bf0c579cac0a1dcd86d8d5e47b5eb902eb0f16b553c1d3c12204a94a","signature":false,"impliedFormat":99},{"version":"e6af1e7c1b560b80c9291bc861168d1656a6ef8d3fffd3a83f07b61c82e9d0cf","signature":false,"impliedFormat":99},{"version":"d85a8538091baab20f6cf05faf6ff5b97e9dffea52bdd29381d9a4b5e0aa60d1","signature":false,"impliedFormat":99},{"version":"df28899221284aaf08640b6ad3e819ab9f4112f4f7cffb4b90179e53476ea495","signature":false,"impliedFormat":99},{"version":"2741ed6b4cebf245949141e47c139b82f6a640a4a4d9db4f097885f443d7d40b","signature":false,"impliedFormat":99},{"version":"0d3d9d02030248338b908179592022d15619d04881db91d00d1a484eb88a0052","signature":false,"impliedFormat":99},{"version":"2b2432c899a2e6652e212a8127fe507a5eb18e054f18a06ebbc29a74c46bfefb","signature":false,"impliedFormat":99},{"version":"f85981bbd8ffd17e39d83e4baf150e87b4b6560d73fa021f3bdf7f15abf4fe7b","signature":false,"impliedFormat":99},{"version":"dc74ed49e914b4cd2f8f028fc3263b24ab66ace68bdbf2a9119c28a698262b3f","signature":false,"impliedFormat":99},{"version":"4fb21c3ddd96225cc0be2224cf62c108972303bf373a9da867f5fca6da848313","signature":false,"impliedFormat":99},{"version":"34a1307870b94bd744d6a651f8ec27f3e3654ed1a3203901660111227b761fe3","signature":false,"impliedFormat":99},{"version":"bf47479ef063e921d82b060b0e1a96904677df4f273a50e08b1df2bd136e2925","signature":false,"impliedFormat":99},{"version":"4ca4f879206579f227372ce8beec213f932d1eaba371d5b3ec0851e3a571d203","signature":false,"impliedFormat":99},{"version":"344e635bf3cad4394e0d4d4d834607e9d819fa8df446d9507a78778ee1012fa6","signature":false,"impliedFormat":99},{"version":"6b528ab3b824af82e43266a979dff47f62d921992e0bdfb6a9043e5396082fc5","signature":false,"impliedFormat":99},{"version":"1558bcadf70f6b363eaeb655cc2cae7360397804de1d9bd585d2feda385192f6","signature":false,"impliedFormat":99},{"version":"90d8434d2a655295f7c8dd900ea86ead98ffe066c59d7aebefe08ffd61e38ef4","signature":false,"impliedFormat":99},{"version":"e9c1381fc978572529d41990a03a53429e5a652e8b1774b70303348c57bb1b31","signature":false,"impliedFormat":99},{"version":"11669d8c5e86ff67b984f8bee64d1faab863ec1c8ff89c262c27e75c063ebdff","signature":false,"impliedFormat":99},{"version":"cfff369fdf1d2afd4c6642a6ebf7c8d727393d532146f42be42283bdbbc5377b","signature":false,"impliedFormat":99},{"version":"ae3de7ae9d29e5038cadb1fe4a2d655e3b01b702dd42fb1d274dbb550f6df698","signature":false,"impliedFormat":99},{"version":"70acecbbb46d43b76ee7e3b4e6f2b0539eede73fe747491caaa093d61f29e4db","signature":false,"impliedFormat":99},{"version":"d6eb3370299b3183336618711ffb27ad6ce9763d96efc80cf6139ec2758ea48f","signature":false,"impliedFormat":99},{"version":"867e3e9ac667faef8df6bd9ec169a2c3f58f71ed0ed36e57b08668c214727eb4","signature":false,"impliedFormat":99},{"version":"5b88f3495fff05e3d31c820935f5a2e9c2bb70fea138d9a152a0fcea46589b40","signature":false,"impliedFormat":99},{"version":"71e44bf474e08d33b49ab3f403d2d79ef2f0d9aadc2f9929330bef87a1909ad2","signature":false,"impliedFormat":99},{"version":"c1fe0d3291fab30900480f68f72cb052f34854441f9786a0cd424857e2de8757","signature":false,"impliedFormat":99},{"version":"503fcc822afc39a884a56823971d420086076e7f8822e93cefba27fe065fff41","signature":false,"impliedFormat":99},{"version":"6d8b3da7d530016f4bc28fc091d796938cd5ac43ab1cb95e65d4c7292a82ca19","signature":false,"impliedFormat":99},{"version":"ee1fe3070063aa57050425ee1cb0d52cc2aa4c356e79cc840c8914c107a4164e","signature":false,"impliedFormat":99},{"version":"6303a23a5cfbd0c2caefc4e3781f759a580aa15565be734406b9637bb5465307","signature":false,"impliedFormat":99},{"version":"98aef5c1cf67ba83fd511cf763045a0906f4192b9a963ca31d66fe99a085d255","signature":false,"impliedFormat":99},{"version":"10adf4d23d463963a359f301db2f98daa8bde4df0690b3c4154758455c71ee5f","signature":false,"impliedFormat":99},{"version":"c47cea8fe761c7646e9df73f2bfe308cccb838e56821208b1a58277d6745d7e5","signature":false,"impliedFormat":99},{"version":"c8a8b70029abc4ee30637b5c9a3d80ea0762f9ee99f5ed6c537fc08ca710be81","signature":false,"impliedFormat":99},{"version":"1c7783e32cd7d5637e47ccbd215e2566f465ac2ba5206f331b91072482dd018f","signature":false,"impliedFormat":99},{"version":"5664dfe415f5c29f87c7c3bf43ea2c1ad2990521b310aa10e8a41abd14f32f27","signature":false,"impliedFormat":99},{"version":"7b839a06f4fe489e14e4420fa43e29901b726de6c8e827708805846f7ae8cafc","signature":false,"impliedFormat":99},{"version":"953fa50ab31648e14301ced3e42b8ccfce6be9a2311424bffe0718b7c9683d96","signature":false,"impliedFormat":99},{"version":"f198f05243b8cc697166824d1b1c8380e9a963145b53afd36a854aaba7cf1899","signature":false,"impliedFormat":99},{"version":"a23b404fbd4eeb4dbe9a16c3cb6c77cdb6969d433c0b723b0f5aa4de5f7b7953","signature":false,"impliedFormat":99},{"version":"9674cbc030a7c5506e8757cc5f092b75e8baf8ba55323a814064bece3df0432d","signature":false,"impliedFormat":99},{"version":"c57d07957ac426ef87ed28d9785a824824d8234e2776e1bdbf098b3ce55a8daa","signature":false,"impliedFormat":99},{"version":"f586a88e367e59a3e1f1b34a1228e52a5155c106b9cb2f8db36fed6d9eb9efb3","signature":false,"impliedFormat":99},{"version":"e6749b3c1e0a0f0541269a9058386ccdce75fa33daad48134cc710ab68a25833","signature":false,"impliedFormat":99},{"version":"e2bc4ead8e63387393a8f7c7b618cd8d2b5595f27e3b3997b664c63841f6df84","signature":false,"impliedFormat":99},{"version":"b9d47ab0b8f1295fff20fda80af86215624d99c96e03dd4486736b0202422aa9","signature":false,"impliedFormat":99},{"version":"db0c5d28711f45008be0afbf2d55a7911688dccb702eeffe3532953460598bd8","signature":false,"impliedFormat":99},{"version":"a620d02ba9bdb7a894cc1bdb911f0123ce0647264fc2de6913d1cae0421128df","signature":false,"impliedFormat":99},{"version":"ed999c1469cce636526c84b819dac6b369fc37541723f6a4b56dfaa1525e0992","signature":false,"impliedFormat":99},{"version":"78f7166e49230242132f49fe71ad3e31fa8c2f95f53e4960d35e5cb797c7d820","signature":false,"impliedFormat":99},{"version":"93f5ab10abfa1761b9f90954f50256ca01ee9e657e6a5f9995a2714ae11c516a","signature":false,"impliedFormat":99},{"version":"be9077c9bb010ba677b101fc40bef4f9839a6ace99568ff57b4c02f4b237db4a","signature":false,"impliedFormat":99},{"version":"0529c4cf5f1a3cb17efba2ba1ffbe6bef8383b4523a03334056105d9e1720fe7","signature":false,"impliedFormat":99},{"version":"7fcb4b818f60e4fe51511aa4e53a51b6d3ec031deafe9b678e47397fb787b42b","signature":false,"impliedFormat":99},{"version":"34767bf6796c20eff1bce8ccb895d7598e9c01f560147a8ab211fa51f5a8ed68","signature":false,"impliedFormat":99},{"version":"7b6863bd3701c0ed87293b617e9e0a91055351825e858a62e7d6e884ca4dae8d","signature":false,"impliedFormat":99},{"version":"5a2109c8a388c03d6f6153fe98ddaa4d0a9aa058dfc0b9d98b7ce45baf85ef1e","signature":false,"impliedFormat":99},{"version":"44386ffe4d68242e8cbfab18cf68f52e345fce6ce9b0fe456ce8f448a89c5ff0","signature":false,"impliedFormat":99},{"version":"d2adb57b7578ff022f341a779e91f718422ef03f4f1b22697b921e49bdbdd500","signature":false,"impliedFormat":99},{"version":"d1344da8b59b8190baca55ee1631f2cdcf9107646e98847588a3210bb4ae0ed7","signature":false,"impliedFormat":99},{"version":"d1ff278bedcd43322c961d38d8619d58070b55e0347d49ad6757849af4332dbc","signature":false,"impliedFormat":99},{"version":"40a38756055ab3ab7f359dd22c55c11b4651b75b273c6ed76d1a6620fd4fcf47","signature":false,"impliedFormat":99},{"version":"6124185e0daf9151bb72f01273bc892e6e2c853c5e8acd7fef17c286838a902b","signature":false,"impliedFormat":99},{"version":"5f17ae4cbfc34d8a68e5e6199884eb9dc9dfc931d526ead14a02ab49177efabd","signature":false,"impliedFormat":99},{"version":"766b9454208585ac1c3525ce9d58c209162bea386487f18445b7f7e706c23d5c","signature":false,"impliedFormat":99},{"version":"0896a5e1c7bb06ee49e7205c66c6b1c0c4f0b3d1ed157826302350d14d3183e0","signature":false,"impliedFormat":99},{"version":"c9a57b1ee3b66e6e227d20598366ab73968778dab609e5139c418c83310ad174","signature":false,"impliedFormat":99},{"version":"4869351067bfe590c999008942fcacec26e4af771ec27ed1b27d1dfc86c2c1a5","signature":false,"impliedFormat":99},{"version":"a6f04472fcf1eafdeed678ac628a23aaa7d027d56e1fbe2c1140ad13b95a5a5f","signature":false,"impliedFormat":99},{"version":"6e0fecd3f3e7ac0ec8d06971ddd01dfe7aa95da8aa65c56db375a28c15134fd9","signature":false,"impliedFormat":99},{"version":"3f65ad2a5d60095fae96c2d46e2fc61c66d1fb65afdc50c04452aa68422aa933","signature":false,"impliedFormat":99},{"version":"0ee692791f05b017c39fe78367768ceb2bd7e5c0ac0fea2298f666b45d190395","signature":false,"impliedFormat":99},{"version":"1ba1caf4981cb5344a7d34d69a9a6c7a0b6fac89cf36e93a6a88b0c53aad951d","signature":false,"impliedFormat":99},{"version":"4682eefaf56f525acfbb3780761496edf9e6671c134ebacc755ee8a046515a98","signature":false,"impliedFormat":99},{"version":"0c32d0c4a2605c3cfee63b60ade3722d6211d0bd7151d5dcb7d19aa185f92e20","signature":false,"impliedFormat":99},{"version":"5a79fc998e1c6841498cd9769ad4b8d212af1fa3ce1be575e244614048fa56f4","signature":false,"impliedFormat":99},{"version":"91049bee4d52e0d6ec9ecec57f9fd444067c11b2d18bfa369a51a78e63565cb2","signature":false,"impliedFormat":99},{"version":"07098c38d1f2300c6d2b62070e3ae3b75ecd3dfe940f88a0019d82e099f96ec5","signature":false,"impliedFormat":99},{"version":"e80d9f821bf1abe950318634b06840e8dad4fc0db46e97427d4a02195b23fbea","signature":false,"impliedFormat":99},{"version":"b3c78b5b1878641bd763116532eb6b03bd32f1d46f349287b87a942ec835c02a","signature":false,"impliedFormat":99},{"version":"399a3acb5e52325ee91ae74c95666f5071e9cdcce29c28b4e6f68f6038972f32","signature":false,"impliedFormat":99},{"version":"99318cb9deeef5b45a497640d4ce5dfa1c06a46d52f69196fccf2f89f7dad92c","signature":false,"impliedFormat":99},{"version":"9ee5f4da6229111e6b48ac5483dae7bd8577a49fd4c25dd7efe76a69dc9ae641","signature":false,"impliedFormat":99},{"version":"f6f27861d673d84dfc9809ab2eeb18d0b40a3997a837928edd7078160a12660c","signature":false,"impliedFormat":99},{"version":"2b4298489804ebe4f90ba2ce7d2f8e4d9b2ed326233bc6cd8fa2d5195d7d1a80","signature":false,"impliedFormat":99},{"version":"cdb35bdd7124a83909c366a75877d899eaeeefbb851c4cbd68b24de3201128c3","signature":false,"impliedFormat":99},{"version":"830123659fac74408548220a59ce6f82a37391db61a22f73d2ee321f9c4bf505","signature":false,"impliedFormat":99},{"version":"adf1fb98edd8782904c6b35e3fc0de9c0814296cf58230a985eaae26e107ed38","signature":false,"impliedFormat":99},{"version":"aee498086404181340c23a2c7737c3f5d3ed2a7f0316da4b94d912ef227fa3a8","signature":false,"impliedFormat":99},{"version":"157b2797f3fdbe4380ceabcf7a6a4cae523c9002256a3ce1cdaa56bb7ae0156e","signature":false,"impliedFormat":99},{"version":"7ff0326ef2a190b9b788b782a9a57112e424c015cf9329266cefba043fb95b65","signature":false,"impliedFormat":99},{"version":"dcb345b68d9e5e49e34f0b6c7439e10eafe626cb440299548684460806db9053","signature":false,"impliedFormat":99},{"version":"424aece36fa94e024a60d4c82771d0657b6ab028949c3b6fdfdd4986a62ef141","signature":false,"impliedFormat":99},{"version":"547f7a5cfbd3b141cce4a784a8cf11f77b1fa0482905cad6c053c6ebed5759d8","signature":false,"impliedFormat":99},{"version":"4de20271e7318991ee1e0f69fff6ce7b5a560f8409f5d4f244e74b044e194ab0","signature":false,"impliedFormat":99},{"version":"0646a2a0d8f7cb8c261e8fdaa3cd52760774f2bf09fad57eb6d322c49e53a791","signature":false,"impliedFormat":99},{"version":"464998e95d1ebe90aa71effabec6a42a77eb8d66a68bb53c8f02f0b0a34fe5ac","signature":false,"impliedFormat":99},{"version":"a0941e0644f91115b011166ef28ca1345cd931ac4b2e92e8314c7ddcef2d8c12","signature":false,"impliedFormat":99},{"version":"2af00fa0c37d8c3856e424b841f0e1bb4431ed0ac4c9e44934bc0cb350037aa8","signature":false,"impliedFormat":99},{"version":"a981ad35e7f6bfe68e26317ada681bc51d09f3abe24603f61f5ff4dd9bcd1d39","signature":false,"impliedFormat":99},{"version":"be2c243908a547a2d35351af3900c9a765ebc40cf8fa97cbcb5bb2d850150991","signature":false,"impliedFormat":99},{"version":"832e04fa9c6949cef6cbf1ffa5ff92f859827533d98f7199810c3654c6a7c82b","signature":false,"impliedFormat":99},{"version":"2a6d709681fbf73106c1daa64360eb0f7563a7f6d7527666d25b22ac5ec3d89b","signature":false,"impliedFormat":99},{"version":"12015d4754cec0afbc26cb1514cfbe246d3836aad746492a9fcc0b571fdff767","signature":false,"impliedFormat":99},{"version":"4a25d16f71ef609569ccbff00768e87325786df27b160f87ab44ddf7e7f603ac","signature":false,"impliedFormat":99},{"version":"a5f9bc96e9c35a8c4afbbd326f46f4ce42def2d053ca65d32b261bfd3462d112","signature":false,"impliedFormat":99},{"version":"c7223f7b2be8859bdd4b5d553c5e02b5d38345c3c828be14701863d1c5c46aa9","signature":false,"impliedFormat":99},{"version":"a778e8948ff336336b4a1b89e38fbc462044b395dc4adad8f7d539ebe51db61d","signature":false,"impliedFormat":99},{"version":"668192f288841913bf9afe0d34afde995d7c8bc63fb4d5e2b584db305cea0aa9","signature":false,"impliedFormat":99},{"version":"68c13200f8a1b994c2911340cee34fe972c121e06ae9e7111b220a387771ad85","signature":false,"impliedFormat":99},{"version":"d21b2c2b65be4881f879d5d97b88353e1e76663bebb58ba5ee9b5565a90e87b6","signature":false,"impliedFormat":99},{"version":"6745194a7f4ae219550989bb9663e04766eac8901e1b7013668bec1f0005f4b7","signature":false,"impliedFormat":99},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"f0ae1ac99c66a4827469b8942101642ae65971e36db438afe67d4985caa31222","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"46b907ed13bd5023adeb5446ad96e9680b1a40d4e4288344d0d0e31d9034d20a","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"ea689c41691ac977c4cf2cfe7fc7de5136851730c9d4dbc97d76eb65df8ee461","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"8d0f0aa989374cc6c7bc141649a9ca7d76b221a39375c8b98b844c3ad8c9b090","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"72c62b406af19eca8080ea63f90f4c907ee5b8348152b75ba106395cd7514f54","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"be3d53a4a6cc2e67e4b4b09c46bffce6282585fe504f77839863c53cb378a47f","signature":false,"impliedFormat":1},{"version":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","signature":false,"impliedFormat":1},{"version":"3199d552cbbbac5a3c6e1499c09acf672ae8c8c8687cf2a3dbfa7c8902cc7054","signature":false,"impliedFormat":1},{"version":"c82377c9ebc4d9788c743b6fba0661d1c34f39a7955476ac780039e120dc41fd","signature":false,"impliedFormat":1},{"version":"e3bf0a5aa199a4fc9f478808c7ffc2aa01411944594c2b305a43ede96e4a521d","signature":false,"impliedFormat":1},{"version":"3b0951ca295694b8d7b8139c1d69c1e6c2085e65fd86c8968eae8224f3bf5bfe","signature":false,"impliedFormat":1},{"version":"f2393e9e894511d174544b3319d5ed107753cc76548e590454024ccf2dedc881","signature":false,"impliedFormat":1},{"version":"83af0534774218e8d8205fb55df878c77e2471708a9d1435778aa69dabc24839","signature":false,"impliedFormat":1},{"version":"0013a72eaf0d971739705e72d2334e90973516c348f3b42a070ea5ec5563f502","signature":false,"impliedFormat":1},{"version":"e7990a14be2e5d20f7c5f3437f77733352c21c6665ad6fb57bf1a02611b88190","signature":false,"impliedFormat":99},{"version":"65862fcf94218b905b52246f5ec0beb74173fa311430211550647afd213b8d41","signature":false},{"version":"ad633a1a426cdbcf6e6e0a4af203552e0ffe544d4e690470baca6a8bc2188204","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ba854883a418fca4343b51cb93718d481770f3b81e978bbf6378a2385264e55c","signature":false,"impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","signature":false,"impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","signature":false,"impliedFormat":1},{"version":"b2d0630483bf337ef9dac326c3334a245aa4946e9f60f12baf7da5be44beafbb","signature":false,"impliedFormat":1},{"version":"68c0f599345d45a3f72fe7b5a89da23053f17d9c2cd5b2321acabe6e6f7b23b3","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"fd6f0bb5bd5f176b689915806a974cdb12a467bdaa414dc107a62d462eb7ddd5","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"770b54f6069a7364c1095e6ed782cf33e404c53ea2a4d8f58615403328fbd8fc","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"a1819d8e80fbf3e8d7acb1deafe67401ccad93d59d6a2416bdfc1a1e74ee7c2b","signature":false,"affectsGlobalScope":true,"impliedFormat":1},{"version":"6537313aa6bd137e046dd52f48698d4075dd511886f17ee70bc1dda1b9fe1066","signature":false,"impliedFormat":1},{"version":"8ac576b6d6707b07707fd5f7ec7089f768a599a39317ba08c423b8b55e76ca16","signature":false,"impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","signature":false,"impliedFormat":1},{"version":"96d14f21b7652903852eef49379d04dbda28c16ed36468f8c9fa08f7c14c9538","signature":false,"impliedFormat":1},{"version":"4ba01987c4b2c4b94a24c0a05bffbd75ab64d9fe8335dc63e9955d6c51a63468","signature":false,"impliedFormat":1},{"version":"916be7d770b0ae0406be9486ac12eb9825f21514961dd050594c4b250617d5a8","signature":false,"impliedFormat":1},{"version":"ed6b820c54de95b2510bb673490d61c7f2187f532a339d8d04981645a918961f","signature":false,"impliedFormat":1},{"version":"4cf58cd73f135e59d2268b4b792623bd8cc7ea887d96498f2a64d550beb930bb","signature":false,"impliedFormat":1},{"version":"be1cc4d94ea60cbe567bc29ed479d42587bf1e6cba490f123d329976b0fe4ee5","signature":false,"impliedFormat":1},{"version":"003ea9cf8d32a1df04a447de1bf59756bfa366841c1ecc91ad1a6a43da1b9718","signature":false,"impliedFormat":1},{"version":"960a68ced7820108787135bdae5265d2cc4b511b7dcfd5b8f213432a8483daf1","signature":false,"impliedFormat":1},{"version":"7c52a6d05a6e68269e63bc63fad6e869368a141ad23a20e2350c831dc499c5f2","signature":false,"impliedFormat":1},{"version":"2e7ebdc7d8af978c263890bbde991e88d6aa31cc29d46735c9c5f45f0a41243b","signature":false,"impliedFormat":1},{"version":"b57fd1c0a680d220e714b76d83eff51a08670f56efcc5d68abc82f5a2684f0c0","signature":false,"impliedFormat":1},{"version":"8cf121e98669f724256d06bebafec912b92bb042a06d4944f7fb27a56c545109","signature":false,"impliedFormat":1},{"version":"1084565c68b2aed5d6d5cea394799bd688afdf4dc99f4e3615957857c15bb231","signature":false,"impliedFormat":1},{"version":"8baa5d0febc68db886c40bf341e5c90dc215a90cd64552e47e8184be6b7e3358","signature":false,"impliedFormat":1},{"version":"15fe687c59d62741b4494d5e623d497d55eb38966ecf5bea7f36e48fc3fbe15e","signature":false,"impliedFormat":1},{"version":"2c3b8be03577c98530ef9cb1a76e2c812636a871f367e9edf4c5f3ce702b77f8","signature":false,"affectsGlobalScope":true,"impliedFormat":1}],"root":[461],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[187,1],[185,2],[186,3],[188,4],[183,5],[181,2],[184,6],[182,7],[165,2],[167,2],[168,8],[462,2],[221,2],[463,2],[467,9],[473,10],[469,2],[468,2],[471,2],[470,2],[474,2],[475,2],[106,11],[107,11],[108,12],[54,13],[109,14],[110,15],[111,16],[52,2],[112,17],[113,18],[114,19],[115,20],[116,21],[117,22],[118,22],[119,23],[120,24],[121,25],[122,26],[55,2],[53,2],[123,27],[124,28],[125,29],[160,30],[126,31],[127,2],[128,32],[129,33],[131,34],[132,35],[133,36],[134,37],[135,38],[136,39],[137,40],[138,40],[139,41],[140,2],[141,42],[142,43],[144,44],[143,45],[145,46],[146,47],[147,48],[148,49],[149,50],[150,51],[151,52],[152,53],[153,54],[154,55],[155,56],[156,57],[157,58],[56,2],[57,59],[58,2],[59,2],[102,60],[103,61],[104,2],[105,46],[158,62],[159,63],[476,2],[477,2],[478,2],[479,2],[480,9],[481,9],[482,2],[484,64],[487,65],[485,9],[483,9],[486,64],[464,2],[466,66],[488,2],[472,2],[490,67],[489,2],[220,68],[219,2],[130,2],[465,2],[211,2],[213,69],[212,2],[207,70],[205,71],[206,72],[194,73],[195,71],[202,74],[193,75],[198,76],[208,2],[199,77],[204,78],[210,79],[209,80],[192,81],[200,82],[201,83],[196,84],[203,70],[197,85],[216,86],[170,87],[171,88],[174,89],[166,90],[173,91],[169,92],[164,2],[175,93],[176,94],[222,95],[191,2],[326,2],[189,96],[439,97],[277,98],[274,99],[395,100],[275,101],[276,102],[367,103],[390,2],[392,2],[405,104],[348,105],[314,106],[297,107],[391,2],[324,2],[337,2],[358,108],[227,2],[408,109],[410,110],[409,111],[229,2],[342,112],[341,113],[344,114],[343,115],[295,2],[430,2],[411,116],[415,117],[413,118],[232,119],[233,119],[234,2],[355,120],[354,2],[368,121],[284,122],[316,123],[383,124],[437,2],[404,125],[338,126],[438,127],[382,128],[436,2],[225,2],[350,129],[428,130],[433,2],[279,131],[278,132],[374,133],[376,134],[240,135],[379,136],[387,137],[235,138],[388,139],[397,140],[389,141],[242,139],[427,142],[386,143],[385,144],[245,145],[246,2],[282,146],[283,147],[272,148],[273,149],[264,150],[263,151],[281,152],[244,153],[328,2],[236,2],[267,154],[248,2],[252,2],[253,155],[269,156],[400,101],[241,2],[270,2],[403,137],[266,154],[237,137],[402,137],[371,157],[375,158],[265,154],[268,154],[339,159],[398,2],[304,160],[271,140],[401,101],[378,161],[353,2],[346,2],[228,2],[360,162],[359,163],[412,118],[416,164],[414,165],[366,2],[231,166],[429,167],[347,131],[280,168],[364,169],[363,2],[315,170],[299,171],[300,2],[251,172],[351,173],[352,173],[286,174],[287,2],[296,2],[334,175],[254,176],[294,177],[319,178],[249,2],[238,179],[325,2],[259,180],[260,181],[370,182],[434,183],[306,184],[320,185],[417,111],[419,186],[418,186],[312,187],[313,188],[250,2],[224,2],[323,2],[322,189],[372,190],[369,191],[431,192],[432,192],[373,193],[425,189],[302,194],[285,195],[301,194],[303,196],[309,197],[239,133],[362,198],[423,199],[393,200],[335,201],[333,2],[329,202],[357,203],[330,202],[332,204],[331,205],[356,141],[396,206],[394,207],[298,208],[247,2],[305,209],[420,118],[422,164],[421,165],[258,99],[381,210],[424,211],[380,212],[262,213],[426,214],[349,215],[261,216],[255,217],[345,2],[361,218],[310,219],[311,220],[291,2],[327,221],[257,189],[435,2],[256,222],[340,223],[384,2],[226,224],[321,189],[406,225],[365,226],[317,227],[230,2],[318,228],[243,2],[290,2],[288,2],[289,2],[307,2],[308,229],[336,2],[407,230],[293,189],[377,134],[292,231],[399,232],[1,2],[50,2],[51,2],[9,2],[13,2],[12,2],[3,2],[14,2],[15,2],[16,2],[17,2],[18,2],[19,2],[20,2],[21,2],[4,2],[22,2],[23,2],[5,2],[24,2],[28,2],[25,2],[26,2],[27,2],[29,2],[30,2],[31,2],[6,2],[32,2],[33,2],[34,2],[35,2],[7,2],[39,2],[36,2],[37,2],[38,2],[40,2],[8,2],[41,2],[46,2],[47,2],[42,2],[43,2],[44,2],[45,2],[2,2],[48,2],[49,2],[11,2],[10,2],[78,233],[90,234],[75,235],[91,236],[100,237],[66,238],[67,239],[65,240],[99,241],[94,242],[98,243],[69,244],[87,245],[68,246],[97,247],[63,248],[64,242],[70,249],[71,2],[77,250],[74,249],[61,251],[101,252],[92,253],[81,254],[80,249],[82,255],[85,256],[79,257],[83,258],[95,241],[72,259],[73,260],[86,261],[62,236],[89,262],[88,249],[76,260],[84,263],[93,2],[60,2],[96,264],[460,265],[162,266],[218,267],[178,268],[163,266],[161,2],[177,269],[217,2],[215,2],[179,2],[214,270],[172,271],[190,272],[180,273],[448,2],[449,2],[442,2],[443,274],[456,275],[457,275],[459,276],[458,275],[455,275],[223,2],[454,277],[450,2],[451,2],[440,2],[441,2],[452,2],[453,274],[444,2],[445,2],[446,2],[447,2],[461,278]],"changeFileSet":[491,492,187,185,186,188,183,181,184,182,165,493,167,168,494,495,496,497,498,462,221,463,467,473,469,468,471,470,474,475,499,500,501,502,503,504,505,506,507,508,509,510,511,106,107,108,54,109,110,111,512,513,514,52,112,113,114,115,116,117,118,119,120,121,122,55,53,123,124,125,160,126,127,128,129,131,132,133,134,135,136,137,138,139,140,141,142,144,143,145,146,147,148,149,150,151,152,153,154,155,156,157,56,57,58,59,102,103,104,105,158,159,476,477,478,479,480,515,516,481,482,484,487,485,483,486,464,466,488,472,490,489,220,219,130,465,517,211,213,212,207,205,206,194,195,202,193,198,208,199,204,210,209,192,200,201,196,203,197,216,170,171,174,166,173,169,164,175,176,222,191,326,189,439,277,274,395,275,276,367,390,392,405,348,314,297,391,324,337,358,227,408,410,409,229,342,341,344,343,295,430,411,415,413,232,233,234,355,354,368,284,316,383,437,404,338,438,382,436,225,350,428,433,279,278,374,376,240,379,387,235,388,397,389,242,427,386,385,245,246,282,283,272,273,264,263,281,244,328,236,267,248,252,253,269,400,241,270,403,266,237,402,371,375,265,268,339,398,304,271,401,378,353,346,228,360,359,412,416,414,366,231,429,347,280,364,363,315,299,300,251,351,352,286,287,296,334,254,294,319,249,238,325,259,260,370,434,306,320,417,419,418,312,313,250,224,323,322,372,369,431,432,373,425,302,285,301,303,309,239,362,423,393,335,333,329,357,330,332,331,356,396,394,298,247,305,420,422,421,258,381,424,380,262,426,349,261,255,345,361,310,311,291,327,257,435,256,340,384,226,321,406,365,317,230,318,243,290,288,289,307,308,336,407,293,377,292,399,1,50,51,9,13,12,3,14,15,16,17,18,19,20,21,4,22,23,5,24,28,25,26,27,29,30,31,6,32,33,34,35,7,39,36,37,38,40,8,41,46,47,42,43,44,45,2,48,49,11,10,78,90,75,91,100,66,67,65,99,94,98,69,87,68,97,63,518,519,64,70,71,77,74,61,101,92,81,80,82,85,79,83,95,72,73,86,62,89,88,76,84,93,60,96,460,162,218,178,163,161,177,217,215,179,214,172,190,180,448,449,442,443,456,457,459,458,455,223,454,450,451,440,441,452,453,444,445,446,447,461],"version":"5.9.3"} \ No newline at end of file diff --git a/ui/twSafelistGenerator/index.js b/ui/twSafelistGenerator/index.js index b92aa81f..00d4c633 100644 --- a/ui/twSafelistGenerator/index.js +++ b/ui/twSafelistGenerator/index.js @@ -54,23 +54,17 @@ const collectFromRow = (row, classes) => { * stilsiz kalir. Siniflar safelist'e yazilir; dosya depoya islendigi icin her ortamda taranir. */ const collectComponentClasses = () => { - if (!fs.existsSync(SEEDS_ROOT)) return [] + const base = nodePath.join(SEEDS_ROOT, 'data/App.DeveloperKit.CustomComponents') - const files = fs - .readdirSync(SEEDS_ROOT, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .flatMap((entry) => { - const base = nodePath.join(SEEDS_ROOT, entry.name, 'data/App.DeveloperKit.CustomComponents') - - // Her bilesen klasorun altinda kendi dosyasindadir; tek dosya bicimi eski kurulumlarda kalir. - return fs.existsSync(base) - ? fs - .readdirSync(base) - .filter((name) => name.endsWith('.json')) - .map((name) => nodePath.join(base, name)) - : [`${base}.json`] - }) - .filter((file) => fs.existsSync(file)) + // Her bilesen klasorun altinda kendi dosyasindadir; tek dosya bicimi eski kurulumlarda kalir. + const files = ( + fs.existsSync(base) + ? fs + .readdirSync(base) + .filter((name) => name.endsWith('.json')) + .map((name) => nodePath.join(base, name)) + : [`${base}.json`] + ).filter((file) => fs.existsSync(file)) const classes = new Set() for (const file of files) { @@ -78,7 +72,7 @@ const collectComponentClasses = () => { const seed = JSON.parse(fs.readFileSync(file, 'utf8')) for (const row of seed.Rows || []) collectFromRow(row, classes) } catch { - // Bozuk bir seed dosyasi derlemeyi durdurmaz; diger kapsamlar islenir. + // Bozuk bir seed dosyasi derlemeyi durdurmaz; diger bilesenler islenir. } } diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 81fab807..c134e195 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -15,10 +15,10 @@ import { VitePWA } from 'vite-plugin-pwa' * (yarım sürümle aktive olmak beyaz ekran demek). * * 2. BİLEŞENLER (`warmup.json`, bu dosyadaki plugin üretir) - * Tüm lazy chunk'lar ve CSS'leri. Güncelleme penceresi açıkken aynı ilerleme - * çubuğunda arka planda indirilir; böylece kullanıcı deploy'dan sonra ilk kez - * bir ekrana girdiğinde chunk beklemez. Best-effort'tur: inemeyen dosya - * kurulumu düşürmez, süre bütçesi aşılırsa kalanlar runtime cache'e kalır. + * Kurulumun parçası değildir. Giriş yapmış kullanıcının yetkili olduğu + * rotaların chunk'ları ve CSS'leri uygulama açıldıktan sonra arka planda + * indirilir; ziyaretçi ve giriş ekranı hiçbir bileşen indirmez. Best-effort'tur: + * inemeyen dosya atlanır, süre bütçesi aşılırsa kalanlar runtime cache'e kalır. * * `dist/css` altındaki 48 DevExtreme teması (~33 MB) bilerek dışarıdadır; * kullanıcı başına yalnızca bir tema gerekir, runtime cache yeterlidir. @@ -40,14 +40,22 @@ const PRECACHE_GLOBS = [ 'assets/css/index-*.css', ] -/** Arka planda ısıtılacak bileşenler: uygulamanın tüm JS/CSS chunk'ları. */ +/** Build çıktısındaki bütün JS/CSS chunk'ları; boyut ve eski sürüm temizliği için listelenir. */ const WARMUP_FILE = /^assets\/(?:js|css)\/.+\.(?:js|css)$/ +/** Rota bileşeni olabilecek modüller (`dynamicRouteLoader` ile aynı glob kökü). */ +const ROUTE_MODULE = /\/src\/(views\/.+)\.tsx$/ + /** * Build çıktısındaki chunk adlarını ve (sıkıştırılmamış) boyutlarını * `dist/warmup.json` olarak yazar. Boyutun derleme anında bilinmesi, service * worker'ın ilerleme yüzdesini ilk saniyeden itibaren doğru göstermesini sağlar * (Content-Length gzip'li boyut olduğu için yüzde zıplıyordu). + * + * `routes` her view modülünün açılması için gereken dosyaları (`files` içindeki + * sıra numaraları) taşır: chunk'ın kendisi, statik import zinciri ve CSS'leri. + * Service worker yalnızca kullanıcının yetkili olduğu rotaların zincirini ısıtır; + * ekranın içinden lazy açılan parçalar (diyalog, editör) runtime cache'e kalır. */ function warmupManifest(): Plugin { return { @@ -55,27 +63,54 @@ function warmupManifest(): Plugin { apply: 'build', enforce: 'post', generateBundle(_options, bundle) { - const files = Object.values(bundle) - .filter((output) => WARMUP_FILE.test(output.fileName)) - .map((output) => { - const source = output.type === 'chunk' ? output.code : output.source - return { - url: `/${output.fileName}`, - size: - typeof source === 'string' - ? new TextEncoder().encode(source).byteLength - : source.byteLength, - } - }) + const outputs = Object.values(bundle).filter((output) => WARMUP_FILE.test(output.fileName)) // vite-plugin-pwa service worker'ı ayrı bir build ile üretir; o build'de // eşleşen dosya olmaz. Boş liste yazmak gerçek manifest'i ezerdi. - if (files.length === 0) return + if (outputs.length === 0) return + + const indexOf = new Map(outputs.map((output, index) => [output.fileName, index])) + const files = outputs.map((output) => { + const source = output.type === 'chunk' ? output.code : output.source + return { + url: `/${output.fileName}`, + size: + typeof source === 'string' + ? new TextEncoder().encode(source).byteLength + : source.byteLength, + } + }) + + const closure = (fileName: string, seen: Set) => { + const index = indexOf.get(fileName) + if (index === undefined || seen.has(index)) return + seen.add(index) + const output = bundle[fileName] + if (output?.type !== 'chunk') return + output.viteMetadata?.importedCss.forEach((css) => closure(css, seen)) + output.imports.forEach((imported) => closure(imported, seen)) + } + + // Başka bir modülün statik olarak da import ettiği view kendi chunk'ını almaz, + // o chunk'ın içine katılır; bu yüzden eşleme chunk'ın facade'ından değil + // içerdiği modüllerden kurulur. + const routes: Record = {} + for (const output of outputs) { + if (output.type !== 'chunk') continue + const views = output.moduleIds + .map((id) => id.replace(/\\/g, '/').match(ROUTE_MODULE)?.[1]) + .filter((view): view is string => !!view) + if (views.length === 0) continue + const seen = new Set() + closure(output.fileName, seen) + const files = [...seen] + views.forEach((view) => (routes[view] = files)) + } this.emitFile({ type: 'asset', fileName: 'warmup.json', - source: JSON.stringify({ files }), + source: JSON.stringify({ files, routes }), }) }, }