CountryGroup, Countries, Cities, Districs List Seeder geliştirildi
This commit is contained in:
parent
93dd058f1d
commit
c76bcb3283
19 changed files with 31505 additions and 27092 deletions
4
.github/instructions/lowcode.instructions.md
vendored
4
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -2750,7 +2750,7 @@ List ve Custom komponent üretirken SQL nesnesi ihtiyacı dört kalıpta çıkar
|
|||
| Parametreli okuma (kullanıcıya/duruma göre değişen küme) | **Table-Valued Function** | `SelectCommandType: 3`, `SelectCommand: "dbo.Fn(@USERID)"` — parametre token'ları çözülür |
|
||||
| Yazma sonrası iş mantığı (stok, log, durum, kuyruk) | **Stored Procedure** | `*BeforeCommand`/`*AfterCommand` → `EXEC {Modul}_S_{Ad} @ID, @USERID` (§4.8) |
|
||||
| Zamanlanmış iş gövdesi | **Stored Procedure** | `BackgroundWorker.BeforeSp` (§9.10) |
|
||||
| Veri dolumu / migration sonrası düzeltme | **Prosedür olarak `execute/` dosyası** | `AfterAllMigrationsSqlExecutor` dosya adındaki prosedürü çağırır (§8.4) |
|
||||
| Veri dolumu / migration sonrası düzeltme | **Prosedür olarak `execute/` dosyası** | `FinalSeeder` dosya adındaki prosedürü çağırır (§8.4) |
|
||||
|
||||
Sorgu editörünün hazır şablonları (`select`, `insert`, `update`, `delete`, `create-view`,
|
||||
`create-procedure`, `create-scalar-function`, `create-table-function`) her iki diyalektte de
|
||||
|
|
@ -2820,7 +2820,7 @@ dosyalardan kurulur. Mekanik:
|
|||
**`execute/` klasörünün özel sözleşmesi:** buradaki dosyalar iki aşamalıdır.
|
||||
|
||||
- 1. aşama: `SqlDataSeeder` dosyayı çalıştırır → nesne oluşur/güncellenir.
|
||||
- 2. aşama: tüm migration ve seed işlemleri bittikten sonra `AfterAllMigrationsSqlExecutor`
|
||||
- 2. aşama: tüm migration ve seed işlemleri bittikten sonra `FinalSeeder`
|
||||
**dosya adından türettiği stored procedure'ü çağırır**.
|
||||
|
||||
Yani `execute/` altındaki bir dosya, **adı prosedür adıyla aynı olan bir stored procedure
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ Aktif veritabanı sağlayıcısına göre `sql/` (SQL Server) ya da `postgres/`
|
|||
| Klasör | Ne zaman çalışır |
|
||||
| --- | --- |
|
||||
| `{sql\|postgres}/object` | Seed sırasında nesneyi oluşturur/günceller. SqlTableDesigner ve SqlQueryManager, deploy ettiği script'i çalıştığı kapsamın bu klasörüne yazar (host bağlamı → `host/`, tenant seçiliyken → `tenants/{tenantId}/`). |
|
||||
| `{sql\|postgres}/execute` | Nesneyi oluşturur; ayrıca tüm migration ve seed'ler bittikten sonra `AfterAllMigrationsSqlExecutor` dosya adından türettiği procedure'ü çalıştırır (dosya adı = procedure adı, `schema.Procedure.sql` de olur). |
|
||||
| `{sql\|postgres}/execute` | Nesneyi oluşturur; ayrıca tüm migration ve seed'ler bittikten sonra `FinalSeeder` dosya adından türettiği procedure'ü çalıştırır (dosya adı = procedure adı, `schema.Procedure.sql` de olur). |
|
||||
| `wizard` | ListForm Wizard'ın ürettiği `.json` konfigürasyonları; `WizardDataSeeder` kendi kapsamındaki dosyaları okur. |
|
||||
| `crud` | CrudEndpoint konfigürasyonları; `CrudDataSeeder` kendi kapsamındaki dosyaları `ICrudEndpointSeedApplier` üzerinden uygular. Politika **damga karşılaştırmalıdır**: eksik endpoint her zaman eklenir, mevcut bir kayıt yalnızca dosyanın `GeneratedAt` damgası kaydın `LastModificationTime`/`CreationTime` değerinden **yeniyse** güncellenir ya da silinir; aksi halde kayıt korunur ve log'a drift uyarısı düşer. Böylece CRUD Endpoint Manager'dan yapılan bir revizyon depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmaz. Damgası olmayan (eski biçimde yazılmış) dosya hiçbir zaman yeni sayılmaz. |
|
||||
| `data` | Ekranlardan girilen listelerin veri aynası (`{ListFormCode}.json`). `ListForm.SeedFilePath` dolu olan ekranda `SeedSyncInsert/Update/Delete` bayraklarından işaretli olan işlemler dosyaya yansır (`ListFormSeedDataSynchronizer`); migrate/seed sırasında bütün contributor'lar bittikten sonra `ListFormSeedDataApplier` dosyayı geri uygular: anahtarı veritabanında **olmayan** satırları ekler, var olanlara dokunmaz. Aynı uygulayıcı wizard import'unun kapanışında da (`CompleteImport`) yalnızca o import'ta yazılan dosyalar için, `overwriteExisting` açık olarak çalışır — orada var olan satır dosyadaki değerlerle güncellenir. |
|
||||
|
|
@ -1615,6 +1615,7 @@ configs/seeds/
|
|||
│ ├── data/ # {ListFormCode}.json → ListFormSeedDataApplier
|
||||
│ │ # App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı
|
||||
│ │ # Abp.Identity.Permission{Groups,s}.json — yetki tanımları
|
||||
│ │ # App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri
|
||||
│ ├── sql/{object,execute}/ # .sql (SQL Server)
|
||||
│ └── postgres/{object,execute}/
|
||||
├── tenants/{tenantId}/… # aynı düzen, tenant kapsamı
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +0,0 @@
|
|||
[
|
||||
{ "Name": "Afrika" },
|
||||
{ "Name": "Antarktika" },
|
||||
{ "Name": "Asya" },
|
||||
{ "Name": "Avrupa" },
|
||||
{ "Name": "Kuzey Amerika" },
|
||||
{ "Name": "Okyanusya" },
|
||||
{ "Name": "Güney Amerika" }
|
||||
]
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -24,7 +24,7 @@ namespace Sozsoft.Platform.Data.Seeds;
|
|||
/// procedure'ü ilgili bağlamda (host ya da tenant) varlığı doğrulanarak çalıştırır.
|
||||
/// Dosya adı = procedure adı. İsteğe bağlı olarak "schema.Procedure.sql" formatı da desteklenir.
|
||||
/// </summary>
|
||||
public class AfterAllMigrationsSqlExecutor : IAfterAllMigrationsSqlExecutor, ITransientDependency
|
||||
public class FinalSeeder : IFinalSeeder, ITransientDependency
|
||||
{
|
||||
/// <summary>{sql|postgres} altındaki, migration sonrası da çalıştırılacak script klasörünün adı.</summary>
|
||||
public const string DirectoryName = SeedPathResolver.ExecuteFolder;
|
||||
|
|
@ -35,13 +35,13 @@ public class AfterAllMigrationsSqlExecutor : IAfterAllMigrationsSqlExecutor, ITr
|
|||
private readonly IDbContextProvider<PlatformDbContext> _dbContextProvider;
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<AfterAllMigrationsSqlExecutor> _logger;
|
||||
private readonly ILogger<FinalSeeder> _logger;
|
||||
|
||||
public AfterAllMigrationsSqlExecutor(
|
||||
public FinalSeeder(
|
||||
IDbContextProvider<PlatformDbContext> dbContextProvider,
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IConfiguration configuration,
|
||||
ILogger<AfterAllMigrationsSqlExecutor> logger)
|
||||
ILogger<FinalSeeder> logger)
|
||||
{
|
||||
_dbContextProvider = dbContextProvider;
|
||||
_unitOfWorkManager = unitOfWorkManager;
|
||||
|
|
@ -55,7 +55,7 @@ public class AfterAllMigrationsSqlExecutor : IAfterAllMigrationsSqlExecutor, ITr
|
|||
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
_logger.LogInformation("{Directory} directory not found, skipping AfterAllMigrationsSqlExecutor.", directoryPath);
|
||||
_logger.LogInformation("{Directory} directory not found, skipping FinalSeeder.", directoryPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +65,12 @@ public class AfterAllMigrationsSqlExecutor : IAfterAllMigrationsSqlExecutor, ITr
|
|||
|
||||
if (sqlFiles.Length == 0)
|
||||
{
|
||||
_logger.LogInformation("No .sql files found in {Directory}, skipping AfterAllMigrationsSqlExecutor.", directoryPath);
|
||||
_logger.LogInformation("No .sql files found in {Directory}, skipping FinalSeeder.", directoryPath);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"AfterAllMigrationsSqlExecutor started for {Scope}. {Count} procedure(s) to be executed.",
|
||||
"FinalSeeder started for {Scope}. {Count} procedure(s) to be executed.",
|
||||
tenantId == null ? "Host" : $"Tenant {tenantId}",
|
||||
sqlFiles.Length);
|
||||
|
||||
|
|
@ -118,13 +118,13 @@ public class AfterAllMigrationsSqlExecutor : IAfterAllMigrationsSqlExecutor, ITr
|
|||
if (failed.Count > 0)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"AfterAllMigrationsSqlExecutor completed with {FailedCount} failed procedure(s): {Failed}",
|
||||
"FinalSeeder completed with {FailedCount} failed procedure(s): {Failed}",
|
||||
failed.Count,
|
||||
string.Join(", ", failed));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("AfterAllMigrationsSqlExecutor completed. {Count} procedure(s) executed.", sqlFiles.Length);
|
||||
_logger.LogInformation("FinalSeeder completed. {Count} procedure(s) executed.", sqlFiles.Length);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,14 +2,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Public;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
|
@ -90,10 +86,6 @@ public class HostDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
private readonly IRepository<SettingDefinition, Guid> _settings;
|
||||
private readonly IRepository<BackgroundWorker, Guid> _backgroundWorkerRepository;
|
||||
private readonly IRepository<Currency, string> _currencyRepository;
|
||||
private readonly IRepository<CountryGroup, string> _countryGroupRepository;
|
||||
private readonly IRepository<Country, string> _countryRepository;
|
||||
private readonly IRepository<City, Guid> _cityRepository;
|
||||
private readonly IRepository<District, Guid> _districtRepository;
|
||||
private readonly IRepository<ContactTitle, Guid> _contactTitleRepository;
|
||||
|
||||
public HostDataSeeder(
|
||||
|
|
@ -102,10 +94,6 @@ public class HostDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
IRepository<SettingDefinition, Guid> settings,
|
||||
IRepository<BackgroundWorker, Guid> backgroundWorkerRepository,
|
||||
IRepository<Currency, string> currencyRepository,
|
||||
IRepository<CountryGroup, string> countryGroupRepository,
|
||||
IRepository<Country, string> countryRepository,
|
||||
IRepository<City, Guid> cityRepository,
|
||||
IRepository<District, Guid> districtRepository,
|
||||
IRepository<ContactTitle, Guid> contactTitleRepository,
|
||||
IRepository<Route, Guid> routeRepository
|
||||
)
|
||||
|
|
@ -115,10 +103,6 @@ public class HostDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
_settings = settings;
|
||||
_backgroundWorkerRepository = backgroundWorkerRepository;
|
||||
_currencyRepository = currencyRepository;
|
||||
_countryGroupRepository = countryGroupRepository;
|
||||
_countryRepository = countryRepository;
|
||||
_cityRepository = cityRepository;
|
||||
_districtRepository = districtRepository;
|
||||
_contactTitleRepository = contactTitleRepository;
|
||||
}
|
||||
|
||||
|
|
@ -132,255 +116,6 @@ public class HostDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
return builder.Build();
|
||||
}
|
||||
|
||||
public async Task SeedCountyGroupsAsync()
|
||||
{
|
||||
var dbCtx = await _countryRepository.GetDbContextAsync();
|
||||
|
||||
// DB’de mevcut kodları set olarak al
|
||||
var existingCodes = (await dbCtx.Set<CountryGroup>()
|
||||
.Select(c => c.Name)
|
||||
.ToListAsync())
|
||||
.ToHashSet();
|
||||
|
||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
|
||||
using var fs = File.OpenRead(Path.Combine("Migrations", "CountryGroups.json"));
|
||||
|
||||
var buffer = new List<CountryGroup>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryGroupDto>(fs, options))
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla
|
||||
|
||||
// hem DB’de hem JSON içinde duplicate engelle
|
||||
if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name))
|
||||
continue;
|
||||
|
||||
buffer.Add(new CountryGroup(
|
||||
item.Name,
|
||||
item.Name
|
||||
));
|
||||
|
||||
if (buffer.Count >= 1000)
|
||||
{
|
||||
await BulkCountryGroupInsertAsync(buffer);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await BulkCountryGroupInsertAsync(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BulkCountryGroupInsertAsync(List<CountryGroup> entities)
|
||||
{
|
||||
var dbCtx = await _countryGroupRepository.GetDbContextAsync();
|
||||
await dbCtx.BulkInsertAsync(entities, new BulkConfig
|
||||
{
|
||||
BatchSize = 1000
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SeedCountriesAsync()
|
||||
{
|
||||
var dbCtx = await _countryRepository.GetDbContextAsync();
|
||||
|
||||
// DB’de mevcut kodları set olarak al
|
||||
var existingCodes = (await dbCtx.Set<Country>()
|
||||
.Select(c => c.Name)
|
||||
.ToListAsync())
|
||||
.ToHashSet();
|
||||
|
||||
var countryGroupNames = (await dbCtx.Set<CountryGroup>()
|
||||
.Select(c => c.Name)
|
||||
.ToListAsync())
|
||||
.ToHashSet();
|
||||
|
||||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
|
||||
using var fs = File.OpenRead(Path.Combine("Migrations", "Countries.json"));
|
||||
|
||||
var buffer = new List<Country>(capacity: 1000);
|
||||
var seenCodes = new HashSet<string>(); // JSON içindeki duplicate’leri yakalamak için
|
||||
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CountryDto>(fs, options))
|
||||
{
|
||||
if (item == null) continue;
|
||||
if (string.IsNullOrWhiteSpace(item.Name)) continue; // boş kodları atla
|
||||
|
||||
// hem DB’de hem JSON içinde duplicate engelle
|
||||
if (!seenCodes.Add(item.Name) || existingCodes.Contains(item.Name))
|
||||
continue;
|
||||
|
||||
var groupName = string.IsNullOrWhiteSpace(item.GroupName)
|
||||
? null
|
||||
: item.GroupName.Trim();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(groupName) && !countryGroupNames.Contains(groupName))
|
||||
{
|
||||
groupName = null;
|
||||
}
|
||||
|
||||
buffer.Add(new Country(
|
||||
item.Name,
|
||||
item.Name,
|
||||
groupName,
|
||||
item.Currency,
|
||||
item.PhoneCode,
|
||||
item.PhoneNumberMinLength,
|
||||
item.PhoneNumberMaxLength,
|
||||
item.PhoneNumberFormat,
|
||||
item.TaxLabel
|
||||
));
|
||||
|
||||
if (buffer.Count >= 1000)
|
||||
{
|
||||
await BulkCountryInsertAsync(buffer);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await BulkCountryInsertAsync(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BulkCountryInsertAsync(List<Country> entities)
|
||||
{
|
||||
var dbCtx = await _countryRepository.GetDbContextAsync();
|
||||
await dbCtx.BulkInsertAsync(entities, new BulkConfig
|
||||
{
|
||||
BatchSize = 1000
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SeedCitiesAsync()
|
||||
{
|
||||
var dbCtx = await _cityRepository.GetDbContextAsync();
|
||||
|
||||
// 1. Mevcut kayıtları çek (tek sorguda)
|
||||
var existingCities = await dbCtx.Set<City>()
|
||||
.Select(d => new { d.Country, d.Name })
|
||||
.ToListAsync();
|
||||
|
||||
var existingSet = existingCities
|
||||
.Select(d => $"{d.Country}.{d.Name}")
|
||||
.ToHashSet();
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
// 2. JSON’u stream et
|
||||
using FileStream fs = File.OpenRead(Path.Combine("Migrations", "Cities.json"));
|
||||
|
||||
var buffer = new List<City>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<CityDto>(fs, options))
|
||||
{
|
||||
if (item == null) continue;
|
||||
|
||||
var key = $"{item.Country}.{item.Name}";
|
||||
if (existingSet.Contains(key)) continue; // duplicate kontrolü
|
||||
|
||||
buffer.Add(new City(
|
||||
Guid.NewGuid(),
|
||||
item.Country,
|
||||
item.Name,
|
||||
item.PlateCode
|
||||
));
|
||||
|
||||
if (buffer.Count >= 1000) // 3. Batch
|
||||
{
|
||||
await BulkCityInsertAsync(buffer);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await BulkCityInsertAsync(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BulkCityInsertAsync(List<City> entities)
|
||||
{
|
||||
var dbCtx = await _cityRepository.GetDbContextAsync();
|
||||
await dbCtx.BulkInsertAsync(entities, new BulkConfig
|
||||
{
|
||||
PreserveInsertOrder = true,
|
||||
SetOutputIdentity = true,
|
||||
BatchSize = 1000
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SeedDistrictsAsync()
|
||||
{
|
||||
var dbCtx = await _districtRepository.GetDbContextAsync();
|
||||
|
||||
// 1. Mevcut kayıtları çek (tek sorguda)
|
||||
var existingDistricts = await dbCtx.Set<District>()
|
||||
.Select(d => new { d.Country, d.City, d.Name, d.Township, d.PostalCode })
|
||||
.ToListAsync();
|
||||
|
||||
var existingSet = existingDistricts
|
||||
.Select(d => $"{d.Country}:{d.City}:{d.Name}:{d.Township}:{d.PostalCode}")
|
||||
.ToHashSet();
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
// 2. JSON’u stream et
|
||||
using FileStream fs = File.OpenRead(Path.Combine("Migrations", "Districts.json"));
|
||||
|
||||
var buffer = new List<District>(capacity: 5000);
|
||||
await foreach (var item in JsonSerializer.DeserializeAsyncEnumerable<DistrictDto>(fs, options))
|
||||
{
|
||||
if (item == null) continue;
|
||||
|
||||
var key = $"{item.Country}:{item.City}:{item.Name}:{item.Township}:{item.PostalCode}";
|
||||
if (existingSet.Contains(key)) continue;
|
||||
|
||||
buffer.Add(new District(
|
||||
Guid.NewGuid(),
|
||||
item.Country,
|
||||
item.City,
|
||||
item.Name,
|
||||
item.Township,
|
||||
item.PostalCode
|
||||
));
|
||||
|
||||
if (buffer.Count >= 5000) // 3. Batch
|
||||
{
|
||||
await BulkDistrictInsertAsync(buffer);
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await BulkDistrictInsertAsync(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BulkDistrictInsertAsync(List<District> entities)
|
||||
{
|
||||
var dbCtx = await _districtRepository.GetDbContextAsync();
|
||||
await dbCtx.BulkInsertAsync(entities, new BulkConfig
|
||||
{
|
||||
PreserveInsertOrder = true,
|
||||
SetOutputIdentity = true,
|
||||
BatchSize = 5000
|
||||
});
|
||||
}
|
||||
|
||||
public async Task SeedAsync(DataSeedContext context)
|
||||
{
|
||||
var settings = await _settings.GetListAsync();
|
||||
|
|
@ -489,14 +224,6 @@ public class HostDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
await SeedCountyGroupsAsync();
|
||||
|
||||
await SeedCountriesAsync();
|
||||
|
||||
await SeedCitiesAsync();
|
||||
|
||||
await SeedDistrictsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27831,20 +27831,20 @@
|
|||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Definitions.Country.Description",
|
||||
"en": "Country — country definitions and their address/phone rules. Table: Sas_H_Country · host scope · list screen.\nUsage: besides being an address lookup, this table is the source of validation rules. Read these fields when validating an address or phone field instead of writing country rules into the screen.\nFields:\n• Name, GroupName → country name and the country group it belongs to\n• Currency → the country's default currency; the lookup comes from the Currency screen\n• PhoneCode → international dialling code (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → phone validation and display mask; the mask of a phone field on a screen is taken from here\n• ZipRequired, StateRequired → is the postal code / state field mandatory in this country\n• TaxLabel → what the tax number is called in that country (VKN, VAT No, EIN); the caption of the tax field is written from this value\nChain: Country Group › Country › City › District. On a screen the city lookup is filtered by the selected country; the ready pattern is in this screen's own field definitions.\nCaution: initial data is loaded by the seeder from Migrations/Countries.json — a country deleted by hand comes back at the next seed run.",
|
||||
"tr": "Ülke — ülke tanımları ve adres/telefon kuralları. Tablo: Sas_H_Country · host kapsamlı · liste ekranı.\nKullanım: bu tablo yalnızca adres lookup'ı değil, doğrulama kurallarının da kaynağıdır. Adres veya telefon alanı doğrulanırken ülke kurallarını ekrana gömmek yerine bu alanlar okunur.\nAlanlar:\n• Name, GroupName → ülke adı ve bağlı olduğu ülke grubu\n• Currency → ülkenin varsayılan para birimi; lookup Para Birimi ekranından gelir\n• PhoneCode → uluslararası arama kodu (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → telefon doğrulaması ve gösterim maskesi; ekrandaki telefon alanının maskesi buradan alınır\n• ZipRequired, StateRequired → o ülkede posta kodu / eyalet alanı zorunlu mu\n• TaxLabel → vergi numarasının o ülkedeki adı (VKN, VAT No, EIN); vergi alanının başlığı bu değerden yazılır\nZincir: Ülke Grubu › Ülke › Şehir › İlçe. Ekranda şehir lookup'ı seçili ülkeye göre süzülür; hazır kalıp bu ekranın kendi alan tanımlarındadır.\nDikkat: başlangıç verisi seeder tarafından Migrations/Countries.json dosyasından yüklenir — elle silinen ülke, sonraki seed çalışmasında geri gelir."
|
||||
"en": "Country — country definitions and their address/phone rules. Table: Sas_H_Country · host scope · list screen.\nUsage: besides being an address lookup, this table is the source of validation rules. Read these fields when validating an address or phone field instead of writing country rules into the screen.\nFields:\n• Name, GroupName → country name and the country group it belongs to\n• Currency → the country's default currency; the lookup comes from the Currency screen\n• PhoneCode → international dialling code (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → phone validation and display mask; the mask of a phone field on a screen is taken from here\n• ZipRequired, StateRequired → is the postal code / state field mandatory in this country\n• TaxLabel → what the tax number is called in that country (VKN, VAT No, EIN); the caption of the tax field is written from this value\nChain: Country Group › Country › City › District. On a screen the city lookup is filtered by the selected country; the ready pattern is in this screen's own field definitions.\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.Country.json — a country deleted by hand comes back at the next seed run.",
|
||||
"tr": "Ülke — ülke tanımları ve adres/telefon kuralları. Tablo: Sas_H_Country · host kapsamlı · liste ekranı.\nKullanım: bu tablo yalnızca adres lookup'ı değil, doğrulama kurallarının da kaynağıdır. Adres veya telefon alanı doğrulanırken ülke kurallarını ekrana gömmek yerine bu alanlar okunur.\nAlanlar:\n• Name, GroupName → ülke adı ve bağlı olduğu ülke grubu\n• Currency → ülkenin varsayılan para birimi; lookup Para Birimi ekranından gelir\n• PhoneCode → uluslararası arama kodu (90, 49, 1)\n• PhoneNumberMinLength, PhoneNumberMaxLength, PhoneNumberFormat → telefon doğrulaması ve gösterim maskesi; ekrandaki telefon alanının maskesi buradan alınır\n• ZipRequired, StateRequired → o ülkede posta kodu / eyalet alanı zorunlu mu\n• TaxLabel → vergi numarasının o ülkedeki adı (VKN, VAT No, EIN); vergi alanının başlığı bu değerden yazılır\nZincir: Ülke Grubu › Ülke › Şehir › İlçe. Ekranda şehir lookup'ı seçili ülkeye göre süzülür; hazır kalıp bu ekranın kendi alan tanımlarındadır.\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.Country.json dosyasından yüklenir — elle silinen ülke, sonraki seed çalışmasında geri gelir."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Definitions.City.Description",
|
||||
"en": "City — city/province definitions. Table: Sas_H_City · host scope · list screen.\nUsage: the third step of the address chain. Bind the city field of an address screen here with a lookup and filter it by the selected country; do not maintain a separate city list.\nFields:\n• Country → the country the city belongs to; the cascade filter runs over this field\n• Name → city/province name\n• PlateCode → the province's numeric code (in Turkey the plate number); used for sorting, for the region key in reports and for integration matching\nCaution: initial data is loaded by the seeder from Migrations/Cities.json. When a city is added by hand, its districts also have to be added — otherwise the district lookup stays empty on the address screen.",
|
||||
"tr": "Şehir — şehir/il tanımları. Tablo: Sas_H_City · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin üçüncü adımı. Adres ekranının şehir alanı buraya lookup ile bağlanır ve seçili ülkeye göre süzülür; ayrı bir şehir listesi tutulmaz.\nAlanlar:\n• Country → şehrin bağlı olduğu ülke; cascade filtresi bu alan üzerinden çalışır\n• Name → şehir/il adı\n• PlateCode → ilin sayısal kodu (Türkiye'de plaka numarası); sıralama, raporlarda bölge anahtarı ve entegrasyon eşleşmesi için kullanılır\nDikkat: başlangıç verisi seeder tarafından Migrations/Cities.json dosyasından yüklenir. Elle şehir eklendiğinde ilçelerinin de eklenmesi gerekir — aksi hâlde adres ekranında ilçe lookup'ı boş kalır."
|
||||
"en": "City — city/province definitions. Table: Sas_H_City · host scope · list screen.\nUsage: the third step of the address chain. Bind the city field of an address screen here with a lookup and filter it by the selected country; do not maintain a separate city list.\nFields:\n• Country → the country the city belongs to; the cascade filter runs over this field\n• Name → city/province name\n• PlateCode → the province's numeric code (in Turkey the plate number); used for sorting, for the region key in reports and for integration matching\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.City.json. When a city is added by hand, its districts also have to be added — otherwise the district lookup stays empty on the address screen.",
|
||||
"tr": "Şehir — şehir/il tanımları. Tablo: Sas_H_City · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin üçüncü adımı. Adres ekranının şehir alanı buraya lookup ile bağlanır ve seçili ülkeye göre süzülür; ayrı bir şehir listesi tutulmaz.\nAlanlar:\n• Country → şehrin bağlı olduğu ülke; cascade filtresi bu alan üzerinden çalışır\n• Name → şehir/il adı\n• PlateCode → ilin sayısal kodu (Türkiye'de plaka numarası); sıralama, raporlarda bölge anahtarı ve entegrasyon eşleşmesi için kullanılır\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.City.json dosyasından yüklenir. Elle şehir eklendiğinde ilçelerinin de eklenmesi gerekir — aksi hâlde adres ekranında ilçe lookup'ı boş kalır."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Definitions.District.Description",
|
||||
"en": "District — district definitions. Table: Sas_H_District · host scope · list screen.\nUsage: the last step of the address chain. On a screen the district lookup is filtered by the selected city; the postal code can be filled automatically from the selected district.\nFields:\n• Country, City → the chain the district is bound to; the cascade filter runs over the City field\n• Name → district name\n• Township → sub-district/neighbourhood level where it exists\n• PostalCode → the district's postal code; used as the default value of the postal code field on an address screen\nCaution: initial data is loaded by the seeder from Migrations/Districts.json. When one district has several postal codes, keep the most used one here and let the user change it on the screen.",
|
||||
"tr": "İlçe — ilçe tanımları. Tablo: Sas_H_District · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin son adımı. Ekranda ilçe lookup'ı seçili şehre göre süzülür; posta kodu seçilen ilçeden otomatik doldurulabilir.\nAlanlar:\n• Country, City → ilçenin bağlı olduğu zincir; cascade filtresi City alanı üzerinden çalışır\n• Name → ilçe adı\n• Township → varsa belde/mahalle seviyesi\n• PostalCode → ilçenin posta kodu; adres ekranındaki posta kodu alanının varsayılan değeri olarak kullanılır\nDikkat: başlangıç verisi seeder tarafından Migrations/Districts.json dosyasından yüklenir. Bir ilçenin birden çok posta kodu varsa en çok kullanılanı burada tut, kullanıcıya ekranda değiştirme imkânı bırak."
|
||||
"en": "District — district definitions. Table: Sas_H_District · host scope · list screen.\nUsage: the last step of the address chain. On a screen the district lookup is filtered by the selected city; the postal code can be filled automatically from the selected district.\nFields:\n• Country, City → the chain the district is bound to; the cascade filter runs over the City field\n• Name → district name\n• Township → sub-district/neighbourhood level where it exists\n• PostalCode → the district's postal code; used as the default value of the postal code field on an address screen\nCaution: initial data is loaded from configs/seeds/host/data/App.Definitions.District.json. When one district has several postal codes, keep the most used one here and let the user change it on the screen.",
|
||||
"tr": "İlçe — ilçe tanımları. Tablo: Sas_H_District · host kapsamlı · liste ekranı.\nKullanım: adres zincirinin son adımı. Ekranda ilçe lookup'ı seçili şehre göre süzülür; posta kodu seçilen ilçeden otomatik doldurulabilir.\nAlanlar:\n• Country, City → ilçenin bağlı olduğu zincir; cascade filtresi City alanı üzerinden çalışır\n• Name → ilçe adı\n• Township → varsa belde/mahalle seviyesi\n• PostalCode → ilçenin posta kodu; adres ekranındaki posta kodu alanının varsayılan değeri olarak kullanılır\nDikkat: başlangıç verisi configs/seeds/host/data/App.Definitions.District.json dosyasından yüklenir. Bir ilçenin birden çok posta kodu varsa en çok kullanılanı burada tut, kullanıcıya ekranda değiştirme imkânı bırak."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
|
|
|
|||
|
|
@ -1789,6 +1789,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.CountryGroup)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.String,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = false,
|
||||
SeedSyncUpdate = false,
|
||||
SeedSyncDelete = false,
|
||||
DefaultFilter = DefaultFilterJson,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
|
|
@ -1878,6 +1882,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Country)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.String,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = false,
|
||||
SeedSyncUpdate = false,
|
||||
SeedSyncDelete = false,
|
||||
DefaultFilter = DefaultFilterJson,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
|
|
@ -2161,6 +2169,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.City)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.Guid,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = false,
|
||||
SeedSyncUpdate = false,
|
||||
SeedSyncDelete = false,
|
||||
DefaultFilter = DefaultFilterJson,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
|
|
@ -2304,6 +2316,10 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
|
|||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.District)),
|
||||
KeyFieldName = "Id",
|
||||
KeyFieldDbSourceType = DbType.Guid,
|
||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||
SeedSyncInsert = false,
|
||||
SeedSyncUpdate = false,
|
||||
SeedSyncDelete = false,
|
||||
DefaultFilter = DefaultFilterJson,
|
||||
SortMode = GridOptions.SortModeSingle,
|
||||
FilterRowJson = DefaultFilterRowJson(),
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ namespace Sozsoft.Platform.Data.Seeds;
|
|||
/// object/ → nesne oluşturulur/güncellenir. SqlTableDesigner ve
|
||||
/// SqlQueryManager, host bağlamındayken buraya yazar.
|
||||
/// execute/ → nesne oluşturulur, ayrıca tüm migration'lar bittikten sonra
|
||||
/// <see cref="AfterAllMigrationsSqlExecutor"/> tarafından çalıştırılır.
|
||||
/// <see cref="FinalSeeder"/> tarafından çalıştırılır.
|
||||
/// tenants/{tenantId}/ → ilgili tenant veritabanı kapsamı
|
||||
/// {sql|postgres}/
|
||||
/// object/
|
||||
|
|
@ -159,9 +159,9 @@ public class SqlDataSeeder : IDataSeedContributor, ITransientDependency
|
|||
sqlFiles.AddRange(GetOrderedSqlFiles(Path.Combine(providerDirectoryPath, ObjectDirectoryName)));
|
||||
|
||||
// {sql|postgres}/execute: nesneler burada da oluşturulur, ayrıca tüm migration
|
||||
// bittikten sonra AfterAllMigrationsSqlExecutor dosya adı üzerinden çalıştırır.
|
||||
// bittikten sonra FinalSeeder dosya adı üzerinden çalıştırır.
|
||||
sqlFiles.AddRange(GetOrderedSqlFiles(
|
||||
Path.Combine(providerDirectoryPath, AfterAllMigrationsSqlExecutor.DirectoryName)));
|
||||
Path.Combine(providerDirectoryPath, FinalSeeder.DirectoryName)));
|
||||
|
||||
return sqlFiles.ToArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,26 +42,6 @@
|
|||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\CountryGroups.json" />
|
||||
<Content Include="Migrations\CountryGroups.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\Countries.json" />
|
||||
<Content Include="Migrations\Countries.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\Cities.json" />
|
||||
<Content Include="Migrations\Cities.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\Districts.json" />
|
||||
<Content Include="Migrations\Districts.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="Migrations\LanguagesData.json" />
|
||||
<Content Include="Migrations\LanguagesData.json">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Sozsoft.Platform.Data;
|
|||
/// Tüm migration ve seed işlemleri tamamlandıktan sonra çalıştırılacak SQL adımlarını yürütür.
|
||||
/// Uygulaması DbMigrator projesindedir; migrator dışındaki host'larda kayıtlı olmayabilir.
|
||||
/// </summary>
|
||||
public interface IAfterAllMigrationsSqlExecutor
|
||||
public interface IFinalSeeder
|
||||
{
|
||||
/// <param name="tenantId">
|
||||
/// null ise host/, aksi halde tenants/{tenantId}/ kapsamındaki {sql|postgres}/execute klasörü işlenir.
|
||||
|
|
@ -27,7 +27,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly ICurrentTenant _currentTenant;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IEnumerable<IAfterAllMigrationsSqlExecutor> _afterAllMigrationsSqlExecutors;
|
||||
private readonly IEnumerable<IFinalSeeder> _finalSeeders;
|
||||
private readonly IListFormSeedDataApplier _listFormSeedDataApplier;
|
||||
|
||||
public PlatformDbMigrationService(
|
||||
|
|
@ -36,7 +36,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
ITenantRepository tenantRepository,
|
||||
ICurrentTenant currentTenant,
|
||||
IConfiguration configuration,
|
||||
IEnumerable<IAfterAllMigrationsSqlExecutor> afterAllMigrationsSqlExecutors,
|
||||
IEnumerable<IFinalSeeder> finalSeeders,
|
||||
IListFormSeedDataApplier listFormSeedDataApplier)
|
||||
{
|
||||
_dataSeeder = dataSeeder;
|
||||
|
|
@ -44,7 +44,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
_tenantRepository = tenantRepository;
|
||||
_currentTenant = currentTenant;
|
||||
_configuration = configuration;
|
||||
_afterAllMigrationsSqlExecutors = afterAllMigrationsSqlExecutors;
|
||||
_finalSeeders = finalSeeders;
|
||||
_listFormSeedDataApplier = listFormSeedDataApplier;
|
||||
|
||||
Logger = NullLogger<PlatformDbMigrationService>.Instance;
|
||||
|
|
@ -108,7 +108,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
|
||||
Logger.LogInformation("Successfully completed all database migrations.");
|
||||
|
||||
await ExecuteAfterAllMigrationsSqlAsync(tenants);
|
||||
await ExecuteFinalSeedersAsync(tenants);
|
||||
|
||||
Logger.LogInformation("You can safely end this process...");
|
||||
}
|
||||
|
|
@ -119,9 +119,9 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
/// türeyen procedure'lerini ilgili bağlamda çalıştırır. İlgili tenant klasörü yoksa
|
||||
/// o tenant adımı sessizce atlanır.
|
||||
/// </summary>
|
||||
private async Task ExecuteAfterAllMigrationsSqlAsync(List<Tenant> tenants)
|
||||
private async Task ExecuteFinalSeedersAsync(List<Tenant> tenants)
|
||||
{
|
||||
if (!_afterAllMigrationsSqlExecutors.Any())
|
||||
if (!_finalSeeders.Any())
|
||||
{
|
||||
Logger.LogInformation("No SQL executor registered, skipping execute scripts.");
|
||||
return;
|
||||
|
|
@ -133,7 +133,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
|
||||
using (_currentTenant.Change(null))
|
||||
{
|
||||
foreach (var executor in _afterAllMigrationsSqlExecutors)
|
||||
foreach (var executor in _finalSeeders)
|
||||
{
|
||||
Logger.LogInformation("Running SQL scripts for host...");
|
||||
await executor.ExecuteAsync();
|
||||
|
|
@ -145,7 +145,7 @@ public class PlatformDbMigrationService : ITransientDependency
|
|||
{
|
||||
using (_currentTenant.Change(tenant.Id))
|
||||
{
|
||||
foreach (var executor in _afterAllMigrationsSqlExecutors)
|
||||
foreach (var executor in _finalSeeders)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Running SQL scripts for {TenantName} tenant...",
|
||||
|
|
|
|||
|
|
@ -263,8 +263,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
|||
- **Menü ağacı ve yetkiler** → `configs/seeds/host/data/App.Menus.{Routes,Module,Menu}.json` ve
|
||||
`Abp.Identity.Permission{Groups,s}.json`; platformun kendi menü/yetki kayıtları da diğer liste
|
||||
verisiyle aynı mekanizmadan (`ListFormSeedDataApplier`) geçer, ayrı bir seeder yoktur. `Order`
|
||||
alanı bağımlılık sırasını kurar: route → modül → menü, yetki grubu → yetki.
|
||||
`App.Menus.Routes.json` dosyasına dokunma.
|
||||
alanı bağımlılık sırasını kurar: route → modül → menü, yetki grubu → yetki, ülke grubu → ülke →
|
||||
şehir → ilçe (`App.Definitions.{CountryGroup,Country,City,District}.json`). 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 dokunma.
|
||||
- **Public site** → menü `Menu` tablosunda `ModuleId = "Pub"` altında (`PlatformConsts.PublicSite`);
|
||||
`App.Public.Header` hem üst menüyü hem alt bilgideki hızlı bağlantıları besler (ikisi de
|
||||
`PublicNav` düğümü) ve ziyaretçiye uygulama konfigürasyonunun `publicMenus` alanıyla taşınır
|
||||
|
|
|
|||
8984
configs/seeds/host/data/App.Definitions.City.json
Normal file
8984
configs/seeds/host/data/App.Definitions.City.json
Normal file
File diff suppressed because it is too large
Load diff
3258
configs/seeds/host/data/App.Definitions.Country.json
Normal file
3258
configs/seeds/host/data/App.Definitions.Country.json
Normal file
File diff suppressed because it is too large
Load diff
36
configs/seeds/host/data/App.Definitions.CountryGroup.json
Normal file
36
configs/seeds/host/data/App.Definitions.CountryGroup.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"ListFormCode": "App.Definitions.CountryGroup",
|
||||
"KeyFieldName": "Id",
|
||||
"GeneratedAt": "2026-09-18T05:51:38.076057Z",
|
||||
"Order": 1,
|
||||
"Rows": [
|
||||
{
|
||||
"Id": "Afrika",
|
||||
"Name": "Afrika"
|
||||
},
|
||||
{
|
||||
"Id": "Antarktika",
|
||||
"Name": "Antarktika"
|
||||
},
|
||||
{
|
||||
"Id": "Asya",
|
||||
"Name": "Asya"
|
||||
},
|
||||
{
|
||||
"Id": "Avrupa",
|
||||
"Name": "Avrupa"
|
||||
},
|
||||
{
|
||||
"Id": "Kuzey Amerika",
|
||||
"Name": "Kuzey Amerika"
|
||||
},
|
||||
{
|
||||
"Id": "Okyanusya",
|
||||
"Name": "Okyanusya"
|
||||
},
|
||||
{
|
||||
"Id": "Güney Amerika",
|
||||
"Name": "Güney Amerika"
|
||||
}
|
||||
]
|
||||
}
|
||||
19176
configs/seeds/host/data/App.Definitions.District.json
Normal file
19176
configs/seeds/host/data/App.Definitions.District.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue