Language ve LanguageText ListForm Seeder taşındı
This commit is contained in:
parent
9816fb91ce
commit
246dc17fcb
17 changed files with 69515 additions and 29914 deletions
2
.github/instructions/ai.instructions.md
vendored
2
.github/instructions/ai.instructions.md
vendored
|
|
@ -660,7 +660,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:
|
a deploy. AI must learn and teach implementation flow primarily from these assets:
|
||||||
|
|
||||||
- ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_{Modul}.cs
|
- ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_{Modul}.cs
|
||||||
- LanguagesData.json
|
- configs/seeds/host/data/App.Languages.{Language,LanguageText}.json — languages and texts (ListForm seed data)
|
||||||
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
|
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
|
||||||
surface B below
|
surface B below
|
||||||
- ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply
|
- ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply
|
||||||
|
|
|
||||||
15
.github/instructions/list.instructions.md
vendored
15
.github/instructions/list.instructions.md
vendored
|
|
@ -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`)
|
- Table names: `{Modul}_T_{Entity}` (e.g., `Mrp_T_Order`)
|
||||||
- Menu/permission keys: `App.{Modul}`, `App.{Modul}.{Liste}`
|
- Menu/permission keys: `App.{Modul}`, `App.{Modul}.{Liste}`
|
||||||
- **Tenant Isolation:** All data, forms, and permissions must be tenant-aware.
|
- **Tenant Isolation:** All data, forms, and permissions must be tenant-aware.
|
||||||
- **Localization:** Every menu, list, and field must have a corresponding entry in `LanguagesData.json`.
|
- **Localization:** Every menu, list, and field must have a corresponding entry in `configs/seeds/host/data/App.Languages.LanguageText.json`.
|
||||||
- **No Redundant Code:** Avoid duplicating permission grants or seeding logic across modules.
|
- **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.
|
- **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`.
|
- **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`.
|
||||||
|
|
@ -30,8 +30,9 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
||||||
- **configs/seeds/host/data/App.Menus.*.json:**
|
- **configs/seeds/host/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.
|
- 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.
|
||||||
|
|
||||||
- **LanguagesData.json:**
|
- **configs/seeds/host/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.
|
- 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -60,7 +61,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
||||||
### 6. PlatformIdentityDataSeeder.cs
|
### 6. PlatformIdentityDataSeeder.cs
|
||||||
- **Removed** redundant {Modul} permission grant logic (now handled by the permission seed data).
|
- **Removed** redundant {Modul} permission grant logic (now handled by the permission seed data).
|
||||||
|
|
||||||
### 7. LanguagesData.json
|
### 7. App.Languages.LanguageText.json
|
||||||
- **Added** localization entries for:
|
- **Added** localization entries for:
|
||||||
- {Modul} menu and {Liste} list
|
- {Modul} menu and {Liste} list
|
||||||
- {Liste} list fields (e.g., `FullName`, `Phone`)
|
- {Liste} list fields (e.g., `FullName`, `Phone`)
|
||||||
|
|
@ -77,7 +78,7 @@ Yeni bir modül ekle (ör: {Modul}):
|
||||||
- configs/seeds/host/data/Abp.Identity.PermissionGroups.json'da ayrı bir grup oluştur
|
- configs/seeds/host/data/Abp.Identity.PermissionGroups.json'da ayrı bir grup oluştur
|
||||||
- ListFormSeeder_{Modul}.cs dosyası oluştur ve list-form seed'ini buraya taşı
|
- ListFormSeeder_{Modul}.cs dosyası oluştur ve list-form seed'ini buraya taşı
|
||||||
- SqlObjects.sql'de tabloyu {Modul}_T_{Entity} olarak adlandır
|
- SqlObjects.sql'de tabloyu {Modul}_T_{Entity} olarak adlandır
|
||||||
- LanguagesData.json'a menü, liste ve alan çevirilerini ekle
|
- configs/seeds/host/data/App.Languages.LanguageText.json dosyasına menü, liste ve alan çevirilerini ekle
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Add a New List Under a Module
|
### 2. Add a New List Under a Module
|
||||||
|
|
@ -87,7 +88,7 @@ Yeni bir liste ekle (ör: {Liste}):
|
||||||
- ListFormSeeder_{Modul}.cs dosyasına seed kodunu ekle
|
- ListFormSeeder_{Modul}.cs dosyasına seed kodunu ekle
|
||||||
- SqlObjects.sql'de tabloyu {Modul}_T_{Entity} olarak oluştur
|
- 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/Abp.Identity.Permissions.json'da ilgili izinleri {Modul} grubuna ekle
|
||||||
- LanguagesData.json'a liste ve alan çevirilerini ekle
|
- configs/seeds/host/data/App.Languages.LanguageText.json dosyasına liste ve alan çevirilerini ekle
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Enforce Tenant Isolation and Full Audit
|
### 3. Enforce Tenant Isolation and Full Audit
|
||||||
|
|
@ -100,7 +101,7 @@ Tablo ve list-form için tenant izolasyonu ve full-audit alanlarını ekle:
|
||||||
|
|
||||||
### 4. Add/Update Localization
|
### 4. Add/Update Localization
|
||||||
```
|
```
|
||||||
Yeni menü, liste veya alan eklediğinde LanguagesData.json'a şu şekilde ekle:
|
Yeni menü, liste veya alan eklediğinde configs/seeds/host/data/App.Languages.LanguageText.json dosyasına (EN + TR satırı) şu şekilde ekle:
|
||||||
- App.{Modul}
|
- App.{Modul}
|
||||||
- App.{Modul}.{Liste}
|
- App.{Modul}.{Liste}
|
||||||
- App.Listform.ListformField.{Alan}
|
- App.Listform.ListformField.{Alan}
|
||||||
|
|
@ -127,7 +128,7 @@ Yeni menü, liste veya alan eklediğinde LanguagesData.json'a şu şekilde ekle:
|
||||||
- [ ] Menü ve izinler doğru hiyerarşide mi?
|
- [ ] Menü ve izinler doğru hiyerarşide mi?
|
||||||
- [ ] List-form seed'leri doğru modül dosyasında mı?
|
- [ ] List-form seed'leri doğru modül dosyasında mı?
|
||||||
- [ ] Tablo isimleri ve referansları standartlara uygun mu? (örn: {Modul}_T_{Entity})
|
- [ ] Tablo isimleri ve referansları standartlara uygun mu? (örn: {Modul}_T_{Entity})
|
||||||
- [ ] LanguagesData.json'da tüm yeni menü, liste ve alanlar var mı?
|
- [ ] App.Languages.LanguageText.json'da tüm yeni menü, liste ve alanlar var mı?
|
||||||
- [ ] Redundant kod veya izin grant'ı var mı?
|
- [ ] Redundant kod veya izin grant'ı var mı?
|
||||||
- [ ] Seed ve migration sonrası UI'da çeviriler doğru görünüyor mu?
|
- [ ] Seed ve migration sonrası UI'da çeviriler doğru görünüyor mu?
|
||||||
- [ ] SeedConsts.cs dosyasında ilgili sabitler var mı, tekrar eklenmemiş mi?
|
- [ ] SeedConsts.cs dosyasında ilgili sabitler var mı, tekrar eklenmemiş mi?
|
||||||
|
|
|
||||||
|
|
@ -1253,7 +1253,7 @@ soneklerini alır.
|
||||||
| **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ğı. |
|
| **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. |
|
| **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`, `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. |
|
| **Setting Definitions** | `App.SettingDefinitions` | `Sas_H_SettingDefinition` | host | Yeni uygulama ayarı: `Code`, `MainGroupKey`/`SubGroupKey`, `DataType`, `SelectOptions`, `DefaultValue`, `Providers`, `RequiredPermissionName`, `IsEncrypted`, `IsVisibleToClients`. Hazır tanımlar `data/App.SettingDefinitions.json`'dan gelir. Ayarlanabilir olması gereken değeri koda gömme. |
|
||||||
| **Languages › Language / Language Text** | `App.Languages.Language` · `.LanguageText` | `Sas_H_Language` · `Sas_H_LanguageKey` · `Sas_H_LanguageText` | host | Dil anahtarları. Üretilen her ekranın EN+TR metinleri buraya (ve `LanguagesData.json`'a) yazılır. |
|
| **Languages › Language / Language Text** | `App.Languages.Language` · `.LanguageText` | `Sas_H_Language` · `Sas_H_LanguageKey` · `Sas_H_LanguageText` | host | Dil anahtarları. Üretilen her ekranın EN+TR metinleri buraya yazılır; ekran `data/App.Languages.{Language,LanguageText}.json` dosyalarına senkronlanır. Metin insert'i eksik `LanguageKey`'i de üretir, aynı kültür+kaynak+anahtar varsa eklemez. |
|
||||||
| **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. |
|
| **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. |
|
| **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. |
|
| **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. |
|
||||||
|
|
@ -1417,7 +1417,7 @@ alanları da doldurur (`lowcode` §0.9).
|
||||||
| "barkod/QR basılsın" | Visual Designer › `BarcodeViewer` | `value`'yu `Form` kolonuna bağla |
|
| "barkod/QR basılsın" | Visual Designer › `BarcodeViewer` | `value`'yu `Form` kolonuna bağla |
|
||||||
| "başka veritabanından gelsin" | Saas › Data Source | Yeni DataSource + ekranın `DataSourceCode` alanı |
|
| "başka veritabanından gelsin" | Saas › Data Source | Yeni DataSource + ekranın `DataSourceCode` alanı |
|
||||||
| "ayarlanabilir olsun" | Saas › Setting Definitions | Tanım aç, değeri `/admin/settings`'ten yönet |
|
| "ayarlanabilir olsun" | Saas › Setting Definitions | Tanım aç, değeri `/admin/settings`'ten yönet |
|
||||||
| "İngilizcesi de olsun" | Saas › Languages | Dil anahtarı + `LanguagesData.json` |
|
| "İngilizcesi de olsun" | Saas › Languages | Dil anahtarı + `data/App.Languages.LanguageText.json` |
|
||||||
| "aramada çıksın" | Saas › Definitions › Global Search | `System`/`Group`/`Term`/`Weight`/`Url` kaydı |
|
| "aramada çıksın" | Saas › Definitions › Global Search | `System`/`Group`/`Term`/`Weight`/`Url` kaydı |
|
||||||
| "sadece şu rol görsün" | Administration › Identity › Roles / Permissions | Yetki kodu + ekranın `PermissionJson`'ı |
|
| "sadece şu rol görsün" | Administration › Identity › Roles / Permissions | Yetki kodu + ekranın `PermissionJson`'ı |
|
||||||
| "herkes yalnızca kendi kayıtlarını görsün" / "şu rol şu bölgeyi görsün" | ListForm editörü › Kişiselleştirme | `ServerWhere` kaydı (herkes/rol/kullanıcı; §2.5) ya da `DefaultFilter` |
|
| "herkes yalnızca kendi kayıtlarını görsün" / "şu rol şu bölgeyi görsün" | ListForm editörü › Kişiselleştirme | `ServerWhere` kaydı (herkes/rol/kullanıcı; §2.5) ya da `DefaultFilter` |
|
||||||
|
|
|
||||||
13
.github/instructions/lowcode.instructions.md
vendored
13
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -90,7 +90,7 @@ Uydurma. Şu üçünü mutlaka kontrol et:
|
||||||
`data/App.Menus.Menu.json` (platform menüleri), `data/Abp.Identity.PermissionGroups.json` ve
|
`data/App.Menus.Menu.json` (platform menüleri), `data/Abp.Identity.PermissionGroups.json` ve
|
||||||
mevcut wizard dosyalarındaki `MenuParentCode` / `PermissionGroupName` değerlerini tara
|
mevcut wizard dosyalarındaki `MenuParentCode` / `PermissionGroupName` değerlerini tara
|
||||||
(MCP'de `list_wizards`).
|
(MCP'de `list_wizards`).
|
||||||
3. **Dil anahtarı çakışması** — `LanguagesData.json` ve diğer wizard dosyaları. Var olan bir
|
3. **Dil anahtarı çakışması** — `data/App.Languages.LanguageText.json` ve diğer wizard dosyaları. Var olan bir
|
||||||
anahtarın metnini seed **ezmez**; yanlış metinle karşılaşırsan anahtarı değiştir.
|
anahtarın metnini seed **ezmez**; yanlış metinle karşılaşırsan anahtarı değiştir.
|
||||||
|
|
||||||
### 0.4 Üretim sonrası
|
### 0.4 Üretim sonrası
|
||||||
|
|
@ -470,7 +470,7 @@ ya da ziyaretçiye açık yeni bir fiziksel sayfa ürettiğinde.
|
||||||
|
|
||||||
| Kural | Neden |
|
| Kural | Neden |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Metin alanları **dil anahtarı** taşır (`::App.PublicSeo.{Sayfa}.{Alan}`), anahtarlar `LanguagesData.json`'a EN + TR yazılır | Kayıt kültür başına çoğalmaz; çeviri dil dosyasında, tek yerde durur (standing default 4) |
|
| Metin alanları **dil anahtarı** taşır (`::App.PublicSeo.{Sayfa}.{Alan}`), anahtarlar `data/App.Languages.LanguageText.json`'a EN + TR yazılır | Kayıt kültür başına çoğalmaz; çeviri dil dosyasında, tek yerde durur (standing default 4) |
|
||||||
| `Title` 60, `Description` 160 karakteri aşmaz | Arama sonucunda kırpılır |
|
| `Title` 60, `Description` 160 karakteri aşmaz | Arama sonucunda kırpılır |
|
||||||
| Metni uydurma — sayfanın kendi içeriğinden türet | Sayfada yazmayan bir vaat meta'da da yazmamalı |
|
| Metni uydurma — sayfanın kendi içeriğinden türet | Sayfada yazmayan bir vaat meta'da da yazmamalı |
|
||||||
| Kişiye özel / tek kullanımlık akış (`/checkout`, `/payment`, `/success`) → `SeoNoIndex: true`, `SeoIncludeInSitemap: false` | İçeriği olmayan, tekrar ziyaret edilemeyen adres |
|
| Kişiye özel / tek kullanımlık akış (`/checkout`, `/payment`, `/success`) → `SeoNoIndex: true`, `SeoIncludeInSitemap: false` | İçeriği olmayan, tekrar ziyaret edilemeyen adres |
|
||||||
|
|
@ -2417,7 +2417,7 @@ Kurallar:
|
||||||
|
|
||||||
- `export default {ComponentName}` zorunludur; ad dosya/kayıt adıyla birebir aynı olmalıdır.
|
- `export default {ComponentName}` zorunludur; ad dosya/kayıt adıyla birebir aynı olmalıdır.
|
||||||
- Kullanıcıya görünen metni gömme; `translate('App.…')` kullan ve dil anahtarını wizard
|
- Kullanıcıya görünen metni gömme; `translate('App.…')` kullan ve dil anahtarını wizard
|
||||||
dosyasında ya da `LanguagesData.json`'da tanımla.
|
dosyasında ya da `data/App.Languages.LanguageText.json`'da tanımla.
|
||||||
- Veriye erişimde `apiService` kullan; taban adres, token ve tenant başlığı ondan gelir.
|
- Veriye erişimde `apiService` kullan; taban adres, token ve tenant başlığı ondan gelir.
|
||||||
Örneklerde gördüğün `axios.create({ baseURL: 'https://localhost:44344/' })` kalıbı **eski bir
|
Örneklerde gördüğün `axios.create({ baseURL: 'https://localhost:44344/' })` kalıbı **eski bir
|
||||||
kayıttır, taklit etme**.
|
kayıttır, taklit etme**.
|
||||||
|
|
@ -3951,6 +3951,11 @@ Kurallar:
|
||||||
ve dosyada olmayan satır silinmez. Wizard import'unun kapanışı ise `overwriteExisting` ile
|
ve dosyada olmayan satır silinmez. Wizard import'unun kapanışı ise `overwriteExisting` ile
|
||||||
çalışır: var olan satır güncellenir, soft delete izi temizlenir (§7.7). Her hedef kendi unit of
|
çalışır: var olan satır güncellenir, soft delete izi temizlenir (§7.7). Her hedef kendi unit of
|
||||||
work'ündedir; birinin hatası diğerlerini durdurmaz.
|
work'ündedir; birinin hatası diğerlerini durdurmaz.
|
||||||
|
- **Toplu yazım:** hedef tablonun anahtarları tek sorguda okunur; anahtarlı yeni satırlar ekranın
|
||||||
|
insert komutuyla (`InsertCommand` dahil) parametreleri `@p{sıra}_{ad}` olarak yeniden adlandırılıp
|
||||||
|
komut başına en fazla 2000 parametreyle toplu gönderilir. Anahtarsız satırlar tek tek eşleşir.
|
||||||
|
- **Senkronizasyon tüm listeyi yazar:** dosyanın listeyle yeniden yazıldığı durumlarda satırlar
|
||||||
|
sayfa sayfa (10000) okunur; sayfa sınırı satır kaybettirmez.
|
||||||
- **Yazma yönü bayrağa bağlıdır:** `list-form-data/*` ve `list-form-dynamic-api/*` uçlarında
|
- **Yazma yönü bayrağa bağlıdır:** `list-form-data/*` ve `list-form-dynamic-api/*` uçlarında
|
||||||
insert → `SeedSyncInsert`, update → `SeedSyncUpdate`, delete → `SeedSyncDelete`; ImportManager
|
insert → `SeedSyncInsert`, update → `SeedSyncUpdate`, delete → `SeedSyncDelete`; ImportManager
|
||||||
toplu yüklemesi `SeedSyncInsert` bayrağına bakar ve dosyayı listenin tamamıyla yeniden yazar.
|
toplu yüklemesi `SeedSyncInsert` bayrağına bakar ve dosyayı listenin tamamıyla yeniden yazar.
|
||||||
|
|
@ -4068,7 +4073,7 @@ isteniyorsa geçilir.
|
||||||
talep sahiplerine kök + `.Create` yeter.
|
talep sahiplerine kök + `.Create` yeter.
|
||||||
7. **Dil** — menü/başlık/açıklama metinleri wizard dosyasındaki `Language` bölümünden,
|
7. **Dil** — menü/başlık/açıklama metinleri wizard dosyasındaki `Language` bölümünden,
|
||||||
alan başlıkları her `Items` öğesinin `En`/`Tr` alanlarından üretilir.
|
alan başlıkları her `Items` öğesinin `En`/`Tr` alanlarından üretilir.
|
||||||
Ayrıca `LanguagesData.json`'a dokunmaya gerek yoktur.
|
Ayrıca `data/App.Languages.LanguageText.json`'a dokunmaya gerek yoktur.
|
||||||
8. **Teslim** — dosyaları listele, DB Migrate gerektiğini söyle, §11 kontrol listesini geç.
|
8. **Teslim** — dosyaları listele, DB Migrate gerektiğini söyle, §11 kontrol listesini geç.
|
||||||
|
|
||||||
**Bu iskeletin türevleri:** izin talebi (tutar yerine gün sayısı, `days` tarifi), masraf talebi
|
**Bu iskeletin türevleri:** izin talebi (tutar yerine gün sayısı, `days` tarifi), masraf talebi
|
||||||
|
|
|
||||||
|
|
@ -1026,7 +1026,7 @@ bileşenler tek bir sözlükte durur (`RUNTIME_NODE_COMPONENTS`): kanvas onu `Vi
|
||||||
tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon`, `BarcodeScanField`
|
tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon`, `BarcodeScanField`
|
||||||
(`components/shared/BarcodeScanner`), `BarcodeViewer` (`components/shared/BarcodeViewer.tsx`) ve
|
(`components/shared/BarcodeScanner`), `BarcodeViewer` (`components/shared/BarcodeViewer.tsx`) ve
|
||||||
public site parçaları (`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
|
public site parçaları (`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
|
||||||
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `LanguagesData.json`
|
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `data/App.Languages.LanguageText.json`
|
||||||
açıklama anahtarı.
|
açıklama anahtarı.
|
||||||
|
|
||||||
**Barkod.** Okuma ve çizme ayrı yollardır, ikisi de `utils/barcode.ts` içindedir ve `zxing-wasm`
|
**Barkod.** Okuma ve çizme ayrı yollardır, ikisi de `utils/barcode.ts` içindedir ve `zxing-wasm`
|
||||||
|
|
@ -1914,11 +1914,10 @@ Yeni ABP modülü eklemek için:
|
||||||
İki ayrı seed yüzeyi vardır ve karıştırılmamalıdır.
|
İ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/`.
|
**1. Depo içi (kod ile taşınan) seed'ler** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
|
||||||
Platformun kendi ekranları, menüleri, yetkileri ve dil metinleri buradadır; değişiklikleri
|
Platformun kendi ekranları buradadır (dil metinleri `configs/seeds/host/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:
|
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` — ListForm + alan tanımları
|
- `ListFormSeeder_Saas.cs`, `ListFormSeeder_Administration.cs` — ListForm + alan tanımları
|
||||||
- `LanguagesData.json` — dil anahtarları (EN/TR)
|
|
||||||
- `WizardDataSeeder.cs`, `CrudDataSeeder.cs` — aşağıdaki
|
- `WizardDataSeeder.cs`, `CrudDataSeeder.cs` — aşağıdaki
|
||||||
runtime seed dosyalarını okuyup uygulayan seeder'lar
|
runtime seed dosyalarını okuyup uygulayan seeder'lar
|
||||||
- `ListFormSeedDataApplier`, `CrudEndpointSeedApplier` (`Domain/Data/`) — uygulama kuralının tek
|
- `ListFormSeedDataApplier`, `CrudEndpointSeedApplier` (`Domain/Data/`) — uygulama kuralının tek
|
||||||
|
|
@ -1940,6 +1939,7 @@ configs/seeds/
|
||||||
│ │ # App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri
|
│ │ # App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri
|
||||||
│ │ # App.Definitions.{Currency,ContactTitle,AiBot}.json — para birimi, hitap, AI bot
|
│ │ # App.Definitions.{Currency,ContactTitle,AiBot}.json — para birimi, hitap, AI bot
|
||||||
│ │ # App.Notifications.Notification{Types,Rules}.json — bildirim tipi → kuralı
|
│ │ # App.Notifications.Notification{Types,Rules}.json — bildirim 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
|
│ │ # App.SettingDefinitions.json, App.BackgroundWorkers.RecurringJobs.json — ayar tanımları, zamanlanmış işler
|
||||||
│ ├── sql/{object,execute}/ # .sql (SQL Server)
|
│ ├── sql/{object,execute}/ # .sql (SQL Server)
|
||||||
│ └── postgres/{object,execute}/
|
│ └── postgres/{object,execute}/
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modulun paylasilan sozlesmesi. Kullaniciya gorunen metinler platformun kendi
|
/// Modulun paylasilan sozlesmesi. Kullaniciya gorunen metinler platformun kendi
|
||||||
/// <c>PlatformResource</c> kaynagindan (LanguagesData.json) okunur; modul ikinci bir dil
|
/// <c>PlatformResource</c> kaynagindan (configs/seeds/host/data/App.Languages.LanguageText.json) okunur; modul ikinci bir dil
|
||||||
/// kaynagi acmaz.
|
/// kaynagi acmaz.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DependsOn(
|
[DependsOn(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
namespace Sozsoft.Mcp;
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun <c>LanguagesData.json</c>
|
/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun <c>configs/seeds/host/data/App.Languages.LanguageText.json</c>
|
||||||
/// dosyasinda EN + TR olarak durur; koda gomulu metin kullanilmaz.
|
/// dosyasinda EN + TR olarak durur; koda gomulu metin kullanilmaz.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class McpErrorCodes
|
public static class McpErrorCodes
|
||||||
|
|
|
||||||
|
|
@ -211,16 +211,7 @@ public class ListFormSeedDataSynchronizer(
|
||||||
/// <summary>Hedefi listenin tamamiyla yeniden yazar.</summary>
|
/// <summary>Hedefi listenin tamamiyla yeniden yazar.</summary>
|
||||||
private async Task WriteAllRowsAsync(ListForm listForm, SeedTarget target)
|
private async Task WriteAllRowsAsync(ListForm listForm, SeedTarget target)
|
||||||
{
|
{
|
||||||
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
|
var rows = await ReadAllRowsAsync(listForm);
|
||||||
{
|
|
||||||
ListFormCode = listForm.ListFormCode,
|
|
||||||
Skip = 0,
|
|
||||||
Take = SyncAllPageSize,
|
|
||||||
RequireTotalCount = false,
|
|
||||||
RequireGroupCount = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
List<Dictionary<string, object>> rows = ToRows((object)result?.Data);
|
|
||||||
|
|
||||||
if (target.IsFolder)
|
if (target.IsFolder)
|
||||||
{
|
{
|
||||||
|
|
@ -235,13 +226,36 @@ public class ListFormSeedDataSynchronizer(
|
||||||
await WriteSeedFileAsync(target.Path, seedFile);
|
await WriteSeedFileAsync(target.Path, seedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Listenin butun satirlarini sayfa sayfa okur. Tek dosya her yazimda listeyle degistirildigi
|
||||||
|
/// icin tek sayfayla yetinilirse sayfa sinirini asan satirlar dosyadan sessizce duser.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<List<Dictionary<string, object>>> ReadAllRowsAsync(ListForm listForm)
|
||||||
|
{
|
||||||
|
var rows = new List<Dictionary<string, object>>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
Skip = rows.Count,
|
||||||
|
Take = SyncAllPageSize,
|
||||||
|
RequireTotalCount = false,
|
||||||
|
RequireGroupCount = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Dictionary<string, object>> page = ToRows((object)result?.Data);
|
||||||
|
rows.AddRange(page);
|
||||||
|
|
||||||
|
if (page.Count < SyncAllPageSize)
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Klasor modunda her satiri kendi dosyasina yazar ve listede kalmayan satirlarin dosyalarini
|
/// Klasor modunda her satiri kendi dosyasina yazar ve listede kalmayan satirlarin dosyalarini
|
||||||
/// siler — klasor listenin aynasidir.
|
/// siler — klasor listenin aynasidir.
|
||||||
/// <para>
|
|
||||||
/// Temizlik yalnizca liste tek sayfaya sigdiginda yapilir: sayfa sinirina dayanan bir listede
|
|
||||||
/// gorulmeyen satirlarin dosyasi silinirse veri kaybolur.
|
|
||||||
/// </para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task WriteRowFilesAsync(ListForm listForm, SeedTarget target, List<Dictionary<string, object>> rows)
|
private async Task WriteRowFilesAsync(ListForm listForm, SeedTarget target, List<Dictionary<string, object>> rows)
|
||||||
{
|
{
|
||||||
|
|
@ -266,9 +280,6 @@ public class ListFormSeedDataSynchronizer(
|
||||||
written.Add(fileName);
|
written.Add(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rows.Count >= SyncAllPageSize)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var filePath in Directory.EnumerateFiles(target.Path, "*.json"))
|
foreach (var filePath in Directory.EnumerateFiles(target.Path, "*.json"))
|
||||||
{
|
{
|
||||||
if (!written.Contains(Path.GetFileName(filePath)))
|
if (!written.Contains(Path.GetFileName(filePath)))
|
||||||
|
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Sozsoft.Languages.Entities;
|
|
||||||
using Volo.Abp.Data;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Data.Seeds;
|
|
||||||
|
|
||||||
public class LanguageTextsSeedDto
|
|
||||||
{
|
|
||||||
public string ResourceName { get; set; }
|
|
||||||
public string Key { get; set; }
|
|
||||||
public string En { get; set; }
|
|
||||||
public string Tr { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LanguageSeederDto
|
|
||||||
{
|
|
||||||
public List<Language> Languages { get; set; }
|
|
||||||
public List<LanguageTextsSeedDto> LanguageTexts { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LanguageDataSeeder : IDataSeedContributor, ITransientDependency
|
|
||||||
{
|
|
||||||
private readonly IRepository<Language, Guid> _languages;
|
|
||||||
private readonly IRepository<LanguageKey, Guid> _languageKey;
|
|
||||||
private readonly IRepository<LanguageText, Guid> _languagesText;
|
|
||||||
|
|
||||||
public LanguageDataSeeder(
|
|
||||||
IRepository<Language, Guid> languages,
|
|
||||||
IRepository<LanguageKey, Guid> languageKey,
|
|
||||||
IRepository<LanguageText, Guid> languagesText)
|
|
||||||
{
|
|
||||||
_languages = languages;
|
|
||||||
_languageKey = languageKey;
|
|
||||||
_languagesText = languagesText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SeedAsync(DataSeedContext context)
|
|
||||||
{
|
|
||||||
var languages = await _languages.GetListAsync();
|
|
||||||
var keys = await _languageKey.GetListAsync();
|
|
||||||
var texts = await _languagesText.GetListAsync();
|
|
||||||
|
|
||||||
var configuration = new ConfigurationBuilder()
|
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
|
||||||
.AddJsonFile(Path.Combine("Migrations", "LanguagesData.json"))
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var items = configuration.Get<LanguageSeederDto>();
|
|
||||||
|
|
||||||
foreach (var item in items.Languages)
|
|
||||||
{
|
|
||||||
if (!languages.Any(a => a.CultureName == item.CultureName))
|
|
||||||
{
|
|
||||||
await _languages.InsertAsync(new()
|
|
||||||
{
|
|
||||||
CultureName = item.CultureName,
|
|
||||||
UiCultureName = item.UiCultureName,
|
|
||||||
DisplayName = item.DisplayName,
|
|
||||||
IsEnabled = item.IsEnabled,
|
|
||||||
TwoLetterISOLanguageName = new CultureInfo(item.CultureName).TwoLetterISOLanguageName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in items.LanguageTexts)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!keys.Any(a => a.Key == item.Key))
|
|
||||||
{
|
|
||||||
await _languageKey.InsertAsync(new()
|
|
||||||
{
|
|
||||||
Key = item.Key,
|
|
||||||
ResourceName = item.ResourceName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!texts.Any(a => a.CultureName == "en" && a.Key == item.Key))
|
|
||||||
{
|
|
||||||
await _languagesText.InsertAsync(new()
|
|
||||||
{
|
|
||||||
CultureName = "en",
|
|
||||||
Key = item.Key,
|
|
||||||
Value = item.En,
|
|
||||||
ResourceName = item.ResourceName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!texts.Any(a => a.CultureName == "tr" && a.Key == item.Key))
|
|
||||||
{
|
|
||||||
await _languagesText.InsertAsync(new()
|
|
||||||
{
|
|
||||||
CultureName = "tr",
|
|
||||||
Key = item.Key,
|
|
||||||
Value = item.Tr,
|
|
||||||
ResourceName = item.ResourceName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Hata veren Kod:' ResourceName='{item.ResourceName}', Key='{item.Key}' Message='{ex.Message}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -4017,6 +4017,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Language)),
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
|
SeedSyncInsert = true,
|
||||||
|
SeedSyncUpdate = true,
|
||||||
|
SeedSyncDelete = true,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = DefaultFilterRowJson(),
|
FilterRowJson = DefaultFilterRowJson(),
|
||||||
HeaderFilterJson = DefaultHeaderFilterJson(),
|
HeaderFilterJson = DefaultHeaderFilterJson(),
|
||||||
|
|
@ -4026,7 +4030,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
ColumnOptionJson = DefaultColumnOptionJson(),
|
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||||
PermissionJson = DefaultPermissionJson(listFormName),
|
PermissionJson = DefaultPermissionJson(listFormName),
|
||||||
PagerOptionJson = DefaultPagerOptionJson,
|
PagerOptionJson = DefaultPagerOptionJson,
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
// Tabloda audit kolonu yoktur; varsayilan insert degerleri yalnizca anahtari uretir.
|
||||||
|
InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
|
new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
|
}),
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 450, true, true, true, true, false, true),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 450, true, true, true, true, false, true),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() {
|
new() {
|
||||||
|
|
@ -4140,14 +4147,35 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
ListFormCode = listForm.ListFormCode,
|
ListFormCode = listForm.ListFormCode,
|
||||||
CultureName = LanguageCodes.En,
|
CultureName = LanguageCodes.En,
|
||||||
SourceDbType = DbType.Boolean,
|
SourceDbType = DbType.Boolean,
|
||||||
FieldName = "IsEnabled",
|
FieldName = "TwoLetterISOLanguageName",
|
||||||
EditGroupOrderNo = 1,
|
EditGroupOrderNo = 1,
|
||||||
EditOrderNo = 4,
|
EditOrderNo = 4,
|
||||||
|
EditorType2 = EditorTypes.dxTextBox,
|
||||||
|
ColSpan = 1,
|
||||||
|
CaptionName = "App.Listform.ListformField.TwoLetterISOLanguageName",
|
||||||
|
Width = 0,
|
||||||
|
ListOrderNo = 5,
|
||||||
|
Visible = true,
|
||||||
|
IsActive = true,
|
||||||
|
|
||||||
|
AllowSearch = true,
|
||||||
|
ValidationRuleJson = DefaultValidationRuleRequiredJson,
|
||||||
|
ColumnCustomizationJson = DefaultColumnCustomizationJson,
|
||||||
|
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
|
||||||
|
PivotSettingsJson = DefaultPivotSettingsJson
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
ListFormCode = listForm.ListFormCode,
|
||||||
|
CultureName = LanguageCodes.En,
|
||||||
|
SourceDbType = DbType.Boolean,
|
||||||
|
FieldName = "IsEnabled",
|
||||||
|
EditGroupOrderNo = 1,
|
||||||
|
EditOrderNo = 5,
|
||||||
EditorType2 = EditorTypes.dxCheckBox,
|
EditorType2 = EditorTypes.dxCheckBox,
|
||||||
ColSpan = 1,
|
ColSpan = 1,
|
||||||
CaptionName = "App.Listform.ListformField.IsEnabled",
|
CaptionName = "App.Listform.ListformField.IsEnabled",
|
||||||
Width = 0,
|
Width = 0,
|
||||||
ListOrderNo = 5,
|
ListOrderNo = 6,
|
||||||
Visible = true,
|
Visible = true,
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
|
|
||||||
|
|
@ -4186,6 +4214,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText)),
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
|
SeedSyncInsert = true,
|
||||||
|
SeedSyncUpdate = true,
|
||||||
|
SeedSyncDelete = true,
|
||||||
SortMode = GridOptions.SortModeSingle,
|
SortMode = GridOptions.SortModeSingle,
|
||||||
FilterRowJson = DefaultFilterRowJson(),
|
FilterRowJson = DefaultFilterRowJson(),
|
||||||
HeaderFilterJson = DefaultHeaderFilterJson(),
|
HeaderFilterJson = DefaultHeaderFilterJson(),
|
||||||
|
|
@ -4198,7 +4230,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 400, true, true, true, true, false),
|
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 400, true, true, true, true, false),
|
||||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||||
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group" },}),
|
new() { Order=1, ColCount=1, ColSpan=1, ItemType="group" },}),
|
||||||
InsertFieldsDefaultValueJson = DefaultInsertFieldsDefaultValueJson(),
|
InsertFieldsDefaultValueJson = JsonSerializer.Serialize(new FieldsDefaultValue[] {
|
||||||
|
new() { FieldName = "Id", FieldDbType = DbType.Guid, Value = "@NEWID", CustomValueType = FieldCustomValueTypeEnum.CustomKey }
|
||||||
|
}),
|
||||||
|
InsertCommand = LanguageTextInsertCommand(),
|
||||||
CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] {
|
CommandColumnJson = JsonSerializer.Serialize(new CommandColumnDto[] {
|
||||||
new() {
|
new() {
|
||||||
ButtonPosition= UiCommandButtonPositionTypeEnum.Toolbar,
|
ButtonPosition= UiCommandButtonPositionTypeEnum.Toolbar,
|
||||||
|
|
@ -12311,4 +12346,25 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metin, (ResourceName, Key) yabanci anahtariyla <c>LanguageKey</c>'e baglidir; anahtar tablosunun
|
||||||
|
/// ekrani ve seed dosyasi yoktur, bu yuzden eksik anahtar metinle birlikte uretilir. Anahtarin
|
||||||
|
/// kimligi metnin <c>@Id</c>'sidir: iki saglayicida da calisan tek GUID kaynagi odur.
|
||||||
|
/// Metin is anahtari (kultur + kaynak + anahtar) zaten varsa eklenmez — wizard seeder'in ya da
|
||||||
|
/// baska bir kurulumun farkli kimlikle yazdigi metin seed dosyasi uygulanirken kopyalanmasin.
|
||||||
|
/// Son select gercek kaydin anahtarini dondurur; senkronizasyon satiri onunla okur.
|
||||||
|
/// </summary>
|
||||||
|
private static string LanguageTextInsertCommand()
|
||||||
|
{
|
||||||
|
var keyTable = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageKey));
|
||||||
|
var textTable = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText));
|
||||||
|
const string textMatch = "\"CultureName\" = @CultureName AND \"ResourceName\" = @ResourceName AND \"Key\" = @Key";
|
||||||
|
|
||||||
|
return $"INSERT INTO \"{keyTable}\" (\"Id\",\"ResourceName\",\"Key\") SELECT @Id, @ResourceName, @Key " +
|
||||||
|
$"WHERE NOT EXISTS (SELECT 1 FROM \"{keyTable}\" WHERE \"ResourceName\" = @ResourceName AND \"Key\" = @Key); " +
|
||||||
|
$"INSERT INTO \"{textTable}\" (\"Id\",\"CultureName\",\"ResourceName\",\"Key\",\"Value\") SELECT @Id, @CultureName, @ResourceName, @Key, @Value " +
|
||||||
|
$"WHERE NOT EXISTS (SELECT 1 FROM \"{textTable}\" WHERE {textMatch}); " +
|
||||||
|
$"SELECT \"Id\" FROM \"{textTable}\" WHERE {textMatch}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,6 @@
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<None Remove="Migrations\LanguagesData.json" />
|
|
||||||
<Content Include="Migrations\LanguagesData.json">
|
|
||||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -67,7 +70,8 @@ public enum SeedRowOutcome
|
||||||
/// Ekranlardan girilen veri <c>ListForm.SeedFilePath</c> ile bir seed dosyasina aynalanir;
|
/// Ekranlardan girilen veri <c>ListForm.SeedFilePath</c> ile bir seed dosyasina aynalanir;
|
||||||
/// bu servis ayni dosyayi ters yonde uygular. Satirlar anahtar alan uzerinden eslesir: kayit
|
/// bu servis ayni dosyayi ters yonde uygular. Satirlar anahtar alan uzerinden eslesir: kayit
|
||||||
/// yoksa eklenir, varsa dokunulmaz. Boylece her migrate calistiginda veritabanindaki guncel
|
/// yoksa eklenir, varsa dokunulmaz. Boylece her migrate calistiginda veritabanindaki guncel
|
||||||
/// kayitlar dosyadaki eski degerlerle ezilmez ve kopya kayit olusmaz.
|
/// kayitlar dosyadaki eski degerlerle ezilmez ve kopya kayit olusmaz. Eklenecek satirlar toplu
|
||||||
|
/// komutlarla yazilir.
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Sorgu uretimi <see cref="IQueryManager.GenerateQuery"/> ile yapilir; ikinci bir SQL uretici
|
/// Sorgu uretimi <see cref="IQueryManager.GenerateQuery"/> ile yapilir; ikinci bir SQL uretici
|
||||||
/// yazilmaz. Yetki suzgecinden gecen <c>ListFormFieldManager</c> yerine alanlar dogrudan
|
/// yazilmaz. Yetki suzgecinden gecen <c>ListFormFieldManager</c> yerine alanlar dogrudan
|
||||||
|
|
@ -318,9 +322,22 @@ public class ListFormSeedDataApplier(
|
||||||
var updated = 0;
|
var updated = 0;
|
||||||
var skipped = 0;
|
var skipped = 0;
|
||||||
|
|
||||||
|
// Anahtarli satirlarin varligi satir basina sorulmaz: tablonun anahtarlari bir kez okunur,
|
||||||
|
// yeni satirlar toplu komutlarla yazilir. Binlerce satirlik bir dosyada (dil metinleri)
|
||||||
|
// satir basina iki gidis-donus seed suresini dakikalara cikarir.
|
||||||
|
var existingKeys = await ReadExistingKeysAsync(listForm, keyFieldName, repository, connectionString);
|
||||||
|
var batch = new InsertBatch();
|
||||||
|
|
||||||
foreach (var row in seedFiles.SelectMany(a => a.Rows))
|
foreach (var row in seedFiles.SelectMany(a => a.Rows))
|
||||||
{
|
{
|
||||||
switch (await ApplyRowAsync(listForm, fields, keyFieldName, row, repository, connectionString, dataSourceType, overwriteExisting))
|
var outcome = await ApplyRowAsync(
|
||||||
|
listForm, fields, keyFieldName, row, repository, connectionString, dataSourceType,
|
||||||
|
overwriteExisting, existingKeys, batch);
|
||||||
|
|
||||||
|
if (batch.ParameterCount >= InsertBatch.MaxParameterCount)
|
||||||
|
await batch.FlushAsync(repository, connectionString);
|
||||||
|
|
||||||
|
switch (outcome)
|
||||||
{
|
{
|
||||||
case SeedRowOutcome.Inserted:
|
case SeedRowOutcome.Inserted:
|
||||||
inserted++;
|
inserted++;
|
||||||
|
|
@ -334,6 +351,8 @@ public class ListFormSeedDataApplier(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await batch.FlushAsync(repository, connectionString);
|
||||||
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"[{Target}] {Inserted} row(s) inserted, {Updated} row(s) updated, {Skipped} existing row(s) skipped.",
|
"[{Target}] {Inserted} row(s) inserted, {Updated} row(s) updated, {Skipped} existing row(s) skipped.",
|
||||||
target.Path, inserted, updated, skipped);
|
target.Path, inserted, updated, skipped);
|
||||||
|
|
@ -371,6 +390,11 @@ public class ListFormSeedDataApplier(
|
||||||
/// true ise dosyadaki degerlerle guncellenir ve soft delete izleri temizlenir, degilse dokunulmaz.
|
/// true ise dosyadaki degerlerle guncellenir ve soft delete izleri temizlenir, degilse dokunulmaz.
|
||||||
/// Dosyada anahtar yoksa (identity kolonlu tablolar) satir diger alanlarinin tamami uzerinden
|
/// Dosyada anahtar yoksa (identity kolonlu tablolar) satir diger alanlarinin tamami uzerinden
|
||||||
/// eslesir, anahtar veritabanina biraktirilir ve guncelleme yapilamaz.
|
/// eslesir, anahtar veritabanina biraktirilir ve guncelleme yapilamaz.
|
||||||
|
/// <para>
|
||||||
|
/// Anahtarli satirin varligi <paramref name="existingKeys"/> ile bilinir ve insert'i
|
||||||
|
/// <paramref name="batch"/>'e eklenir; anahtarsiz satir eslesmesi butun alanlara baktigi icin
|
||||||
|
/// eskisi gibi tek tek sorgulanir ve yazilir.
|
||||||
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<SeedRowOutcome> ApplyRowAsync(
|
private async Task<SeedRowOutcome> ApplyRowAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
|
@ -380,7 +404,9 @@ public class ListFormSeedDataApplier(
|
||||||
IDynamicDataRepository repository,
|
IDynamicDataRepository repository,
|
||||||
string connectionString,
|
string connectionString,
|
||||||
DataSourceTypeEnum dataSourceType,
|
DataSourceTypeEnum dataSourceType,
|
||||||
bool overwriteExisting)
|
bool overwriteExisting,
|
||||||
|
HashSet<string> existingKeys,
|
||||||
|
InsertBatch batch)
|
||||||
{
|
{
|
||||||
var keyField = fields.FirstOrDefault(a => a.FieldName == keyFieldName);
|
var keyField = fields.FirstOrDefault(a => a.FieldName == keyFieldName);
|
||||||
var keyValue = QueryHelper.GetFormattedValue(
|
var keyValue = QueryHelper.GetFormattedValue(
|
||||||
|
|
@ -393,16 +419,12 @@ public class ListFormSeedDataApplier(
|
||||||
var resolvedRow = await ResolveTokensAsync(row);
|
var resolvedRow = await ResolveTokensAsync(row);
|
||||||
var insertParameters = BuildParameters(fields, resolvedRow, keyFieldName, includeKey: hasKey);
|
var insertParameters = BuildParameters(fields, resolvedRow, keyFieldName, includeKey: hasKey);
|
||||||
|
|
||||||
var match = hasKey
|
// Ayni anahtar dosyada iki kez gecerse ikincisi de "var" sayilir; toplu komutta kopya olusmaz.
|
||||||
? new Dictionary<string, object> { [keyFieldName] = keyValue }
|
var exists = hasKey
|
||||||
: new Dictionary<string, object>(insertParameters);
|
? !existingKeys.Add(ToKeyText(keyValue))
|
||||||
|
: await ExistsAsync(listForm, BuildKeylessMatch(listForm, insertParameters), repository, connectionString);
|
||||||
|
|
||||||
if (!hasKey && PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
if (exists)
|
||||||
{
|
|
||||||
match["TenantId"] = currentTenant.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await ExistsAsync(listForm, match, repository, connectionString))
|
|
||||||
{
|
{
|
||||||
// Anahtarsiz satirin hangi kayda karsilik geldigi bilinmedigi icin guncellenemez.
|
// Anahtarsiz satirin hangi kayda karsilik geldigi bilinmedigi icin guncellenemez.
|
||||||
if (!overwriteExisting || !hasKey)
|
if (!overwriteExisting || !hasKey)
|
||||||
|
|
@ -447,10 +469,84 @@ public class ListFormSeedDataApplier(
|
||||||
}
|
}
|
||||||
|
|
||||||
var insertSql = queryManager.GenerateQuery(listForm, fields, insertParameters, OperationEnum.Insert, dataSourceType, keys);
|
var insertSql = queryManager.GenerateQuery(listForm, fields, insertParameters, OperationEnum.Insert, dataSourceType, keys);
|
||||||
|
|
||||||
|
if (hasKey)
|
||||||
|
batch.Add(insertSql, insertParameters);
|
||||||
|
else
|
||||||
await repository.ExecuteAsync(insertSql, connectionString, insertParameters);
|
await repository.ExecuteAsync(insertSql, connectionString, insertParameters);
|
||||||
|
|
||||||
return SeedRowOutcome.Inserted;
|
return SeedRowOutcome.Inserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hedef tablodaki anahtar degerleri. Anahtar karsilastirmasi metin uzerindendir
|
||||||
|
/// (<see cref="ToKeyText"/>); dosyadaki deger ile veritabanindan donen deger farkli tipte gelir.
|
||||||
|
/// Soft delete edilmis satirlar da doner: kayit tabloda durdugu surece yeniden eklenemez.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<HashSet<string>> ReadExistingKeysAsync(
|
||||||
|
ListForm listForm,
|
||||||
|
string keyFieldName,
|
||||||
|
IDynamicDataRepository repository,
|
||||||
|
string connectionString)
|
||||||
|
{
|
||||||
|
var rows = await repository.QueryAsync(
|
||||||
|
$"SELECT \"{keyFieldName}\" FROM \"{listForm.SelectCommand}\"",
|
||||||
|
connectionString);
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.OfType<IDictionary<string, object>>()
|
||||||
|
.Select(a => a.Values.FirstOrDefault())
|
||||||
|
.Where(a => a != null && a is not DBNull)
|
||||||
|
.Select(ToKeyText)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToKeyText(object value) =>
|
||||||
|
Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Insert komutlarini tek bir komut metninde biriktirir. Her satirin parametreleri
|
||||||
|
/// <c>@p{sira}_{ad}</c> olarak yeniden adlandirilir; boylece ekranin kendi insert komutu
|
||||||
|
/// (<c>InsertCommand</c>) da degismeden toplu calisir. Komut basina parametre sayisi SQL Server
|
||||||
|
/// sinirinin (2100) altinda tutulur.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class InsertBatch
|
||||||
|
{
|
||||||
|
public const int MaxParameterCount = 2000;
|
||||||
|
|
||||||
|
private static readonly Regex ParameterRegex = new(@"(?<![@\w])@(\w+)", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private readonly StringBuilder sql = new();
|
||||||
|
private readonly Dictionary<string, object> parameters = [];
|
||||||
|
private int rowCount;
|
||||||
|
|
||||||
|
public int ParameterCount => parameters.Count;
|
||||||
|
|
||||||
|
public void Add(string rowSql, Dictionary<string, object> rowParameters)
|
||||||
|
{
|
||||||
|
var prefix = $"p{rowCount++}_";
|
||||||
|
|
||||||
|
sql.Append(ParameterRegex.Replace(rowSql, m =>
|
||||||
|
rowParameters.ContainsKey(m.Groups[1].Value) ? $"@{prefix}{m.Groups[1].Value}" : m.Value));
|
||||||
|
sql.Append(";\n");
|
||||||
|
|
||||||
|
foreach (var item in rowParameters)
|
||||||
|
parameters[prefix + item.Key] = item.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task FlushAsync(IDynamicDataRepository repository, string connectionString)
|
||||||
|
{
|
||||||
|
if (rowCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await repository.ExecuteAsync(sql.ToString(), connectionString, parameters);
|
||||||
|
|
||||||
|
sql.Clear();
|
||||||
|
parameters.Clear();
|
||||||
|
rowCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ekranin silme komutunun yazdigi soft delete kolonlarini bulup onlari sifirlayan
|
/// Ekranin silme komutunun yazdigi soft delete kolonlarini bulup onlari sifirlayan
|
||||||
/// parametreleri dondurur. Kolon listesi komuttan okunur; boylece yalnizca gercekten var olan
|
/// parametreleri dondurur. Kolon listesi komuttan okunur; boylece yalnizca gercekten var olan
|
||||||
|
|
@ -476,6 +572,17 @@ public class ListFormSeedDataApplier(
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Anahtarsiz satirin eslesme kosulu: butun alan degerleri, tenant ekranda kapsam da.</summary>
|
||||||
|
private Dictionary<string, object> BuildKeylessMatch(ListForm listForm, Dictionary<string, object> parameters)
|
||||||
|
{
|
||||||
|
var match = new Dictionary<string, object>(parameters);
|
||||||
|
|
||||||
|
if (PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
||||||
|
match["TenantId"] = currentTenant.Id;
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verilen alan degerleriyle eslesen bir kayit hedef tabloda var mi.</summary>
|
/// <summary>Verilen alan degerleriyle eslesen bir kayit hedef tabloda var mi.</summary>
|
||||||
private static async Task<bool> ExistsAsync(
|
private static async Task<bool> ExistsAsync(
|
||||||
ListForm listForm,
|
ListForm listForm,
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
`IListFormArtifact` / `IListFormFieldArtifact` sözleşmeleri üzerinden yapılır.
|
`IListFormArtifact` / `IListFormFieldArtifact` sözleşmeleri üzerinden yapılır.
|
||||||
Ayrıntı: `dotnet.instructions.md` §5.
|
Ayrıntı: `dotnet.instructions.md` §5.
|
||||||
- **Yeni bir yetki** → `PlatformConsts.AppCodes` + `data/Abp.Identity.Permissions.json` + (UI'da kullanılacaksa)
|
- **Yeni bir yetki** → `PlatformConsts.AppCodes` + `data/Abp.Identity.Permissions.json` + (UI'da kullanılacaksa)
|
||||||
`permission.constant.ts` + `LanguagesData.json`. Yetki ekranındaki sıra `AbpPermissions.Order`
|
`permission.constant.ts` + `data/App.Languages.LanguageText.json`. Yetki ekranındaki sıra `AbpPermissions.Order`
|
||||||
(grup için `AbpPermissionGroups.Order`) sütunundan gelir ve seed satırındaki `Order` alanıyla
|
(grup için `AbpPermissionGroups.Order`) sütunundan gelir ve seed satırındaki `Order` alanıyla
|
||||||
yazılır; sıra kardeşler arasındadır — her üst yetkinin altında 1'den başlar, yeni satırı menüdeki
|
yazılır; sıra kardeşler arasındadır — her üst yetkinin altında 1'den başlar, yeni satırı menüdeki
|
||||||
yerine denk gelen numarayla ekle.
|
yerine denk gelen numarayla ekle.
|
||||||
|
|
@ -288,7 +288,8 @@ 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
|
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` +
|
eklenir, yoksa sayfa sitemap'te görünmez. Yeni bir meta alanı = iki entity + `SeoRouteDto` +
|
||||||
`SeoFields` (seeder) + `SeoHead` birlikte.
|
`SeoFields` (seeder) + `SeoHead` birlikte.
|
||||||
- **Yeni bir dil anahtarı** → `LanguagesData.json`; eklemeden önce anahtarın zaten var olup
|
- **Yeni bir dil anahtarı** → `configs/seeds/host/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.
|
olmadığını kontrol et.
|
||||||
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
|
- **Custom component sınıfları** → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca
|
||||||
seed dosyasını görür. Seed dosyası `ui/` dışında olduğu için aynı sınıflar `ui/safelist.txt`
|
seed dosyasını görür. Seed dosyası `ui/` dışında olduğu için aynı sınıflar `ui/safelist.txt`
|
||||||
|
|
@ -359,7 +360,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
şehir → ilçe (`App.Definitions.{CountryGroup,Country,City,District}.json`), bildirim tipi → kuralı
|
şehir → ilçe (`App.Definitions.{CountryGroup,Country,City,District}.json`), bildirim tipi → kuralı
|
||||||
(`App.Notifications.Notification{Types,Rules}.json`). Para birimi, hitap, AI bot
|
(`App.Notifications.Notification{Types,Rules}.json`). Para birimi, hitap, AI bot
|
||||||
(`App.Definitions.{Currency,ContactTitle,AiBot}.json`), ayar tanımları (`App.SettingDefinitions.json`)
|
(`App.Definitions.{Currency,ContactTitle,AiBot}.json`), ayar tanımları (`App.SettingDefinitions.json`)
|
||||||
ve zamanlanmış işler (`App.BackgroundWorkers.RecurringJobs.json`) da buradadır; `HostData.json`
|
ve zamanlanmış işler (`App.BackgroundWorkers.RecurringJobs.json`), dil → metin
|
||||||
|
(`App.Languages.{Language,LanguageText}.json`; `LanguagesData.json` + `LanguageDataSeeder` kaldırıldı) da buradadır; `HostData.json`
|
||||||
kaldırıldı, `HostDataSeeder` yalnızca bağlantı cümlesi appsettings'ten gelen varsayılan veri kaynağını kurar. Dosyalar ekranın
|
kaldırıldı, `HostDataSeeder` yalnızca bağlantı cümlesi appsettings'ten gelen varsayılan veri kaynağını kurar. Dosyalar ekranın
|
||||||
anahtarını (`KeyFieldName`) kullanır; `Guid` anahtarlı satırın `Id`'si iş anahtarından türetilmiş
|
anahtarını (`KeyFieldName`) kullanır; `Guid` anahtarlı satırın `Id`'si iş anahtarından türetilmiş
|
||||||
sabit bir GUID'dir, satır eklerken aynı kuralla üret. `App.Menus.Routes.json` dosyasına
|
sabit bir GUID'dir, satır eklerken aynı kuralla üret. `App.Menus.Routes.json` dosyasına
|
||||||
|
|
|
||||||
136
configs/seeds/host/data/App.Languages.Language.json
Normal file
136
configs/seeds/host/data/App.Languages.Language.json
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
{
|
||||||
|
"ListFormCode": "App.Languages.Language",
|
||||||
|
"KeyFieldName": "Id",
|
||||||
|
"GeneratedAt": "2026-09-24T19:17:56.868760Z",
|
||||||
|
"Order": 1,
|
||||||
|
"Rows": [
|
||||||
|
{
|
||||||
|
"Id": "d9541ac8-390c-5611-8f34-40ee6d3c8c54",
|
||||||
|
"CultureName": "ar",
|
||||||
|
"UiCultureName": "ar",
|
||||||
|
"DisplayName": "العربية",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "ar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "7eb292ce-e8f0-5b41-805a-863dfdbf46f3",
|
||||||
|
"CultureName": "de-DE",
|
||||||
|
"UiCultureName": "de-DE",
|
||||||
|
"DisplayName": "Deutsch",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "72e8ca2c-a01b-5857-a7b6-1ce03e26968d",
|
||||||
|
"CultureName": "en",
|
||||||
|
"UiCultureName": "en",
|
||||||
|
"DisplayName": "English",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "827c6542-089b-5df1-bf7b-bac686530c6a",
|
||||||
|
"CultureName": "es",
|
||||||
|
"UiCultureName": "es",
|
||||||
|
"DisplayName": "Español",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "es"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "8adf7238-64a0-5141-b9ff-fd21c18f5942",
|
||||||
|
"CultureName": "fi",
|
||||||
|
"UiCultureName": "fi",
|
||||||
|
"DisplayName": "Finnish",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "fi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "728c95bb-119c-55b0-b807-f3eb744fba46",
|
||||||
|
"CultureName": "fr",
|
||||||
|
"UiCultureName": "fr",
|
||||||
|
"DisplayName": "Français",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "fr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "6915db67-5a62-5dba-8304-8c51edcb2c6e",
|
||||||
|
"CultureName": "hi",
|
||||||
|
"UiCultureName": "hi",
|
||||||
|
"DisplayName": "Hindi",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "hi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "81aa40ad-2dd7-58c9-8fcf-81abd92a05b5",
|
||||||
|
"CultureName": "hr",
|
||||||
|
"UiCultureName": "hr",
|
||||||
|
"DisplayName": "Croatian",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "hr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "5622a2a2-98e3-56b3-b13c-df260d3f6bf9",
|
||||||
|
"CultureName": "hu",
|
||||||
|
"UiCultureName": "hu",
|
||||||
|
"DisplayName": "Magyar",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "hu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "be35f5d2-5eb9-5ed6-b1bb-4da462a92070",
|
||||||
|
"CultureName": "it",
|
||||||
|
"UiCultureName": "it",
|
||||||
|
"DisplayName": "Italiano",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "2fe16fd9-23ec-520f-971c-3ff529aecb24",
|
||||||
|
"CultureName": "pt-BR",
|
||||||
|
"UiCultureName": "pt-BR",
|
||||||
|
"DisplayName": "Português",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "pt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "d42bd1b1-5aa7-5f7e-a0c9-af13abd971d4",
|
||||||
|
"CultureName": "ru",
|
||||||
|
"UiCultureName": "ru",
|
||||||
|
"DisplayName": "Русский",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "ru"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "0694a5a5-e444-5e55-a36c-0e170c34749e",
|
||||||
|
"CultureName": "sk",
|
||||||
|
"UiCultureName": "sk",
|
||||||
|
"DisplayName": "Slovak",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "sk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "a94d08f3-467a-596d-87b1-a5177ba5d4e8",
|
||||||
|
"CultureName": "sl",
|
||||||
|
"UiCultureName": "sl",
|
||||||
|
"DisplayName": "Slovenščina",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "sl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "a768d2e0-b77d-5ed1-a8b1-93ffa253db36",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"UiCultureName": "tr",
|
||||||
|
"DisplayName": "Türkçe",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "tr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "30dbe4b4-4137-5ab1-ad9f-2d8403232cf7",
|
||||||
|
"CultureName": "zh-Hans",
|
||||||
|
"UiCultureName": "zh-Hans",
|
||||||
|
"DisplayName": "繁體中文",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"TwoLetterISOLanguageName": "zh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
69140
configs/seeds/host/data/App.Languages.LanguageText.json
Normal file
69140
configs/seeds/host/data/App.Languages.LanguageText.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -256,7 +256,7 @@
|
||||||
"SelectOptions": "{}",
|
"SelectOptions": "{}",
|
||||||
"IsVisibleToClients": false,
|
"IsVisibleToClients": false,
|
||||||
"IsInherited": false,
|
"IsInherited": false,
|
||||||
"IsEncrypted": true
|
"IsEncrypted": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "81a25360-a113-5dc3-bc42-8629286abdf8",
|
"Id": "81a25360-a113-5dc3-bc42-8629286abdf8",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue