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:
|
||||
|
||||
- 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
|
||||
surface B below
|
||||
- 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`)
|
||||
- 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 `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.
|
||||
- **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`.
|
||||
|
|
@ -30,8 +30,9 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
|||
- **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.
|
||||
|
||||
- **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.
|
||||
- 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
|
||||
- **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:
|
||||
- {Modul} menu and {Liste} list
|
||||
- {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
|
||||
- ListFormSeeder_{Modul}.cs dosyası oluştur ve list-form seed'ini buraya taşı
|
||||
- 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
|
||||
|
|
@ -87,7 +88,7 @@ Yeni bir liste ekle (ör: {Liste}):
|
|||
- 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
|
||||
- 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
|
||||
|
|
@ -100,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 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}.{Liste}
|
||||
- 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?
|
||||
- [ ] List-form seed'leri doğru modül dosyasında mı?
|
||||
- [ ] 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ı?
|
||||
- [ ] 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?
|
||||
|
|
|
|||
|
|
@ -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ğı. |
|
||||
| **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. |
|
||||
| **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. |
|
||||
| **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. |
|
||||
|
|
@ -1417,7 +1417,7 @@ alanları da doldurur (`lowcode` §0.9).
|
|||
| "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ı |
|
||||
| "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ı |
|
||||
| "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` |
|
||||
|
|
|
|||
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
|
||||
mevcut wizard dosyalarındaki `MenuParentCode` / `PermissionGroupName` değerlerini tara
|
||||
(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.
|
||||
|
||||
### 0.4 Üretim sonrası
|
||||
|
|
@ -470,7 +470,7 @@ ya da ziyaretçiye açık yeni bir fiziksel sayfa ürettiğinde.
|
|||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
|
@ -2417,7 +2417,7 @@ Kurallar:
|
|||
|
||||
- `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
|
||||
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.
|
||||
Örneklerde gördüğün `axios.create({ baseURL: 'https://localhost:44344/' })` kalıbı **eski bir
|
||||
kayıttır, taklit etme**.
|
||||
|
|
@ -3951,6 +3951,11 @@ Kurallar:
|
|||
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
|
||||
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
|
||||
insert → `SeedSyncInsert`, update → `SeedSyncUpdate`, delete → `SeedSyncDelete`; ImportManager
|
||||
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.
|
||||
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.
|
||||
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ç.
|
||||
|
||||
**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`
|
||||
(`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
|
||||
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ı.
|
||||
|
||||
**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.
|
||||
|
||||
**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:
|
||||
|
||||
- `ListFormSeeder_Saas.cs`, `ListFormSeeder_Administration.cs` — ListForm + alan tanımları
|
||||
- `LanguagesData.json` — dil anahtarları (EN/TR)
|
||||
- `WizardDataSeeder.cs`, `CrudDataSeeder.cs` — aşağıdaki
|
||||
runtime seed dosyalarını okuyup uygulayan seeder'lar
|
||||
- `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.{Currency,ContactTitle,AiBot}.json — para birimi, hitap, AI bot
|
||||
│ │ # 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
|
||||
│ ├── sql/{object,execute}/ # .sql (SQL Server)
|
||||
│ └── postgres/{object,execute}/
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Sozsoft.Mcp;
|
|||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[DependsOn(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace Sozsoft.Mcp;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public static class McpErrorCodes
|
||||
|
|
|
|||
|
|
@ -211,16 +211,7 @@ public class ListFormSeedDataSynchronizer(
|
|||
/// <summary>Hedefi listenin tamamiyla yeniden yazar.</summary>
|
||||
private async Task WriteAllRowsAsync(ListForm listForm, SeedTarget target)
|
||||
{
|
||||
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
|
||||
{
|
||||
ListFormCode = listForm.ListFormCode,
|
||||
Skip = 0,
|
||||
Take = SyncAllPageSize,
|
||||
RequireTotalCount = false,
|
||||
RequireGroupCount = false,
|
||||
});
|
||||
|
||||
List<Dictionary<string, object>> rows = ToRows((object)result?.Data);
|
||||
var rows = await ReadAllRowsAsync(listForm);
|
||||
|
||||
if (target.IsFolder)
|
||||
{
|
||||
|
|
@ -235,13 +226,36 @@ public class ListFormSeedDataSynchronizer(
|
|||
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>
|
||||
/// Klasor modunda her satiri kendi dosyasina yazar ve listede kalmayan satirlarin dosyalarini
|
||||
/// 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>
|
||||
private async Task WriteRowFilesAsync(ListForm listForm, SeedTarget target, List<Dictionary<string, object>> rows)
|
||||
{
|
||||
|
|
@ -266,9 +280,6 @@ public class ListFormSeedDataSynchronizer(
|
|||
written.Add(fileName);
|
||||
}
|
||||
|
||||
if (rows.Count >= SyncAllPageSize)
|
||||
return;
|
||||
|
||||
foreach (var filePath in Directory.EnumerateFiles(target.Path, "*.json"))
|
||||
{
|
||||
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)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.Guid,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = true,
|
||||
SeedSyncUpdate = true,
|
||||
SeedSyncDelete = true,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
HeaderFilterJson = DefaultHeaderFilterJson(),
|
||||
|
|
@ -4026,7 +4030,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
ColumnOptionJson = DefaultColumnOptionJson(),
|
||||
PermissionJson = DefaultPermissionJson(listFormName),
|
||||
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),
|
||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||
new() {
|
||||
|
|
@ -4140,14 +4147,35 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
ListFormCode = listForm.ListFormCode,
|
||||
CultureName = LanguageCodes.En,
|
||||
SourceDbType = DbType.Boolean,
|
||||
FieldName = "IsEnabled",
|
||||
FieldName = "TwoLetterISOLanguageName",
|
||||
EditGroupOrderNo = 1,
|
||||
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,
|
||||
ColSpan = 1,
|
||||
CaptionName = "App.Listform.ListformField.IsEnabled",
|
||||
Width = 0,
|
||||
ListOrderNo = 5,
|
||||
ListOrderNo = 6,
|
||||
Visible = true,
|
||||
IsActive = true,
|
||||
|
||||
|
|
@ -4186,6 +4214,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.LanguageText)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.Guid,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = true,
|
||||
SeedSyncUpdate = true,
|
||||
SeedSyncDelete = true,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
HeaderFilterJson = DefaultHeaderFilterJson(),
|
||||
|
|
@ -4198,7 +4230,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 400, true, true, true, true, false),
|
||||
EditingFormJson = JsonSerializer.Serialize(new List<EditingFormDto>() {
|
||||
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[] {
|
||||
new() {
|
||||
ButtonPosition= UiCommandButtonPositionTypeEnum.Toolbar,
|
||||
|
|
@ -12311,4 +12346,25 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
#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>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\LanguagesData.json" />
|
||||
<Content Include="Migrations\LanguagesData.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -67,7 +70,8 @@ public enum SeedRowOutcome
|
|||
/// 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
|
||||
/// 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>
|
||||
/// Sorgu uretimi <see cref="IQueryManager.GenerateQuery"/> ile yapilir; ikinci bir SQL uretici
|
||||
/// yazilmaz. Yetki suzgecinden gecen <c>ListFormFieldManager</c> yerine alanlar dogrudan
|
||||
|
|
@ -318,9 +322,22 @@ public class ListFormSeedDataApplier(
|
|||
var updated = 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))
|
||||
{
|
||||
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:
|
||||
inserted++;
|
||||
|
|
@ -334,6 +351,8 @@ public class ListFormSeedDataApplier(
|
|||
}
|
||||
}
|
||||
|
||||
await batch.FlushAsync(repository, connectionString);
|
||||
|
||||
logger.LogInformation(
|
||||
"[{Target}] {Inserted} row(s) inserted, {Updated} row(s) updated, {Skipped} existing row(s) 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.
|
||||
/// Dosyada anahtar yoksa (identity kolonlu tablolar) satir diger alanlarinin tamami uzerinden
|
||||
/// 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>
|
||||
private async Task<SeedRowOutcome> ApplyRowAsync(
|
||||
ListForm listForm,
|
||||
|
|
@ -380,7 +404,9 @@ public class ListFormSeedDataApplier(
|
|||
IDynamicDataRepository repository,
|
||||
string connectionString,
|
||||
DataSourceTypeEnum dataSourceType,
|
||||
bool overwriteExisting)
|
||||
bool overwriteExisting,
|
||||
HashSet<string> existingKeys,
|
||||
InsertBatch batch)
|
||||
{
|
||||
var keyField = fields.FirstOrDefault(a => a.FieldName == keyFieldName);
|
||||
var keyValue = QueryHelper.GetFormattedValue(
|
||||
|
|
@ -393,16 +419,12 @@ public class ListFormSeedDataApplier(
|
|||
var resolvedRow = await ResolveTokensAsync(row);
|
||||
var insertParameters = BuildParameters(fields, resolvedRow, keyFieldName, includeKey: hasKey);
|
||||
|
||||
var match = hasKey
|
||||
? new Dictionary<string, object> { [keyFieldName] = keyValue }
|
||||
: new Dictionary<string, object>(insertParameters);
|
||||
// Ayni anahtar dosyada iki kez gecerse ikincisi de "var" sayilir; toplu komutta kopya olusmaz.
|
||||
var exists = hasKey
|
||||
? !existingKeys.Add(ToKeyText(keyValue))
|
||||
: await ExistsAsync(listForm, BuildKeylessMatch(listForm, insertParameters), repository, connectionString);
|
||||
|
||||
if (!hasKey && PlatformConsts.IsMultiTenant && listForm.IsTenant)
|
||||
{
|
||||
match["TenantId"] = currentTenant.Id;
|
||||
}
|
||||
|
||||
if (await ExistsAsync(listForm, match, repository, connectionString))
|
||||
if (exists)
|
||||
{
|
||||
// Anahtarsiz satirin hangi kayda karsilik geldigi bilinmedigi icin guncellenemez.
|
||||
if (!overwriteExisting || !hasKey)
|
||||
|
|
@ -447,10 +469,84 @@ public class ListFormSeedDataApplier(
|
|||
}
|
||||
|
||||
var insertSql = queryManager.GenerateQuery(listForm, fields, insertParameters, OperationEnum.Insert, dataSourceType, keys);
|
||||
|
||||
if (hasKey)
|
||||
batch.Add(insertSql, insertParameters);
|
||||
else
|
||||
await repository.ExecuteAsync(insertSql, connectionString, insertParameters);
|
||||
|
||||
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>
|
||||
/// Ekranin silme komutunun yazdigi soft delete kolonlarini bulup onlari sifirlayan
|
||||
/// parametreleri dondurur. Kolon listesi komuttan okunur; boylece yalnizca gercekten var olan
|
||||
|
|
@ -476,6 +572,17 @@ public class ListFormSeedDataApplier(
|
|||
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>
|
||||
private static async Task<bool> ExistsAsync(
|
||||
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.
|
||||
Ayrıntı: `dotnet.instructions.md` §5.
|
||||
- **Yeni bir yetki** → `PlatformConsts.AppCodes` + `data/Abp.Identity.Permissions.json` + (UI'da kullanılacaksa)
|
||||
`permission.constant.ts` + `LanguagesData.json`. 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
|
||||
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.
|
||||
|
|
@ -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
|
||||
eklenir, yoksa sayfa sitemap'te görünmez. Yeni bir meta alanı = iki entity + `SeoRouteDto` +
|
||||
`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.
|
||||
- **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`
|
||||
|
|
@ -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ı
|
||||
(`App.Notifications.Notification{Types,Rules}.json`). Para birimi, hitap, AI bot
|
||||
(`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
|
||||
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
|
||||
|
|
|
|||
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": "{}",
|
||||
"IsVisibleToClients": false,
|
||||
"IsInherited": false,
|
||||
"IsEncrypted": true
|
||||
"IsEncrypted": false
|
||||
},
|
||||
{
|
||||
"Id": "81a25360-a113-5dc3-bc42-8629286abdf8",
|
||||
|
|
|
|||
Loading…
Reference in a new issue