diff --git a/.github/instructions/dotnet.instructions.md b/.github/instructions/dotnet.instructions.md index 219f3172..6db7d91a 100644 --- a/.github/instructions/dotnet.instructions.md +++ b/.github/instructions/dotnet.instructions.md @@ -129,7 +129,13 @@ Ek kurallar: ile çevirir. Çeviride karşılığı olmayan bir sözdizimi kullanılırsa `PostgreSqlModelSyntax`'a eklenir; immutable olmayan hesaplanan kolon (`GETDATE()`'e bağlı) PG'de yazılamaz, sağlayıcıya göre dallanır. C#'ta üretilen SQL tırnaklı tanımlayıcı ve ANSI ifadelerle yazılır ya da - `DataSourceType`'a göre dallanır; yalnızca T-SQL'de çalışan metin eklenmez. + `DataSourceType`'a göre dallanır; yalnızca T-SQL'de çalışan metin eklenmez. Tanımlayıcı + doğrulama/tırnaklama ve varsayılan şema tek yerdedir: `Sozsoft.Platform.Data.SqlIdentifier` + (Domain.Shared; `IsSafe`, `Quote`, `QuoteQualified`, `DefaultSchema`), nesne kataloğu sorgusu + `SqlCatalogQueries`; ikinci bir regex/`"public" : "dbo"` kopyası yazılmaz. Dapper deposu tek + gerçeklemedir (`DynamicDataRepository`, sağlayıcı `DbProviderFactory` ile seçilir; `Ms`/`Pg` + anahtarlı sınıflar yalnızca DI kaydıdır). `PropertyNameCaseInsensitive` JSON okuması + `PlatformJsonOptions.CaseInsensitive` ile yapılır, çağrı başına `JsonSerializerOptions` üretilmez. - **Mapping**: Mapperly (`[Mapper]` partial class) kullanılır; AutoMapper eklenmez. Mapper'lar servis klasöründeki `{Alan}Mappers.cs` dosyasındadır (modülde `{Modül}ApplicationMappers.cs`), ABP'nin `MapperBase` / diff --git a/.github/instructions/lowcode-reference.instructions.md b/.github/instructions/lowcode-reference.instructions.md index 366b9560..4415f8b3 100644 --- a/.github/instructions/lowcode-reference.instructions.md +++ b/.github/instructions/lowcode-reference.instructions.md @@ -998,7 +998,7 @@ Bir kolonu **başka bir tablodan** çekmek için; view yazmadan tek alanlık joi | `SelectFieldName` | Ekranda gösterilecek alan | | `FieldNameAlias` | Alanın takma adı — doluysa kolon sorgudan **bu adla** döner; grid sütunu `FieldName`'e bağlı olduğu için boş kalır. Ekranda gösterilecek join alanında boş bırak | | `JoinCondition2` | İkinci koşulun bağlacı (`AND` varsayılan \| `OR`) | -| `JoinFieldName2` | İkinci koşulun alanı: `Def_T_City.CultureName` ya da yalın `CultureName` (join tablosuna bağlanır); parçalar ayrı ayrı tırnaklanır | +| `JoinFieldName2` | İkinci koşulun alanı: `Sas_H_City.CultureName` ya da yalın `CultureName` (join tablosuna bağlanır); parçalar ayrı ayrı tırnaklanır | | `BaseTableJoinFieldName2_OrValue` | İkinci koşulun karşı tarafı: **ham** yazılır (tırnaklanmaz) — tablo adlı alan kendi tırnağıyla, sabit `'en'` gibi | Aynı JOIN cümlesi birden çok alanda geçerse bir kez yazılır; join alanına filtre/sıralama @@ -1393,7 +1393,7 @@ işler `data/App.BackgroundWorkers.RecurringJobs.json` ile gelir. | --- | --- | | `MailType` | Şablon tipi etiketi | | `MailSubject` | Mail konusu | -| `MailTemplate` | Razor ile render edilen HTML gövde (`EmailLayout` düzeni içinde) | +| `MailTemplate` | Razor ile render edilen HTML gövde (`EmailLayout` düzeni içinde). Şablon işin adıyla `PlatformTemplateDynamicStore` üzerinden istendiği anda okunur; seçenek değişince yeniden başlatma gerekmez | | `TableName` | Maildeki tabloların tarifi (§11.2) | ### 11.2 `TableName` biçimi @@ -1415,9 +1415,11 @@ birden fazlası `};{` ile birleştirilir: ### 11.3 Kuyruk satırı (`Sas_H_BackgroundWorker_MailQueue`) `TemplateId` (worker), `From`, `To`, `MailParameter`, `TableName`, `TableParameter`, -`Attachment`, `AttachmentParameter`, `RelatedRecordId`, `SendStatus`, `SendTime`, `AwsMessageId`. -Gönderim sonuçları `Sas_H_BackgroundWorker_MailQueueEvents` tablosunda izlenir. Tabloların -ekranı yoktur; satırı kanca/prosedür yazar. +`RelatedRecordId`, `SendStatus`, `SendTime`, `AwsMessageId`. `Attachment` ve `AttachmentParameter` +kolonları eski MS Access rapor eki yolundan kalmadır ve **okunmaz**; mail eki yalnızca +`TableName` içindeki `PDF`/`XLS`/`TXT` tablolarından üretilir (§11.2). Gönderim sonuçları +`Sas_H_BackgroundWorker_MailQueueEvents` tablosunda izlenir. Tabloların ekranı yoktur; satırı +kanca/prosedür yazar. Kuyruğa satır yazma işi genellikle worker'ın `BeforeSp` prosedüründe ya da ekranın `InsertAfterCommand` kancasında yapılır — yani "kayıt onaylanınca mail çıksın" isteği kod @@ -1452,7 +1454,7 @@ Platform tablolarının adı `TableNameResolver` tarafından üretilir: | Ad | `TableNameEnum` üyesi | Örnek: `Sas_H_Currency` (host — tüm kiracılar aynı para birimlerini görür), `Hr_T_Department` -(kiracı başına), `V_Sas_H_Order` (view). Menü ile tablo öneki her zaman örtüşmez: +(kiracı başına), `Hr_V_LeaveSummary` (view — `{Modül}_V_{Ad}`). Menü ile tablo öneki her zaman örtüşmez: `Sas_T_GlobalSearch`, `Sas_T_Branch`, `Sas_T_Forum*`, `Sas_T_IntranetWidget` kiracı tablosudur. Özel view'ler `TableNameResolver.GetFullSpecialViewName` ile `V_{Önek}_{Prefix}_{Ad}` biçiminde üretilir. @@ -1620,7 +1622,7 @@ listesinde `Hr` varsa görür (modül listesi boş kiracıda süzme yoktur — ` | **Intranet › Announcement** | `App.Hr.Intranet.Announcement` (alt formlar Like/Comment) | `Hr_T_Announcement` · `Hr_T_AnnouncementLike` · `Hr_T_AnnouncementComment` | tenant | Duyuru + beğeni + yorum: **ana kayıt / alt etkileşim** kalıbının hazır örneği. | | **Intranet › Survey** | `App.Hr.Intranet.Survey` (alt form Question → Option; Response → Answer) | `Hr_T_Survey` · `Hr_T_SurveyQuestion` · `Hr_T_SurveyQuestionOption` · `Hr_T_SurveyResponse` · `Hr_T_SurveyAnswer` | tenant | Anket motoru; soru–seçenek–yanıt zinciri **iki seviyeli alt form** ihtiyacının referansıdır. | | **Intranet › Social Post** | `App.Hr.Intranet.SocialPost` | `Hr_T_SocialPost` · `Hr_T_SocialComment` · `Hr_T_SocialLike` (+ `SocialPollOption`, `SocialMedia`, `SocialLocation`) | tenant | Kurum içi akış; `IsLiked`, `IsOwnPost` gibi hesaplanmış kolonların view içinde üretilmesine örnek. Alt formlar: `App.Hr.Intranet.SocialMedia`, `SocialComment`, `SocialLike` (`Id` → `SocialPostId`); anket seçenekleri (`App.Hr.Intranet.SocialPollOption`) medyaya bağlı olduğu için `SocialMedia` ekranının alt formudur (`Id` → `SocialMediaId`). | -| **Intranet › Event Type / Category / Event** | `App.Hr.Intranet.Events.EventType` · `.EventCategory` · `.Event` | `Hr_T_EventType` · `Hr_T_EventCategory` · `Hr_T_Event` · `Hr_T_EventPhoto` · `Hr_T_EventLike` · `Hr_T_EventComment` | tenant | Etkinlik yönetimi; foto ve katılımcı sayacı olan kayıt kalıbı. `EventLike`/`EventComment` alt formdur, `EventPhoto` menüde yer almaz; yetkileri `App.Hr.Intranet.Events.Event` altındadır. `Hr_T_EventPhoto` tablosu `sql/object/Hr_T_EventPhoto.sql` ile oluşturulur. | +| **Intranet › Event Type / Category / Event** | `App.Hr.Intranet.Events.EventType` · `.EventCategory` · `.Event` | `Hr_T_EventType` · `Hr_T_EventCategory` · `Hr_T_Event` · `Hr_T_EventLike` · `Hr_T_EventComment` | tenant | Etkinlik yönetimi; foto ve katılımcı sayacı olan kayıt kalıbı. `EventLike`/`EventComment` alt formdur; yetkileri `App.Hr.Intranet.Events.Event` altındadır. Ayrı bir fotoğraf tablosu yoktur: fotoğraflar `Hr_T_Event.Photos` kolonunda tutulur (bkz. `docs/modules/App.Hr.md`). | | **Intranet › Documents** | `App.Hr.Intranet.Document` · `/admin/intranet-documents` | Dosya yöneticisi (`intranet` / `intranet\|_passive` klasörleri) | tenant | Kurum içi doküman paylaşımı — aktif/pasif klasör, yükleme, taşıma, toplu silme. Doküman listesi isteği yeni tablo gerektirmez. | **Şube bilgisi:** `Department.BranchId` ve `JobPosition.BranchId` (`Guid`, **zorunlu** — kolon `NOT NULL`, formda diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 8bf5b950..8e413fbf 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -2237,7 +2237,7 @@ Düzenleme tabloya hiç dokunmaz. bırakılan (`Kept`) satırın sahibi silinen wizard'dı ve sonradan hiçbir silme onu kaldırmaz. 1. **Alt formlar önce, onları taşıyan ekran sonra** — en derindeki alt form ilk gider - (`HealthCardWorkHistory` → `HealthCardGeneral` → … → `HealthCard`). Bağlı wizard'ı olan wizard + (`Hr_HealthCardHistory` → `Hr_HealthCardResult` → … → `Hr_HealthCard`). Bağlı wizard'ı olan wizard zaten silinemez (yukarıda). 2. **Aynı seviyede en son deploy edilen önce** — paylaşılan menü seviyesi, ara yetki, izin grubu ve dil anahtarı onu **ilk** üreten wizard'a aittir; o wizard, diğerleri gidip satır kullanımsız @@ -2258,7 +2258,7 @@ işlemde siler. Onay diyaloğu sırayı ve tabloları gösterir: yüzünden korunmaz (`WizardRecordCleaner.CleanAsync(seed, batchListForms)`); tablo kullanımında da talebin ekranları sayılmaz (`WizardTableCleaner.AnalyzeAsync(seed, batchListForms)`). - **Tablolar en sonda** düşürülür: bütün ekranlar gittikten sonra kullanım yeniden ölçülür; aynı - tabloyu paylaşan alt formlar (`Hr_T_HealthCard` gibi) tabloyu bir kez listeler. Bağlantılar veri + tabloyu paylaşan alt formlar (`Inv_T_Transfer`'ı paylaşan `TransferOther`/`TransferNote` gibi) tabloyu bir kez listeler. Bağlantılar veri kaynağı kaydı silinmeden önce çözülür. Tablo seçmek yine `App.SqlQueryManager` ister. - **Talebin Custom Endpoint'leri de gider:** tablo talebe aitse (`create_table`/tasarımcı scripti var, talep dışında ekran kullanmıyor) onu sorgulayan Custom Endpoint'ler tabloyu tutmaz; veritabanından @@ -2807,12 +2807,12 @@ adıma döner. **Adım 2 — Menü.** `WizardName` her yolda zorunludur. Wizard adı seed dosyasının adıdır ve bütün modüllerde tekil olmak zorundadır; aynı ekran adı birden çok modülde geçebilir (İK ve Satınalma altında `RejectionReason`). Bu yüzden yeni wizard'ın adı modül önekini taşır: `{ModuleId}_{Ad}` -(`Hr_RejectionReason`, `Prc_RejectionReason`, `Inventory_Warehouse`). Önek `MenuParentModuleId`'den +(`Hr_RejectionReason`, `Prc_RejectReason`, `Inventory_Warehouse`). Önek `MenuParentModuleId`'den gelir ve deploy'da eklenir (`ListFormWizardDto.ResolveWizardName`); ad zaten önekle başlıyorsa tekrar eklenmez. `configs/seeds/wizard/` altındaki bütün dosyalar bu düzendedir; öneksiz ad yalnızca eski bir export arşivinden gelebilir ve o wizard düzenlenirken adı korunur. Ekran, menü ve yetki kodları (`ListFormCode`, `MenuCode`) önekten etkilenmez. Üretim planında ve `replaceFileName`'de -dosya adı önekli yazılır (`deploy_wizard: Prc_RejectionReason.json`). `CreateMenu` anahtarı kapatılırsa menü +dosya adı önekli yazılır (`deploy_wizard: Prc_RejectReason.json`). `CreateMenu` anahtarı kapatılırsa menü alanlarının tamamı gizlenir ve menü/üst menü kaydı üretilmez (alt form ekranı kalıbı, §4.6). Açıkken zorunlular: üst menü (ağaçtan seçilir ya da yeni kök tanımlanır), `MenuCode`, EN+TR menü etiketi, izin grubu (listeden seçilir ya da yeni ad; görünen adları EN+TR yazılır), `MenuIcon` @@ -3200,7 +3200,7 @@ DevExtreme editörüne (ve platformun kendi editörlerine) geçirilen JSON. UI s | --- | --- | --- | | `boolean` | üç durumlu seçim | `true` / `false` / (yok) | | `number` | sayı | `12` | -| `text` | metin | `"dd/MM/yyyy"` | +| `text` | metin | `"shortDate"` | | `select` | listeden seçim (bilinmeyen mevcut değer korunur) | seçilen değer | | `size` | sayı ya da CSS | `200` veya `"100%"` | | `stringList` | virgülle ayrılmış | `["key","name"]` | @@ -3242,8 +3242,8 @@ Preset mevcut JSON ile **birleşir**, diğer ayarları silmez; aynı anahtar var | Salt okunur / Pasif | `{"readOnly":true}` · `{"disabled":true}` | | Sağa hizalı | `{"inputAttr":{"style":"text-align: right"}}` | | 2 ondalık · Artırmalı sayı · Para | `format.fixedPoint(2)` · + `useMaskBehavior` + `showSpinButtons` · `format.currency TRY` | -| Tarih · Tarih-saat · Saat | `type`+`displayFormat`+`dateSerializationFormat` üçlüsü (saatte ayrıca `pickerType:list`, `interval`, `width:100%`) | -| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage`, `placeholder`, `useMaskedValue:false` — token değil sabit maskedir; `phoneGlobal` token'ının preset'i yoktur, `format` alanına elle yazılır | +| Tarih · Tarih-saat · Saat | `type`+`displayFormat`+`dateSerializationFormat` üçlüsü (saatte ayrıca `pickerType:list`, `interval`, `width:100%`). `displayFormat` DevExtreme'in adlı biçimidir — `shortDate` · `shortDateShortTime` · `shortTime` — aktif dilin düzeniyle çizilir; `dd/MM/yyyy` gibi sabit desen yazılmaz | +| Telefon maskesi | `mask`, `maskRules.X`, `maskInvalidMessage` (dil anahtarı, ör. `App.PresetPhoneMask.InvalidMessage`; liste/form aktif dile çevirir), `placeholder`, `useMaskedValue:false` — token değil sabit maskedir; `phoneGlobal` token'ının preset'i yoktur, `format` alanına elle yazılır | | Telefon (Türkiye) · (uluslararası) · (ülkeye göre) | `{"format":"phoneTR"}` · `{"format":"phoneIntl"}` · `{"format":"phoneCountry","phoneCountryField":"Country"}` — üçü de `showClearButton:true` ekler; ayrıntı §6.4.1 | | Otomatik büyüyen metin | `autoResizeEnabled`, `minHeight:80`, `maxHeight:320` | | Aranabilir liste | `searchEnabled`, `searchMode:contains`, `searchTimeout`, `showClearButton` | @@ -3297,7 +3297,7 @@ varsayılanı ezer. | Çok satırlı | `EditorOptions.Multiline(80)` | `{"height":80}` | | Sabit ondalık | `EditorOptions.Number(2)` | format + maske davranışı + spin | | Yüzde | `EditorOptions.Percent()` | `{"format":"#0.##'%'","useMaskBehavior":true,"showSpinButtons":true}` | -| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | Tarih/tarih-saat: `format` + `displayFormat` (UI preset'inden farklı olarak `type` ve `dateSerializationFormat` yazmaz). Saat: `type:time`, `pickerType:list`, `displayFormat:HH:mm`, `dateSerializationFormat`, `interval`, `width:100%` — UI preset'iyle aynı | +| Tarih / tarih-saat / saat | `EditorOptions.Date()` · `DateTime()` · `Time(15)` | UI preset'leriyle (`date` · `dateTime` · `timePicker`) birebir aynı: `type` + `displayFormat` (`shortDate` · `shortDateShortTime` · `shortTime`, DevExtreme adlı biçim — sabit desen yok) + `dateSerializationFormat`; saatte ayrıca `pickerType:list`, `interval`, `width:100%` | | Telefon | `EditorOptions.Phone()` | `format:"phoneGlobal"` + `mask` + `maskInvalidMessage` + `useMaskedValue:false` + `maskRules` + `placeholder` | | Telefon (ülkeye göre) | `EditorOptions.PhoneByCountry("Country")` | `{"format":"phoneCountry","phoneCountryField":"Country","showClearButton":true}` | | Kaydırıcı | `EditorOptions.Slider()` | `{"tooltip":{"enabled":true}}` | diff --git a/claude.md b/CLAUDE.md similarity index 97% rename from claude.md rename to CLAUDE.md index 2ef58a89..514d9cb0 100644 --- a/claude.md +++ b/CLAUDE.md @@ -149,6 +149,11 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → sözleşmesini göstermeli. 4. **Kullanıcıya görünen her metin** → dil anahtarı üzerinden, EN + TR birlikte. Koda gömülü metin önerme. + **Kullanıcıya görünen her tarih ve sayı** → aktif kültürle: UI'da `localeFormat.ts` + (`formatLocaleDate/Time/DateTime/Number/Currency/Fixed`), API'de `CultureFormat` + `CultureInfo.CurrentCulture`; + DevExtreme kolon/editörü adlı biçim kullanır (`shortDate`, `shortDateShortTime`, `shortTime`, `decimal`, + `fixedPoint`, `currency`). Sabit desen (`DD.MM.YYYY`, `dd/MM/yyyy`), `'tr-TR'` sabiti, `toLocaleString()` ve + görüntülenen ondalık için `toFixed` yazılmaz; makine biçimi (ISO yük, ``) `ui/README.md`'de. 5. **Runtime'da üretilen her artefakt** (wizard, custom component, crud endpoint) → ilgili seed dosyası da yazılır/güncellenir. Veritabanı sıfırlandığında geri gelmeyen bir şey üretme. 6. **Yeni ekran/komponent talebi — yazılı ya da görsel** → dosya yazmadan önce **keşif turu**: @@ -258,12 +263,12 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → 21. **Demo veri ≠ seed senkronu** → `SeedData: true` yalnızca `SeedFilePath` açar; `SeedSyncInsert/Update/Delete` istekte açıkça istenmedikçe `false`'tur (wizard cevabı `SeedSync`). 22. **Wizard adı modül önekli** → yeni wizard'ın adı (= seed dosyasının adı) `{ModuleId}_{Ad}`'dır - (`Prc_RejectionReason.json`, `Hr_RejectionReason.json`); aynı ekran adı başka modülde de geçebildiği + (`Prc_RejectReason.json`, `Hr_RejectionReason.json`); aynı ekran adı başka modülde de geçebildiği için öneksiz ad dosya çakışması (`'X' adında bir Wizard zaten var`) üretir. Önek deploy'da `MenuParentModuleId`'den eklenir (`ListFormWizardDto.ResolveWizardName`); analiz ve üretim planında dosya adı önekli yazılır. `configs/seeds/wizard/` altındaki bütün dosyalar bu düzendedir; öneksiz dosya yalnızca eski bir export arşivinden gelebilir ve düzenlenirken adı korunur. Ayrıntı: - `lowcode.instructions.md` §4.4 Adım 2. + `lowcode.instructions.md` §4.10 (Adım 2 — Menü). 23. **Otomatik numara Sequence'tan** → wizard/MCP üretiminde talep otomatik numara istediğinde ya da analiz bir alanın (`ProjectNo`, `OrderNo`, `RequestNo` …) otomatik numaralanmasına karar verdiğinde alan `App.Definitions.Sequence` numaratörüne bağlanır; elle girilen numara/kod alanı bu kurala @@ -312,7 +317,10 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → - **`Sequence.GetNextSequence` (C#) ↔ `Adm_P_SequenceNext.sql` (SQL Server + PG)** — numaratörün biçim (`FormatTemplate` token'ları, `PaddingSize`) ve sıfırlama (`ResetPeriod`/`LastPeriodKey`) kuralı iki yerde yazılır: formdan açılan kayıt C#'tan, yordamın açtığı belge (tekliften sipariş, alternatif teklif) SQL'den numara alır. - Biri değişirse diğeri de değişir; yoksa aynı numaratör iki farklı biçimde numara üretir. + Biri değişirse diğeri de değişir; yoksa aynı numaratör iki farklı biçimde numara üretir. Eşzamanlılık korumaları + ayrıdır ve ikisi de kalır: yordam satır kilidi (`UPDLOCK`/`FOR UPDATE`), C# karşılaştır-ve-yaz + (`ISequenceRepository.TryAdvanceAsync`, `SequenceManager` yeniden dener) — entity izlenerek okunup `UpdateAsync` + ile yazılmaz, iki form aynı numarayı alırdı. - **Liste popup'ı (Grid/Tree) ↔ form sayfası (`FormDevExpress`)** — editör yetenekleri ortak yardımcılardadır (`list/shared/editingForm.ts`, `editingFormScripts.ts`, `cascade.ts`); TagBox/GridBox/ImageUpload/ImageViewer/Rating'in tek uygulaması `form/editors/*`, liste hücresi yalnızca `list/editors/cellEditors.tsx` adaptörüdür. Bir tarafa @@ -620,8 +628,12 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` → kaydın `LastModificationTime`/`CreationTime` değerinden yeniyse güncellenir/silinir. Migrate kullanıcının runtime revizyonunu ezmez. Yeni bir seeder yazarken bu kuralı taklit et: **migrate varsayılanı eklemektir, ezmek değil.** -- **Modül = menü = tablo öneki = yetki kökü** → kök menüler `Sas` (`App.Saas`), `Adm` - (`App.Administration`), `Hr` (`App.Hr`, İK) ve `Pub` modüllerindedir. Bir ekranı modüller arası +- **Modül = menü = tablo öneki = yetki kökü** → platformun C# seeder'lı kök menüleri `Sas` (`App.Saas`), + `Adm` (`App.Administration`), `Hr` (`App.Hr`, İK) ve `Pub` (`App.Public.Header`) modüllerindedir + (`TableNameResolver.MenuPrefix` yalnızca bunları bilir); wizard ile üretilen modüller kendi kök menüsü ve + tablo önekiyle gelir — `Prj` (`App.Prj`), `Prc` (`App.Prc`), `Inventory` (`App.Inventory`, `Inv_`), + `Mm` (`App.Maintenance`), `Production` (`App.Production`, `Prd_`); tam liste `data/App.Menus.Menu.json` + kök satırlarıdır. Bir ekranı modüller arası taşımak birlikte yapılır: `TableNameResolver` (`MenuPrefix`) + `AppCodes` (Seed/Platform) + `ListFormSeeder_{Saas,Administration,Hr}.cs` + `data/{ListFormCode}.json` adı + menü/yetki/dil satırları + route `Authority`. Yetki `Modules` değeri: Saas/Administration `Sas|Adm`, İK `Sas|Hr` diff --git a/README.md b/README.md index 4a5fe050..cc66b6b5 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ DevExtreme bileşenleri bu metadata'ya göre kendini kurar. | --- | --- | | .NET | `net10.0` (SDK 10.0.300, `global.json` ile sabit) | | ABP Framework | 10.0.0 (Identity, TenantManagement, PermissionManagement, AuditLogging, OpenIddict, BlobStoring, BackgroundWorkers) | -| Veritabanı | SQL Server 2022 veya PostgreSQL 17 (`PlatformConsts.DefaultDatabaseProvider`) | +| Veritabanı | SQL Server 2022 veya PostgreSQL 17 (`SettingsConsts.DefaultDatabaseProvider`) | | ORM | Entity Framework Core | | Kimlik | OpenIddict (password + refresh token, `/connect/token`) | | Cache | Redis (opsiyonel; `Redis:IsEnabled`) | @@ -169,11 +169,11 @@ sozsoft-platform/ ├── ui/ # React uygulaması │ └── src/ │ ├── routes/ # dynamicRouter, dynamicRouteLoader, route.constant -│ ├── views/ # list, form, admin, developerKit, public, intranet, forum, report, auth, setup +│ ├── views/ # list, form, admin, developerKit, public, intranet, forum, report, auth, setup, settings, menu, branch, purchasing, ai, version, shared │ ├── components/ # ui (tasarım sistemi), template (layout), importManager, visualDesigner, componentEditor │ ├── services/ # API istemcileri (her modül için ayrı servis dosyası) │ ├── store/ # easy-peasy modelleri (auth, theme, locale, abpConfig, admin) -│ ├── contexts/ # ComponentContext (runtime bileşenler), Dialog, Scroll +│ ├── contexts/ # ComponentContext (runtime bileşen kaydı), DemoContext │ └── utils/ # editorScriptRuntime, workflow, hooks, hoc, biçimlendirme yardımcıları ├── configs/ │ ├── docker/ # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler @@ -184,6 +184,7 @@ sozsoft-platform/ │ └── ai/ # AI entegrasyonu (n8n akış tanımları, Ollama model kurulum notları) ├── .github/instructions/ # ai.instructions.md (bağlayıcı platform kuralları) │ # lowcode.instructions.md (artefakt üretim referansı: şemalar, örnekler) +│ # lowcode-reference.instructions.md (yetenek envanteri: JSON kolon sözleşmeleri, hazır ekranlar) │ # dotnet.instructions.md (api/ için bağlayıcı .NET/ABP kod standardı) │ # list.instructions.md (modül/liste ekleme prosedürü) ├── CLAUDE.md # Claude Code için operasyon kuralları @@ -368,7 +369,6 @@ okur. Kiracı tablolarının satırları, kiracı adıyla kayıtlı veri kaynağ | --- | --- | | `VITE_API_URL` | API kök adresi. | | `VITE_CDN_URL` | Dosya/CDN kök adresi. | -| `VITE_REACT_APP_VERSION` | `package.json` sürümünden beslenir. | | `VITE_AI_URL` | AI asistanının n8n webhook kökü. | | `VITE_USE_POLLING` | Dosya izlemede polling (WSL/Docker senaryoları). | | `VITE_PWA_DEV` | Geliştirmede service worker'ı açar (varsayılan kapalı). | @@ -1283,11 +1283,14 @@ ekranın kendi ayrıntılı açıklamasını gösterir (`DescTr`/`DescEn`, `lowc | Doküman | Kök menü | Bölümler | | --- | --- | --- | +| [App.Saas.md](configs/seeds/docs/modules/App.Saas.md) | Saas (`App.Saas`, host) | Tanımlamalar, Public Site Yönetimi, Kiracılar ve Şubeler, Ayar Tanımları, Diller, Veri Kaynakları, Arka Plan İşleri, Menüler, Developer Kit, Forum Yönetimi (elle yazılır; ekranlar C# seeder ile kurulur) | | [App.Administration.md](configs/seeds/docs/modules/App.Administration.md) | Yönetim (`App.Administration`) | Tanımlamalar (Kontaklar: satış ve satınalmanın ortak müşteri/tedarikçi kartı ve sekmeleri, kontak etiketleri) | | [App.Hr.md](configs/seeds/docs/modules/App.Hr.md) | İnsan Kaynakları (`App.Hr`) | Tanımlamalar (dizin + genel tanımlar), Organizasyon ve Yetkinlik, Personel Kaydı (Kimlik › Kullanıcılar › detay), İşe Alım, Çalışma Takvimi ve Vardiyalar, İzin Yönetimi, Puantaj ve Devam Takibi, Sağlık Kartları, Eğitimler, Performans Yönetimi, Disiplin Dosyaları, Araçlar, Ziyaretçiler, Olay Günlüğü, Intranet | +| [App.Prj.md](configs/seeds/docs/modules/App.Prj.md) | Proje (`App.Prj`) | Tanımlamalar, İş Paketleri, Günce Listesi, Projeler, Adam/Ay Oranları, Kaynak Listesi, Personel Adam/Ay Oranları, Proje Bütçe Raporu | | [App.Prc.md](configs/seeds/docs/modules/App.Prc.md) | Satınalma (`App.Prc`) | Tanımlamalar (ödeme/teslimat koşulları, ödeme/teslim yöntemleri, red nedenleri) · Talepler · Siparişler (sözleşme, tedarikçi bazlı teklif talebi ihalesi + fiyat/termin kıyası, satınalma siparişi) | | [App.Inventory.md](configs/seeds/docs/modules/App.Inventory.md) | Envanter (`App.Inventory`) | Tanımlamalar (ürün kategorileri, nitelikler, ürünler ve varyantlar, reçeteler, reçete patlatma, depolar, konumlar, operasyon türleri), Operasyonlar (alım, teslimat, iç transfer; lot/seri, backorder, iade), Raporlama (stok, hareketler) | | [App.Production.md](configs/seeds/docs/modules/App.Production.md) | Üretim (`App.Production`) | Tanımlamalar (iş merkezi etiketleri, iş merkezleri, operasyonlar) | +| [App.Maintenance.md](configs/seeds/docs/modules/App.Maintenance.md) | Bakım (`App.Maintenance`) | Tanımlamalar | --- @@ -1354,6 +1357,10 @@ ekranın kendi ayrıntılı açıklamasını gösterir (`DescTr`/`DescEn`, `lowc `Accept-Language`'tan kurulur. `TenantLocalizationMiddleware` + `TenantLocalizationInitializer` istek dışı iş parçacıklarının (Hangfire, worker) varsayılan kültürünü ve `IClock` tipini global `Abp.Localization.DefaultLanguage` / `Abp.Timing.TimeZone` ayarlarından tutar; değerler dakikada en fazla bir kez okunur ve yalnızca değişince yazılır. + Sunucuda kullanıcıya giden tarih/sayı tek yoldan biçimlenir: `CultureFormat` (`Domain.Shared/Localization`) ile + `CultureInfo.CurrentCulture` — koda `dd.MM.yyyy` ya da `tr-TR` gibi sabit desen/kültür yazılmaz; bildirim ve worker + metinleri kiracının `DefaultLanguage` ayarıyla üretilir (`INotificationCulture`), rapor metinleri (`App.Reports.PageOf`, + Evet/Hayır) localizer'dan gelir. --- @@ -1523,13 +1530,13 @@ kuyruğu arka plan işçisiyle çalıştırır ve sonuçları günlükler. ### 13.8. AI entegrasyonu `AiBot` kayıtları tenant bazlıdır; n8n webhook üzerinden bir niyet sınıflandırma adımı ve -ardından sohbet ya da SQL dalı çalışır (`configs/ai/Chat.json`). Model, DevOps makinesindeki +ardından sohbet ya da SQL dalı çalışır (`configs/ai/chat_SQL_gemma3_4b_Select_Summary_Pivot_Grouping.json`). Model, DevOps makinesindeki Ollama'da yerel olarak koşar (`http://ollama:11434/api/chat`, varsayılan `qwen2.5:3b`) — dışarıya model çağrısı yapılmaz. UI tarafında `/admin/ai` ve header'daki asistan bileşeni bu uç noktayı kullanır. -Ollama'ya yüklü modelleri yönetmek için `configs/ai/Kurulum.txt`, sohbet arayüzü (Open WebUI) ve -alan adı yönlendirmesi için §20 kullanılır. Akıştaki model adı değiştirilirse `configs/ai/Chat.json` +Ollama'ya yüklü modelleri yönetmek için `configs/ai/install_ollama.txt`, sohbet arayüzü (Open WebUI) ve +alan adı yönlendirmesi için §20 kullanılır. Akıştaki model adı değiştirilirse akış dosyası içindeki üç varsayılanın (`intentModel`, `chatModel`, `sqlModel`) birlikte güncellenmesi gerekir; biri kalırsa o dal "model not found" ile düşer. @@ -1659,7 +1666,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay | Yetki | Kim alabilir | Ne açar | Açmadığı / sınırı | | --- | --- | --- | --- | | `App.DeveloperKit.McpServers` | Host, kiracı | MCP Servers ekranını görme (`.Create/.Update/.Delete/.Export/.Import` ekran işlemleri) | `/mcp` ucuna erişim vermez | -| `App.DeveloperKit.McpServers.Invoke` | Host, kiracı | `/mcp` bağlantısı ve bütün okuma araçları: `server_info`, `list_lists`, `describe_list`, `search_schema`, `query_list`; host'ta ayrıca `get_dev_guide`, `list_wizards`, `get_wizard`, `verify_wizard`, `get_dev_request`, `list_components`, `get_component` | Hiçbir şey yazmaz. Okuma da ekran yetkisine bağlıdır: `R` yetkisi olmayan liste görünmez, sorgulanamaz. Ham SQL (`run_select`, `list_queries`, `run_query`) ayrıca `App.SqlQueryManager` ister | +| `App.DeveloperKit.McpServers.Invoke` | Host, kiracı | `/mcp` bağlantısı ve bütün okuma araçları: `server_info`, `list_lists`, `describe_list`, `search_schema`, `query_list`; host'ta ayrıca `get_dev_guide`, `list_wizards`, `get_wizard`, `verify_wizard`, `get_dev_request`, `get_module_doc`, `list_components`, `get_component` | Hiçbir şey yazmaz. Okuma da ekran yetkisine bağlıdır: `R` yetkisi olmayan liste görünmez, sorgulanamaz. Ham SQL (`run_select`, `list_queries`, `run_query`) ayrıca `App.SqlQueryManager` ister | | `App.DeveloperKit.McpServers.Write` | Host, kiracı | MCP üzerinden her değişiklik. **Kiracıda:** `insert_row`, `update_row`, `delete_row`. **Host'ta:** bunlara ek olarak `create_table`, `save_sql_object`, `generate_crud_endpoints`, `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `save_report_category`, `save_report_template`, `delete_report_template`, `save_module_doc`, `start_dev_request`, `save_dev_request_analysis`, `append_dev_request_note`, `add_dev_request_subtasks` | Kendi başına bir şey yazdırmaz: satır araçları ekranın `C`/`U`/`D` yetkisini, geliştirme araçları Wizard / Custom Components / SQL Query Manager / `App.DevRequests.Update` yetkisini ayrıca ister. Kiracıda geliştirme araçlarını **açmaz** — kiracı bağlamında bu araçlar yetkiden bağımsız kapalıdır (`McpToolNames.TenantTools`). `Invoke` olmadan işe yaramaz | **Araç → gereken yetkiler** (Invoke hepsinde aranır, tabloda tekrar yazılmadı) @@ -1676,6 +1683,8 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay | `verify_wizard` | — | — | `App.Listforms.Wizard`; seed kapanışı için `.Export` (yoksa o kontrol atlanır, doğrulama düşmez) | | `deploy_wizard` | — | `.Write` | `App.Listforms.Wizard` + `.Create` (yeni) / `.Update` (`replaceFileName`) | | `delete_wizard` | — | `.Write` | `App.Listforms.Wizard` + `.Delete`; `dropTables` için ayrıca `App.SqlQueryManager` | +| `get_module_doc` | — | — | `App.Listforms.Wizard` | +| `save_module_doc` | — | `.Write` | `App.Listforms.Wizard` + `.Update` | | `create_table`, `save_sql_object` | — | `.Write` | `App.SqlQueryManager` | | `generate_crud_endpoints` | — | `.Write` | `App.SqlQueryManager.CrudEndpoints` | | `list_components`, `get_component` | — | — | `App.DeveloperKit.CustomComponents` | @@ -1926,7 +1935,7 @@ konfigürasyonu ile üretilir. - **easy-peasy** global store: `auth`, `theme`, `locale`, `abpConfig`, `admin`, `client`, `base`. Oturum ve tercih verileri `localStorage` yerine store üzerinden yönetilir (`stateSync.ts`). - **React Query**: sunucu verisi önbellekleme. -- **Context'ler**: `ComponentContext` (runtime bileşen kaydı), `DialogContext`, `ScrollContext`. +- **Context'ler**: `ComponentContext` (runtime bileşen kaydı), `DemoContext` (demo talebi diyaloğu). ### 18.3. Tema ve lokalizasyon @@ -1937,6 +1946,12 @@ konfigürasyonu ile üretilir. ilk açılış varsayılanıdır. - Dil metinleri veritabanından gelir (`Sozsoft.Languages`); dil seçici header'dadır. Tenant bazlı çeviri geçersiz kılma desteklenir. +- Tarih ve sayı biçimi aktif dilden gelir: editör/kolon `displayFormat` değerleri DevExtreme'in adlı biçimleridir + (`shortDate`, `shortDateShortTime`, `shortTime`; `EditorOptions.Date/DateTime/Time` ↔ `presets.ts` aynası), sunucu + tarafı tek yol `CultureFormat` + `CultureInfo.CurrentCulture`'dır — sabit desen (`dd/MM/yyyy`) ne seed'e ne koda yazılır. + İstemcide kullanıcıya görünen her tarih/sayı `ui/src/utils/localeFormat.ts` üzerinden biçimlenir (`formatLocaleDate/Time/ + DateTime/Number/Currency`), DevExtreme arayüz metinleri `configureDevExtreme`'de `loadMessages` ile dile göre yüklenir; + izinli makine biçimleri `ui/README.md` › "Tarih ve sayı biçimi". - Layout seçenekleri: dikey menü, yatay menü, yığılmış yan menü, mobil navigasyon. ### 18.4. PWA ve sürüm yönetimi @@ -1977,6 +1992,16 @@ konfigürasyonu ile üretilir. - Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır: herhangi bir chunk değiştiğinde entry hash'i, dolayısıyla index.html ve `sw.js` içindeki precache manifest'i de değişir. +- **Deploy sonrası eksik chunk yoktur.** UI konteyneri nginx ile servis eder + (`ui/Sozsoft.Platform.Ui.Dockerfile`, `ui/nginx/default.conf`): hash'li `assets/` dosyaları + `ui-assets` volume'unda biriktirilir (`ui/nginx/40-archive-assets.sh` açılışta yeni sürümün + dosyalarını ekler, 30 günden eski ve artık hiçbir sürümde olmayanları siler), böylece + deploy'dan önce açılmış sekmeler kendi sürümlerinin chunk'larını bulmaya devam eder. + `index.html`/`sw.js`/`warmup.json`/`manifest.webmanifest` `no-store`, hash'li dosyalar + `immutable` başlığıyla döner. Yine de bir chunk bulunamazsa (`vite:preloadError`) uygulama + kabuk cache'ini silip sayfayı bir kez yeniler; ikinci başarısızlıkta hata sınırı + "uygulama güncellendi, yenileyin" mesajı ve Yenile butonu gösterir + (`swRegistration.isChunkLoadError` / `reloadApp`). Ayrıntı: `ui/README.md`. --- diff --git a/api/Directory.Build.props b/api/Directory.Build.props index 5b3ae63a..1690db5d 100644 --- a/api/Directory.Build.props +++ b/api/Directory.Build.props @@ -6,4 +6,15 @@ true true + + + + + + diff --git a/api/README.md b/api/README.md index 27e28aa5..ff31f42c 100644 --- a/api/README.md +++ b/api/README.md @@ -55,11 +55,11 @@ username=system%40sozsoft.com - WhatsApp -> API Setup kısmından "From" kısmında yeni telefon numarası eklenir. - WhatsApp -> API Setup kısmından Generate Access Token oluşturulur. -- token: EAASM83pDJf0BO0ZCYBr3Fx4SES0ox8XiZCgP3FIteprYpwawZCkGYjpMlKk4OZCWgRNZC2Ttofgf8amMf929ZBuDcgYIOsXkPMGBNmVxm2czipzq63LmtbYfyCWsKVy0q1jtay0nRoeDuO2FKhqCfLLtNn9cSWjINVLcz26ptK8a2Oko83cZBzrNsp3cpIECnzqRfEjHdEz1U73y3ZB1MZBOhHilax7yZALf4ZA1Tmk +- token: - WhatsApp telefon numarasını Register etmek için aşağıdaki postman Curl gönderilir. ``` - curl --location 'https://graph.facebook.com/v21.0/521106361082067/register' \ + curl --location 'https://graph.facebook.com/v21.0//register' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer ' \ --data '{ @@ -68,7 +68,7 @@ username=system%40sozsoft.com }' ``` -- Message göndermek için url : https://graph.facebook.com/v21.0/521106361082067/messages +- Message göndermek için url : https://graph.facebook.com/v21.0//messages ``` { diff --git a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguagesApplicationMappers.cs b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguagesApplicationMappers.cs index 191b52d4..e367a395 100644 --- a/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguagesApplicationMappers.cs +++ b/api/modules/Sozsoft.Languages/Sozsoft.Languages.Application/LanguagesApplicationMappers.cs @@ -11,14 +11,6 @@ public partial class LanguageToLanguageInfoMapper : MapperBase Extract(string attachment, string attachmentParametre); -} - -public class AttachmentExtractor : IAttachmentExtractor, ITransientDependency -{ - protected readonly IParameterExtractor ParameterExtractor; - - public AttachmentExtractor( - IParameterExtractor parameterExtractor) - { - ParameterExtractor = parameterExtractor; - } - - public IList Extract(string attachment, string attachmentParametre) - { - var result = new List(); - - if (attachment.IsNullOrWhiteSpace()) - return result; - - var parameters = ParameterExtractor.Extract(attachmentParametre); - /* - Dict< - 0, Dict< - Tarih, 1232 - No 4334 - > - 1, Dict< - QUERY, ghghg - QUERYSQL select ffddf - */ - //Attachment: - //{0:MRP:rpt_DepoCikisCekiListesi:PDF:MailEkteGozukecekAd};{1:CRM:rpt_DepoCikisCekiListesiCarpet:XLS:MailEkteGozukecekAd} - //AttachmentParametre: - //Filtre değerleri olabilir {0:Tarih=1232&No=4334};{1:Tarih=213} - //Veya query olabilir {0:QUERY=…&QUERYSQL=..};{1:QUERY=…&QUERYSQL=..} - foreach (var item in attachment.Split("};{")) - { - var keyValue = item.Trim('{').Trim('}').Split(":"); - if (keyValue.Length == 5) - { - result.Add(new AttachmentModel - { - Index = keyValue[0], - DbAdi = keyValue[1], - RaporAdi = keyValue[2], - ExportDosyaTipi = keyValue[3], - Filtreler = parameters.GetOrDefault(keyValue[0]), - MailEkAdi = keyValue[4] - }); - } - } - - return result; - } -} - diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/AttachmentGenerator.cs b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/AttachmentGenerator.cs index fa9d56f2..56703662 100644 --- a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/AttachmentGenerator.cs +++ b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/AttachmentGenerator.cs @@ -2,85 +2,36 @@ using Sozsoft.MailQueue.FileGeneration; using Sozsoft.MailQueue.MailGeneration.Models; using Volo.Abp.DependencyInjection; -using Volo.Abp.Guids; -using Volo.Abp.TextTemplating; namespace Sozsoft.MailQueue.Domain.MailGeneration; public interface IAttachmentGenerator { - Task> GenerateAsync( - Guid kuyrukId, - string attachment, - string attachmentParametre, - string jobId = null); - Task> GenerateAsync(List tables, Guid? kuyrukId = null, string jobId = null); - } +/// +/// Kuyruk satırındaki tablo şablonlarından (DosyaTipi PDF/XLS/TXT) mail eki üretir. +/// Ek üretiminin tek yolu budur; eski Attachment/AttachmentParameter kolonlarıyla +/// MS Access raporundan ek üretme yolu hiç gerçeklenmediği için kaldırıldı — kolon dolu olsa da +/// satır artık atlanmaz, tablo ekleriyle gönderilir. +/// public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency { - public IParameterExtractor ParameterExtractor { get; } - public IAttachmentExtractor AttachmentExtractor { get; } - public ITemplateRenderer TemplateRenderer { get; } - public IGuidGenerator GuidGenerator { get; } public IServiceProvider ServiceProvider { get; } protected ILogManager LogManager { get; } private readonly IConfiguration Configuration; public AttachmentGenerator( - IAttachmentExtractor attachmentExtractor, - ITemplateRenderer templateRenderer, - IGuidGenerator guidGenerator, IServiceProvider serviceProvider, ILogManager logManager, - IConfiguration configuration - ) + IConfiguration configuration) { - AttachmentExtractor = attachmentExtractor; - TemplateRenderer = templateRenderer; - GuidGenerator = guidGenerator; ServiceProvider = serviceProvider; LogManager = logManager; Configuration = configuration; } - /// - /// Verilen parametreler (attachment parametreleri) ile KIM raporlari ile istenilen formatta dosya uretir - /// - /// Export edilecek olan attachmentlar - /// Her attachment için parametreler - /// Uretilen ekleri Key: MailEkAdi, Value: Base64 encode edilmis dosya olan dictionary olarak döner - public async Task> GenerateAsync( - Guid kuyrukId, - string attachment, - string attachmentParametre, - string jobId) - { - var result = new Dictionary(); - var models = AttachmentExtractor.Extract(attachment, attachmentParametre); - var attachmentFolder = $"{kuyrukId}-{jobId}"; - foreach (var item in models) - { - var file = GenerateAttachment(item, attachmentFolder); - if (file.IsNullOrEmpty()) - { - LogManager.LogError($"Attachment oluşturulamadı. Index: {item.Index}", "ATTACHMENT", "KUYRUK", kuyrukId, jobId); - continue; - } - - var base64 = await ConvertFileToBase64Async(file); - var key = item.MailEkAdi; - if (result.ContainsKey(key)) - { - key = result.Count + "-" + item.MailEkAdi; - } - result.Add(key, base64); - } - return result; - } - /// /// Verilan tablo modeli ile istenilen dosya tipinde (PDF, XLS, TXT) ek üretir /// @@ -132,70 +83,6 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency return attachments; } - private string GenerateAttachment(AttachmentModel model, string attachmentFolder) - { - if (model.Filtreler.IsNullOrEmpty() || model.RaporAdi.IsNullOrWhiteSpace()) - { - return null; - } - - return null; - - // if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL")) - // { - // /* - //Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz - //params - //0 => - // QUERY -> qryDepo_Cikis_CekiListesi_Detayli - // QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993 - // QUERY2 -> qryDepo_Cikis_CekiListesi_Sub - // QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993 - - //SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap - // QUERY -> qryDepo_Cikis_CekiListesi_Detayli - // dict.add(item.Value, params[item.Key + "SQL"].Value) - // QUERY2 -> qryDepo_Cikis_CekiListesi_Sub - // dict.add(item.Value, params[item.Key + "SQL"].Value) - - //Bunlari yeni dict'e ekle - //dict - //qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993 - //qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993 - // */ - // //dict -> key (name), value (sql) - // var queries = model.Filtreler - // .Where(a => a.Key.StartsWith("QUERY")) - // .Where(a => !a.Key.EndsWith("SQL")) - // .Where(a => !a.Value.IsNullOrWhiteSpace()) - // .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL")); - - // return MsAccessReportManager.GenerateAccessReport( - // model.DbAdi, - // model.Index, - // model.RaporAdi, - // model.ExportDosyaTipi, - // queries, - // attachmentFolder); - // } - // else - // { - // return MsAccessReportManager.GenerateAccessReport( - // model.DbAdi, - // model.Index, - // model.RaporAdi, - // string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")), - // model.ExportDosyaTipi, - // attachmentFolder); - // } - } - - private async Task ConvertFileToBase64Async(string filePath) - { - var bytes = await File.ReadAllBytesAsync(filePath); - return Convert.ToBase64String(bytes); - } - private void LogToFile(Guid id, byte[] content, string fileName) { if (!Configuration.GetValue("App:LogMailToFile")) @@ -224,4 +111,3 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency } } } - diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/MailBodyGenerator.cs b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/MailBodyGenerator.cs index 3c65f165..b6e22664 100644 --- a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/MailBodyGenerator.cs +++ b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Generators/MailBodyGenerator.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Sozsoft.MailQueue.MailGeneration.Models; +using Volo.Abp.Timing; using Volo.Abp.DependencyInjection; using Volo.Abp.TextTemplating; @@ -23,17 +24,20 @@ public class MailBodyGenerator : IMailBodyGenerator, ITransientDependency public IParameterExtractor ParameterExtractor { get; } public ITemplateRenderer TemplateRenderer { get; } protected ILogManager LogManager { get; } + protected IClock Clock { get; } public MailBodyGenerator( IParameterExtractor parameterExtractor, ITemplateRenderer templateRenderer, IConfiguration configuration, - ILogManager logManager) + ILogManager logManager, + IClock clock) { ParameterExtractor = parameterExtractor; TemplateRenderer = templateRenderer; this.configuration = configuration; LogManager = logManager; + Clock = clock; } /// @@ -84,7 +88,7 @@ public class MailBodyGenerator : IMailBodyGenerator, ITransientDependency return; } path = Path.Combine(path, id.ToString()); - var date = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); + var date = Clock.Now.ToString("yyyy-MM-dd-HH-mm-ss"); var dosyaAdi = $"mail-{id}-{date}.html "; try { diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/AttachmentModel.cs b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/AttachmentModel.cs deleted file mode 100644 index 6c43163a..00000000 --- a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/AttachmentModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Sozsoft.MailQueue.MailGeneration.Models; - -public class AttachmentModel -{ - public string Index { get; set; } - public string DbAdi { get; set; } //MRP.mdb, CRM.mdb - public string RaporAdi { get; set; } - public string ExportDosyaTipi { get; set; } //PDF, TXT, XLS, .. - public Dictionary Filtreler { get; set; } - public string MailEkAdi { get; set; } -} - diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/MailTemplateTableColumnModel.cs b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/MailTemplateTableColumnModel.cs index 0bbdfd95..aefda9f8 100644 --- a/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/MailTemplateTableColumnModel.cs +++ b/api/modules/Sozsoft.MailQueue/Domain/MailGeneration/Models/MailTemplateTableColumnModel.cs @@ -1,4 +1,6 @@ -namespace Sozsoft.MailQueue.MailGeneration.Models; +using Sozsoft.Platform.Localization; + +namespace Sozsoft.MailQueue.MailGeneration.Models; public class MailTemplateTableColumnModel { @@ -15,5 +17,10 @@ public class MailTemplateTableColumnModel public string AltToplam { get; set; } public int Genislik { get; set; } + /// + /// Hucre metni: DataFormat deseni aktif kulturde uygulanir (HTML/PDF/TXT ortak yol). + /// XLS bu yoldan gecmez; orada desen Excel bicim kodu olarak hucreye yazilir. + /// + public string FormatValue(object value) => CultureFormat.Value(value, VeriFormati); } diff --git a/api/modules/Sozsoft.MailQueue/Domain/MailQueueWorker.cs b/api/modules/Sozsoft.MailQueue/Domain/MailQueueWorker.cs index 0533e3a2..f194ef99 100644 --- a/api/modules/Sozsoft.MailQueue/Domain/MailQueueWorker.cs +++ b/api/modules/Sozsoft.MailQueue/Domain/MailQueueWorker.cs @@ -1,11 +1,9 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Sozsoft.MailQueue.Domain.Entities; using Sozsoft.MailQueue.Domain.MailGeneration; using Sozsoft.MailQueue.Domain.Shared; using Sozsoft.Sender.Mail; using Volo.Abp.BackgroundWorkers; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; using Volo.Abp.Timing; using Volo.Abp.Uow; @@ -18,195 +16,105 @@ public class MailQueueWorker : BackgroundWorkerBase public MailQueueWorkerOptions Options { get; set; } protected ISozsoftEmailSender ErpEmailSender { get; } - protected IRepository Repository { get; } + protected IRepository Repository { get; } public IClock Clock { get; } - public IConfiguration Configuration { get; } public ILogManager LogManager { get; } public ITableGenerator TableGenerator { get; } public IMailBodyGenerator MailBodyGenerator { get; } public IAttachmentGenerator AttachmentGenerator { get; } - public IGuidGenerator GuidGenerator { get; } public MailQueueWorker( ISozsoftEmailSender erpEmailSender, - IRepository repository, + IRepository repository, IClock clock, - IConfiguration configuration, ILogManager logManager, ITableGenerator tableGenerator, IMailBodyGenerator mailBodyGenerator, - IAttachmentGenerator attachmentGenerator, - IGuidGenerator guidGenerator - ) + IAttachmentGenerator attachmentGenerator) { ErpEmailSender = erpEmailSender; Repository = repository; Clock = clock; - Configuration = configuration; LogManager = logManager; TableGenerator = tableGenerator; MailBodyGenerator = mailBodyGenerator; AttachmentGenerator = attachmentGenerator; - GuidGenerator = guidGenerator; } /// - /// ID'si verilen Template satırının kuyruk kayıtlarını proses ederek bildirimleri yapar + /// Şablonun (TemplateId) gönderilmemiş kuyruk kayıtlarını işler: her kayıt için tablo, gövde ve + /// ekler üretilir, mail gönderilir, sonuç kayda yazılır. Akış: tablo → gövde → ekler → gönderim. /// public override async Task StartAsync(CancellationToken cancellationToken = default) { - /* - * TemplateId'den ilgili template satırını al - * TemplateId'den ilgili kuyruk kayıtlarını al - * Bu kurallar inclusive'dir. - * Yani birden fazla kural aynı kuyruk kaydı için işletilebilir. - * BeforeSP varsa çalıştır. - * SP görevini yaptıktan sonra devam et - * Template: - * Dosya Tipi: NULL, PDF, XLS, IN - * NULL: Sadece mail body - * PDF, XLS, TXT, XML, HTML, RTF: Bu durumda ilgili rapor hazırlanıp eklenir - * IN: Tablo, mail body'e gömülür - * Tablo: - * Mail Body metni oluşturulacak - * Kuyruk tablosu MAILPARAMETRE alanı ile - * MAILTEMPLATE'de belirtilen HTML üretilerek - * mail body oluşturulur - * Tablo Alanı dolu ise; - * Belirtilen tablodan kayıtları al ve Kuyruk'taki - * TABLOPARAMETRE ve TABLO alanlarına göre tabloyu oluştur - * ve Dosya Tipi'ne göre bildirime ekle. - * Bu durumda ilgili Tabloda belirtilen css ve başlıklar kullanılmalı - * Kuyruk - ATTACHMENT ve ATTACHMENTPARAMETRE dolu ise; - * Bu alanlarda belirtilen raporlar Access API ile - * oluşturulup DOSYA TIPI'ne göre bildirime eklenecek - * Gönderimi Yap - * KUYRUK - GATEWAYTEMPLATEID alanında belirtilen template ile - * mail gateway'de ilgili template gönderimi yapılır - * KIMDEN ve KIME alanları kullanılır. - * Gateway'e gönderim emri verildikten sonra - * GonderilmeTarihi alanı setlenir. - * AFTERSP'de belirtilen SP'yi veritabanında çalıştır - * - */ - /* - Options: - - Mail gidecek kişiler listesi - - Mail subject - - Mail lojik türü - - Kayıt kaynağı - - RS Filtre (id, vs) - - - - 1- Template Id'ye göre, Kuyruk tablosunu filtrele - 2- Her kayıt için mail gönder - */ - using var scope = ServiceProvider.CreateScope(); var uowManager = LazyServiceProvider.LazyGetRequiredService(); using var uow = uowManager.Begin(); var jobId = $"{TemplateId}-{Clock.Now:yyyy-MM-dd-HH-mm-ss}"; - var attachmentsBasePath = Configuration.GetValue("App:AttachmentsPath"); - //var template = await RepositoryTemplate.GetAsync(a => a.Id == templateId); var queues = await Repository.GetListAsync(a => a.TemplateId == TemplateId && !a.SendStatus, cancellationToken: cancellationToken); foreach (var queue in queues) { + // Her kayıt kendi UoW'sinde: bir kaydın hatası diğerlerinin gönderimini ve kaydını düşürmez. using var uow2 = uowManager.Begin(requiresNew: true, isTransactional: false); try { - // Tablo oluştur - var tables = await TableGenerator.GenerateAsync(queue.TableName, queue.TableParameter, queue.Id); - - // Mail Body oluştur - var mailBody = await MailBodyGenerator.GenerateAsync( - TemplateName, - queue.MailParameter, - queue.Id, - tables, - jobId); - - // Attachment oluştur - // Iki attachment türü var: - // 1- ATTACHMENT alanı dolu olanlar - var attachments = await AttachmentGenerator.GenerateAsync(queue.Id, queue.Attachment, queue.AttachmentParameter, jobId); - if (!queue.Attachment.IsNullOrWhiteSpace() && attachments.IsNullOrEmpty()) - { - //Kuyruk uzerinde Attachment var fakat hicbiri olusturulamis, devam etmeyelim - LogManager.LogWarning("Attachmentlar oluşturulamadı", "ATTACHMENT", "KUYRUK", queue.Id, jobId); - continue; - } - - // 2- Tablo (IN olmayanlar-PDF,XLS,TXT) - var attachmentsTable = await AttachmentGenerator.GenerateAsync(tables.Values.ToList(), queue.Id, jobId); - // IN olmayan herhangi bir tablo belirtilmisse fakat bu tablo attachment olarak uretilememisse, hata vardir - if (tables.Values.Where(a => a.DosyaTipi != "IN").Any() && attachmentsTable.IsNullOrEmpty()) - { - //NULL ise hata olmustur, devam etmeyelim - LogManager.LogWarning("Tablo Attachmentlar oluşturulamadı", "ATTACHMENT", "KUYRUK", queue.Id, jobId); - continue; - } - - foreach (var attachmentTable in attachmentsTable) - { - var key = attachmentTable.Key; - if (attachments.ContainsKey(key)) - { - key += "-" + GuidGenerator.Create(); - } - attachments.Add(key, attachmentTable.Value); - } - - - // Gönderimi yap - var senderKeyValue = queue.From.Split(":"); - var sender = new KeyValuePair( - senderKeyValue.ElementAtOrDefault(0), - senderKeyValue.ElementAtOrDefault(1)); - var result = await ErpEmailSender.SendEmailAsync( - queue.To.Split(";").Select(a => a.Trim()).ToArray(), - sender, - null, - mailBody, - Options.MailSubject, - attachments, - true); - - if (result.Success) - { - queue.AwsMessageId = result.MessageId; - } - else - { - LogManager.LogWarning($"Mail gönderilemedi. Gelen Mesaj: {result.ErrorMessage}", "MAILSEND", "KUYRUK", queue.Id, jobId); - queue.AwsMessageId = result.ErrorMessage; - } - queue.SendStatus = result.Success; - queue.SendTime = Clock.Now; - await Repository.UpdateAsync(queue, cancellationToken: cancellationToken); - - // Attachmentlari silebiliriz - try - { - var attachmentsPath = Path.Combine(attachmentsBasePath, $"{queue.Id}-{jobId}"); - if (Directory.Exists(attachmentsPath)) - { - Directory.Delete(attachmentsPath, true); - } - } - catch (Exception ex) - { - LogManager.LogError($"Attachments klasörü silinemedi. Kuyruk Id: {queue.Id}. {ex}", "HATA", "ATTACHMENT", queue.Id, jobId); - } + await ProcessQueueAsync(queue, jobId, cancellationToken); + await uow2.CompleteAsync(cancellationToken); } catch (Exception ex) { LogManager.LogError($"Hata. Kuyruk Id: {queue.Id}. {ex}", "HATA", "KUYRUK", queue.Id, jobId); } - await uow2.SaveChangesAsync(cancellationToken); } + await uow.CompleteAsync(cancellationToken); + } - await uow.SaveChangesAsync(cancellationToken); + private async Task ProcessQueueAsync(BackgroundWorker_MailQueue queue, string jobId, CancellationToken cancellationToken) + { + var tables = await TableGenerator.GenerateAsync(queue.TableName, queue.TableParameter, queue.Id); + + var mailBody = await MailBodyGenerator.GenerateAsync( + TemplateName, + queue.MailParameter, + queue.Id, + tables, + jobId); + + // Ekler tablo şablonlarından (IN olmayanlar: PDF, XLS, TXT) üretilir. + var attachments = await AttachmentGenerator.GenerateAsync(tables.Values.ToList(), queue.Id, jobId); + // IN olmayan bir tablo belirtilmiş ama hiçbiri üretilememişse hata vardır, gönderilmez. + if (tables.Values.Any(a => a.DosyaTipi != "IN") && attachments.IsNullOrEmpty()) + { + LogManager.LogWarning("Tablo Attachmentlar oluşturulamadı", "ATTACHMENT", "KUYRUK", queue.Id, jobId); + return; + } + + var senderKeyValue = queue.From.Split(":"); + var sender = new KeyValuePair( + senderKeyValue.ElementAtOrDefault(0), + senderKeyValue.ElementAtOrDefault(1)); + var result = await ErpEmailSender.SendEmailAsync( + queue.To.Split(";").Select(a => a.Trim()).ToArray(), + sender, + null, + mailBody, + Options.MailSubject, + attachments, + true); + + if (result.Success) + { + queue.AwsMessageId = result.MessageId; + } + else + { + LogManager.LogWarning($"Mail gönderilemedi. Gelen Mesaj: {result.ErrorMessage}", "MAILSEND", "KUYRUK", queue.Id, jobId); + queue.AwsMessageId = result.ErrorMessage; + } + + queue.SendStatus = result.Success; + queue.SendTime = Clock.Now; + await Repository.UpdateAsync(queue, cancellationToken: cancellationToken); } } - diff --git a/api/modules/Sozsoft.MailQueue/EntityFrameworkCore/QueryRepository.cs b/api/modules/Sozsoft.MailQueue/EntityFrameworkCore/QueryRepository.cs index 91db2efc..db56a4cb 100644 --- a/api/modules/Sozsoft.MailQueue/EntityFrameworkCore/QueryRepository.cs +++ b/api/modules/Sozsoft.MailQueue/EntityFrameworkCore/QueryRepository.cs @@ -1,6 +1,7 @@ using System.Data; using Microsoft.EntityFrameworkCore; using Sozsoft.MailQueue.Domain.Entities; +using Sozsoft.Platform.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; @@ -20,34 +21,50 @@ public class QueryRepository : IQueryRepository, ITransientDependency Repository = repository; } + /// + /// Mail şablonunun tablo kaynağını okur. Tablo ve kolon adları şablon kaydından gelir, parametre + /// olarak bağlanamaz; bu yüzden beyaz listeden geçirilip ANSI tırnakla yazılır (iki sağlayıcıda da + /// geçerli), değerler ise parametredir. + /// public async Task QueryAsync(string tabloAdi, Dictionary where) { + if (!SqlIdentifier.IsSafe(tabloAdi)) + throw new ArgumentException($"Invalid table name: {tabloAdi}", nameof(tabloAdi)); + var table = new DataTable(); var ctx = await Repository.GetDbContextAsync(); - var cmd = ctx.Database.GetDbConnection().CreateCommand(); - var query = $"SELECT * FROM {tabloAdi} WHERE "; + var connection = ctx.Database.GetDbConnection(); + await using var cmd = connection.CreateCommand(); + var wheres = new List(); + var index = 0; foreach (var item in where) { - var parameterName = $"@p{item.Key}"; - wheres.Add($"{item.Key}={parameterName}"); + if (!SqlIdentifier.IsSafe(item.Key)) + throw new ArgumentException($"Invalid column name: {item.Key}", nameof(where)); + + var parameterName = $"@p{index++}"; + wheres.Add($"{SqlIdentifier.QuoteAnsi(item.Key)} = {parameterName}"); var prm = cmd.CreateParameter(); prm.ParameterName = parameterName; prm.Value = item.Value; cmd.Parameters.Add(prm); } - query += string.Join(" AND ", wheres); - cmd.CommandText = query; + + cmd.CommandText = $"SELECT * FROM {SqlIdentifier.QuoteAnsi(tabloAdi)}" + + (wheres.Count > 0 ? " WHERE " + string.Join(" AND ", wheres) : string.Empty); + + await connection.OpenAsync(); try { - cmd.Connection.Open(); - table.Load(await cmd.ExecuteReaderAsync()); + await using var reader = await cmd.ExecuteReaderAsync(); + table.Load(reader); } finally { - cmd.Connection.Close(); + await connection.CloseAsync(); } + return table; } } - diff --git a/api/modules/Sozsoft.MailQueue/FileGeneration/TXTFileGenerator.cs b/api/modules/Sozsoft.MailQueue/FileGeneration/TXTFileGenerator.cs index 9855b840..bd5599c7 100644 --- a/api/modules/Sozsoft.MailQueue/FileGeneration/TXTFileGenerator.cs +++ b/api/modules/Sozsoft.MailQueue/FileGeneration/TXTFileGenerator.cs @@ -16,7 +16,7 @@ public class TXTFileGenerator : IFileGenerator, ITransientDependency { var sb = new StringBuilder(); table.TabloDegerleri.ForEach(row => - sb.AppendLine(string.Join('\t', table.TabloSutunlari.Select(col => row.GetValueOrDefault(col.SutunAdi))))); + sb.AppendLine(string.Join('\t', table.TabloSutunlari.Select(col => col.FormatValue(row.GetValueOrDefault(col.SutunAdi)))))); return await Task.FromResult(Encoding.UTF8.GetBytes(sb.ToString())); } diff --git a/api/modules/Sozsoft.MailQueue/FileGeneration/Templates/Table.cshtml b/api/modules/Sozsoft.MailQueue/FileGeneration/Templates/Table.cshtml index ae4c6c1a..485d4e42 100644 --- a/api/modules/Sozsoft.MailQueue/FileGeneration/Templates/Table.cshtml +++ b/api/modules/Sozsoft.MailQueue/FileGeneration/Templates/Table.cshtml @@ -119,7 +119,7 @@ table.tablo tbody tr:hover { { @foreach (var sutun in tablo0.TabloSutunlari){ - @satir.GetValueOrDefault(sutun.SutunAdi) + @sutun.FormatValue(satir.GetValueOrDefault(sutun.SutunAdi)) } } diff --git a/api/modules/Sozsoft.MailQueue/FileGeneration/XLSFileGenerator.cs b/api/modules/Sozsoft.MailQueue/FileGeneration/XLSFileGenerator.cs index 310d5084..e4acbf6f 100644 --- a/api/modules/Sozsoft.MailQueue/FileGeneration/XLSFileGenerator.cs +++ b/api/modules/Sozsoft.MailQueue/FileGeneration/XLSFileGenerator.cs @@ -1,4 +1,7 @@ -using ClosedXML.Excel; +using System.Globalization; +using ClosedXML.Excel; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Sozsoft.MailQueue.MailGeneration.Models; using Volo.Abp.DependencyInjection; @@ -6,6 +9,8 @@ namespace Sozsoft.MailQueue.FileGeneration; public class XLSFileGenerator : IFileGenerator, ITransientDependency { + public ILogger Logger { get; set; } = NullLogger.Instance; + string sheetPassword = "Erp123"; public async Task Generate(MailTemplateTableModel table) @@ -87,7 +92,8 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency switch (dataType) { case XLDataType.Number: - cell.SetValue(Convert.ToInt32(value)); + // Int32'ye cevirmek ondaligi atardi; hucre sayisal kalir, gosterimi Excel bicim kodu verir. + cell.SetValue(Convert.ToDecimal(value, CultureInfo.InvariantCulture)); break; case XLDataType.DateTime: cell.SetValue(Convert.ToDateTime(value)); @@ -166,7 +172,11 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency cell.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin); } } - catch { } + catch (Exception ex) + { + // Geçersiz CSS değeri hücreyi biçimsiz bırakır; dosya üretimi sürer. + Logger.LogWarning(ex, "Hücre CSS'i uygulanamadı: {Css}", string.Join(";", css.Select(a => a.Key + ":" + a.Value))); + } } } diff --git a/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj b/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj index 1b797a54..6d55af25 100644 --- a/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj +++ b/api/modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj @@ -42,6 +42,7 @@ + diff --git a/api/modules/Sozsoft.MailQueue/SozsoftMailQueueModule.cs b/api/modules/Sozsoft.MailQueue/SozsoftMailQueueModule.cs index 14adf6e1..8ecdaecd 100644 --- a/api/modules/Sozsoft.MailQueue/SozsoftMailQueueModule.cs +++ b/api/modules/Sozsoft.MailQueue/SozsoftMailQueueModule.cs @@ -26,22 +26,22 @@ namespace Sozsoft.MailQueue; typeof(AbpAuthorizationModule), typeof(AbpEntityFrameworkCoreModule), typeof(AbpTextTemplatingRazorModule), - typeof(ErpSenderModule) + typeof(SozsoftSenderModule) )] -public class ErpMailQueueModule : AbpModule +public class SozsoftMailQueueModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddMapperlyObjectMapper(); + context.Services.AddMapperlyObjectMapper(); Configure(options => { - options.Resources.Add("en"); + options.Resources.Add("en"); }); Configure(options => { - options.MapCodeNamespace("MailQueue", typeof(ErpMailQueueResource)); + options.MapCodeNamespace("MailQueue", typeof(SozsoftMailQueueResource)); }); context.Services.AddAbpDbContext(options => @@ -51,7 +51,7 @@ public class ErpMailQueueModule : AbpModule Configure(options => { - options.References.Add(MetadataReference.CreateFromFile(typeof(ErpMailQueueModule).Assembly.Location)); + options.References.Add(MetadataReference.CreateFromFile(typeof(SozsoftMailQueueModule).Assembly.Location)); }); Configure(options => @@ -69,7 +69,7 @@ public class ErpMailQueueModule : AbpModule // FileGenerationModule Configure(options => { - options.FileSets.AddEmbedded(); + options.FileSets.AddEmbedded(); }); context.Services.AddWkhtmltopdf($"FileGeneration{Path.DirectorySeparatorChar}Rotativa"); diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs index a92aef88..6d370de2 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpQueryAppService.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Sozsoft.Mcp.Dtos; using Sozsoft.Mcp.Services; using Sozsoft.Platform; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; @@ -265,9 +266,7 @@ public partial class McpQueryAppService( private async Task> GetDatabaseObjectsAsync() { var context = await queryExecutor.ResolveDataSourceAsync(); - var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql - ? McpCatalogQueries.Postgres - : McpCatalogQueries.SqlServer; + var catalogSql = SqlCatalogQueries.NativeObjects(context.DataSourceType); // Katalog sorgusunun kendi satir siniri yoktur; nesne sayisi tavani asmaz. var catalog = await queryExecutor.ExecuteAsync(context, catalogSql, new Dictionary(), int.MaxValue); @@ -322,7 +321,7 @@ public partial class McpQueryAppService( var context = await queryExecutor.ResolveDataSourceAsync(); var schemaName = string.IsNullOrWhiteSpace(input.SchemaName) - ? sqlGuard.DefaultSchemaName(context.DataSourceType) + ? SqlIdentifier.DefaultSchema(context.DataSourceType) : input.SchemaName; if (!sqlGuard.QuoteQualifiedName(schemaName, input.Name, context.DataSourceType).TryGetValue(out var qualifiedName, out problem) || diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs index dbde9fe7..82123f95 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpSchemaAppService.cs @@ -48,20 +48,9 @@ public class McpSchemaAppService( CurrentTenant.IsAvailable, config?.AllowedTools ?? []); - var required = candidates + var granted = await permissionChecker.GetGrantedNamesAsync(candidates .Where(McpToolNames.RequiredPermissions.ContainsKey) - .Select(tool => McpToolNames.RequiredPermissions[tool]) - .Distinct(StringComparer.Ordinal) - .ToArray(); - - var granted = new HashSet(StringComparer.Ordinal); - if (required.Length > 0) - { - var grants = await permissionChecker.IsGrantedAsync(required); - granted.UnionWith(grants.Result - .Where(result => result.Value == PermissionGrantResult.Granted) - .Select(result => result.Key)); - } + .Select(tool => McpToolNames.RequiredPermissions[tool])); return [.. candidates.Where(tool => !McpToolNames.RequiredPermissions.TryGetValue(tool, out var permission) || granted.Contains(permission))]; diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/PermissionCheckerExtensions.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/PermissionCheckerExtensions.cs new file mode 100644 index 00000000..2ee76b1e --- /dev/null +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/PermissionCheckerExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; + +namespace Sozsoft.Mcp; + +public static class PermissionCheckerExtensions +{ + /// + /// Verilen yetki adlarından kullanıcıya açık olanlar; tek toplu kontrol yapılır ve sonuç + /// adla sorgulanabilen küme olarak döner. Araç listesi, ekran süzgeci ve ret mesajı aynı kümeyi okur. + /// + public static async Task> GetGrantedNamesAsync( + this IPermissionChecker permissionChecker, + IEnumerable names) + { + var distinct = names.Distinct(StringComparer.Ordinal).ToArray(); + if (distinct.Length == 0) + { + return new HashSet(StringComparer.Ordinal); + } + + var grants = await permissionChecker.IsGrantedAsync(distinct); + + return grants.Result + .Where(result => result.Value == PermissionGrantResult.Granted) + .Select(result => result.Key) + .ToHashSet(StringComparer.Ordinal); + } +} diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpListFormAccessFilter.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpListFormAccessFilter.cs index 2ebe2603..1afe04ff 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpListFormAccessFilter.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpListFormAccessFilter.cs @@ -55,17 +55,7 @@ public class McpListFormAccessFilter( } } - var distinctPermissions = readPermissions.Values.Distinct(StringComparer.Ordinal).ToArray(); - if (distinctPermissions.Length == 0) - { - return new HashSet(StringComparer.OrdinalIgnoreCase); - } - - var grants = await permissionChecker.IsGrantedAsync(distinctPermissions); - var granted = grants.Result - .Where(result => result.Value == PermissionGrantResult.Granted) - .Select(result => result.Key) - .ToHashSet(StringComparer.Ordinal); + var granted = await permissionChecker.GetGrantedNamesAsync(readPermissions.Values); return readPermissions .Where(entry => granted.Contains(entry.Value)) diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSchemaDictionary.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSchemaDictionary.cs index 461fca90..818cdb9b 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSchemaDictionary.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSchemaDictionary.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Sozsoft.Platform; using Sozsoft.Platform.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Services; @@ -43,11 +44,6 @@ public class McpSchemaDictionary( ICurrentTenant currentTenant) : DomainService, IMcpSchemaDictionary { - private static readonly JsonSerializerOptions DictionaryJsonOptions = new() - { - PropertyNameCaseInsensitive = true - }; - private static readonly CultureInfo TurkishCulture = CultureInfo.GetCultureInfo("tr-TR"); public string ResolveTenantName() @@ -81,7 +77,7 @@ public class McpSchemaDictionary( return []; } - var document = JsonSerializer.Deserialize(json, DictionaryJsonOptions); + var document = JsonSerializer.Deserialize(json, PlatformJsonOptions.CaseInsensitive); return document?.ListForms ?? []; } diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSqlGuard.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSqlGuard.cs index d61ca7bc..88f3b17d 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSqlGuard.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpSqlGuard.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.Extensions.Localization; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Enums; using Sozsoft.Platform.Localization; using Volo.Abp.Domain.Services; @@ -21,9 +22,6 @@ public interface IMcpSqlGuard /// Sema/nesne adini dogrular ve saglayiciya gore tirnaklar. McpOutcome QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType); - /// Saglayicinin varsayilan semasi. - string DefaultSchemaName(DataSourceTypeEnum dataSourceType); - /// /// Satir tavanini sorgunun kendisine yazar: SQL Server'da TOP (n), PostgreSQL'de /// LIMIT n. Sorgu kendi sinirini zaten tasiyorsa dokunulmaz. @@ -39,11 +37,6 @@ public interface IMcpSqlGuard public class McpSqlGuard(IStringLocalizer localizer) : DomainService, IMcpSqlGuard { - /// Tek parcali SQL tanimlayicisi; sema ve nesne adlari bu kalibi gecmek zorundadir. - private static readonly Regex SafeIdentifierRegex = new( - @"^[A-Za-z_][A-Za-z0-9_]{0,127}$", - RegexOptions.Compiled | RegexOptions.CultureInvariant); - private static readonly Regex SafeParameterNameRegex = new( @"^@?[A-Za-z_][A-Za-z0-9_]{0,127}$", RegexOptions.Compiled | RegexOptions.CultureInvariant); @@ -111,20 +104,15 @@ public class McpSqlGuard(IStringLocalizer localizer) : DomainS { foreach (var name in (string?[])[schemaName, objectName]) { - if (!SafeIdentifierRegex.IsMatch(name ?? string.Empty)) + if (!SqlIdentifier.IsSafe(name)) { return McpProblem.From(localizer, McpErrorCodes.InvalidIdentifier, name ?? string.Empty); } } - return dataSourceType == DataSourceTypeEnum.Postgresql - ? $"\"{schemaName}\".\"{objectName}\"" - : $"[{schemaName}].[{objectName}]"; + return SqlIdentifier.QuoteQualified(schemaName, objectName, dataSourceType); } - public string DefaultSchemaName(DataSourceTypeEnum dataSourceType) - => dataSourceType == DataSourceTypeEnum.Postgresql ? "public" : "dbo"; - public string ApplyRowLimit(string sql, int maxRows, DataSourceTypeEnum dataSourceType) { if (dataSourceType == DataSourceTypeEnum.Postgresql) diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs index cc55c231..d2df7442 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Services/McpTableCreator.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Localization; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Enums; using Sozsoft.Platform.Localization; @@ -43,10 +44,6 @@ public class McpTableCreator( private const int DefaultTextLength = 100; private const int MaxTextLength = 4000; - private static readonly Regex SafeIdentifierRegex = new( - @"^[A-Za-z_][A-Za-z0-9_]{0,127}$", - RegexOptions.Compiled | RegexOptions.CultureInvariant); - /// /// Istemcinin SQL bicimiyle yazdigi tip: nvarchar(50), nvarchar (MAX), /// decimal(18, 4). Boyut tipten ayrilir; tip adi yine beyaz listeden gecer. @@ -427,7 +424,7 @@ public class McpTableCreator( } private McpProblem? CheckIdentifier(string identifier) - => SafeIdentifierRegex.IsMatch(identifier) ? null : Problem(McpErrorCodes.InvalidIdentifier, identifier); + => SqlIdentifier.IsSafe(identifier) ? null : Problem(McpErrorCodes.InvalidIdentifier, identifier); private McpProblem Problem(string name, params object[] arguments) => McpProblem.From(localizer, name, arguments); diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolErrorTranslator.cs b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolErrorTranslator.cs index fad90680..841f9187 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolErrorTranslator.cs +++ b/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpToolErrorTranslator.cs @@ -43,10 +43,11 @@ public class McpToolErrorTranslator( candidates.Add(policyName); } - var grants = await permissionChecker.IsGrantedAsync([.. candidates.Distinct(StringComparer.Ordinal)]); - var missing = grants.Result - .Where(result => result.Value != PermissionGrantResult.Granted) - .Select(result => McpPermissionText.Describe(localizer, result.Key)) + var granted = await permissionChecker.GetGrantedNamesAsync(candidates); + var missing = candidates + .Distinct(StringComparer.Ordinal) + .Where(permission => !granted.Contains(permission)) + .Select(permission => McpPermissionText.Describe(localizer, permission)) .ToList(); var target = toolName ?? McpConsts.EndpointPath; diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationApplicationMappers.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationApplicationMappers.cs index fd1dcc39..a61eec5f 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationApplicationMappers.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/NotificationApplicationMappers.cs @@ -12,14 +12,6 @@ public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase +/// Bildirim metninin (tarih, sayi, dil anahtari) uretildigi kultur. Istek disinda (worker) is +/// parcaciginin kulturu surec varsayilanidir; alicinin kendi dil tercihi platformda saklanmaz +/// (dil istekte Accept-Language ile gelir), bu yuzden ulasilabilen en yakin deger +/// kiracinin Abp.Localization.DefaultLanguage ayaridir (kiraci → global → varsayilan). +/// +public interface INotificationCulture +{ + Task UseAsync(); +} + +public class NotificationCulture(ISettingProvider settingProvider) : INotificationCulture, ITransientDependency +{ + public async Task UseAsync() + { + var culture = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); + return culture.IsNullOrWhiteSpace() || !CultureHelper.IsValidCultureCode(culture!) + ? NullDisposable.Instance + : CultureHelper.Use(culture!); + } +} diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs index 9473bc48..1c61d4b9 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationManager.cs @@ -70,6 +70,7 @@ public class NotificationManager : INotificationManager private readonly INotificationRealtimeNotifier realtimeNotifier; private readonly ICurrentTenant currentTenant; private readonly IClock clock; + private readonly INotificationCulture notificationCulture; public NotificationManager( IReadOnlyRepository repository, @@ -80,7 +81,8 @@ public class NotificationManager : INotificationManager IOrganizationUnitRepository repositoryOrganizationUnit, INotificationRealtimeNotifier realtimeNotifier, ICurrentTenant currentTenant, - IClock clock + IClock clock, + INotificationCulture notificationCulture ) { this.repository = repository; @@ -92,6 +94,7 @@ public class NotificationManager : INotificationManager this.realtimeNotifier = realtimeNotifier; this.currentTenant = currentTenant; this.clock = clock; + this.notificationCulture = notificationCulture; } public async Task SendNotificationAsync(Guid NotificationTypeId, string NotificationMessage) @@ -208,30 +211,36 @@ public class NotificationManager : INotificationManager .ToListAsync(); var notifications = new List(); - foreach (var channel in channels) - { - foreach (var user in users) - { - var identifier = await notificationIdentifierProvider.GetIdentifier(user.Id, channel); - if (identifier.IsNullOrWhiteSpace()) - { - result.SkippedByChannel[channel] = result.SkippedByChannel.GetOrDefault(channel) + 1; - continue; - } - notifications.Add(new Notification + // {Date}/{Time} yer tutuculari gonderenin istek kulturunde degil kiracinin dilinde yazilir; + // alicinin kendi dil tercihi platformda saklanmadigi icin alici basina ayrim yapilamaz. + using (await notificationCulture.UseAsync()) + { + foreach (var channel in channels) + { + foreach (var user in users) { - NotificationTypeId = request.NotificationTypeId, - NotificationTemplateId = request.NotificationTemplateId, - NotificationRuleId = anchorRule.Id, - NotificationChannel = channel, - Identifier = identifier!, - UserId = user.Id, - Message = ResolveTokens(request.Message, user), - Subject = request.Subject, - IsSent = IsDeliveredOnCreate(channel), - IsRead = false - }); + var identifier = await notificationIdentifierProvider.GetIdentifier(user.Id, channel); + if (identifier.IsNullOrWhiteSpace()) + { + result.SkippedByChannel[channel] = result.SkippedByChannel.GetOrDefault(channel) + 1; + continue; + } + + notifications.Add(new Notification + { + NotificationTypeId = request.NotificationTypeId, + NotificationTemplateId = request.NotificationTemplateId, + NotificationRuleId = anchorRule.Id, + NotificationChannel = channel, + Identifier = identifier!, + UserId = user.Id, + Message = ResolveTokens(request.Message, user), + Subject = request.Subject, + IsSent = IsDeliveredOnCreate(channel), + IsRead = false + }); + } } } diff --git a/api/modules/Sozsoft.Sender/Localization/SozsoftMailingResource.cs b/api/modules/Sozsoft.Sender/Localization/SozsoftMailingResource.cs index 3c08ead5..2b790a1b 100644 --- a/api/modules/Sozsoft.Sender/Localization/SozsoftMailingResource.cs +++ b/api/modules/Sozsoft.Sender/Localization/SozsoftMailingResource.cs @@ -3,7 +3,6 @@ namespace Sozsoft.Sender.Localization; [LocalizationResourceName("SozsoftSender")] -public class ErpSenderResource +public class SozsoftMailingResource { } - diff --git a/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs b/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs index 267d837b..3b2da288 100644 --- a/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs +++ b/api/modules/Sozsoft.Sender/Mail/AmazonSes/AmazonSesEmailSender.cs @@ -63,7 +63,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans } await BackgroundJobManager.EnqueueAsync( - new ErpBackgroundEmailSendingJobArgs + new SozsoftBackgroundEmailSendingJobArgs { TenantId = CurrentTenant.Id, To = to, @@ -76,7 +76,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans ); } - public async Task SendEmailAsync( + public async Task SendEmailAsync( string[] to, KeyValuePair? sender, dynamic? @params, @@ -134,7 +134,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans using var client = await amazonSesClientFactory.GetAmazonSesClient(); var response = await client.SendEmailAsync(request); - var result = new EmailSendResult(); + var result = new SendResult(); if (response == null) { result.Success = false; @@ -157,7 +157,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans return result; } - public async Task SendEmailAsync( + public async Task SendEmailAsync( string to, KeyValuePair? sender, dynamic? @params, diff --git a/api/modules/Sozsoft.Sender/Mail/EmailSendResult.cs b/api/modules/Sozsoft.Sender/Mail/EmailSendResult.cs deleted file mode 100644 index 6d0cf25b..00000000 --- a/api/modules/Sozsoft.Sender/Mail/EmailSendResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sozsoft.Sender.Mail; - -public class EmailSendResult -{ - public bool Success { get; set; } - public string MessageId { get; set; } //Eger basarili ise gateway messageId - public string ErrorMessage { get; set; } //Basarisiz ise hata mesaji -} - diff --git a/api/modules/Sozsoft.Sender/Mail/ISozsoftEmailSender.cs b/api/modules/Sozsoft.Sender/Mail/ISozsoftEmailSender.cs index be869d7f..c72ca5f4 100644 --- a/api/modules/Sozsoft.Sender/Mail/ISozsoftEmailSender.cs +++ b/api/modules/Sozsoft.Sender/Mail/ISozsoftEmailSender.cs @@ -7,8 +7,7 @@ namespace Sozsoft.Sender.Mail; /// public interface ISozsoftEmailSender : IEmailSender { - //ErpEmailTemplate template - Task SendEmailAsync( + Task SendEmailAsync( string to, KeyValuePair? sender, dynamic? @params, @@ -16,7 +15,7 @@ public interface ISozsoftEmailSender : IEmailSender string? subject = null, Dictionary? attachments = null, bool IsBodyHtml = false); - Task SendEmailAsync( + Task SendEmailAsync( string[] to, KeyValuePair? sender, dynamic? @params, diff --git a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs index 1aa66f0b..fa4fd54d 100644 --- a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs +++ b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJob.cs @@ -4,13 +4,13 @@ using Volo.Abp.MultiTenancy; namespace Sozsoft.Sender.Mail; -public class ErpBackgroundEmailSendingJob : - AsyncBackgroundJob, ITransientDependency +public class SozsoftBackgroundEmailSendingJob : + AsyncBackgroundJob, ITransientDependency { protected ISozsoftEmailSender EmailSender { get; } protected ICurrentTenant CurrentTenant { get; } - public ErpBackgroundEmailSendingJob( + public SozsoftBackgroundEmailSendingJob( ISozsoftEmailSender emailSender, ICurrentTenant currentTenant) { @@ -18,11 +18,10 @@ public class ErpBackgroundEmailSendingJob : CurrentTenant = currentTenant; } - public override async Task ExecuteAsync(ErpBackgroundEmailSendingJobArgs args) + public override async Task ExecuteAsync(SozsoftBackgroundEmailSendingJobArgs args) { using (CurrentTenant.Change(args.TenantId)) { - //await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject); await EmailSender.SendEmailAsync( args.To, args.Sender, diff --git a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs index 084b7264..1d0732d0 100644 --- a/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs +++ b/api/modules/Sozsoft.Sender/Mail/SozsoftBackgroundEmailSendingJobArgs.cs @@ -3,7 +3,7 @@ namespace Sozsoft.Sender.Mail; [Serializable] -public class ErpBackgroundEmailSendingJobArgs : IMultiTenant +public class SozsoftBackgroundEmailSendingJobArgs : IMultiTenant { public Guid? TenantId { get; set; } diff --git a/api/modules/Sozsoft.Sender/Rocket/ISozsoftRocketSender.cs b/api/modules/Sozsoft.Sender/Rocket/ISozsoftRocketSender.cs index 036e1390..d30a193e 100644 --- a/api/modules/Sozsoft.Sender/Rocket/ISozsoftRocketSender.cs +++ b/api/modules/Sozsoft.Sender/Rocket/ISozsoftRocketSender.cs @@ -4,6 +4,6 @@ namespace Sozsoft.Sender.Rocket; public interface ISozsoftRocketSender : ITransientDependency { - Task SendAsync(string Username, string Message); + Task SendAsync(string Username, string Message); } diff --git a/api/modules/Sozsoft.Sender/Rocket/RocketHttpClient.cs b/api/modules/Sozsoft.Sender/Rocket/RocketHttpClient.cs index 5c73f6bd..873cf9bb 100644 --- a/api/modules/Sozsoft.Sender/Rocket/RocketHttpClient.cs +++ b/api/modules/Sozsoft.Sender/Rocket/RocketHttpClient.cs @@ -1,46 +1,38 @@ -using System.Net.Http.Json; -using Volo.Abp.Settings; +using Volo.Abp.Settings; namespace Sozsoft.Sender.Rocket; public interface IRocketHttpClient { - Task SendMessageAsync(string Username, string Message); + Task SendMessageAsync(string Username, string Message); } -public sealed class RocketHttpClient : IRocketHttpClient, IDisposable +public sealed class RocketHttpClient : SettingHttpClientBase, IRocketHttpClient { - private readonly HttpClient httpClient; - private readonly ISettingProvider settingProvider; - - public RocketHttpClient( - HttpClient httpClient, - ISettingProvider settingProvider) + public RocketHttpClient(HttpClient httpClient, ISettingProvider settingProvider) + : base(httpClient, settingProvider) { - this.httpClient = httpClient; - this.settingProvider = settingProvider; } - public async Task SendMessageAsync(string Username, string Message) + public async Task SendMessageAsync(string Username, string Message) { - var url = await settingProvider.GetOrNullAsync(RocketSettingNames.Url); - var userId = await settingProvider.GetOrNullAsync(RocketSettingNames.UserId); - var token = await settingProvider.GetOrNullAsync(RocketSettingNames.Token); + var url = await GetSettingAsync(RocketSettingNames.Url); + var userId = await GetSettingAsync(RocketSettingNames.UserId); + var token = await GetSettingAsync(RocketSettingNames.Token); - var request = new HttpRequestMessage(HttpMethod.Post, $"{url}/chat.postMessage"); - request.Headers.Add("X-Auth-Token", token); - request.Headers.Add("X-User-Id", userId); - request.Content = JsonContent.Create(new RocketChatPostMessageInput - { - Channel = $"@{Username}", - Text = Message - }); - - var response = await httpClient.SendAsync(request); - response.EnsureSuccessStatusCode(); // Hata durumunda istisna atar. - return await response.Content.ReadFromJsonAsync(); + // Rocket.Chat cevabı yalnızca { success } taşır; SendResult aynı sözleşmedir, ayrı DTO gerekmez. + return await PostJsonAsync( + $"{url}/chat.postMessage", + new RocketChatPostMessageInput + { + Channel = $"@{Username}", + Text = Message + }, + request => + { + request.Headers.Add("X-Auth-Token", token); + request.Headers.Add("X-User-Id", userId); + }, + ensureSuccess: true); } - - public void Dispose() => httpClient?.Dispose(); } - diff --git a/api/modules/Sozsoft.Sender/Rocket/RocketSendResult.cs b/api/modules/Sozsoft.Sender/Rocket/RocketSendResult.cs deleted file mode 100644 index cd2feb09..00000000 --- a/api/modules/Sozsoft.Sender/Rocket/RocketSendResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sozsoft.Sender.Rocket; - -public class RocketSendResult -{ - public bool Success { get; set; } -} - diff --git a/api/modules/Sozsoft.Sender/Rocket/RocketSendResultDto.cs b/api/modules/Sozsoft.Sender/Rocket/RocketSendResultDto.cs deleted file mode 100644 index 53a06991..00000000 --- a/api/modules/Sozsoft.Sender/Rocket/RocketSendResultDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sozsoft.Sender.Rocket; - -public class RocketSendResultDto -{ - public bool Success { get; set; } -} - diff --git a/api/modules/Sozsoft.Sender/Rocket/RocketSender.cs b/api/modules/Sozsoft.Sender/Rocket/RocketSender.cs index efca20d8..c542c13e 100644 --- a/api/modules/Sozsoft.Sender/Rocket/RocketSender.cs +++ b/api/modules/Sozsoft.Sender/Rocket/RocketSender.cs @@ -15,11 +15,12 @@ public class RocketSender : ISozsoftRocketSender this.logger = logger; } - public async Task SendAsync(string Username, string Message) + public async Task SendAsync(string Username, string Message) { logger.LogInformation("Sending Rocket message. {Username}, {Message}", Username, Message); var result = await httpClient.SendMessageAsync(Username, Message); - if (result.Success) + var success = result?.Success == true; + if (success) { logger.LogInformation("Rocket sent successfully. {Username}, {Message}", Username, Message); } @@ -28,10 +29,6 @@ public class RocketSender : ISozsoftRocketSender logger.LogWarning("Failed to send Rocket message. {Username}, {Message}", Username, Message); } - return new RocketSendResult - { - Success = result.Success, - }; + return new SendResult { Success = success }; } } - diff --git a/api/modules/Sozsoft.Sender/SendResult.cs b/api/modules/Sozsoft.Sender/SendResult.cs new file mode 100644 index 00000000..c17729bf --- /dev/null +++ b/api/modules/Sozsoft.Sender/SendResult.cs @@ -0,0 +1,16 @@ +namespace Sozsoft.Sender; + +/// +/// Her kanalın (e-posta, SMS, Rocket, WhatsApp, Telegram) tek gönderim sonucu. Kanal yalnızca +/// başarı bilgisi veriyorsa ve boş kalır. +/// +public class SendResult +{ + public bool Success { get; set; } + + /// Başarılı gönderimde sağlayıcının verdiği ileti kimliği. + public string? MessageId { get; set; } + + /// Başarısız gönderimde sağlayıcının açıklaması. + public string? ErrorMessage { get; set; } +} diff --git a/api/modules/Sozsoft.Sender/SettingHttpClientBase.cs b/api/modules/Sozsoft.Sender/SettingHttpClientBase.cs new file mode 100644 index 00000000..d03099e0 --- /dev/null +++ b/api/modules/Sozsoft.Sender/SettingHttpClientBase.cs @@ -0,0 +1,52 @@ +using System.Net.Http.Json; +using Volo.Abp.Settings; + +namespace Sozsoft.Sender; + +/// +/// Sağlayıcı istemcilerinin ortak iskeleti: ile verilen +/// , ayarlardan okunan uç nokta ve JSON gövde ile POST. Sağlayıcıya özgü +/// olan yalnızca istek gövdesi, başlıklar ve cevap tipidir. +/// +public abstract class SettingHttpClientBase : IDisposable +{ + protected HttpClient HttpClient { get; } + protected ISettingProvider SettingProvider { get; } + + protected SettingHttpClientBase(HttpClient httpClient, ISettingProvider settingProvider) + { + HttpClient = httpClient; + SettingProvider = settingProvider; + } + + protected Task GetSettingAsync(string name) => SettingProvider.GetOrNullAsync(name); + + /// + /// Gövdeyi JSON olarak gönderir ve cevabı olarak okur. + /// kapalıyken 4xx gövdesi de okunur: bazı API'ler hatayı + /// durum koduyla değil gövdedeki açıklamayla verir. + /// + protected async Task PostJsonAsync( + string url, + object body, + Action? configureRequest = null, + bool ensureSuccess = false) + { + using var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = JsonContent.Create(body) }; + configureRequest?.Invoke(request); + + using var response = await HttpClient.SendAsync(request); + if (ensureSuccess) + { + response.EnsureSuccessStatusCode(); + } + + return await response.Content.ReadFromJsonAsync(); + } + + public void Dispose() + { + HttpClient.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/api/modules/Sozsoft.Sender/Sms/ISozsoftSmsSender.cs b/api/modules/Sozsoft.Sender/Sms/ISozsoftSmsSender.cs index 480094b5..6d1ae3f8 100644 --- a/api/modules/Sozsoft.Sender/Sms/ISozsoftSmsSender.cs +++ b/api/modules/Sozsoft.Sender/Sms/ISozsoftSmsSender.cs @@ -5,6 +5,6 @@ namespace Sozsoft.Sender.Sms; public interface ISozsoftSmsSender : ISmsSender, ITransientDependency { - Task SendAsync(string PhoneNumber, string Message); + Task SendAsync(string PhoneNumber, string Message); } diff --git a/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniHttpClient.cs b/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniHttpClient.cs index 1c74eb7a..85a58e5d 100644 --- a/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniHttpClient.cs +++ b/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniHttpClient.cs @@ -7,61 +7,51 @@ public interface IPostaGuverciniHttpClient Task SendSmsAsync(string PhoneNumber, string Message); } -public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable +public sealed class PostaGuverciniHttpClient : SettingHttpClientBase, IPostaGuverciniHttpClient { - private readonly HttpClient httpClient; - private readonly ISettingProvider settingProvider; - - public PostaGuverciniHttpClient( - HttpClient httpClient, - ISettingProvider settingProvider) + public PostaGuverciniHttpClient(HttpClient httpClient, ISettingProvider settingProvider) + : base(httpClient, settingProvider) { - this.httpClient = httpClient; - this.settingProvider = settingProvider; } public async Task SendSmsAsync(string PhoneNumber, string Message) { - var url = await settingProvider.GetOrNullAsync(PostaGuverciniSettingNames.Url); - var u = await settingProvider.GetOrNullAsync(PostaGuverciniSettingNames.Username); - var p = await settingProvider.GetOrNullAsync(PostaGuverciniSettingNames.Password); - //httpClient.BaseAddress = new Uri(url); - var response = await httpClient.GetStringAsync($"{url}/sendsms.asp?user={u}&password={p}&gsm={PhoneNumber}&text={Message}"); - // Gelen result başarı: errno=0&errtext=&message_id=EZ_2643D2B5-QCFD-40B7-E95F-4204CFCD0409&charge=-1 - // Gelen result hata: errno=10&errtext=HATA: Giriş bilgileri hatalı! + var url = await GetSettingAsync(PostaGuverciniSettingNames.Url); + var user = await GetSettingAsync(PostaGuverciniSettingNames.Username); + var password = await GetSettingAsync(PostaGuverciniSettingNames.Password); + + // Sağlayıcı yalnızca sorgu dizisi kabul eder; metindeki '&' ve '=' kaçırılmazsa parametre sınırı bozulur. + var response = await HttpClient.GetStringAsync( + $"{url}/sendsms.asp?user={Uri.EscapeDataString(user ?? string.Empty)}&password={Uri.EscapeDataString(password ?? string.Empty)}" + + $"&gsm={Uri.EscapeDataString(PhoneNumber)}&text={Uri.EscapeDataString(Message)}"); + + // Başarı: errno=0&errtext=&message_id=EZ_2643D2B5-...&charge=-1 + // Hata: errno=10&errtext=HATA: Giriş bilgileri hatalı! var result = new PostaGuverciniSmsSendResultDto(); - try + var options = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; + foreach (var item in response.Split('&', options)) { - var options = StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries; - foreach (var item in response.Split('&', options)) + var token = item.Split('=', 2, options); + var value = token.Length == 2 ? token[1] : null; + switch (token[0]) { - var token = item.Split('=', options); - switch (token[0]) - { - case "errno": - result.ErrNo = token.Length == 2 ? token[1] : null; - break; - case "errtext": - result.ErrText = token.Length == 2 ? token[1] : null; - break; - case "message_id": - result.MessageId = token.Length == 2 ? token[1] : null; - break; - case "charge": - result.Charge = token.Length == 2 ? token[1] : null; - break; - default: - break; - } + case "errno": + result.ErrNo = value; + break; + case "errtext": + result.ErrText = value; + break; + case "message_id": + result.MessageId = value; + break; + case "charge": + result.Charge = value; + break; + default: + break; } } - catch - { - } return result; } - - public void Dispose() => httpClient?.Dispose(); } - diff --git a/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniSmsSender.cs b/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniSmsSender.cs index e8df71e1..63847c8b 100644 --- a/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniSmsSender.cs +++ b/api/modules/Sozsoft.Sender/Sms/PostaGuvercini/PostaGuverciniSmsSender.cs @@ -19,7 +19,7 @@ public class PostaGuverciniSmsSender : ISozsoftSmsSender public Task SendAsync(SmsMessage smsMessage) => SendAsync(smsMessage.PhoneNumber, smsMessage.Text); - public async Task SendAsync(string PhoneNumber, string Message) + public async Task SendAsync(string PhoneNumber, string Message) { PhoneNumber = Check.NotNullOrWhiteSpace(PhoneNumber, nameof(PhoneNumber), 11, 10); Message = Check.NotNullOrWhiteSpace(Message, nameof(Message)); @@ -36,7 +36,7 @@ public class PostaGuverciniSmsSender : ISozsoftSmsSender logger.LogWarning("Failed to send SMS message. {PhoneNumber}, {Message}, {Error}", PhoneNumber, Message, $"{result.ErrNo}:{result.ErrText}"); } - return new SmsSendResult + return new SendResult { Success = result.IsSuccess, ErrorMessage = $"{result.ErrNo}:{result.ErrText}", diff --git a/api/modules/Sozsoft.Sender/Sms/SmsSendResult.cs b/api/modules/Sozsoft.Sender/Sms/SmsSendResult.cs deleted file mode 100644 index faca3af2..00000000 --- a/api/modules/Sozsoft.Sender/Sms/SmsSendResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sozsoft.Sender.Sms; - -public class SmsSendResult -{ - public bool Success { get; set; } - public string MessageId { get; set; } //Eger basarili ise gateway messageId - public string ErrorMessage { get; set; } //Basarisiz ise hata mesaji -} - diff --git a/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs b/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs index f39f2463..b10f9381 100644 --- a/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs +++ b/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs @@ -18,18 +18,18 @@ namespace Sozsoft.Sender; [DependsOn( typeof(AbpEmailingModule), typeof(AbpSmsModule))] -public class ErpSenderModule : AbpModule +public class SozsoftSenderModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { - options.Resources.Add("en"); + options.Resources.Add("en"); }); Configure(options => { - options.AddJob(); + options.AddJob(); }); //Register a factory method that resolves from IServiceProvider diff --git a/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs b/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs index ab6e681f..136a9930 100644 --- a/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs +++ b/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs @@ -5,7 +5,7 @@ namespace Sozsoft.Sender.Telegram; public interface ISozsoftTelegramSender : ITransientDependency { - Task SendAsync(string ChatId, string Message); + Task SendAsync(string ChatId, string Message); } public class SozsoftTelegramSender : ISozsoftTelegramSender @@ -21,7 +21,7 @@ public class SozsoftTelegramSender : ISozsoftTelegramSender this.logger = logger; } - public async Task SendAsync(string ChatId, string Message) + public async Task SendAsync(string ChatId, string Message) { logger.LogInformation("Sending Telegram message. {ChatId}, {Message}", ChatId, Message); var result = await httpClient.SendMessageAsync(ChatId, Message); @@ -36,9 +36,11 @@ public class SozsoftTelegramSender : ISozsoftTelegramSender ChatId, Message, result?.error_code, result?.description); } - return new TelegramSendResult + return new SendResult { Success = success, + MessageId = success ? result!.result?.message_id.ToString() : null, + ErrorMessage = success ? null : result?.description, }; } } diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs index 034bd983..69dcd32d 100644 --- a/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs @@ -1,33 +1,28 @@ -using System.Net.Http.Json; using Volo.Abp.Settings; namespace Sozsoft.Sender.Telegram; public interface ITelegramHttpClient { - Task SendMessageAsync(string ChatId, string Message); + Task SendMessageAsync(string ChatId, string Message); } -public sealed class TelegramHttpClient : ITelegramHttpClient, IDisposable +public sealed class TelegramHttpClient : SettingHttpClientBase, ITelegramHttpClient { - private readonly HttpClient httpClient; - private readonly ISettingProvider settingProvider; - - public TelegramHttpClient( - HttpClient httpClient, - ISettingProvider settingProvider) + public TelegramHttpClient(HttpClient httpClient, ISettingProvider settingProvider) + : base(httpClient, settingProvider) { - this.httpClient = httpClient; - this.settingProvider = settingProvider; } - public async Task SendMessageAsync(string ChatId, string Message) + public async Task SendMessageAsync(string ChatId, string Message) { - var url = await settingProvider.GetOrNullAsync(TelegramSettingNames.Url); - var botToken = await settingProvider.GetOrNullAsync(TelegramSettingNames.BotToken); - var parseMode = await settingProvider.GetOrNullAsync(TelegramSettingNames.ParseMode); + var url = await GetSettingAsync(TelegramSettingNames.Url); + var botToken = await GetSettingAsync(TelegramSettingNames.BotToken); + var parseMode = await GetSettingAsync(TelegramSettingNames.ParseMode); - var response = await httpClient.PostAsJsonAsync($"{url.TrimEnd('/')}/bot{botToken}/sendMessage", + // Bot API hatayı 4xx gövdesinde açıklar; durum kodunu fırlatmak açıklamayı kaybettirir. + return await PostJsonAsync( + $"{url?.TrimEnd('/')}/bot{botToken}/sendMessage", new TelegramPostMessageInput { chat_id = ChatId, @@ -35,10 +30,5 @@ public sealed class TelegramHttpClient : ITelegramHttpClient, IDisposable parse_mode = parseMode.IsNullOrWhiteSpace() ? null : parseMode, disable_web_page_preview = true }); - - // Bot API hatayı 4xx gövdesinde açıklar; durum kodunu fırlatmak açıklamayı kaybettirir. - return await response.Content.ReadFromJsonAsync(); } - - public void Dispose() => httpClient?.Dispose(); } diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs deleted file mode 100644 index b7215513..00000000 --- a/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Sozsoft.Sender.Telegram; - -public class TelegramSendResult -{ - public bool Success { get; set; } -} diff --git a/api/modules/Sozsoft.Sender/WhatsApp/SozsoftWhatsAppSender.cs b/api/modules/Sozsoft.Sender/WhatsApp/SozsoftWhatsAppSender.cs index 7a43606e..a667d376 100644 --- a/api/modules/Sozsoft.Sender/WhatsApp/SozsoftWhatsAppSender.cs +++ b/api/modules/Sozsoft.Sender/WhatsApp/SozsoftWhatsAppSender.cs @@ -5,7 +5,7 @@ namespace Sozsoft.Sender.WhatsApp; public interface ISozsoftWhatsAppSender : ITransientDependency { - Task SendAsync(string To, string Message, string LanguageCode = "tr"); + Task SendAsync(string To, string Message, string LanguageCode = "tr"); } public class SozsoftWhatsAppSender : ISozsoftWhatsAppSender @@ -21,11 +21,12 @@ public class SozsoftWhatsAppSender : ISozsoftWhatsAppSender this.logger = logger; } - public async Task SendAsync(string To, string Message, string LanguageCode = "tr") + public async Task SendAsync(string To, string Message, string LanguageCode = "tr") { logger.LogInformation("Sending WhatsApp message. {To}, {LanguageCode}, {Message}", To, LanguageCode, Message); var result = await httpClient.SendMessageAsync(To, Message, LanguageCode); - var success = !result.messages.IsNullOrEmpty() && result.messages.FirstOrDefault()?.message_status == "accepted"; + var message = result?.messages?.FirstOrDefault(); + var success = message?.message_status == "accepted"; if (success) { logger.LogInformation("WhatsApp sent successfully. {To}, {LanguageCode}, {Message}", To, LanguageCode, Message); @@ -35,10 +36,10 @@ public class SozsoftWhatsAppSender : ISozsoftWhatsAppSender logger.LogWarning("Failed to send WhatsApp message. {To}, {LanguageCode}, {Message}", To, LanguageCode, Message); } - return new WhatsAppSendResult + return new SendResult { Success = success, + MessageId = success ? message!.id : null, }; } } - diff --git a/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppHttpClient.cs b/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppHttpClient.cs index f784823f..371c734e 100644 --- a/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppHttpClient.cs +++ b/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppHttpClient.cs @@ -1,36 +1,29 @@ using System.Net.Http.Headers; -using System.Net.Http.Json; using Volo.Abp.Settings; namespace Sozsoft.Sender.WhatsApp; public interface IWhatsAppHttpClient { - Task SendMessageAsync(string To, string Message, string LanguageCode = "tr"); + Task SendMessageAsync(string To, string Message, string LanguageCode = "tr"); } -public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable +public sealed class WhatsAppHttpClient : SettingHttpClientBase, IWhatsAppHttpClient { - private readonly HttpClient httpClient; - private readonly ISettingProvider settingProvider; - - public WhatsAppHttpClient( - HttpClient httpClient, - ISettingProvider settingProvider) + public WhatsAppHttpClient(HttpClient httpClient, ISettingProvider settingProvider) + : base(httpClient, settingProvider) { - this.httpClient = httpClient; - this.settingProvider = settingProvider; } - public async Task SendMessageAsync(string To, string Message, string LanguageCode = "tr") + public async Task SendMessageAsync(string To, string Message, string LanguageCode = "tr") { - var url = await settingProvider.GetOrNullAsync(WhatsAppSettingNames.Url); - var templateName = await settingProvider.GetOrNullAsync(WhatsAppSettingNames.TemplateName); - var token = await settingProvider.GetOrNullAsync(WhatsAppSettingNames.Token); - var phoneId = await settingProvider.GetOrNullAsync(WhatsAppSettingNames.PhoneNumberId); + var url = await GetSettingAsync(WhatsAppSettingNames.Url); + var templateName = await GetSettingAsync(WhatsAppSettingNames.TemplateName); + var token = await GetSettingAsync(WhatsAppSettingNames.Token); + var phoneId = await GetSettingAsync(WhatsAppSettingNames.PhoneNumberId); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - var response = await httpClient.PostAsJsonAsync($"{url}/{phoneId}/messages", + return await PostJsonAsync( + $"{url}/{phoneId}/messages", new WhatsAppPostMessageInput { messaging_product = "whatsapp", @@ -59,10 +52,7 @@ public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable } ] } - }); - return await response.Content.ReadFromJsonAsync(); + }, + request => request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token)); } - - public void Dispose() => httpClient?.Dispose(); } - diff --git a/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppSendResult.cs b/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppSendResult.cs deleted file mode 100644 index ae9e784c..00000000 --- a/api/modules/Sozsoft.Sender/WhatsApp/WhatsAppSendResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sozsoft.Sender.WhatsApp; - -public class WhatsAppSendResult -{ - public bool Success { get; set; } -} - diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs index cbbc668b..5837bb17 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingUiAppService.cs @@ -13,27 +13,26 @@ namespace Sozsoft.Settings; [Authorize] public class SettingUiAppService : ApplicationService, ISettingUiAppService { - //private readonly IRepository repositorySettingDefinition; private readonly ISettingDefinitionManager settingDefinitionManager; private readonly ISettingManager settingManager; - private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager; + private readonly SettingDefinitionManager platformSettingDefinitionManager; private readonly ICurrentTenant currentTenant; public SettingUiAppService( ISettingDefinitionManager settingDefinitionManager, ISettingManager settingManager, - ErpSettingDefinitionManager ErpSettingDefinitionManager, + SettingDefinitionManager platformSettingDefinitionManager, ICurrentTenant currentTenant) { this.settingDefinitionManager = settingDefinitionManager; this.settingManager = settingManager; - this.ErpSettingDefinitionManager = ErpSettingDefinitionManager; + this.platformSettingDefinitionManager = platformSettingDefinitionManager; this.currentTenant = currentTenant; } public virtual async Task> GetListAsync() { - var groups = await ErpSettingDefinitionManager.GetGroupedSettingDefinitionsAsync(); + var groups = await platformSettingDefinitionManager.GetGroupedSettingDefinitionsAsync(); return groups.Select(group => new MainGroupedSettingDto { GroupName = group.GroupName, diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingsMappers.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingsMappers.cs index b6baa4fd..3b779212 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingsMappers.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Application/SettingsMappers.cs @@ -11,14 +11,6 @@ public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMappe public override partial void Map(SettingDefinition source, SettingDefinitionDto destination); - public override void BeforeMap(SettingDefinition source) - { - } - - public override void AfterMap(SettingDefinition source, SettingDefinitionDto destination) - { - } - public override partial SettingDefinition ReverseMap(SettingDefinitionDto destination); public override partial void ReverseMap(SettingDefinitionDto destination, SettingDefinition source); diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/SettingDefinitionManager.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/SettingDefinitionManager.cs index af2fdd3b..c081a7d9 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/SettingDefinitionManager.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/SettingDefinitionManager.cs @@ -12,14 +12,14 @@ using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition; namespace Sozsoft.Settings; -public class ErpSettingDefinitionManager : DomainService +public class SettingDefinitionManager : DomainService { private readonly IRepository repository; private readonly IStringLocalizer l; private readonly ISettingProvider settingProvider; private readonly IAuthorizationService authorizationService; - public ErpSettingDefinitionManager( + public SettingDefinitionManager( IRepository repository, IStringLocalizer l, ISettingProvider settingProvider, @@ -30,8 +30,6 @@ public class ErpSettingDefinitionManager : DomainService this.l = l; this.settingProvider = settingProvider; this.authorizationService = authorizationService; - //TODO: Bu sekilde settingleri alip vt ile Code uzerinden kiyaslayip insert eden seeder yazalim - //var settingDefinitions = _settingDefinitionManager.GetAll(); } public virtual async Task> GetGroupedSettingDefinitionsAsync() @@ -88,24 +86,5 @@ public class ErpSettingDefinitionManager : DomainService return result; } - - //private async Task CheckPermissionsAsync(List permissions) - //{ - // //TODO: permisson tanimli degilse hata firlatma - // var results = new List(); - // try - // { - // foreach (var item in permissions) - // { - // results.Add(await authorizationService.IsGrantedAsync(item)); - // } - // } - // catch - // { - // results.Add(false); - // } - - // return results.Any(a => a); - //} } diff --git a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/Settings/SettingsSettingDefinitionProvider.cs b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/Settings/SettingsSettingDefinitionProvider.cs index 60e5b3e6..9f92b746 100644 --- a/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/Settings/SettingsSettingDefinitionProvider.cs +++ b/api/modules/Sozsoft.Settings/Sozsoft.Settings.Domain/Settings/SettingsSettingDefinitionProvider.cs @@ -68,42 +68,3 @@ public class SettingsDefinitionProvider : SettingDefinitionProvider return new LocalizableString(typeof(SettingsResource), name); } } - -/* Definitionlar normalde eziliyor fakat bir sebepten mevcut definitionlar ezilmezse, mevcutları alıp değiştirmek için gerekli yapı şu şekilde: -var iDescription = item.DescriptionKey.IsNullOrEmpty() ? null : L(item.DescriptionKey); -var providers = item.Providers.IsNullOrEmpty() ? new string[] { } : item.Providers.ToArray(); - -var def = context.GetOrNull(item.Code); - -if (def == null) -{ - def = new Volo.Abp.Settings.SettingDefinition( - item.Code, item.DefaultValue, L(item.NameKey), iDescription, - item.IsVisibleToClients, item.IsInherited, item.IsEncrypted) - .WithProperty(SettingsConsts.MainGroup, L(item.MainGroupKey)) - .WithProperty(SettingsConsts.SubGroup, L(item.SubGroupKey)) - .WithProperty(SettingsConsts.DataType, item.DataType) - .WithProperty(SettingsConsts.RequiredPermission, item.RequiredPermissionName) - .WithProperty(SettingsConsts.SelectOptions, item.SelectOptions?.ToDictionary(x => x.Key, x => L(x.Value))) - .WithProviders(providers); - context.Add(def); -} -else -{ - def.DefaultValue = item.DefaultValue; - def.DisplayName = L(item.NameKey); - def.Description = iDescription; - def.IsVisibleToClients = item.IsVisibleToClients; - def.IsInherited = item.IsInherited; - def.IsEncrypted = item.IsEncrypted; - def.Properties.Clear(); - def.Providers.Clear(); - def.WithProperty(SettingsConsts.MainGroup, L(item.MainGroupKey)) - .WithProperty(SettingsConsts.SubGroup, L(item.SubGroupKey)) - .WithProperty(SettingsConsts.DataType, item.DataType) - .WithProperty(SettingsConsts.RequiredPermission, item.RequiredPermissionName) - .WithProperty(SettingsConsts.SelectOptions, item.SelectOptions?.ToDictionary(x => x.Key, x => L(x.Value))) - .WithProviders(providers); -} - */ - diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs index a9e42cb6..95284096 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Application/SqlObjectManagerAppService.cs @@ -9,8 +9,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Enums; +using Sozsoft.Platform.Localization; using Sozsoft.Platform.Queries; using Sozsoft.Settings; using Sozsoft.SqlQueryManager.Application.Contracts; @@ -27,8 +29,6 @@ namespace Sozsoft.SqlQueryManager.Application; [Authorize("App.SqlQueryManager")] public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerAppService { - private const string QueryExecutedSuccessfullyMessage = "Query executed successfully."; - private const string QueryExecutedAndDeployedMessage = "Query executed and deployed successfully"; private const string SqlIdentifierPattern = @"(?:\[[^\]]+\]|\""[^\""\r\n]+\""|[A-Za-z_][A-Za-z0-9_@$#]*)"; private const string MultiPartSqlIdentifierPattern = SqlIdentifierPattern + @"(?:\s*\.\s*" + SqlIdentifierPattern + @"){0,2}"; @@ -69,6 +69,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA _httpContextAccessor = httpContextAccessor; _configuration = configuration; _logger = logger; + LocalizationResource = typeof(PlatformResource); } private string GetTenantFromHeader() @@ -88,7 +89,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA { if (headerTenant != currentTenantName) { - throw new Volo.Abp.UserFriendlyException($"Tenant mismatch. Header tenant '{headerTenant}' does not match current tenant '{currentTenantName}'."); + throw new Volo.Abp.UserFriendlyException(L["App.SqlQueryManager.TenantMismatch", headerTenant, currentTenantName]); } } } @@ -100,9 +101,11 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA var dataSourceType = await GetDataSourceTypeAsync(dataSourceCode); result.Tables = await GetTablesAsync(dataSourceCode, dataSourceType); - result.Views = await GetNativeObjectsAsync(dataSourceCode, dataSourceType, "V"); - result.StoredProcedures = await GetNativeObjectsAsync(dataSourceCode, dataSourceType, "P"); - result.Functions = await GetNativeObjectsAsync(dataSourceCode, dataSourceType, "FN", "IF", "TF"); + + var nativeObjects = await GetNativeObjectsAsync(dataSourceCode, dataSourceType); + result.Views = OfKinds(nativeObjects, SqlCatalogQueries.ObjectKinds.View); + result.StoredProcedures = OfKinds(nativeObjects, SqlCatalogQueries.ObjectKinds.Procedure); + result.Functions = OfKinds(nativeObjects, SqlCatalogQueries.ObjectKinds.ScalarFunction, SqlCatalogQueries.ObjectKinds.TableFunction); result.Templates = _templateProvider.GetAvailableQueryTemplates() .Select(t => new SqlTemplateDto @@ -122,37 +125,36 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA var dataSource = await _dataSourceManager.GetDataSourceAsync(_currentTenant.IsAvailable, dataSourceCode); if (dataSource == null) { - throw new Volo.Abp.UserFriendlyException($"Data source '{dataSourceCode}' was not found."); + throw new Volo.Abp.UserFriendlyException(L["App.SqlQueryManager.DataSourceNotFound", dataSourceCode]); } return dataSource.DataSourceType; } - private async Task> GetNativeObjectsAsync( + /// + /// Katalog bir kez okunur; tür başına liste, katalog sırasını (şema + ad) koruyarak süzülür. + /// + private async Task> GetNativeObjectsAsync( string dataSourceCode, - DataSourceTypeEnum dataSourceType, - params string[] objectTypes) + DataSourceTypeEnum dataSourceType) { - var query = dataSourceType == DataSourceTypeEnum.Postgresql - ? BuildPostgreSqlNativeObjectsQuery(objectTypes) - : BuildSqlServerNativeObjectsQuery(objectTypes); + var result = await _sqlExecutorService.ExecuteQueryAsync(SqlCatalogQueries.NativeObjects(dataSourceType), dataSourceCode); - var result = await _sqlExecutorService.ExecuteQueryAsync(query, dataSourceCode); - - var objects = new List(); + var objects = new List<(string Kind, SqlNativeObjectDto Dto)>(); if (result.Success && result.Data != null) { foreach (var row in result.Data) { - var dict = row as IDictionary; - if (dict != null) + if (row is IDictionary dict) { - objects.Add(new SqlNativeObjectDto - { - SchemaName = GetDictionaryValue(dict, "SchemaName")?.ToString() ?? GetDefaultSchemaName(dataSourceType), - ObjectName = GetDictionaryValue(dict, "ObjectName")?.ToString() ?? "", - DataSourceType = dataSourceType.ToString() - }); + objects.Add(( + GetDictionaryValue(dict, "ObjectKind")?.ToString() ?? "", + new SqlNativeObjectDto + { + SchemaName = GetDictionaryValue(dict, "SchemaName")?.ToString() ?? SqlIdentifier.DefaultSchema(dataSourceType), + ObjectName = GetDictionaryValue(dict, "ObjectName")?.ToString() ?? "", + DataSourceType = dataSourceType.ToString() + })); } } } @@ -160,60 +162,8 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA return objects; } - private static string BuildSqlServerNativeObjectsQuery(params string[] objectTypes) - { - var typeList = string.Join(",", objectTypes.Select(t => $"'{t}'")); - return $@" - SELECT - SCHEMA_NAME(o.schema_id) AS SchemaName, - o.name AS ObjectName - FROM - sys.objects o - WHERE - o.type IN ({typeList}) - AND o.is_ms_shipped = 0 - ORDER BY - SCHEMA_NAME(o.schema_id), o.name"; - } - - private static string BuildPostgreSqlNativeObjectsQuery(params string[] objectTypes) - { - var wantsViews = objectTypes.Contains("V"); - var wantsProcedures = objectTypes.Contains("P"); - var wantsFunctions = objectTypes.Any(t => t is "FN" or "IF" or "TF"); - - if (wantsViews) - { - return @" - SELECT - table_schema AS ""SchemaName"", - table_name AS ""ObjectName"" - FROM information_schema.views - WHERE table_schema NOT IN ('pg_catalog', 'information_schema') - ORDER BY table_schema, table_name"; - } - - var proKinds = new List(); - if (wantsProcedures) - { - proKinds.Add("'p'"); - } - - if (wantsFunctions) - { - proKinds.Add("'f'"); - } - - return $@" - SELECT - n.nspname AS ""SchemaName"", - p.proname AS ""ObjectName"" - FROM pg_proc p - INNER JOIN pg_namespace n ON n.oid = p.pronamespace - WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') - AND p.prokind IN ({string.Join(",", proKinds)}) - ORDER BY n.nspname, p.proname"; - } + private static List OfKinds(List<(string Kind, SqlNativeObjectDto Dto)> objects, params string[] kinds) + => [.. objects.Where(o => kinds.Contains(o.Kind)).Select(o => o.Dto)]; private async Task> GetTablesAsync(string dataSourceCode, DataSourceTypeEnum dataSourceType) { @@ -249,7 +199,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA { tables.Add(new DatabaseTableDto { - SchemaName = GetDictionaryValue(dict, "SchemaName")?.ToString() ?? GetDefaultSchemaName(dataSourceType), + SchemaName = GetDictionaryValue(dict, "SchemaName")?.ToString() ?? SqlIdentifier.DefaultSchema(dataSourceType), TableName = GetDictionaryValue(dict, "TableName")?.ToString() ?? "", DataSourceType = dataSourceType.ToString() }); @@ -611,11 +561,6 @@ FROM ( return string.IsNullOrEmpty(match.Key) ? null : match.Value; } - private static string GetDefaultSchemaName(DataSourceTypeEnum dataSourceType) - { - return dataSourceType == DataSourceTypeEnum.Postgresql ? "public" : "dbo"; - } - private static string ToSqlLiteral(string value) { return $"'{(value ?? string.Empty).Replace("'", "''")}'"; @@ -649,7 +594,7 @@ FROM ( { Success = result.Success, Message = result.Success - ? (isDeployed ? QueryExecutedAndDeployedMessage : QueryExecutedSuccessfullyMessage) + ? L[isDeployed ? "App.SqlQueryManager.QueryExecutedAndDeployed" : "App.Platform.QueryExecutedSuccessfully"] : result.Message, Data = result.Data, RowsAffected = result.RowsAffected, @@ -666,7 +611,7 @@ FROM ( input.FileName.Contains('\\') || input.FileName.Contains("..")) { - throw new Volo.Abp.UserFriendlyException("Invalid file name."); + throw new Volo.Abp.UserFriendlyException(L["App.Platform.InvalidFileName"]); } try diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SozsoftSqlQueryManagerResource.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SozsoftSqlQueryManagerResource.cs index 8b031b94..a7f5c252 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SozsoftSqlQueryManagerResource.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SozsoftSqlQueryManagerResource.cs @@ -3,6 +3,6 @@ namespace Sozsoft.SqlQueryManager.Domain.Shared; [LocalizationResourceName("SqlQueryManager")] -public class ErpSqlQueryManagerResource +public class SozsoftSqlQueryManagerResource { } diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SqlQueryManagerDomainSharedModule.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SqlQueryManagerDomainSharedModule.cs index d4e3417f..0c1f44dc 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SqlQueryManagerDomainSharedModule.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain.Shared/SqlQueryManagerDomainSharedModule.cs @@ -23,12 +23,12 @@ public class SqlQueryManagerDomainSharedModule : AbpModule Configure(options => { - options.Resources.Add("en"); + options.Resources.Add("en"); }); Configure(options => { - options.MapCodeNamespace("SqlQueryManager", typeof(ErpSqlQueryManagerResource)); + options.MapCodeNamespace("SqlQueryManager", typeof(SozsoftSqlQueryManagerResource)); }); } } diff --git a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain/Services/SqlExecutorService.cs b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain/Services/SqlExecutorService.cs index 62e569ba..56d547d6 100644 --- a/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain/Services/SqlExecutorService.cs +++ b/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain/Services/SqlExecutorService.cs @@ -5,8 +5,10 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Enums; +using Sozsoft.Platform.Localization; using Sozsoft.Platform.Queries; using Volo.Abp; using Volo.Abp.Domain.Services; @@ -17,13 +19,16 @@ public class SqlExecutorService : DomainService, ISqlExecutorService { private readonly IDataSourceManager _dataSourceManager; private readonly IServiceProvider _serviceProvider; + private readonly IStringLocalizer _localizer; public SqlExecutorService( IDataSourceManager dataSourceManager, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + IStringLocalizer localizer) { _dataSourceManager = dataSourceManager; _serviceProvider = serviceProvider; + _localizer = localizer; } private async Task GetRepositoryAsync(string dataSourceCode) @@ -92,13 +97,13 @@ public class SqlExecutorService : DomainService, ISqlExecutorService result.Data = data; result.RowsAffected = data?.Count() ?? 0; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; - result.Message = $"Query executed successfully."; + result.Message = _localizer["App.Platform.QueryExecutedSuccessfully"]; } catch (Exception ex) { stopwatch.Stop(); result.Success = false; - result.Message = $"Query execution failed: {ex.Message}"; + result.Message = _localizer["App.SqlQueryManager.QueryExecutionFailed", ex.Message]; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; result.Metadata["ErrorDetail"] = ex.ToString(); } @@ -134,13 +139,13 @@ public class SqlExecutorService : DomainService, ISqlExecutorService result.Success = true; result.RowsAffected = rowsAffected; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; - result.Message = $"Command executed successfully. Rows affected: {rowsAffected}"; + result.Message = _localizer["App.SqlQueryManager.CommandExecuted", rowsAffected]; } catch (Exception ex) { stopwatch.Stop(); result.Success = false; - result.Message = $"Command execution failed: {ex.Message}"; + result.Message = _localizer["App.SqlQueryManager.CommandExecutionFailed", ex.Message]; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; result.Metadata["ErrorDetail"] = ex.ToString(); } @@ -176,14 +181,14 @@ public class SqlExecutorService : DomainService, ISqlExecutorService result.Success = true; result.Data = new[] { new { Value = scalarValue } }; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; - result.Message = "Scalar query executed successfully"; + result.Message = _localizer["App.SqlQueryManager.ScalarQueryExecuted"]; result.Metadata["ScalarValue"] = scalarValue; } catch (Exception ex) { stopwatch.Stop(); result.Success = false; - result.Message = $"Scalar query execution failed: {ex.Message}"; + result.Message = _localizer["App.SqlQueryManager.ScalarQueryFailed", ex.Message]; result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds; result.Metadata["ErrorDetail"] = ex.ToString(); } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsDto.cs deleted file mode 100644 index 2a392d77..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsDto.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Sozsoft.Platform.Dashboard; - -public class DashboardWidgetsDto -{ - public int Id { get; set; } - public string Label { get; set; } - public Single? DataValue { get; set; } - public Single? DataGrowShrink { get; set; } - public string ValuePrefix { get; set; } - public DateTime Date { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsRequestDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsRequestDto.cs deleted file mode 100644 index 205b7a61..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Dashboard/DashboardWidgetsRequestDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Sozsoft.Platform.Dashboard; - -public class DashboardWidgetsRequestDto -{ - public DateTime Tarih1 { get; set; } - public DateTime Tarih2 { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/CustomComponentDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/CustomComponentDto.cs index cd471518..308d1df5 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/CustomComponentDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/DeveloperKit/CustomComponentDto.cs @@ -30,7 +30,7 @@ public class CreateUpdateCustomComponentDto /// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz. /// [StringLength(512)] - [RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")] + [RegularExpression(@"^(/.*)?$", ErrorMessage = "App.Validation.RoutePathMustStartWithSlash")] public string? RoutePath { get; set; } public string Code { get; set; } = string.Empty; public string? Props { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnimationDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnimationDto.cs index 2f07a108..a1563b69 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnimationDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnimationDto.cs @@ -8,7 +8,7 @@ public class ChartAnimationDto { /// Animasyonun milisaniye cinsinden ne kadar süreceğini belirtir. /// - [Range(100, 2500, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + [Range(100, 2500, ErrorMessage = "App.Validation.RangeBetween")] public int Duration { get; set; } = 1000; /// Animasyonun kolaylaştırma işlevini belirtir. /// Accepted Values: 'easeOutCubic' | 'linear' diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnnotationDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnnotationDto.cs index e5afc9ff..a2480895 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnnotationDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartAnnotationDto.cs @@ -86,21 +86,4 @@ public class ChartAnnotationDto /// Ek açıklamanın merkezini belirli bir piksel koordinatına konumlandırmak için x ile birlikte kullanılır. (0, 0), UI bileşeninin sol üst köşesidir. /// public int Y { get; set; } - - // allowDragging - // arrowLength - // arrowWidth - // axis - // component - // customizeTooltip - // data - // opacity - // render for react - // shadow {} - // template - // textOverflow - // tooltipComponent for react - // tooltipRender - // tooltipTemplate } - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartArgumentAxisDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartArgumentAxisDto.cs index 5d541aef..4888eada 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartArgumentAxisDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartArgumentAxisDto.cs @@ -74,48 +74,4 @@ public class ChartArgumentAxisDto /// Eksen üzerinde yalnızca iş günlerini bırakır: çalışma haftası günleri artı tek iş günleri eksi tatiller. Yalnızca eksenin argümanTürü "tarihsaat" ise geçerlidir. /// public bool WorkdaysOnly { get; set; } - - // ihtiyaç olunca açılacak proptert ler - - // aggregateByCategory - // aggregatedPointsPosition - // aggregationGroupWidth - // allowDecimals - // axisDivisionFactor // İki komşu ana çizgi arasındaki minimum mesafeyi piksel cinsinden belirtir. Yalnızca "sürekli" ve "logaritmik" tiplerin eksenleri için geçerlidir. - // breaks[] - // breakStyle - // categories - // color - // constantLines[] - // constantLineStyle - // customPosition - // customPositionAxis - // holidays - // inverted - // linearThreshold - // logarithmBase - // maxValueMargin - // minorGrid - // minorTick - // minorTickCount - // minorTickInterval - // minValueMargin - // minVisualRangeLength - // offset - // opecity - // placeholderSize - // singleWorkdays - // strips[] - // stripStyle - // tick - // tickInterval - // type - // valueMarginsEnabled - // visualRange - // visualRangeUpdateMode - // wholeRange - // workWeek - } - - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartBorderDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartBorderDto.cs index fbd1e43f..4f08ab2d 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartBorderDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartBorderDto.cs @@ -16,7 +16,7 @@ public class ChartBorderDto public string Color { get; set; } = "#d3d3d3"; /// Legend in tüm köşelerini yuvarlatır. /// - [Range(0, 100, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + [Range(0, 100, ErrorMessage = "App.Validation.RangeBetween")] public int CornerRadius { get; set; } = 0; /// Açıklamanın kenarlığı için bir tire stili ayarlar /// Accepted Values: 'dash' | 'dot' | 'longDash' | 'solid' @@ -31,7 +31,6 @@ public class ChartBorderDto ///// Açıklamanın kenarlığının saydamlığını belirtir ///// Bu özellik, 0 ile 1 arasında bir değer kabul eder; burada 0, kenarlığı tamamen saydam, 1 ise opak yapar. ///// - //[Range(0.1, 1, ErrorMessage = "Value for {0} must be between {1} and {2}.")] //public double Opacity { get; set; } = 0.5; public bool Visible { get; set; } /// Açıklamanın kenarlığının genişliğini piksel cinsinden belirtir diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAnnotationDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAnnotationDto.cs index c71be03c..3f2b24b0 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAnnotationDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAnnotationDto.cs @@ -81,21 +81,4 @@ public class ChartCommonAnnotationDto /// Ek açıklamanın merkezini belirli bir piksel koordinatına konumlandırmak için x ile birlikte kullanılır. (0, 0), UI bileşeninin sol üst köşesidir. /// public int Y { get; set; } - - // allowDragging - // arrowLength - // arrowWidth - // axis - // component - // customizeTooltip - // data - // opacity - // render for react - // shadow {} - // template - // textOverflow - // tooltipComponent for react - // tooltipRender - // tooltipTemplate } - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAxisDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAxisDto.cs index 1f3d66c6..487721eb 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAxisDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonAxisDto.cs @@ -21,25 +21,4 @@ public class ChartCommonAxisDto /// Eksen çizgisinin genişliğini piksel cinsinden belirtir. /// public int Width { get; set; } = 1; - - // ihtiyaç olunca açılacak proptert ler - - // aggregatedPointsPosition - // allowDecimals - // breakStyle {} - // color - // constantLineStyle {} - // discreteAxisDivisionMode - // endOnTick - // inverted - // label {} - // maxValueMargin - // minorGrid {} - // minorTick {} - // minValueMargin - // opacity - // placeholderSize - // stripStyle {} - // tick {} - // valueMarginsEnabled } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonDto.cs index 431e1235..7ea7ce68 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonDto.cs @@ -34,27 +34,4 @@ public class ChartCommonDto /// Accepted Values: 'generic.dark' | 'generic.light' | 'generic.contrast' | 'generic.carmine' | 'generic.darkmoon' | 'generic.darkviolet' | 'generic.greenmist' | 'generic.softblue' | 'material.blue.light' | 'material.lime.light' | 'material.orange.light' | 'material.purple.light' | 'material.teal.light' /// public string Theme { get; set; } = "generic.light"; - - // diğer eklenebilecek property ler - - // autoHidePointMarkers - // barGroupPadding - // barGroupWidth - // customizeAnnotation - // customizeLabel - // customizePoint - // elementAttr - // maxBubbleSize - // minBubbleSize - // negativesAsZeroes - // pathModified - // pointSelectionMode - // redrawOnResize - // resizePanesOnZoom - // resolveLabelOverlapping - // rotated - // rtlEnabled - // seriesSelectionMode - // stickyHovering - // synchronizeMultiAxes } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonSeriesSettingsDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonSeriesSettingsDto.cs index c44f96ee..65793a16 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonSeriesSettingsDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartCommonSeriesSettingsDto.cs @@ -99,49 +99,5 @@ public class ChartCommonSeriesSettingsDto /// public int Width { get; set; } = 2; - // aggregation {} - // area {} - // bar {} - // border {} - // bubble {} - // candlestick {} - // closeValueField // for financial series - // fullstackedarea {} - // fullstackedbar {} - // fullstackedline {} - // fullstackedspline {} - // fullstackedsplinearea {} - // highValueField - // hoverMode - // hoverStyle - // innerColor - // label {} - // line {} - // lowValueField - // maxLabelCount - // minBarSize - // opacity - // openValueField - // point {} - // rangearea {} - // rangebar {} - // reduction {} // financial seriler için indirgeme özelliklerini belirtir. - // scatter {} - // slectionStyle // Bir kullanıcı seçtiğinde seri tarafından benimsenen görünümü yapılandırır. - // sizeField - // spline {} - // splinearea {} - // stack {} - // stackedarea {} - // stackedbar {} - // stackedline {} - // stackedspline{} - // stackedsplinearea {} - // steparea {} - // stepline {} - // stock {} - // tagField - // valueErrorBar - public ChartLabelDto Label { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartLegendDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartLegendDto.cs index 1eb857f6..23c096c4 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartLegendDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartLegendDto.cs @@ -40,33 +40,4 @@ public class ChartLegendDto /// Göstergenin görünürlüğünü belirtir /// public bool Visible { get; set; } = true; - - ////İhtiyaç olması durumunda açılacak diğer property ler - - ///// horizontalAlignment ile birlikte göstergenin konumunu belirtir - ///// Accepted Values: 'bottom' | 'top' - ///// - //public string VerticalAlignment { get; set; } = "top"; - //public int RowItemSpacing { get; set; } = 8; - //public int PaddingTopBottom { get; set; } = 10; - //public int PaddingLeftRight { get; set; } = 10; - //public string MarkerTemplate { get; set; } - //public string MarkerSize { get; set; } - //public string MarkerRender { get; set; } - //public string MarkerComponent { get; set; } - //public string Margin { get; set; } - //public string ItemTextPosition { get; set; } - //public string ItemsAlignment { get; set; } - //public string HoverMode { get; set; } - //public string HorizontalAlignment { get; set; } - //public string CustomizeText { get; set; } - //public string CustomizeItems { get; set; } - //public string CustomizeHint { get; set; } - ///// Öğe sütunları arasında piksel cinsinden boş bir alan belirtir - ///// - //public int ColumnItemSpacing { get; set; } = 20; - } - - - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartSeriesDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartSeriesDto.cs index d0e19793..93c5435e 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartSeriesDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartSeriesDto.cs @@ -101,25 +101,6 @@ public class ChartSeriesDto /// public int Width { get; set; } = 2; - // hoverStyle - // hoverMode - // highValueField // for financial series - // closeValueField // for financial series - // border Dto - // innerColor - // label Dto - // lowValueField - // maxLabelCount - // minBarSize - // openValueField - // point Dto - // reduction // financial seriler için indirgeme özelliklerini belirtir. - // slectionStyle // Bir kullanıcı seçtiğinde seri tarafından benimsenen görünümü yapılandırır. - // stack - // tag - // tagField - // valueErrorBar - public ChartLabelDto Label { get; set; } /// Değer özetleme işlevini belirtir. diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartValueAxisDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartValueAxisDto.cs index 40ab385f..8774224b 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartValueAxisDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Chart/ChartValueAxisDto.cs @@ -55,45 +55,4 @@ public class ChartValueAxisDto public string Type { get; set; } public bool AutoBreaksEnabled { get; set; } = true; public int MaxAutoBreakCount { get; set; } = 2; - - // ihtiyaç olunca açılacak property ler - - // aggregatedPointsPosition - // allowDecimals - // axisDivisionFactor - // categories - // color - // constantLines[] - // constantLineStyle - // customPosition - // discreteAxisDivisionMode - // endOnTick - // inverted - // label - // linearThreshold - // logarithmBase - // maxValueMargin - // minorGrid - // minorTick - // minorTickCount - // minorTickInterval - // minValueMargin - // minVisualRangeLength - // multipleAxesSpacing - // offset - // opacity - // pane - // placeholderSize - // showZero - // strips[] - // stripStyle - // synchronizedValue - // tick - // tickInterval - // valueMarginsEnabled - // visualRange - // visualRangeUpdateMode - // wholeRange } - - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/ColumnFormatDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/ColumnFormatDto.cs index a0064e24..b3f3a611 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/ColumnFormatDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/ColumnFormatDto.cs @@ -10,11 +10,6 @@ namespace Sozsoft.Platform.ListForms; public class ColumnFormatDto : AuditedEntityDto { - private static readonly JsonSerializerOptions EditorOptionsSerializerOptions = new() - { - PropertyNameCaseInsensitive = true - }; - public ColumnFormatDto() { } public string FieldName { get; set; } @@ -282,7 +277,7 @@ public class ColumnFormatDto : AuditedEntityDto try { - return JsonSerializer.Deserialize(EditorOptions, EditorOptionsSerializerOptions); + return JsonSerializer.Deserialize(EditorOptions, PlatformJsonOptions.CaseInsensitive); } catch (JsonException) { diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ColumnMappingDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ColumnMappingDto.cs deleted file mode 100644 index 799f1dc5..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ColumnMappingDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sozsoft.Platform.ListForms.ImportManager; - -public class ColumnMappingDto -{ - public string SourceColumn { get; set; } // CSV/Excel'deki sütun adı - public string TargetField { get; set; } // Hedef Entity içindeki alan - public bool IsRequired { get; set; } - public string DataType { get; set; } // "string", "date", "number", "boolean" -} - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ImportValidationErrorDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ImportValidationErrorDto.cs deleted file mode 100644 index 3353e893..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/ImportManager/ImportValidationErrorDto.cs +++ /dev/null @@ -1,8 +0,0 @@ -public class ImportValidationErrorDto -{ - public int Row { get; set; } - public string Column { get; set; } - public string Message { get; set; } - public string Value { get; set; } - public string Severity { get; set; } // örn: "error", "warning" -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Select/WidgetDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Select/WidgetDto.cs index e6112444..f2671d82 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Select/WidgetDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Select/WidgetDto.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +namespace Sozsoft.Platform.ListForms.Select; + public class WidgetDto { public int ColGap { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardScreenOverlay.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardScreenOverlay.cs index 65e45026..92a33f4f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardScreenOverlay.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardScreenOverlay.cs @@ -55,8 +55,6 @@ public class WizardScreenOverlay [nameof(WizardColumnItemInputDto.DbSourceType)] = nameof(ColumnFormatEditDto.SourceDbType), }; - private static readonly JsonSerializerOptions ReadOptions = new() { PropertyNameCaseInsensitive = true }; - /// /// Katmani wizard'in urettigi ListForm'a uygular. ayni donusumun /// wizard'i icin ciktisidir. @@ -145,7 +143,7 @@ public class WizardScreenOverlay target[key] = value; } - return target.Deserialize(ReadOptions); + return target.Deserialize(PlatformJsonOptions.CaseInsensitive); } /// diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs index 58fcfe81..d3dcb9ae 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Wizard/WizardSeedFileDto.cs @@ -187,8 +187,6 @@ public class WizardSeedFileDto WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; - private static readonly JsonSerializerOptions SeedReadOptions = new() { PropertyNameCaseInsensitive = true }; - /// /// Dosyaya yazilacak JSON'u uretir: once ortama ozgu alanlar temizlenir, sonra wizard /// bolumunden ekranin sozlesmesinde zaten bulunan alanlar cikarilir. Wizard bolumunde @@ -287,7 +285,7 @@ public class WizardSeedFileDto if (node?[nameof(Wizard)] is JsonObject wizardNode) FlattenWizard(wizardNode); - return node.Deserialize(SeedReadOptions)?.Rehydrate(); + return node.Deserialize(PlatformJsonOptions.CaseInsensitive)?.Rehydrate(); } /// @@ -326,7 +324,7 @@ public class WizardSeedFileDto return null; FlattenWizard(wizardNode); - var wizard = wizardNode.Deserialize(SeedReadOptions); + var wizard = wizardNode.Deserialize(PlatformJsonOptions.CaseInsensitive); if (wizard == null) return null; @@ -344,7 +342,7 @@ public class WizardSeedFileDto [nameof(Wizard)] = wizardNode.DeepClone(), [nameof(ListForm)] = listForm?.DeepClone() ?? new JsonObject(), [nameof(Fields)] = fields?.DeepClone() ?? new JsonArray(), - }.Deserialize(SeedReadOptions).Rehydrate().Wizard; + }.Deserialize(PlatformJsonOptions.CaseInsensitive).Rehydrate().Wizard; if (listForm != null) FillMissing(wizard, sections, wizardNode, ListFormOwnedWizardKeys); diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Note/NoteListRequestDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Note/NoteListRequestDto.cs index 8c738d86..2d4eb72e 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Note/NoteListRequestDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Note/NoteListRequestDto.cs @@ -1,5 +1,7 @@ using Volo.Abp.Application.Dtos; +namespace Sozsoft.Platform.Entities; + public class NoteListRequestDto : PagedAndSortedResultRequestDto { public string EntityName { get; set; } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Public/CountryGroupDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Public/CountryGroupDto.cs deleted file mode 100644 index 039f90b9..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Public/CountryGroupDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using Volo.Abp.Application.Dtos; - -namespace Sozsoft.Platform.Public; - -public class CountryGroupDto : AuditedEntityDto -{ - public string Name { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillDto.cs deleted file mode 100644 index cf0bac55..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using Volo.Abp.Application.Dtos; - -namespace Sozsoft.Platform.Skills; - -public class SkillDto : AuditedEntityDto -{ - public string Name { get; set; } - public string TypeName { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillLevelDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillLevelDto.cs deleted file mode 100644 index fe46f9c0..00000000 --- a/api/src/Sozsoft.Platform.Application.Contracts/Skill/SkillLevelDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Volo.Abp.Application.Dtos; - -namespace Sozsoft.Platform.Skills; - -public class SkillLevelDto : AuditedEntityDto -{ - public string Name { get; set; } - public int Progress { get; set; } - public bool IsDefault { get; set; } - public string TypeName { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Application/AiBots/AiBotMappers.cs b/api/src/Sozsoft.Platform.Application/AiBots/AiBotMappers.cs index 3b18a9a0..f20843f9 100644 --- a/api/src/Sozsoft.Platform.Application/AiBots/AiBotMappers.cs +++ b/api/src/Sozsoft.Platform.Application/AiBots/AiBotMappers.cs @@ -25,14 +25,6 @@ public partial class AiBotToAiBotDtoMapper : TwoWayMapperBase [MapperIgnoreTarget(nameof(AiBotDto.LastModifierId))] public override partial void Map(AiBot source, AiBotDto destination); - public override void BeforeMap(AiBot source) - { - } - - public override void AfterMap(AiBot source, AiBotDto destination) - { - } - [MapperIgnoreTarget(nameof(AiBot.IsActive))] public override partial AiBot ReverseMap(AiBotDto destination); diff --git a/api/src/Sozsoft.Platform.Application/AuditLogs/AuditLogMappers.cs b/api/src/Sozsoft.Platform.Application/AuditLogs/AuditLogMappers.cs index 0d47d987..15839df9 100644 --- a/api/src/Sozsoft.Platform.Application/AuditLogs/AuditLogMappers.cs +++ b/api/src/Sozsoft.Platform.Application/AuditLogs/AuditLogMappers.cs @@ -12,14 +12,6 @@ public partial class AuditLogToAuditLogDtoMapper : MapperBase _repo; private readonly ITenantRepository _tenantRepository; - private readonly PlatformTemplateManager _templateManager; private readonly ILogger _logger; private readonly IUnitOfWorkManager _unitOfWorkManager; public BackgroundWorkerInitializer( IRepository repo, ITenantRepository tenantRepository, - PlatformTemplateManager templateManager, ILogger logger, IUnitOfWorkManager unitOfWorkManager) { _repo = repo; _tenantRepository = tenantRepository; - _templateManager = templateManager; _logger = logger; _unitOfWorkManager = unitOfWorkManager; } @@ -93,19 +88,8 @@ public class BackgroundWorkerInitializer : ITransientDependency }); } - foreach (var worker in workers) - { - if (worker.WorkerType == WorkerTypeEnum.MailQueueWorker && !worker.Options.IsNullOrWhiteSpace()) - { - var options = JsonSerializer.Deserialize(worker.Options); - - if (options != null && !options.MailTemplate.IsNullOrEmpty()) - { - await _templateManager.AddOrUpdateTemplateAsync(worker.Name, options.MailTemplate); - } - } - } - + // Mail şablonları burada kaydedilmez: PlatformTemplateDynamicStore şablonu istendiği anda + // işin Options.MailTemplate alanından okur. _logger.LogInformation( "{WorkerCount} active background workers loaded into Hangfire.", workers.Count); diff --git a/api/src/Sozsoft.Platform.Application/Blog/BlogAppService.cs b/api/src/Sozsoft.Platform.Application/Blog/BlogAppService.cs index e233c645..74f70e42 100644 --- a/api/src/Sozsoft.Platform.Application/Blog/BlogAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Blog/BlogAppService.cs @@ -80,7 +80,7 @@ public class BlogAppService : PlatformAppService, IBlogAppService post.ContentTr = input.ContentTr; post.ContentEn = input.ContentEn; - if (input.IsPublished) post.Publish(); else post.Unpublish(); + if (input.IsPublished) post.Publish(Clock.Now); else post.Unpublish(); if (post.CategoryId != input.CategoryId) { @@ -143,7 +143,7 @@ public class BlogAppService : PlatformAppService, IBlogAppService throw new Volo.Abp.Authorization.AbpAuthorizationException(); } - post.Publish(); + post.Publish(Clock.Now); await _postRepository.UpdateAsync(post); return await GetPostAsync(id); diff --git a/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs b/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs index 57868ff3..d2c629c1 100644 --- a/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Branch/BranchAppService.cs @@ -1,11 +1,11 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Sozsoft.Platform; -using Sozsoft.Platform.Branchs; using Sozsoft.Platform.Entities; using Volo.Abp.Domain.Repositories; +namespace Sozsoft.Platform.Branchs; + public class BranchAppService : PlatformAppService { private readonly IRepository _branchRepository; @@ -26,7 +26,7 @@ public class BranchAppService : PlatformAppService // Branch kiracı filtresi taşımaz; kiracı kullanıcısı yalnızca kendi şubesini seed edebilir, host hepsini. var branch = await _branchRepository.FirstOrDefaultAsync(x => x.Id == branchId); if (branch == null || (CurrentTenant.Id.HasValue && branch.TenantId != CurrentTenant.Id)) - return new BranchSeedResultDto { Success = false, Message = "Branch not found." }; + return new BranchSeedResultDto { Success = false, Message = L["App.BranchSeed.NotFound"] }; using (CurrentTenant.Change(branch.TenantId)) { diff --git a/api/src/Sozsoft.Platform.Application/Branch/BranchMappers.cs b/api/src/Sozsoft.Platform.Application/Branch/BranchMappers.cs index e52ed232..1dddf605 100644 --- a/api/src/Sozsoft.Platform.Application/Branch/BranchMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Branch/BranchMappers.cs @@ -9,14 +9,6 @@ public partial class BranchSeedResultToBranchSeedResultDtoMapper : MapperBase
> GetWidgetsAsync(DashboardWidgetsRequestDto requestDto) - { - var ids = await OuRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.GetId()); - - var results = await SpRepository.CallSpAsync("PDashboard_Widgets", "KIM", - new Dictionary() { - { "tarih1", requestDto.Tarih1 }, - { "tarih2", requestDto.Tarih2 }, - { "ids", ids.IsNullOrEmpty() ? Guid.Empty : ids.JoinAsString("','")} - } - ); - - var dtoResults = new List(); - foreach (var item in results) - { - var dto = new DashboardWidgetsDto - { - Id = Convert.ToInt32(item["id"]), - Label = item["label"]?.ToString(), - ValuePrefix = item["valueprefix"]?.ToString(), - Date = Convert.ToDateTime(item["date"]), - DataValue = Convert.ToSingle(item["datavalue"]), - DataGrowShrink = Convert.ToSingle(item["datagrowshrink"]) - }; - - dtoResults.Add(dto); - } - - return dtoResults; - } -} - diff --git a/api/src/Sozsoft.Platform.Application/DataSource/DataSourceMappers.cs b/api/src/Sozsoft.Platform.Application/DataSource/DataSourceMappers.cs index c209b531..331b7702 100644 --- a/api/src/Sozsoft.Platform.Application/DataSource/DataSourceMappers.cs +++ b/api/src/Sozsoft.Platform.Application/DataSource/DataSourceMappers.cs @@ -11,14 +11,6 @@ public partial class DataSourceToDataSourceDtoMapper : TwoWayMapperBase @@ -102,7 +104,7 @@ public class CrudEndpointAppService : ApplicationService x.IsActive); if (!exists) - throw new UserFriendlyException($"No active endpoint defined for {entityName} {operation}"); + throw new UserFriendlyException(L["App.DeveloperKitEndpoint.NoActiveEndpoint", entityName, operation]); } } diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs index 9a42b5e8..d62e137b 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/CustomEndpointAppService.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Extensions; +using Sozsoft.Platform.Queries; using Volo.Abp.Domain.Repositories; using Volo.Abp.Tracing; using Volo.Abp.Uow; @@ -104,7 +105,6 @@ public class CustomEndpointAppService : PlatformAppService return new UnauthorizedResult(); } - Dictionary param = []; // Parametreler: // 1- Statik @@ -277,81 +277,3 @@ public class CustomEndpointAppService : PlatformAppService return values; } } - -//TODO: Custom Endpoint rol, permission seed -/* -Token İsteği Örnek: -POST /connect/token HTTP/1.1 -Host: localhost:44344 -Content-Type: application/x-www-form-urlencoded - -username=system%40sozsoft.com -&password=... -&grant_type=password -&client_id=Platform_PublicApi -&scope=offline_access%20Platform - -Custom Endpoint Seed: -SELECT * FROM PLanguage WHERE IsEnabled = @IsEnabled AND CultureName = @CultureName -INSERT INTO [dbo].[Orders] - ([CustomerName] - ,[ProductName] - ,[OrderDate] - ,[Quantity]) - OUTPUT Inserted.* - VALUES - (@CustomerName, @ProductName, @OrderDate, @Quantity) - SELECT * FROM Orders WHERE Id = SCOPE_IDENTITY() -[{ "Type": "S", "Name": "CultureName", "DefaultValue": "ar" }] - -[{ "Type": "Q", "Name": "CultureName", "DefaultValue": "ar" }] - -[ - { "Type": "P", "Name": "IsEnabled", "DefaultValue": "en", "Path": "/yxcdfn/8/:IsEnabled/:CultureName/" }, - { "Type": "P", "Name": "CultureName", "DefaultValue": "en", "Path": "/yxcdfn/8/:IsEnabled/:CultureName/" } -] - -[ - { "Type": "B", "Name": "CustomerName", "DefaultValue": "" }, - { "Type": "B", "Name": "ProductName", "DefaultValue": "" }, - { "Type": "B", "Name": "OrderDate", "DefaultValue": "@NOW" }, - { "Type": "B", "Name": "Quantity", "DefaultValue": "" } -] - - -Guid? TenantId -string Name -string Description -string Url -> https://api.sozsoft.com/api/app/dinamik/yxfgu -string Method -> GET -string Params = [ - { Type: 'Static', Name: 'StartDate', DefaultValue: '234' }, - { Type: 'Query', Name: 'StartDate', DefaultValue: '', IsRequired: false }, - { Type: 'Path', Name: 'FaturaId', DefaultValue: '', Path: '/yxfgu/fatura/:FaturaId/kalem/357' }, - { Type: 'Path', Name: 'KalemId', DefaultValue: '', Path: '/yxfgu/fatura/xxx/kalem/:KalemId' }, - { Type: 'Body', Name: 'StartDate', DefaultValue: '' }, -] -string Sql -> SELECT * FROM VSatislar WHERE MusteriId = @MusteriId AND StartDate >= @StartDate -string Permissions = [ - { ResourceType: 'User', ResourceId: 'system' }, - { ResourceType: 'User', ResourceId: 'sedat' }, -] - -Query Parameter -URL: https://api.sozsoft.com/api/app/dinamik/yxfgu -Method: GET -Parameters: ?StartDate=2024-12-31&EndDate=2025-12-31 - -Path Parameter -URL: https://api.sozsoft.com/api/app/dinamik/yxfgu/fatura/467/kalem/357 -Method: GET -Parameters: FaturaId=467 - -Body Parameter -URL: https://api.sozsoft.com/api/app/dinamik/yxfgu?UrunId=5 -Method: POST -Parameters: ?StartDate=2024-12-31&EndDate=2025-12-31 -Body: { Tutar: 2000, Tarih: '2024-12-31' } - -*/ - diff --git a/api/src/Sozsoft.Platform.Application/DeveloperKit/DeveloperKitMappers.cs b/api/src/Sozsoft.Platform.Application/DeveloperKit/DeveloperKitMappers.cs index 143a87c3..5a7439d9 100644 --- a/api/src/Sozsoft.Platform.Application/DeveloperKit/DeveloperKitMappers.cs +++ b/api/src/Sozsoft.Platform.Application/DeveloperKit/DeveloperKitMappers.cs @@ -10,14 +10,6 @@ public partial class CustomComponentToCustomComponentDtoMapper : MapperBase _logger; + private readonly IStringLocalizer _localizer; // Yüklenmiş dinamik assembly'leri takip etmek için private static readonly List _loadedAssemblies = new(); @@ -72,9 +75,10 @@ public class DynamicServiceCompiler "Assembly.Load" }; - public DynamicServiceCompiler(ILogger logger) + public DynamicServiceCompiler(ILogger logger, IStringLocalizer localizer) { _logger = logger; + _localizer = localizer; } /// @@ -135,7 +139,7 @@ public class DynamicServiceCompiler if (!result.Success) { - result.ErrorMessage = $"Derleme {result.Errors.Count} hata ile başarısız oldu."; + result.ErrorMessage = _localizer["App.DynamicServicesEditor.CompileFailedWithCount", result.Errors.Count]; } _logger.LogInformation("Compilation completed. Success: {Success}, Time: {Time}ms, Error Count: {ErrorCount}", @@ -151,7 +155,7 @@ public class DynamicServiceCompiler return new CompileResultDto { Success = false, - ErrorMessage = $"Derleme sırasında hata: {ex.Message}", + ErrorMessage = _localizer["App.DynamicServicesEditor.CompileUnexpectedError", ex.Message], CompilationTimeMs = stopwatch.ElapsedMilliseconds, Errors = new List() }; @@ -211,7 +215,7 @@ public class DynamicServiceCompiler return new CompileResultDto { Success = false, - ErrorMessage = $"Failed to load assembly: {ex.Message}", + ErrorMessage = _localizer["App.DynamicServicesEditor.AssemblyLoadFailed", ex.Message], Errors = new List() }; } @@ -232,7 +236,7 @@ public class DynamicServiceCompiler errors.Add(new CompilationErrorDto { Code = "SECURITY001", - Message = $"Güvenlik nedeniyle '{forbiddenNs}' namespace'i kullanılamaz", + Message = _localizer["App.DynamicServicesEditor.ForbiddenNamespace", forbiddenNs], Severity = "Error", Line = GetLineNumber(code, forbiddenNs), Column = 1 @@ -248,7 +252,7 @@ public class DynamicServiceCompiler errors.Add(new CompilationErrorDto { Code = "SECURITY002", - Message = $"Güvenlik nedeniyle '{forbiddenType}' tipi kullanılamaz", + Message = _localizer["App.DynamicServicesEditor.ForbiddenType", forbiddenType], Severity = "Error", Line = GetLineNumber(code, forbiddenType), Column = 1 @@ -259,7 +263,7 @@ public class DynamicServiceCompiler return new CompileResultDto { Success = errors.Count == 0, - ErrorMessage = errors.Count > 0 ? "Güvenlik kontrolü başarısız" : null, + ErrorMessage = errors.Count > 0 ? _localizer["App.DynamicServicesEditor.SecurityCheckFailed"].Value : null, Errors = errors }; } diff --git a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs index bfa7232e..7cc22baf 100644 --- a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs +++ b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.Data.Seeds; +using Sozsoft.Platform.Localization; using Sozsoft.Platform.Permissions; using Volo.Abp; using Volo.Abp.Application.Services; @@ -58,6 +59,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe _currentTenant = currentTenant; _blobContainer = blobContainer; _configuration = configuration; + LocalizationResource = typeof(PlatformResource); } private const string HostFolderName = "host"; @@ -117,7 +119,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { if (!await CanAccessSeedsAsync(tenantId)) { - throw new UserFriendlyException($"'{SeedsFolderName}' folder is not accessible."); + throw new UserFriendlyException(L["App.FileManager.SeedsFolderNotAccessible", SeedsFolderName]); } rootPath = ResolveSeedsRootPath(); @@ -146,7 +148,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe if (!string.Equals(fullPath, fullRoot, StringComparison.OrdinalIgnoreCase) && !fullPath.StartsWith(fullRootWithSeparator, StringComparison.OrdinalIgnoreCase)) { - throw new UserFriendlyException("Invalid path"); + throw new UserFriendlyException(L["App.Platform.InvalidFilePath"]); } return fullPath; @@ -214,7 +216,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe if (!string.IsNullOrEmpty(inputTenantId) && !string.Equals(inputTenantId, currentTenantId, StringComparison.OrdinalIgnoreCase)) { - throw new AbpAuthorizationException("Files of another tenant cannot be accessed."); + throw new AbpAuthorizationException(L["App.FileManager.OtherTenantFiles"]); } return currentTenantId; @@ -263,6 +265,17 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe }; } + private string GetRequiredCdnBasePath() + { + var cdnBasePath = _configuration["App:CdnPath"]; + if (string.IsNullOrEmpty(cdnBasePath)) + { + throw new UserFriendlyException(L["App.FileManager.CdnPathNotConfigured"]); + } + + return cdnBasePath; + } + private void ValidateNotProtectedFolder(string id, string operation) { var decodedPath = DecodeIdAsPath(id); @@ -271,7 +284,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var folderName = decodedPath.Split('/')[0]; Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}"); - throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected."); + throw new UserFriendlyException(L["App.FileManager.ProtectedFolder", folderName]); } } @@ -451,11 +464,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { ValidateFileName(input.Name); - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var tenantId = GetEffectiveTenantId(input.TenantId); @@ -471,13 +480,13 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Klasör zaten var mı kontrol et if (Directory.Exists(folderPath)) { - throw new UserFriendlyException("A folder with this name already exists"); + throw new UserFriendlyException(L["App.FileManager.FolderAlreadyExists"]); } // Dosya ile aynı isimde bir şey var mı kontrol et if (File.Exists(folderPath)) { - throw new UserFriendlyException("A file with this name already exists"); + throw new UserFriendlyException(L["App.FileManager.FileAlreadyExists"]); } // Klasörü oluştur @@ -490,8 +499,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe Id = EncodePathAsId(newFolderPath), Name = input.Name, Type = "folder", - CreatedAt = DateTime.Now, - ModifiedAt = DateTime.Now, + CreatedAt = Clock.Now, + ModifiedAt = Clock.Now, Path = newFolderPath, ParentId = decodedParentId ?? string.Empty, IsReadOnly = false, @@ -527,11 +536,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var fileId = EncodePathAsId(filePath); // Save file content to CDN path - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var fullCdnPath = await ResolveFullPathAsync(cdnBasePath, tenantId, decodedParentId); @@ -552,7 +557,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } else { - throw new UserFriendlyException("Files must be provided"); + throw new UserFriendlyException(L["App.Platform.FileRequired"]); } var fileInfo = new FileInfo(uniqueFileName); @@ -566,8 +571,8 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe Size = fileSize, Extension = normalizedExtension, MimeType = GetMimeType(normalizedExtension), - CreatedAt = DateTime.Now, - ModifiedAt = DateTime.Now, + CreatedAt = Clock.Now, + ModifiedAt = Clock.Now, Path = filePath, ParentId = decodedParentId ?? string.Empty, IsReadOnly = false, @@ -591,14 +596,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var metadata = await FindItemMetadataAsync(id, tenantId); if (metadata == null) { - throw new UserFriendlyException("Item not found"); + throw new UserFriendlyException(L["App.FileManager.ItemNotFound"]); } var parentItems = await GetFolderIndexAsync(metadata.ParentId == string.Empty ? null : metadata.ParentId, tenantId); if (parentItems.Any(x => x.Id != id && x.Name.Equals(input.Name, StringComparison.OrdinalIgnoreCase))) { - throw new UserFriendlyException("An item with this name already exists"); + throw new UserFriendlyException(L["App.FileManager.ItemAlreadyExists"]); } var oldBlobPath = GetTenantPrefix(tenantId) + metadata.Path; @@ -622,13 +627,13 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Update metadata metadata.Name = input.Name; metadata.Path = newPath; - metadata.ModifiedAt = DateTime.Now; + metadata.ModifiedAt = Clock.Now; // Update parent index var itemToUpdate = parentItems.First(x => x.Id == id); itemToUpdate.Name = input.Name; itemToUpdate.Path = newPath; - itemToUpdate.ModifiedAt = DateTime.Now; + itemToUpdate.ModifiedAt = Clock.Now; await SaveFolderIndexAsync(parentItems, tenantId, metadata.ParentId == string.Empty ? null : metadata.ParentId); @@ -643,14 +648,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var metadata = await FindItemMetadataAsync(id, tenantId); if (metadata == null) { - throw new UserFriendlyException("Item not found"); + throw new UserFriendlyException(L["App.FileManager.ItemNotFound"]); } var targetItems = await GetFolderIndexAsync(input.TargetFolderId, tenantId); if (targetItems.Any(x => x.Name.Equals(metadata.Name, StringComparison.OrdinalIgnoreCase))) { - throw new UserFriendlyException("An item with this name already exists in the target folder"); + throw new UserFriendlyException(L["App.FileManager.ItemAlreadyExistsInTarget"]); } // Remove from source @@ -678,7 +683,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Update metadata metadata.Path = newPath; metadata.ParentId = input.TargetFolderId ?? string.Empty; - metadata.ModifiedAt = DateTime.Now; + metadata.ModifiedAt = Clock.Now; // Add to target targetItems.Add(metadata); @@ -693,11 +698,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Check if this is a protected system folder ValidateNotProtectedFolder(id, "delete"); - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var effectiveTenantId = GetEffectiveTenantId(tenantId); var actualPath = DecodeIdAsPath(id); @@ -715,7 +716,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } else { - throw new UserFriendlyException("Item not found"); + throw new UserFriendlyException(L["App.FileManager.ItemNotFound"]); } } @@ -724,14 +725,10 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { if (input.ItemIds == null || !input.ItemIds.Any()) { - throw new UserFriendlyException("No items selected for deletion"); + throw new UserFriendlyException(L["App.FileManager.NoItemsSelectedForDelete"]); } - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var tenantId = GetEffectiveTenantId(input.TenantId); var errors = new List(); @@ -758,18 +755,18 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } else { - errors.Add($"Item not found: {itemId}"); + errors.Add(L["App.FileManager.ItemNotFoundWithName", itemId]); } } catch (Exception ex) { - errors.Add($"Failed to delete {itemId}: {ex.Message}"); + errors.Add(L["App.FileManager.DeleteItemFailed", itemId, ex.Message]); } } if (errors.Any()) { - throw new UserFriendlyException($"Some items could not be deleted: {string.Join(", ", errors)}"); + throw new UserFriendlyException(L["App.FileManager.SomeItemsNotDeleted", string.Join(", ", errors)]); } } @@ -778,14 +775,10 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { if (input.ItemIds == null || !input.ItemIds.Any()) { - throw new UserFriendlyException("No items selected for copy"); + throw new UserFriendlyException(L["App.FileManager.NoItemsSelectedForCopy"]); } - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var tenantId = GetEffectiveTenantId(input.TenantId); @@ -868,18 +861,18 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } else { - errors.Add($"Source item not found: {itemId}"); + errors.Add(L["App.FileManager.ItemNotFoundWithName", itemId]); } } catch (Exception ex) { - errors.Add($"Failed to copy {itemId}: {ex.Message}"); + errors.Add(L["App.FileManager.CopyItemFailed", itemId, ex.Message]); } } if (errors.Any()) { - throw new UserFriendlyException($"Some items could not be copied: {string.Join(", ", errors)}"); + throw new UserFriendlyException(L["App.FileManager.SomeItemsNotCopied", string.Join(", ", errors)]); } return copiedItems; @@ -890,14 +883,10 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { if (input.ItemIds == null || !input.ItemIds.Any()) { - throw new UserFriendlyException("No items selected for move"); + throw new UserFriendlyException(L["App.FileManager.NoItemsSelectedForMove"]); } - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var tenantId = GetEffectiveTenantId(input.TenantId); @@ -930,7 +919,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Check if moving to same location if (Path.GetFullPath(sourceFullPath) == Path.GetFullPath(targetFullPath)) { - errors.Add($"Cannot move item to the same location: {sourceItemName}"); + errors.Add(L["App.FileManager.CannotMoveToSameLocation", sourceItemName]); continue; } @@ -997,18 +986,18 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } else { - errors.Add($"Source item not found: {itemId}"); + errors.Add(L["App.FileManager.ItemNotFoundWithName", itemId]); } } catch (Exception ex) { - errors.Add($"Failed to move {itemId}: {ex.Message}"); + errors.Add(L["App.FileManager.MoveItemFailed", itemId, ex.Message]); } } if (errors.Any()) { - throw new UserFriendlyException($"Some items could not be moved: {string.Join(", ", errors)}"); + throw new UserFriendlyException(L["App.FileManager.SomeItemsNotMoved", string.Join(", ", errors)]); } return movedItems; @@ -1017,11 +1006,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe [HttpPost("api/app/file-management/{id}/download-file/{tenantId}")] public async Task DownloadFileAsync(string id, string? tenantId = null) { - var cdnBasePath = _configuration["App:CdnPath"]; - if (string.IsNullOrEmpty(cdnBasePath)) - { - throw new UserFriendlyException("CDN path is not configured"); - } + var cdnBasePath = GetRequiredCdnBasePath(); var effectiveTenantId = GetEffectiveTenantId(tenantId); var actualPath = DecodeIdAsPath(id); @@ -1029,7 +1014,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe if (!File.Exists(fullFilePath)) { - throw new UserFriendlyException("File not found"); + throw new UserFriendlyException(L["App.Platform.FileNotFound"]); } return File.OpenRead(fullFilePath); @@ -1114,18 +1099,18 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { if (string.IsNullOrWhiteSpace(fileName)) { - throw new UserFriendlyException("File name cannot be empty"); + throw new UserFriendlyException(L["App.FileManager.FileNameEmpty"]); } var invalidChars = Path.GetInvalidFileNameChars(); if (fileName.Any(c => invalidChars.Contains(c))) { - throw new UserFriendlyException("File name contains invalid characters"); + throw new UserFriendlyException(L["App.FileManager.FileNameInvalidChars"]); } if (fileName.Length > 255) { - throw new UserFriendlyException("File name is too long"); + throw new UserFriendlyException(L["App.FileManager.FileNameTooLong"]); } } diff --git a/api/src/Sozsoft.Platform.Application/Forum/ForumMappers.cs b/api/src/Sozsoft.Platform.Application/Forum/ForumMappers.cs index d5d8e1f3..67e8f8d0 100644 --- a/api/src/Sozsoft.Platform.Application/Forum/ForumMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Forum/ForumMappers.cs @@ -11,14 +11,6 @@ public partial class ForumCategoryToForumCategoryDtoMapper : TwoWayMapperBase repo; public GlobalSearchAppService(IRepository repo) @@ -29,9 +31,9 @@ public class GlobalSearchAppService : PlatformAppService public async Task> GetAsync(string q, string[] s, int p = 1) { - if (q.IsNullOrWhiteSpace() || q.Length < 3) + if (q.IsNullOrWhiteSpace() || q.Length < MinSearchTermLength) { - throw new UserFriendlyException("Arama terimi en az 3 karakter olmalıdır."); + throw new UserFriendlyException(L["App.Platform.SearchTermMinLength", MinSearchTermLength]); } var query = await repo.GetQueryableAsync(); diff --git a/api/src/Sozsoft.Platform.Application/GlobalSearch/GlobalSearchMappers.cs b/api/src/Sozsoft.Platform.Application/GlobalSearch/GlobalSearchMappers.cs index ab046c9d..f087b019 100644 --- a/api/src/Sozsoft.Platform.Application/GlobalSearch/GlobalSearchMappers.cs +++ b/api/src/Sozsoft.Platform.Application/GlobalSearch/GlobalSearchMappers.cs @@ -11,12 +11,4 @@ public partial class GlobalSearchToGlobalSearchResultDtoMapper public override partial GlobalSearchResultDto Map(GlobalSearch source); public override partial void Map(GlobalSearch source, GlobalSearchResultDto destination); - - public override void BeforeMap(GlobalSearch source) - { - } - - public override void AfterMap(GlobalSearch source, GlobalSearchResultDto destination) - { - } } diff --git a/api/src/Sozsoft.Platform.Application/Hr/HrMappers.cs b/api/src/Sozsoft.Platform.Application/Hr/HrMappers.cs index dc6deb8c..4d67d2fb 100644 --- a/api/src/Sozsoft.Platform.Application/Hr/HrMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Hr/HrMappers.cs @@ -11,14 +11,6 @@ public partial class DepartmentToDepartmentDtoMapper : MapperBase(user); } diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs index 9192c0c1..0a49ecf8 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs @@ -185,7 +185,8 @@ public class PlatformIdentityAppService : ApplicationService userRoleNames = userRoleNames, LockoutEnabled = user.LockoutEnabled, LockoutEnd = user.LockoutEnd, - LockUser = user.LockoutEnabled && user.LockoutEnd.HasValue && user.LockoutEnd.Value.DateTime > DateTime.Now, + // LockoutEnd UTC offset tasir; `.DateTime > DateTime.Now` offset'i dusurup UTC saati yerel saatle kiyasliyordu. + LockUser = user.LockoutEnabled && user.LockoutEnd.HasValue && user.LockoutEnd.Value > DateTimeOffset.UtcNow, LoginEndDate = user.GetLoginEndDate(), ConcurrencyStamp = user.ConcurrencyStamp, LastPasswordChangeTime = user.LastPasswordChangeTime, diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs index 2e3a0220..52c26498 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformProfileAppService.cs @@ -93,11 +93,6 @@ public class PlatformProfileAppService : ProfileAppService, IProfileAppService var user = await UserManager.GetByIdAsync(CurrentUser.GetId()); - if (user is null) - { - throw new UserFriendlyException("Kullanıcı bulunamadı"); - } - var fileName = $"{user.Id}.jpg"; if (input.Avatar is null || input.Avatar.ContentLength == 0) { diff --git a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs index 11e17e93..4d97698b 100644 --- a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs @@ -404,7 +404,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService private async Task> GetBirthdaysAsync() { - var today = DateTime.Now; + var today = Clock.Now; var users = await _identityUserRepository.GetListAsync(); diff --git a/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs b/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs index d5819516..684c5e8e 100644 --- a/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs @@ -17,14 +17,6 @@ public partial class AnnouncementToAnnouncementDtoMapper : MapperBase [MapperIgnoreTarget(nameof(SurveyDto.MyResponse))] public override partial void Map(Survey source, SurveyDto destination); - - public override void BeforeMap(Survey source) - { - } - - public override void AfterMap(Survey source, SurveyDto destination) - { - } } [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] @@ -51,14 +35,6 @@ public partial class SurveyQuestionToSurveyQuestionDtoMapper : MapperBase> CreateFilteredQueryAsync(ListFormFieldRequestDto input) @@ -240,37 +242,25 @@ public class ListFormFieldsAppService : CrudAppService< [Authorize(AppCodes.Listforms.ListformField + ".Create")] public async Task CopyField(CopyListFormFieldColumnDto input) { - ////Izin logic process - //bool canAccess = await authManager.CanAccess(input.ListFormCode, AuthorizationTypeEnum.Create); - //if (!canAccess) - // throw new Volo.Abp.UserFriendlyException(L[PlatformDomainErrorCodes.NoAuth]); - - #region validations - - if (input.RoleId == null && input.UserId == null) + // Rol ve kullanıcı birlikte verilirse kapsam tanımsızdır; eski davranış gibi kontrol yapılmaz. + var existsKey = (input.RoleId, input.UserId) switch { - var listFormFields = await Repository.GetListAsync(a => a.ListFormCode == input.ListFormCode && a.FieldName == input.TargetCultureName && a.CultureName == input.TargetCultureName && a.UserId == null && a.RoleId == null); - if (listFormFields != null && listFormFields.Count > 0) - throw new Volo.Abp.UserFriendlyException("This FieldName and Language exist for all users!"); - } - if (input.RoleId != null && input.UserId == null) + (null, null) => "App.ListForm.FieldExistsForAllUsers", + (not null, null) => "App.ListForm.FieldExistsForRole", + (null, not null) => "App.ListForm.FieldExistsForUser", + _ => null, + }; + if (existsKey != null && await Repository.AnyAsync(a => + a.ListFormCode == input.ListFormCode && + a.FieldName == input.TargetFieldName && + a.CultureName == input.TargetCultureName && + a.UserId == input.UserId && + a.RoleId == input.RoleId)) { - var listFormFields = await Repository.GetListAsync(a => a.ListFormCode == input.ListFormCode && a.FieldName == input.TargetFieldName && a.CultureName == input.TargetCultureName && a.UserId == null && a.RoleId == input.RoleId); - if (listFormFields != null && listFormFields.Count > 0) - throw new Volo.Abp.UserFriendlyException("This FieldName, Language and RoleId exist!"); + throw new Volo.Abp.UserFriendlyException(L[existsKey]); } - if (input.RoleId == null && input.UserId != null) - { - var listFormFields = await Repository.GetListAsync(a => a.ListFormCode == input.ListFormCode && a.FieldName == input.TargetFieldName && a.CultureName == input.TargetCultureName && a.UserId == input.UserId && a.RoleId == null); - if (listFormFields != null && listFormFields.Count > 0) - throw new Volo.Abp.UserFriendlyException("This FieldName, Language and UserId exist!"); - } - #endregion - //TODO manager icerisine alinabilir var sourceField = await Repository.GetAsync(a => a.ListFormCode == input.ListFormCode && a.FieldName == input.SourceFieldName && a.CultureName == input.SourceCultureName); - if (sourceField == null) - throw new Volo.Abp.UserFriendlyException("Source field not found!"); ListFormField field = new ListFormField diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs index 2f6a528d..ed7cb62d 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDataAppService.cs @@ -74,7 +74,7 @@ public class ListFormDataAppService : PlatformAppService, IListFormDataAppServic RequireGroupCount = false, }; var selectResult = await listFormSelectAppService.GetSelectAsync(selectRequest); - var record = ((selectResult?.Data as System.Collections.IEnumerable)?.Cast()?.FirstOrDefault()) ?? throw new Volo.Abp.UserFriendlyException("Kopyalanacak kayıt bulunamadı."); + var record = ((selectResult?.Data as System.Collections.IEnumerable)?.Cast()?.FirstOrDefault()) ?? throw new Volo.Abp.UserFriendlyException(L["App.ListForm.CopyRecordNotFound"]); if (record is not IDictionary dict) throw new Exception("DapperRow IDictionary'e çevrilemedi."); diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs index 9601577f..799ec3c5 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormDynamicApiAppService.cs @@ -300,12 +300,12 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic if (input.Data.Name.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Tenant name is required."); + throw new UserFriendlyException(L["App.Platform.TenantNameRequired"]); } if (input.Data.Modules.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Module is required."); + throw new UserFriendlyException(L["App.Platform.ModuleRequired"]); } var order = await orderRepository.GetAsync(input.Data.OrderId) @@ -313,7 +313,7 @@ public class ListFormDynamicApiAppService : PlatformAppService, IListFormDynamic if (order.TenantId.HasValue) { - throw new UserFriendlyException("This order already has a tenant."); + throw new UserFriendlyException(L["App.Platform.OrderAlreadyHasTenant"]); } var tenant = await CreateTenantAsync(new CreateUpdateTenantInput diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs index 96efd697..635d364a 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormImportAppService.cs @@ -56,7 +56,7 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService if (string.IsNullOrWhiteSpace(file.FileName)) { - throw new UserFriendlyException("Geçersiz dosya adı."); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); } // Uygunsuz karakterleri temizle @@ -141,7 +141,7 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService { var queryable = await _importSessionRepository.GetQueryableAsync(); var session = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(a => a.Id == request.SessionId)) - ?? throw new UserFriendlyException("Import session not found."); + ?? throw new UserFriendlyException(L["App.ListFormImportManager.SessionNotFound"]); // Izin logic process if (!await _authManager.CanAccess(request.ListFormCode, AuthorizationTypeEnum.Import)) @@ -244,7 +244,7 @@ public class ListFormImportAppService : PlatformAppService, IImportAppService session.Status = "execute_failed"; await _importSessionRepository.UpdateAsync(session, autoSave: true); - throw new UserFriendlyException($"Import failed: {ex.Message}"); + throw new UserFriendlyException(L["App.ListFormImportManager.ImportFailed", ex.Message]); } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormMappers.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormMappers.cs index 72b72ce3..a0e182a0 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormMappers.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormMappers.cs @@ -51,14 +51,6 @@ public partial class ListFormFieldToColumnFormatDtoMapper : MapperBase listFormRepository; private readonly IRepository listFormFieldRepository; private readonly IRepository listFormWorkflowRepository; @@ -158,7 +157,7 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe var editingJson = listForm.EditingOptionJson; var editing = string.IsNullOrWhiteSpace(editingJson) ? null - : JsonSerializer.Deserialize(editingJson, CaseInsensitiveJson); + : JsonSerializer.Deserialize(editingJson, PlatformJsonOptions.CaseInsensitive); if (editing?.AllowAllDeleting != true || !await authManager.CanAccess(input.ListFormCode, AuthorizationTypeEnum.Delete)) diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs index c44b5408..351987ed 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWizardAppService.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging; using Sozsoft.Languages.Caching; using Sozsoft.Languages.Entities; using Sozsoft.Languages.Languages; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; @@ -83,8 +84,6 @@ public class ListFormWizardAppService( private readonly ILogger logger = logger; private readonly string cultureNameDefault = PlatformConsts.DefaultLanguage; - private static readonly JsonSerializerOptions SeedJsonReadOptions = new() { PropertyNameCaseInsensitive = true }; - /// /// Wizard'i deploy eder: ekranin butun kayitlari seed dosyalarina yazilir, veritabanina hicbir /// sey yazilmaz ve hicbir sey silinmez. Kurulumu bir sonraki DB Migrate yapar — wizard dosyasi @@ -120,7 +119,7 @@ public class ListFormWizardAppService( var wizardName = input.WizardName?.Trim(); if (string.IsNullOrWhiteSpace(wizardName)) - throw new UserFriendlyException("Wizard adı zorunludur."); + throw new UserFriendlyException(L["App.ListForm.WizardError.NameRequired"]); var code = input.ResolveMenuCode(); var listFormCode = input.ResolveListFormCode(); @@ -135,7 +134,7 @@ public class ListFormWizardAppService( if (previous?.Wizard != null) input.SeededAt = previous.Wizard.SeededAt; - var rows = await WizardSeedRowStore.LoadAsync(ResolveScopeRoot(), await ReadSeedFilePathsAsync()); + var rows = await WizardSeedRowStore.LoadAsync(ResolveScopeRoot(), await ReadSeedFilePathsAsync(), L); await BindExistingMenuParentsAsync(input, rows, previous); // Ad ve kod catismalari, dosyalar yazilmaya baslamadan once reddedilir; aksi halde yarim @@ -316,7 +315,7 @@ public class ListFormWizardAppService( if (!await repoDataSource.AnyAsync(a => a.Code == input.DataSourceCode)) { if (string.IsNullOrWhiteSpace(input.DataSourceConnectionString)) - throw new UserFriendlyException($"'{input.DataSourceCode}' veri kaynağı bulunamadı. Yeni veri kaynağı için bağlantı cümlesi zorunludur."); + throw new UserFriendlyException(L["App.ListForm.WizardError.DataSourceNotFound", input.DataSourceCode]); inserted.DataSourceCodes.Add(input.DataSourceCode); } @@ -525,7 +524,7 @@ public class ListFormWizardAppService( } if (errors.Count > 0) - throw new UserFriendlyException($"ListForm/Fields bölümü uygulanamadı: {string.Join(" ", errors)}"); + throw new UserFriendlyException(L["App.ListForm.WizardError.OverlayFailed", string.Join(" ", errors)]); // Katmanin is akisi ham gelir (oneksiz "N1", bos onayci); wizard'in urettigi gibi normalize edilir. listForm.WorkflowDto = ListFormWizardDto.NormalizeWorkflow(listForm.WorkflowDto, listForm.ListFormCode); @@ -1028,7 +1027,7 @@ public class ListFormWizardAppService( /// aynı şekilde '/' ile başlamalıdır; aksi halde menü, router'a kayıtlı olmayan bir adrese bağlanır. /// List yolunda bileşen adı temizlenerek seed dosyasında artık veri bırakılmaz. /// - private static string? NormalizeCustomRoutePath(ListFormWizardDto input, bool isCustomComponent) + private string? NormalizeCustomRoutePath(ListFormWizardDto input, bool isCustomComponent) { if (!isCustomComponent) { @@ -1038,11 +1037,11 @@ public class ListFormWizardAppService( var componentName = input.CustomComponentName?.Trim(); if (string.IsNullOrWhiteSpace(componentName)) - throw new UserFriendlyException("Custom Component seçimi zorunludur."); + throw new UserFriendlyException(L["App.ListForm.WizardError.CustomComponentRequired"]); var routePath = input.MenuUrl?.Trim(); if (string.IsNullOrWhiteSpace(routePath)) - throw new UserFriendlyException($"'{componentName}' bileşeninin rota yolu (Route Path) boş olamaz."); + throw new UserFriendlyException(L["App.ListForm.WizardError.RoutePathRequired", componentName]); if (!routePath.StartsWith('/')) routePath = "/" + routePath; @@ -1314,7 +1313,7 @@ public class ListFormWizardAppService( var json = await File.ReadAllTextAsync(filePath); var seed = WizardSeedFileDto.FromSeedJson(json); - return seed ?? throw new UserFriendlyException("Dosya okunamadı."); + return seed ?? throw new UserFriendlyException(L["App.Platform.FileUnreadable"]); } [UnitOfWork] @@ -1443,7 +1442,7 @@ public class ListFormWizardAppService( .Select(f => (f.FileName, Seed: f.Seed!)) .ToList(); if (members.Count == 0) - throw new UserFriendlyException("Bu talebe bağlı wizard dosyası yok."); + throw new UserFriendlyException(L["App.ListForm.WizardError.NoWizardForRequest"]); var dependencies = (await GetFiles()).ToDictionary(f => f.FileName, f => f.Dependencies, StringComparer.OrdinalIgnoreCase); var remaining = members.ToList(); @@ -1600,7 +1599,7 @@ public class ListFormWizardAppService( /// private async Task> RemovePendingSeedRowsAsync(WizardSeedFileDto seed) { - var rows = await WizardSeedRowStore.LoadAsync(ResolveScopeRoot(), await ReadSeedFilePathsAsync()); + var rows = await WizardSeedRowStore.LoadAsync(ResolveScopeRoot(), await ReadSeedFilePathsAsync(), L); var owned = (seed.InsertedRecords ?? new WizardInsertedRecordsDto()).Union(seed.Pending?.Replaced); var usages = new Dictionary<(WizardRecordTypeEnum, string), List>(); @@ -1653,7 +1652,7 @@ public class ListFormWizardAppService( private string ResolveWizardSeedFilePath(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) - throw new UserFriendlyException("Geçersiz dosya adı."); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); // Path.GetFileName sadece son segmenti döndürür; farklıysa yol bileşeni var demektir. if (!string.Equals(Path.GetFileName(fileName), fileName, StringComparison.Ordinal) || @@ -1661,17 +1660,17 @@ public class ListFormWizardAppService( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || !fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { - throw new UserFriendlyException("Geçersiz dosya adı."); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); } var outputPath = Path.GetFullPath(ResolveWizardSeedOutputPath()); var filePath = Path.GetFullPath(Path.Combine(outputPath, fileName)); if (!filePath.StartsWith(outputPath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) - throw new UserFriendlyException("Geçersiz dosya adı."); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); if (!File.Exists(filePath)) - throw new UserFriendlyException($"Dosya bulunamadı: {fileName}"); + throw new UserFriendlyException(L["App.Platform.FileNotFoundWithName", fileName]); return filePath; } @@ -1932,7 +1931,7 @@ public class ListFormWizardAppService( { var filePath = ResolveWizardSeedFilePath(fileName); var seed = WizardSeedFileDto.FromSeedJson(await File.ReadAllTextAsync(filePath)) - ?? throw new UserFriendlyException("Dosya okunamadı."); + ?? throw new UserFriendlyException(L["App.Platform.FileUnreadable"]); var collector = new WizardExportCollector(SeedPathResolver.GetRootPath(_configuration), await ReadSeedFilePathsAsync()); var items = await collector.CollectAsync([filePath]); @@ -1946,7 +1945,7 @@ public class ListFormWizardAppService( .Where(f => f.Seed?.DevRequest?.Id == devRequestId) .ToList(); if (files.Count == 0) - throw new UserFriendlyException("Bu talebe bağlı wizard dosyası yok."); + throw new UserFriendlyException(L["App.ListForm.WizardError.NoWizardForRequest"]); var devRequest = await repoDevRequest.FindAsync(devRequestId); var title = devRequest?.Title ?? files[0].Seed!.DevRequest.Title; @@ -2021,7 +2020,7 @@ public class ListFormWizardAppService( public async Task AnalyzeImport([FromForm] IRemoteStreamContent file) { if (file == null) - throw new UserFriendlyException("Dosya bulunamadı."); + throw new UserFriendlyException(L["App.Platform.FileNotFound"]); CleanupStaleImportSessions(); @@ -2030,10 +2029,10 @@ public class ListFormWizardAppService( await upload.CopyToAsync(buffer); if (buffer.Length == 0) - throw new UserFriendlyException("Dosya boş."); + throw new UserFriendlyException(L["App.Platform.FileEmpty"]); if (buffer.Length > ImportMaxArchiveBytes) - throw new UserFriendlyException("Zip dosyası çok büyük."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ZipTooLarge"]); buffer.Position = 0; @@ -2044,7 +2043,7 @@ public class ListFormWizardAppService( } catch (InvalidDataException) { - throw new UserFriendlyException("Geçerli bir zip dosyası değil."); + throw new UserFriendlyException(L["App.ListForm.WizardError.NotAZip"]); } var importId = GuidGenerator.Create().ToString("N"); @@ -2096,7 +2095,7 @@ public class ListFormWizardAppService( } if (seen.Count > ImportMaxEntryCount) - throw new UserFriendlyException("Zip içindeki dosya sayısı çok fazla."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ZipTooManyEntries"]); var incoming = await ReadZipEntryAsync(zipEntry, ImportMaxEntryBytes); if (incoming == null) @@ -2107,7 +2106,7 @@ public class ListFormWizardAppService( totalUncompressed += incoming.LongLength; if (totalUncompressed > ImportMaxTotalUncompressedBytes) - throw new UserFriendlyException("Zip içeriği açıldığında izin verilen boyutu aşıyor."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ZipTooLargeExtracted"]); stagedEntries.Add(new WizardImportStagedEntry(relativePath, incoming)); @@ -2177,7 +2176,7 @@ public class ListFormWizardAppService( if (result.Entries.Count == 0) { DeleteImportSession(sessionPath); - throw new UserFriendlyException("Zip içinde import edilebilecek seed dosyası bulunamadı."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ZipNoSeedFiles"]); } result.Issues = await ValidateImportAsync(stagedEntries, scopeRoot); @@ -2230,7 +2229,8 @@ public class ListFormWizardAppService( ReadTableColumnsAsync, TableHasRowsAsync, listFormCode => repoListForm.AnyAsync(a => a.ListFormCode == listFormCode), - await ReadSeedFilePathsAsync()); + await ReadSeedFilePathsAsync(), + L); return await validator.ValidateAsync(stagedEntries); } @@ -2243,12 +2243,11 @@ public class ListFormWizardAppService( /// virgul iceren bir ad reddedilir — boyle bir adin sorgudan cikmasi mumkun olmamalidir. /// /// - private static string QuoteIdentifier(string identifier) + private string EnsureSafeIdentifier(string identifier) { - if (string.IsNullOrWhiteSpace(identifier) - || identifier.IndexOfAny(['"', '[', ']', ';', '\'', '\r', '\n']) >= 0) + if (!SqlIdentifier.IsSafe(identifier)) { - throw new UserFriendlyException($"Geçersiz tablo adı: {identifier}"); + throw new UserFriendlyException(L["App.ListForm.WizardError.InvalidTableName", identifier]); } return identifier; @@ -2265,11 +2264,11 @@ public class ListFormWizardAppService( { try { - var (repo, connectionString, _) = await _dynamicDataManager.GetAsync( + var (repo, connectionString, dataSourceType) = await _dynamicDataManager.GetAsync( CurrentTenant.Id != null, SeedConsts.DataSources.DefaultCode); var count = await repo.ExecuteScalarAsync( - $"SELECT COUNT(1) FROM \"{QuoteIdentifier(tableName)}\"", connectionString); + $"SELECT COUNT(1) FROM {SqlIdentifier.Quote(EnsureSafeIdentifier(tableName), dataSourceType)}", connectionString); return count > 0; } @@ -2397,17 +2396,17 @@ public class ListFormWizardAppService( EnsureImportNotBlocked(sessionPath); var relativePath = NormalizeImportEntryPath(input.Path) - ?? throw new UserFriendlyException("Geçersiz dosya yolu."); + ?? throw new UserFriendlyException(L["App.Platform.InvalidFilePath"]); var systemPath = relativePath.Replace('/', Path.DirectorySeparatorChar); var stagedPath = Path.Combine(sessionPath, ImportStagedFolder, systemPath); if (!File.Exists(stagedPath)) - throw new UserFriendlyException($"Import oturumunda dosya bulunamadı: {relativePath}"); + throw new UserFriendlyException(L["App.ListForm.WizardError.ImportFileNotFound", relativePath]); var scopeRoot = Path.GetFullPath(SeedPathResolver.GetRootPath(_configuration)); var targetPath = Path.GetFullPath(Path.Combine(scopeRoot, systemPath)); if (!targetPath.StartsWith(scopeRoot + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) - throw new UserFriendlyException("Geçersiz dosya yolu."); + throw new UserFriendlyException(L["App.Platform.InvalidFilePath"]); // Icerik gonderildiyse kullanici diff ekraninda birlestirmis demektir; gonderilmediyse // zip'ten gelen dosya oldugu gibi yazilir. @@ -2513,7 +2512,7 @@ public class ListFormWizardAppService( result.AppliedCrud.Add(new WizardImportStepResultDto { Path = applied.Path, - Detail = $"{applied.Inserted} eklendi, {applied.Updated} güncellendi, {applied.Deleted} silindi, {applied.Skipped} korundu.", + Detail = L["App.ListForm.WizardImportStep.DataApplied", applied.Inserted, applied.Updated, applied.Deleted, applied.Skipped], Error = applied.Error, }); } @@ -2568,14 +2567,14 @@ public class ListFormWizardAppService( var seed = fullPath == null ? null : WizardSeedFileDto.FromSeedJson(await File.ReadAllTextAsync(fullPath)); if (seed?.Wizard == null || string.IsNullOrWhiteSpace(seed.Wizard.WizardName)) { - step.Error = "Geçerli bir wizard dosyası değil."; + step.Error = L["App.ListForm.WizardImportStep.InvalidWizardFile"]; return step; } seed.Pending ??= new WizardPendingDto { Since = Clock.Now }; await File.WriteAllTextAsync(fullPath!, seed.ToSeedJson()); - step.Detail = $"'{seed.Wizard.WizardName}' DB Migrate ile kurulacak."; + step.Detail = L["App.ListForm.WizardImportStep.WizardPending", seed.Wizard.WizardName]; } catch (Exception ex) { @@ -2604,7 +2603,7 @@ public class ListFormWizardAppService( return results; var comparison = await SqlTableSchemaComparer.CompareAsync( - await File.ReadAllTextAsync(fullPath), ReadTableColumnsAsync, TableHasRowsAsync); + await File.ReadAllTextAsync(fullPath), ReadTableColumnsAsync, TableHasRowsAsync, L); if (comparison == null) return results; @@ -2620,7 +2619,7 @@ public class ListFormWizardAppService( var step = new WizardImportStepResultDto { Path = relativePath, - Detail = $"{comparison.TableName}.{column.ColumnName} eklendi.", + Detail = L["App.ListForm.WizardImportStep.ColumnAdded", $"{comparison.TableName}.{column.ColumnName}"], }; if (column.BlockingReason != null) @@ -2628,7 +2627,7 @@ public class ListFormWizardAppService( // Analiz bunu hata olarak isaretledigi icin normalde buraya gelinmez; oturum // arasinda sema degismis olabilir, sessizce zorlanmaz. step.Detail = null; - step.Error = $"{comparison.TableName}.{column.ColumnName} eklenmedi — {column.BlockingReason}."; + step.Error = L["App.ListForm.WizardImportStep.ColumnSkipped", $"{comparison.TableName}.{column.ColumnName}", column.BlockingReason]; results.Add(step); continue; } @@ -2641,7 +2640,7 @@ public class ListFormWizardAppService( catch (Exception ex) { step.Detail = null; - step.Error = $"{comparison.TableName}.{column.ColumnName} eklenemedi: {ex.Message}"; + step.Error = L["App.ListForm.WizardImportStep.ColumnFailed", $"{comparison.TableName}.{column.ColumnName}", ex.Message]; logger.LogError(ex, "Wizard import could not add column: {Statement}", column.Statement); } @@ -2655,10 +2654,10 @@ public class ListFormWizardAppService( /// Analizde engelleyici bulgu cikmis bir oturumda islemi reddeder. Karar sunucuda saklandigi /// icin istemci atlayamaz; veritabanina dokunan her uc bunu cagirir. /// - private static void EnsureImportNotBlocked(string sessionPath) + private void EnsureImportNotBlocked(string sessionPath) { if (File.Exists(Path.Combine(sessionPath, ImportBlockedFile))) - throw new UserFriendlyException("Import, giderilmemiş hatalar nedeniyle sürdürülemez."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ImportBlocked"]); } /// @@ -2673,7 +2672,7 @@ public class ListFormWizardAppService( try { var fullPath = ResolveScopeFilePath(scopeRoot, relativePath) - ?? throw new UserFriendlyException($"Script bulunamadı: {relativePath}"); + ?? throw new UserFriendlyException(L["App.ListForm.WizardError.ScriptNotFound", relativePath]); var (repo, connectionString, _) = await _dynamicDataManager.GetAsync( CurrentTenant.Id != null, SeedConsts.DataSources.DefaultCode); @@ -2693,7 +2692,7 @@ public class ListFormWizardAppService( executed++; } - step.Detail = $"{executed} batch çalıştırıldı."; + step.Detail = L["App.ListForm.WizardImportStep.BatchesExecuted", executed]; logger.LogInformation("Wizard import script executed: {RelativePath} ({Count} batch)", relativePath, executed); } catch (Exception ex) @@ -2856,8 +2855,8 @@ public class ListFormWizardAppService( { try { - var incomingFile = JsonSerializer.Deserialize(incoming, SeedJsonReadOptions); - var existingFile = JsonSerializer.Deserialize(existing, SeedJsonReadOptions); + var incomingFile = JsonSerializer.Deserialize(incoming, PlatformJsonOptions.CaseInsensitive); + var existingFile = JsonSerializer.Deserialize(existing, PlatformJsonOptions.CaseInsensitive); var keyFieldName = incomingFile?.KeyFieldName ?? existingFile?.KeyFieldName; if (incomingFile == null || existingFile == null || string.IsNullOrWhiteSpace(keyFieldName)) @@ -2922,9 +2921,9 @@ public class ListFormWizardAppService( { try { - var incomingFile = JsonSerializer.Deserialize(incoming, SeedJsonReadOptions); + var incomingFile = JsonSerializer.Deserialize(incoming, PlatformJsonOptions.CaseInsensitive); var existingFile = JsonSerializer.Deserialize( - await File.ReadAllBytesAsync(targetPath), SeedJsonReadOptions); + await File.ReadAllBytesAsync(targetPath), PlatformJsonOptions.CaseInsensitive); var keyFieldName = incomingFile?.KeyFieldName ?? existingFile?.KeyFieldName; if (incomingFile == null || existingFile == null || string.IsNullOrWhiteSpace(keyFieldName)) @@ -3064,11 +3063,11 @@ public class ListFormWizardAppService( { // Oturum kimligi klasor adina donustugu icin yalnizca uretilen format kabul edilir. if (string.IsNullOrWhiteSpace(importId) || !Guid.TryParseExact(importId, "N", out _)) - throw new UserFriendlyException("Geçersiz import oturumu."); + throw new UserFriendlyException(L["App.ListForm.WizardError.InvalidImportSession"]); var sessionPath = GetImportSessionPath(importId); if (!Directory.Exists(sessionPath)) - throw new UserFriendlyException("Import oturumu bulunamadı veya süresi dolmuş."); + throw new UserFriendlyException(L["App.ListForm.WizardError.ImportSessionExpired"]); return sessionPath; } @@ -3191,13 +3190,13 @@ public class ListFormWizardAppService( /// Anahtarsiz ekranda sayim, siralama ve satir secimi bozulur. Tip bos (0) gelirse secilen /// alanlardaki karsiligindan tamamlanir. /// - private static void ValidateQueryKeyField(ListFormWizardDto input) + private void ValidateQueryKeyField(ListFormWizardDto input) { if (input.ComponentKind == WizardComponentKindEnum.Custom || input.SelectCommandType != SelectCommandTypeEnum.Query) return; if (string.IsNullOrWhiteSpace(input.KeyFieldName)) - throw new UserFriendlyException("Query kaynaklı ekranda anahtar alan (KeyFieldName) zorunludur."); + throw new UserFriendlyException(L["App.ListForm.WizardError.QueryKeyFieldRequired"]); input.KeyFieldName = input.KeyFieldName.Trim(); if (input.KeyFieldDbSourceType != default) @@ -3207,7 +3206,7 @@ public class ListFormWizardAppService( .SelectMany(g => g.Items) .FirstOrDefault(i => string.Equals(i.FieldName, input.KeyFieldName, StringComparison.OrdinalIgnoreCase)); if (keyItem == null || keyItem.DbSourceType == default) - throw new UserFriendlyException($"Query kaynaklı ekranda '{input.KeyFieldName}' anahtar alanının tipi (KeyFieldDbSourceType) zorunludur."); + throw new UserFriendlyException(L["App.ListForm.WizardError.QueryKeyFieldTypeRequired", input.KeyFieldName]); input.KeyFieldDbSourceType = keyItem.DbSourceType; } @@ -3228,7 +3227,7 @@ public class ListFormWizardAppService( return false; if (!Regex.IsMatch(selectList, @"\bIsDeleted\b", RegexOptions.IgnoreCase)) - throw new UserFriendlyException($"Query kaynağının ana tablosu '{mainTable}' soft delete taşıyor; silinen satırların listelenmemesi için sorgu ana tablonun IsDeleted kolonunu seçmelidir."); + throw new UserFriendlyException(L["App.ListForm.WizardError.QuerySoftDeleteColumn", mainTable]); return true; } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs index cc007594..f185cf16 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ListFormWorkflowAppService.cs @@ -7,13 +7,11 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Localization; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms.Select; -using Sozsoft.Platform.Localization; using Sozsoft.Platform.Queries; using Sozsoft.Sender.Mail; using Volo.Abp; @@ -48,7 +46,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA private readonly IdentityUserManager identityUserManager; private readonly ISozsoftEmailSender erpEmailSender; private readonly ISettingProvider settingProvider; - private readonly IStringLocalizer localizer; public ListFormWorkflowAppService( IRepository criteriaRepository, @@ -60,8 +57,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA IDynamicDataManager dynamicDataManager, IdentityUserManager identityUserManager, ISozsoftEmailSender erpEmailSender, - ISettingProvider settingProvider, - IStringLocalizer localizer) + ISettingProvider settingProvider) { this.criteriaRepository = criteriaRepository; this.noteRepository = noteRepository; @@ -73,7 +69,6 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA this.identityUserManager = identityUserManager; this.erpEmailSender = erpEmailSender; this.settingProvider = settingProvider; - this.localizer = localizer; } [HttpGet("criteria")] @@ -97,7 +92,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA if (!isNew && criteria.ListFormCode != code) { - throw new UserFriendlyException("Workflow adımı seçili liste formuna ait değil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.StepNotInListForm"]); } criteria.ListFormCode = code; @@ -215,7 +210,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { var context = await CreateRunContextAsync(listFormCode, keys); var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start") - ?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı."); + ?? throw new UserFriendlyException(L["App.ListFormWorkflow.StartStepNotFound"]); context.WorkflowNoteRows.Add((NoteLabel("App.WorkflowNote.StartedBy", "Started By"), ResolveCurrentUserDisplayName())); var result = await RunUntilWaitAsync(context, start); @@ -243,11 +238,11 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var context = await CreateRunContextAsync(listFormCode, keys); if (!context.Workflow.ApprovalIsResetWorkflow) { - throw new UserFriendlyException("Bu liste formu için workflow sıfırlama tanımlı değil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.ResetNotDefined"]); } var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start") - ?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı."); + ?? throw new UserFriendlyException(L["App.ListFormWorkflow.StartStepNotFound"]); var previousStatus = GetRowValue(context.Row, context.Workflow.ApprovalStatusFieldName)?.ToString(); @@ -330,7 +325,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA if (current == null && currentNodeId.IsNullOrWhiteSpace()) { var start = context.Criteria.FirstOrDefault(x => x.Kind == "Start") - ?? throw new UserFriendlyException("Workflow başlangıç adımı bulunamadı."); + ?? throw new UserFriendlyException(L["App.ListFormWorkflow.StartStepNotFound"]); var started = await RunUntilWaitAsync(context, start); current = FindCurrentCriteria(context.Criteria, started.CurrentNodeId); @@ -343,15 +338,15 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA if (current?.Kind != "Approval") { - throw new UserFriendlyException("Seçili kayıt onay adımında beklemiyor."); + throw new UserFriendlyException(L["App.ListFormWorkflow.RecordNotWaitingApproval"]); } if (!input.CurrentNodeId.IsNullOrWhiteSpace() && input.CurrentNodeId != current.Id) { - throw new UserFriendlyException("Seçili kayıt bu onay adımında beklemiyor."); + throw new UserFriendlyException(L["App.ListFormWorkflow.RecordNotWaitingThisStep"]); } if (!IsCurrentUserWorkflowApprover(context, current)) { - throw new UserFriendlyException("Bu kayıt için onay yetkiniz yok."); + throw new UserFriendlyException(L["App.ListFormWorkflow.NoApprovalPermission"]); } var rejectReasonConfig = input.Approved ? null : WorkflowRejectReasonDto.Deserialize(current.RejectReasonJson); @@ -434,15 +429,15 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var workflow = DeserializeWorkflow(listForm.WorkflowJson); if (workflow.ApprovalStatusFieldName.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Workflow durum alanı tanımlı değil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.StatusFieldNotDefined"]); } if (listForm.KeyFieldName.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Liste formu anahtar alanı tanımlı değil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.KeyFieldNotDefined"]); } if (keys == null || keys.Length == 0) { - throw new UserFriendlyException("Workflow için kayıt anahtarı seçilmelidir."); + throw new UserFriendlyException(L["App.ListFormWorkflow.RecordKeyRequired"]); } var criteria = (await criteriaRepository.GetListAsync(x => x.ListFormCode == code)) @@ -467,14 +462,14 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA }); var row = ((result?.Data as System.Collections.IEnumerable)?.Cast()?.FirstOrDefault()) - ?? throw new UserFriendlyException("Workflow kaydı bulunamadı."); + ?? throw new UserFriendlyException(L["App.ListFormWorkflow.RecordNotFound"]); if (row is IDictionary dictionary) { return dictionary; } - throw new UserFriendlyException("Workflow kaydı okunamadı."); + throw new UserFriendlyException(L["App.ListFormWorkflow.RecordNotReadable"]); } private async Task RunUntilWaitAsync(WorkflowRunContext context, ListFormWorkflow? current) @@ -485,7 +480,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { if (!visited.Add(current.Id)) { - throw new UserFriendlyException("Workflow adımlarında döngü tespit edildi."); + throw new UserFriendlyException(L["App.ListFormWorkflow.CycleDetected"]); } await ApplyNodeAsync(context, current); @@ -570,13 +565,13 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA sender, new { }, body, - $"Workflow Bilgilendirme: {node.Title}", + L["App.ListFormWorkflow.InformMail.Subject", node.Title].Value, null, true); if (!result.Success) { - throw new UserFriendlyException($"Bilgilendirme maili gonderilemedi: {result.ErrorMessage}"); + throw new UserFriendlyException(L["App.ListFormWorkflow.InformMailFailed", result.ErrorMessage ?? string.Empty]); } } @@ -592,7 +587,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA if (emails.Count == 0) { - throw new UserFriendlyException("Bilgilendirme dugumu icin approver tanimli degil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.InformApproverMissing"]); } return emails; @@ -602,7 +597,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { if (approver.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Bilgilendirme dugumu icin approver tanimli degil."); + throw new UserFriendlyException(L["App.ListFormWorkflow.InformApproverMissing"]); } if (approver.Contains('@')) @@ -613,7 +608,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var user = await identityUserManager.FindByNameAsync(approver.Trim()); if (user == null || user.Email.IsNullOrWhiteSpace()) { - throw new UserFriendlyException($"Bilgilendirme kullanicisinin mail adresi bulunamadi: {approver}"); + throw new UserFriendlyException(L["App.ListFormWorkflow.InformUserEmailNotFound", approver]); } return user.Email; @@ -690,7 +685,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA return string.Join(string.Empty, noteItems); } - private static string BuildInformEmailBody( + private string BuildInformEmailBody( WorkflowRunContext context, ListFormWorkflow node, string previousWorkflowNotesHtml) @@ -703,21 +698,21 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var previousNotesSection = previousWorkflowNotesHtml.IsNullOrWhiteSpace() ? string.Empty : $""" -

Önceki Workflow Notları

+

{L["App.ListFormWorkflow.InformMail.PreviousNotes"]}

{previousWorkflowNotesHtml} """; return $"""
-

Workflow sürecinde bilgilendirme adımına ulaşıldı.

+

{L["App.ListFormWorkflow.InformMail.Intro"]}

- - - - + + + +
Liste Formu{listFormCode}
Kayıt{keyText}
Bilgilendirme Adımı{nodeTitle}
Alıcı{recipient}
{L["App.ListForm.WizardRecordType.ListForm"]}{listFormCode}
{L["App.SnippetGroup.Record"]}{keyText}
{L["App.ListFormWorkflow.InformMail.Step"]}{nodeTitle}
{L["App.Listform.ListformField.RecipientId"]}{recipient}
-

Bu İşlemdeki Süreç Özeti

+

{L["App.ListFormWorkflow.InformMail.ProcessSummary"]}

{processRows} {previousNotesSection} @@ -783,21 +778,21 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { "Start" => new[] { - localizer["App.ListFormWorkflow.WorkflowStarted"].Value, - localizer["App.ListFormWorkflow.Step", node.Title].Value, - localizer["App.ListFormWorkflow.PerformedBy", userName].Value + L["App.ListFormWorkflow.WorkflowStarted"].Value, + L["App.ListFormWorkflow.Step", node.Title].Value, + L["App.ListFormWorkflow.PerformedBy", userName].Value }.Concat(FormatNextToastNode(context, next)), "Inform" => new[] { - localizer["App.ListFormWorkflow.InformReached"].Value, - localizer["App.ListFormWorkflow.Step", node.Title].Value, - localizer["App.ListFormWorkflow.InformUser", FormatToastUser(DisplayNodeApprover(context, node))].Value + L["App.ListFormWorkflow.InformReached"].Value, + L["App.ListFormWorkflow.Step", node.Title].Value, + L["App.ListFormWorkflow.InformUser", FormatToastUser(DisplayNodeApprover(context, node))].Value }.Concat(FormatNextToastNode(context, next)), "End" => new[] { - localizer["App.ListFormWorkflow.WorkflowCompleted"].Value, - localizer["App.ListFormWorkflow.Step", node.Title].Value, - localizer["App.ListFormWorkflow.PerformedBy", userName].Value + L["App.ListFormWorkflow.WorkflowCompleted"].Value, + L["App.ListFormWorkflow.Step", node.Title].Value, + L["App.ListFormWorkflow.PerformedBy", userName].Value }, _ => null }; @@ -839,29 +834,29 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { "Approval" => [ - localizer["App.WorkflowNextStep.Approval"].Value, - localizer["App.ListFormWorkflow.NextStepName", node.Title].Value, - localizer["App.ListFormWorkflow.ApproverUser", FormatToastUser(DisplayNodeApprover(context, node))].Value + L["App.WorkflowNextStep.Approval"].Value, + L["App.ListFormWorkflow.NextStepName", node.Title].Value, + L["App.ListFormWorkflow.ApproverUser", FormatToastUser(DisplayNodeApprover(context, node))].Value ], "Inform" => [ - localizer["App.WorkflowNextStep.Inform"].Value, - localizer["App.ListFormWorkflow.NextStepName", node.Title].Value, - localizer["App.ListFormWorkflow.InformUser", FormatToastUser(DisplayNodeApprover(context, node))].Value + L["App.WorkflowNextStep.Inform"].Value, + L["App.ListFormWorkflow.NextStepName", node.Title].Value, + L["App.ListFormWorkflow.InformUser", FormatToastUser(DisplayNodeApprover(context, node))].Value ], "End" => [ - localizer["App.WorkflowNextStep.End"].Value, - localizer["App.ListFormWorkflow.NextStepName", node.Title].Value + L["App.WorkflowNextStep.End"].Value, + L["App.ListFormWorkflow.NextStepName", node.Title].Value ], - _ => [localizer["App.ListFormWorkflow.NextStep", node.Title].Value] + _ => [L["App.ListFormWorkflow.NextStep", node.Title].Value] }; } private string FormatToastUser(string userName) { return userName.IsNullOrWhiteSpace() - ? localizer["App.ListFormWorkflow.UndefinedUser"].Value + ? L["App.ListFormWorkflow.UndefinedUser"].Value : FormatWorkflowApprovers(userName); } @@ -869,7 +864,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA /// Onay ve bilgilendirme için düğümün onaycısı. ile yazılmış /// onaycı kayıttan okunur; boş kalırsa akış durur — boş onaycı herkese onay yetkisi verirdi. /// - private static string ResolveNodeApprover(WorkflowRunContext context, ListFormWorkflow node) + private string ResolveNodeApprover(WorkflowRunContext context, ListFormWorkflow node) { var approver = node.Approver?.Trim() ?? string.Empty; if (!approver.StartsWith(RowApproverPrefix, StringComparison.Ordinal)) @@ -881,7 +876,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA var value = GetRowValue(context.Row, fieldName)?.ToString(); if (value.IsNullOrWhiteSpace()) { - throw new UserFriendlyException($"Workflow onaycısı kayıttaki '{fieldName}' alanından okunamadı; alan boş."); + throw new UserFriendlyException(L["App.ListFormWorkflow.RowApproverEmpty", fieldName]); } return value; @@ -962,7 +957,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA return fallback; } - var value = localizer[key]; + var value = L[key]; return value.ResourceNotFound ? fallback : value.Value; } @@ -983,7 +978,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA } var text = title.Trim(); - var direct = localizer[text]; + var direct = L[text]; if (!direct.ResourceNotFound) { return direct.Value; @@ -1001,7 +996,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA } var baseKey = text[..^digitCount]; - var baseValue = localizer[baseKey]; + var baseValue = L[baseKey]; return baseValue.ResourceNotFound ? text : $"{baseValue.Value} {text[^digitCount..]}"; @@ -1409,7 +1404,7 @@ public class ListFormWorkflowAppService : PlatformAppService, IListFormWorkflowA { if (!await authManager.CanAccess(listFormCode, AuthorizationTypeEnum.Update)) { - throw new UserFriendlyException("Workflow işlemi için güncelleme yetkiniz yok."); + throw new UserFriendlyException(L["App.ListFormWorkflow.NoUpdatePermission"]); } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs index 25c683a2..1c54bbb7 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/ModuleDocs/ModuleDocManager.cs @@ -5,8 +5,10 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Sozsoft.Platform.Data.Seeds; +using Sozsoft.Platform.Localization; using Volo.Abp; using Volo.Abp.DependencyInjection; @@ -20,7 +22,8 @@ namespace Sozsoft.Platform.ListForms; /// public class ModuleDocManager( IConfiguration configuration, - ILogger logger) : ITransientDependency + ILogger logger, + IStringLocalizer localizer) : ITransientDependency { private static readonly UTF8Encoding Utf8NoBom = new(encoderShouldEmitUTF8Identifier: false); @@ -155,7 +158,7 @@ public class ModuleDocManager( public async Task GetAsync(string input) { var catalog = await LoadCatalogAsync(); - var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException($"Modül dokümanı bulunamadı: {input}"); + var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException(localizer["App.ListForm.ModuleDocNotFound", input]); return await ToDtoAsync(catalog, name, section, catalog.GroupByDoc(), includeContent: true); } @@ -167,9 +170,9 @@ public class ModuleDocManager( public async Task SaveAsync(string input, string content) { var catalog = await LoadCatalogAsync(); - var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException($"Geçersiz modül dokümanı adı: {input}"); + var (name, section) = catalog.Find(input) ?? throw new UserFriendlyException(localizer["App.ListForm.InvalidModuleDocName", input]); if (string.IsNullOrWhiteSpace(content)) - throw new UserFriendlyException("Doküman içeriği boş olamaz."); + throw new UserFriendlyException(localizer["App.ListForm.ModuleDocContentEmpty"]); var path = catalog.DocPath(name); diff --git a/api/src/Sozsoft.Platform.Application/ListForms/SqlTableSchemaComparer.cs b/api/src/Sozsoft.Platform.Application/ListForms/SqlTableSchemaComparer.cs index 405d3b7a..76b3c9a3 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/SqlTableSchemaComparer.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/SqlTableSchemaComparer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Localization; namespace Sozsoft.Platform.ListForms; @@ -71,7 +72,8 @@ public static class SqlTableSchemaComparer public static async Task CompareAsync( string sql, Func?>> tableColumnsAsync, - Func> tableHasRowsAsync) + Func> tableHasRowsAsync, + IStringLocalizer localizer) { var declaration = SqlCreateTableParser.Parse(sql); if (declaration == null) @@ -105,7 +107,7 @@ public static class SqlTableSchemaComparer if (liveByName.TryGetValue(declared.Name, out var actual)) { - var difference = DescribeDifference(declared, actual); + var difference = DescribeDifference(declared, actual, localizer); if (difference != null) differences.Add(new SqlColumnDifference(declared.Name, difference)); @@ -115,7 +117,7 @@ public static class SqlTableSchemaComparer missing.Add(new SqlMissingColumn( declared.Name, $"ALTER TABLE {declaration.RawName} ADD {declared.Definition};", - await ResolveBlockingReasonAsync(declaration.TableName, declared, tableHasRowsAsync))); + await ResolveBlockingReasonAsync(declaration.TableName, declared, tableHasRowsAsync, localizer))); } // Hedefteki fazla kolon ekrani bozmaz ve asla dusurulmez; yalnizca bilgi verilir. @@ -138,7 +140,8 @@ public static class SqlTableSchemaComparer private static async Task ResolveBlockingReasonAsync( string tableName, SqlColumnDeclaration declared, - Func> tableHasRowsAsync) + Func> tableHasRowsAsync, + IStringLocalizer localizer) { if (declared.IsNullable != false) return null; @@ -161,8 +164,8 @@ public static class SqlTableSchemaComparer return null; return hasRows == null - ? "kolon NOT NULL ve varsayılan değeri yok; tabloda kayıt olup olmadığı okunamadı" - : "kolon NOT NULL ve varsayılan değeri yok, tabloda kayıt var"; + ? localizer["App.ListForm.WizardImport.BlockNotNullRowsUnknown"] + : localizer["App.ListForm.WizardImport.BlockNotNullHasRows"]; } /// @@ -170,31 +173,31 @@ public static class SqlTableSchemaComparer /// saglayicida da ayni anlami tasiyan ve katalogdan kesin okunabilen olculer bakilir; tip /// adi karsilastirilmaz (bkz. ). /// - private static string? DescribeDifference(SqlColumnDeclaration declared, SqlLiveColumn actual) + private static string? DescribeDifference(SqlColumnDeclaration declared, SqlLiveColumn actual, IStringLocalizer localizer) { var differences = new List(); if (declared.Length.HasValue && actual.Length.HasValue && declared.Length != actual.Length) { - differences.Add( - $"uzunluk hedefte {Describe(actual.Length.Value)}, script'te {Describe(declared.Length.Value)}"); + differences.Add(localizer["App.ListForm.WizardImport.DiffLength", Describe(actual.Length.Value), Describe(declared.Length.Value)]); } if (declared.Precision.HasValue && actual.Precision.HasValue && declared.Precision != actual.Precision) - differences.Add($"kesinlik hedefte {actual.Precision}, script'te {declared.Precision}"); + differences.Add(localizer["App.ListForm.WizardImport.DiffPrecision", actual.Precision, declared.Precision]); if (declared.Scale.HasValue && actual.Scale.HasValue && declared.Scale != actual.Scale) - differences.Add($"ondalık hedefte {actual.Scale}, script'te {declared.Scale}"); + differences.Add(localizer["App.ListForm.WizardImport.DiffScale", actual.Scale, declared.Scale]); if (declared.IsNullable.HasValue && declared.IsNullable != actual.IsNullable) { - differences.Add( - $"boş değer hedefte {(actual.IsNullable ? "serbest" : "yasak")}, " + - $"script'te {(declared.IsNullable.Value ? "serbest" : "yasak")}"); + differences.Add(localizer["App.ListForm.WizardImport.DiffNullable", DescribeNullable(actual.IsNullable, localizer), DescribeNullable(declared.IsNullable.Value, localizer)]); } return differences.Count == 0 ? null : string.Join("; ", differences); } private static string Describe(int length) => length < 0 ? "MAX" : length.ToString(); + + private static string DescribeNullable(bool isNullable, IStringLocalizer localizer) + => localizer[isNullable ? "App.ListForm.WizardImport.NullAllowed" : "App.ListForm.WizardImport.NullNotAllowed"]; } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs index 252eaae7..c4920ddd 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardExportCollector.cs @@ -44,8 +44,6 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary< /// Custom component satirlarinin anahtar alani; bagimliliklar bu adla eslesir. public const string CustomComponentNameField = "Name"; - private static readonly JsonSerializerOptions JsonReadOptions = new() { PropertyNameCaseInsensitive = true }; - /// /// Lookup sorgusundaki tablo/gorunum adlari. Alias ve alt sorgular da eslesebilir; bulunan ad /// icin diskte dosya aranir, yoksa sessizce gecilir — bu yuzden fazla eslesme zararsizdir. @@ -653,7 +651,7 @@ public sealed class WizardExportCollector(string scopeRoot, IReadOnlyDictionary< try { - return JsonSerializer.Deserialize>(json, JsonReadOptions) ?? []; + return JsonSerializer.Deserialize>(json, PlatformJsonOptions.CaseInsensitive) ?? []; } catch (JsonException) { diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardImportValidator.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardImportValidator.cs index 93d048f9..61e8ba1d 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardImportValidator.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardImportValidator.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Extensions.Localization; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Enums; @@ -36,10 +37,9 @@ public sealed class WizardImportValidator( Func?>> tableColumnsAsync, Func> tableHasRowsAsync, Func> listFormExistsAsync, - IReadOnlyDictionary seedFilePaths) + IReadOnlyDictionary seedFilePaths, + IStringLocalizer localizer) { - private static readonly JsonSerializerOptions JsonReadOptions = new() { PropertyNameCaseInsensitive = true }; - private readonly string scopeRoot = System.IO.Path.GetFullPath(scopeRoot); private readonly List issues = []; @@ -122,7 +122,7 @@ public sealed class WizardImportValidator( } if (wizards.Count == 0 && !paths.Any(x => x.StartsWith(SeedPathResolver.WizardFolder + "/", StringComparison.OrdinalIgnoreCase))) - Add(WizardImportIssueSeverityEnum.Warning, null, "Zip içinde wizard dosyası yok; yalnızca bağımlılık dosyaları aktarılacak."); + Add(WizardImportIssueSeverityEnum.Warning, null, localizer["App.ListForm.WizardImport.NoWizardFile"]); ValidateProvider(paths); @@ -159,7 +159,7 @@ public sealed class WizardImportValidator( if (hasOther && !hasCurrent) { Add(WizardImportIssueSeverityEnum.Warning, null, - $"Zip yalnızca '{otherFolder}' sağlayıcısının scriptlerini içeriyor; bu ortam '{providerFolder}' kullanıyor ve scriptler çalıştırılmayacak."); + localizer["App.ListForm.WizardImport.ProviderMismatch", otherFolder, providerFolder]); } } @@ -171,10 +171,10 @@ public sealed class WizardImportValidator( if (wizard.ComponentKind == WizardComponentKindEnum.Custom) { if (string.IsNullOrWhiteSpace(wizard.CustomComponentName)) - Add(WizardImportIssueSeverityEnum.Error, path, $"{screen}: custom component adı boş."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.CustomComponentNameEmpty", screen]); if (string.IsNullOrWhiteSpace(wizard.MenuUrl)) - Add(WizardImportIssueSeverityEnum.Error, path, $"{screen}: custom component rota yolu boş; menü hiçbir yere gitmez."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.CustomComponentRouteEmpty", screen]); ValidateCustomComponent(path, wizard.CustomComponentName); return; @@ -182,7 +182,7 @@ public sealed class WizardImportValidator( if (seed.ListForm == null) { - Add(WizardImportIssueSeverityEnum.Error, path, $"{screen}: ListForm bölümü eksik, ekran kurulamaz."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.ListFormSectionMissing", screen]); return; } @@ -195,13 +195,13 @@ public sealed class WizardImportValidator( var objectName = WizardExportCollector.NormalizeDatabaseObjectName(seed.ListForm.SelectCommand); if (objectName == null) { - Add(WizardImportIssueSeverityEnum.Error, path, $"{screen}: veri kaynağı nesnesi (SelectCommand) boş."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.SelectCommandEmpty", screen]); } else { // View icin tablo tasarimcisi dogru arac degil; yalnizca tabloda onarim yolu sunulur. await RequireDatabaseObjectAsync( - path, dataSourceCode, objectName, $"{screen} ekranının tablosu", + path, dataSourceCode, objectName, localizer["App.ListForm.WizardImport.ScreenTable", screen], commandType == SelectCommandTypeEnum.Table ? objectName : null); } } @@ -212,7 +212,7 @@ public sealed class WizardImportValidator( && (seedTarget == null || (!paths.Any(seedTarget.Contains) && !seedTarget.Exists))) { Add(WizardImportIssueSeverityEnum.Warning, path, - $"{screen}: ekranın veri dosyası ({seedFilePath}) ne zip'te ne hedefte var; ekran boş gelecek."); + localizer["App.ListForm.WizardImport.SeedFileMissing", screen, seedFilePath]); } foreach (var subForm in ReadSubForms(seed, path)) @@ -224,7 +224,7 @@ public sealed class WizardImportValidator( if (!await listFormExistsAsync(code)) { Add(WizardImportIssueSeverityEnum.Error, path, - $"{screen}: '{code}' alt formu ne zip'te ne hedefte var; sekme boş açılır."); + localizer["App.ListForm.WizardImport.SubFormMissing", screen, code]); } } } @@ -238,12 +238,12 @@ public sealed class WizardImportValidator( if (file.Endpoints == null || file.Endpoints.Count == 0) { - Add(WizardImportIssueSeverityEnum.Warning, path, $"{entityName}: CRUD dosyasında endpoint yok."); + Add(WizardImportIssueSeverityEnum.Warning, path, localizer["App.ListForm.WizardImport.CrudNoEndpoints", entityName]); return; } await RequireDatabaseObjectAsync( - path, null, entityName, $"{entityName} CRUD endpoint'lerinin tablosu", entityName); + path, null, entityName, localizer["App.ListForm.WizardImport.CrudTable", entityName], entityName); } /// Veri dosyasi, satirlarin yazilacagi ekran tanimi olmadan uygulanamaz. @@ -254,11 +254,11 @@ public sealed class WizardImportValidator( if (!incomingListFormCodes.Contains(listFormCode) && !await listFormExistsAsync(listFormCode)) { Add(WizardImportIssueSeverityEnum.Error, path, - $"'{listFormCode}' ekranı ne zip'te ne hedefte var; veri satırları uygulanamaz."); + localizer["App.ListForm.WizardImport.DataScreenMissing", listFormCode]); } if (file.Rows.Count == 0) - Add(WizardImportIssueSeverityEnum.Warning, path, $"{listFormCode}: veri dosyasında satır yok."); + Add(WizardImportIssueSeverityEnum.Warning, path, localizer["App.ListForm.WizardImport.DataNoRows", listFormCode]); if (IsCustomComponentsFile(path, file)) ValidateCustomComponentRows(path, file, paths); @@ -277,7 +277,7 @@ public sealed class WizardImportValidator( private async Task ValidateTableSchemaAsync(WizardImportStagedEntry entry) { var comparison = await SqlTableSchemaComparer.CompareAsync( - System.Text.Encoding.UTF8.GetString(entry.Content), tableColumnsAsync, tableHasRowsAsync); + System.Text.Encoding.UTF8.GetString(entry.Content), tableColumnsAsync, tableHasRowsAsync, localizer); if (comparison == null) return; @@ -290,10 +290,7 @@ public sealed class WizardImportValidator( Path = entry.Path, TableName = comparison.TableName, SchemaName = comparison.SchemaName, - Message = - $"{comparison.TableName}.{difference.ColumnName}: {difference.Description}. " + - "Platform var olan bir kolonu değiştirmez — daraltma veri keser, tip dönüşümü başarısız " + - "olabilir. Kolonu tablo tasarımcısından düzenleyip import'u tekrar başlatın.", + Message = localizer["App.ListForm.WizardImport.ColumnDifference", $"{comparison.TableName}.{difference.ColumnName}", difference.Description], }); } @@ -307,9 +304,7 @@ public sealed class WizardImportValidator( Path = entry.Path, TableName = comparison.TableName, SchemaName = comparison.SchemaName, - Message = - $"{comparison.TableName}.{column.ColumnName}: kolon hedefte yok, import tamamlanırken eklenecek. " + - "Tablo zaten var olduğu için script'in CREATE bloğu çalışmaz; kolon aşağıdaki cümleyle eklenir.", + Message = localizer["App.ListForm.WizardImport.ColumnWillBeAdded", $"{comparison.TableName}.{column.ColumnName}"], Statement = column.Statement, }); @@ -322,11 +317,7 @@ public sealed class WizardImportValidator( Path = entry.Path, TableName = comparison.TableName, SchemaName = comparison.SchemaName, - Message = - $"{comparison.TableName}.{column.ColumnName}: kolon hedefte yok ve otomatik eklenemez — " + - $"{column.BlockingReason}. Kolona hangi değerin yazılacağı bir iş kararıdır; kolonu tablo " + - "tasarımcısından bir varsayılan değerle ya da NULL kabul edecek şekilde ekleyip import'u " + - "tekrar başlatın.", + Message = localizer["App.ListForm.WizardImport.ColumnBlocked", $"{comparison.TableName}.{column.ColumnName}", column.BlockingReason], Statement = column.Statement, }); } @@ -339,9 +330,7 @@ public sealed class WizardImportValidator( Path = entry.Path, TableName = comparison.TableName, SchemaName = comparison.SchemaName, - Message = - $"{comparison.TableName}: hedefteki şu kolonlar script'te yok, oldukları gibi bırakılacak — " + - $"{string.Join(", ", comparison.ExtraColumns)}.", + Message = localizer["App.ListForm.WizardImport.ExtraColumns", comparison.TableName, string.Join(", ", comparison.ExtraColumns)], }); } } @@ -359,7 +348,7 @@ public sealed class WizardImportValidator( var name = WizardExportCollector.GetRowText(row, WizardExportCollector.CustomComponentNameField); if (string.IsNullOrWhiteSpace(name)) { - Add(WizardImportIssueSeverityEnum.Error, path, "Adı olmayan bir custom component satırı var."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.ComponentRowNameless"]); continue; } @@ -371,7 +360,7 @@ public sealed class WizardImportValidator( continue; Add(WizardImportIssueSeverityEnum.Error, path, - $"{name}: '{trimmed}' bağımlılığı ne zip'te ne hedefte var."); + localizer["App.ListForm.WizardImport.ComponentDependencyMissing", name, trimmed]); } foreach (var dataSource in WizardExportCollector.ParseSeedRowList( @@ -382,7 +371,7 @@ public sealed class WizardImportValidator( continue; Add(WizardImportIssueSeverityEnum.Error, path, - $"{name}: veri kaynağının CRUD dosyası ({seedFile}) ne zip'te ne hedefte var."); + localizer["App.ListForm.WizardImport.ComponentCrudMissing", name, seedFile]); } } } @@ -398,7 +387,7 @@ public sealed class WizardImportValidator( return; Add(WizardImportIssueSeverityEnum.Error, wizardPath, - $"'{name}' bileşeninin tanımı ne zip'te ne hedefte var; menü boş bir rotaya bağlanır."); + localizer["App.ListForm.WizardImport.ComponentMissing", name]); } /// Veri dosyasinin custom component seed kaynagi olup olmadigi. @@ -460,7 +449,7 @@ public sealed class WizardImportValidator( if (exists == null) { Add(WizardImportIssueSeverityEnum.Warning, path, - $"{description} ('{objectName}') veritabanında doğrulanamadı; scripti zip'te de yok."); + localizer["App.ListForm.WizardImport.ObjectUnverified", description, objectName]); return; } @@ -473,10 +462,9 @@ public sealed class WizardImportValidator( // Tablo bu ekrandan olusturulabilir; view olusturulamaz, tek yol scriptiyle birlikte // yeniden export etmektir. RequiresReexport = repairableTableName == null, - Message = $"{description} ('{objectName}') veritabanında yok ve zip onu oluşturan .sql dosyasını içermiyor." - + (repairableTableName != null - ? " Tabloyu tablo tasarımcısından oluşturup import'a devam edebilirsiniz." - : string.Empty), + Message = repairableTableName != null + ? $"{localizer["App.ListForm.WizardImport.ObjectMissing", description, objectName]} {localizer["App.ListForm.WizardImport.ObjectMissingRepairHint"]}" + : localizer["App.ListForm.WizardImport.ObjectMissing", description, objectName], }); } @@ -494,7 +482,7 @@ public sealed class WizardImportValidator( { try { - var file = JsonSerializer.Deserialize(File.ReadAllText(fullPath), JsonReadOptions); + var file = JsonSerializer.Deserialize(File.ReadAllText(fullPath), PlatformJsonOptions.CaseInsensitive); foreach (var row in file?.Rows ?? []) { var name = WizardExportCollector.GetRowText(row, WizardExportCollector.CustomComponentNameField); @@ -519,7 +507,7 @@ public sealed class WizardImportValidator( } catch (JsonException) { - Add(WizardImportIssueSeverityEnum.Error, path, "Alt form tanımı (SubFormsJson) çözülemedi."); + Add(WizardImportIssueSeverityEnum.Error, path, localizer["App.ListForm.WizardImport.SubFormsUnreadable"]); return []; } } @@ -531,7 +519,7 @@ public sealed class WizardImportValidator( var seed = WizardSeedFileDto.FromSeedJson(System.Text.Encoding.UTF8.GetString(entry.Content)); if (seed?.Wizard == null || string.IsNullOrWhiteSpace(seed.Wizard.WizardName)) { - Add(WizardImportIssueSeverityEnum.Error, entry.Path, "Geçerli bir wizard dosyası değil (Wizard bölümü ya da adı yok)."); + Add(WizardImportIssueSeverityEnum.Error, entry.Path, localizer["App.ListForm.WizardImport.InvalidWizardFile"]); return null; } @@ -539,7 +527,7 @@ public sealed class WizardImportValidator( } catch (Exception) { - Add(WizardImportIssueSeverityEnum.Error, entry.Path, "Wizard dosyası okunamadı (bozuk JSON)."); + Add(WizardImportIssueSeverityEnum.Error, entry.Path, localizer["App.ListForm.WizardImport.WizardFileCorrupt"]); return null; } } @@ -548,15 +536,15 @@ public sealed class WizardImportValidator( { try { - var value = JsonSerializer.Deserialize(entry.Content, JsonReadOptions); + var value = JsonSerializer.Deserialize(entry.Content, PlatformJsonOptions.CaseInsensitive); if (value == null) - Add(WizardImportIssueSeverityEnum.Error, entry.Path, "Dosya boş."); + Add(WizardImportIssueSeverityEnum.Error, entry.Path, localizer["App.Platform.FileEmpty"]); return value; } catch (JsonException) { - Add(WizardImportIssueSeverityEnum.Error, entry.Path, "Dosya okunamadı (bozuk JSON)."); + Add(WizardImportIssueSeverityEnum.Error, entry.Path, localizer["App.ListForm.WizardImport.FileCorrupt"]); return null; } } diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs index af87ce32..ff36ad95 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardSeedRowStore.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Localization; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Enums; using Volo.Abp; @@ -53,14 +54,14 @@ public sealed class WizardSeedRowStore /// baslar ve ilk yazimda olusur. Hedefi tanimsiz bir ekran deploy'u durdurur: satirlar yazilacak /// yer bilinmeden dosyaya dusurulmez. /// - public static async Task LoadAsync(string scopeRoot, IReadOnlyDictionary seedFilePaths) + public static async Task LoadAsync(string scopeRoot, IReadOnlyDictionary seedFilePaths, IStringLocalizer localizer) { var store = new WizardSeedRowStore(); foreach (var spec in Specs) { var target = ListFormSeedTarget.Create(scopeRoot, seedFilePaths.GetValueOrDefault(spec.ListFormCode)) - ?? throw new UserFriendlyException($"'{spec.ListFormCode}' ekranının veri seed hedefi (SeedFilePath) tanımlı değil."); + ?? throw new UserFriendlyException(localizer["App.ListForm.WizardError.SeedTargetMissing", spec.ListFormCode]); var file = new ListFormSeedDataFileDto { diff --git a/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs b/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs index e2db9590..30012e3a 100644 --- a/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs +++ b/api/src/Sozsoft.Platform.Application/ListForms/WizardTableCleaner.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Enums; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; @@ -232,7 +233,7 @@ public class WizardTableCleaner( try { var (repo, connectionString, dbType) = await dynamicDataManager.GetAsync(false, table.DataSourceCode); - table.SchemaName ??= dbType == DataSourceTypeEnum.Postgresql ? "public" : "dbo"; + table.SchemaName ??= SqlIdentifier.DefaultSchema(dbType); var parameters = new Dictionary { @@ -252,7 +253,7 @@ public class WizardTableCleaner( // Tablo adi parametre olarak baglanamaz; yalnizca katalogda var oldugu dogrulanan ad // tirnaklanarak kullanilir. table.RowCount = await repo.ExecuteScalarAsync( - $"SELECT COUNT(*) FROM {QuoteIdentifier(table.SchemaName, table.TableName, dbType)}", + $"SELECT COUNT(*) FROM {SqlIdentifier.QuoteQualified(table.SchemaName, table.TableName, dbType)}", connectionString); } catch (Exception ex) @@ -346,7 +347,7 @@ public class WizardTableCleaner( throw new InvalidOperationException($"'{table.DataSourceCode}' veri kaynağı çözülemedi."); await connection.Repository.ExecuteAsync( - $"DROP TABLE {QuoteIdentifier(table.SchemaName, table.TableName, connection.DataSourceType)}", + $"DROP TABLE {SqlIdentifier.QuoteQualified(table.SchemaName, table.TableName, connection.DataSourceType)}", connection.ConnectionString); report.Add(new WizardDeleteLogItemDto { RecordType = WizardRecordTypeEnum.Table, Name = name, Status = WizardDeleteStatusEnum.Deleted }); @@ -419,11 +420,6 @@ public class WizardTableCleaner( return (schemaName, parts[^1].Trim().Trim('"', '[', ']')); } - private static string QuoteIdentifier(string schemaName, string tableName, DataSourceTypeEnum dbType) => - dbType == DataSourceTypeEnum.Postgresql - ? $"\"{schemaName.Replace("\"", "\"\"")}\".\"{tableName.Replace("\"", "\"\"")}\"" - : $"[{schemaName.Replace("]", "]]")}].[{tableName.Replace("]", "]]")}]"; - private static string QualifiedName(WizardDeleteTableDto table) => $"{table.SchemaName}.{table.TableName}"; private static WizardRecordRefDto Ref(WizardRecordTypeEnum type, string name) => new() { RecordType = type, Name = name }; diff --git a/api/src/Sozsoft.Platform.Application/Menu/MenuMappers.cs b/api/src/Sozsoft.Platform.Application/Menu/MenuMappers.cs index c4100456..ed71cfdc 100644 --- a/api/src/Sozsoft.Platform.Application/Menu/MenuMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Menu/MenuMappers.cs @@ -29,14 +29,6 @@ public partial class MenuToMenuDtoMapper : TwoWayMapperBase [MapperIgnoreTarget(nameof(MenuDto.MenuTextTr))] public override partial void Map(Menu source, MenuDto destination); - public override void BeforeMap(Menu source) - { - } - - public override void AfterMap(Menu source, MenuDto destination) - { - } - public override partial Menu ReverseMap(MenuDto destination); public override partial void ReverseMap(MenuDto destination, Menu source); diff --git a/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs b/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs index a5bd3b56..82bdb667 100644 --- a/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Localization; using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; @@ -52,6 +53,7 @@ public class MessengerAppService : ApplicationService _blobManager = blobManager; _configuration = configuration; _messengerHubContext = messengerHubContext; + LocalizationResource = typeof(PlatformResource); } public async Task> GetContactsAsync(string? filter = null) @@ -152,7 +154,7 @@ public class MessengerAppService : ApplicationService if (recipientIds.Count == 0) { - throw new UserFriendlyException("En az bir alici secilmelidir."); + throw new UserFriendlyException(L["App.Messenger.RecipientRequired"]); } var attachments = await NormalizeAttachmentsAsync(input.Attachments); @@ -160,19 +162,19 @@ public class MessengerAppService : ApplicationService if (trimmedText.IsNullOrWhiteSpace() && attachments.Count == 0) { - throw new UserFriendlyException("Mesaj veya dosya gonderilmelidir."); + throw new UserFriendlyException(L["App.Messenger.MessageOrFileRequired"]); } var validRecipients = await GetValidUsersAsync(recipientIds); if (validRecipients.Count != recipientIds.Count) { - throw new UserFriendlyException("Bu tenant icinde gecersiz alici var."); + throw new UserFriendlyException(L["App.Messenger.InvalidRecipient"]); } var onlineRecipientIds = await _presenceStore.GetOnlineUserIdsAsync(CurrentTenant.Id, recipientIds); if (onlineRecipientIds.Count != recipientIds.Count) { - throw new UserFriendlyException("Sadece online kullanicilara mesaj gonderebilirsiniz."); + throw new UserFriendlyException(L["App.MessengerWidget.OnlyOnlineUsersCanReceiveMessages"]); } var participantIds = recipientIds.Append(senderId).Distinct().ToList(); @@ -182,12 +184,12 @@ public class MessengerAppService : ApplicationService if (input.ConversationId.HasValue) { conversation = await FindConversationByIdAsync(input.ConversationId.Value) - ?? throw new UserFriendlyException("Gorusme bulunamadi."); + ?? throw new UserFriendlyException(L["App.Messenger.ConversationNotFound"]); EnsureConversationParticipant(conversation, senderId); if (conversation.ParticipantKey != participantKey) { - throw new UserFriendlyException("Alicilar bu gorusme ile eslesmiyor."); + throw new UserFriendlyException(L["App.Messenger.RecipientsMismatch"]); } } @@ -243,12 +245,12 @@ public class MessengerAppService : ApplicationService if (message.SenderId != currentUserId) { - throw new UserFriendlyException("Sadece kendi mesajlarinizi silebilirsiniz."); + throw new UserFriendlyException(L["App.Messenger.OnlyOwnMessages"]); } if (Clock.Now - message.SentAt > MessageDeleteWindow) { - throw new UserFriendlyException("Mesajlar ilk 10 dakika icinde silinebilir."); + throw new UserFriendlyException(L["App.Messenger.DeleteWindowExpired", MessageDeleteWindow.TotalMinutes]); } var recipientIds = DeserializeGuidList(message.RecipientIdsJson); @@ -291,13 +293,13 @@ public class MessengerAppService : ApplicationService if (input.File is null || input.File.ContentLength is null or <= 0) { - throw new UserFriendlyException("Dosya seçilmelidir."); + throw new UserFriendlyException(L["App.Platform.FileRequired"]); } var originalFileName = Path.GetFileName(input.File.FileName); if (originalFileName.IsNullOrWhiteSpace()) { - throw new UserFriendlyException("Dosya adı geçersiz."); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); } var savedFileName = $"{GuidGenerator.Create():N}_{originalFileName}"; @@ -319,7 +321,7 @@ public class MessengerAppService : ApplicationService { if (!CurrentUser.Id.HasValue) { - throw new UserFriendlyException("Kullanici oturumu bulunamadi."); + throw new UserFriendlyException(L["App.Messenger.SessionNotFound"]); } return CurrentUser.Id.Value; @@ -357,7 +359,7 @@ public class MessengerAppService : ApplicationService if (attachments.Count > MaxAttachmentCount) { - throw new UserFriendlyException($"En fazla {MaxAttachmentCount} dosya gonderilebilir."); + throw new UserFriendlyException(L["App.Messenger.MaxAttachments", MaxAttachmentCount]); } var normalized = new List(attachments.Count); @@ -369,7 +371,7 @@ public class MessengerAppService : ApplicationService savedFileName != Path.GetFileName(savedFileName) || !await _blobManager.ExistsAsync(BlobContainerNames.Messenger, savedFileName!)) { - throw new UserFriendlyException("Gecersiz dosya eki."); + throw new UserFriendlyException(L["App.Messenger.InvalidAttachment"]); } normalized.Add(new MessengerAttachmentDto @@ -397,13 +399,13 @@ public class MessengerAppService : ApplicationService if (normalizedIds.Count < 2) { - throw new UserFriendlyException("En az iki katilimci secilmelidir."); + throw new UserFriendlyException(L["App.Messenger.ParticipantsRequired"]); } var users = await GetValidUsersAsync(normalizedIds); if (users.Count != normalizedIds.Count) { - throw new UserFriendlyException("Bu tenant icinde gecersiz katilimci var."); + throw new UserFriendlyException(L["App.Messenger.InvalidParticipant"]); } return normalizedIds; @@ -440,12 +442,12 @@ public class MessengerAppService : ApplicationService return string.Join(",", participantIds.Select(id => id.ToString("N")).OrderBy(id => id)); } - private static void EnsureConversationParticipant(MessengerConversation conversation, Guid currentUserId) + private void EnsureConversationParticipant(MessengerConversation conversation, Guid currentUserId) { var participantIds = DeserializeGuidList(conversation.ParticipantIdsJson); if (!participantIds.Contains(currentUserId)) { - throw new UserFriendlyException("Bu gorusmeye erisim yetkiniz yok."); + throw new UserFriendlyException(L["App.Messenger.NoConversationAccess"]); } } diff --git a/api/src/Sozsoft.Platform.Application/Note/NoteAppService.cs b/api/src/Sozsoft.Platform.Application/Note/NoteAppService.cs index eedfd12d..309dd0b6 100644 --- a/api/src/Sozsoft.Platform.Application/Note/NoteAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Note/NoteAppService.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Localization; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; @@ -35,26 +36,17 @@ public class NoteAppService : CrudAppService< { _repositoryUser = repositoryUser; _blobContainer = blobContainer; - - // CreatePolicyName = $"{AppCodes.Listforms.Listform}.Create"; - // UpdatePolicyName = $"{AppCodes.Listforms.Listform}.Update"; - // DeletePolicyName = $"{AppCodes.Listforms.Listform}.Delete"; - - // bool canAccess = await authManager.CanAccess(listFormCode, AuthorizationTypeEnum.Update); - // if (!canAccess) - // { - // throw new UserFriendlyException(L[AppErrorCodes.NoAuth]); - // } + LocalizationResource = typeof(PlatformResource); } public async Task GetDownloadAsync(string savedFileName) { if (string.IsNullOrWhiteSpace(savedFileName)) - throw new UserFriendlyException("Dosya adı geçersiz"); + throw new UserFriendlyException(L["App.Platform.InvalidFileName"]); var stream = await _blobContainer.GetAsync(BlobContainerNames.Note, savedFileName); if (stream == null) - throw new UserFriendlyException("Dosya bulunamadı"); + throw new UserFriendlyException(L["App.Platform.FileNotFound"]); // Performans: TÜM notları çekmek yerine sadece bu dosyayı içereni bul var queryable = await Repository.GetQueryableAsync(); diff --git a/api/src/Sozsoft.Platform.Application/Note/NoteMappers.cs b/api/src/Sozsoft.Platform.Application/Note/NoteMappers.cs index ccdd3926..841949f9 100644 --- a/api/src/Sozsoft.Platform.Application/Note/NoteMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Note/NoteMappers.cs @@ -14,14 +14,6 @@ public partial class NoteToNoteDtoMapper : MapperBase [MapperIgnoreTarget(nameof(NoteDto.CreateUserName))] [MapperIgnoreTarget(nameof(NoteDto.Files))] public override partial void Map(Note source, NoteDto destination); - - public override void BeforeMap(Note source) - { - } - - public override void AfterMap(Note source, NoteDto destination) - { - } } [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] @@ -30,12 +22,4 @@ public partial class NoteDtoToNoteMapper : MapperBase public override partial Note Map(NoteDto source); public override partial void Map(NoteDto source, Note destination); - - public override void BeforeMap(NoteDto source) - { - } - - public override void AfterMap(NoteDto source, Note destination) - { - } } diff --git a/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs b/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs index 8bc695d4..95b7b74d 100644 --- a/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Public/PublicAppService.cs @@ -11,6 +11,7 @@ using Sozsoft.Languages.Entities; using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Demos; using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Localization; using Sozsoft.Sender.Mail; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; @@ -26,6 +27,9 @@ namespace Sozsoft.Platform.Public; /// public class PublicAppService : PlatformAppService { + // Siparis tablosunda para birimi kolonu yok; public sitenin fiyat listesi TRY ile tutulur. + private const string OrderCurrency = "TRY"; + private readonly ISettingProvider _settingProvider; private readonly ISozsoftEmailSender _emailSender; private readonly IRepository _demoRepository; @@ -181,7 +185,7 @@ public class PublicAppService : PlatformAppService - + @@ -513,7 +517,7 @@ public class PublicAppService : PlatformAppService } var body = BuildOrderEmailBody(order); - var subject = $"{PlatformConsts.AppName} : Yeni Sipariş #{order.Id}"; + var subject = $"{PlatformConsts.AppName} : {Translate("App.OrderSuccess.NewOrderSubject")} #{order.Id}"; foreach (var recipient in recipients) { @@ -594,10 +598,10 @@ public class PublicAppService : PlatformAppService @@ -616,7 +620,7 @@ public class PublicAppService : PlatformAppService
Demo{Html(Translate("App.Demos"))}
{FormatDate(Clock.Now)} - + - +
Invoice{Html(Translate("App.Listform.ListformField.InvoiceId"))}
{FormatDate(DateTime.Now)}{FormatDate(Clock.Now)}
- +
Customer Information{Html(Translate("App.PublicPayment.CustomerInfo"))}
@@ -638,24 +642,24 @@ public class PublicAppService : PlatformAppService - +
License Information{Html(Translate("App.OrderSuccess.LicenseInfo"))}
- + - + - + - +
License Start Time{Html(Translate("App.Listform.ListformField.LicenseStartTime"))} {FormatDate(order.LicenseStartTime)}
License End Time{Html(Translate("App.Listform.ListformField.LicenseEndTime"))} {FormatDate(order.LicenseEndTime)}
Billing Cycle{Html(Translate("App.Listform.ListformField.BillingCycle"))} {BillingCycleLabel(order.BillingCycle)}
Period{Html(Translate("App.Listform.ListformField.Period"))} {order.Period}
@@ -669,13 +673,13 @@ public class PublicAppService : PlatformAppService - - - - - - - + + + + + + + {itemRows} @@ -728,9 +732,9 @@ public class PublicAppService : PlatformAppService return text.ResourceNotFound ? key : text.Value; } - private static string BillingCycleLabel(string billingCycle) + private string BillingCycleLabel(string billingCycle) { - return billingCycle == "monthly" ? "Aylık" : "Yıllık"; + return Translate(billingCycle == "monthly" ? "App.StaticLookup.Monthly" : "App.StaticLookup.Yearly"); } private static string NormalizePhoneNumber(string value, string country) @@ -770,12 +774,12 @@ public class PublicAppService : PlatformAppService private static string FormatDate(DateTime? value) { - return value.HasValue ? value.Value.ToString("dd.MM.yyyy") : "-"; + return value.HasValue ? CultureFormat.Date(value.Value) : "-"; } private static string FormatPrice(decimal value) { - return string.Format(new System.Globalization.CultureInfo("tr-TR"), "{0:C2}", value); + return CultureFormat.Currency(value, OrderCurrency); } /// diff --git a/api/src/Sozsoft.Platform.Application/Public/PublicMappers.cs b/api/src/Sozsoft.Platform.Application/Public/PublicMappers.cs index c6ed6aa5..b5002375 100644 --- a/api/src/Sozsoft.Platform.Application/Public/PublicMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Public/PublicMappers.cs @@ -14,14 +14,6 @@ public partial class DemoToDemoDtoMapper : TwoWayMapperBase [MapperIgnoreTarget(nameof(DemoDto.TenantId))] public override partial void Map(Demo source, DemoDto destination); - public override void BeforeMap(Demo source) - { - } - - public override void AfterMap(Demo source, DemoDto destination) - { - } - public override partial Demo ReverseMap(DemoDto destination); public override partial void ReverseMap(DemoDto destination, Demo source); @@ -43,14 +35,6 @@ public partial class BlogCategoryToBlogCategoryDtoMapper : MapperBase [MapperIgnoreTarget(nameof(OrderItemDto.Product))] private partial OrderItemDto? MapItem(OrderItem? source); - - public override void BeforeMap(Order source) - { - } - - public override void AfterMap(Order source, OrderDto destination) - { - } } [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] @@ -204,12 +140,4 @@ public partial class CountryToCountryDtoMapper : MapperBase public override partial CountryDto Map(Country source); public override partial void Map(Country source, CountryDto destination); - - public override void BeforeMap(Country source) - { - } - - public override void AfterMap(Country source, CountryDto destination) - { - } } diff --git a/api/src/Sozsoft.Platform.Application/Routes/RouteMappers.cs b/api/src/Sozsoft.Platform.Application/Routes/RouteMappers.cs index 632007fd..5a822fe4 100644 --- a/api/src/Sozsoft.Platform.Application/Routes/RouteMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Routes/RouteMappers.cs @@ -11,12 +11,4 @@ public partial class RouteToRouteDtoMapper : MapperBase public override partial RouteDto Map(Route source); public override partial void Map(Route source, RouteDto destination); - - public override void BeforeMap(Route source) - { - } - - public override void AfterMap(Route source, RouteDto destination) - { - } } diff --git a/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomAppService.cs b/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomAppService.cs index 5550f59a..e19c238a 100644 --- a/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomAppService.cs @@ -150,7 +150,7 @@ public class VideoroomAppService : PlatformAppService, IVideoroomAppService throw new UnauthorizedAccessException("Only the teacher can start this class"); } - classSession.ActualStartTime = DateTime.Now; + classSession.ActualStartTime = Clock.Now; await _classSessionRepository.UpdateAsync(classSession); @@ -167,7 +167,7 @@ public class VideoroomAppService : PlatformAppService, IVideoroomAppService throw new UnauthorizedAccessException("Only the teacher can end this class"); } - classSession.ActualEndTime = DateTime.Now; + classSession.ActualEndTime = Clock.Now; await _classSessionRepository.UpdateAsync(classSession); @@ -178,7 +178,7 @@ public class VideoroomAppService : PlatformAppService, IVideoroomAppService foreach (var attendance in activeAttendances) { - attendance.LeaveTime = DateTime.Now; + attendance.LeaveTime = Clock.Now; attendance.CalculateDuration(); await _attendanceRepository.UpdateAsync(attendance); } @@ -231,7 +231,7 @@ public class VideoroomAppService : PlatformAppService, IVideoroomAppService id, CurrentUser.Id, CurrentUser.Name, - DateTime.Now + Clock.Now ); await _attendanceRepository.InsertAsync(attendance); diff --git a/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomMappers.cs b/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomMappers.cs index d5fd8db5..c7807fc0 100644 --- a/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Videoroom/VideoroomMappers.cs @@ -12,14 +12,6 @@ public partial class VideoroomToVideoroomDtoMapper : MapperBase _dbContextProvider; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConfiguration _configuration; @@ -146,9 +143,8 @@ public class FinalSeeder : IFinalSeeder, ITransientDependency /// private static bool TryResolveObjectName(string fileName, out string schemaName, out string procedureName) { - var defaultSchema = DefaultDatabaseProvider == DatabaseProvider.PostgreSql ? "public" : "dbo"; - - schemaName = defaultSchema; + schemaName = SqlIdentifier.DefaultSchema( + DefaultDatabaseProvider == DatabaseProvider.PostgreSql ? DataSourceTypeEnum.Postgresql : DataSourceTypeEnum.Mssql); procedureName = fileName; var parts = fileName.Split('.'); @@ -162,7 +158,7 @@ public class FinalSeeder : IFinalSeeder, ITransientDependency return false; } - return SafeIdentifierRegex.IsMatch(schemaName) && SafeIdentifierRegex.IsMatch(procedureName); + return SqlIdentifier.IsSafe(schemaName) && SqlIdentifier.IsSafe(procedureName); } private static string BuildExecuteScript(string schemaName, string procedureName) diff --git a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj index 4ee9dacf..07bb3358 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj +++ b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj @@ -43,7 +43,7 @@ - + diff --git a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpCatalogQueries.cs b/api/src/Sozsoft.Platform.Domain.Shared/Data/SqlCatalogQueries.cs similarity index 54% rename from api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpCatalogQueries.cs rename to api/src/Sozsoft.Platform.Domain.Shared/Data/SqlCatalogQueries.cs index 429008f6..55c180f3 100644 --- a/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/McpCatalogQueries.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Data/SqlCatalogQueries.cs @@ -1,12 +1,29 @@ -namespace Sozsoft.Mcp; +using Sozsoft.Platform.Enums; + +namespace Sozsoft.Platform.Data; /// -/// Kayitli nesneleri listelemek icin kullanilan katalog sorgulari. Parametre almazlar; -/// sabit metin oldugu icin string birlestirme riski yoktur. +/// Veritabanındaki kayıtlı nesneleri (görünüm, yordam, fonksiyon) listeleyen katalog sorguları. +/// Parametre almazlar; sabit metin oldukları için string birleştirme riski yoktur. SQL Query Manager +/// ve MCP aynı listeyi gösterdiği için sorgu tek yerde durur; tür ayrımı C# tarafında +/// ile yapılır. /// -internal static class McpCatalogQueries +public static class SqlCatalogQueries { - public const string SqlServer = """ + /// ObjectKind kolonunun aldığı değerler. + public static class ObjectKinds + { + public const string View = "VIEW"; + public const string Procedure = "PROCEDURE"; + public const string TableFunction = "TABLE_FUNCTION"; + public const string ScalarFunction = "SCALAR_FUNCTION"; + } + + /// SchemaName, ObjectName, ObjectKind kolonlarını şema + ad sırasıyla döner. + public static string NativeObjects(DataSourceTypeEnum provider) + => provider == DataSourceTypeEnum.Postgresql ? PostgresNativeObjects : SqlServerNativeObjects; + + private const string SqlServerNativeObjects = """ SELECT SCHEMA_NAME(o.schema_id) AS SchemaName, o.name AS ObjectName, @@ -23,7 +40,7 @@ internal static class McpCatalogQueries ORDER BY SchemaName, ObjectName """; - public const string Postgres = """ + private const string PostgresNativeObjects = """ SELECT n.nspname AS "SchemaName", c.relname AS "ObjectName", 'VIEW' AS "ObjectKind" diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Data/SqlIdentifier.cs b/api/src/Sozsoft.Platform.Domain.Shared/Data/SqlIdentifier.cs new file mode 100644 index 00000000..22b3c94c --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain.Shared/Data/SqlIdentifier.cs @@ -0,0 +1,72 @@ +using System; +using System.Text.RegularExpressions; +using Sozsoft.Platform.Enums; + +namespace Sozsoft.Platform.Data; + +/// +/// SQL tanımlayıcılarının (şema, tablo, kolon, rutin) iki sağlayıcı için tek doğrulama ve +/// tırnaklama kuralı. Tanımlayıcı parametre olarak bağlanamaz; SQL metnine giren her ad ya +/// beyaz listesinden geçer ya da ile kaçırılarak yazılır. +/// +public static partial class SqlIdentifier +{ + // Üst sınır SQL Server'ın 128 karakterlik sysname uzunluğudur (ilk karakter + 127). PostgreSQL + // 63 baytı aşan adı reddetmez, sessizce kısaltır; bu yüzden reddi veritabanı değil bu kalıp + // verir ve iki sağlayıcıda aynı girdi aynı kararı alır. + [GeneratedRegex(@"^[A-Za-z_][A-Za-z0-9_]{0,127}$", RegexOptions.CultureInvariant)] + private static partial Regex SafeIdentifierRegex(); + + /// Tek parçalı, tırnaksız yazılabilen ad: harf/alt çizgi ile başlar, harf/rakam/alt çizgi ile sürer. + public static bool IsSafe(string name) + => name is not null && SafeIdentifierRegex().IsMatch(name); + + public static string DefaultSchema(DataSourceTypeEnum provider) + => provider == DataSourceTypeEnum.Postgresql ? "public" : "dbo"; + + /// Kapanış tırnağını ikiler: PostgreSQL'de ", SQL Server'da ]. + public static string Escape(string name, DataSourceTypeEnum provider) + => provider == DataSourceTypeEnum.Postgresql + ? name.Replace("\"", "\"\"") + : name.Replace("]", "]]"); + + public static string Quote(string name, DataSourceTypeEnum provider) + => provider == DataSourceTypeEnum.Postgresql + ? QuoteAnsi(name) + : $"[{Escape(name, provider)}]"; + + /// + /// ANSI çift tırnak. İki sağlayıcıda da geçerlidir (SQL Server bağlantıları QUOTED_IDENTIFIER ON ile + /// açılır); PostgreSQL'e özgü metin üretilen ya da sağlayıcıdan bağımsız tek metin istenen yerde kullanılır. + /// + public static string QuoteAnsi(string name) + => $"\"{name.Replace("\"", "\"\"")}\""; + + public static string QuoteQualified(string schemaName, string name, DataSourceTypeEnum provider) + => $"{Quote(schemaName, provider)}.{Quote(name, provider)}"; + + /// + /// Name, schema.Name, [dbo].[Name] ya da "Name" biçimindeki adı parçalar: ayraçlar + /// soyulur, her parça 'ten geçer, şema yoksa sağlayıcının varsayılanı yazılır. + /// SQL Server'ın dbo şeması PostgreSQL'de public karşılığına çevrilir; aynı ayar kaydı + /// (zamanlanmış işin BeforeSp/AfterSp'si) iki sağlayıcıda da çalışır. + /// + public static bool TryParseQualified(string qualifiedName, DataSourceTypeEnum provider, out string schema, out string name) + { + schema = null; + name = null; + if (string.IsNullOrWhiteSpace(qualifiedName)) + return false; + + var parts = qualifiedName.Split('.'); + if (parts.Length > 2) + return false; + + name = parts[^1].Trim().Trim('[', ']', '"'); + schema = parts.Length == 2 ? parts[0].Trim().Trim('[', ']', '"') : DefaultSchema(provider); + if (provider == DataSourceTypeEnum.Postgresql && string.Equals(schema, "dbo", StringComparison.OrdinalIgnoreCase)) + schema = DefaultSchema(provider); + + return IsSafe(schema) && IsSafe(name); + } +} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptions.cs b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptions.cs index 465a0e86..076b0a41 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptions.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptions.cs @@ -42,26 +42,35 @@ public static class EditorOptions public static EditorOptionsBuilder Percent() => New().Format("#0.##'%'").UseMaskBehavior().ShowSpinButtons(); - /// Tarih alani (gun/ay/yil). + /// + /// Tarih alani. Gosterim DevExtreme'in adli bicimidir (shortDate): desen sabitlenmez, + /// aktif dilin bicimiyle cizilir. UI preset'i (presets.ts · date) ile birebir aynidir. + /// public static EditorOptionsBuilder Date() - => New().Format("dd/MM/yyyy").DisplayFormat("dd/MM/yyyy"); + => New() + .Text("type", "date") + .DisplayFormat("shortDate") + .DateSerializationFormat("yyyy-MM-dd"); - /// Tarih + saat alani. + /// Tarih + saat alani (shortDateShortTime); UI preset'i dateTime ile aynidir. public static EditorOptionsBuilder DateTime() - => New().Format("dd/MM/yyyy HH:mm").DisplayFormat("dd/MM/yyyy HH:mm"); + => New() + .Text("type", "datetime") + .DisplayFormat("shortDateShortTime") + .DateSerializationFormat("yyyy-MM-ddTHH:mm:ss"); - /// Listeden secilen saat alani. + /// Listeden secilen saat alani (shortTime); UI preset'i timePicker ile aynidir. public static EditorOptionsBuilder Time(int intervalMinutes = 5) => New() .Text("type", "time") .Text("pickerType", "list") - .DisplayFormat("HH:mm") + .DisplayFormat("shortTime") .DateSerializationFormat("yyyy-MM-ddTHH:mm:ss") .Number("interval", intervalMinutes) .Width("100%"); /// Telefon alani: global bicim, maske ve ornek deger. - public static EditorOptionsBuilder Phone(string invalidMessage = "Lütfen geçerli bir telefon numarası girin") + public static EditorOptionsBuilder Phone(string invalidMessage = "App.PresetPhoneMask.InvalidMessage") => New() .Format("phoneGlobal") .Mask("(000) 000-0000", invalidMessage) diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptionsBuilder.cs b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptionsBuilder.cs index ea65b75d..84b12f1b 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptionsBuilder.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Editors/EditorOptionsBuilder.cs @@ -78,7 +78,7 @@ public sealed class EditorOptionsBuilder /// Sunucuya gonderilecek tarih bicimi. public EditorOptionsBuilder DateSerializationFormat(string format) => Text("dateSerializationFormat", format); - /// Giris maskesi. invalidMessage bos birakilirsa DevExtreme varsayilani kullanilir. + /// Giris maskesi. invalidMessage dil anahtaridir (UI aktif dile cevirir); bos birakilirsa DevExtreme varsayilani kullanilir. public EditorOptionsBuilder Mask(string mask, string invalidMessage = null, bool useMaskedValue = false) { Text("mask", mask); diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs index ded99813..c55bc40c 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -84,7 +84,6 @@ public enum TableNameEnum EventCategory, EventType, Event, - EventPhoto, EventComment, IntranetWidget, MessengerConversation, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/UiColumnCopyFieldType.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/UiColumnCopyFieldType.cs index 5529cb61..0c468803 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/UiColumnCopyFieldType.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/UiColumnCopyFieldType.cs @@ -1,23 +1,5 @@ namespace Sozsoft.Platform.Enums; -/// ListFormField icin field kopyalama asamasinda hangi alanlarin kopyalanacagini belirtir -/// -//public enum UiColumnCopyFieldType -//{ -// All = 0, -// General = 1, -// Filter = 2, -// Header = 3, -// Group = 4, -// Customization = 5, -// GroupSummary = 6, -// TotalSummary = 7, -// Editing = 8, -// Lookup = 9, -// Validation = 10, -// Styling = 11, -//} - public class UiColumnCopyFieldType { public bool All { get; set; } @@ -37,4 +19,3 @@ public class UiColumnCopyFieldType public bool Formatting { get; set; } public bool PivotSetting { get; set; } } - diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/UomType.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/UomType.cs deleted file mode 100644 index 98039dd2..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/UomType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Sozsoft.Platform.Enums; - -public class UomType -{ - public const string Reference = "Reference"; - public const string SmallerThanReference = "SmallerThanReference"; - public const string BiggerThanReference = "BiggerThanReference"; -} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/LookupDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/ListForms/LookupDto.cs similarity index 100% rename from api/src/Sozsoft.Platform.Application.Contracts/ListForms/Dto/LookupDto.cs rename to api/src/Sozsoft.Platform.Domain.Shared/ListForms/LookupDto.cs diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Localization/CultureFormat.cs b/api/src/Sozsoft.Platform.Domain.Shared/Localization/CultureFormat.cs new file mode 100644 index 00000000..b667ecc0 --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain.Shared/Localization/CultureFormat.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Linq; + +namespace Sozsoft.Platform.Localization; + +/// +/// Kullaniciya giden tarih ve sayi metinlerinin tek bicimlendirme yolu. Desen koda yazilmaz; +/// istegin (ya da worker'in) degeri karar verir, boylece +/// e-posta, bildirim ve rapor metni UI'daki DevExtreme adli bicimleriyle (shortDate, +/// shortDateShortTime, shortTime) ayni gorunur. +/// +public static class CultureFormat +{ + // Ayni para birimi her satirda tekrar aranmasin diye ISO kod → simge eslemesi surec boyunca tutulur. + private static readonly ConcurrentDictionary CurrencySymbols = new(StringComparer.OrdinalIgnoreCase); + + /// Kisa tarih (d); DevExtreme shortDate karsiligi. + public static string Date(System.DateTime value) => value.ToString("d", CultureInfo.CurrentCulture); + + /// + /// Tutar aktif kulturun sayi duzeniyle (ayrac, simge konumu) yazilir; simge ise kulturun degil + /// verilen ISO kodunun simgesidir — belgenin para birimi kazanir, kultur yalnizca duzeni verir. + /// + public static string Currency(decimal value, string currencyCode, int decimals = 2) + { + var format = (NumberFormatInfo)CultureInfo.CurrentCulture.NumberFormat.Clone(); + format.CurrencySymbol = ResolveCurrencySymbol(currencyCode); + format.CurrencyDecimalDigits = decimals; + return value.ToString("C", format); + } + + /// + /// Serbest degeri verilen .NET bicim deseniyle (N2, d, 0.## …) aktif kulturde + /// yazar; desen bossa kulturun varsayilan gosterimi kullanilir. + /// + public static string Value(object value, string format) + { + if (value is null || value is DBNull) + { + return string.Empty; + } + + return string.IsNullOrWhiteSpace(format) + ? Convert.ToString(value, CultureInfo.CurrentCulture) ?? string.Empty + : string.Format(CultureInfo.CurrentCulture, "{0:" + format + "}", value); + } + + private static string ResolveCurrencySymbol(string currencyCode) + { + if (string.IsNullOrWhiteSpace(currencyCode)) + { + return CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol; + } + + return CurrencySymbols.GetOrAdd(currencyCode, code => + CultureInfo.GetCultures(CultureTypes.SpecificCultures) + .Select(TryGetRegion) + .FirstOrDefault(region => region is not null && string.Equals(region.ISOCurrencySymbol, code, StringComparison.OrdinalIgnoreCase)) + ?.CurrencySymbol ?? code); + } + + private static RegionInfo TryGetRegion(CultureInfo culture) + { + try + { + return new RegionInfo(culture.Name); + } + catch (ArgumentException) + { + // Bolgesi olmayan kultur (ornegin yalnizca dil) para birimi tasimaz. + return null; + } + } +} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformJsonOptions.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformJsonOptions.cs new file mode 100644 index 00000000..e23c1623 --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformJsonOptions.cs @@ -0,0 +1,17 @@ +using System.Text.Json; + +namespace Sozsoft.Platform; + +/// +/// Paylaşılan örnekleri. Seçenek nesnesi ilk kullanımda +/// dönüştürücü önbelleğini kurar; her çağrıda yenisini üretmek bu önbelleği her seferinde yeniden +/// kurdurur (CA1869), bu yüzden aynı yapılandırma tek örnekte durur. +/// +public static class PlatformJsonOptions +{ + /// + /// Seed dosyaları ve ekranın JSON kolonları (düzenleme, kırılım, ağaç seçenekleri) elle ya da + /// istemciden camelCase/PascalCase karışık yazılabildiği için alan adı büyük/küçük harfe bakmadan okunur. + /// + public static readonly JsonSerializerOptions CaseInsensitive = new() { PropertyNameCaseInsensitive = true }; +} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs index 41eb89b1..2eab8938 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs @@ -124,39 +124,5 @@ public static class PlatformModuleExtensionConfigurator entity.AddOrUpdateProperty("IsActive"); }); }); - - /* You can configure extra properties for the - * entities defined in the modules used by your application. - * - * This class can be used to define these extra properties - * with a high level, easy to use API. - * - * Example: Add a new property to the user entity of the identity module - - ObjectExtensionManager.Instance.Modules() - .ConfigureIdentity(identity => - { - identity.ConfigureUser(user => - { - user.AddOrUpdateProperty( //property type: string - "SocialSecurityNumber", //property name - property => - { - //validation rules - property.Attributes.Add(new RequiredAttribute()); - property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); - - property.Configuration[IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit] = true; - - //...other configurations for this property - } - ); - }); - }); - - * See the documentation for more: - * https://docs.abp.io/en/abp/latest/Module-Entity-Extensions - */ } } - diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Public/BankDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/Public/BankDto.cs deleted file mode 100644 index 3bd6d355..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Public/BankDto.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Sozsoft.Platform.Public; - -public class BankDto -{ - public string AccountHolder { get; set; } - public string Branch { get; set; } - public string AccountNumber { get; set; } - public string Iban { get; set; } - public string StyleClass { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Public/MapDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/Public/MapDto.cs deleted file mode 100644 index fd6784cb..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Public/MapDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Sozsoft.Platform.Public; - -public class MapDto -{ - public string Title { get; set; } - public string Src { get; set; } - public string Width { get; set; } - public string Height { get; set; } - public string Style { get; set; } - public string ContainerStyleClass { get; set; } - public string FrameStyleClass { get; set; } - public bool? AllowFullScreen { get; set; } - public string Loading { get; set; } - public string ReferrerPolicy { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Public/SectionDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/Public/SectionDto.cs deleted file mode 100644 index 5beb18cd..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Public/SectionDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sozsoft.Platform.Public; - -public class SectionDto -{ - public string Key { get; set; } - public string DescKey { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Public/StatDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/Public/StatDto.cs deleted file mode 100644 index 6c5bdb37..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Public/StatDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Sozsoft.Platform.Public; - -public class StatDto -{ - public string Icon { get; set; } - public string Value { get; set; } // number/string farkını normalize ettik - public string LabelKey { get; set; } - public bool? UseCounter { get; set; } - public string? CounterEnd { get; set; } - public string CounterSuffix { get; set; } - public int? CounterDuration { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Public/WorkHoursDto.cs b/api/src/Sozsoft.Platform.Domain.Shared/Public/WorkHoursDto.cs deleted file mode 100644 index 6490c567..00000000 --- a/api/src/Sozsoft.Platform.Domain.Shared/Public/WorkHoursDto.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sozsoft.Platform.Public; - -public class WorkHoursDto -{ - public string Weekday { get; set; } - public string Weekend { get; set; } - public string Whatsapp { get; set; } - public string StyleClass { get; set; } -} diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs index f495795a..6938e59a 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs @@ -121,7 +121,6 @@ public static class TableNameResolver { nameof(TableNameEnum.EventCategory), (TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.EventType), (TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.Event), (TablePrefix.TenantByName, MenuPrefix.Hr) }, - { nameof(TableNameEnum.EventPhoto), (TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.EventComment), (TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.MessengerConversation), (TablePrefix.TenantByName, MenuPrefix.Hr) }, { nameof(TableNameEnum.MessengerConversationMessage), (TablePrefix.TenantByName, MenuPrefix.Hr) }, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs index 04e7ea78..0dea26cd 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/WizardConsts.cs @@ -5,6 +5,8 @@ using System.Text.Json; using Sozsoft.Platform.Enums; using static Sozsoft.Platform.PlatformConsts; +namespace Sozsoft.Platform; + public static class WizardConsts { public static int EditFormMaxWidth => 1200; diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs index 26ec08ec..9d987671 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs @@ -64,7 +64,7 @@ public partial class NotificationWorker : BackgroundWorkerBase await SendChannelAsync(NotificationChannels.Telegram, async notification => (await ErpTelegramSender.SendAsync(notification.Identifier, notification.Message)).Success, cancellationToken); await SendMailAsync(cancellationToken); - await uow.SaveChangesAsync(cancellationToken); + await uow.CompleteAsync(cancellationToken); } /// diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs index 4b8f8f88..e0f7d764 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs @@ -77,6 +77,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou if (handle is null) { Logger.LogInformation(LogPrefix, $"Handle is null because of the locking for : {DistributedLockName}"); + await uow.CompleteAsync(cancellationToken); return; } @@ -96,7 +97,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou } Logger.LogInformation(LogPrefix, $"Lock is released for {DistributedLockName}"); - return; + await uow.CompleteAsync(cancellationToken); } /// Isin govdesi: oncesi/sonrasi saklı yordamlar ve tipine gore calisan worker. diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs deleted file mode 100644 index ec340b14..00000000 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorkerTemplateDefinitionProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Linq; -using System.Text.Json; -using Sozsoft.MailQueue.Domain.Shared; -using Sozsoft.Platform.Enums; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.TextTemplating; -using Volo.Abp.TextTemplating.Razor; - -namespace Sozsoft.Platform.BackgroundWorkers; - -public class PlatformBackgroundWorkerTemplateDefinitionProvider : TemplateDefinitionProvider, ITransientDependency -{ - private readonly IRepository repository; - - public PlatformBackgroundWorkerTemplateDefinitionProvider( - IRepository repository) - { - this.repository = repository; - } - - public override async void Define(ITemplateDefinitionContext context) - { - var workers = await repository.GetQueryableAsync(); - foreach (var worker in workers.Where(a => - a.IsActive && - a.WorkerType == WorkerTypeEnum.MailQueueWorker && - a.Options != null).ToList().Where(a => !a.Options.IsNullOrWhiteSpace())) - { - var Options = JsonSerializer.Deserialize(worker.Options); - - // Add template - if (!Options.MailTemplate.IsNullOrEmpty()) - { - DefineTemplate(worker.Name, Options.MailTemplate, context); - } - } - } - - public void DefineTemplate(string templateName, string template, ITemplateDefinitionContext context) - { - context.Add( - new TemplateDefinition(templateName) - .WithRazorEngine() - .WithProperty(PropertyTemplateContentContributor.PropertyContentPropertyName, template)); - //layout: "EmailLayout" - } -} - - diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateDynamicStore.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateDynamicStore.cs index e8f2d7dd..dd7bd891 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateDynamicStore.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateDynamicStore.cs @@ -1,37 +1,91 @@ -using System.Collections.Generic; -using System.Collections.Immutable; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Sozsoft.MailQueue.Domain.Shared; +using Sozsoft.Platform.Enums; using Volo.Abp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; using Volo.Abp.TextTemplating; +using Volo.Abp.TextTemplating.Razor; +using Volo.Abp.Uow; namespace Sozsoft.Platform.BackgroundWorkers; -public class PlatformTemplateDynamicStore : IDynamicTemplateDefinitionStore +/// +/// Mail kuyruğu işlerinin () şablonlarını +/// BackgroundWorker.Options.MailTemplate alanından okuyan dinamik şablon deposu. +/// +/// Statik TemplateDefinitionProvider ile tanımlanamaz: ABP Define'ı senkron çağırır, +/// veritabanı okuması async void ile yarışa girip şablonları rastgele eksik bırakıyordu. +/// Dinamik depo şablonu istendiği anda okur; işin seçeneği değiştiğinde yeniden başlatma gerekmez. +/// Şablon yöneticisi singleton olduğu için depo da singleton'dır ve her çağrıda kendi kapsamını açar. +/// +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(IDynamicTemplateDefinitionStore))] +public class PlatformTemplateDynamicStore : IDynamicTemplateDefinitionStore, ISingletonDependency { - public static IDictionary TemplateDefinitions { get; } + private readonly IServiceScopeFactory scopeFactory; - public Task> GetAllAsync() + public PlatformTemplateDynamicStore(IServiceScopeFactory scopeFactory) { - return Task.FromResult>(TemplateDefinitions.Values.ToImmutableList()); + this.scopeFactory = scopeFactory; } - public Task GetAsync(string name) + public async Task> GetAllAsync() + { + var workers = await LoadWorkersAsync(null); + return workers.Select(ToDefinition).Where(definition => definition != null).ToList()!; + } + + public async Task GetAsync(string name) + { + return await GetOrNullAsync(name) ?? throw new AbpException("Undefined template: " + name); + } + + public async Task GetOrNullAsync(string name) { Check.NotNull(name, nameof(name)); - var template = GetOrNullAsync(name); + var workers = await LoadWorkersAsync(name); + return workers.Select(ToDefinition).FirstOrDefault(definition => definition != null); + } - if (template == null) + private async Task> LoadWorkersAsync(string? name) + { + using var scope = scopeFactory.CreateScope(); + var repository = scope.ServiceProvider.GetRequiredService>(); + var unitOfWorkManager = scope.ServiceProvider.GetRequiredService(); + + using var unitOfWork = unitOfWorkManager.Begin(requiresNew: true, isTransactional: false); + var workers = await repository.GetListAsync(worker => + worker.IsActive && + worker.WorkerType == WorkerTypeEnum.MailQueueWorker && + worker.Options != null && + (name == null || worker.Name == name)); + await unitOfWork.CompleteAsync(); + + return workers; + } + + private static TemplateDefinition? ToDefinition(Entities.BackgroundWorker worker) + { + if (worker.Options.IsNullOrWhiteSpace()) { - throw new AbpException("Undefined template: " + name); + return null; } - return template!; - } + var options = JsonSerializer.Deserialize(worker.Options); + if (options?.MailTemplate.IsNullOrEmpty() != false) + { + return null; + } - public Task GetOrNullAsync(string name) - { - return Task.FromResult(TemplateDefinitions.GetOrDefault(name)); + return new TemplateDefinition(worker.Name) + .WithRazorEngine() + .WithProperty(PropertyTemplateContentContributor.PropertyContentPropertyName, options.MailTemplate); } } - diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateManager.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateManager.cs deleted file mode 100644 index a5995648..00000000 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformTemplateManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.Domain.Services; -using Volo.Abp.TextTemplating; -using Volo.Abp.TextTemplating.Razor; - -namespace Sozsoft.Platform.BackgroundWorkers; - -public class PlatformTemplateManager : DomainService -{ - private readonly ITemplateDefinitionManager templateDefinitionManager; - - public PlatformTemplateManager(ITemplateDefinitionManager templateDefinitionManager) - { - this.templateDefinitionManager = templateDefinitionManager; - } - - public async Task AddOrUpdateTemplateAsync(string name, string template) - { - var templateDefinition = await templateDefinitionManager.GetOrNullAsync(name); - if (templateDefinition == null) - { - templateDefinition = new TemplateDefinition(name) - .WithRazorEngine() - .WithProperty(PropertyTemplateContentContributor.PropertyContentPropertyName, template); - PlatformTemplateDynamicStore.TemplateDefinitions.Add(name, templateDefinition); - } - else - { - templateDefinition.WithProperty(PropertyTemplateContentContributor.PropertyContentPropertyName, template); - } - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/ProbationReminderWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/ProbationReminderWorker.cs index b3634467..5d7f12f8 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/ProbationReminderWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/ProbationReminderWorker.cs @@ -26,6 +26,7 @@ public class ProbationReminderWorker( IReadOnlyRepository userRepository, IReadOnlyRepository notificationTypeRepository, INotificationManager notificationManager, + INotificationCulture notificationCulture, IStringLocalizer localizer, IClock clock, ILogger logger) : ITransientDependency @@ -48,10 +49,14 @@ public class ProbationReminderWorker( var targetDate = clock.Now.Date.AddDays(ReminderDays); var users = await userRepository.GetListAsync(a => a.IsActive, cancellationToken: cancellationToken); - foreach (var user in users.Where(u => u.GetProbationEndDate()?.Date == targetDate && u.GetTerminationDate() is null)) + // Metin ve tarih alicinin degil kiracinin dilinde uretilir; worker'in kendi kulturu surec varsayilanidir. + using (await notificationCulture.UseAsync()) { - var message = localizer["App.Notifications.ProbationEnding", $"{user.Name} {user.Surname}".Trim(), targetDate.ToString("dd.MM.yyyy")]; - await notificationManager.SendNotificationAsync(notificationType.Id, message); + foreach (var user in users.Where(u => u.GetProbationEndDate()?.Date == targetDate && u.GetTerminationDate() is null)) + { + var message = localizer["App.Notifications.ProbationEnding", $"{user.Name} {user.Surname}".Trim(), CultureFormat.Date(targetDate)]; + await notificationManager.SendNotificationAsync(notificationType.Id, message); + } } } } diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/SqlWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/SqlWorker.cs index 96539ddd..394a5fe1 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/SqlWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/SqlWorker.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Volo.Abp.BackgroundWorkers; using Volo.Abp.Uow; @@ -11,12 +10,12 @@ public class SqlWorker : BackgroundWorkerBase { public override async Task StartAsync(CancellationToken cancellationToken = default) { - using var scope = ServiceProvider.CreateScope(); using var uow = LazyServiceProvider.LazyGetRequiredService().Begin(); Logger.LogInformation("Executed SqlWorker..!"); await Task.Yield(); + + await uow.CompleteAsync(cancellationToken); } } - diff --git a/api/src/Sozsoft.Platform.Domain/Branch/BranchSeedManager.cs b/api/src/Sozsoft.Platform.Domain/Branch/BranchSeedManager.cs index 248ef1e6..de5c8e3a 100644 --- a/api/src/Sozsoft.Platform.Domain/Branch/BranchSeedManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Branch/BranchSeedManager.cs @@ -1,20 +1,15 @@ using System; using System.IO; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Localization; +using Sozsoft.Platform.Localization; using Volo.Abp.Domain.Services; namespace Sozsoft.Platform.Branchs; -public class BranchSeedManager : DomainService +public class BranchSeedManager(IStringLocalizer localizer) : DomainService { - - public BranchSeedManager( - ) - { - } - public async Task SeedRecordsAsync(Guid? tenantId, Guid branchId) { var result = new BranchSeedResult(); @@ -30,33 +25,23 @@ public class BranchSeedManager : DomainService .AddJsonFile($"BranchData.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? ""}.json", optional: true) .Build(); - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var items = configuration.Get(); if (items == null) { result.Success = false; - result.Message = "BranchData.json okunamadı veya format hatalı."; + result.Message = localizer["App.BranchSeed.DataUnreadable"]; return result; } - // Yardımcı fonksiyon: kolay log ekleme - BranchSeedDetail CreateLog(string entity) => - new() - { - EntityName = entity, - InsertedCount = 0, - InsertedItems = [] - }; - result.Success = true; - result.Message = $"Seed işlemi başarıyla tamamlandı. Toplam {result.TotalInsertedCount} kayıt eklendi."; + result.Message = localizer["App.BranchSeed.Completed", result.TotalInsertedCount]; return result; } catch (Exception ex) { result.Success = false; - result.Message = $"Hata: {ex.Message}"; + result.Message = localizer["App.BranchSeed.ErrorWithMessage", ex.Message]; return result; } } diff --git a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs index 0bd2a751..d64a10a3 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedApplier.cs @@ -54,8 +54,6 @@ public class CrudEndpointSeedApplier( IUnitOfWorkManager unitOfWorkManager, ILogger logger) : ICrudEndpointSeedApplier, ITransientDependency { - private static readonly JsonSerializerOptions JsonReadOptions = new() { PropertyNameCaseInsensitive = true }; - public async Task> ApplyScopeAsync(Guid? tenantId) { var crudPath = SeedPathResolver.GetPath(configuration, SeedPathResolver.CrudFolder); @@ -134,7 +132,7 @@ public class CrudEndpointSeedApplier( try { var seedFile = JsonSerializer.Deserialize( - await File.ReadAllTextAsync(filePath), JsonReadOptions); + await File.ReadAllTextAsync(filePath), PlatformJsonOptions.CaseInsensitive); var entityName = string.IsNullOrWhiteSpace(seedFile?.EntityName) ? Path.GetFileNameWithoutExtension(filePath) diff --git a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs index 62e852b0..fbcffdd7 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CrudEndpointSeedFile.cs @@ -23,7 +23,11 @@ public class CrudEndpointSeedFileDto public List Endpoints { get; set; } = []; - private static readonly JsonSerializerOptions WriteOptions = new() { WriteIndented = true }; + private static readonly JsonSerializerOptions WriteOptions = new() + { + WriteIndented = true, + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; /// /// Bir entity'nin standart endpoint kumesi: tabloda bes operasyon, view/procedure/function'da @@ -77,14 +81,17 @@ public class CrudEndpointSeedFileDto return string.IsNullOrWhiteSpace(safe) ? null : safe; } - /// Dosyayi crud/{EntityName}.json olarak yazar ve tam yolunu dondurur. + /// + /// Dosyayi crud/{EntityName}.json olarak yazar ve tam yolunu dondurur. Metin diger seed dosyalari + /// gibi ten gecer; emoji vekil cift olarak kacirilip sahte fark uretmez. + /// public string Write(IConfiguration configuration) { var folder = SeedPathResolver.GetPath(configuration, SeedPathResolver.CrudFolder); Directory.CreateDirectory(folder); var filePath = Path.Combine(folder, $"{SafeFileName(EntityName)}.json"); - File.WriteAllText(filePath, JsonSerializer.Serialize(this, WriteOptions)); + File.WriteAllText(filePath, SeedJsonText.RestoreSurrogatePairs(JsonSerializer.Serialize(this, WriteOptions))); return filePath; } diff --git a/api/src/Sozsoft.Platform.Domain/Data/CustomComponentDataSourceResolver.cs b/api/src/Sozsoft.Platform.Domain/Data/CustomComponentDataSourceResolver.cs index 0c2bfc09..22e7a341 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/CustomComponentDataSourceResolver.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/CustomComponentDataSourceResolver.cs @@ -15,11 +15,6 @@ namespace Sozsoft.Platform.Data.Seeds; /// public static class CustomComponentDataSourceResolver { - private static readonly JsonSerializerOptions JsonReadOptions = new() - { - PropertyNameCaseInsensitive = true - }; - /// Eslestirmede kullanilan CRUD endpoint tanimi. public sealed class CrudEndpointReference { @@ -101,7 +96,7 @@ public static class CustomComponentDataSourceResolver return []; } - return JsonSerializer.Deserialize>(dataSources.GetRawText(), JsonReadOptions) ?? []; + return JsonSerializer.Deserialize>(dataSources.GetRawText(), PlatformJsonOptions.CaseInsensitive) ?? []; } catch (JsonException) { diff --git a/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs b/api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFileDto.cs similarity index 100% rename from api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFile.cs rename to api/src/Sozsoft.Platform.Domain/Data/ListFormSeedDataFileDto.cs diff --git a/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs b/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs index 482ef111..3b5010f0 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/PostgreSqlTableScript.cs @@ -176,7 +176,7 @@ public static partial class PostgreSqlTableScript return "DO $$\nBEGIN\n" + $" IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = '{name.Replace("'", "''")}') THEN\n" + - $" ALTER TABLE {table} ADD CONSTRAINT {Quote(name)} {definition};\n" + + $" ALTER TABLE {table} ADD CONSTRAINT {SqlIdentifier.QuoteAnsi(name)} {definition};\n" + " END IF;\nEND\n$$;"; } @@ -187,7 +187,7 @@ public static partial class PostgreSqlTableScript if (index.Groups["unique"].Success) sql.Append("UNIQUE "); - sql.Append($"INDEX IF NOT EXISTS {Quote(index.Groups["name"].Value)} ON {Identifier(index.Groups["table"].Value)} ({ColumnList(index.Groups["columns"].Value)})"); + sql.Append($"INDEX IF NOT EXISTS {SqlIdentifier.QuoteAnsi(index.Groups["name"].Value)} ON {Identifier(index.Groups["table"].Value)} ({ColumnList(index.Groups["columns"].Value)})"); if (index.Groups["include"].Success) sql.Append($" INCLUDE ({ColumnList(index.Groups["include"].Value)})"); @@ -225,7 +225,7 @@ public static partial class PostgreSqlTableScript if (type is null) return null; - var line = new StringBuilder($" {Quote(column.Groups["name"].Value)} {type}"); + var line = new StringBuilder($" {SqlIdentifier.QuoteAnsi(column.Groups["name"].Value)} {type}"); if (column.Groups["identity"].Success) line.Append(" GENERATED BY DEFAULT AS IDENTITY"); @@ -242,9 +242,9 @@ public static partial class PostgreSqlTableScript { var columns = string.Join(", ", primaryKey.Groups["columns"].Value .Split(',') - .Select(a => Quote(SortOrderRegex().Replace(a.Trim(), string.Empty).Trim('[', ']', ' ')))); + .Select(a => SqlIdentifier.QuoteAnsi(SortOrderRegex().Replace(a.Trim(), string.Empty).Trim('[', ']', ' ')))); - lines.Add($" CONSTRAINT {Quote(primaryKey.Groups["name"].Value)} PRIMARY KEY ({columns})"); + lines.Add($" CONSTRAINT {SqlIdentifier.QuoteAnsi(primaryKey.Groups["name"].Value)} PRIMARY KEY ({columns})"); } return $"CREATE TABLE IF NOT EXISTS {Identifier(match.Groups["table"].Value)}\n(\n{string.Join(",\n", lines)}\n);"; @@ -266,7 +266,7 @@ public static partial class PostgreSqlTableScript .Select(a => { var descending = a.EndsWith(" DESC", StringComparison.OrdinalIgnoreCase); - var name = Quote(SortOrderRegex().Replace(a, string.Empty).Trim('[', ']', ' ')); + var name = SqlIdentifier.QuoteAnsi(SortOrderRegex().Replace(a, string.Empty).Trim('[', ']', ' ')); return descending ? name + " DESC" : name; })); @@ -287,12 +287,10 @@ public static partial class PostgreSqlTableScript /// [dbo].[X] → public."X", kalan [x] → "x". private static string Identifier(string text) { - text = DboIdentifierRegex().Replace(text, m => "public." + Quote(m.Groups["name"].Value)); - return BracketIdentifierRegex().Replace(text, m => Quote(m.Groups["name"].Value)); + text = DboIdentifierRegex().Replace(text, m => "public." + SqlIdentifier.QuoteAnsi(m.Groups["name"].Value)); + return BracketIdentifierRegex().Replace(text, m => SqlIdentifier.QuoteAnsi(m.Groups["name"].Value)); } - private static string Quote(string name) => "\"" + name.Replace("\"", "\"\"") + "\""; - private static string? ColumnType(string sqlServerType) { var type = WhitespaceRegex().Replace(sqlServerType.Trim().ToLowerInvariant(), string.Empty); diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Host/DeveloperKit/DynamicService.cs b/api/src/Sozsoft.Platform.Domain/Entities/Host/DeveloperKit/DynamicService.cs index 074e8edc..b3ed891e 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Host/DeveloperKit/DynamicService.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Host/DeveloperKit/DynamicService.cs @@ -115,11 +115,12 @@ public class DynamicService : FullAuditedEntity /// /// Derleme başarısını işaretler /// - public void MarkCompilationSuccess() + /// Derleme başarısını işaretler; zaman çağıranın Clock.Now değeridir. + public void MarkCompilationSuccess(DateTime now) { CompilationStatus = CompilationStatus.Success; LastCompilationError = null; - LastSuccessfulCompilation = DateTime.Now; + LastSuccessfulCompilation = now; } /// diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Host/Website/BlogPost.cs b/api/src/Sozsoft.Platform.Domain/Entities/Host/Website/BlogPost.cs index 42f9d782..de73ab05 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Host/Website/BlogPost.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Host/Website/BlogPost.cs @@ -25,10 +25,11 @@ public class BlogPost : FullAuditedEntity public bool IsPublished { get; set; } = false; public DateTime? PublishedAt { get; set; } - public void Publish() + /// Yayınlar; zaman çağıranın Clock.Now değeridir. + public void Publish(DateTime now) { IsPublished = true; - PublishedAt = DateTime.Now; + PublishedAt = now; } public void Unpublish() diff --git a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Definitions/Sequence.cs b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Definitions/Sequence.cs index a238fb84..e05119e5 100644 --- a/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Definitions/Sequence.cs +++ b/api/src/Sozsoft.Platform.Domain/Entities/Tenant/Administration/Definitions/Sequence.cs @@ -3,6 +3,8 @@ using Sozsoft.Platform.Enums; using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.MultiTenancy; +namespace Sozsoft.Platform.Entities; + public class Sequence : FullAuditedEntity, IMultiTenant { public Guid? TenantId { get; set; } @@ -29,11 +31,6 @@ public class Sequence : FullAuditedEntity, IMultiTenant public string FormatTemplate { get; set; } = "{Prefix}{Number}{Suffix}"; - public string GetNextSequence() - { - return GetNextSequence(DateTime.Now); - } - public string GetNextSequence(DateTime dateTime) { ResetIfPeriodChanged(dateTime); @@ -45,11 +42,6 @@ public class Sequence : FullAuditedEntity, IMultiTenant return sequenceValue; } - public string GetPreviewSequence() - { - return GetPreviewSequence(DateTime.Now); - } - public string GetPreviewSequence(DateTime dateTime) { var previewNumber = NextNumber; diff --git a/api/src/Sozsoft.Platform.Domain/Identity/CaptchaManager.cs b/api/src/Sozsoft.Platform.Domain/Identity/CaptchaManager.cs deleted file mode 100644 index 1d169411..00000000 --- a/api/src/Sozsoft.Platform.Domain/Identity/CaptchaManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -//using System; -//using System.Threading.Tasks; -//using Flurl.Http; -//using Sozsoft.Platform.Localization; -//using Microsoft.Extensions.Localization; -//using Volo.Abp; -//using Volo.Abp.ObjectMapping; -//using Volo.Abp.Settings; -//using Volo.Abp.Users; - -//namespace Sozsoft.Platform.Identity; - -//public class CaptchaManager : PlatformDomainService, ICaptchaManager -//{ -// public CaptchaManager( -// ISettingProvider settingProvider, -// IStringLocalizer localizer, -// ICurrentUser currentUser, -// IObjectMapper objectMapper) -// : base(settingProvider, localizer, currentUser, objectMapper, null) -// { -// } - -// public async Task VerifyCaptchaAsync(string CaptchaResponse, bool throwOnFailure = false) -// { -// var endPoint = await SettingProvider.GetOrNullAsync(PlatformConsts.Settings.AccountCaptchaEndPoint); -// var privateKey = await SettingProvider.GetOrNullAsync(PlatformConsts.Settings.AccountCaptchaPrivateKey); -// if (endPoint.IsNullOrWhiteSpace() || privateKey.IsNullOrWhiteSpace()) -// { -// //alertManager.Alerts.Danger(LK[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]); -// return false; -// } - -// // METHOD: POST -// /* -// secret Required. The shared key between your site and reCAPTCHA. -// response Required. The user response token provided by the reCAPTCHA client-side integration on your site. -// remoteip Optional. The user's IP address. -// */ - -// var response = await endPoint.PostUrlEncodedAsync(new -// { -// secret = privateKey, -// response = CaptchaResponse -// }); - -// if (response != null && response.StatusCode == 200) -// { -// var result = await response.GetJsonAsync(); -// if (!(bool)(result.success)) -// { -// if (throwOnFailure) -// { -// throw new UserFriendlyException(Localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]); -// } -// } -// return (bool)(result.success); -// } - -// return false; -// } - -//} - diff --git a/api/src/Sozsoft.Platform.Domain/ListForms/ListFormManager.cs b/api/src/Sozsoft.Platform.Domain/ListForms/ListFormManager.cs index 274f723e..6f2112bf 100644 --- a/api/src/Sozsoft.Platform.Domain/ListForms/ListFormManager.cs +++ b/api/src/Sozsoft.Platform.Domain/ListForms/ListFormManager.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Primitives; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.Localization; -using Sozsoft.Platform.OrganizationUnits; using Sozsoft.Platform.Queries; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; @@ -35,8 +34,6 @@ public class ListFormManager : PlatformDomainService, IListFormManager { private readonly IRepository listFormRepository; private readonly IDefaultValueManager defaultValueManager; - private readonly IPlatformOuRepository ouRepository; - private readonly IRepository branchUsersRepository; private readonly string cultureName; private readonly string cultureNameDefault; @@ -47,15 +44,11 @@ public class ListFormManager : PlatformDomainService, IListFormManager IObjectMapper objectMapper, IListFormAuthorizationManager authManager, IRepository listFormRepository, - IDefaultValueManager defaultValueManager, - IPlatformOuRepository ouRepository, - IRepository branchUsersRepository) + IDefaultValueManager defaultValueManager) : base(settingProvider, localizer, currentUser, objectMapper, authManager) { this.listFormRepository = listFormRepository; this.defaultValueManager = defaultValueManager; - this.ouRepository = ouRepository; - this.branchUsersRepository = branchUsersRepository; cultureName = CultureInfo.CurrentUICulture.Name; cultureNameDefault = PlatformConsts.DefaultLanguage; } @@ -123,32 +116,6 @@ public class ListFormManager : PlatformDomainService, IListFormManager } } - // if (listForm.IsBranch) - // { - // var ids = await branchUsersRepository.GetListAsync((a) => a.UserId == CurrentUser.Id.Value); - // if (ids.Count > 0) - // { - // fields.Add("BranchId", $"IN ({string.Join(",", ids.Select(a => $"'{a.BranchId}'"))})"); - // } - // else - // { - // fields.Add("BranchId", $"IN ('{Guid.Empty}')"); - // } - // } - - // if (listForm.IsOrganizationUnit) - // { - // var ids = await ouRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.Id.Value); - // if (ids.Count > 0) - // { - // fields.Add("OrganizationUnitId", $"IN ({string.Join(",", ids.Select(a => $"'{a}'"))})"); - // } - // else - // { - // fields.Add("OrganizationUnitId", $"IN ('{Guid.Empty}')"); - // } - // } - foreach (var item in defaultFields) { fields[item.Key] = item.Value.CustomValue; @@ -157,4 +124,3 @@ public class ListFormManager : PlatformDomainService, IListFormManager return fields; } } - diff --git a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs b/api/src/Sozsoft.Platform.Domain/LookupQueryValues.cs similarity index 98% rename from api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs rename to api/src/Sozsoft.Platform.Domain/LookupQueryValues.cs index 4581d6bd..8d1b0535 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs +++ b/api/src/Sozsoft.Platform.Domain/LookupQueryValues.cs @@ -371,7 +371,7 @@ public static class LookupQueryValues public static string TenantShiftValues = $"SELECT " + $"\"Id\" AS \"Key\", " + - $"\"Name\" + ' (' + \"StartTime\" + '-' + \"EndTime\" + ')' AS \"Name\" " + + $"CONCAT(\"Name\", ' (', \"StartTime\", '-', \"EndTime\", ')') AS \"Name\" " + $"FROM \"Hr_T_Shift\" " + $"WHERE " + $"\"IsDeleted\" = 'false' " + @@ -383,7 +383,7 @@ public static class LookupQueryValues public static string TerminationReasonValues = $"SELECT " + $"\"Id\" AS \"Key\", " + - $"\"Code\" + ' - ' + \"Name\" AS \"Name\" " + + $"CONCAT(\"Code\", ' - ', \"Name\") AS \"Name\" " + $"FROM \"{FullNameTable(TableNameEnum.TerminationReason)}\" " + $"WHERE " + $"\"IsDeleted\" = 'false' " + @@ -433,7 +433,7 @@ public static class LookupQueryValues public static string TenantNonLanguageSkillValues = $"SELECT " + $"s.\"Id\" AS \"Key\", " + - $"t.\"Name\" + ' - ' + s.\"Name\" AS \"Name\" " + + $"CONCAT(t.\"Name\", ' - ', s.\"Name\") AS \"Name\" " + $"FROM \"{FullNameTable(TableNameEnum.Skill)}\" s " + $"INNER JOIN \"{FullNameTable(TableNameEnum.SkillType)}\" t ON t.\"Id\" = s.\"SkillTypeId\" " + $"WHERE " + diff --git a/api/src/Sozsoft.Platform.Domain/PlatformDomainModule.cs b/api/src/Sozsoft.Platform.Domain/PlatformDomainModule.cs index e95fe6ad..695ea034 100644 --- a/api/src/Sozsoft.Platform.Domain/PlatformDomainModule.cs +++ b/api/src/Sozsoft.Platform.Domain/PlatformDomainModule.cs @@ -31,9 +31,9 @@ namespace Sozsoft.Platform; typeof(AbpPermissionManagementDomainIdentityModule), typeof(AbpTenantManagementDomainModule), typeof(LanguagesDomainModule), - typeof(ErpSenderModule), + typeof(SozsoftSenderModule), typeof(SettingsDomainModule), - typeof(ErpMailQueueModule), + typeof(SozsoftMailQueueModule), typeof(NotificationDomainModule), typeof(AbpBlobStoringModule), typeof(AbpBlobStoringFileSystemModule) diff --git a/api/src/Sozsoft.Platform.Domain/PlatformDomainService.cs b/api/src/Sozsoft.Platform.Domain/PlatformDomainService.cs index 7a27640b..a92eb8cb 100644 --- a/api/src/Sozsoft.Platform.Domain/PlatformDomainService.cs +++ b/api/src/Sozsoft.Platform.Domain/PlatformDomainService.cs @@ -16,18 +16,6 @@ public class PlatformDomainService : DomainService protected IObjectMapper ObjectMapper { get; } protected IListFormAuthorizationManager AuthManager { get; } - public PlatformDomainService( - ISettingProvider settingProvider, - IStringLocalizer localizer, - ICurrentUser currentUser, - IObjectMapper objectMapper) - { - SettingProvider = settingProvider; - Localizer = localizer; - CurrentUser = currentUser; - ObjectMapper = objectMapper; - } - public PlatformDomainService( ISettingProvider settingProvider, IStringLocalizer localizer, @@ -42,4 +30,3 @@ public class PlatformDomainService : DomainService AuthManager = authManager; } } - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/Break.cs b/api/src/Sozsoft.Platform.Domain/Queries/Break.cs deleted file mode 100644 index 9d89882f..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/Break.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -/// -/// Break tanımı -/// -public class Break : ValueObject -{ - [JsonInclude] - [JsonPropertyName("StartValue")] - public int StartValue { get; private set; } - - [JsonInclude] - [JsonPropertyName("EndValue")] - public int EndValue { get; private set; } - - public Break() { } // JSON için gerekli - - protected override IEnumerable GetAtomicValues() - { - yield return StartValue; - yield return EndValue; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/BreakStyle.cs b/api/src/Sozsoft.Platform.Domain/Queries/BreakStyle.cs deleted file mode 100644 index 6bd08dc3..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/BreakStyle.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class BreakStyle : ValueObject -{ - [JsonInclude] - [JsonPropertyName("Color")] - public string Color { get; private set; } - - [JsonInclude] - [JsonPropertyName("Line")] - public string Line { get; private set; } - - [JsonInclude] - [JsonPropertyName("Width")] - public int Width { get; private set; } - - // JSON deserialization için parametresiz public ctor bırak - public BreakStyle() { } - - protected override IEnumerable GetAtomicValues() - { - yield return Color; - yield return Line; - yield return Width; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartAnnotation.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartAnnotation.cs deleted file mode 100644 index b8e978e0..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartAnnotation.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartAnnotation : ValueObject -{ - [JsonInclude] public string Argument { get; private set; } - [JsonInclude] public ChartBorder Border { get; private set; } = new(); - [JsonInclude] public string Color { get; private set; } = "#ffffff"; - [JsonInclude] public string Description { get; private set; } - [JsonInclude] public ChartFont Font { get; private set; } = new(); - [JsonInclude] public int Height { get; private set; } = -1; - [JsonInclude] public string Image { get; private set; } - [JsonInclude] public string Name { get; private set; } - [JsonInclude] public int OffsetX { get; private set; } = -1; - [JsonInclude] public int OffsetY { get; private set; } = -1; - [JsonInclude] public int PaddingLeftRight { get; private set; } = 10; - [JsonInclude] public int PaddingTopBottom { get; private set; } = 10; - [JsonInclude] public string Series { get; private set; } - [JsonInclude] public string Text { get; private set; } - [JsonInclude] public bool TooltipEnabled { get; private set; } = true; - [JsonInclude] public string Type { get; private set; } - [JsonInclude] public string Value { get; private set; } - [JsonInclude] public int Width { get; private set; } - [JsonInclude] public string WordWrap { get; private set; } = "normal"; - [JsonInclude] public int X { get; private set; } - [JsonInclude] public int Y { get; private set; } - - public ChartAnnotation() { } - - protected override IEnumerable GetAtomicValues() - { - yield return Argument; - yield return Border; - yield return Color; - yield return Description; - yield return Font; - yield return Height; - yield return Image; - yield return Name; - yield return OffsetX; - yield return OffsetY; - yield return PaddingLeftRight; - yield return PaddingTopBottom; - yield return Series; - yield return Text; - yield return TooltipEnabled; - yield return Type; - yield return Value; - yield return Width; - yield return WordWrap; - yield return X; - yield return Y; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartAxisGrid.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartAxisGrid.cs deleted file mode 100644 index 9ae8c1a8..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartAxisGrid.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -/// -/// Grid tanımı -/// -public class ChartAxisGrid : ValueObject -{ - [JsonInclude] - [JsonPropertyName("Color")] - public string Color { get; private set; } - - [JsonInclude] - [JsonPropertyName("Visible")] - public bool Visible { get; private set; } - - [JsonInclude] - [JsonPropertyName("Width")] - public int Width { get; private set; } - - public ChartAxisGrid() { } // JSON için gerekli - - protected override IEnumerable GetAtomicValues() - { - yield return Color; - yield return Visible; - yield return Width; - } -} - - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartBorder.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartBorder.cs deleted file mode 100644 index af8ad23a..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartBorder.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartBorder : ValueObject -{ - [JsonInclude] - public string Color { get; private set; } = "#d3d3d3"; - - [JsonInclude] - public int CornerRadius { get; private set; } = 0; - - [JsonInclude] - public string DashStyle { get; private set; } = "solid"; - - [JsonInclude] - public bool Visible { get; private set; } - - [JsonInclude] - public int Width { get; private set; } = 1; - - public ChartBorder() { } // JSON deserialize için parametresiz public ctor - - protected override IEnumerable GetAtomicValues() - { - yield return Color; - yield return CornerRadius; - yield return DashStyle; - yield return Visible; - yield return Width; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartFont.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartFont.cs deleted file mode 100644 index 936e3b3a..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartFont.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartFont : ValueObject -{ - [JsonInclude] - public string Color { get; private set; } = "#FFFFFF"; - - [JsonInclude] - public string Family { get; private set; } = "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif"; - - [JsonInclude] - public int Size { get; private set; } = 12; - - [JsonInclude] - public int Weight { get; private set; } = 400; - - public ChartFont() { } - - protected override IEnumerable GetAtomicValues() - { - yield return Color; - yield return Family; - yield return Size; - yield return Weight; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartLabel.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartLabel.cs deleted file mode 100644 index 06387bd0..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartLabel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartLabel : ValueObject -{ - [JsonInclude] public string BackgroundColor { get; private set; } = "#f05b41"; - [JsonInclude] public string CustomizeText { get; private set; } - [JsonInclude] public ChartFont Font { get; private set; } = new(); - [JsonInclude] public bool Visible { get; private set; } - [JsonInclude] public string Format { get; private set; } - - public ChartLabel() { } - - protected override IEnumerable GetAtomicValues() - { - yield return BackgroundColor; - yield return CustomizeText; - yield return Font; - yield return Visible; - yield return Format; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartPanes.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartPanes.cs deleted file mode 100644 index 5f59b539..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartPanes.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartPanes : ValueObject -{ - public string BackgroundColor { get; private set; } - public int Height { get; private set; } = 250; - public string Name { get; private set; } - - private ChartPanes() { } // EF Core / JSON için - - public ChartPanes(string name, int height, string backgroundColor) - { - Name = name; - Height = height > 0 ? height : 250; - BackgroundColor = backgroundColor ?? string.Empty; - } - - protected override IEnumerable GetAtomicValues() - { - yield return Name; - yield return Height; - yield return BackgroundColor; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/ChartValueAxis.cs b/api/src/Sozsoft.Platform.Domain/Queries/ChartValueAxis.cs deleted file mode 100644 index bcd3c800..00000000 --- a/api/src/Sozsoft.Platform.Domain/Queries/ChartValueAxis.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Volo.Abp.Domain.Values; - -namespace Sozsoft.Platform.Queries; - -public class ChartValueAxis : ValueObject -{ - [JsonInclude] - [JsonPropertyName("Grid")] - public ChartAxisGrid Grid { get; private set; } - - [JsonInclude] - [JsonPropertyName("Name")] - public string Name { get; private set; } - - [JsonInclude] - [JsonPropertyName("Position")] - public string Position { get; private set; } = "left"; - - [JsonInclude] - [JsonPropertyName("Title")] - public string Title { get; private set; } - - [JsonInclude] - [JsonPropertyName("ValueType")] - public string ValueType { get; private set; } = "numeric"; - - [JsonInclude] - [JsonPropertyName("Visible")] - public bool Visible { get; private set; } = true; - - [JsonInclude] - [JsonPropertyName("Width")] - public int Width { get; private set; } = 1; - - [JsonInclude] - [JsonPropertyName("Breaks")] - public List Breaks { get; private set; } = new(); - - [JsonInclude] - [JsonPropertyName("BreakStyle")] - public BreakStyle BreakStyle { get; private set; } = new(); - - [JsonInclude] - [JsonPropertyName("Type")] - public string Type { get; private set; } - - [JsonInclude] - [JsonPropertyName("AutoBreaksEnabled")] - public bool AutoBreaksEnabled { get; private set; } = true; - - [JsonInclude] - [JsonPropertyName("MaxAutoBreakCount")] - public int MaxAutoBreakCount { get; private set; } = 2; - - [JsonConstructor] - public ChartValueAxis() { } - - protected override IEnumerable GetAtomicValues() - { - yield return Grid; - yield return Name; - yield return Position; - yield return Title; - yield return ValueType; - yield return Visible; - yield return Width; - foreach (var br in Breaks) - yield return br; - yield return BreakStyle; - yield return Type; - yield return AutoBreaksEnabled; - yield return MaxAutoBreakCount; - } -} - diff --git a/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueHelper.cs b/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueHelper.cs index e3d5a6cb..81b52c96 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueHelper.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueHelper.cs @@ -1,11 +1,13 @@ using System; using System.Globalization; -using Sozsoft.Platform; +using System.Linq; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; using Volo.Abp.Users; +namespace Sozsoft.Platform.Queries; + public class DefaultValueHelper : ITransientDependency { private readonly ICurrentUser _currentUser; @@ -30,17 +32,23 @@ public class DefaultValueHelper : ITransientDependency var now = _clock.Now; + // Bu token'lar SelectCommand/DefaultFilter metnine parametre olarak bağlanmaz, metnin içine + // yazılır ('@USERNAME' gibi tırnak içinde). Kullanıcı adı ve rol adı serbest metindir; tek + // tırnağın ikilenmesi SQL metnini kapatmasını önleyen tek korumadır. + var userName = EscapeSqlLiteral(_currentUser.UserName ?? string.Empty); + var roles = string.Join("','", (_currentUser.Roles ?? []).Select(EscapeSqlLiteral)); + var result = strValue .Replace(PlatformConsts.DefaultValues.UserId, _currentUser.Id?.ToString() ?? Guid.Empty.ToString()) - .Replace(PlatformConsts.DefaultValues.UserName, _currentUser.UserName ?? string.Empty) - .Replace(PlatformConsts.DefaultValues.Roles, string.Join("','", _currentUser.Roles ?? [])) + .Replace(PlatformConsts.DefaultValues.UserName, userName) + .Replace(PlatformConsts.DefaultValues.Roles, roles) .Replace(PlatformConsts.DefaultValues.Date, now.Date.ToString("O", CultureInfo.InvariantCulture)) .Replace(PlatformConsts.DefaultValues.Now, now.ToString("O", CultureInfo.InvariantCulture)) .Replace(PlatformConsts.DefaultValues.Day, now.Day.ToString(CultureInfo.InvariantCulture)) .Replace(PlatformConsts.DefaultValues.Month, now.Month.ToString(CultureInfo.InvariantCulture)) .Replace(PlatformConsts.DefaultValues.Year, now.Year.ToString(CultureInfo.InvariantCulture)); - // 🔹 TenantId özel durumu: NULL => IS NULL, varsa => = 'GUID' + // TenantId özel durumu: NULL => IS NULL, varsa => = 'GUID' if (_currentTenant?.Id.HasValue == true) { result = result.Replace( @@ -64,5 +72,6 @@ public class DefaultValueHelper : ITransientDependency return result; } -} + private static string EscapeSqlLiteral(string value) => value.Replace("'", "''"); +} diff --git a/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueManager.cs index 263015a7..7b0ead48 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/DefaultValueManager.cs @@ -5,6 +5,7 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Primitives; +using Sozsoft.Platform.Sequences; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; diff --git a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs index 16888a68..9e993d7b 100644 --- a/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs @@ -542,12 +542,12 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager } catch (JsonException) { - throw new UserFriendlyException("Invalid filter."); + throw new UserFriendlyException(Localizer["App.Platform.InvalidFilter"]); } if (root.ValueKind != JsonValueKind.Array) { - throw new UserFriendlyException("Invalid filter."); + throw new UserFriendlyException(Localizer["App.Platform.InvalidFilter"]); } return BuildFilterNode(root, listFormFields, 0); @@ -557,7 +557,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager { if (depth > MaxFilterDepth) { - throw new UserFriendlyException("Invalid filter."); + throw new UserFriendlyException(Localizer["App.Platform.InvalidFilter"]); } var items = node.EnumerateArray().ToList(); @@ -613,7 +613,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager } else { - throw new UserFriendlyException("Invalid filter."); + throw new UserFriendlyException(Localizer["App.Platform.InvalidFilter"]); } if (term == null) @@ -675,7 +675,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager JsonValueKind.True => "true", JsonValueKind.False => "false", JsonValueKind.Null or JsonValueKind.Undefined => "null", - _ => throw new UserFriendlyException("Invalid filter."), + _ => throw new UserFriendlyException(Localizer["App.Platform.InvalidFilter"]), }; var fieldFilter = fieldName.Split('.'); @@ -693,7 +693,7 @@ public class SelectQueryManager : PlatformDomainService, ISelectQueryManager var filterOperator = op; if (!FilterOperators.Contains(filterOperator)) { - throw new UserFriendlyException($"'{op}' is not a supported filter operator."); + throw new UserFriendlyException(Localizer["App.Platform.UnsupportedFilterOperator", op]); } var filterParamKey = $"@{filterFieldName}{filterParameterIndex++}"; diff --git a/api/src/Sozsoft.Platform.Domain/Repositories/IReportTemplateRepository.cs b/api/src/Sozsoft.Platform.Domain/Repositories/IReportTemplateRepository.cs deleted file mode 100644 index 958b0264..00000000 --- a/api/src/Sozsoft.Platform.Domain/Repositories/IReportTemplateRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Sozsoft.Platform.Entities; -using Volo.Abp.Domain.Repositories; - -namespace Sozsoft.Platform.Repositories; - -public interface IReportTemplateRepository : IRepository -{ - Task> GetListAsync( - int skipCount = 0, - int maxResultCount = int.MaxValue, - string sorting = null, - string filter = null, - string category = null - ); - - Task GetCountAsync(string filter = null, string category = null); - - Task GetByIdWithParametersAsync(Guid id); - - Task> GetByCategoryAsync(string category); -} - diff --git a/api/src/Sozsoft.Platform.Domain/Sequences/ISequenceRepository.cs b/api/src/Sozsoft.Platform.Domain/Sequences/ISequenceRepository.cs new file mode 100644 index 00000000..6612f73f --- /dev/null +++ b/api/src/Sozsoft.Platform.Domain/Sequences/ISequenceRepository.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Sozsoft.Platform.Entities; +using Volo.Abp.Domain.Repositories; + +namespace Sozsoft.Platform.Sequences; + +public interface ISequenceRepository : IRepository +{ + /// Aktif numaratörü izlemesiz okur; değişiklik ile yazılır. + Task FindActiveAsync(string name, CancellationToken cancellationToken = default); + + /// + /// Karşılaştır-ve-yaz: satır hâlâ okunan değerdeyse (, + /// ) ilerletir ve true döner; araya başka bir istek + /// girdiyse hiçbir şey yazmaz ve false döner — çağıran yeniden okuyup dener. + /// + Task TryAdvanceAsync( + Guid id, + long expectedNextNumber, + string? expectedPeriodKey, + long nextNumber, + string? periodKey, + CancellationToken cancellationToken = default); +} diff --git a/api/src/Sozsoft.Platform.Domain/Sequences/SequenceManager.cs b/api/src/Sozsoft.Platform.Domain/Sequences/SequenceManager.cs index 9c639041..8b209807 100644 --- a/api/src/Sozsoft.Platform.Domain/Sequences/SequenceManager.cs +++ b/api/src/Sozsoft.Platform.Domain/Sequences/SequenceManager.cs @@ -1,59 +1,71 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.Localization; -using Sozsoft.Platform; +using Sozsoft.Platform.Entities; using Sozsoft.Platform.Localization; -using Volo.Abp.Domain.Repositories; +using Volo.Abp; +using Volo.Abp.Data; using Volo.Abp.ObjectMapping; using Volo.Abp.Settings; using Volo.Abp.Users; +namespace Sozsoft.Platform.Sequences; + +/// +/// Formdan açılan kaydın numarasını üretir. Biçim ve sıfırlama kuralı ile +/// Adm_P_SequenceNext yordamında birebir aynıdır (CLAUDE.md cross-cutting); eşzamanlılık +/// korumasını yordam satır kilidiyle, burası karşılaştır-ve-yaz ile sağlar. +/// public class SequenceManager : PlatformDomainService { - private readonly IRepository _sequenceRepository; + /// Yarışan isteklerde aynı numaratör için yeniden okuma sayısı; aşılırsa istek düşer. + private const int MaxAttempts = 5; + + private readonly ISequenceRepository sequenceRepository; public SequenceManager( ISettingProvider settingProvider, IStringLocalizer localizer, ICurrentUser currentUser, IObjectMapper objectMapper, - IRepository sequenceRepository) + ISequenceRepository sequenceRepository) : base(settingProvider, localizer, currentUser, objectMapper) { - _sequenceRepository = sequenceRepository; + this.sequenceRepository = sequenceRepository; } public async Task GetNextSequenceAsync(string name) { - var sequence = await _sequenceRepository.FirstOrDefaultAsync(x => - x.Name == name && - x.IsActive - ); - - if (sequence == null) + for (var attempt = 1; attempt <= MaxAttempts; attempt++) { - throw new Exception($"Sequence bulunamadı: {name}"); + var sequence = await FindAsync(name); + var expectedNextNumber = sequence.NextNumber; + var expectedPeriodKey = sequence.LastPeriodKey; + + var value = sequence.GetNextSequence(Clock.Now); + + if (await sequenceRepository.TryAdvanceAsync( + sequence.Id, + expectedNextNumber, + expectedPeriodKey, + sequence.NextNumber, + sequence.LastPeriodKey)) + { + return value; + } } - var nextSequence = sequence.GetNextSequence(DateTime.Now); - - await _sequenceRepository.UpdateAsync(sequence, autoSave: true); - - return nextSequence; + throw new AbpDbConcurrencyException(Localizer["App.Platform.SequenceBusy", name]); } public async Task GetPreviewSequenceAsync(string name) { - var sequence = await _sequenceRepository.FirstOrDefaultAsync(x => - x.Name == name && - x.IsActive - ); + var sequence = await FindAsync(name); + return sequence.GetPreviewSequence(Clock.Now); + } - if (sequence == null) - { - throw new Exception($"Sequence bulunamadı: {name}"); - } - - return sequence.GetPreviewSequence(DateTime.Now); + private async Task FindAsync(string name) + { + return await sequenceRepository.FindActiveAsync(name) + ?? throw new UserFriendlyException(Localizer["App.Platform.SequenceNotFound", name]); } } diff --git a/api/src/Sozsoft.Platform.Domain/Sozsoft.Platform.Domain.csproj b/api/src/Sozsoft.Platform.Domain/Sozsoft.Platform.Domain.csproj index 2841a635..c0333af0 100644 --- a/api/src/Sozsoft.Platform.Domain/Sozsoft.Platform.Domain.csproj +++ b/api/src/Sozsoft.Platform.Domain/Sozsoft.Platform.Domain.csproj @@ -37,7 +37,7 @@ - + diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs index 972df54a..fe854167 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/DeveloperKit/PlatformDynamicEntityManager.cs @@ -5,12 +5,17 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Localization; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Domain.DeveloperKit; -using Sozsoft.Platform.EntityFrameworkCore; +using Sozsoft.Platform.Enums; +using Sozsoft.Platform.Localization; using Volo.Abp; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.MultiTenancy; +namespace Sozsoft.Platform.EntityFrameworkCore; + public class DynamicEntityManager : IDynamicEntityManager { private const string TenantIdColumn = "TenantId"; @@ -27,13 +32,16 @@ public class DynamicEntityManager : IDynamicEntityManager private readonly IDbContextProvider _dbContextProvider; private readonly ICurrentTenant _currentTenant; + private readonly IStringLocalizer _localizer; public DynamicEntityManager( IDbContextProvider dbContextProvider, - ICurrentTenant currentTenant) + ICurrentTenant currentTenant, + IStringLocalizer localizer) { _dbContextProvider = dbContextProvider; _currentTenant = currentTenant; + _localizer = localizer; } public async Task?> GetEntityListAsync( @@ -41,17 +49,17 @@ public class DynamicEntityManager : IDynamicEntityManager IReadOnlyDictionary? filters = null) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); - var tableName = QuoteIdentifier(entityName, isPostgreSql); - var idDeletedColumn = QuoteIdentifier("IsDeleted", isPostgreSql); + var provider = ProviderOf(dbContext); + var tableName = SqlIdentifier.Quote(entityName, provider); + var idDeletedColumn = SqlIdentifier.Quote("IsDeleted", provider); var hasIsDeleted = await ColumnExistsAsync(entityName, "IsDeleted"); var conditions = new List(); if (hasIsDeleted) - conditions.Add($"({idDeletedColumn} = {FalseLiteral(isPostgreSql)} OR {idDeletedColumn} IS NULL)"); + conditions.Add($"({idDeletedColumn} = {FalseLiteral(provider)} OR {idDeletedColumn} IS NULL)"); var parameters = new List(); - var tenantCondition = await BuildTenantConditionAsync(entityName, isPostgreSql, parameters); + var tenantCondition = await BuildTenantConditionAsync(entityName, provider, parameters); if (tenantCondition != null) conditions.Add(tenantCondition); @@ -60,7 +68,7 @@ public class DynamicEntityManager : IDynamicEntityManager var columns = await GetColumnNamesAsync(entityName); foreach (var filter in filters) { - var condition = BuildFilterCondition(filter.Key, filter.Value, entityName, columns, isPostgreSql, parameters); + var condition = BuildFilterCondition(filter.Key, filter.Value, entityName, columns, provider, parameters); if (condition != null) conditions.Add(condition); } @@ -79,12 +87,12 @@ public class DynamicEntityManager : IDynamicEntityManager /// SQL as a quoted identifier; the compared value is always a parameter, so /// neither half of a filter can carry injected SQL. /// - private static string? BuildFilterCondition( + private string? BuildFilterCondition( string key, string? rawValue, string entityName, IReadOnlyDictionary columns, - bool isPostgreSql, + DataSourceTypeEnum provider, List parameters) { if (string.IsNullOrWhiteSpace(key) || ReservedQueryKeys.Contains(key)) @@ -97,9 +105,9 @@ public class DynamicEntityManager : IDynamicEntityManager // An unknown column is a configuration mistake, not a filter to ignore: // dropping it silently would return the whole table as if it had matched. if (!columns.TryGetValue(columnName, out var actualColumn)) - throw new UserFriendlyException($"'{columnName}' is not a column of {entityName}."); + throw new UserFriendlyException(_localizer["App.Platform.ColumnNotInEntity", columnName, entityName]); - var column = QuoteIdentifier(actualColumn, isPostgreSql); + var column = SqlIdentifier.Quote(actualColumn, provider); if (operatorName == "isnull") return $"{column} IS NULL"; @@ -125,11 +133,11 @@ public class DynamicEntityManager : IDynamicEntityManager case "lte": return $"{column} <= {AddParameter(parameters, ToFilterValue(value))}"; case "contains": - return LikeCondition(column, "%" + EscapeLike(value) + "%", isPostgreSql, parameters); + return LikeCondition(column, "%" + EscapeLike(value) + "%", provider, parameters); case "startswith": - return LikeCondition(column, EscapeLike(value) + "%", isPostgreSql, parameters); + return LikeCondition(column, EscapeLike(value) + "%", provider, parameters); case "endswith": - return LikeCondition(column, "%" + EscapeLike(value), isPostgreSql, parameters); + return LikeCondition(column, "%" + EscapeLike(value), provider, parameters); case "in": var entries = value .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) @@ -137,7 +145,7 @@ public class DynamicEntityManager : IDynamicEntityManager .ToList(); return entries.Count == 0 ? null : $"{column} IN ({string.Join(", ", entries)})"; default: - throw new UserFriendlyException($"'{operatorName}' is not a supported filter operator."); + throw new UserFriendlyException(_localizer["App.Platform.UnsupportedFilterOperator", operatorName]); } } @@ -146,10 +154,10 @@ public class DynamicEntityManager : IDynamicEntityManager /// default, PostgreSQL ones are not, so it uses ILIKE there. The column is /// cast to text so a non-string column (a Guid key) can still be searched. /// - private static string LikeCondition(string column, string pattern, bool isPostgreSql, List parameters) + private static string LikeCondition(string column, string pattern, DataSourceTypeEnum provider, List parameters) { var placeholder = AddParameter(parameters, pattern); - return isPostgreSql + return provider == DataSourceTypeEnum.Postgresql ? $"{column}::text ILIKE {placeholder} ESCAPE '\\'" : $"CAST({column} AS NVARCHAR(MAX)) LIKE {placeholder} ESCAPE '\\'"; } @@ -193,8 +201,8 @@ public class DynamicEntityManager : IDynamicEntityManager private async Task> GetColumnNamesAsync(string tableName) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); - var query = isPostgreSql + var provider = ProviderOf(dbContext); + var query = provider == DataSourceTypeEnum.Postgresql ? $"SELECT column_name FROM information_schema.columns WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name = '{tableName.Replace("'", "''")}'" : $"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{tableName.Replace("'", "''")}'"; var rows = await ExecuteRawQueryAsync(query); @@ -211,18 +219,18 @@ public class DynamicEntityManager : IDynamicEntityManager public async Task GetEntityByIdAsync(string entityName, Guid id) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); - var tableName = QuoteIdentifier(entityName, isPostgreSql); - var idColumn = QuoteIdentifier("Id", isPostgreSql); - var isDeletedColumn = QuoteIdentifier("IsDeleted", isPostgreSql); + var provider = ProviderOf(dbContext); + var tableName = SqlIdentifier.Quote(entityName, provider); + var idColumn = SqlIdentifier.Quote("Id", provider); + var isDeletedColumn = SqlIdentifier.Quote("IsDeleted", provider); var hasIsDeleted = await ColumnExistsAsync(entityName, "IsDeleted"); var parameters = new List(); var conditions = new List { $"{idColumn} = {AddParameter(parameters, id)}" }; if (hasIsDeleted) - conditions.Add($"({isDeletedColumn} = {FalseLiteral(isPostgreSql)} OR {isDeletedColumn} IS NULL)"); + conditions.Add($"({isDeletedColumn} = {FalseLiteral(provider)} OR {isDeletedColumn} IS NULL)"); - var tenantCondition = await BuildTenantConditionAsync(entityName, isPostgreSql, parameters); + var tenantCondition = await BuildTenantConditionAsync(entityName, provider, parameters); if (tenantCondition != null) conditions.Add(tenantCondition); @@ -233,22 +241,22 @@ public class DynamicEntityManager : IDynamicEntityManager public async Task CreateEntityAsync(string entityName, JsonElement data) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); + var provider = ProviderOf(dbContext); var newId = Guid.NewGuid(); var hasIsDeleted = await ColumnExistsAsync(entityName, "IsDeleted"); var hasCreationTime = await ColumnExistsAsync(entityName, "CreationTime"); var parameters = new List(); - var columns = new List { QuoteIdentifier("Id", isPostgreSql) }; + var columns = new List { SqlIdentifier.Quote("Id", provider) }; var values = new List { AddParameter(parameters, newId) }; - if (hasCreationTime) { columns.Add(QuoteIdentifier("CreationTime", isPostgreSql)); values.Add(UtcNowExpression(isPostgreSql)); } - if (hasIsDeleted) { columns.Add(QuoteIdentifier("IsDeleted", isPostgreSql)); values.Add(FalseLiteral(isPostgreSql)); } + if (hasCreationTime) { columns.Add(SqlIdentifier.Quote("CreationTime", provider)); values.Add(UtcNowExpression(provider)); } + if (hasIsDeleted) { columns.Add(SqlIdentifier.Quote("IsDeleted", provider)); values.Add(FalseLiteral(provider)); } // Kayıt her zaman isteği yapanın kiracısına yazılır; istemcinin gönderdiği TenantId yok sayılır. if (_currentTenant.Id.HasValue && await ColumnExistsAsync(entityName, TenantIdColumn)) { - columns.Add(QuoteIdentifier(TenantIdColumn, isPostgreSql)); + columns.Add(SqlIdentifier.Quote(TenantIdColumn, provider)); values.Add(AddParameter(parameters, _currentTenant.Id.Value)); } @@ -257,11 +265,11 @@ public class DynamicEntityManager : IDynamicEntityManager if (IsProtectedColumn(prop.Name)) continue; - columns.Add(QuoteIdentifier(prop.Name, isPostgreSql)); + columns.Add(SqlIdentifier.Quote(prop.Name, provider)); values.Add(AddParameter(parameters, ToSqlParameterValue(prop.Value))); } - var insertQuery = $"INSERT INTO {QuoteIdentifier(entityName, isPostgreSql)} ({string.Join(", ", columns)}) VALUES ({string.Join(", ", values)})"; + var insertQuery = $"INSERT INTO {SqlIdentifier.Quote(entityName, provider)} ({string.Join(", ", columns)}) VALUES ({string.Join(", ", values)})"; await dbContext.Database.ExecuteSqlRawAsync(insertQuery, parameters.ToArray()); @@ -271,7 +279,7 @@ public class DynamicEntityManager : IDynamicEntityManager public async Task UpdateEntityAsync(string entityName, Guid id, JsonElement data) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); + var provider = ProviderOf(dbContext); var existing = await GetEntityByIdAsync(entityName, id); if (existing == null) return null; @@ -281,17 +289,17 @@ public class DynamicEntityManager : IDynamicEntityManager var hasLastModification = await ColumnExistsAsync(entityName, "LastModificationTime"); if (hasLastModification) - setParts.Add($"{QuoteIdentifier("LastModificationTime", isPostgreSql)} = {UtcNowExpression(isPostgreSql)}"); + setParts.Add($"{SqlIdentifier.Quote("LastModificationTime", provider)} = {UtcNowExpression(provider)}"); foreach (var prop in data.EnumerateObject()) { if (IsProtectedColumn(prop.Name)) continue; - setParts.Add($"{QuoteIdentifier(prop.Name, isPostgreSql)} = {AddParameter(parameters, ToSqlParameterValue(prop.Value))}"); + setParts.Add($"{SqlIdentifier.Quote(prop.Name, provider)} = {AddParameter(parameters, ToSqlParameterValue(prop.Value))}"); } - var updateQuery = $"UPDATE {QuoteIdentifier(entityName, isPostgreSql)} SET {string.Join(", ", setParts)} WHERE {QuoteIdentifier("Id", isPostgreSql)} = {AddParameter(parameters, id)}"; + var updateQuery = $"UPDATE {SqlIdentifier.Quote(entityName, provider)} SET {string.Join(", ", setParts)} WHERE {SqlIdentifier.Quote("Id", provider)} = {AddParameter(parameters, id)}"; await dbContext.Database.ExecuteSqlRawAsync(updateQuery, parameters.ToArray()); @@ -305,7 +313,7 @@ public class DynamicEntityManager : IDynamicEntityManager return false; var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); + var provider = ProviderOf(dbContext); var hasIsDeleted = await ColumnExistsAsync(entityName, "IsDeleted"); try @@ -314,21 +322,21 @@ public class DynamicEntityManager : IDynamicEntityManager { var hasDeletionTime = await ColumnExistsAsync(entityName, "DeletionTime"); var softDeleteQuery = hasDeletionTime - ? $"UPDATE {QuoteIdentifier(entityName, isPostgreSql)} SET {QuoteIdentifier("IsDeleted", isPostgreSql)} = {TrueLiteral(isPostgreSql)}, {QuoteIdentifier("DeletionTime", isPostgreSql)} = {UtcNowExpression(isPostgreSql)} WHERE {QuoteIdentifier("Id", isPostgreSql)} = '{id}'" - : $"UPDATE {QuoteIdentifier(entityName, isPostgreSql)} SET {QuoteIdentifier("IsDeleted", isPostgreSql)} = {TrueLiteral(isPostgreSql)} WHERE {QuoteIdentifier("Id", isPostgreSql)} = '{id}'"; + ? $"UPDATE {SqlIdentifier.Quote(entityName, provider)} SET {SqlIdentifier.Quote("IsDeleted", provider)} = {TrueLiteral(provider)}, {SqlIdentifier.Quote("DeletionTime", provider)} = {UtcNowExpression(provider)} WHERE {SqlIdentifier.Quote("Id", provider)} = '{id}'" + : $"UPDATE {SqlIdentifier.Quote(entityName, provider)} SET {SqlIdentifier.Quote("IsDeleted", provider)} = {TrueLiteral(provider)} WHERE {SqlIdentifier.Quote("Id", provider)} = '{id}'"; var affected = await dbContext.Database.ExecuteSqlRawAsync(softDeleteQuery); return affected > 0; } else { - var hardDeleteQuery = $"DELETE FROM {QuoteIdentifier(entityName, isPostgreSql)} WHERE {QuoteIdentifier("Id", isPostgreSql)} = '{id}'"; + var hardDeleteQuery = $"DELETE FROM {SqlIdentifier.Quote(entityName, provider)} WHERE {SqlIdentifier.Quote("Id", provider)} = '{id}'"; var affected = await dbContext.Database.ExecuteSqlRawAsync(hardDeleteQuery); return affected > 0; } } catch { - var hardDeleteQuery = $"DELETE FROM {QuoteIdentifier(entityName, isPostgreSql)} WHERE {QuoteIdentifier("Id", isPostgreSql)} = '{id}'"; + var hardDeleteQuery = $"DELETE FROM {SqlIdentifier.Quote(entityName, provider)} WHERE {SqlIdentifier.Quote("Id", provider)} = '{id}'"; var affected = await dbContext.Database.ExecuteSqlRawAsync(hardDeleteQuery); return affected > 0; } @@ -339,12 +347,12 @@ public class DynamicEntityManager : IDynamicEntityManager /// TenantId IS NULL). Güncelleme ve silme önce ile satırı /// bulduğu için aynı sınır onlara da uygulanır. /// - private async Task BuildTenantConditionAsync(string entityName, bool isPostgreSql, List parameters) + private async Task BuildTenantConditionAsync(string entityName, DataSourceTypeEnum provider, List parameters) { if (!await ColumnExistsAsync(entityName, TenantIdColumn)) return null; - var column = QuoteIdentifier(TenantIdColumn, isPostgreSql); + var column = SqlIdentifier.Quote(TenantIdColumn, provider); return _currentTenant.Id.HasValue ? $"{column} = {AddParameter(parameters, _currentTenant.Id.Value)}" : $"{column} IS NULL"; @@ -375,8 +383,8 @@ public class DynamicEntityManager : IDynamicEntityManager private async Task ColumnExistsAsync(string tableName, string columnName) { var dbContext = await _dbContextProvider.GetDbContextAsync(); - var isPostgreSql = IsPostgreSql(dbContext); - var query = isPostgreSql + var provider = ProviderOf(dbContext); + var query = provider == DataSourceTypeEnum.Postgresql ? $"SELECT COUNT(1) FROM information_schema.columns WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_name = '{tableName.Replace("'", "''")}' AND column_name = '{columnName.Replace("'", "''")}'" : $"SELECT COUNT(1) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{tableName.Replace("'", "''")}' AND COLUMN_NAME = '{columnName.Replace("'", "''")}'"; var connection = dbContext.Database.GetDbConnection(); @@ -474,31 +482,26 @@ public class DynamicEntityManager : IDynamicEntityManager }; } - private static bool IsPostgreSql(PlatformDbContext dbContext) + private static DataSourceTypeEnum ProviderOf(PlatformDbContext dbContext) { - return dbContext.Database.ProviderName?.Contains("Npgsql", StringComparison.OrdinalIgnoreCase) == true; + return dbContext.Database.ProviderName?.Contains("Npgsql", StringComparison.OrdinalIgnoreCase) == true + ? DataSourceTypeEnum.Postgresql + : DataSourceTypeEnum.Mssql; } - private static string QuoteIdentifier(string identifier, bool isPostgreSql) + private static string TrueLiteral(DataSourceTypeEnum provider) { - return isPostgreSql - ? $"\"{identifier.Replace("\"", "\"\"")}\"" - : $"[{identifier.Replace("]", "]]")}]"; + return provider == DataSourceTypeEnum.Postgresql ? "TRUE" : "1"; } - private static string TrueLiteral(bool isPostgreSql) + private static string FalseLiteral(DataSourceTypeEnum provider) { - return isPostgreSql ? "TRUE" : "1"; + return provider == DataSourceTypeEnum.Postgresql ? "FALSE" : "0"; } - private static string FalseLiteral(bool isPostgreSql) + private static string UtcNowExpression(DataSourceTypeEnum provider) { - return isPostgreSql ? "FALSE" : "0"; - } - - private static string UtcNowExpression(bool isPostgreSql) - { - return isPostgreSql ? "NOW() AT TIME ZONE 'UTC'" : "SYSUTCDATETIME()"; + return provider == DataSourceTypeEnum.Postgresql ? "NOW() AT TIME ZONE 'UTC'" : "SYSUTCDATETIME()"; } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/DynamicDataRepository.cs similarity index 90% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/DynamicDataRepository.cs index 1c2abffe..8f05369e 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/MsDynamicDataRepository.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/DynamicDataRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading.Tasks; using Dapper; using Microsoft.Data.SqlClient; +using Npgsql; using Sozsoft.Platform.DynamicData; using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; @@ -15,14 +16,19 @@ using Volo.Abp.Uow; namespace Sozsoft.Platform.Domain.DynamicData; -[ExposeKeyedService("Ms")] -public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable +/// +/// Dapper üzerinden dinamik SQL çalıştıran tek depo gerçeklemesi. Sağlayıcı yalnızca bağlantının +/// üretildiği yerde () seçilir; parametre normalizasyonu, UoW'ye bağlı +/// transaction ve bağlantı ömrü iki sağlayıcıda aynıdır. +/// +public abstract class DynamicDataRepository : IDynamicDataRepository, IUnitOfWorkEnabled, IDisposable { + private readonly DbProviderFactory _providerFactory; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly ICancellationTokenProvider _cancellationTokenProvider; private readonly Dictionary _transactions; - private readonly Dictionary _connections; + private readonly Dictionary _connections; private readonly HashSet _registeredTransactions; private readonly HashSet _registeredConnections; private readonly object _lock = new object(); @@ -34,10 +40,12 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency public bool IsDisposed { get; private set; } - public MsDynamicDataRepository( + protected DynamicDataRepository( + DbProviderFactory providerFactory, IUnitOfWorkManager unitOfWorkManager, ICancellationTokenProvider cancellationTokenProvider) { + _providerFactory = providerFactory; _unitOfWorkManager = unitOfWorkManager; _cancellationTokenProvider = cancellationTokenProvider; @@ -53,10 +61,10 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency return $"Dapper_{uowId}_{_instanceId}_{cs}"; } - private async Task GetOrCreateConnectionAsync(string cs) + private async Task GetOrCreateConnectionAsync(string cs) { var key = BuildKey(cs); - SqlConnection connection; + DbConnection connection; lock (_lock) { @@ -66,7 +74,8 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency } else { - connection = new SqlConnection(cs); + connection = _providerFactory.CreateConnection(); + connection.ConnectionString = cs; _connections[key] = connection; } } @@ -93,7 +102,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency _unitOfWorkManager.Current.OnCompleted(async () => { - SqlConnection conn = null; + DbConnection conn = null; lock (_lock) { if (_connections.TryGetValue(key, out conn)) @@ -127,7 +136,7 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency return connection; } - private async Task GetOrCreateTransactionAsync(SqlConnection con, string cs) + private async Task GetOrCreateTransactionAsync(DbConnection con, string cs) { var key = BuildKey(cs); @@ -546,3 +555,19 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency } } +/// +/// Sağlayıcı başına DI kaydı: ABP'nin geleneksel kaydı (ve interceptor'ı) +/// anahtar başına bir gerçekleme tipi ister; fabrika ile kayıt interceptor'ı düşürürdü. +/// +[ExposeKeyedService("Ms")] +public class MsDynamicDataRepository( + IUnitOfWorkManager unitOfWorkManager, + ICancellationTokenProvider cancellationTokenProvider) + : DynamicDataRepository(SqlClientFactory.Instance, unitOfWorkManager, cancellationTokenProvider), IScopedDependency; + +[ExposeKeyedService("Pg")] +public class PgDynamicDataRepository( + IUnitOfWorkManager unitOfWorkManager, + ICancellationTokenProvider cancellationTokenProvider) + : DynamicDataRepository(NpgsqlFactory.Instance, unitOfWorkManager, cancellationTokenProvider), IScopedDependency; + diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/PgDynamicDataRepository.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/PgDynamicDataRepository.cs deleted file mode 100644 index af788b98..00000000 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/PgDynamicDataRepository.cs +++ /dev/null @@ -1,550 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Globalization; -using System.Linq; -using System.Text.Json; -using System.Threading.Tasks; -using Dapper; -using Npgsql; -using Sozsoft.Platform.DynamicData; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Threading; -using Volo.Abp.Uow; - -namespace Sozsoft.Platform.Domain.DynamicData; - -[ExposeKeyedService("Pg")] -public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable -{ - private readonly IUnitOfWorkManager _unitOfWorkManager; - private readonly ICancellationTokenProvider _cancellationTokenProvider; - - private readonly Dictionary _transactions; - private readonly Dictionary _connections; - private readonly HashSet _registeredTransactions; - private readonly HashSet _registeredConnections; - private readonly object _lock = new object(); - - // Bağlantı ve transaction bu örneğe aittir ve kapsamı kapanınca dispose edilir; aynı UoW içinde - // ikinci bir DI kapsamı (ör. form raporunun alt form raporları) aynı anahtarla AddTransactionApi - // çağırıp UoW'yi reddettirmesin diye anahtar örneğe özgüdür. - private readonly string _instanceId = Guid.NewGuid().ToString("N"); - - public bool IsDisposed { get; private set; } - - public PgDynamicDataRepository( - IUnitOfWorkManager unitOfWorkManager, - ICancellationTokenProvider cancellationTokenProvider) - { - _unitOfWorkManager = unitOfWorkManager; - _cancellationTokenProvider = cancellationTokenProvider; - _transactions = []; - _connections = []; - _registeredTransactions = []; - _registeredConnections = []; - } - - private string BuildKey(string cs) - { - var uowId = _unitOfWorkManager.Current?.GetHashCode() ?? 0; - return $"Dapper_{uowId}_{_instanceId}_{cs}"; - } - - private async Task GetOrCreateConnectionAsync(string cs) - { - var key = BuildKey(cs); - NpgsqlConnection connection; - - lock (_lock) - { - if (_connections.TryGetValue(key, out connection)) - { - // varsa aynı connection'ı kullan - } - else - { - connection = new NpgsqlConnection(cs); - _connections[key] = connection; - } - } - - // Lock dışında state yönetimi - if (connection.State == ConnectionState.Broken) - { - connection.Close(); - } - - if (connection.State == ConnectionState.Closed) - { - await connection.OpenAsync(_cancellationTokenProvider.FallbackToProvider(default)); - } - - // UoW tamamlandığında connection'ı kapatmak için tek seferlik kayıt - if (_unitOfWorkManager.Current != null) - { - lock (_lock) - { - if (!_registeredConnections.Contains(key)) - { - _registeredConnections.Add(key); - - _unitOfWorkManager.Current.OnCompleted(async () => - { - NpgsqlConnection conn = null; - lock (_lock) - { - if (_connections.TryGetValue(key, out conn)) - { - _connections.Remove(key); - } - - _registeredConnections.Remove(key); - } - - if (conn != null) - { - try - { - if (conn.State != ConnectionState.Closed) - { - await conn.CloseAsync(); - } - conn.Dispose(); - } - catch - { - // ignore - } - } - }); - } - } - } - - return connection; - } - - private async Task GetOrCreateTransactionAsync(NpgsqlConnection con, string cs) - { - var key = BuildKey(cs); - - lock (_lock) - { - if (_transactions.TryGetValue(key, out var existing)) - { - if (existing?.Connection != null && - existing.Connection == con && - existing.Connection.State == ConnectionState.Open) - { - return existing; - } - - try { existing?.Dispose(); } catch { } - _transactions.Remove(key); - } - } - - var newTransaction = await con.BeginTransactionAsync(_cancellationTokenProvider.FallbackToProvider(default)); - bool shouldRegister = false; - - lock (_lock) - { - _transactions[key] = newTransaction; - - if (!_registeredTransactions.Contains(key)) - { - _registeredTransactions.Add(key); - shouldRegister = true; - } - } - - if (shouldRegister && _unitOfWorkManager.Current != null) - { - _unitOfWorkManager.Current.AddTransactionApi( - key, - new DapperTransactionApi(newTransaction, _cancellationTokenProvider) - ); - - _unitOfWorkManager.Current.OnCompleted(() => - { - lock (_lock) - { - _transactions.Remove(key); - _registeredTransactions.Remove(key); - } - return Task.CompletedTask; - }); - } - - return newTransaction; - } - - // ------------------ Dapper metotları ------------------ - - public virtual async Task> QueryAsync(string sql, string cs, Dictionary parameters = null) - { - var param = CreateDynamicParameters(parameters); - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - var result = await dbConnection.QueryAsync(sql, param, transaction); - return result.AsList(); - } - - public virtual async Task> QueryAsync(string sql, string cs, Dictionary parameters = null) - { - var param = CreateDynamicParameters(parameters); - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.QueryAsync(sql, param, transaction); - } - - public virtual async Task QuerySingleAsync(string sql, string cs, Dictionary parameters = null) - { - var param = CreateDynamicParameters(parameters); - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.QuerySingleAsync(sql, param, transaction); - } - - public virtual async Task ExecuteScalarAsync(string sql, string cs, Dictionary parameters = null) - { - var param = CreateDynamicParameters(parameters); - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.ExecuteScalarAsync(sql, param, transaction); - } - - public virtual async Task GetSingleField(string cs, string tableName, string id, string selectFieldName, string idFieldName = "Id") - { - var param = new { ID = id }; - var sql = $"select {selectFieldName} from {tableName} where {idFieldName} = @ID "; - - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.QueryFirstAsync(sql, param, transaction); - } - - public virtual async Task UpdateField(string cs, string tableName, string id, string updateFieldName, string value, string idFieldName = "Id") - { - var param = new { ID = id }; - var sql = $"update {tableName} set {updateFieldName} = '{value}' where {idFieldName} = @ID "; - - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.ExecuteAsync(sql, param, transaction); - } - - public virtual async Task ExecuteAsync(string sql, string cs, Dictionary parameters = null) - { - var param = CreateDynamicParameters(parameters); - var dbConnection = await GetOrCreateConnectionAsync(cs); - var transaction = await GetOrCreateTransactionAsync(dbConnection, cs); - - return await dbConnection.ExecuteAsync(sql, param, transaction); - } - - private static DynamicParameters CreateDynamicParameters(Dictionary parameters) - { - var dynamicParameters = new DynamicParameters(); - - if (parameters == null) - { - return dynamicParameters; - } - - foreach (var parameter in parameters) - { - dynamicParameters.Add(parameter.Key, NormalizeParameterValue(parameter.Value)); - } - - return dynamicParameters; - } - - private static object NormalizeParameterValue(object value) - { - if (value == null || value == DBNull.Value) - { - return value; - } - - if (value is JsonElement jsonElement) - { - return NormalizeJsonElement(jsonElement); - } - - if (value is Array array && value is not byte[]) - { - return NormalizeArrayParameter(array); - } - - return value; - } - - private static object NormalizeArrayParameter(Array values) - { - var normalizedValues = values - .Cast() - .Select(NormalizeParameterValue) - .Where(value => value != null && value != DBNull.Value) - .ToArray(); - - if (normalizedValues.Length == 0) - { - return Array.Empty(); - } - - if (TryBuildGuidArray(normalizedValues, out var guidValues)) - { - return guidValues; - } - - if (TryBuildIntArray(normalizedValues, out var intValues)) - { - return intValues; - } - - if (TryBuildLongArray(normalizedValues, out var longValues)) - { - return longValues; - } - - if (TryBuildDecimalArray(normalizedValues, out var decimalValues)) - { - return decimalValues; - } - - if (TryBuildBoolArray(normalizedValues, out var boolValues)) - { - return boolValues; - } - - if (TryBuildDateTimeOffsetArray(normalizedValues, out var dateTimeOffsetValues)) - { - return dateTimeOffsetValues; - } - - var stringValues = normalizedValues.Select(value => value.ToString()).ToArray(); - - if (stringValues.Length == 1 && stringValues[0]?.Contains(PlatformConsts.MultiValueDelimiter) == true) - { - return stringValues[0].Split(PlatformConsts.MultiValueDelimiter, StringSplitOptions.RemoveEmptyEntries); - } - - return stringValues; - } - - private static object NormalizeJsonElement(JsonElement value) - { - return value.ValueKind switch - { - JsonValueKind.String => value.GetString(), - JsonValueKind.Number when value.TryGetInt32(out var intValue) => intValue, - JsonValueKind.Number when value.TryGetInt64(out var longValue) => longValue, - JsonValueKind.Number when value.TryGetDecimal(out var decimalValue) => decimalValue, - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.Null => null, - JsonValueKind.Undefined => null, - _ => value.ToString() - }; - } - - private static bool TryBuildGuidArray(object[] values, out Guid[] result) - { - result = new Guid[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is Guid guidValue) - { - result[i] = guidValue; - continue; - } - - if (!Guid.TryParse(values[i]?.ToString(), out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - private static bool TryBuildIntArray(object[] values, out int[] result) - { - result = new int[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is int intValue) - { - result[i] = intValue; - continue; - } - - if (!int.TryParse(values[i]?.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - private static bool TryBuildLongArray(object[] values, out long[] result) - { - result = new long[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is long longValue) - { - result[i] = longValue; - continue; - } - - if (!long.TryParse(values[i]?.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - private static bool TryBuildDecimalArray(object[] values, out decimal[] result) - { - result = new decimal[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is decimal decimalValue) - { - result[i] = decimalValue; - continue; - } - - if (!decimal.TryParse(values[i]?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - private static bool TryBuildBoolArray(object[] values, out bool[] result) - { - result = new bool[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is bool boolValue) - { - result[i] = boolValue; - continue; - } - - if (!bool.TryParse(values[i]?.ToString(), out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - private static bool TryBuildDateTimeOffsetArray(object[] values, out DateTimeOffset[] result) - { - result = new DateTimeOffset[values.Length]; - - for (var i = 0; i < values.Length; i++) - { - if (values[i] is DateTimeOffset dateTimeOffsetValue) - { - result[i] = dateTimeOffsetValue; - continue; - } - - if (values[i] is DateTime dateTimeValue) - { - result[i] = dateTimeValue; - continue; - } - - if (!DateTimeOffset.TryParse(values[i]?.ToString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result[i])) - { - result = null; - return false; - } - } - - return true; - } - - // ------------------ Dispose ------------------ - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (IsDisposed) - { - return; - } - - if (disposing) - { - lock (_lock) - { - foreach (var tx in _transactions.Values) - { - try { tx?.Dispose(); } catch { } - } - _transactions.Clear(); - _registeredTransactions.Clear(); - - foreach (var connection in _connections.Values) - { - try - { - if (connection != null) - { - if (connection.State != ConnectionState.Closed) - { - connection.Close(); - } - connection.Dispose(); - } - } - catch - { - // ignore - } - } - _connections.Clear(); - _registeredConnections.Clear(); - } - } - - IsDisposed = true; - } -} - diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/SpRepository.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/SpRepository.cs index f4d19d65..9b7c7841 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/SpRepository.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/DynamicData/SpRepository.cs @@ -6,26 +6,22 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Npgsql; +using Sozsoft.Platform.Data; using Sozsoft.Platform.DynamicData; -using Sozsoft.Platform.EntityFrameworkCore; using Sozsoft.Platform.Enums; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.Uow; namespace Sozsoft.Platform.Domain.DynamicData; +/// +/// Zamanlanmış işin öncesi/sonrası saklı yordamını çağırır. Kendi bağlantısını açtığı için +/// unit of work'e girmez: ABP UoW yalnızca EF bağlantısını yönetir, buradaki komutu göremezdi. +/// public class SpRepository : ISpRepository { - public IAbpLazyServiceProvider LazyServiceProvider { get; set; } - public IDbContextProvider DbContextProvider { get; } public IDynamicDataManager DynamicDataManager { get; } - public SpRepository( - IDbContextProvider dbContextProvider, - IDynamicDataManager dynamicDataManager) + public SpRepository(IDynamicDataManager dynamicDataManager) { - DbContextProvider = dbContextProvider; DynamicDataManager = dynamicDataManager; } @@ -36,21 +32,23 @@ public class SpRepository : ISpRepository { var (_, connectionString, dataSourceType) = await DynamicDataManager.GetAsync(false, dataSourceCode); - using var uow = LazyServiceProvider.LazyGetRequiredService().Begin(); + // Yordam adı parametre olarak bağlanamaz; iki sağlayıcıda da aynı beyaz listeden geçer. + if (!SqlIdentifier.TryParseQualified(sp, dataSourceType, out var schema, out var name)) + throw new ArgumentException($"Invalid procedure name: {sp}", nameof(sp)); await using var connection = CreateDbConnection(dataSourceType, connectionString); await connection.OpenAsync(); // PostgreSQL'de prosedür `CALL`, fonksiyon `SELECT` ile çağrılır; tür adından bilinmez, katalogdan okunur. var isPostgreSqlProcedure = dataSourceType == DataSourceTypeEnum.Postgresql - && await IsPostgreSqlProcedureAsync(connection, PostgreSqlRoutineName(sp)); + && await IsPostgreSqlProcedureAsync(connection, schema, name); - await using var command = CreateDbCommand(connection, sp, dataSourceType, parameters, isPostgreSqlProcedure); + await using var command = CreateDbCommand(connection, schema, name, dataSourceType, parameters, isPostgreSqlProcedure); return await ExecuteQueryAsync(command); } - private DbConnection CreateDbConnection(DataSourceTypeEnum dataSourceType, string connectionString) + private static DbConnection CreateDbConnection(DataSourceTypeEnum dataSourceType, string connectionString) { return dataSourceType switch { @@ -60,25 +58,39 @@ public class SpRepository : ISpRepository }; } - private static async Task IsPostgreSqlProcedureAsync(DbConnection connection, string name) + private static async Task IsPostgreSqlProcedureAsync(DbConnection connection, string schema, string name) { await using var command = connection.CreateCommand(); - command.CommandText = "SELECT p.prokind FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'public' AND p.proname = @name LIMIT 1"; + command.CommandText = "SELECT p.prokind FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = @schema AND p.proname = @name LIMIT 1"; + command.Parameters.Add(new NpgsqlParameter("@schema", schema)); command.Parameters.Add(new NpgsqlParameter("@name", name)); return await command.ExecuteScalarAsync() is char kind && kind == 'p'; } - private DbCommand CreateDbCommand(DbConnection connection, string sp, DataSourceTypeEnum dataSourceType, Dictionary parameters, bool isPostgreSqlProcedure) + private static DbCommand CreateDbCommand( + DbConnection connection, + string schema, + string name, + DataSourceTypeEnum dataSourceType, + Dictionary parameters, + bool isPostgreSqlProcedure) { var command = connection.CreateCommand(); command.CommandTimeout = 300; // 5 dakika timeout command.CommandType = CommandType.Text; + var routine = SqlIdentifier.QuoteQualified(schema, name, dataSourceType); + var arguments = parameters != null && parameters.Count > 0 + ? string.Join(", ", parameters.Keys.Select((k, i) => $"@p{i}")) + : string.Empty; + command.CommandText = dataSourceType switch { - DataSourceTypeEnum.Mssql => BuildSqlForMssql(sp, parameters), - DataSourceTypeEnum.Postgresql => BuildSqlForPostgresql(sp, parameters, isPostgreSqlProcedure), + DataSourceTypeEnum.Mssql => $"EXEC {routine} {arguments}".TrimEnd(), + DataSourceTypeEnum.Postgresql => isPostgreSqlProcedure + ? $"CALL {routine}({arguments})" + : $"SELECT * FROM {routine}({arguments})", _ => throw new Exception("Unsupported database type") }; @@ -86,38 +98,7 @@ public class SpRepository : ISpRepository return command; } - private string BuildSqlForMssql(string sp, Dictionary parameters) - { - return parameters != null && parameters.Count > 0 - ? $"EXEC {sp} {string.Join(", ", parameters.Keys.Select((k, i) => $"@p{i}"))}" - : $"EXEC {sp}"; - } - - private static string BuildSqlForPostgresql(string sp, Dictionary parameters, bool isProcedure) - { - var arguments = parameters != null && parameters.Count > 0 - ? string.Join(", ", parameters.Keys.Select((k, i) => $"@p{i}")) - : string.Empty; - - var routine = $"public.\"{PostgreSqlRoutineName(sp)}\"({arguments})"; - - return isProcedure ? $"CALL {routine}" : $"SELECT * FROM {routine}"; - } - - /// - /// Rutin adı SQL Server'daki gibi PascalCase yazılır (`Hr_S_TimesheetNightly`); PostgreSQL tırnaksız adı - /// küçük harfe indirdiği için ad tırnaklanarak kullanılır. `dbo.` öneki SQL Server şemasıdır, PG'de `public`. - /// - private static string PostgreSqlRoutineName(string sp) - { - var name = sp.Split('.').Last().Trim('[', ']', '"'); - if (name.Length == 0 || !name.All(c => char.IsLetterOrDigit(c) || c == '_')) - throw new ArgumentException($"Invalid procedure name: {sp}", nameof(sp)); - - return name; - } - - private void AddParameters(DbCommand command, DataSourceTypeEnum dataSourceType, Dictionary parameters) + private static void AddParameters(DbCommand command, DataSourceTypeEnum dataSourceType, Dictionary parameters) { if (parameters == null) return; @@ -136,7 +117,7 @@ public class SpRepository : ISpRepository } } - private async Task>> ExecuteQueryAsync(DbCommand command) + private static async Task>> ExecuteQueryAsync(DbCommand command) { var results = new List>(); await using var reader = await command.ExecuteReaderAsync(); @@ -154,29 +135,3 @@ public class SpRepository : ISpRepository return results; } } - - -/* SP Params -param = new SqlParameter[] { - new SqlParameter() { - ParameterName = "@Name", - SqlDbType = System.Data.SqlDbType.VarChar, - Size = 100, - Direction = System.Data.ParameterDirection.Input, - Value = "Tony" - }, - new SqlParameter() { - ParameterName = "@Standard", - SqlDbType = System.Data.SqlDbType.Int, - Direction = System.Data.ParameterDirection.Input, - Value = 10 - }, - new SqlParameter() { - ParameterName = "@TotalStudents", - SqlDbType = System.Data.SqlDbType.Int, - Direction = System.Data.ParameterDirection.Output, - }}; -int affectedRows = await context.Database.ExecuteSqlRawAsync("[dbo].[sp_GetStudentsNew] @Name, @Standard, @TotalStudents out", param); -int totalStudents = Convert.ToInt32(param[2].Value); -*/ - diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sequences/EfCoreSequenceRepository.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sequences/EfCoreSequenceRepository.cs new file mode 100644 index 00000000..5ad76b81 --- /dev/null +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sequences/EfCoreSequenceRepository.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Sozsoft.Platform.Entities; +using Sozsoft.Platform.EntityFrameworkCore; +using Sozsoft.Platform.Sequences; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Sozsoft.Platform.Sequences; + +/// +/// Numaratör satırı iki sağlayıcıda da aynı şekilde ilerletilir: izlemesiz okuma + koşullu +/// UPDATE … WHERE NextNumber = @okunan. Satır kilidi (UPDLOCK/FOR UPDATE) sağlayıcıya +/// özgü olduğu için kullanılmaz; yarışan istekten biri 0 satır etkiler ve yeniden dener. +/// +public class EfCoreSequenceRepository : EfCoreRepository, ISequenceRepository +{ + public EfCoreSequenceRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindActiveAsync(string name, CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + + // İzlenmez: entity üzerinde hesaplanan yeni değer UoW kapanışında ikinci bir UPDATE üretmesin. + return await dbSet + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Name == name && x.IsActive, GetCancellationToken(cancellationToken)); + } + + public async Task TryAdvanceAsync( + Guid id, + long expectedNextNumber, + string? expectedPeriodKey, + long nextNumber, + string? periodKey, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + + var affected = await dbSet + .Where(x => x.Id == id && x.NextNumber == expectedNextNumber && x.LastPeriodKey == expectedPeriodKey) + .ExecuteUpdateAsync( + setters => setters + .SetProperty(x => x.NextNumber, nextNumber) + .SetProperty(x => x.LastPeriodKey, periodKey), + GetCancellationToken(cancellationToken)); + + return affected == 1; + } +} diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj index f37760bf..46164d4d 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj @@ -24,7 +24,7 @@ - + diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs index 4c1f2af8..4ff5c23d 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs @@ -54,8 +54,7 @@ public class DynamicAssemblyRegistrationService : BackgroundService _pendingRegistrations.Enqueue(new AssemblyRegistrationRequest { Assembly = assembly, - AssemblyName = assemblyName, - RequestTime = DateTime.Now + AssemblyName = assemblyName }); } } @@ -69,8 +68,7 @@ public class DynamicAssemblyRegistrationService : BackgroundService { _pendingUnregistrations.Enqueue(new AssemblyUnregistrationRequest { - ServiceName = serviceName, - RequestTime = DateTime.Now + ServiceName = serviceName }); } } @@ -339,12 +337,10 @@ public class DynamicAssemblyRegistrationService : BackgroundService { public Assembly Assembly { get; set; } public string AssemblyName { get; set; } - public DateTime RequestTime { get; set; } } private class AssemblyUnregistrationRequest { public string ServiceName { get; set; } - public DateTime RequestTime { get; set; } } } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/CaptchaMiddleware.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/CaptchaMiddleware.cs index c7a580d8..1eb13bb1 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/CaptchaMiddleware.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/CaptchaMiddleware.cs @@ -16,11 +16,7 @@ public class CaptchaMiddleware : IMiddleware, ITransientDependency private readonly ICaptchaManager captchaManager; private readonly IStringLocalizer localizer; - public CaptchaMiddleware(ICaptchaManager captchaManager, - IStringLocalizer localizer - - - ) + public CaptchaMiddleware(ICaptchaManager captchaManager, IStringLocalizer localizer) { this.captchaManager = captchaManager; this.localizer = localizer; @@ -80,36 +76,4 @@ public class CaptchaMiddleware : IMiddleware, ITransientDependency context.Response.StatusCode = (int)HttpStatusCode.BadRequest; await context.Response.WriteAsync(localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]); } - - - // public async Task InvokeAsync(HttpContext context, RequestDelegate next) - // { - // var attribute = context.GetEndpoint()?.Metadata?.Any(a => a is CaptchaAttribute); - // //var endpoint = context.Features.Get()?.Endpoint; - // //var attribute = endpoint?.Metadata.GetMetadata(); - // if (attribute == true) - // { - // context.Request.EnableBuffering(); - // var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync(); - // var jsonDocument = JsonDocument.Parse(requestBody); - // if (jsonDocument.RootElement.TryGetProperty("captchaResponse", out var response)) - // { - // var result = await captchaManager.VerifyCaptchaAsync(response.GetString()); - // if (result == false) - // { - // context.Response.Clear(); - // context.Response.ContentType = "text/plain"; - // context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - // await context.Response.WriteAsync(localizer[PlatformConsts.AbpIdentity.User.CaptchaWrongCode]); - // return; - // } - // } - // context.Request.Body.Position = 0; - // } - - // await next(context); - // } - - } - diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ChangeLogStartupService.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ChangeLogStartupService.cs index 19b7fc50..3bb0ed81 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ChangeLogStartupService.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Extensions/ChangeLogStartupService.cs @@ -107,7 +107,7 @@ public class ChangeLogStartupService : IHostedService await using var stream = File.OpenRead(path); var data = await JsonSerializer.DeserializeAsync( stream, - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + PlatformJsonOptions.CaseInsensitive); var items = data?.ChangeLogs ?? []; if (items.Count == 0) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index bd01f5b9..9ba1c014 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -245,7 +245,7 @@ public class PlatformHttpApiHostModule : AbpModule }); options.ConventionalControllers.Create(typeof(LanguagesApplicationModule).Assembly); options.ConventionalControllers.Create(typeof(SettingsApplicationModule).Assembly); - options.ConventionalControllers.Create(typeof(ErpMailQueueModule).Assembly); + options.ConventionalControllers.Create(typeof(SozsoftMailQueueModule).Assembly); options.ConventionalControllers.Create(typeof(NotificationApplicationModule).Assembly); options.ConventionalControllers.Create(typeof(SqlQueryManagerApplicationModule).Assembly); options.ChangeControllerModelApiExplorerGroupName = false; @@ -307,23 +307,7 @@ public class PlatformHttpApiHostModule : AbpModule private void ConfigureIdentity(IConfiguration configuration) { - //Veritabanından yonetildigi icin bunlar kapatildi - //Configure(options => - //{ - // options.SignIn.RequireConfirmedEmail = true; - // options.SignIn.RequireConfirmedAccount = true; - // options.SignIn.RequireConfirmedPhoneNumber = false; - // options.User.RequireUniqueEmail = true; - // options.Lockout.AllowedForNewUsers = true; - // options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); - // options.Lockout.MaxFailedAccessAttempts = 5; - // options.Password.RequiredLength = 6; - // options.Password.RequiredUniqueChars = 0; - // options.Password.RequireNonAlphanumeric = false; - // options.Password.RequireLowercase = false; - // options.Password.RequireUppercase = false; - // options.Password.RequireDigit = false; - //}); + // IdentityOptions burada yapılandırılmaz; kurallar veritabanından (PlatformIdentityOptions) yönetilir. Configure(configuration.GetSection(PlatformConsts.AbpIdentity.GroupName)); } @@ -390,7 +374,6 @@ public class PlatformHttpApiHostModule : AbpModule #pragma warning restore CS0162 } - private void ConfigureBlobStoring(IConfiguration configuration) { Configure(options => @@ -598,5 +581,4 @@ public class PlatformHttpApiHostModule : AbpModule }); }); } - } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs index 1d753f0f..47bbe2a6 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs @@ -5,8 +5,11 @@ using System.Linq; using DevExpress.XtraPrinting; using DevExpress.XtraReports.UI; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Sozsoft.Languages.Entities; using Sozsoft.Platform.EntityFrameworkCore; +using Sozsoft.Platform.Localization; +using Volo.Abp.Timing; using Volo.Abp.Users; namespace Sozsoft.Reports.PredefinedReports; @@ -27,6 +30,21 @@ public partial class DynamicReport : XtraReport _scopeFactory = scopeFactory; } + /// + /// DI disinda calisan rapor yardimcilari (ifade fonksiyonlari, sayfa bilgisi) icin localizer. + /// Kapsam kapansa da localizer singleton fabrikaya dayandigi icin kullanilabilir kalir. + /// + internal static IStringLocalizer ResolveLocalizer() + { + if (_scopeFactory == null) + { + return null; + } + + using var scope = _scopeFactory.CreateScope(); + return scope.ServiceProvider.GetRequiredService>(); + } + public DynamicReport() { InitializeComponent(); @@ -37,6 +55,8 @@ public partial class DynamicReport : XtraReport // Language verilerini yükle LoadLanguageParameters(); + // Tasarimci dosyasindaki sabit metin uretilmis koddur; sayfa bilgisi calisma aninda dilden gelir. + pageInfo2.TextFormatString = DynamicReportImageHelper.GetPageInfoFormat(); AddHeaderGeneratedAt(); AddFooterCurrentUserName(); AddFooterLine(); @@ -119,10 +139,11 @@ public partial class DynamicReport : XtraReport SetParameter("name", language.DisplayName); } - SetParameter("date", DynamicReportImageHelper.FormatReportDate(DateTime.Now)); + SetParameter("date", DynamicReportImageHelper.FormatReportDate(scope.ServiceProvider.GetRequiredService().Now)); } catch { + // Kapsam/dil okunamadıysa rapor yine üretilir; saat uygulama saatine (Clock) erişilemediği için sistem saatidir. SetParameter("date", DynamicReportImageHelper.FormatReportDate(DateTime.Now)); } } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportGridStateHelper.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportGridStateHelper.cs index 68677f5d..50d42778 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportGridStateHelper.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportGridStateHelper.cs @@ -407,10 +407,7 @@ internal static class DynamicReportGridStateHelper try { - var grouping = JsonSerializer.Deserialize(field.GroupingJson, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + var grouping = JsonSerializer.Deserialize(field.GroupingJson, PlatformJsonOptions.CaseInsensitive); return grouping?.GroupIndex; } catch (JsonException) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs index 8c2676a1..fd0e5b78 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs @@ -57,6 +57,11 @@ internal static class DynamicReportImageHelper private const int MaxCachedRemoteImages = 256; private static readonly ConcurrentDictionary RemoteImageCache = new(StringComparer.Ordinal); + // Rapor ifadeleri (DevExpress custom function) ve sayfa bilgisi DI disinda calisir; metinler + // DynamicReport'un kapsam fabrikasindan bir kez cozulen localizer ile aktif dilden okunur. + private static IStringLocalizer _localizer; + private static IStringLocalizer Localizer => _localizer ??= DynamicReport.ResolveLocalizer(); + static DynamicReportImageHelper() { RegisterImageFunction(new ReportImageUrlFunction()); @@ -286,7 +291,7 @@ internal static class DynamicReportImageHelper controls.Add(new XRPageInfo { PageInfo = PageInfo.DateTime, - TextFormatString = "{0:dd.MM.yyyy HH:mm}", + TextFormatString = "{0:g}", BoundsF = new System.Drawing.RectangleF(pageWidth - width, 0, width, 18), Font = DynamicReportFontHelper.CreateHeaderFont(DevExpress.Drawing.DXFontStyle.Regular), TextAlignment = TextAlignment.MiddleRight, @@ -294,6 +299,19 @@ internal static class DynamicReportImageHelper }); } + /// Sayfa bilgisi metni (App.Reports.PageOf); localizer yoksa dilsiz {0} / {1}. + public static string GetPageInfoFormat() + { + return LocalizeOrDefault("App.Reports.PageOf", "{0} / {1}"); + } + + /// Kapsam fabrikasi kurulmamissa (localizer yok) dilsiz varsayilan doner. + private static string LocalizeOrDefault(string key, string fallback) + { + var localizer = Localizer; + return localizer == null ? fallback : Localize(localizer, key, fallback); + } + public static void AddCurrentUserNameToFooter(XRControlCollection controls, string userName, float pageWidth) { if (string.IsNullOrWhiteSpace(userName)) @@ -880,9 +898,8 @@ internal static class DynamicReportImageHelper private static string FormatBoolean(bool value) { - return CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("tr", StringComparison.OrdinalIgnoreCase) - ? value ? "Evet" : "Hayır" - : value ? "Yes" : "No"; + var key = value ? "App.Platform.Yes" : "App.Platform.No"; + return LocalizeOrDefault(key, value.ToString(CultureInfo.CurrentCulture)); } private static Dictionary CreateFieldCaptionMap( @@ -971,7 +988,9 @@ internal static class DynamicReportImageHelper items[0].GetString() == "!") { var text = FormatFilterElement(items[1], fieldCaptions); - return string.IsNullOrWhiteSpace(text) ? null : "değil (" + text + ")"; + return string.IsNullOrWhiteSpace(text) + ? null + : string.Format(CultureInfo.CurrentCulture, LocalizeOrDefault("App.Reports.Filter.Not", "!({0})"), text); } var parts = new List(); @@ -1011,25 +1030,24 @@ internal static class DynamicReportImageHelper private static string FormatFilterOperation(string operation) { - return operation?.ToLowerInvariant() switch + var key = operation?.ToLowerInvariant() switch { - "=" => "=", - "<>" => "eşit değil", - ">" => ">", - ">=" => ">=", - "<" => "<", - "<=" => "<=", - "contains" => "içerir", - "notcontains" => "içermez", - "startswith" => "ile başlar", - "endswith" => "ile biter", - "between" => "arasında", - "anyof" => "şunlardan biri", - "noneof" => "şunlardan hiçbiri", - "isblank" => "boş", - "isnotblank" => "boş değil", - _ => operation + "<>" => "NotEqual", + "contains" => "Contains", + "notcontains" => "NotContains", + "startswith" => "StartsWith", + "endswith" => "EndsWith", + "between" => "Between", + "anyof" => "AnyOf", + "noneof" => "NoneOf", + "isblank" => "IsBlank", + "isnotblank" => "IsNotBlank", + _ => null }; + + // Simge isleclerin (=, <, >=) dili yoktur; adli islecler App.Reports.Filter.* anahtarindan okunur, + // localizer kurulmamissa DevExtreme token'i oldugu gibi yazilir. + return key == null ? operation : LocalizeOrDefault($"App.Reports.Filter.{key}", operation); } private static string FormatJsonValue(JsonElement value) @@ -1066,7 +1084,8 @@ internal static class DynamicReportImageHelper private static string FormatFilterConnector(string value) { - return string.Equals(value, "or", StringComparison.OrdinalIgnoreCase) ? "veya" : "ve"; + var isOr = string.Equals(value, "or", StringComparison.OrdinalIgnoreCase); + return LocalizeOrDefault(isOr ? "App.Reports.Filter.Or" : "App.Reports.Filter.And", isOr ? "or" : "and"); } private static bool IsBooleanField(string editorType, DbType dbType) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportLayoutHelper.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportLayoutHelper.cs index 9bd57f87..3751395e 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportLayoutHelper.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportLayoutHelper.cs @@ -221,7 +221,7 @@ internal static class DynamicReportLayoutHelper { controls.Add(new XRPageInfo { - TextFormatString = "Sayfa {0} / {1}", + TextFormatString = DynamicReportImageHelper.GetPageInfoFormat(), BoundsF = new System.Drawing.RectangleF(pageWidth / 2, 8, pageWidth / 2, 18), Font = DynamicReportFontHelper.CreateValueFont(8F), TextAlignment = TextAlignment.MiddleRight diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs index 2038076a..39374583 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Sozsoft.Platform; +using Sozsoft.Platform.Data; using Sozsoft.Platform.Entities; using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms; +using Sozsoft.Platform.Queries; namespace Sozsoft.Reports.PredefinedReports; @@ -98,7 +100,7 @@ internal static class DynamicReportSqlBuilder return selectQuery; } - var sourceAlias = QuoteSqlIdentifier(SourceAlias); + var sourceAlias = SqlIdentifier.QuoteAnsi(SourceAlias); var normalizedSql = NormalizeSql(selectQuery); var orderBy = ExtractOrderBy(normalizedSql); if (!string.IsNullOrWhiteSpace(orderBy)) @@ -156,7 +158,7 @@ internal static class DynamicReportSqlBuilder .Select(item => $"WHEN {DynamicReportImageHelper.ToSqlLiteral(item.Key, dataSourceType)} THEN {DynamicReportImageHelper.ToSqlLiteral(item.Value, dataSourceType)}"); return new LookupSelectPart( - $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}"); + $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {SqlIdentifier.QuoteAnsi(column.BindingName)}"); } private static LookupSelectPart CreateQueryLookupSelectPart( @@ -176,11 +178,11 @@ internal static class DynamicReportSqlBuilder } var (commonTableExpression, lookupSelectQuery) = SplitCommonTableExpression(lookupQuery); - var lookupAlias = QuoteSqlIdentifier(LookupAlias); - var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key"); - var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name"); + var lookupAlias = SqlIdentifier.QuoteAnsi(LookupAlias); + var valueExpr = SqlIdentifier.QuoteAnsi(column.Lookup.ValueExpr ?? "Key"); + var displayExpr = SqlIdentifier.QuoteAnsi(column.Lookup.DisplayExpr ?? "Name"); var sourceField = GetSourceField(column.Field.FieldName); - var resultAlias = QuoteSqlIdentifier(column.BindingName); + var resultAlias = SqlIdentifier.QuoteAnsi(column.BindingName); string CreateScalarQuery(string selectExpression) => dataSourceType == DataSourceTypeEnum.Mssql ? $"SELECT TOP 1 {selectExpression} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField}" @@ -190,7 +192,7 @@ internal static class DynamicReportSqlBuilder var colorPair = CreateColorPairExpression(column, lookupAlias, displayExpr, dataSourceType); if (colorPair != null) { - selectSql += $", ({CreateScalarQuery(colorPair)}) AS {QuoteSqlIdentifier(GetLookupColorBindingName(column.Field))}"; + selectSql += $", ({CreateScalarQuery(colorPair)}) AS {SqlIdentifier.QuoteAnsi(GetLookupColorBindingName(column.Field))}"; } return new LookupSelectPart(selectSql, commonTableExpression); @@ -211,7 +213,7 @@ internal static class DynamicReportSqlBuilder return null; } - var colorExpr = $"{lookupAlias}.{QuoteSqlIdentifier(colorField)}"; + var colorExpr = $"{lookupAlias}.{SqlIdentifier.QuoteAnsi(colorField)}"; return dataSourceType == DataSourceTypeEnum.Mssql ? $"CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX)) + N'{DynamicReportColorHelper.PairSeparator}' + COALESCE(CAST({colorExpr} AS NVARCHAR(64)), N'')" : $"CAST({lookupAlias}.{displayExpr} AS TEXT) || '{DynamicReportColorHelper.PairSeparator}' || COALESCE(CAST({colorExpr} AS TEXT), '')"; @@ -222,7 +224,7 @@ internal static class DynamicReportSqlBuilder DataSourceTypeEnum dataSourceType) { var sourceField = GetSourceField(column.Field.FieldName); - var resultAlias = QuoteSqlIdentifier(column.BindingName); + var resultAlias = SqlIdentifier.QuoteAnsi(column.BindingName); var values = string.Join( ", ", column.StaticLookupValues.Select(item => @@ -252,11 +254,11 @@ internal static class DynamicReportSqlBuilder DataSourceTypeEnum dataSourceType) { var (commonTableExpression, lookupSelectQuery) = SplitCommonTableExpression(lookupQuery); - var lookupAlias = QuoteSqlIdentifier(LookupAlias); - var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key"); - var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name"); + var lookupAlias = SqlIdentifier.QuoteAnsi(LookupAlias); + var valueExpr = SqlIdentifier.QuoteAnsi(column.Lookup.ValueExpr ?? "Key"); + var displayExpr = SqlIdentifier.QuoteAnsi(column.Lookup.DisplayExpr ?? "Name"); var sourceField = GetSourceField(column.Field.FieldName); - var resultAlias = QuoteSqlIdentifier(column.BindingName); + var resultAlias = SqlIdentifier.QuoteAnsi(column.BindingName); string CreateAggregateQuery(string itemExpression, string separator) => dataSourceType == DataSourceTypeEnum.Mssql ? $""" @@ -279,7 +281,7 @@ internal static class DynamicReportSqlBuilder var colorPair = CreateColorPairExpression(column, lookupAlias, displayExpr, dataSourceType); if (colorPair != null) { - selectSql += $", ({CreateAggregateQuery(colorPair, DynamicReportColorHelper.ItemSeparator)}) AS {QuoteSqlIdentifier(GetLookupColorBindingName(column.Field))}"; + selectSql += $", ({CreateAggregateQuery(colorPair, DynamicReportColorHelper.ItemSeparator)}) AS {SqlIdentifier.QuoteAnsi(GetLookupColorBindingName(column.Field))}"; } return new LookupSelectPart(selectSql, commonTableExpression); @@ -363,7 +365,7 @@ internal static class DynamicReportSqlBuilder private static string GetSourceField(string fieldName) { - return $"{QuoteSqlIdentifier(SourceAlias)}.{QuoteSqlIdentifier(fieldName)}"; + return $"{SqlIdentifier.QuoteAnsi(SourceAlias)}.{SqlIdentifier.QuoteAnsi(fieldName)}"; } private static string NormalizeLookupQuery(string lookupQuery) @@ -464,11 +466,6 @@ internal static class DynamicReportSqlBuilder return result; } - private static string QuoteSqlIdentifier(string identifier) - { - return $"\"{identifier.Replace("\"", "\"\"")}\""; - } - private sealed record LookupSelectPart(string SelectSql, string CommonTableExpression = null); } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs index 4bca3a31..3bfa62f0 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs @@ -12,6 +12,7 @@ using DevExpress.XtraReports.UI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using Sozsoft.Platform; using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.Entities; using Sozsoft.Platform.ListForms; @@ -497,10 +498,7 @@ public class DynamicTreeReport : XtraReport try { - return JsonSerializer.Deserialize(listForm.TreeOptionJson, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + return JsonSerializer.Deserialize(listForm.TreeOptionJson, PlatformJsonOptions.CaseInsensitive); } catch (JsonException) { diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj index 359df5ae..10dedb96 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Sozsoft.Platform.HttpApi.Host.csproj @@ -34,9 +34,9 @@ - + diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationMiddleware.cs b/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationMiddleware.cs index 4fa3e6b6..e9246b9c 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationMiddleware.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/TenantLocalizationMiddleware.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Http; using Sozsoft.Platform.Localization; +namespace Sozsoft.Platform; + public class TenantLocalizationMiddleware : IMiddleware { private readonly TenantLocalizationInitializer _initializer; diff --git a/configs/deployment/README.md b/configs/deployment/README.md index e1c9acce..6c2f8fff 100644 --- a/configs/deployment/README.md +++ b/configs/deployment/README.md @@ -215,21 +215,23 @@ Not: Eğer aşağıda kullanılan .sh dosyalarının içeriğinde bir güncellem ### App -DevOps Makinasında: -`./sozsoft-platform/configs/deployment/scripts/1-devops-api.sh` -`./sozsoft-platform/configs/deployment/scripts/1-devops-ui.sh dev/production` +DevOps Makinasında (api + migrator + ui imajlarını derler ve registry'ye iter): +`~/sozsoft-platform/configs/deployment/scripts/8-dev-build.sh` (dev) +`~/sozsoft-platform/configs/deployment/scripts/4-production-build.sh` (production) +Tek imaj için alt scriptler: `scripts/build/api.sh`, `scripts/build/migrator.sh`, `scripts/build/ui.sh dev|production`. -Dev / Prod Ortam Makinalarında: -`./sozsoft-platform/configs/deployment/scripts/2-dev-app.sh` +Dev / Prod Ortam Makinalarında (imajları çeker, api ve ui konteynerlerini yerinde yeniler): +`~/sozsoft-platform/configs/deployment/scripts/9-dev-deploy.sh` (dev) +`~/sozsoft-platform/configs/deployment/scripts/5-production-deploy.sh` (production) +İkisi de `scripts/deploy/app.sh {env}` çağırır: konteynerler önceden durdurulmaz, `pull` → `up -d`; +UI'nin eski sürüm chunk'ları `ui-assets` volume'unda kalır (bkz. `ui/README.md` › Konteyner). ### Migrator -DevOps Makinasında: -`./sozsoft-platform/configs/deployment/scripts/1-devops-migrator.sh` - -Dev / Prod Ortam Makinalarında: -`./sozsoft-platform/configs/deployment/scripts/2-dev-migrator.sh` -Seedleri çalıştırmak için sonuna "true" eklenir. Örn: `2-dev-migrator.sh true` +Dev / Prod Ortam Makinalarında (seed'ler açık): +`~/sozsoft-platform/configs/deployment/scripts/10-dev-migrator-true.sh` (dev) +`~/sozsoft-platform/configs/deployment/scripts/6-production-migrator-true.sh` (production) +Alt script: `scripts/migrator/migrator.sh {seed:true|false} {env}`. ## 6.2. Manuel Deployment diff --git a/configs/deployment/Readme_Production.md b/configs/deployment/Readme_Production.md index b61196df..e9857639 100644 --- a/configs/deployment/Readme_Production.md +++ b/configs/deployment/Readme_Production.md @@ -65,5 +65,26 @@ - cd ~/sozsoft-platform/configs/deployment - docker compose -f docker-compose-data.yml up -d - cd ~/sozsoft-platform/configs/deployment/scripts - - ./2-app.sh production - - ./2-migrator.sh true production + - ./5-production-deploy.sh # deploy/app.sh production: pull -> up -d (konteynerler önceden durdurulmaz) + - ./6-production-migrator-true.sh + +### UI konteyneri ve host nginx + +UI imajı `vite preview` yerine nginx ile servis eder (`ui/Sozsoft.Platform.Ui.Dockerfile`, +`ui/nginx/default.conf`). Cache-Control başlıkları, SPA fallback'i ve `sitemap.xml`/`robots.txt` +proxy'si konteynerin içindedir; eski sürüm chunk'ları `ui-assets` volume'unda birikir (deploy sonrası +açık sekmeler "eksik js" hatası almaz; `docker logs sozsoft-ui | grep arşiv`). + +Host'taki ters vekil (`configs/deployment/configs/nginx.conf`) başlıkları ezmez; `sozsoft.com` +bloğundaki eski `location = /index.html` / `location = /sw.js` blokları kaldırıldı. Sunucudaki kopya +depodan farklıysa yalnızca o iki bloğu silmek yeterlidir: + + - sudo cp ~/sozsoft-platform/configs/deployment/configs/nginx.conf /etc/nginx/sites-available/ + - sudo nginx -t && sudo systemctl reload nginx + +Doğrulama: + + - curl -sI https://sozsoft.com/index.html | grep -i cache-control # no-store + - curl -sI https://sozsoft.com/sw.js | grep -i cache-control # no-store + - curl -sI https://sozsoft.com/assets/js/ | grep -i cache-control # immutable + - curl -sI https://sozsoft.com/sitemap.xml | head -1 # 200 (API üretir) diff --git a/configs/deployment/configs/nginx.conf b/configs/deployment/configs/nginx.conf index 91963f8f..4352ad8b 100644 --- a/configs/deployment/configs/nginx.conf +++ b/configs/deployment/configs/nginx.conf @@ -272,20 +272,10 @@ server { ignore_invalid_headers off; large_client_header_buffers 4 16k; - location = /index.html { - proxy_pass http://127.0.0.1:3002; - include /etc/nginx/proxy_params; - add_header Cache-Control "no-store, no-cache, must-revalidate"; - } - - location = /sw.js { - proxy_pass http://127.0.0.1:3002; - include /etc/nginx/proxy_params; - add_header Cache-Control "no-store, no-cache, must-revalidate"; - } - + # Cache-Control basliklari UI konteynerindeki nginx'ten gelir (ui/nginx/default.conf); + # burada ezilmez, tenant alt alan adlari da ayni basliklari alir. location / { - proxy_pass http://127.0.0.1:3002; + proxy_pass http://127.0.0.1:3002; include /etc/nginx/proxy_params; } } diff --git a/configs/deployment/docker-compose-app.dev.yml b/configs/deployment/docker-compose-app.dev.yml index 13416671..57e403ef 100644 --- a/configs/deployment/docker-compose-app.dev.yml +++ b/configs/deployment/docker-compose-app.dev.yml @@ -16,7 +16,3 @@ services: restart: always cdn: restart: always - nginx: - restart: always - volumes: - - ./configs/nginx.dev.conf:/etc/nginx/conf.d/default.conf diff --git a/configs/deployment/docker-compose-app.yml b/configs/deployment/docker-compose-app.yml index ac2b8f3d..46616333 100644 --- a/configs/deployment/docker-compose-app.yml +++ b/configs/deployment/docker-compose-app.yml @@ -8,6 +8,9 @@ networks: volumes: cdn: api-keys: + # Onceki surumlerin hash'li chunk arsivi (ui/nginx/40-archive-assets.sh). Deploy'dan once + # acilmis sekmeler kendi surumlerinin dosyalarini buradan bulmaya devam eder. + ui-assets: sql-backups: external: true name: sozsoft-platform-data_sql-backups @@ -70,7 +73,7 @@ services: ports: - 3002:80 volumes: - - /home/user/sozsoft.com:/etc/ssl/sozsoft.com:ro + - ui-assets:/srv/assets networks: - default diff --git a/configs/deployment/scripts/deploy/app.sh b/configs/deployment/scripts/deploy/app.sh index 7f7a982c..7ea51ab4 100644 --- a/configs/deployment/scripts/deploy/app.sh +++ b/configs/deployment/scripts/deploy/app.sh @@ -1,13 +1,6 @@ export ENV=${1:-dev} echo "Env değeri: ${ENV}" -cd ~/sozsoft-platform/configs/deployment -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile api stop -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile api rm -f - -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile ui stop -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile ui rm -f - cd ~/sozsoft-platform git checkout main # --tags --force: sürüm notları git tag'lerinden üretiliyor; yerel tag origin'den @@ -29,15 +22,11 @@ if [ ! -s "$RELEASE_NOTES" ]; then fi cd ~/sozsoft-platform/configs/deployment -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile api pull -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile api up -d +COMPOSE="docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml" -# API_URL="https://api.sozsoft.com/api/app/platform-tenant?skipCount=0&maxResultCount=1" - -# echo "sozsoft-api açılması bekleniyor: $API_URL" -# until curl -fsS --max-time 5 -o /dev/null "$API_URL"; do -# sleep 2 -# done - -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile ui pull -docker compose -f docker-compose-app.yml -f docker-compose-app.${ENV}.yml --profile ui up -d +# Konteynerler önceden durdurulup silinmez: imajlar önce indirilir, `up -d` değişen +# servisi yerinde yeniden oluşturur. Böylece kesinti pull süresi kadar değil, konteyner +# değişimi kadardır; UI'nin eski sürüm chunk'ları da `ui-assets` volume'unda kalır. +$COMPOSE --profile api --profile ui pull +$COMPOSE --profile api up -d --remove-orphans +$COMPOSE --profile ui up -d --remove-orphans diff --git a/configs/seeds/data/App.Languages.LanguageText.json b/configs/seeds/data/App.Languages.LanguageText.json index 9d915f0f..18c9dc80 100644 --- a/configs/seeds/data/App.Languages.LanguageText.json +++ b/configs/seeds/data/App.Languages.LanguageText.json @@ -410,20 +410,6 @@ "Key": "App.Platform.Saving", "Value": "Kaydediliyor..." }, - { - "Id": "9120a104-4bd7-5e1e-be66-9e93b5ab818d", - "CultureName": "en", - "ResourceName": "AbpAccount", - "Key": "App.Platform.Cancel", - "Value": "Cancel" - }, - { - "Id": "b7f3017e-217e-51fa-9479-9d3198b34a4d", - "CultureName": "tr", - "ResourceName": "AbpAccount", - "Key": "App.Platform.Cancel", - "Value": "İptal" - }, { "Id": "7f8d16ef-8f13-56b7-b9a1-8418d50e92be", "CultureName": "en", @@ -1054,34 +1040,6 @@ "Key": "ResetMyPassword", "Value": "Şifreyi Sıfırla" }, - { - "Id": "eaf9410b-47d8-594a-9caa-99d1817cb1bd", - "CultureName": "en", - "ResourceName": "AbpAccount", - "Key": "App.Platform.AccessDenied", - "Value": "Access denied!" - }, - { - "Id": "27edfa1d-e788-5a3f-8c77-f2cedff5a1ea", - "CultureName": "tr", - "ResourceName": "AbpAccount", - "Key": "App.Platform.AccessDenied", - "Value": "Erişim reddedildi!" - }, - { - "Id": "70462589-bf0c-5f80-957d-caf9b69c81bb", - "CultureName": "en", - "ResourceName": "AbpAccount", - "Key": "App.Platform.AccessDeniedMessage", - "Value": "You do not have access to this resource." - }, - { - "Id": "62883e79-15c6-500a-ad58-0e1596814441", - "CultureName": "tr", - "ResourceName": "AbpAccount", - "Key": "App.Platform.AccessDeniedMessage", - "Value": "Bu kaynağa erişiminiz yok." - }, { "Id": "23f69c8f-7e64-54f4-92e5-f6f562dd4241", "CultureName": "en", @@ -27458,6 +27416,62 @@ "Key": "App.Platform.Refresh", "Value": "Tazele" }, + { + "Id": "04d10fdb-b69b-54c3-b0c6-bde5c8b596b3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.ChunkLoadFailed", + "Value": "The application has been updated. Refresh the page to load the new version." + }, + { + "Id": "3e244351-c5cf-5f61-977c-0e4cde07bf47", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.ChunkLoadFailed", + "Value": "Uygulama güncellendi. Yeni sürümü yüklemek için sayfayı yenileyin." + }, + { + "Id": "fa86f621-67e8-54b7-83f9-24b6365d3bb8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.UnexpectedError", + "Value": "An unexpected error occurred" + }, + { + "Id": "76550fa0-5e2a-5f67-8168-b328f798ff22", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.UnexpectedError", + "Value": "Beklenmeyen bir hata oluştu" + }, + { + "Id": "b2c394cc-0fe2-5eb0-9e2b-7a98d77d4e15", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.SequenceNotFound", + "Value": "Sequence not found: {0}" + }, + { + "Id": "8a57fb60-c317-50fd-b9a9-889e0ade152e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.SequenceNotFound", + "Value": "Numaratör bulunamadı: {0}" + }, + { + "Id": "fd1be045-9c8f-5839-bc0a-e90e4c2eb765", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.SequenceBusy", + "Value": "The sequence {0} is in use by another request, please try again" + }, + { + "Id": "0d66913f-cd62-5d8d-96cd-a3afeddf23b2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.SequenceBusy", + "Value": "{0} numaratörü başka bir istek tarafından kullanılıyor, lütfen yeniden deneyin" + }, { "Id": "48d18796-4483-5a50-81a9-164fd9bf22ed", "CultureName": "en", @@ -53274,6 +53288,20 @@ "Key": "App.Profile.AvatarAlt", "Value": "Profil fotoğrafı" }, + { + "Id": "03cae430-473b-5175-b097-29b0def77862", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.Avatar", + "Value": "Avatar" + }, + { + "Id": "681cf0ae-f9c1-5e34-8fe5-23ce547dbc5e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.Avatar", + "Value": "Avatar" + }, { "Id": "accbb20c-aede-5b88-bfb0-accb4e35c73a", "CultureName": "en", @@ -68539,14 +68567,14 @@ "CultureName": "en", "ResourceName": "Platform", "Key": "App.Hr.Intranet.Events.Event.Description", - "Value": "Event — corporate events. Table: Hr_T_Event · tenant scope · list screen (with EventPhoto, EventLike and EventComment).\nUsage: the pattern for a record that carries images and counters; the announcement, participation and post-event photo album flow is managed on one screen.\nFields:\n• Name, Description → the name and the detail of the event\n• TypeId, CategoryId → the type and category; the lookups come from the Event Type and Event Category screens\n• Date, Place → when and where it takes place\n• Status → the state of the event (planned, happened, cancelled)\n• ParticipantsCount → the number of participants\n• Likes, Photos → the like and photo counts; produced from the related tables\n• UserId → who created the event\n• EventId → the reference used by the sub-records\nSub-tabs: Event Comment and Event Like.\nTip: for a calendar view use the scheduler layout — the reference is Administration › Agenda.\nCaution: do not delete a cancelled event, change its status — the participation records and the interaction history stay bound to it." + "Value": "Event — corporate events. Table: Hr_T_Event · tenant scope · list screen (with EventLike and EventComment).\nUsage: the pattern for a record that carries images and counters; the announcement, participation and post-event photo album flow is managed on one screen.\nFields:\n• Name, Description → the name and the detail of the event\n• TypeId, CategoryId → the type and category; the lookups come from the Event Type and Event Category screens\n• Date, Place → when and where it takes place\n• Status → the state of the event (planned, happened, cancelled)\n• ParticipantsCount → the number of participants\n• Likes → the like count; produced from the Event Like table\n• Photos → the photos of the event, kept on the record\n• UserId → who created the event\n• EventId → the reference used by the sub-records\nSub-tabs: Event Comment and Event Like.\nTip: for a calendar view use the scheduler layout — the reference is Administration › Agenda.\nCaution: do not delete a cancelled event, change its status — the participation records and the interaction history stay bound to it." }, { "Id": "dcd43c80-994d-5b31-b5b2-01aa1b52753c", "CultureName": "tr", "ResourceName": "Platform", "Key": "App.Hr.Intranet.Events.Event.Description", - "Value": "Etkinlik — kurum içi etkinlikler. Tablo: Hr_T_Event · tenant kapsamlı · liste ekranı (EventPhoto, EventLike ve EventComment tablolarıyla birlikte).\nKullanım: görsel ve sayaç taşıyan kayıt kalıbı; duyuru, katılım ve etkinlik sonrası fotoğraf albümü akışı tek ekrandan yönetilir.\nAlanlar:\n• Name, Description → etkinliğin adı ve ayrıntısı\n• TypeId, CategoryId → tür ve kategori; lookup'lar Etkinlik Türü ve Etkinlik Kategorisi ekranlarından gelir\n• Date, Place → ne zaman ve nerede yapılacağı\n• Status → etkinliğin durumu (planlandı, gerçekleşti, iptal)\n• ParticipantsCount → katılımcı sayısı\n• Likes, Photos → beğeni ve fotoğraf sayıları; ilgili tablolardan üretilir\n• UserId → etkinliği oluşturan kullanıcı\n• EventId → alt kayıtların referans verdiği değer\nAlt sekmeler: Etkinlik Yorumu ve Etkinlik Beğenisi.\nİpucu: takvim görünümü için scheduler düzeni kullanılır — referansı Administration › Ajanda ekranıdır.\nDikkat: iptal edilen etkinliği silme, durumunu değiştir — katılım kayıtları ve etkileşim geçmişi ona bağlı kalır." + "Value": "Etkinlik — kurum içi etkinlikler. Tablo: Hr_T_Event · tenant kapsamlı · liste ekranı (EventLike ve EventComment tablolarıyla birlikte).\nKullanım: görsel ve sayaç taşıyan kayıt kalıbı; duyuru, katılım ve etkinlik sonrası fotoğraf albümü akışı tek ekrandan yönetilir.\nAlanlar:\n• Name, Description → etkinliğin adı ve ayrıntısı\n• TypeId, CategoryId → tür ve kategori; lookup'lar Etkinlik Türü ve Etkinlik Kategorisi ekranlarından gelir\n• Date, Place → ne zaman ve nerede yapılacağı\n• Status → etkinliğin durumu (planlandı, gerçekleşti, iptal)\n• ParticipantsCount → katılımcı sayısı\n• Likes → beğeni sayısı; Etkinlik Beğenisi tablosundan üretilir\n• Photos → etkinliğin fotoğrafları, kaydın üzerinde tutulur\n• UserId → etkinliği oluşturan kullanıcı\n• EventId → alt kayıtların referans verdiği değer\nAlt sekmeler: Etkinlik Yorumu ve Etkinlik Beğenisi.\nİpucu: takvim görünümü için scheduler düzeni kullanılır — referansı Administration › Ajanda ekranıdır.\nDikkat: iptal edilen etkinliği silme, durumunu değiştir — katılım kayıtları ve etkileşim geçmişi ona bağlı kalır." }, { "Id": "30f033d8-7b7d-59ba-8233-4e82fa89cf47", @@ -97989,6 +98017,3926 @@ "ResourceName": "Platform", "Key": "App.Maintenance.RejectReason.Desc", "Value": "Red Nedenleri — bakım ve hizmet iş taleplerinin reddedilme sebepleri (kullanıcı hatası, arıza bulunamadı, garanti, dış firma, hurda, mükerrer, bakım gerekmiyor, diğer).\nNerede: Bakım › Tanımlamalar › Red Nedenleri.\nAlanlar: kod (benzersiz), red nedeni, açıklama, sıra, aktif.\nKullanıldığı yer: bakım ve hizmet talepleri/iş emirleri reddedilirken neden buradan seçilir. Liste sıraya göre dizilir; kullanılmış neden silinmez, pasife alınır." + }, + { + "Id": "e2bef9f5-5268-5ebf-b563-78c13b5d3b29", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.PageOf", + "Value": "Page {0} of {1}" + }, + { + "Id": "3a523cb6-bdc0-5f29-9317-5a333509dc76", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.PageOf", + "Value": "Sayfa {0} / {1}" + }, + { + "Id": "791ffafa-efc6-5a20-a3e6-ee5b9d5951da", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.OrderSuccess.LicenseInfo", + "Value": "License Information" + }, + { + "Id": "0c336366-9d75-50f0-aef8-fde23f8d4b81", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.OrderSuccess.LicenseInfo", + "Value": "Lisans Bilgileri" + }, + { + "Id": "f0619b12-c4e8-5aae-a7e5-2c877a0ae2cf", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.OrderSuccess.NewOrderSubject", + "Value": "New Order" + }, + { + "Id": "f3934305-a0e8-500e-91d8-57762b07cdf4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.OrderSuccess.NewOrderSubject", + "Value": "Yeni Sipariş" + }, + { + "Id": "d81458c9-b2a9-53cf-b17f-2e18d1dca64c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NotEqual", + "Value": "not equal" + }, + { + "Id": "62e75d89-22af-533f-a5ae-c7341c5c4289", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NotEqual", + "Value": "eşit değil" + }, + { + "Id": "d6a352d9-b1a4-55d9-98b2-9a0047625d6c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Contains", + "Value": "contains" + }, + { + "Id": "3dcbc2ba-448f-5fe7-b736-8f558293c6e3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Contains", + "Value": "içerir" + }, + { + "Id": "21eabcc3-790e-5810-98a1-64dd8db994a2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NotContains", + "Value": "does not contain" + }, + { + "Id": "9f857ef5-7086-57c3-aecc-687c02fa8209", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NotContains", + "Value": "içermez" + }, + { + "Id": "0804317b-85e7-5484-8c43-2d7e6ec17106", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.StartsWith", + "Value": "starts with" + }, + { + "Id": "7210922e-954c-5e1d-aa9e-9bb0d1a45c84", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.StartsWith", + "Value": "ile başlar" + }, + { + "Id": "780197b0-2b27-5caa-98f5-a3141f2d383d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.EndsWith", + "Value": "ends with" + }, + { + "Id": "bcd5dac9-5389-5ab9-a80b-2e1fa5954014", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.EndsWith", + "Value": "ile biter" + }, + { + "Id": "e25b5700-103a-53fd-9b48-0da28e739111", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Between", + "Value": "between" + }, + { + "Id": "b277d03b-52a3-54c3-b883-16cfaca0e9d2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Between", + "Value": "arasında" + }, + { + "Id": "2c94ef98-8461-58d3-a052-474da21f3072", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.AnyOf", + "Value": "is any of" + }, + { + "Id": "387ac2f5-e9a8-527c-862e-5cd15d02681f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.AnyOf", + "Value": "şunlardan biri" + }, + { + "Id": "8acbaf56-8755-5206-91d9-fc6e07159d26", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NoneOf", + "Value": "is none of" + }, + { + "Id": "e6b6dd81-2d6a-5f0b-b491-ebce888c32a4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.NoneOf", + "Value": "şunlardan hiçbiri" + }, + { + "Id": "59b4eab7-9a11-53f5-be1b-6694cfb4c621", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.IsBlank", + "Value": "is blank" + }, + { + "Id": "c404d137-8566-55f1-ac2c-7a2c7b9197a5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.IsBlank", + "Value": "boş" + }, + { + "Id": "e26822ca-3059-579c-a644-530d89000415", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.IsNotBlank", + "Value": "is not blank" + }, + { + "Id": "5401b9e9-129a-5b9a-afd5-cd52c1df5d6b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.IsNotBlank", + "Value": "boş değil" + }, + { + "Id": "8c576937-7fe3-5956-861c-1b1daf647926", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Not", + "Value": "not ({0})" + }, + { + "Id": "58c4cb0e-1622-54cb-9f5b-91220958dc18", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Not", + "Value": "değil ({0})" + }, + { + "Id": "49459689-7622-5d3d-9f10-38c777cb8aa2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.And", + "Value": "and" + }, + { + "Id": "c825a476-36be-5384-a3ad-4a507c704e5f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.And", + "Value": "ve" + }, + { + "Id": "808d3b2d-0cc3-568f-9d91-df099dc10023", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Or", + "Value": "or" + }, + { + "Id": "fab162f8-d0aa-5f1c-9ba3-9e8f7fc2d4bd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Reports.Filter.Or", + "Value": "veya" + }, + { + "Id": "ed2b47c1-a85e-56df-b122-a9ddbba176b0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFilter", + "Value": "Invalid filter." + }, + { + "Id": "9e03b4da-bdd2-5f07-855c-7db256b8335f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFilter", + "Value": "Geçersiz filtre." + }, + { + "Id": "35e7eaac-104e-5da0-a399-3fbabafd933b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.UnsupportedFilterOperator", + "Value": "'{0}' is not a supported filter operator." + }, + { + "Id": "5d3053aa-c0fa-54de-8e0d-b8c60fb995e3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.UnsupportedFilterOperator", + "Value": "'{0}' desteklenen bir filtre operatörü değil." + }, + { + "Id": "b3e3468f-f851-5cbf-9814-7b4cb866b1fc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.ColumnNotInEntity", + "Value": "'{0}' is not a column of {1}." + }, + { + "Id": "1a1d52c2-74f3-536b-8f8e-6421b8c702dd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.ColumnNotInEntity", + "Value": "'{0}', {1} varlığının bir kolonu değil." + }, + { + "Id": "70231710-d0ee-5933-bd2c-82188551ddcc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFileName", + "Value": "Invalid file name." + }, + { + "Id": "d66c7e44-6778-5831-be71-85780d872bbb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFileName", + "Value": "Geçersiz dosya adı." + }, + { + "Id": "d5f9fa65-c9f7-5837-ad83-707ea243ea67", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFilePath", + "Value": "Invalid file path." + }, + { + "Id": "14b974c1-3f45-57de-b71c-6370ff3a0939", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.InvalidFilePath", + "Value": "Geçersiz dosya yolu." + }, + { + "Id": "456fad5e-d0b2-5151-a8e9-0a13d345959d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.FileNotFound", + "Value": "File not found." + }, + { + "Id": "3b5c1673-001b-5f3d-8029-b017da24df6f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.FileNotFound", + "Value": "Dosya bulunamadı." + }, + { + "Id": "1e515fa7-0c24-5cd7-802c-bbc650c5156f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.FileNotFoundWithName", + "Value": "File not found: {0}" + }, + { + "Id": "810d5bca-6fac-56be-ad47-9a5dc9fa419e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.FileNotFoundWithName", + "Value": "Dosya bulunamadı: {0}" + }, + { + "Id": "8f8f8231-ad0b-52b9-a269-b68c765e3ebc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.FileRequired", + "Value": "A file must be selected." + }, + { + "Id": "089c728d-6aa1-55fa-a2fc-666f155ecd8d", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.FileRequired", + "Value": "Dosya seçilmelidir." + }, + { + "Id": "5291d540-ddb0-5046-92a2-5bc563e3bb5f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.FileUnreadable", + "Value": "The file could not be read." + }, + { + "Id": "d35f94f9-1e57-5ed6-b963-683f18662fe7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.FileUnreadable", + "Value": "Dosya okunamadı." + }, + { + "Id": "27857eaf-661b-578c-8aa6-f942968c0a21", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.FileEmpty", + "Value": "The file is empty." + }, + { + "Id": "411e4030-7b43-522e-948d-db177fdb1d74", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.FileEmpty", + "Value": "Dosya boş." + }, + { + "Id": "24c75fa0-b3f9-51e4-b894-1e597142334f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.SearchTermMinLength", + "Value": "The search term must be at least {0} characters." + }, + { + "Id": "603544d2-6f2c-5278-bf9f-1f950c7e2fd7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.SearchTermMinLength", + "Value": "Arama terimi en az {0} karakter olmalıdır." + }, + { + "Id": "b62702f1-fa11-5528-a8b5-47c72abc6503", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.TenantNameRequired", + "Value": "Tenant name is required." + }, + { + "Id": "80a99546-13f4-57af-bd40-7463d36b97f6", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.TenantNameRequired", + "Value": "Kiracı adı zorunludur." + }, + { + "Id": "680799b8-2bae-5f70-bb97-cd627f583c94", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.ModuleRequired", + "Value": "Module is required." + }, + { + "Id": "b7b81089-b544-5207-bce3-c5a22cb7903b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.ModuleRequired", + "Value": "Modül zorunludur." + }, + { + "Id": "b363ef8f-be50-50d5-bb64-aad94ecee28d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.OrderAlreadyHasTenant", + "Value": "This order already has a tenant." + }, + { + "Id": "f6916193-19f3-545e-859e-977cce345dd3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.OrderAlreadyHasTenant", + "Value": "Bu siparişin zaten bir kiracısı var." + }, + { + "Id": "18bc3c50-26ff-5e6e-ae9c-8c8611549cb5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SeedsFolderNotAccessible", + "Value": "The '{0}' folder is not accessible." + }, + { + "Id": "220faf90-d2d3-50bd-9ed6-b6be68e53f90", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SeedsFolderNotAccessible", + "Value": "'{0}' klasörüne erişilemiyor." + }, + { + "Id": "198cd779-0f32-5011-82f6-13d6560ec495", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.OtherTenantFiles", + "Value": "Files of another tenant cannot be accessed." + }, + { + "Id": "b0def56a-763b-5a81-97e9-039fbf55c84f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.OtherTenantFiles", + "Value": "Başka bir kiracının dosyalarına erişilemez." + }, + { + "Id": "43df4515-0603-5589-bd0c-d1787fc6b0dd", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedFolder", + "Value": "The system folder '{0}' is protected and cannot be changed." + }, + { + "Id": "1d0ee10c-555d-5f73-9f1b-daa2f8266ad4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedFolder", + "Value": "'{0}' sistem klasörü korumalıdır, değiştirilemez." + }, + { + "Id": "8bbd5d64-d62e-5488-b7fc-384f48ca8095", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.CdnPathNotConfigured", + "Value": "The CDN path is not configured." + }, + { + "Id": "8579200d-15ab-5b96-aefb-c64c40ab876c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.CdnPathNotConfigured", + "Value": "CDN yolu yapılandırılmamış." + }, + { + "Id": "ea6861c8-ae7b-5897-9c23-db4e72be743e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderAlreadyExists", + "Value": "A folder with this name already exists." + }, + { + "Id": "f2b5548a-8c03-595c-9464-2160c52bbc6a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderAlreadyExists", + "Value": "Bu adda bir klasör zaten var." + }, + { + "Id": "07d90570-dcaa-5ce2-8137-543d128de5d3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FileAlreadyExists", + "Value": "A file with this name already exists." + }, + { + "Id": "5cbcfade-3a4a-5f7f-a61e-67da43eda289", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FileAlreadyExists", + "Value": "Bu adda bir dosya zaten var." + }, + { + "Id": "c240b1e9-f02f-5dd5-9494-be43b11e949c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemAlreadyExists", + "Value": "An item with this name already exists." + }, + { + "Id": "a14f78fe-13a9-5d88-ad7c-05180538e551", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemAlreadyExists", + "Value": "Bu adda bir öğe zaten var." + }, + { + "Id": "328c4762-0d81-59dd-b065-fbb384806198", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemAlreadyExistsInTarget", + "Value": "An item with this name already exists in the target folder." + }, + { + "Id": "5069c108-4f1f-5db7-bfc0-bc18ca6a6036", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemAlreadyExistsInTarget", + "Value": "Hedef klasörde bu adda bir öğe zaten var." + }, + { + "Id": "4753d4cf-e577-53ca-a3db-25631522ab24", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemNotFound", + "Value": "Item not found." + }, + { + "Id": "71b053e7-a77a-571c-8712-e3e556584efd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemNotFound", + "Value": "Öğe bulunamadı." + }, + { + "Id": "e2a161a3-1ca6-5401-9e67-c6aeae76f312", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemNotFoundWithName", + "Value": "Item not found: {0}" + }, + { + "Id": "389c1905-6208-5804-83f8-f705bc763742", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemNotFoundWithName", + "Value": "Öğe bulunamadı: {0}" + }, + { + "Id": "4f051f30-f6cd-542c-8a01-c18cac7d9eba", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForDelete", + "Value": "No items selected for deletion." + }, + { + "Id": "7c054b1d-0f04-58c8-b524-b12b0b21840b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForDelete", + "Value": "Silinecek öğe seçilmedi." + }, + { + "Id": "52e5d383-b511-52b9-a55d-11fdf0a29c33", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForCopy", + "Value": "No items selected for copying." + }, + { + "Id": "d80d56c1-d666-5ed0-bda6-b3bd617118dd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForCopy", + "Value": "Kopyalanacak öğe seçilmedi." + }, + { + "Id": "1f4b3c26-a78d-5b25-8839-6402eda51e9c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForMove", + "Value": "No items selected for moving." + }, + { + "Id": "67557837-274b-563a-84ba-44b37fec2170", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsSelectedForMove", + "Value": "Taşınacak öğe seçilmedi." + }, + { + "Id": "28dacb37-d501-5d13-b83e-bc5219e6614d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteItemFailed", + "Value": "{0} could not be deleted: {1}" + }, + { + "Id": "64d8e709-9109-5c1b-ba8e-77e364cd1de3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteItemFailed", + "Value": "{0} silinemedi: {1}" + }, + { + "Id": "2f69b796-0558-5dbc-a94f-d8877d7401a1", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.CopyItemFailed", + "Value": "{0} could not be copied: {1}" + }, + { + "Id": "37001645-3158-5703-8d40-52f211a5abf2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.CopyItemFailed", + "Value": "{0} kopyalanamadı: {1}" + }, + { + "Id": "d444d6da-fdf0-560f-a992-82a1cb2751ec", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.MoveItemFailed", + "Value": "{0} could not be moved: {1}" + }, + { + "Id": "eeb10e91-ccde-573e-9445-fbbba617764b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.MoveItemFailed", + "Value": "{0} taşınamadı: {1}" + }, + { + "Id": "2bb971cf-f9d7-531c-9e85-c3967f883e5b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.CannotMoveToSameLocation", + "Value": "The item cannot be moved to the same location: {0}" + }, + { + "Id": "e6a0a17e-de74-53ff-bbce-74e1be24c6cf", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.CannotMoveToSameLocation", + "Value": "Öğe aynı konuma taşınamaz: {0}" + }, + { + "Id": "fa0b86b9-b2fb-5385-acaf-da8132880d03", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotDeleted", + "Value": "Some items could not be deleted: {0}" + }, + { + "Id": "44fe03c2-858a-5e43-8488-6f9b003c3eaa", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotDeleted", + "Value": "Bazı öğeler silinemedi: {0}" + }, + { + "Id": "500a6095-99a2-57e3-8ab9-0c21f844dbd8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotCopied", + "Value": "Some items could not be copied: {0}" + }, + { + "Id": "4fa960b4-4e5d-527e-a6b6-fcda6ba23ed6", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotCopied", + "Value": "Bazı öğeler kopyalanamadı: {0}" + }, + { + "Id": "34b1b5fa-ce55-5d96-bad6-0da2dd91e89f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotMoved", + "Value": "Some items could not be moved: {0}" + }, + { + "Id": "1c8ca6a4-765b-5709-87e6-c0e9b172209e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SomeItemsNotMoved", + "Value": "Bazı öğeler taşınamadı: {0}" + }, + { + "Id": "fd111915-102f-57de-92c8-73fcc2b8d9ac", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameEmpty", + "Value": "The file name cannot be empty." + }, + { + "Id": "8a832443-bfe5-5604-a1b3-ef18ba00622c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameEmpty", + "Value": "Dosya adı boş olamaz." + }, + { + "Id": "169f72c9-3587-560e-b77d-cf08242a0e9e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameInvalidChars", + "Value": "The file name contains invalid characters." + }, + { + "Id": "b651ee85-91cf-5320-9523-0d5ddc1b3acb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameInvalidChars", + "Value": "Dosya adı geçersiz karakterler içeriyor." + }, + { + "Id": "c8fdc9ab-77ad-5305-8719-f413cf152162", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameTooLong", + "Value": "The file name is too long." + }, + { + "Id": "f2d79727-798f-5806-a546-b71a9394179c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FileNameTooLong", + "Value": "Dosya adı çok uzun." + }, + { + "Id": "94354d0a-594d-5147-9dd1-6b8974a927b4", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DeveloperKitEndpoint.NoActiveEndpoint", + "Value": "No active endpoint is defined for {0} {1}." + }, + { + "Id": "2217ec1a-406b-5575-931a-29233f914bbf", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DeveloperKitEndpoint.NoActiveEndpoint", + "Value": "{0} {1} için tanımlı aktif endpoint yok." + }, + { + "Id": "1c24689b-958d-5afe-97d9-103140938d39", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.RecipientRequired", + "Value": "At least one recipient must be selected." + }, + { + "Id": "ecbcbbad-9b9f-5b57-894c-5d46d74b9aef", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.RecipientRequired", + "Value": "En az bir alıcı seçilmelidir." + }, + { + "Id": "52bde549-06f0-516b-abb9-e5033b09f7fc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.MessageOrFileRequired", + "Value": "A message or a file must be sent." + }, + { + "Id": "a372738e-653f-5813-b9f2-b6701eeda525", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.MessageOrFileRequired", + "Value": "Mesaj veya dosya gönderilmelidir." + }, + { + "Id": "bd62d93a-5999-552b-9351-6a21bb227613", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidRecipient", + "Value": "There is an invalid recipient for this tenant." + }, + { + "Id": "600bb147-954f-51fe-8ef2-3dde525a3d0c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidRecipient", + "Value": "Bu kiracı için geçersiz bir alıcı var." + }, + { + "Id": "b03276c6-e638-5062-82e2-007e4a7ecbb3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.ConversationNotFound", + "Value": "Conversation not found." + }, + { + "Id": "3baa9659-e267-588a-a2fe-010274d20b71", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.ConversationNotFound", + "Value": "Görüşme bulunamadı." + }, + { + "Id": "219b115b-4543-5691-a266-b0dd6e41321b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.RecipientsMismatch", + "Value": "The recipients do not match this conversation." + }, + { + "Id": "5d7b776b-dd34-5f50-ac8e-3b561ce76d76", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.RecipientsMismatch", + "Value": "Alıcılar bu görüşmeyle eşleşmiyor." + }, + { + "Id": "252ae686-0a29-5943-8c40-75b26c6c7056", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.OnlyOwnMessages", + "Value": "You can only delete your own messages." + }, + { + "Id": "d24b060a-d297-5e6c-943d-eda5109b5c98", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.OnlyOwnMessages", + "Value": "Yalnızca kendi mesajlarınızı silebilirsiniz." + }, + { + "Id": "aa31aa8e-b6ca-5fed-8a44-b6c1cb15d437", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.DeleteWindowExpired", + "Value": "Messages can only be deleted within the first {0} minutes." + }, + { + "Id": "0c616a31-e56d-5cc4-8b9a-9f257f8b781f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.DeleteWindowExpired", + "Value": "Mesajlar yalnızca ilk {0} dakika içinde silinebilir." + }, + { + "Id": "93eb4bbf-d2e3-527d-9934-a1343f882b44", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.SessionNotFound", + "Value": "User session not found." + }, + { + "Id": "2e43e941-68c2-5c29-b8fd-5acbae05af62", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.SessionNotFound", + "Value": "Kullanıcı oturumu bulunamadı." + }, + { + "Id": "6d3d52c4-5a00-54b5-b5e2-06b4c55dd1ea", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.MaxAttachments", + "Value": "At most {0} files can be sent." + }, + { + "Id": "6b11d740-9301-5b62-a9df-31854ac3b06a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.MaxAttachments", + "Value": "En fazla {0} dosya gönderilebilir." + }, + { + "Id": "1ec5fd05-4e23-5059-ba84-a4336cc15ff7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidAttachment", + "Value": "Invalid file attachment." + }, + { + "Id": "d9b4b3cd-dfbf-54cf-a63f-84b2143751fe", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidAttachment", + "Value": "Geçersiz dosya eki." + }, + { + "Id": "f1258e06-5e02-5c38-924d-b78c8c0175cd", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.ParticipantsRequired", + "Value": "At least two participants must be selected." + }, + { + "Id": "aa8cf95c-7b02-5e25-8e70-4a8e292f6857", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.ParticipantsRequired", + "Value": "En az iki katılımcı seçilmelidir." + }, + { + "Id": "d1b8588a-1dfc-522f-a4fe-91c93916c9e7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidParticipant", + "Value": "There is an invalid participant for this tenant." + }, + { + "Id": "cd8b4700-2064-5030-a2be-59a97ea82363", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.InvalidParticipant", + "Value": "Bu kiracı için geçersiz bir katılımcı var." + }, + { + "Id": "5c70f876-c724-5120-b688-2fa207ab355d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Messenger.NoConversationAccess", + "Value": "You do not have access to this conversation." + }, + { + "Id": "ddc33f11-3cd9-5f66-a563-c74e4d6c92cc", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Messenger.NoConversationAccess", + "Value": "Bu görüşmeye erişim yetkiniz yok." + }, + { + "Id": "6b2c26d8-1634-53b5-8f54-9eb2c6c2ad12", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StepNotInListForm", + "Value": "The workflow step does not belong to the selected list form." + }, + { + "Id": "db5291ce-262c-5778-8f1e-940eb98d1cce", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StepNotInListForm", + "Value": "Workflow adımı seçili liste formuna ait değil." + }, + { + "Id": "ac1af6a7-5272-59f9-b061-c0e18d69edf8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StartStepNotFound", + "Value": "The workflow start step was not found." + }, + { + "Id": "ed164d0a-2de1-5169-b592-502998c51921", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StartStepNotFound", + "Value": "Workflow başlangıç adımı bulunamadı." + }, + { + "Id": "11852a06-4f54-5eef-9876-08c4f7fe12f3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.ResetNotDefined", + "Value": "Workflow reset is not defined for this list form." + }, + { + "Id": "0c52aedb-2e5f-502c-b63b-a3a12d774635", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.ResetNotDefined", + "Value": "Bu liste formu için workflow sıfırlama tanımlı değil." + }, + { + "Id": "afe1d64b-e763-50ad-9687-1bcd05a8311f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotWaitingApproval", + "Value": "The selected record is not waiting at an approval step." + }, + { + "Id": "f7ebaafd-106d-5d9f-ae98-8e6cbd5855a9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotWaitingApproval", + "Value": "Seçili kayıt onay adımında beklemiyor." + }, + { + "Id": "fdd3d8ec-d151-5c93-823c-34f51348f836", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotWaitingThisStep", + "Value": "The selected record is not waiting at this approval step." + }, + { + "Id": "ec2e5127-b7fd-5d4e-85f6-30138417466f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotWaitingThisStep", + "Value": "Seçili kayıt bu onay adımında beklemiyor." + }, + { + "Id": "49ecd036-fdf6-5414-9dc5-f57a1f09e484", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.NoApprovalPermission", + "Value": "You are not authorized to approve this record." + }, + { + "Id": "5ac971e5-8d7c-50ff-8faa-d4533553374e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.NoApprovalPermission", + "Value": "Bu kayıt için onay yetkiniz yok." + }, + { + "Id": "d02fcaea-4547-5b4c-b901-5e184a3b2aa5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StatusFieldNotDefined", + "Value": "The workflow status field is not defined." + }, + { + "Id": "15d87cf8-d97a-5dc1-b697-9725b8e272af", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.StatusFieldNotDefined", + "Value": "Workflow durum alanı tanımlı değil." + }, + { + "Id": "79a6048d-c549-539b-a0b3-c4d0cd52ad5a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.KeyFieldNotDefined", + "Value": "The list form key field is not defined." + }, + { + "Id": "6a582a4a-7242-5fc2-a153-f96507bde62e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.KeyFieldNotDefined", + "Value": "Liste formu anahtar alanı tanımlı değil." + }, + { + "Id": "953a4592-0367-5cfe-86ea-95611b84badb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordKeyRequired", + "Value": "A record key must be selected for the workflow." + }, + { + "Id": "f92df43e-52cf-595e-834a-8769c78ebf6c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordKeyRequired", + "Value": "Workflow için kayıt anahtarı seçilmelidir." + }, + { + "Id": "ec4a9ff1-464b-547a-bfcd-b207bb3b166e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotFound", + "Value": "The workflow record was not found." + }, + { + "Id": "54f7fa28-9d39-57ba-b5f6-6156ad85bda5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotFound", + "Value": "Workflow kaydı bulunamadı." + }, + { + "Id": "403ff071-859d-55ca-99bc-c6357368ea41", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotReadable", + "Value": "The workflow record could not be read." + }, + { + "Id": "869949da-6754-51ad-af5b-a54dea70cfc2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RecordNotReadable", + "Value": "Workflow kaydı okunamadı." + }, + { + "Id": "40127bdd-8ac6-5853-b6ee-31986ac6ac5c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.CycleDetected", + "Value": "A cycle was detected in the workflow steps." + }, + { + "Id": "99c28e97-3f5f-595e-a8d3-6b5daca01d04", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.CycleDetected", + "Value": "Workflow adımlarında döngü tespit edildi." + }, + { + "Id": "46c04e0e-9ce7-57a1-85b4-8b73ea7d8a0c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMailFailed", + "Value": "The notification e-mail could not be sent: {0}" + }, + { + "Id": "805a61ed-b3cd-5642-a800-3e20fec7d757", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMailFailed", + "Value": "Bilgilendirme e-postası gönderilemedi: {0}" + }, + { + "Id": "7f4546de-e3f1-5fb8-b5f5-4cf47c572ebb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformApproverMissing", + "Value": "No recipient is defined for the inform step." + }, + { + "Id": "f2547428-4267-5c44-b50d-c9176904b483", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformApproverMissing", + "Value": "Bilgilendirme adımı için alıcı tanımlı değil." + }, + { + "Id": "ae87266b-4c8a-5a51-9add-3441dda70271", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformUserEmailNotFound", + "Value": "The e-mail address of the user to inform was not found: {0}" + }, + { + "Id": "4a844f9c-c597-5a1b-91ff-89cd40088e02", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformUserEmailNotFound", + "Value": "Bilgilendirilecek kullanıcının e-posta adresi bulunamadı: {0}" + }, + { + "Id": "1a5b55cd-4e43-5b77-8961-0d80e9ed1d79", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RowApproverEmpty", + "Value": "The workflow approver could not be read from the record's '{0}' field; the field is empty." + }, + { + "Id": "e643c308-c969-55d4-830e-504320034454", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.RowApproverEmpty", + "Value": "Workflow onaycısı kayıttaki '{0}' alanından okunamadı; alan boş." + }, + { + "Id": "d5083ceb-bf07-58e1-950b-103cc41a1532", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.NoUpdatePermission", + "Value": "You do not have update permission for the workflow operation." + }, + { + "Id": "fd5592c7-ecfe-52ad-b1d7-a9083138cdfb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.NoUpdatePermission", + "Value": "Workflow işlemi için güncelleme yetkiniz yok." + }, + { + "Id": "b898c70d-b7cd-5a0a-978f-9e6b7148321c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Subject", + "Value": "Workflow notification: {0}" + }, + { + "Id": "4b54b6bd-037e-5aa9-bcf7-418e96cdcc79", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Subject", + "Value": "Workflow Bilgilendirme: {0}" + }, + { + "Id": "6c1a5fb2-14ef-5a81-ae86-ca7ad2e04f87", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Intro", + "Value": "The workflow has reached an inform step." + }, + { + "Id": "8afdd559-3bdb-5fdf-83cd-523360550dbb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Intro", + "Value": "Workflow sürecinde bilgilendirme adımına ulaşıldı." + }, + { + "Id": "0ea377ef-2090-5c65-bfe5-21144f05c34c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Step", + "Value": "Inform Step" + }, + { + "Id": "7ff69d36-f2a5-547b-83d8-56db244d77da", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.Step", + "Value": "Bilgilendirme Adımı" + }, + { + "Id": "c58616f8-8f1d-5487-bffe-1aaf536cd647", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.ProcessSummary", + "Value": "Process Summary of This Operation" + }, + { + "Id": "f91f8147-cd55-5790-aacc-a4b3ae2d325e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.ProcessSummary", + "Value": "Bu İşlemdeki Süreç Özeti" + }, + { + "Id": "a81e03c5-cda7-5737-8a01-a3dead629350", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.PreviousNotes", + "Value": "Previous Workflow Notes" + }, + { + "Id": "672dd141-fcb9-5c88-9556-e48fcdec3552", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormWorkflow.InformMail.PreviousNotes", + "Value": "Önceki Workflow Notları" + }, + { + "Id": "fca0c46b-6b87-50d1-9532-48a08584cf1b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormImportManager.SessionNotFound", + "Value": "Import session not found." + }, + { + "Id": "d2a1c166-e1cf-5795-8e0e-d1a1bfc58a39", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormImportManager.SessionNotFound", + "Value": "İçe aktarma oturumu bulunamadı." + }, + { + "Id": "776c92f4-8c4c-54d0-8c17-c1050652c90e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListFormImportManager.ImportFailed", + "Value": "Import failed: {0}" + }, + { + "Id": "27c7b6a8-dacb-578f-a219-e4b41c3169aa", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListFormImportManager.ImportFailed", + "Value": "İçe aktarma başarısız: {0}" + }, + { + "Id": "009fb2ce-9574-5618-adc0-6216bc3935d5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NameRequired", + "Value": "Wizard name is required." + }, + { + "Id": "05594127-07a5-5d57-a35a-00950792cc78", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NameRequired", + "Value": "Wizard adı zorunludur." + }, + { + "Id": "63d6cfff-adad-599e-8ad8-ad0cbe842b28", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.DataSourceNotFound", + "Value": "Data source '{0}' was not found. A connection string is required for a new data source." + }, + { + "Id": "b09e1f94-137f-5009-8e6c-24b2289f76d6", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.DataSourceNotFound", + "Value": "'{0}' veri kaynağı bulunamadı. Yeni veri kaynağı için bağlantı cümlesi zorunludur." + }, + { + "Id": "f8659a08-e681-5a82-bd08-16247420433f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.OverlayFailed", + "Value": "The ListForm/Fields section could not be applied: {0}" + }, + { + "Id": "3e0bbe76-4075-5cd8-9a06-3f32ef67ac23", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.OverlayFailed", + "Value": "ListForm/Fields bölümü uygulanamadı: {0}" + }, + { + "Id": "c52aca15-66a3-56d0-af4d-31357c638399", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.CustomComponentRequired", + "Value": "A custom component must be selected." + }, + { + "Id": "c8c3922c-2073-5469-a4e4-0093ec6383b7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.CustomComponentRequired", + "Value": "Custom Component seçimi zorunludur." + }, + { + "Id": "7b021349-d64f-5474-b1b0-0938fcb81c0e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.RoutePathRequired", + "Value": "The route path of component '{0}' cannot be empty." + }, + { + "Id": "7e74855f-b27f-52ad-85d6-69913a96d229", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.RoutePathRequired", + "Value": "'{0}' bileşeninin rota yolu (Route Path) boş olamaz." + }, + { + "Id": "9701f930-a494-507b-b0bc-65912c86ce45", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NoWizardForRequest", + "Value": "There is no wizard file linked to this request." + }, + { + "Id": "61a4cd36-4c21-5842-9583-31915c78a91c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NoWizardForRequest", + "Value": "Bu talebe bağlı wizard dosyası yok." + }, + { + "Id": "60af85ff-a26c-5884-ad66-810ab6a7f6b6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooLarge", + "Value": "The zip file is too large." + }, + { + "Id": "586027a1-5407-57d9-96c9-3bbb8b396531", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooLarge", + "Value": "Zip dosyası çok büyük." + }, + { + "Id": "0c486d52-36f5-5cf0-89b2-1f5f1d520a52", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NotAZip", + "Value": "Not a valid zip file." + }, + { + "Id": "0a58b4be-c1aa-5276-b811-f68a164f52c8", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.NotAZip", + "Value": "Geçerli bir zip dosyası değil." + }, + { + "Id": "905d386a-cfaa-5ce1-be36-cfc1295a2812", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooManyEntries", + "Value": "The zip contains too many files." + }, + { + "Id": "d4aa9024-a088-58f5-892b-747123dad8e4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooManyEntries", + "Value": "Zip içindeki dosya sayısı çok fazla." + }, + { + "Id": "2c6c12ef-3532-5987-ae1a-10a2504cfa99", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooLargeExtracted", + "Value": "The zip content exceeds the allowed size when extracted." + }, + { + "Id": "e0f92bca-6dd1-5783-805e-be03cd4b47b2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipTooLargeExtracted", + "Value": "Zip içeriği açıldığında izin verilen boyutu aşıyor." + }, + { + "Id": "797c1167-3f50-5d48-9144-ae17f501879c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipNoSeedFiles", + "Value": "No importable seed file was found in the zip." + }, + { + "Id": "02ebdc38-f2bd-514a-a787-b54647038edb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ZipNoSeedFiles", + "Value": "Zip içinde import edilebilecek seed dosyası bulunamadı." + }, + { + "Id": "12596af0-1e9d-5802-bb54-8b0fbf8ae017", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.InvalidTableName", + "Value": "Invalid table name: {0}" + }, + { + "Id": "c281abf1-852d-50bb-b410-ef95cb762de6", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.InvalidTableName", + "Value": "Geçersiz tablo adı: {0}" + }, + { + "Id": "060cb140-5a88-566c-8305-d503af2eb83e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportFileNotFound", + "Value": "File not found in the import session: {0}" + }, + { + "Id": "1db70e03-b2d8-51aa-a69d-ba9085e52fd5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportFileNotFound", + "Value": "Import oturumunda dosya bulunamadı: {0}" + }, + { + "Id": "83a532ff-4103-59ac-86ad-088b4fcb8e68", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportBlocked", + "Value": "The import cannot continue because of unresolved errors." + }, + { + "Id": "0f3fb0f5-b687-5a6f-983e-c386915a6b0c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportBlocked", + "Value": "Import, giderilmemiş hatalar nedeniyle sürdürülemez." + }, + { + "Id": "4ab5ea27-1c55-528c-a4ee-e0559896fb4e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ScriptNotFound", + "Value": "Script not found: {0}" + }, + { + "Id": "33792f9f-b265-5934-bdf5-0446214bade9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ScriptNotFound", + "Value": "Script bulunamadı: {0}" + }, + { + "Id": "b852f90a-7b4c-520f-b34a-3765bc0efbd0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.InvalidImportSession", + "Value": "Invalid import session." + }, + { + "Id": "0cdaa3b0-9054-5731-a79e-ea8204326e6a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.InvalidImportSession", + "Value": "Geçersiz import oturumu." + }, + { + "Id": "57793a5f-96ee-5c47-8ce1-0c26dda729eb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportSessionExpired", + "Value": "The import session was not found or has expired." + }, + { + "Id": "16cf1e5c-4bf6-55cd-93d7-3d4c6f51c075", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.ImportSessionExpired", + "Value": "Import oturumu bulunamadı veya süresi dolmuş." + }, + { + "Id": "d39693ce-1134-54d7-8d60-ab3a69558166", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QueryKeyFieldRequired", + "Value": "A key field (KeyFieldName) is required for a query-based screen." + }, + { + "Id": "e42f5c27-1e5d-5636-8d36-7fc804c89e6a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QueryKeyFieldRequired", + "Value": "Query kaynaklı ekranda anahtar alan (KeyFieldName) zorunludur." + }, + { + "Id": "0f5f0c2b-3d56-54cf-aac3-eb7dbfc1829d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QueryKeyFieldTypeRequired", + "Value": "The type of key field '{0}' (KeyFieldDbSourceType) is required for a query-based screen." + }, + { + "Id": "db74b5b4-eeb6-59c2-9bcd-fd736c85826a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QueryKeyFieldTypeRequired", + "Value": "Query kaynaklı ekranda '{0}' anahtar alanının tipi (KeyFieldDbSourceType) zorunludur." + }, + { + "Id": "7c625b82-9b4c-5a49-800e-fa36fc9e58b5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QuerySoftDeleteColumn", + "Value": "The main table '{0}' of the query uses soft delete; the query must select the main table's IsDeleted column so that deleted rows are not listed." + }, + { + "Id": "ddeb370e-9860-5abb-ab7e-03885bc1cfa5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.QuerySoftDeleteColumn", + "Value": "Query kaynağının ana tablosu '{0}' soft delete taşıyor; silinen satırların listelenmemesi için sorgu ana tablonun IsDeleted kolonunu seçmelidir." + }, + { + "Id": "2557eba9-80c0-5624-b779-b856a12526a0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.SeedTargetMissing", + "Value": "The data seed target (SeedFilePath) of screen '{0}' is not defined." + }, + { + "Id": "f643f58d-a0a6-5492-bc0a-b43b353fed64", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardError.SeedTargetMissing", + "Value": "'{0}' ekranının veri seed hedefi (SeedFilePath) tanımlı değil." + }, + { + "Id": "412e3e47-9002-5073-a63a-0aa0f6970789", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.DataApplied", + "Value": "{0} inserted, {1} updated, {2} deleted, {3} kept." + }, + { + "Id": "66e7803b-d318-5bcb-9055-940de19c25ce", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.DataApplied", + "Value": "{0} eklendi, {1} güncellendi, {2} silindi, {3} korundu." + }, + { + "Id": "7372d2be-93a9-5c0c-b13e-8c73af8188cf", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.InvalidWizardFile", + "Value": "Not a valid wizard file." + }, + { + "Id": "c8bd3c55-214c-59e1-8b1b-e257045ee65a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.InvalidWizardFile", + "Value": "Geçerli bir wizard dosyası değil." + }, + { + "Id": "75dfd33f-62e7-562e-bf8b-e734921dfd89", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.WizardPending", + "Value": "'{0}' will be installed by DB Migrate." + }, + { + "Id": "a06e0e1b-7627-50a3-b77f-1b31b5a5ef5f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.WizardPending", + "Value": "'{0}' DB Migrate ile kurulacak." + }, + { + "Id": "94854e21-bd0b-571d-949c-ea722718cb5f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnAdded", + "Value": "{0} added." + }, + { + "Id": "8a080312-0a54-51a7-8ecc-a10c01c4a989", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnAdded", + "Value": "{0} eklendi." + }, + { + "Id": "b1afbf12-05ad-5342-a501-1e24de40f47a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnSkipped", + "Value": "{0} was not added — {1}." + }, + { + "Id": "12093f4f-0a1c-5651-b48c-79564c5116dc", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnSkipped", + "Value": "{0} eklenmedi — {1}." + }, + { + "Id": "12d4d09c-1b09-5564-848d-f42c887750c0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnFailed", + "Value": "{0} could not be added: {1}" + }, + { + "Id": "d9737360-25af-5d3b-976d-c44d0b2dc680", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.ColumnFailed", + "Value": "{0} eklenemedi: {1}" + }, + { + "Id": "c808ecf4-487f-5965-a9f6-d9f8384d7a1d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.BatchesExecuted", + "Value": "{0} batch(es) executed." + }, + { + "Id": "05d85461-1d51-5863-b8b9-dc397179fe94", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImportStep.BatchesExecuted", + "Value": "{0} batch çalıştırıldı." + }, + { + "Id": "ad7bb8be-db69-54a1-b91b-396ee61a3cf0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.ModuleDocNotFound", + "Value": "Module document not found: {0}" + }, + { + "Id": "6f442179-9fb1-5e4f-bff0-24f80c885ffc", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.ModuleDocNotFound", + "Value": "Modül dokümanı bulunamadı: {0}" + }, + { + "Id": "3627098f-4699-5a9a-9a87-4edd82ae3b48", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.InvalidModuleDocName", + "Value": "Invalid module document name: {0}" + }, + { + "Id": "cfe41d9e-5908-5e4a-8820-26e1d811cd9c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.InvalidModuleDocName", + "Value": "Geçersiz modül dokümanı adı: {0}" + }, + { + "Id": "2027c032-82f8-53f4-b16a-19c9a671c1cb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.ModuleDocContentEmpty", + "Value": "The document content cannot be empty." + }, + { + "Id": "7cbbb20b-2f01-5fca-9287-6ce26a2ddbf9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.ModuleDocContentEmpty", + "Value": "Doküman içeriği boş olamaz." + }, + { + "Id": "d2ad238a-6cdb-5140-9f6c-d77076e6122a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForAllUsers", + "Value": "This field name and language already exist for all users." + }, + { + "Id": "cb6975a7-eb00-5ec0-b0d9-a18aca891dcd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForAllUsers", + "Value": "Bu alan adı ve dil tüm kullanıcılar için zaten tanımlı." + }, + { + "Id": "c9019a72-1085-5bd0-80cb-2cc9f4c9c293", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForRole", + "Value": "This field name, language and role already exist." + }, + { + "Id": "e7332c89-7ad2-51ad-95a3-06ada6776c76", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForRole", + "Value": "Bu alan adı, dil ve rol zaten tanımlı." + }, + { + "Id": "60b68eb3-b283-5eef-858d-d1f79e699af9", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForUser", + "Value": "This field name, language and user already exist." + }, + { + "Id": "9c35303b-4e1e-587c-ba41-322870e0ca02", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.FieldExistsForUser", + "Value": "Bu alan adı, dil ve kullanıcı zaten tanımlı." + }, + { + "Id": "9bfb30f5-6b4f-5b8c-a7b8-7619ae82b367", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.SourceFieldNotFound", + "Value": "Source field not found." + }, + { + "Id": "a1390f8d-b649-5705-86f9-261744914d5a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.SourceFieldNotFound", + "Value": "Kaynak alan bulunamadı." + }, + { + "Id": "514d3d52-80dd-59e6-a95c-b74858decb2b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.CopyRecordNotFound", + "Value": "The record to copy was not found." + }, + { + "Id": "f81d8666-b933-559c-97fb-44c81bb4cf68", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.CopyRecordNotFound", + "Value": "Kopyalanacak kayıt bulunamadı." + }, + { + "Id": "68abcd91-2475-5cf8-9d5d-fef23719e34b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.TenantMismatch", + "Value": "Tenant mismatch: the header tenant '{0}' does not match the current tenant '{1}'." + }, + { + "Id": "d3f0668b-c11b-5f09-897e-88cb81ca2a10", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.TenantMismatch", + "Value": "Kiracı uyuşmuyor: başlıktaki '{0}' kiracısı geçerli '{1}' kiracısıyla eşleşmiyor." + }, + { + "Id": "9f50b26b-5b23-59ec-aff3-3a129092ef82", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.DataSourceNotFound", + "Value": "Data source '{0}' was not found." + }, + { + "Id": "4bec6e02-1859-5943-bb62-7d765f081d2b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.DataSourceNotFound", + "Value": "'{0}' veri kaynağı bulunamadı." + }, + { + "Id": "9d5ddf22-402c-5bb7-bec9-a2ed3b6625df", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.GridItemsNotCollection", + "Value": "The Grid items binding must return a collection." + }, + { + "Id": "49256ce1-19f1-57ef-8dc0-9e3b203289bc", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.GridItemsNotCollection", + "Value": "Grid items bağlantısı bir koleksiyon döndürmelidir." + }, + { + "Id": "50b71ee0-38bf-59b0-b604-e25f895c8490", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.GridSelectPreviewColumn", + "Value": "Select at least one column for the preview." + }, + { + "Id": "fa10b2d7-f015-5893-a91d-8d511284807d", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.GridSelectPreviewColumn", + "Value": "Önizleme için en az bir sütun seçin." + }, + { + "Id": "1e9e0fbb-d5fa-5f7b-8816-e0c63b5ec046", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.MenuItemFallback", + "Value": "Menu {0}" + }, + { + "Id": "71e7390a-4902-5bfe-9507-1ca1ebdcb812", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.MenuItemFallback", + "Value": "Menü {0}" + }, + { + "Id": "aa67b520-c9a7-597a-8f77-874483372b01", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.ItemFallback", + "Value": "Item {0}" + }, + { + "Id": "81739e04-3d1a-5b0b-ab6c-d403381d2f7f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.ItemFallback", + "Value": "Öğe {0}" + }, + { + "Id": "5601ca24-12fd-5e1f-877b-b96cbab44f6b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.DevExtremeLoadFailed", + "Value": "DevExtreme could not be loaded: {0}" + }, + { + "Id": "dbcced39-19a4-5616-9e99-8b02201f1828", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.DevExtremeLoadFailed", + "Value": "DevExtreme yüklenemedi: {0}" + }, + { + "Id": "f6ff651a-097f-5eda-9ad0-33584b0d569a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Platform.Info", + "Value": "Info" + }, + { + "Id": "c773797e-57ea-5010-849c-83170c044408", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Platform.Info", + "Value": "Bilgi" + }, + { + "Id": "a616f785-e2cc-5d47-9218-044fd5cda4b7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.UserAvatar", + "Value": "User Avatar" + }, + { + "Id": "11731f0c-8fce-5dcd-9a7c-c01664d8499f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.UserAvatar", + "Value": "Kullanıcı Avatarı" + }, + { + "Id": "62d6e7cf-5cec-5cad-bd40-f4b53265afb6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Button", + "Value": "Button" + }, + { + "Id": "a76274c8-a020-5ca0-ac64-febd47c55d51", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Button", + "Value": "Buton" + }, + { + "Id": "a5b3cec4-05a6-53db-aef5-cc12ed7e3b1c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.CardContent", + "Value": "Card content goes here. Lorem ipsum dolor sit amet consectetur adipisicing elit." + }, + { + "Id": "92220354-779d-5da3-b188-202793ac0e08", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.CardContent", + "Value": "Kart içeriği buraya gelir. Lorem ipsum dolor sit amet consectetur adipisicing elit." + }, + { + "Id": "ecc44156-dd22-5f39-bf11-e6846607fee0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.CardTitle", + "Value": "Card Title" + }, + { + "Id": "27bb4743-2f0f-574d-adee-1b504b0316b7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.CardTitle", + "Value": "Kart Başlığı" + }, + { + "Id": "d199d52b-97b3-546e-878a-e0341a2fd18e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Checkbox", + "Value": "Checkbox" + }, + { + "Id": "de68c7b5-c61a-542c-addd-0ba93a70bb4d", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Checkbox", + "Value": "Onay Kutusu" + }, + { + "Id": "37f0b5a3-c357-543f-bd2d-9d9cee07cfe3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.SelectDate", + "Value": "Select a date" + }, + { + "Id": "37e0c8d7-9c18-5c9c-adbe-e5a0616fcb89", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.SelectDate", + "Value": "Tarih seçin" + }, + { + "Id": "686d2074-f7ef-518c-983f-7fb6377d9897", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemA", + "Value": "Item A" + }, + { + "Id": "a11336d4-7f49-5477-acf6-248e89a1c2e9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemA", + "Value": "Öğe A" + }, + { + "Id": "92aa5a7f-0e4f-5a1f-b4dd-c84fba4162d3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemB", + "Value": "Item B" + }, + { + "Id": "ef731715-93ce-5c53-b8a2-8d90c2dee3e5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemB", + "Value": "Öğe B" + }, + { + "Id": "4df0ebf3-f3c4-5e7f-828b-5cacf68e1e3b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemC", + "Value": "Item C" + }, + { + "Id": "c125cae1-7d65-574d-ba87-ad900528cd39", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ItemC", + "Value": "Öğe C" + }, + { + "Id": "46b42272-01d0-524a-a45b-1bf7a57cee27", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.EnterText", + "Value": "Enter text..." + }, + { + "Id": "238b5542-24e1-5336-80e2-995d3a09e93c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.EnterText", + "Value": "Metin girin..." + }, + { + "Id": "62fc5e85-2cf7-5170-9817-6de1a9f95087", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Gallery", + "Value": "Gallery" + }, + { + "Id": "6cdbcca4-04de-501d-b469-ad7c79a76a1e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Gallery", + "Value": "Galeri" + }, + { + "Id": "3d5002e8-35d4-5d86-bbbe-c6002f2b5584", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NotificationMessage", + "Value": "This is a notification message" + }, + { + "Id": "46f781cd-ba3b-5d7d-9808-68082011b9a5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NotificationMessage", + "Value": "Bu bir bildirim mesajıdır" + }, + { + "Id": "04d4a8ca-52b2-5df0-855e-121f0242a141", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NotificationTitle", + "Value": "Notification Title" + }, + { + "Id": "573b975e-4466-5746-90de-36c7fb0737b0", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NotificationTitle", + "Value": "Bildirim Başlığı" + }, + { + "Id": "30afb58f-3c5a-5da4-b9a9-001af2021749", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionA", + "Value": "Option A" + }, + { + "Id": "d2a289c8-77bc-5186-a4f4-a06cea7c4aec", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionA", + "Value": "Seçenek A" + }, + { + "Id": "065dbda3-a6b9-51f0-aeee-b27d779872af", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionB", + "Value": "Option B" + }, + { + "Id": "6ea874bd-a843-534f-8634-709f6fe7c709", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionB", + "Value": "Seçenek B" + }, + { + "Id": "1275c866-855c-5c9a-a94b-f5aa74660353", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionC", + "Value": "Option C" + }, + { + "Id": "8211a834-7cc8-5b3e-adf2-5b8927d807b0", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.OptionC", + "Value": "Seçenek C" + }, + { + "Id": "f5fd0d80-2fa0-56ed-999f-b05b65c15eca", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab1", + "Value": "Tab 1" + }, + { + "Id": "78625244-4778-53c5-ae67-770195d02a7b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab1", + "Value": "Sekme 1" + }, + { + "Id": "2398a460-dc2d-5944-bd89-ca7cd5b19520", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab2", + "Value": "Tab 2" + }, + { + "Id": "c3367deb-f731-568d-abcc-88c390784ebe", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab2", + "Value": "Sekme 2" + }, + { + "Id": "fb71bdef-b23f-5a8c-80ba-d5b77a84f003", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab3", + "Value": "Tab 3" + }, + { + "Id": "3deceb91-e0cc-5675-b90b-b141d32c705e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tab3", + "Value": "Sekme 3" + }, + { + "Id": "a93a8e07-0f0d-5210-a2be-d35efa41ba92", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tag", + "Value": "Tag" + }, + { + "Id": "02104ad6-56d5-5cbd-9e76-dbc3d5daa0df", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Tag", + "Value": "Etiket" + }, + { + "Id": "27271684-9a7c-5d45-977b-00f7ea268b3a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.HoverMe", + "Value": "Hover over me" + }, + { + "Id": "4838962d-e80e-5d70-a726-0095dcb6645b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.HoverMe", + "Value": "Üzerime gel" + }, + { + "Id": "93a37b84-a2b7-594e-b4fd-919fadae2981", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.TooltipContent", + "Value": "Tooltip content" + }, + { + "Id": "e6fbd35e-721c-5f6c-afe0-361ed5837675", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.TooltipContent", + "Value": "Tooltip içeriği" + }, + { + "Id": "25d7c2c3-7228-5da8-b715-60496ff4bc83", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Sample", + "Value": "Sample" + }, + { + "Id": "d3995799-458f-535b-a61c-da8e6c341ed5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Sample", + "Value": "Deneme" + }, + { + "Id": "72653f48-0e97-58df-aece-7a526ee68f65", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Sample2", + "Value": "Sample 2" + }, + { + "Id": "8f7b517a-2db5-5003-bccf-6de321dc20d9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Sample2", + "Value": "Deneme 2" + }, + { + "Id": "ba7c2f48-de5c-5510-a4bb-a41c18154e08", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Image1", + "Value": "Image 1" + }, + { + "Id": "a5aaa418-73f8-5aa0-8226-f0614e9eea8f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Image1", + "Value": "Görsel 1" + }, + { + "Id": "eeaa30c6-3403-5d42-b241-c3a791b4ddcd", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Image2", + "Value": "Image 2" + }, + { + "Id": "dc0fa1fa-2b14-5b96-98a8-63bc8ae1e533", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.Image2", + "Value": "Görsel 2" + }, + { + "Id": "0465901a-c70c-56e6-9651-837cd6d1027b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NameRequired", + "Value": "Name is required" + }, + { + "Id": "4b203642-3647-50c6-bcb3-2d42e5ebed5b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.NameRequired", + "Value": "Ad alanı zorunludur" + }, + { + "Id": "eddf3355-55ce-575d-94cb-1fa6f4bd93ae", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ApprovedCannotDelete", + "Value": "An approved record cannot be deleted" + }, + { + "Id": "3ebc0d8d-dbe9-5127-b0a2-857cf7d25b6e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.ApprovedCannotDelete", + "Value": "Onaylı kayıt silinemez" + }, + { + "Id": "0cdc25ef-b2ac-51e9-aa76-a40b843fa5eb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.RecordCompleted", + "Value": "Record completed" + }, + { + "Id": "26f2c023-2dbc-57b7-a163-52308a5c52c3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentDefault.RecordCompleted", + "Value": "Kayıt tamamlandı" + }, + { + "Id": "ef4b1c08-4390-5157-9836-cd757d2f6243", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogOptions.OptionsLabel3", + "Value": "Option 3" + }, + { + "Id": "6b3f1dc0-43ad-5524-8ba1-1e5db58f1f64", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogOptions.OptionsLabel3", + "Value": "Seçenek 3" + }, + { + "Id": "e367e40f-7545-50e1-87aa-68e2d1ca2f12", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentRadioGroup.RadioGroupDescription", + "Value": "Radio button group" + }, + { + "Id": "530a2b6e-41d9-5f39-9f98-5741d46866da", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentRadioGroup.RadioGroupDescription", + "Value": "Radio buton grubu" + }, + { + "Id": "89b2926e-e29b-5107-9526-870da4c3a371", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentTable.TableDescription", + "Value": "Data table" + }, + { + "Id": "11058c69-b3be-5abe-b981-293387c067da", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentTable.TableDescription", + "Value": "Veri tablosu" + }, + { + "Id": "0f493f53-1b53-5267-97e1-956668475ca6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentTabs.TabsDescription", + "Value": "Tabbed navigation" + }, + { + "Id": "1582ff94-e59e-5104-aeb9-9bba1df74a07", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentTabs.TabsDescription", + "Value": "Sekmeli navigasyon" + }, + { + "Id": "8d37c3c1-bebc-5282-bf51-d562bd78527f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.TextContentDescription", + "Value": "Text content" + }, + { + "Id": "32ce5a72-f163-578b-bb9f-545f27d95441", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.TextContentDescription", + "Value": "Metin içeriği" + }, + { + "Id": "469d441f-ba2c-50b1-b332-6e8de5ba2873", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.HeadingTextDescription", + "Value": "Heading text" + }, + { + "Id": "759cc53f-f6be-57be-93f8-49824df30699", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.HeadingTextDescription", + "Value": "Başlık metni" + }, + { + "Id": "361abade-f6d4-5768-9131-38307d4f1486", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.ClickHandlerDescription", + "Value": "Click event handler" + }, + { + "Id": "b7f776f8-1792-579e-928d-740bc6c8fc65", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ComponentHtml.ClickHandlerDescription", + "Value": "Tıklama olayı işleyicisi" + }, + { + "Id": "3ed43da4-9ba4-5cb4-a5ef-7d3b38d51db6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogApiCalls.NotifyTypesDescription", + "Value": "notify types: success, info, warning, danger." + }, + { + "Id": "a923a610-20a1-54aa-b41b-9b8adc5c9d06", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogApiCalls.NotifyTypesDescription", + "Value": "notify tipleri: success, info, warning, danger." + }, + { + "Id": "c058f4d8-d078-5d05-942f-97147966b0b2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogApiCalls.FreeRequestTitle", + "Value": "Custom request" + }, + { + "Id": "0764d947-7f35-55e9-a78a-6fe46c632944", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogApiCalls.FreeRequestTitle", + "Value": "Serbest istek" + }, + { + "Id": "48b85b8a-0343-5e92-bf75-de8d9e10ff2d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogComponentAccess.ReadOnlyTitle", + "Value": "Make read-only" + }, + { + "Id": "a7d396ca-0590-5a4c-bf77-d541b74a283c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogComponentAccess.ReadOnlyTitle", + "Value": "Salt okunur yapma" + }, + { + "Id": "8ce46919-83a2-5fab-8358-3b9a41315eae", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogComponentAccess.FreePropertyTitle", + "Value": "Set any property" + }, + { + "Id": "e8b2be5e-2020-535c-90df-b2277b03e67f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogComponentAccess.FreePropertyTitle", + "Value": "Serbest property atama" + }, + { + "Id": "67e7d62b-f349-534a-9014-5fa45762b93c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.EventDataTitle", + "Value": "Event data" + }, + { + "Id": "8a954caf-3616-58f6-937c-9332e8393faa", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.EventDataTitle", + "Value": "Event verisi" + }, + { + "Id": "4c417152-06de-5755-84de-1e7250ab36a6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.ComponentInfoTitle", + "Value": "The component's own info" + }, + { + "Id": "b4540c9e-7fa4-5186-be62-2b7ffa70bc4c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.ComponentInfoTitle", + "Value": "Komponentin kendi bilgileri" + }, + { + "Id": "d1252309-5526-5af6-b5d3-0ceaa8c50ed5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.UrlParamTitle", + "Value": "Read a URL parameter" + }, + { + "Id": "bc1e89cf-db04-57f6-8013-fda92b085759", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogEventAndPage.UrlParamTitle", + "Value": "URL parametresi okuma" + }, + { + "Id": "291f7ab3-dbe6-5554-b067-59ba3c46daf0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.CatalogGenerated.GeneratedUiDescription", + "Value": "Sozsoft UI component generated from its props contract" + }, + { + "Id": "6737d3cd-efad-524c-a640-9e346f6dc406", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.CatalogGenerated.GeneratedUiDescription", + "Value": "Props sözleşmesinden üretilen Sozsoft UI bileşeni" + }, + { + "Id": "e66dda39-a293-59fe-9724-23791ce21655", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentMissing", + "Value": "{0}: ComponentName is not defined" + }, + { + "Id": "c4274bbf-2cb2-5eb1-a9af-ceb64685fab2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentMissing", + "Value": "{0}: ComponentName tanımlı değil" + }, + { + "Id": "f51c6861-b89b-5a80-aab1-84abfc32cc14", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentLoading", + "Value": "{0} is loading…" + }, + { + "Id": "933a85ba-19d2-5bcb-a218-a0c611b55430", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentLoading", + "Value": "{0} yükleniyor…" + }, + { + "Id": "b9da78db-1508-520e-8466-2e5077e36394", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentNotFound", + "Value": "{0} not found or inactive" + }, + { + "Id": "e4e454ad-73a0-59cb-9dc5-c09e952434f2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetComponentNotFound", + "Value": "{0} bulunamadı veya pasif" + }, + { + "Id": "d1414c26-fd27-5181-b62b-513cec9d59f1", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetNotRegistered", + "Value": "{0}: no widget is registered in code" + }, + { + "Id": "eb84b09d-12c1-5d3a-ba63-bbf6eeee2b24", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Dashboard.WidgetNotRegistered", + "Value": "{0}: koda kayıtlı bir widget yok" + }, + { + "Id": "c9d4af88-ab33-5f30-a6a0-a6e6cae969c8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.LoadFailed", + "Value": "Failed to load files and folders" + }, + { + "Id": "d3564780-662d-5db1-ba1f-5a25654ccdd4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.LoadFailed", + "Value": "Dosya ve klasörler yüklenemedi" + }, + { + "Id": "1a9501e2-a5a4-5e35-8b55-a9278f22344a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.UploadSuccess", + "Value": "Files uploaded successfully" + }, + { + "Id": "e9020802-f83a-582a-b68d-7d374c0e68a3", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.UploadSuccess", + "Value": "Dosyalar başarıyla yüklendi" + }, + { + "Id": "be492f83-bbbd-5f47-9935-44c380274714", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.UploadFailed", + "Value": "Failed to upload files" + }, + { + "Id": "2a2d3dab-c364-5d2a-a5e5-2b47ef6a82da", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.UploadFailed", + "Value": "Dosyalar yüklenemedi" + }, + { + "Id": "32e42984-6c5f-5ae8-9e40-bf39f451db37", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderCreated", + "Value": "Folder created successfully" + }, + { + "Id": "6918b8a3-935a-5aa6-90aa-b28b16d9aeb2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderCreated", + "Value": "Klasör başarıyla oluşturuldu" + }, + { + "Id": "ada6a139-d096-5fc8-a988-d9564b741cfd", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderCreateFailed", + "Value": "Failed to create folder" + }, + { + "Id": "6a4cbd6c-cc6a-5201-a30d-6c89dccff932", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderCreateFailed", + "Value": "Klasör oluşturulamadı" + }, + { + "Id": "d1ae2f1b-9112-5c9b-826d-7f65b9378f6f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.RenameSuccess", + "Value": "Item renamed successfully" + }, + { + "Id": "7fe14d93-7d93-5986-a1a5-80bcbc7ae805", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.RenameSuccess", + "Value": "Öğe başarıyla yeniden adlandırıldı" + }, + { + "Id": "51e6297d-10cf-50d1-80d3-925c3a9547a7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.RenameFailed", + "Value": "Failed to rename item" + }, + { + "Id": "f73da6a7-f2f3-5b5f-a385-9910c78a8dee", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.RenameFailed", + "Value": "Öğe yeniden adlandırılamadı" + }, + { + "Id": "9d3c97f4-b416-5f03-8dcc-565037370cc0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteSuccess", + "Value": "Items deleted successfully" + }, + { + "Id": "45f868e7-d934-5d4b-8b2c-b0e2da5ac9bb", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteSuccess", + "Value": "Öğeler başarıyla silindi" + }, + { + "Id": "ef94e372-5771-586c-b32d-dc205d195790", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteFailed", + "Value": "Failed to delete items" + }, + { + "Id": "11db423e-5a40-55ef-8293-6d51560b428e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.DeleteFailed", + "Value": "Öğeler silinemedi" + }, + { + "Id": "c224177b-04d8-5f4e-9fce-a769bdf5d01b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.DownloadFailed", + "Value": "Failed to download file" + }, + { + "Id": "f1f4eac3-7642-5729-ac54-4281d92c1cff", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.DownloadFailed", + "Value": "Dosya indirilemedi" + }, + { + "Id": "cac27f25-2f45-5ade-b128-2c58d145d221", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.InvalidFileLocation", + "Value": "File location is invalid" + }, + { + "Id": "7537c64a-d148-5de3-ada9-6bdcb62f81d7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.InvalidFileLocation", + "Value": "Dosya konumu geçersiz" + }, + { + "Id": "efa75d63-83ee-5a42-8a88-a2f6204e30c6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SecurityWarning", + "Value": "Security Warning" + }, + { + "Id": "5e89a006-0749-5b1d-9afb-b0422fd1a0b5", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SecurityWarning", + "Value": "Güvenlik Uyarısı" + }, + { + "Id": "c9ed6f95-8843-50b2-bdf2-78ba9cf8b609", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedCannotDelete", + "Value": "Protected system folders cannot be deleted." + }, + { + "Id": "35c25fa2-41dc-5098-9950-c645f566651f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedCannotDelete", + "Value": "Korumalı sistem klasörleri silinemez." + }, + { + "Id": "8bbc3e8c-eb11-5e72-b7f0-0e0f0b87cdcb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedCannotRename", + "Value": "Protected system folders cannot be renamed." + }, + { + "Id": "271d238c-a5a6-5a9b-8a51-b93136b354b4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedCannotRename", + "Value": "Korumalı sistem klasörleri yeniden adlandırılamaz." + }, + { + "Id": "10e0dcde-636a-5860-b400-6bb83cd2b034", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderHasFiles", + "Value": "Folder {0} contains files and cannot be deleted for security reasons." + }, + { + "Id": "2def4edf-8d24-5f67-bdc5-bf3c04c7c7ad", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FolderHasFiles", + "Value": "{0} klasörü dosya içerdiği için güvenlik nedeniyle silinemez." + }, + { + "Id": "f60eed30-862d-5a53-99f5-686e53e97622", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotDelete", + "Value": "{0} protected system folder(s) cannot be deleted: {1}" + }, + { + "Id": "41145676-c373-59a2-8817-a27e0b61480f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotDelete", + "Value": "{0} korumalı sistem klasörü silinemez: {1}" + }, + { + "Id": "2a6d9316-3c09-5c7f-a326-732be22938aa", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotCopy", + "Value": "{0} protected system folder(s) cannot be copied: {1}" + }, + { + "Id": "a72c2976-355d-51b0-8e5f-f9f85306c50d", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotCopy", + "Value": "{0} korumalı sistem klasörü kopyalanamaz: {1}" + }, + { + "Id": "70a7ffe1-2ca6-5dad-a248-c506c9c243f3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotMove", + "Value": "{0} protected system folder(s) cannot be moved: {1}" + }, + { + "Id": "1b93c63c-5385-5b34-a407-7d831813b606", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ProtectedItemsCannotMove", + "Value": "{0} korumalı sistem klasörü taşınamaz: {1}" + }, + { + "Id": "5c500c95-7b69-56d6-91f4-aa3d83d4a947", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.FoldersWithFiles", + "Value": "{0} folder(s) containing files cannot be deleted for security reasons: {1}" + }, + { + "Id": "3ce8ee05-7e20-5cfa-9731-8ecae7ce6d3e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.FoldersWithFiles", + "Value": "Dosya içeren {0} klasör güvenlik nedeniyle silinemez: {1}" + }, + { + "Id": "b2b6606a-14ad-5b16-8d55-c8f04339ed8a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NothingToDelete", + "Value": "No items can be deleted. Selected items are either protected or folders containing files." + }, + { + "Id": "3766d8ad-0360-5828-bb04-d7c6ac96434f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NothingToDelete", + "Value": "Silinebilecek öğe yok. Seçilen öğeler korumalı ya da dosya içeren klasörler." + }, + { + "Id": "425c3f54-7740-5c19-bd43-f244a7ddfa37", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.Cut", + "Value": "Cut" + }, + { + "Id": "bd7dbaf0-a648-576d-9e7b-c05f587b7f0f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.Cut", + "Value": "Kesildi" + }, + { + "Id": "15d4b7bb-0a24-5302-811c-fca64794fe85", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCopiedToClipboard", + "Value": "{0} item(s) copied to clipboard" + }, + { + "Id": "55f07925-2f9d-56ce-943a-fc1c916847cd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCopiedToClipboard", + "Value": "{0} öğe panoya kopyalandı" + }, + { + "Id": "fec63087-71ad-5a85-b77c-74f54584f9f0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCutToClipboard", + "Value": "{0} item(s) cut to clipboard" + }, + { + "Id": "7f71d602-ea56-5dba-862e-3d48575145e9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCutToClipboard", + "Value": "{0} öğe taşınmak üzere panoya alındı" + }, + { + "Id": "3450bc36-1e80-514b-b07b-4e30eb4c98ae", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ClipboardEmpty", + "Value": "Clipboard Empty" + }, + { + "Id": "31703593-79f5-51eb-8913-a6a0a3acd763", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ClipboardEmpty", + "Value": "Pano Boş" + }, + { + "Id": "22b83b08-8b02-5db2-83f6-4f91c8c9a0db", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsInClipboard", + "Value": "No items in clipboard" + }, + { + "Id": "8bd60410-6a49-511e-9e95-71c3063eb6e8", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NoItemsInClipboard", + "Value": "Panoda öğe yok" + }, + { + "Id": "f28334c5-4fa0-5af8-80f1-18c6615ea0f1", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.CrossTenantPaste", + "Value": "Items cannot be pasted into a different tenant" + }, + { + "Id": "2a56cb63-4660-50c9-b117-49095c5c4cb9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.CrossTenantPaste", + "Value": "Öğeler farklı bir kiracıya yapıştırılamaz" + }, + { + "Id": "6448dc19-55b3-5aff-bd20-fde6ea572839", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SameFolderMove", + "Value": "Cannot move items to the same folder" + }, + { + "Id": "727dc774-5596-5b65-95e1-62729a53c4b8", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SameFolderMove", + "Value": "Öğeler aynı klasöre taşınamaz" + }, + { + "Id": "a2a798f4-439b-5c44-a456-5a6eb0149cc4", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCopied", + "Value": "{0} item(s) copied successfully" + }, + { + "Id": "287037ce-e61d-5592-9e6d-5db6d76b1d18", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsCopied", + "Value": "{0} öğe başarıyla kopyalandı" + }, + { + "Id": "574ba831-1622-545e-852e-4b68eb03e428", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsMoved", + "Value": "{0} item(s) moved successfully" + }, + { + "Id": "19952211-3d78-5b24-8d52-01e971f8d707", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ItemsMoved", + "Value": "{0} öğe başarıyla taşındı" + }, + { + "Id": "f60d4a85-020d-55b1-a41e-2a698c6f301f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.CopyFailed", + "Value": "Failed to copy items" + }, + { + "Id": "6ef738cb-4693-5c37-a946-7d03ac0b043f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.CopyFailed", + "Value": "Öğeler kopyalanamadı" + }, + { + "Id": "190bc4ec-b3c7-5602-be65-94d54b0818a0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.MoveFailed", + "Value": "Failed to move items" + }, + { + "Id": "a3067d9e-ca08-50d2-9fac-052ec62bb693", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.MoveFailed", + "Value": "Öğeler taşınamadı" + }, + { + "Id": "caa48e3c-1e42-5ce0-b933-6c4e2b8b042e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.ImagePreviewSoon", + "Value": "Image preview feature will be added soon." + }, + { + "Id": "ebddbed2-37ff-53e1-9546-6f6443914795", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.ImagePreviewSoon", + "Value": "Görsel önizleme özelliği yakında eklenecek." + }, + { + "Id": "ba453468-4764-5c17-8b25-e6d76be3742a", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.GoUpOneLevel", + "Value": "Go up one level" + }, + { + "Id": "f8cca681-42e3-52e6-9087-e66a7c51b24c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.GoUpOneLevel", + "Value": "Bir üst klasöre çık" + }, + { + "Id": "9f98c22a-7c46-581c-8916-ac5526dcb07e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.SelectAllSelectable", + "Value": "Select all selectable items" + }, + { + "Id": "ff1b3e2e-9079-5904-b953-a845fff5f014", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.SelectAllSelectable", + "Value": "Seçilebilir tüm öğeleri seç" + }, + { + "Id": "f92edbd7-1341-59f1-99b2-9745d0e3e8b7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.DeselectAllSelectable", + "Value": "Deselect all selectable items" + }, + { + "Id": "ad78fbc8-baef-5dd9-8e7b-bdf5ba98dcce", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.DeselectAllSelectable", + "Value": "Seçilebilir tüm öğelerin seçimini kaldır" + }, + { + "Id": "e0c3facc-a57c-5a45-85bf-a792ca9ba406", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.NoSearchMatch", + "Value": "No files match your search" + }, + { + "Id": "fba72e25-8ce2-53e4-8464-84ab5347ae0f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.NoSearchMatch", + "Value": "Aramanızla eşleşen dosya yok" + }, + { + "Id": "19b4635f-aa00-5bdd-80d2-14a94de3b6c8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.FileManager.EmptyFolder", + "Value": "This folder is empty" + }, + { + "Id": "e8763a19-6958-5e61-96b2-601d03a97dbe", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.FileManager.EmptyFolder", + "Value": "Bu klasör boş" + }, + { + "Id": "9c43c2fc-e2c3-5c17-aeb2-5bce927fee9e", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Tenant.EnterOrgCode", + "Value": "Enter your organization code" + }, + { + "Id": "704b8523-9271-5df0-a6ee-76a8ec76b522", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Tenant.EnterOrgCode", + "Value": "Kurum kodunuzu girin" + }, + { + "Id": "e5389a38-2b4b-56cf-b38a-62c218902d42", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Tenant.EnterFounderName", + "Value": "Enter the founder's name" + }, + { + "Id": "90c28cfb-f000-5589-9b9e-6c742a2ef463", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Tenant.EnterFounderName", + "Value": "Kurucunun adını girin" + }, + { + "Id": "7c57b81b-9031-5af9-b8b7-8d36ab0defd3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Tenant.TaxOfficePlaceholder", + "Value": "Tax office name" + }, + { + "Id": "d5dee8b5-6296-5a15-9345-80a544d6ad74", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Tenant.TaxOfficePlaceholder", + "Value": "Vergi dairesi adı" + }, + { + "Id": "74401b9a-e929-5426-afd3-7c1bd3bf22ed", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.PresetPhoneMask.InvalidMessage", + "Value": "Please enter a valid phone number" + }, + { + "Id": "c8be95df-a6c0-5a09-83b6-73a3b1815a93", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.PresetPhoneMask.InvalidMessage", + "Value": "Lütfen geçerli bir telefon numarası girin" + }, + { + "Id": "fbbe225c-f4f7-5ecf-91ff-7ec5e21bfebc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NoWizardFile", + "Value": "The zip contains no wizard file; only dependency files will be imported." + }, + { + "Id": "9050eaea-aba4-5774-86e1-f3dad9a17ede", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NoWizardFile", + "Value": "Zip içinde wizard dosyası yok; yalnızca bağımlılık dosyaları aktarılacak." + }, + { + "Id": "dec4b5ae-5c42-54ee-ad46-15806208cf2d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ProviderMismatch", + "Value": "The zip only contains scripts for the '{0}' provider; this environment uses '{1}', so the scripts will not be executed." + }, + { + "Id": "56dedf2a-441e-509b-a221-1002886f3401", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ProviderMismatch", + "Value": "Zip yalnızca '{0}' sağlayıcısının scriptlerini içeriyor; bu ortam '{1}' kullanıyor ve scriptler çalıştırılmayacak." + }, + { + "Id": "3073097e-2fb2-59dc-a2bd-18d933249adf", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CustomComponentNameEmpty", + "Value": "{0}: the custom component name is empty." + }, + { + "Id": "8ee7d805-a1bb-590d-938c-11586f4cec33", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CustomComponentNameEmpty", + "Value": "{0}: custom component adı boş." + }, + { + "Id": "f4627fb5-0fcd-504a-93f3-198c7c76d64d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CustomComponentRouteEmpty", + "Value": "{0}: the custom component route path is empty; the menu leads nowhere." + }, + { + "Id": "a3d933f6-03e4-5323-8ae4-258e9f6d214c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CustomComponentRouteEmpty", + "Value": "{0}: custom component rota yolu boş; menü hiçbir yere gitmez." + }, + { + "Id": "8a04ac46-6fb8-508a-b03c-924d530d677f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ListFormSectionMissing", + "Value": "{0}: the ListForm section is missing; the screen cannot be installed." + }, + { + "Id": "c9fef8dd-f234-5838-96fa-f697d1d47d4a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ListFormSectionMissing", + "Value": "{0}: ListForm bölümü eksik, ekran kurulamaz." + }, + { + "Id": "5cae535b-d377-53af-9af3-b4dfde4497d2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SelectCommandEmpty", + "Value": "{0}: the data source object (SelectCommand) is empty." + }, + { + "Id": "541b939f-f70e-5ff0-a0b0-75a7cd2bf23c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SelectCommandEmpty", + "Value": "{0}: veri kaynağı nesnesi (SelectCommand) boş." + }, + { + "Id": "930d7348-cc6f-5ea9-9d61-33ac828536f7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ScreenTable", + "Value": "Table of the {0} screen" + }, + { + "Id": "2da8e277-3a38-55ae-8f77-09c696b1b8c6", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ScreenTable", + "Value": "{0} ekranının tablosu" + }, + { + "Id": "6a722d05-fb9a-5092-93e0-82508ae0777c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SeedFileMissing", + "Value": "{0}: the screen's data file ({1}) is neither in the zip nor in the target; the screen will be empty." + }, + { + "Id": "2c3d33b3-d9ae-5ab1-b83d-661823258054", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SeedFileMissing", + "Value": "{0}: ekranın veri dosyası ({1}) ne zip'te ne hedefte var; ekran boş gelecek." + }, + { + "Id": "9a184b4c-863d-599a-91a5-bcb68c30246c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SubFormMissing", + "Value": "{0}: sub form '{1}' is neither in the zip nor in the target; the tab will open empty." + }, + { + "Id": "6783db16-00ee-5fe2-a963-274a8e8fa347", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SubFormMissing", + "Value": "{0}: '{1}' alt formu ne zip'te ne hedefte var; sekme boş açılır." + }, + { + "Id": "5e576183-eb86-50f3-a5d8-e75e7efd8b31", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CrudNoEndpoints", + "Value": "{0}: the CRUD file has no endpoints." + }, + { + "Id": "2256adeb-e294-5f48-be25-afcfc17c2781", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CrudNoEndpoints", + "Value": "{0}: CRUD dosyasında endpoint yok." + }, + { + "Id": "ee260bc5-04bf-541e-b209-dad3ed3e87ca", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CrudTable", + "Value": "Table of the {0} CRUD endpoints" + }, + { + "Id": "54f4c9ff-12fe-5644-8934-8f3d32e8a761", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.CrudTable", + "Value": "{0} CRUD endpoint'lerinin tablosu" + }, + { + "Id": "115dd8e1-8aff-56d4-b73e-df2222e58eab", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DataScreenMissing", + "Value": "Screen '{0}' is neither in the zip nor in the target; the data rows cannot be applied." + }, + { + "Id": "824882ad-1814-5df3-af6f-4e4f7c399f5a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DataScreenMissing", + "Value": "'{0}' ekranı ne zip'te ne hedefte var; veri satırları uygulanamaz." + }, + { + "Id": "23b57954-e048-5381-a760-53dc85d029ef", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DataNoRows", + "Value": "{0}: the data file has no rows." + }, + { + "Id": "cfed1f1c-7cd4-5460-86b6-4056444d9430", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DataNoRows", + "Value": "{0}: veri dosyasında satır yok." + }, + { + "Id": "6ed53906-78e3-56ad-966b-d3dedc05ab22", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnDifference", + "Value": "{0}: {1}. The platform does not change an existing column — narrowing truncates data and a type conversion may fail. Edit the column in the table designer and restart the import." + }, + { + "Id": "c140df78-e816-54d6-a581-eccaff5e0943", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnDifference", + "Value": "{0}: {1}. Platform var olan bir kolonu değiştirmez — daraltma veri keser, tip dönüşümü başarısız olabilir. Kolonu tablo tasarımcısından düzenleyip import'u tekrar başlatın." + }, + { + "Id": "ad6912f6-2339-52b0-a65a-441c4cfe3080", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnWillBeAdded", + "Value": "{0}: the column does not exist in the target and will be added when the import completes. Because the table already exists, the script's CREATE block does not run; the column is added with the statement below." + }, + { + "Id": "f544243f-a13b-5583-acbe-f9269f265f44", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnWillBeAdded", + "Value": "{0}: kolon hedefte yok, import tamamlanırken eklenecek. Tablo zaten var olduğu için script'in CREATE bloğu çalışmaz; kolon aşağıdaki cümleyle eklenir." + }, + { + "Id": "a7134dc7-16f5-5176-97ff-b26f94512ccb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnBlocked", + "Value": "{0}: the column does not exist in the target and cannot be added automatically — {1}. Which value to write into the column is a business decision; add the column in the table designer with a default value or as nullable and restart the import." + }, + { + "Id": "000b6046-78d9-5343-ba4a-6ce0e937e4b9", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ColumnBlocked", + "Value": "{0}: kolon hedefte yok ve otomatik eklenemez — {1}. Kolona hangi değerin yazılacağı bir iş kararıdır; kolonu tablo tasarımcısından bir varsayılan değerle ya da NULL kabul edecek şekilde ekleyip import'u tekrar başlatın." + }, + { + "Id": "2c1a6464-bc7f-5328-8f64-323f433bc6da", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ExtraColumns", + "Value": "{0}: the following target columns are not in the script and will be left as they are — {1}." + }, + { + "Id": "ac9a4d90-b0c5-59e2-903a-2673a19aeccd", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ExtraColumns", + "Value": "{0}: hedefteki şu kolonlar script'te yok, oldukları gibi bırakılacak — {1}." + }, + { + "Id": "f8b1fdf0-be65-5991-bb6d-9dc24843fe2d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentRowNameless", + "Value": "There is a custom component row without a name." + }, + { + "Id": "eb422232-8ce6-5f59-b0ca-4f938d7b309c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentRowNameless", + "Value": "Adı olmayan bir custom component satırı var." + }, + { + "Id": "9e03bfb7-93e2-5ef2-af03-f1489a958ae3", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentDependencyMissing", + "Value": "{0}: dependency '{1}' is neither in the zip nor in the target." + }, + { + "Id": "2353392f-cc98-56f6-86b5-3e531cb1bb67", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentDependencyMissing", + "Value": "{0}: '{1}' bağımlılığı ne zip'te ne hedefte var." + }, + { + "Id": "aa1634a2-165e-52b9-a04a-851082866f1b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentCrudMissing", + "Value": "{0}: the CRUD file of the data source ({1}) is neither in the zip nor in the target." + }, + { + "Id": "675752a0-916c-51c4-bf60-a480c611f590", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentCrudMissing", + "Value": "{0}: veri kaynağının CRUD dosyası ({1}) ne zip'te ne hedefte var." + }, + { + "Id": "dca0e15e-7876-50b4-8ed5-b5e1ee4acefb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentMissing", + "Value": "The definition of component '{0}' is neither in the zip nor in the target; the menu is bound to an empty route." + }, + { + "Id": "b52b6ee2-3a01-53eb-8553-dfe0f3046073", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ComponentMissing", + "Value": "'{0}' bileşeninin tanımı ne zip'te ne hedefte var; menü boş bir rotaya bağlanır." + }, + { + "Id": "c1e35af5-c0f3-5902-8e65-6de277a355a7", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectUnverified", + "Value": "{0} ('{1}') could not be verified in the database, and its script is not in the zip either." + }, + { + "Id": "a91385cd-edfa-5ce8-8db3-07616720894e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectUnverified", + "Value": "{0} ('{1}') veritabanında doğrulanamadı; scripti zip'te de yok." + }, + { + "Id": "9513d144-9f90-5b9d-aa98-99165fcf8d44", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectMissing", + "Value": "{0} ('{1}') does not exist in the database and the zip does not contain the .sql file that creates it." + }, + { + "Id": "acb7927c-e289-5635-aadc-9b4998ab3b2b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectMissing", + "Value": "{0} ('{1}') veritabanında yok ve zip onu oluşturan .sql dosyasını içermiyor." + }, + { + "Id": "b8d1a1f4-923c-56ca-bb10-736c92ec545b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectMissingRepairHint", + "Value": "You can create the table in the table designer and continue the import." + }, + { + "Id": "ab7c8fcf-3bcb-50e5-87ef-ff8108d6cfbc", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.ObjectMissingRepairHint", + "Value": "Tabloyu tablo tasarımcısından oluşturup import'a devam edebilirsiniz." + }, + { + "Id": "6181e3c8-2c89-547f-81ff-0e2aef17ad8d", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SubFormsUnreadable", + "Value": "The sub form definition (SubFormsJson) could not be parsed." + }, + { + "Id": "71d71e40-9b7d-5833-8810-cde4adf7fd4a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.SubFormsUnreadable", + "Value": "Alt form tanımı (SubFormsJson) çözülemedi." + }, + { + "Id": "bdb3284b-d80e-538b-991a-8219590604d2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.InvalidWizardFile", + "Value": "Not a valid wizard file (the Wizard section or its name is missing)." + }, + { + "Id": "03107506-23e9-5676-8d93-3ed4dfc6ceef", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.InvalidWizardFile", + "Value": "Geçerli bir wizard dosyası değil (Wizard bölümü ya da adı yok)." + }, + { + "Id": "c08ede45-0c57-5c33-beca-0b9c5b6fcd20", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.WizardFileCorrupt", + "Value": "The wizard file could not be read (invalid JSON)." + }, + { + "Id": "90ef5f99-71b8-55e0-880f-54842295b7b4", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.WizardFileCorrupt", + "Value": "Wizard dosyası okunamadı (bozuk JSON)." + }, + { + "Id": "4de95ba9-2dc6-57aa-b59a-b57d5c3aab93", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.FileCorrupt", + "Value": "The file could not be read (invalid JSON)." + }, + { + "Id": "5237f723-0fd3-57a3-86f8-dc83f3ec3102", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.FileCorrupt", + "Value": "Dosya okunamadı (bozuk JSON)." + }, + { + "Id": "097133b7-f304-5208-96eb-31da7a5b18a8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.BlockNotNullRowsUnknown", + "Value": "the column is NOT NULL without a default value, and it could not be read whether the table has rows" + }, + { + "Id": "d9c026d7-5f19-5115-9545-9cd98d0bba12", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.BlockNotNullRowsUnknown", + "Value": "kolon NOT NULL ve varsayılan değeri yok; tabloda kayıt olup olmadığı okunamadı" + }, + { + "Id": "2b459a99-c1ff-5574-ac7f-e47c72b837bc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.BlockNotNullHasRows", + "Value": "the column is NOT NULL without a default value, and the table has rows" + }, + { + "Id": "70daadba-6539-5f85-9338-506669615528", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.BlockNotNullHasRows", + "Value": "kolon NOT NULL ve varsayılan değeri yok, tabloda kayıt var" + }, + { + "Id": "7f0a0264-67b9-573a-8dc4-ca2316fc0861", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffLength", + "Value": "length is {0} in the target, {1} in the script" + }, + { + "Id": "07504b0e-be18-51c1-bdf6-d5bc115c100f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffLength", + "Value": "uzunluk hedefte {0}, script'te {1}" + }, + { + "Id": "72bc2c39-b233-5e23-b556-1037746a114c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffPrecision", + "Value": "precision is {0} in the target, {1} in the script" + }, + { + "Id": "ef79958e-2784-5e65-9400-6e8fa7acbdba", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffPrecision", + "Value": "kesinlik hedefte {0}, script'te {1}" + }, + { + "Id": "22dff8b5-584e-5944-aa1e-90da641f97f5", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffScale", + "Value": "scale is {0} in the target, {1} in the script" + }, + { + "Id": "cc8ab38a-1e8a-59d3-bc14-20342c31ec69", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffScale", + "Value": "ondalık hedefte {0}, script'te {1}" + }, + { + "Id": "8f18f61d-50c7-550a-b06c-47359da287e2", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffNullable", + "Value": "null values are {0} in the target, {1} in the script" + }, + { + "Id": "f81eba14-f8a5-5213-b850-98504d9127b1", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.DiffNullable", + "Value": "boş değer hedefte {0}, script'te {1}" + }, + { + "Id": "79953ddb-b611-5fde-b778-ebc0ddb77bc1", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NullAllowed", + "Value": "allowed" + }, + { + "Id": "7c70cb2c-0e36-5f71-8c92-c7328ff6b760", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NullAllowed", + "Value": "serbest" + }, + { + "Id": "5931028c-4f93-5039-add6-8140c961b0bc", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NullNotAllowed", + "Value": "not allowed" + }, + { + "Id": "5cc1937f-a3f1-51e8-8bdd-45892945c73c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.ListForm.WizardImport.NullNotAllowed", + "Value": "yasak" + }, + { + "Id": "bebf4f4a-04b4-5685-b817-0b3e74154115", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.CompileFailedWithCount", + "Value": "Compilation failed with {0} error(s)." + }, + { + "Id": "fbc3e0a6-b4d9-5ab4-b978-2bae3ce410da", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.CompileFailedWithCount", + "Value": "Derleme {0} hata ile başarısız oldu." + }, + { + "Id": "bf7312c8-0e2d-5bbe-ae3f-0a7e5e4c3fbb", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.CompileUnexpectedError", + "Value": "Error during compilation: {0}" + }, + { + "Id": "2c57308b-6fb2-50cd-a114-9cbbd636148c", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.CompileUnexpectedError", + "Value": "Derleme sırasında hata: {0}" + }, + { + "Id": "5d786e8a-4427-513c-8511-bb01d5c1b5d9", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.AssemblyLoadFailed", + "Value": "The compiled assembly could not be loaded: {0}" + }, + { + "Id": "f1370a97-130a-5e53-8e79-f31643e7810a", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.AssemblyLoadFailed", + "Value": "Derlenen assembly yüklenemedi: {0}" + }, + { + "Id": "882b904a-785d-5c74-8b66-149d70f0bf8f", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.ForbiddenNamespace", + "Value": "The '{0}' namespace cannot be used for security reasons." + }, + { + "Id": "e6971f96-92e0-5c41-83f6-110c6f4afaec", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.ForbiddenNamespace", + "Value": "Güvenlik nedeniyle '{0}' namespace'i kullanılamaz." + }, + { + "Id": "61936aea-2564-5a54-8544-80e281987276", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.ForbiddenType", + "Value": "The '{0}' type cannot be used for security reasons." + }, + { + "Id": "ab8f7d34-e4b1-5b03-8573-c18a8eb7cead", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.ForbiddenType", + "Value": "Güvenlik nedeniyle '{0}' tipi kullanılamaz." + }, + { + "Id": "399b9a81-4aee-562b-98e2-f222b6c961a6", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.SecurityCheckFailed", + "Value": "Security check failed." + }, + { + "Id": "7cc6e472-aa21-586a-9a30-53902c3a741e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.SecurityCheckFailed", + "Value": "Güvenlik kontrolü başarısız." + }, + { + "Id": "1038632f-3c7c-594b-a6f4-28896378492c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.PublishFailedWithError", + "Value": "The dynamic service could not be published: {0}" + }, + { + "Id": "007566b2-2fc4-5125-b59f-c7efdbe80e99", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.DynamicServicesEditor.PublishFailedWithError", + "Value": "Dinamik servis yayınlanamadı: {0}" + }, + { + "Id": "0f98ee8e-7d0f-57d1-a3b1-ea2a011af509", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.BranchSeed.NotFound", + "Value": "Branch not found." + }, + { + "Id": "bcaa7913-8f96-5304-9779-770a98c6ea5e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.BranchSeed.NotFound", + "Value": "Şube bulunamadı." + }, + { + "Id": "6dc15b93-30fb-5993-a707-f01f47e0ee6c", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.BranchSeed.DataUnreadable", + "Value": "BranchData.json could not be read or has an invalid format." + }, + { + "Id": "70ff5b38-635d-5fba-aae2-fc8fc6998c0b", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.BranchSeed.DataUnreadable", + "Value": "BranchData.json okunamadı veya format hatalı." + }, + { + "Id": "ee42959f-d177-5ea9-a881-b086794a2bc0", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.BranchSeed.Completed", + "Value": "The seed operation completed successfully. {0} records were inserted in total." + }, + { + "Id": "f45534b1-1b66-53b1-b489-247b2bcf2dff", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.BranchSeed.Completed", + "Value": "Seed işlemi başarıyla tamamlandı. Toplam {0} kayıt eklendi." + }, + { + "Id": "f733df5c-56eb-5e99-97b0-abfcf1f64843", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.BranchSeed.ErrorWithMessage", + "Value": "Error: {0}" + }, + { + "Id": "5724830f-fc82-5c0e-b748-cfa27ba84831", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.BranchSeed.ErrorWithMessage", + "Value": "Hata: {0}" + }, + { + "Id": "c2c8a964-3ac5-5133-be11-8161548adc69", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.QueryExecutionFailed", + "Value": "Query execution failed: {0}" + }, + { + "Id": "517f1c88-efe2-54de-a8e5-07bd5e9938e7", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.QueryExecutionFailed", + "Value": "Sorgu çalıştırılamadı: {0}" + }, + { + "Id": "91e3173b-d0dd-505d-b190-df123327618b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.CommandExecuted", + "Value": "Command executed successfully. Rows affected: {0}" + }, + { + "Id": "f1b5696d-1819-5592-9c8e-4489ff0a28de", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.CommandExecuted", + "Value": "Komut başarıyla çalıştırıldı. Etkilenen satır: {0}" + }, + { + "Id": "6b7790b3-7e27-5beb-b383-c6cf160c647b", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.CommandExecutionFailed", + "Value": "Command execution failed: {0}" + }, + { + "Id": "39309255-9ea1-5ab5-9236-ee4e91cab896", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.CommandExecutionFailed", + "Value": "Komut çalıştırılamadı: {0}" + }, + { + "Id": "29126708-6697-5494-b6c6-bddb85418cfa", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.ScalarQueryExecuted", + "Value": "Scalar query executed successfully." + }, + { + "Id": "fed08d22-58ec-5ff0-a98a-14a71a70a355", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.ScalarQueryExecuted", + "Value": "Tek değer sorgusu başarıyla çalıştırıldı." + }, + { + "Id": "fea5e59f-9664-5157-8cc1-b19b0ca09a66", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.ScalarQueryFailed", + "Value": "Scalar query execution failed: {0}" + }, + { + "Id": "5d46005b-c0aa-5b85-a4d8-8065223b2343", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.ScalarQueryFailed", + "Value": "Tek değer sorgusu çalıştırılamadı: {0}" + }, + { + "Id": "88e1258f-994a-5412-9e96-70325b8de145", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.QueryExecutedAndDeployed", + "Value": "Query executed and deployed successfully." + }, + { + "Id": "b60f038c-9600-517d-8e26-408cea7ca9e2", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.SqlQueryManager.QueryExecutedAndDeployed", + "Value": "Sorgu başarıyla çalıştırıldı ve yayınlandı." + }, + { + "Id": "9257a632-2e63-5e65-910e-d67bb83449fe", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Validation.RangeBetween", + "Value": "The value of {0} must be between {1} and {2}." + }, + { + "Id": "c29990b8-2350-5526-b93c-0f58291d840e", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Validation.RangeBetween", + "Value": "{0} değeri {1} ile {2} arasında olmalıdır." + }, + { + "Id": "75a3208c-c939-5168-b2f0-08cb4aaef3d8", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Validation.RoutePathMustStartWithSlash", + "Value": "The route path must start with '/'." + }, + { + "Id": "2f5e9482-d23c-5bab-9622-3ab6fb48f670", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Validation.RoutePathMustStartWithSlash", + "Value": "Rota yolu '/' ile başlamalıdır." + }, + { + "Id": "b6d91ea4-f7c2-5bfb-95cf-f444ca970150", + "CultureName": "en", + "ResourceName": "Platform", + "Key": "App.Validation.IdentityNumberDigits", + "Value": "The identity number must be 11 digits" + }, + { + "Id": "9d439897-78b3-5b31-bdce-ec00b2fcbf6f", + "CultureName": "tr", + "ResourceName": "Platform", + "Key": "App.Validation.IdentityNumberDigits", + "Value": "T.C. Kimlik No 11 haneli olmalıdır" } ] } diff --git a/configs/seeds/data/App.Maintenance.Team.json b/configs/seeds/data/App.Maintenance.Team.json index d06f4e4f..13437037 100644 --- a/configs/seeds/data/App.Maintenance.Team.json +++ b/configs/seeds/data/App.Maintenance.Team.json @@ -12,19 +12,19 @@ }, { "BranchId": "1c125bf3-3ffd-4445-a270-1bc43ac3f61c", - "Name": "tedarikçi", + "Name": "Tedarikçi", "IsActive": true, "Id": "fd35736a-d0ac-9628-97c0-3a244ea3b791" }, { "BranchId": "1c125bf3-3ffd-4445-a270-1bc43ac3f61c", - "Name": "mekanik bakım", + "Name": "Mekanik Bakım", "IsActive": true, "Id": "b08b0002-73d9-cd12-6378-3a244ea3b791" }, { "BranchId": "1c125bf3-3ffd-4445-a270-1bc43ac3f61c", - "Name": "elektrik bakım", + "Name": "Elektrik Bakım", "IsActive": true, "Id": "65f59861-d337-6bb5-ee5f-3a244ea3b791" }, diff --git a/configs/seeds/docs/modules/App.Hr.md b/configs/seeds/docs/modules/App.Hr.md index 654db5dd..79244c48 100644 --- a/configs/seeds/docs/modules/App.Hr.md +++ b/configs/seeds/docs/modules/App.Hr.md @@ -330,7 +330,7 @@ Kontrol edilmeli (koddan tespit edilen tutarsızlıklar): | `api/src/Sozsoft.Platform.Application/OrgChart/OrgChartAppService.cs`, `Application.Contracts/OrgChart/OrgChartDto.cs` | Şema API'si (`GET departments`, `GET job-positions`) | | `ui/src/views/admin/hr/OrgChart.tsx`, `ui/src/services/orgChart.service.ts` | Şema sayfası | | `ui/src/views/admin/user-management/Details.tsx`, `EducationSkillsTab.tsx` | Kullanıcı iş bilgileri ve yetkinlik sekmeleri | -| `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` | Şube/departman/pozisyon/yetenek lookup'ları | +| `api/src/Sozsoft.Platform.Domain/LookupQueryValues.cs` | Şube/departman/pozisyon/yetenek lookup'ları | | `configs/seeds/data/App.Hr.{Department,JobPosition,SkillType,Skill,SkillLevel}.json` | Seed | | `configs/seeds/data/App.Menus.Routes.json` (`admin.hr.organization`) | Şema rotası | @@ -890,7 +890,7 @@ yetki ağacı aynı yeri gösterir. | `configs/seeds/sql/object/Hr_T_{WorkCalendar,Shift}.sql` | Tablolar | | `configs/seeds/wizard/{WorkCalendar,Shift}.json` | 2 wizard | | `configs/seeds/data/App.Hr.{WorkCalendar,Shift}.json` | Takvim ve vardiya verisi (ekran kayıtları dosyaya aynalanır) | -| `api/src/Sozsoft.Platform.Application.Contracts/LookUpQueryValues.cs` (`TenantShiftValues`) | Kullanıcı ekranındaki vardiya lookup'ı | +| `api/src/Sozsoft.Platform.Domain/LookupQueryValues.cs` (`TenantShiftValues`) | Kullanıcı ekranındaki vardiya lookup'ı | Geri alma: Wizard Manager'dan silinir; vardiya tablosu kullanıcı ekranının lookup'ı tarafından okunduğu için düşürülmemelidir. diff --git a/configs/seeds/docs/modules/App.Saas.md b/configs/seeds/docs/modules/App.Saas.md new file mode 100644 index 00000000..9511de98 --- /dev/null +++ b/configs/seeds/docs/modules/App.Saas.md @@ -0,0 +1,130 @@ +# Saas (`App.Saas`) + +> Bu modülün ekranları wizard ile değil C# seeder (`ListFormSeeder_Saas`) ve `data/*.json` dosyalarıyla kurulur; bu yüzden dosya ve bölümler deploy tarafından değil elle yazılır (`lowcode.instructions.md` §0.13, "wizard dışı modülün bölümü elle yazılır"). Ekran sözleşmeleri alan alan `lowcode-reference.instructions.md` §12'de, çalışma şekli `README.md`'nin anılan bölümlerindedir; burada her dal için amaç, ekran listesi ve gerçeğin yaşadığı yer tutulur. Menü ağacı `data/App.Menus.Menu.json`, yetkiler `data/Abp.Identity.Permissions.json` (`Modules = Sas`, yalnızca host bağlamında görünür). + + +## Tanımlamalar (`App.Saas.Definitions`) + +### 1. Amaç ve kapsam + +Bütün kiracıların ortak kullandığı host tanımları: AI botları, genel arama kaynakları, hitaplar, para birimleri, ülke grubu › ülke › şehir › ilçe zinciri, menü modülleri ve uygulama paketleri. Satırlar seed dosyasından gelir ve host yöneticisi tarafından düzenlenir; kiracı ekranları bu tanımları lookup olarak okur. + +### 2. Menü ve ekranlar + +| Sıra | Ekran | Kod | Seed verisi | +| --- | --- | --- | --- | +| 1 | AI Botları | `App.Definitions.AiBot` | `data/App.Definitions.AiBot.json` (README §13.8) | +| 2 | Genel Arama | `App.Definitions.GlobalSearch` | `data/App.Definitions.GlobalSearch.json` | +| 3 | Hitaplar | `App.Definitions.ContactTitle` | `data/App.Definitions.ContactTitle.json` | +| 4 | Para Birimleri | `App.Definitions.Currency` | `data/App.Definitions.Currency.json` | +| 5 | Ülke Grupları | `App.Definitions.CountryGroup` | `data/App.Definitions.CountryGroup.json` | +| 6 | Ülkeler | `App.Definitions.Country` | `data/App.Definitions.Country.json` | +| 7 | Şehirler | `App.Definitions.City` | `data/App.Definitions.City.json` | +| 8 | İlçeler | `App.Definitions.District` | `data/App.Definitions.District.json` | +| 9 | Modüller | `App.Menus.Module` | `data/App.Menus.Module.json` (README §9) | +| 10 | Uygulama Paketleri | `App.Definitions.ApplicationSuite` | `data/App.Definitions.ApplicationSuite.json` | + +### 3. Veri modeli ve kurallar + +- Tablolar `Sas_H_*` (host; `TableNameResolver`, `TableNameEnum`). Ülke grubu › ülke › şehir › ilçe dosyaları `Order` alanıyla bağımlılık sırasında uygulanır (CLAUDE.md "Menü ağacı ve yetkiler"). +- Para birimi lookup'ının anahtarı `Name`'dir (`'TRY'` gibi); ekranlardaki `*CurrencyId` varsayılanı tablo `DEFAULT`'undan gelir (standing default 18). +- Adres zinciri (Ülke › Şehir › İlçe › Mahalle) Kiracılar ve Kontaklar ekranlarında `CascadeParentFields` ile kullanılır (`lowcode-reference.instructions.md` §12). + + +## Public Site Yönetimi (`App.Public`) + +### 1. Amaç ve kapsam + +Ziyaretçiye açık sitenin arkasındaki yönetim ekranları: ürün/ödeme/taksit tanımları ve satış siparişleri (sepet akışı), blog kategorileri ve yazıları, demo talepleri. Sitenin sayfaları Custom Component, menüsü `Pub` modülüdür (README §10.4); buradaki ekranlar onların verisini yönetir. + +### 2. Menü ve ekranlar + +| Sıra | Ekran | Kod | Not | +| --- | --- | --- | --- | +| 1 | Ürünler | `App.Orders.Products` | sepet/ödeme akışının ürün kataloğu | +| 2 | Ödeme Yöntemleri | `App.Orders.PaymentMethods` | | +| 3 | Taksit Seçenekleri | `App.Orders.InstallmentOptions` | | +| 4 | Satış Siparişleri | `App.Orders.SalesOrders` | sipariş e-postası `PublicAppService` üretir; tarih/tutar `CultureFormat` ile aktif kültürde | +| 5 | Blog Kategorileri | `App.BlogManagement.Category` | sitemap'e giren yayın kaynağı (`SeoAppService`) | +| 6 | Blog Yazıları | `App.BlogManagement.Posts` | yayın tarihi `BlogPost.Publish(Clock.Now)` | +| 7 | Demo Talepleri | `App.Demos` | public demo formunun kayıtları | + + +## Kiracılar (`AbpTenantManagement.Tenants`) ve Şubeler (`App.Branches`) + +### 1. Amaç ve kapsam + +Kiracı kaydı (ABP TenantManagement; kendi veritabanı ya da host veritabanı paylaşımı), kiracı yöneticisinin ilk seed'i ve şube tanımı. Çalışma şekli README §12 (Çok Kiracılılık); seed kapsamı CLAUDE.md "Seed kapsamı" (`SeedFileScope`). Şube ekranı kullanıcıya atanmış aktif şubelerin kaynağıdır (`Sas_T_BranchUsers`, `LookupQueryValues.UserBranchValues`; standing default 1). + +| Ekran | Kod | Not | +| --- | --- | --- | +| Kiracılar | `AbpTenantManagement.Tenants` | ABP yerleşik yetkileri (`ManageConnectionStrings`, `ManageFeatures`) | +| Şubeler | `App.Branches` | `Sas_T_Branch`; host şubesi `PlatformConsts.Branches.BranchIdDefaultValue` | + + +## Ayar Tanımları (`App.SettingDefinitions`) + +Uygulama ayarlarının tanım listesi (`data/App.SettingDefinitions.json`, `Sozsoft.Settings` modülü); değerler Ayarlar ekranından host/kiracı/kullanıcı düzeyinde yazılır. Ayar anahtarları ve anlamları README §6.3 ve `lowcode-reference.instructions.md`'de; SEO varsayılanları `App.SiteManagement.Seo.*` (CLAUDE.md "SEO"). + + +## Diller (`App.Languages`) + +| Ekran | Kod | Seed verisi | +| --- | --- | --- | +| Diller | `App.Languages.Language` | `data/App.Languages.Language.json` | +| Dil Metinleri | `App.Languages.LanguageText` | `data/App.Languages.LanguageText.json` (EN + TR, `Id` = `Sas_H_LanguageText:{ResourceName}:{Key}:{CultureName}` v5 GUID) | + +Kullanıcıya görünen her metin dil anahtarıyla gelir (standing default 4); UI çevirisi `useLocalization`/`translateStatic`, API çevirisi `IStringLocalizer`. Tarih/sayı biçimi aktif kültürü izler (`localeFormat.ts`, `CultureFormat`; README §18.3). + + +## Veri Kaynakları (`App.DataSource`) + +Ekranların bağlandığı bağlantı kayıtları (`DataSourceManager`); kiracı tablosunun satırları kiracı adıyla kayıtlı `DataSource` üzerinden gider (CLAUDE.md "Seed kapsamı"). Varsayılan veri kaynağını `HostDataSeeder` appsettings'teki bağlantı cümlesinden kurar. İki sağlayıcı (SQL Server / PostgreSQL) `DataSourceTypeEnum` ile ayrılır; SQL metinleri ANSI tırnaklı ya da sağlayıcıya göre dallanır (standing default 16). + + +## Arka Plan İşleri (`App.BackgroundWorkers`) + +| Ekran | Kod | Not | +| --- | --- | --- | +| Zamanlanmış İşler | `App.BackgroundWorkers.RecurringJobs` | `data/App.BackgroundWorkers.RecurringJobs.json`; `Scope` = Host / Tenant / Host ve Kiracılar (`WorkerScopeEnum`) | +| İşler | `App.BackgroundWorkers.Jobs` | Hangfire kuyruğu | + +Çalışma şekli README §14; `BeforeSp`/`AfterSp` yordam adları `SqlIdentifier.IsSafe` ile doğrulanıp sağlayıcıya göre `EXEC`/`CALL` ile çağrılır (`SpRepository`). Mail kuyruğu işi (`MailQueueWorker`) şablonunu `Options.MailTemplate` alanından `PlatformTemplateDynamicStore` ile okur (README §13.7, `lowcode-reference.instructions.md` §11). + + +## Menüler (`App.Menus`) + +| Ekran | Kod | Seed verisi | +| --- | --- | --- | +| Rotalar | `App.Menus.Routes` | `data/App.Menus.Routes.json` (yalnızca `Seo*` alanları çerçeve içinde düzenlenir) | +| Menü | `App.Menus.Menu` | `data/App.Menus.Menu.json` | +| Menü Yöneticisi | `App.Menus.Manager` | `/admin/menuManager` (çekirdek sayfa) | + +Menü/route/yetki modeli README §9; yetki ağacı menü ağacını izler (standing default 2). + + +## Developer Kit (`App.DeveloperKit`) + +| Ekran | Kod | Not | +| --- | --- | --- | +| Geliştirme Talepleri | `App.DevRequests` | MCP talep döngüsü (CLAUDE.md "Talep analizi"); `data/App.DevRequests/` klasör modu | +| SQL Query Manager | `App.SqlQueryManager` | `/admin/sqlQueryManager`; tablo tasarımcısı iki sağlayıcının scriptini üretir (`PostgreSqlTableScript`) | +| Dosyalar | `App.Files` | `/admin/files` (README §16) | +| Custom Endpoint'ler | `App.DeveloperKit.CustomEndpoints` | `crud/{Entity}.json` (`CrudEndpointSeedApplier`) | +| Dynamic Service'ler | `App.DeveloperKit.DynamicServices` | runtime derleme (`DynamicAssemblyRegistrationService`) | +| Custom Component'ler | `App.DeveloperKit.CustomComponents` | `data/App.DeveloperKit.CustomComponents/{Id}.json` (README §8.2) | +| ListForm'lar | `App.Listforms.Listform` | ekran tanımları; kayıt wizard seed dosyasına geri yazılır (`WizardSeedSynchronizer`) | +| Intranet Widget'ları | `App.DeveloperKit.IntranetWidgets` | `data/App.DeveloperKit.IntranetWidgets.json` | +| MCP Sunucuları | `App.DeveloperKit.McpServers` | `AllowedTools` (README §13.9) | +| Wizard Manager | `App.Listforms.WizardManager` | `/admin/listform/wizardManager`; deploy yalnızca seed dosyası yazar, kurulum DB Migrate'tedir (`lowcode.instructions.md` §4.0) | + + +## Forum Yönetimi (`App.ForumManagement`) + +| Ekran | Kod | +| --- | --- | +| Kategoriler | `App.ForumManagement.Categories` | +| Konular | `App.ForumManagement.Topics` | +| Gönderiler | `App.ForumManagement.Posts` | + +Tablolar `Sas_T_Forum*` kiracı tablosudur (menü öneki ile tablo öneki örtüşmez; `lowcode-reference.instructions.md` "Tablo adlandırma"). Forum sayfaları kullanıcı tarafında `views/forum` altındadır. diff --git a/configs/seeds/wizard/Hr_AttendanceLog.json b/configs/seeds/wizard/Hr_AttendanceLog.json index 632c5624..436822d1 100644 --- a/configs/seeds/wizard/Hr_AttendanceLog.json +++ b/configs/seeds/wizard/Hr_AttendanceLog.json @@ -1306,7 +1306,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1822,7 +1822,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Candidate.json b/configs/seeds/wizard/Hr_Candidate.json index 78d69f97..6eac83f4 100644 --- a/configs/seeds/wizard/Hr_Candidate.json +++ b/configs/seeds/wizard/Hr_Candidate.json @@ -2030,7 +2030,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -4716,7 +4716,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DevelopmentPlan.json b/configs/seeds/wizard/Hr_DevelopmentPlan.json index 72eed564..42a5c348 100644 --- a/configs/seeds/wizard/Hr_DevelopmentPlan.json +++ b/configs/seeds/wizard/Hr_DevelopmentPlan.json @@ -2371,7 +2371,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2497,7 +2497,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DisciplinaryCase.json b/configs/seeds/wizard/Hr_DisciplinaryCase.json index c45a272b..ee54693d 100644 --- a/configs/seeds/wizard/Hr_DisciplinaryCase.json +++ b/configs/seeds/wizard/Hr_DisciplinaryCase.json @@ -1752,7 +1752,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1878,7 +1878,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"shortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DisciplinaryDecision.json b/configs/seeds/wizard/Hr_DisciplinaryDecision.json index 9a6872df..f8e27889 100644 --- a/configs/seeds/wizard/Hr_DisciplinaryDecision.json +++ b/configs/seeds/wizard/Hr_DisciplinaryDecision.json @@ -1082,7 +1082,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1346,7 +1346,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1472,7 +1472,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DisciplinaryDefense.json b/configs/seeds/wizard/Hr_DisciplinaryDefense.json index 204e8898..d0ed2b8d 100644 --- a/configs/seeds/wizard/Hr_DisciplinaryDefense.json +++ b/configs/seeds/wizard/Hr_DisciplinaryDefense.json @@ -1075,7 +1075,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1213,7 +1213,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1339,7 +1339,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DisciplinaryFile.json b/configs/seeds/wizard/Hr_DisciplinaryFile.json index d7e2f0e9..6da8de20 100644 --- a/configs/seeds/wizard/Hr_DisciplinaryFile.json +++ b/configs/seeds/wizard/Hr_DisciplinaryFile.json @@ -1171,7 +1171,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Doctor.json b/configs/seeds/wizard/Hr_Doctor.json index 12a09175..6c4d0a39 100644 --- a/configs/seeds/wizard/Hr_Doctor.json +++ b/configs/seeds/wizard/Hr_Doctor.json @@ -1197,7 +1197,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1449,7 +1449,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1575,7 +1575,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxTextBox", "ColSpan": 1, - "EditorOptions": "{\"showClearButton\":true,\"maxLength\":11,\"mask\":\"00000000000\",\"maskInvalidMessage\":\"T.C. Kimlik No 11 haneli olmalıdır\"}", + "EditorOptions": "{\"showClearButton\":true,\"maxLength\":11,\"mask\":\"00000000000\",\"maskInvalidMessage\":\"App.Validation.IdentityNumberDigits\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DriverAccident.json b/configs/seeds/wizard/Hr_DriverAccident.json index d88e1dc9..d506518e 100644 --- a/configs/seeds/wizard/Hr_DriverAccident.json +++ b/configs/seeds/wizard/Hr_DriverAccident.json @@ -1043,7 +1043,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_DriverPenalty.json b/configs/seeds/wizard/Hr_DriverPenalty.json index 4adc3a5f..bf87d153 100644 --- a/configs/seeds/wizard/Hr_DriverPenalty.json +++ b/configs/seeds/wizard/Hr_DriverPenalty.json @@ -1049,7 +1049,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_EventLog.json b/configs/seeds/wizard/Hr_EventLog.json index f7a0b3f7..b40b184e 100644 --- a/configs/seeds/wizard/Hr_EventLog.json +++ b/configs/seeds/wizard/Hr_EventLog.json @@ -1368,7 +1368,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_HealthCard.json b/configs/seeds/wizard/Hr_HealthCard.json index db089449..4706bd19 100644 --- a/configs/seeds/wizard/Hr_HealthCard.json +++ b/configs/seeds/wizard/Hr_HealthCard.json @@ -1433,7 +1433,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2315,7 +2315,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_HealthCardPatientProtocol.json b/configs/seeds/wizard/Hr_HealthCardPatientProtocol.json index e51e7b97..abbb4d45 100644 --- a/configs/seeds/wizard/Hr_HealthCardPatientProtocol.json +++ b/configs/seeds/wizard/Hr_HealthCardPatientProtocol.json @@ -1181,7 +1181,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_HealthCardPeriodicExamination.json b/configs/seeds/wizard/Hr_HealthCardPeriodicExamination.json index 9efdbb2e..ff09d43c 100644 --- a/configs/seeds/wizard/Hr_HealthCardPeriodicExamination.json +++ b/configs/seeds/wizard/Hr_HealthCardPeriodicExamination.json @@ -1043,7 +1043,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_HealthCardSickLeave.json b/configs/seeds/wizard/Hr_HealthCardSickLeave.json index c2169144..d5c19f74 100644 --- a/configs/seeds/wizard/Hr_HealthCardSickLeave.json +++ b/configs/seeds/wizard/Hr_HealthCardSickLeave.json @@ -1070,7 +1070,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1208,7 +1208,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Interview.json b/configs/seeds/wizard/Hr_Interview.json index 8790cddd..3a048590 100644 --- a/configs/seeds/wizard/Hr_Interview.json +++ b/configs/seeds/wizard/Hr_Interview.json @@ -1642,7 +1642,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1780,7 +1780,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_JobApplication.json b/configs/seeds/wizard/Hr_JobApplication.json index 1080f9e1..f39dc52a 100644 --- a/configs/seeds/wizard/Hr_JobApplication.json +++ b/configs/seeds/wizard/Hr_JobApplication.json @@ -1609,7 +1609,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_JobOffer.json b/configs/seeds/wizard/Hr_JobOffer.json index fc2b1949..3369bd50 100644 --- a/configs/seeds/wizard/Hr_JobOffer.json +++ b/configs/seeds/wizard/Hr_JobOffer.json @@ -1699,7 +1699,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1825,7 +1825,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1951,7 +1951,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2497,7 +2497,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3961,7 +3961,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_JobPosting.json b/configs/seeds/wizard/Hr_JobPosting.json index 8aec1b6b..bac8e129 100644 --- a/configs/seeds/wizard/Hr_JobPosting.json +++ b/configs/seeds/wizard/Hr_JobPosting.json @@ -2655,7 +2655,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2781,7 +2781,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_LeaveBalance.json b/configs/seeds/wizard/Hr_LeaveBalance.json index 7cbfe220..fbb4d661 100644 --- a/configs/seeds/wizard/Hr_LeaveBalance.json +++ b/configs/seeds/wizard/Hr_LeaveBalance.json @@ -1366,7 +1366,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2374,7 +2374,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_LeaveBalanceAdjustment.json b/configs/seeds/wizard/Hr_LeaveBalanceAdjustment.json index 8c580244..48852fa1 100644 --- a/configs/seeds/wizard/Hr_LeaveBalanceAdjustment.json +++ b/configs/seeds/wizard/Hr_LeaveBalanceAdjustment.json @@ -1082,7 +1082,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_LeaveBalanceYear.json b/configs/seeds/wizard/Hr_LeaveBalanceYear.json index c8e6a157..49d8f7a5 100644 --- a/configs/seeds/wizard/Hr_LeaveBalanceYear.json +++ b/configs/seeds/wizard/Hr_LeaveBalanceYear.json @@ -1166,7 +1166,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1292,7 +1292,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2048,7 +2048,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_LeaveRequest.json b/configs/seeds/wizard/Hr_LeaveRequest.json index 8da3373f..10ec11d6 100644 --- a/configs/seeds/wizard/Hr_LeaveRequest.json +++ b/configs/seeds/wizard/Hr_LeaveRequest.json @@ -2048,7 +2048,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2188,7 +2188,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3700,7 +3700,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_My360Feedback.json b/configs/seeds/wizard/Hr_My360Feedback.json index 6c955fe7..79c0f606 100644 --- a/configs/seeds/wizard/Hr_My360Feedback.json +++ b/configs/seeds/wizard/Hr_My360Feedback.json @@ -1725,7 +1725,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_MyDevelopmentPlan.json b/configs/seeds/wizard/Hr_MyDevelopmentPlan.json index 26188a8c..22b157e6 100644 --- a/configs/seeds/wizard/Hr_MyDevelopmentPlan.json +++ b/configs/seeds/wizard/Hr_MyDevelopmentPlan.json @@ -2203,7 +2203,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2329,7 +2329,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_MyLeaveBalance.json b/configs/seeds/wizard/Hr_MyLeaveBalance.json index fe92db9f..3bdad0a4 100644 --- a/configs/seeds/wizard/Hr_MyLeaveBalance.json +++ b/configs/seeds/wizard/Hr_MyLeaveBalance.json @@ -1159,7 +1159,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1285,7 +1285,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2041,7 +2041,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_MyLeaveRequest.json b/configs/seeds/wizard/Hr_MyLeaveRequest.json index 9c09d985..948c2e11 100644 --- a/configs/seeds/wizard/Hr_MyLeaveRequest.json +++ b/configs/seeds/wizard/Hr_MyLeaveRequest.json @@ -1322,7 +1322,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1462,7 +1462,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2722,7 +2722,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_MyPerformanceReview.json b/configs/seeds/wizard/Hr_MyPerformanceReview.json index f60c8ae8..3f7fbcd0 100644 --- a/configs/seeds/wizard/Hr_MyPerformanceReview.json +++ b/configs/seeds/wizard/Hr_MyPerformanceReview.json @@ -3412,7 +3412,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3538,7 +3538,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3664,7 +3664,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_MyTimesheetDay.json b/configs/seeds/wizard/Hr_MyTimesheetDay.json index 4ff71489..63db4092 100644 --- a/configs/seeds/wizard/Hr_MyTimesheetDay.json +++ b/configs/seeds/wizard/Hr_MyTimesheetDay.json @@ -928,7 +928,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1473,7 +1473,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1599,7 +1599,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_PerformanceCalibration.json b/configs/seeds/wizard/Hr_PerformanceCalibration.json index e304eb2c..94e03ef9 100644 --- a/configs/seeds/wizard/Hr_PerformanceCalibration.json +++ b/configs/seeds/wizard/Hr_PerformanceCalibration.json @@ -3381,7 +3381,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3507,7 +3507,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3633,7 +3633,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_PerformanceFeedback.json b/configs/seeds/wizard/Hr_PerformanceFeedback.json index 2fbd20f8..16af4fd3 100644 --- a/configs/seeds/wizard/Hr_PerformanceFeedback.json +++ b/configs/seeds/wizard/Hr_PerformanceFeedback.json @@ -1674,7 +1674,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_PerformancePeriod.json b/configs/seeds/wizard/Hr_PerformancePeriod.json index 7743ea90..e78ff247 100644 --- a/configs/seeds/wizard/Hr_PerformancePeriod.json +++ b/configs/seeds/wizard/Hr_PerformancePeriod.json @@ -1456,7 +1456,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1596,7 +1596,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1722,7 +1722,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1848,7 +1848,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_PerformanceReview.json b/configs/seeds/wizard/Hr_PerformanceReview.json index 79fa152b..87670ea0 100644 --- a/configs/seeds/wizard/Hr_PerformanceReview.json +++ b/configs/seeds/wizard/Hr_PerformanceReview.json @@ -3612,7 +3612,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3738,7 +3738,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3864,7 +3864,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_ReviewDevelopmentPlan.json b/configs/seeds/wizard/Hr_ReviewDevelopmentPlan.json index 75d8299c..717bee33 100644 --- a/configs/seeds/wizard/Hr_ReviewDevelopmentPlan.json +++ b/configs/seeds/wizard/Hr_ReviewDevelopmentPlan.json @@ -2272,7 +2272,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2398,7 +2398,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_StaffRequest.json b/configs/seeds/wizard/Hr_StaffRequest.json index 4585cb33..04ad83b7 100644 --- a/configs/seeds/wizard/Hr_StaffRequest.json +++ b/configs/seeds/wizard/Hr_StaffRequest.json @@ -1440,7 +1440,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2392,7 +2392,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3792,7 +3792,7 @@ "EditGroupOrderNo": 4, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TeamDevelopmentPlan.json b/configs/seeds/wizard/Hr_TeamDevelopmentPlan.json index 8ad1614c..50f419e2 100644 --- a/configs/seeds/wizard/Hr_TeamDevelopmentPlan.json +++ b/configs/seeds/wizard/Hr_TeamDevelopmentPlan.json @@ -2180,7 +2180,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2306,7 +2306,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TeamLeaveRequest.json b/configs/seeds/wizard/Hr_TeamLeaveRequest.json index 45c2ff20..af63a7f8 100644 --- a/configs/seeds/wizard/Hr_TeamLeaveRequest.json +++ b/configs/seeds/wizard/Hr_TeamLeaveRequest.json @@ -1749,7 +1749,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1875,7 +1875,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3261,7 +3261,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TeamPerformanceReview.json b/configs/seeds/wizard/Hr_TeamPerformanceReview.json index 7b4bd7a8..f31d9a35 100644 --- a/configs/seeds/wizard/Hr_TeamPerformanceReview.json +++ b/configs/seeds/wizard/Hr_TeamPerformanceReview.json @@ -3412,7 +3412,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3538,7 +3538,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3664,7 +3664,7 @@ "EditGroupOrderNo": 3, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Timesheet.json b/configs/seeds/wizard/Hr_Timesheet.json index 7c89c26c..33fe6f57 100644 --- a/configs/seeds/wizard/Hr_Timesheet.json +++ b/configs/seeds/wizard/Hr_Timesheet.json @@ -315,22 +315,7 @@ } ], "FormFieldsDefaultValueDto": [], - "WidgetsDto": [ - { - "ColGap": 3, - "ColSpan": 3, - "SqlQuery": "SELECT 'App.Hr.Attendance.Widget.Absent' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'red' AS \"Color\", 'App.Hr.Timesheet' AS \"SubTitle\", 'FaUserTimes' AS \"Icon\" FROM \"Hr_T_Timesheet\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' AND \"PeriodYear\" = YEAR(GETDATE()) AND \"PeriodMonth\" = MONTH(GETDATE()) AND \"DayType\" = 'Absent' UNION ALL SELECT 'App.Hr.Attendance.Widget.Late' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'orange' AS \"Color\", 'App.Hr.Timesheet' AS \"SubTitle\", 'FaClock' AS \"Icon\" FROM \"Hr_T_Timesheet\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' AND \"PeriodYear\" = YEAR(GETDATE()) AND \"PeriodMonth\" = MONTH(GETDATE()) AND \"LateMinutes\" > 0 UNION ALL SELECT 'App.Hr.Attendance.Widget.Overtime' AS \"Title\", CAST(COALESCE(SUM(\"OvertimeHours\"), 0) AS VARCHAR(32)) AS \"Value\", 'purple' AS \"Color\", 'App.Hr.Timesheet' AS \"SubTitle\", 'FaBusinessTime' AS \"Icon\" FROM \"Hr_T_Timesheet\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' AND \"PeriodYear\" = YEAR(GETDATE()) AND \"PeriodMonth\" = MONTH(GETDATE()) UNION ALL SELECT 'App.Hr.Attendance.Widget.Incomplete' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'yellow' AS \"Color\", 'App.Hr.Timesheet' AS \"SubTitle\", 'FaExclamationTriangle' AS \"Icon\" FROM \"Hr_T_Timesheet\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' AND \"PeriodYear\" = YEAR(GETDATE()) AND \"PeriodMonth\" = MONTH(GETDATE()) AND \"IsIncomplete\" = 'true'", - "Title": "Title", - "Value": "Value", - "ValueClassName": "text-3xl", - "Color": "Color", - "Icon": "Icon", - "SubTitle": "SubTitle", - "OnClick": null, - "ClassName": "mb-3", - "IsActive": true - } - ], + "WidgetsDto": [], "ExtraFilterEditDto": [], "ListFormCode": "App.Hr.Timesheet", "CultureName": "en", @@ -1214,7 +1199,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true,\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1897,7 +1882,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2023,7 +2008,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TimesheetPeriod.json b/configs/seeds/wizard/Hr_TimesheetPeriod.json index cc8af545..c59bc154 100644 --- a/configs/seeds/wizard/Hr_TimesheetPeriod.json +++ b/configs/seeds/wizard/Hr_TimesheetPeriod.json @@ -1416,7 +1416,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1542,7 +1542,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1794,7 +1794,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"dateSerializationFormat\":\"yyyy-MM-ddTHH:mm:ss\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Training.json b/configs/seeds/wizard/Hr_Training.json index 9c9d6c09..e68a928a 100644 --- a/configs/seeds/wizard/Hr_Training.json +++ b/configs/seeds/wizard/Hr_Training.json @@ -1395,7 +1395,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1675,7 +1675,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1815,7 +1815,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1941,7 +1941,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"shortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2067,7 +2067,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"time\",\"displayFormat\":\"shortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TrainingFile.json b/configs/seeds/wizard/Hr_TrainingFile.json index 23c71ab7..88ad7157 100644 --- a/configs/seeds/wizard/Hr_TrainingFile.json +++ b/configs/seeds/wizard/Hr_TrainingFile.json @@ -1163,7 +1163,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_TrainingParticipant.json b/configs/seeds/wizard/Hr_TrainingParticipant.json index 67665a8a..b4369860 100644 --- a/configs/seeds/wizard/Hr_TrainingParticipant.json +++ b/configs/seeds/wizard/Hr_TrainingParticipant.json @@ -1208,7 +1208,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1976,7 +1976,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_Vehicle.json b/configs/seeds/wizard/Hr_Vehicle.json index 999a82b3..4d4d461a 100644 --- a/configs/seeds/wizard/Hr_Vehicle.json +++ b/configs/seeds/wizard/Hr_Vehicle.json @@ -1848,7 +1848,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2506,7 +2506,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2632,7 +2632,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2758,7 +2758,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2884,7 +2884,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3010,7 +3010,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3136,7 +3136,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3262,7 +3262,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3388,7 +3388,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_VehicleExpense.json b/configs/seeds/wizard/Hr_VehicleExpense.json index 3447440f..b9c83ca4 100644 --- a/configs/seeds/wizard/Hr_VehicleExpense.json +++ b/configs/seeds/wizard/Hr_VehicleExpense.json @@ -1243,7 +1243,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_VehicleMovement.json b/configs/seeds/wizard/Hr_VehicleMovement.json index 7935fd8f..e4315284 100644 --- a/configs/seeds/wizard/Hr_VehicleMovement.json +++ b/configs/seeds/wizard/Hr_VehicleMovement.json @@ -1202,7 +1202,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2770,7 +2770,7 @@ "EditGroupOrderNo": 2, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Hr_WorkCalendar.json b/configs/seeds/wizard/Hr_WorkCalendar.json index d910c0ca..fcfb0964 100644 --- a/configs/seeds/wizard/Hr_WorkCalendar.json +++ b/configs/seeds/wizard/Hr_WorkCalendar.json @@ -1126,7 +1126,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1264,7 +1264,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"dateSerializationFormat\":\"yyyy-MM-dd\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_Bom.json b/configs/seeds/wizard/Inventory_Bom.json index 8a902d4f..034c271d 100644 --- a/configs/seeds/wizard/Inventory_Bom.json +++ b/configs/seeds/wizard/Inventory_Bom.json @@ -183,22 +183,7 @@ "SqlQuery": "" } ], - "WidgetsDto": [ - { - "ColGap": 3, - "ColSpan": 4, - "SqlQuery": "SELECT 'App.Widget.Total' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'blue' AS \"Color\", 'App.Inventory.Bom' AS \"SubTitle\", 'FaClipboardList' AS \"Icon\" FROM \"Inv_T_Bom\" WHERE \"TenantId\" = '@TENANTID' UNION ALL SELECT 'App.Widget.VariantBased', CAST(COUNT(*) AS VARCHAR(32)), 'purple', 'App.Inventory.Bom', 'FaLayerGroup' FROM \"Inv_T_Bom\" WHERE \"TenantId\" = '@TENANTID' AND \"VariantKey\" IS NOT NULL AND \"VariantKey\" <> '' UNION ALL SELECT 'App.Widget.Active', CAST(COUNT(*) AS VARCHAR(32)), 'green', 'App.Inventory.Bom', 'FaCheckCircle' FROM \"Inv_T_Bom\" WHERE \"TenantId\" = '@TENANTID' AND \"IsActive\" = 1", - "Title": "Title", - "Value": "Value", - "ValueClassName": "text-3xl", - "Color": "Color", - "Icon": "Icon", - "SubTitle": "SubTitle", - "OnClick": null, - "ClassName": "mb-3", - "IsActive": true - } - ], + "WidgetsDto": [], "ExtraFilterEditDto": [], "ListFormCode": "App.Inventory.Bom", "CultureName": "en", diff --git a/configs/seeds/wizard/Inventory_Delivery.json b/configs/seeds/wizard/Inventory_Delivery.json index ac727571..36116be8 100644 --- a/configs/seeds/wizard/Inventory_Delivery.json +++ b/configs/seeds/wizard/Inventory_Delivery.json @@ -1479,7 +1479,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2417,7 +2417,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_InternalTransfer.json b/configs/seeds/wizard/Inventory_InternalTransfer.json index 65a7b3a3..9c7dbbe2 100644 --- a/configs/seeds/wizard/Inventory_InternalTransfer.json +++ b/configs/seeds/wizard/Inventory_InternalTransfer.json @@ -1340,7 +1340,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2138,7 +2138,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_ProductSupplier.json b/configs/seeds/wizard/Inventory_ProductSupplier.json index 8c1bd131..212cb06e 100644 --- a/configs/seeds/wizard/Inventory_ProductSupplier.json +++ b/configs/seeds/wizard/Inventory_ProductSupplier.json @@ -2012,7 +2012,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_Receipt.json b/configs/seeds/wizard/Inventory_Receipt.json index 3d1faa2f..aa3169cc 100644 --- a/configs/seeds/wizard/Inventory_Receipt.json +++ b/configs/seeds/wizard/Inventory_Receipt.json @@ -1592,7 +1592,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1718,7 +1718,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_Stock.json b/configs/seeds/wizard/Inventory_Stock.json index de9246f4..94144ad3 100644 --- a/configs/seeds/wizard/Inventory_Stock.json +++ b/configs/seeds/wizard/Inventory_Stock.json @@ -1853,7 +1853,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1979,7 +1979,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_StockMove.json b/configs/seeds/wizard/Inventory_StockMove.json index b1a2874a..c6f91cfb 100644 --- a/configs/seeds/wizard/Inventory_StockMove.json +++ b/configs/seeds/wizard/Inventory_StockMove.json @@ -1324,7 +1324,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_Transfer.json b/configs/seeds/wizard/Inventory_Transfer.json index 722e13bc..138a143e 100644 --- a/configs/seeds/wizard/Inventory_Transfer.json +++ b/configs/seeds/wizard/Inventory_Transfer.json @@ -2059,7 +2059,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2185,7 +2185,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_TransferLineLotStock.json b/configs/seeds/wizard/Inventory_TransferLineLotStock.json index eb3aabb1..e81eeab2 100644 --- a/configs/seeds/wizard/Inventory_TransferLineLotStock.json +++ b/configs/seeds/wizard/Inventory_TransferLineLotStock.json @@ -1262,7 +1262,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"date\",\"displayFormat\":\"shortDate\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Inventory_TransferNote.json b/configs/seeds/wizard/Inventory_TransferNote.json index 3f3bcfe4..cace1e89 100644 --- a/configs/seeds/wizard/Inventory_TransferNote.json +++ b/configs/seeds/wizard/Inventory_TransferNote.json @@ -33,8 +33,8 @@ "MenuTr": "Not", "TitleEn": "Note", "TitleTr": "Not", - "DescEn": "Note — free note of a transfer document (Note tab of receipts, deliveries and internal transfers).\nField: note (rich text).", - "DescTr": "Not — transfer belgesinin serbest notu (alım, teslimat ve iç transferlerin Not sekmesi).\nAlan: not (zengin metin)." + "DescEn": "Note — the free-text note of a transfer document. Table: Inv_T_Transfer (same row as the document) · tenant and branch scope · opened as the Note tab of the Receipt, Delivery and Internal Transfer screens; it has no menu entry of its own.\nUsage: remarks that do not belong to a line (carrier instructions, counting remarks, reason of a partial receipt). The tab edits the single row of the open document; it cannot add or delete rows.\nField: Note (rich text, HtmlEditor).\nRelated screens: Operations tab (App.Inventory.TransferLine) and Additional Info tab (App.Inventory.TransferOther) of the same document.", + "DescTr": "Not — transfer belgesinin serbest metin notu. Tablo: Inv_T_Transfer (belgenin kendi satırı) · tenant ve şube kapsamlı · Alım, Teslimat ve İç Transfer ekranlarının Not sekmesi olarak açılır; kendi menüsü yoktur.\nKullanım: satıra ait olmayan açıklamalar (nakliyeci talimatı, sayım notu, kısmi alım nedeni). Sekme açık belgenin tek satırını düzenler; satır ekleyemez ve silemez.\nAlan: Not (zengin metin, HtmlEditor).\nİlişkili ekranlar: aynı belgenin Operasyonlar (App.Inventory.TransferLine) ve Ek Bilgi (App.Inventory.TransferOther) sekmeleri." }, "Groups": [ { diff --git a/configs/seeds/wizard/Inventory_TransferOther.json b/configs/seeds/wizard/Inventory_TransferOther.json index 8b0b154f..a824050a 100644 --- a/configs/seeds/wizard/Inventory_TransferOther.json +++ b/configs/seeds/wizard/Inventory_TransferOther.json @@ -33,8 +33,8 @@ "MenuTr": "Ek Bilgi", "TitleEn": "Additional Info", "TitleTr": "Ek Bilgi", - "DescEn": "Additional Info — shipping and other information of a transfer document (Additional Info tab of receipts, deliveries and internal transfers).\nFields: carrier (contact), tracking reference, weight (calculated from the lines: product weight × quantity), shipping weight, responsible, project; for internal transfers received by and receipt date (written by Validate, read only).", - "DescTr": "Ek Bilgi — transfer belgesinin nakliye ve diğer bilgileri (alım, teslimat ve iç transferlerin Ek Bilgi sekmesi).\nAlanlar: nakliyeci (kontak), takip referansı, ağırlık (satırlardan hesaplanır: ürün ağırlığı × miktar), nakliye ağırlığı, sorumlu, proje; iç transferde teslim alan ve teslim alma tarihi (Doğrula yazar, salt okunur)." + "DescEn": "Additional Info — shipping and other information of a transfer document. Table: Inv_T_Transfer (same row as the document) · tenant and branch scope · opened as the Additional Info tab of the Receipt, Delivery and Internal Transfer screens; it has no menu entry of its own.\nUsage: the tab edits the single row of the open document; it cannot add or delete rows.\nFields: CarrierId (carrier contact), TrackingReference, Weight (calculated from the lines: product weight × quantity), ShippingWeight, ResponsibleUserId, ProjectId; for internal transfers ReceivedByUserId and ReceivedDate (written by the Validate action, read only).\nRelated screens: Operations tab (App.Inventory.TransferLine) and Note tab (App.Inventory.TransferNote) of the same document.", + "DescTr": "Ek Bilgi — transfer belgesinin nakliye ve diğer bilgileri. Tablo: Inv_T_Transfer (belgenin kendi satırı) · tenant ve şube kapsamlı · Alım, Teslimat ve İç Transfer ekranlarının Ek Bilgi sekmesi olarak açılır; kendi menüsü yoktur.\nKullanım: sekme açık belgenin tek satırını düzenler; satır ekleyemez ve silemez.\nAlanlar: CarrierId (nakliyeci kontağı), TrackingReference (takip referansı), Weight (satırlardan hesaplanır: ürün ağırlığı × miktar), ShippingWeight (nakliye ağırlığı), ResponsibleUserId (sorumlu), ProjectId (proje); iç transferde ReceivedByUserId ve ReceivedDate (Doğrula eylemi yazar, salt okunur).\nİlişkili ekranlar: aynı belgenin Operasyonlar (App.Inventory.TransferLine) ve Not (App.Inventory.TransferNote) sekmeleri." }, "Groups": [ { @@ -1824,7 +1824,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequest.json b/configs/seeds/wizard/Prc_MaterialRequest.json index 5299e246..e63a81cc 100644 --- a/configs/seeds/wizard/Prc_MaterialRequest.json +++ b/configs/seeds/wizard/Prc_MaterialRequest.json @@ -1999,7 +1999,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3259,7 +3259,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestDeptApproval.json b/configs/seeds/wizard/Prc_MaterialRequestDeptApproval.json index 92b821f6..8eb362ef 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestDeptApproval.json +++ b/configs/seeds/wizard/Prc_MaterialRequestDeptApproval.json @@ -2122,7 +2122,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestLine.json b/configs/seeds/wizard/Prc_MaterialRequestLine.json index 5fced1e7..ca9ad322 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestLine.json +++ b/configs/seeds/wizard/Prc_MaterialRequestLine.json @@ -1541,7 +1541,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlan.json b/configs/seeds/wizard/Prc_MaterialRequestPlan.json index 72f72fc1..9f51247a 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlan.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlan.json @@ -2482,7 +2482,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3616,7 +3616,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlanLastPurchase.json b/configs/seeds/wizard/Prc_MaterialRequestPlanLastPurchase.json index 2b4fb567..cc671948 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlanLastPurchase.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlanLastPurchase.json @@ -1025,7 +1025,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlanStock.json b/configs/seeds/wizard/Prc_MaterialRequestPlanStock.json index 7a392af6..35d9ce60 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlanStock.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlanStock.json @@ -1763,7 +1763,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1889,7 +1889,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlanSupplier.json b/configs/seeds/wizard/Prc_MaterialRequestPlanSupplier.json index a85adc00..05b037d8 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlanSupplier.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlanSupplier.json @@ -1757,7 +1757,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlanTrack.json b/configs/seeds/wizard/Prc_MaterialRequestPlanTrack.json index 72ce40c9..d88fe635 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlanTrack.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlanTrack.json @@ -1279,7 +1279,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3169,7 +3169,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPlanVendor.json b/configs/seeds/wizard/Prc_MaterialRequestPlanVendor.json index ffad1603..21f28e05 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPlanVendor.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPlanVendor.json @@ -2269,7 +2269,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MaterialRequestPurchaseApproval.json b/configs/seeds/wizard/Prc_MaterialRequestPurchaseApproval.json index 4cb502b0..f36de883 100644 --- a/configs/seeds/wizard/Prc_MaterialRequestPurchaseApproval.json +++ b/configs/seeds/wizard/Prc_MaterialRequestPurchaseApproval.json @@ -2127,7 +2127,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\"}", + "EditorOptions": "{\"readOnly\":true,\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_MyMaterialRequest.json b/configs/seeds/wizard/Prc_MyMaterialRequest.json index f3897eeb..b888b9a1 100644 --- a/configs/seeds/wizard/Prc_MyMaterialRequest.json +++ b/configs/seeds/wizard/Prc_MyMaterialRequest.json @@ -2174,7 +2174,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseAgreement.json b/configs/seeds/wizard/Prc_PurchaseAgreement.json index f3706018..218e57e4 100644 --- a/configs/seeds/wizard/Prc_PurchaseAgreement.json +++ b/configs/seeds/wizard/Prc_PurchaseAgreement.json @@ -1906,7 +1906,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2032,7 +2032,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseOrder.json b/configs/seeds/wizard/Prc_PurchaseOrder.json index 15f53078..7a85c99e 100644 --- a/configs/seeds/wizard/Prc_PurchaseOrder.json +++ b/configs/seeds/wizard/Prc_PurchaseOrder.json @@ -2141,7 +2141,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2393,7 +2393,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2519,7 +2519,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseOrderAlternative.json b/configs/seeds/wizard/Prc_PurchaseOrderAlternative.json index a153a3c1..b05bf614 100644 --- a/configs/seeds/wizard/Prc_PurchaseOrderAlternative.json +++ b/configs/seeds/wizard/Prc_PurchaseOrderAlternative.json @@ -1533,7 +1533,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1659,7 +1659,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseOrderLine.json b/configs/seeds/wizard/Prc_PurchaseOrderLine.json index 98626ed4..bfb20eb8 100644 --- a/configs/seeds/wizard/Prc_PurchaseOrderLine.json +++ b/configs/seeds/wizard/Prc_PurchaseOrderLine.json @@ -2172,7 +2172,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseOrderProduct.json b/configs/seeds/wizard/Prc_PurchaseOrderProduct.json index 9a4de795..038b0679 100644 --- a/configs/seeds/wizard/Prc_PurchaseOrderProduct.json +++ b/configs/seeds/wizard/Prc_PurchaseOrderProduct.json @@ -3157,7 +3157,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3283,7 +3283,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"readOnly\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"readOnly\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3409,7 +3409,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -3535,7 +3535,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseRfq.json b/configs/seeds/wizard/Prc_PurchaseRfq.json index e49df5eb..bae58054 100644 --- a/configs/seeds/wizard/Prc_PurchaseRfq.json +++ b/configs/seeds/wizard/Prc_PurchaseRfq.json @@ -2124,7 +2124,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -2376,7 +2376,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseRfqAlternative.json b/configs/seeds/wizard/Prc_PurchaseRfqAlternative.json index 8279a70f..72561551 100644 --- a/configs/seeds/wizard/Prc_PurchaseRfqAlternative.json +++ b/configs/seeds/wizard/Prc_PurchaseRfqAlternative.json @@ -1545,7 +1545,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1671,7 +1671,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseRfqComparison.json b/configs/seeds/wizard/Prc_PurchaseRfqComparison.json index 9e5f2d20..e1b7168a 100644 --- a/configs/seeds/wizard/Prc_PurchaseRfqComparison.json +++ b/configs/seeds/wizard/Prc_PurchaseRfqComparison.json @@ -2229,7 +2229,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseRfqLine.json b/configs/seeds/wizard/Prc_PurchaseRfqLine.json index a1ecf27d..66a8e25c 100644 --- a/configs/seeds/wizard/Prc_PurchaseRfqLine.json +++ b/configs/seeds/wizard/Prc_PurchaseRfqLine.json @@ -2040,7 +2040,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prc_PurchaseRfqQuote.json b/configs/seeds/wizard/Prc_PurchaseRfqQuote.json index b706dec9..34f96854 100644 --- a/configs/seeds/wizard/Prc_PurchaseRfqQuote.json +++ b/configs/seeds/wizard/Prc_PurchaseRfqQuote.json @@ -1058,7 +1058,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"dd.MM.yyyy HH:mm\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"datetime\",\"displayFormat\":\"shortDateShortTime\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, @@ -1184,7 +1184,7 @@ "EditGroupOrderNo": 1, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"dd.MM.yyyy\",\"useMaskBehavior\":true,\"showClearButton\":true}", + "EditorOptions": "{\"type\":\"date\",\"displayFormat\":\"shortDate\",\"useMaskBehavior\":true,\"showClearButton\":true}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/configs/seeds/wizard/Prj_Diary.json b/configs/seeds/wizard/Prj_Diary.json index b3e1469b..71d91774 100644 --- a/configs/seeds/wizard/Prj_Diary.json +++ b/configs/seeds/wizard/Prj_Diary.json @@ -185,22 +185,7 @@ "SqlQuery": "SELECT b.\"Id\" FROM \"Sas_T_Branch\" b INNER JOIN \"Sas_T_BranchUsers\" bu ON bu.\"BranchId\" = b.\"Id\" AND bu.\"UserId\" = '@USERID' WHERE b.\"IsDeleted\" = 'false' AND b.\"IsActive\" = 'true' AND b.\"TenantId\" = '@TENANTID' ORDER BY b.\"Name\"" } ], - "WidgetsDto": [ - { - "ColGap": 3, - "ColSpan": 4, - "SqlQuery": "SELECT 'App.Widget.Total' AS \"Title\", CAST(COUNT(*) AS VARCHAR(32)) AS \"Value\", 'blue' AS \"Color\", 'App.Prj.Diary' AS \"SubTitle\", 'FaBook' AS \"Icon\" FROM \"Prj_T_Diary\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' UNION ALL SELECT 'App.Widget.Today', CAST(COUNT(*) AS VARCHAR(32)), 'green', 'App.Prj.Diary', 'FaCalendarDay' FROM \"Prj_T_Diary\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false' AND CAST(\"StartTime\" AS DATE) = CAST(CURRENT_TIMESTAMP AS DATE) UNION ALL SELECT 'App.Widget.TotalHours', CAST(CAST(COALESCE(SUM(\"Minutes\"), 0) / 60.0 AS DECIMAL(18,1)) AS VARCHAR(32)), 'purple', 'App.Prj.Diary', 'FaClock' FROM \"Prj_T_Diary\" WHERE \"TenantId\" = '@TENANTID' AND \"IsDeleted\" = 'false'", - "Title": "Title", - "Value": "Value", - "ValueClassName": "text-3xl", - "Color": "Color", - "Icon": "Icon", - "SubTitle": "SubTitle", - "OnClick": null, - "ClassName": "mb-3", - "IsActive": true - } - ], + "WidgetsDto": [], "ExtraFilterEditDto": [], "ListFormCode": "App.Prj.Diary", "CultureName": "en", diff --git a/configs/seeds/wizard/Prj_PersonnelManMonth.json b/configs/seeds/wizard/Prj_PersonnelManMonth.json index 2071b5c8..cb187d18 100644 --- a/configs/seeds/wizard/Prj_PersonnelManMonth.json +++ b/configs/seeds/wizard/Prj_PersonnelManMonth.json @@ -1536,7 +1536,7 @@ "EditGroupOrderNo": null, "EditorType2": "dxDateBox", "ColSpan": 1, - "EditorOptions": "{\"displayFormat\":\"yyyy MMMM\"}", + "EditorOptions": "{\"displayFormat\":\"monthAndYear\"}", "GridBoxOptions": null, "TagBoxOptions": null, "ImageUploadOptions": null, diff --git a/ui/.dockerignore b/ui/.dockerignore new file mode 100644 index 00000000..3209a0ac --- /dev/null +++ b/ui/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +dev-dist +.vite-cache +*.log +src/devextreme-license.ts diff --git a/ui/README.md b/ui/README.md index f6c42994..61f149a4 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1 +1,177 @@ -# elstar-ts +# Sözsoft Platform UI + +React 19 + Vite 8 (rolldown) + DevExtreme + easy-peasy + Tailwind 4. Ekranların tamamı +veritabanından gelen ListForm/menü/route tanımlarıyla çizilir; bu klasör yalnızca çekirdek +motoru taşır. Platformun geneli için [README.md](../README.md), çalışma kuralları için +[CLAUDE.md](../CLAUDE.md). + +## Komutlar + +| Komut | Ne yapar | +| --- | --- | +| `npm start` | Dev sunucusu (3000). `prestart` DevExtreme lisans dosyasını üretir. | +| `npm run build` | Üretim derlemesi → `dist/`. `--mode dev|production` ile `.env.{mode}` okunur. | +| `npm run preview` | Yerel önizleme; üretim sunucusu **değildir** (bkz. Konteyner). | +| `npm run typecheck` / `npm run lint` | `tsc --noEmit` / `oxlint`. Her değişiklikten sonra ikisi de 0 hata vermeli. | +| `npm run format` | prettier + oxlint --fix. | + +Ortam değişkenleri `.env` (yerel), `.env.dev`, `.env.production`: `VITE_API_URL`, `VITE_CDN_URL`. +Node/npm sürümü `package.json` → `volta`. + +## Derleme çıktısı + +`vite.config.ts` üç şey üretir: + +- **Chunk'lar** `assets/js/entry-*.js` (giriş), `assets/js/vendor-react-*.js`, + `vendor-router-*.js` (her açılışta yüklenen, sürümler arası değişmeyen çekirdek) ve rolldown'un + otomatik böldüğü lazy chunk'lar; CSS `assets/css/`, görsel/font `assets/img|fonts/`. + Adlar hash'lidir: aynı ad = aynı içerik. +- **`sw.js`** — `src/sw.ts`'ten (`vite-plugin-pwa`, `injectManifest`). Precache manifest'i + yalnızca kabuğu (`PRECACHE_GLOBS`) taşır. +- **`warmup.json`** — `warmupManifest()` eklentisi: bütün JS/CSS chunk'larının adı/boyutu ve + her `views/**` modülünün açılması için gereken dosya zinciri (`routes`). Service worker ısıtma ve + eski sürüm temizliği için okur. + +`twSafelistGenerator.ts`: custom component kodu veritabanında yaşadığı için Tailwind'in +göremediği sınıflar `safelist.txt`'ten derlemeye eklenir. + +## Rota ve bileşen çözümü + +`src/routes/dynamicRouteLoader.ts` `import.meta.glob('../views/**/*.tsx')` ile bütün view +modüllerini lazy olarak kaydeder; `App.Menus.Routes` kaydındaki `componentPath` bu haritadan +çözülür (`hasPhysicalComponent`), karşılığı olmayan rota router'a alınmaz. Runtime bileşenler +(`Custom Components`) `ComponentContext` üzerinden çizilir ve aynı yolu paylaşan fiziksel rotayı +gölgeler. Ayrıntı: [README.md §18.1](../README.md). + +## PWA ve sürüm geçişi + +Kod: `src/sw.ts` (service worker), `src/views/version/swRegistration.ts` (kayıt/güncelleme +motoru, entry chunk'ta, React'a bağımlı değil), `AppUpdateDialog.tsx` (lazy, kendi React +kökünde mount edilir), `installPrompt.ts` (PWA kurulum teklifi). + +1. **Kurulum = kabuk.** `install` olayı yalnızca precache manifest'indeki dosyaları indirir + (index.html, entry JS/CSS, vendor chunk'ları). Biri inmezse kurulum düşer; yarım sürümle + aktive olunmaz. İndirme ilerlemesi dosya dosya istemciye yayınlanır. +2. **Isıtma = bileşenler.** Oturum açılınca store, yetkili `protected` rotaların view + modüllerini `setWarmupRoutes` ile bildirir; worker `ENSURE_WARMUP` ile `warmup.json`'daki + zinciri arka planda indirir. Ziyaretçi ve giriş ekranı hiçbir bileşen indirmez. Best-effort: + inemeyen dosya atlanır, süre bütçesi dolunca kalanlar runtime cache'e kalır. +3. **Güncelleme.** Deploy'da herhangi bir chunk değişince entry hash'i, index.html ve `sw.js` + manifest'i değişir; `registration.update()` (kayıt, sekmeye dönüş, bağlantı, 30 dk yoklama) + yeni worker'ı görür → kurulum → `SKIP_WAITING` → `controllerchange` → yenileme. Pencere geçiş + bitene kadar kapanmaz; ilk kurulumda (controller yok) kapatılabilir. +4. **Sürüm numarası ve notlar backend'dedir** (`extraProperties.changeLogs`, + `/api/app/change-log`); UI'da sürüm dosyası yoktur. Son görülen sürüm store'un `locale` + diliminde tutulur, giriş sonrası changelog yönlendirmesi oradan okunur. +5. **Runtime cache.** Hash'li dosyalar cache-first (`sozsoft-assets-v1`, aktivasyonda + `warmup.json`'da olmayan eski chunk'lar silinir), DevExtreme temaları stale-while-revalidate, + font/görsel cache-first. `/api`, `/connect`, `/swagger`, `/signalr`, `/hangfire` worker'a + girmez. Gezinmeler kabuk cache'indeki index.html ile anında açılır, kopya arka planda tazelenir. + +### Deploy sonrası eksik / değişmiş chunk + +İki katman birlikte çalışır: + +- **Sunucu tarafı (asıl çözüm):** konteyner nginx'i hash'li `assets/` dosyalarını `/srv/assets` + volume'undan servis eder; `nginx/40-archive-assets.sh` her açılışta yeni sürümün dosyalarını + arşive ekler (var olanı ezmez), imajda artık olmayan ve 30 günden eski dosyaları siler. Açık + sekmeler deploy'dan sonra da kendi sürümlerinin chunk'larını bulur. +- **İstemci tarafı (yedek):** bir chunk yine de bulunamazsa Vite `vite:preloadError` + fırlatır; `handleChunkLoadFailures` kabuk cache'ini silip sayfayı **bir kez** yeniler + (`reloadWithFreshShell`; düz `reload` eski index.html'i geri getirirdi). Koruma bayrağı + (`localStorage.chunkReloadGuard`, 10 s) döngüyü keser; ikinci başarısızlık hata sınırına + düşer, `Views.tsx` `isChunkLoadError` ile "uygulama güncellendi, yenileyin" mesajı ve + `reloadApp` butonu gösterir. Hata sınırı rota değişince kendini sıfırlar (`resetKeys`). + +Dev'de service worker kapalıdır (`VITE_PWA_DEV=true` ile açılır); kapalıyken önceki kayıtlar +temizlenir, yoksa `sw.js` yerine index.html döner ve "unsupported MIME type" hatası üretirdi. + +## Konteyner + +`Sozsoft.Platform.Ui.Dockerfile` iki aşamalıdır: + +1. `node:24.22-alpine` — `npm run build:production -- --mode $ENV`; DevExtreme lisansı yalnızca + bu aşamada, tek `RUN` içinde yazılıp silinir. `.env.$ENV`'deki `VITE_API_URL` `/tmp/api-url` + olarak ikinci aşamaya geçer. +2. `nginx:1.27-alpine` — `dist/` + `nginx/default.conf` (`${API_URL}` envsubst ile build'de + doldurulur, `nginx -t` ile doğrulanır) + `/docker-entrypoint.d/40-archive-assets.sh`. + +`nginx/default.conf` sözleşmesi: + +| Yol | Cache-Control | Not | +| --- | --- | --- | +| `/assets/**` | `public, max-age=31536000, immutable` | `/srv/assets` (imaj + arşiv) | +| `/index.html`, `/sw.js`, `/warmup.json`, `/manifest.webmanifest` | `no-store, no-cache, must-revalidate` | sürüm kapısı | +| `/css/**` (DevExtreme temaları) | `public, max-age=86400` | hash'siz, SW tazeler | +| `/img/**` | `public, max-age=604800` | | +| `/sitemap.xml`, `/robots.txt` | — | API'ye proxy (`SeoController`) | +| diğer | — | SPA fallback → `index.html` | + +Compose (`configs/deployment/docker-compose-app.yml`): `ui` servisi `3002:80`, volume +`ui-assets:/srv/assets`. Deploy (`scripts/deploy/app.sh`) konteynerleri önceden durdurmaz: +`pull` → `up -d`; kesinti yalnızca konteyner değişimi kadardır. Ters vekil `nginx.conf` +Cache-Control başlıklarını ezmez; başlıklar konteynerden gelir, tenant alt alan adları da aynı +başlıkları alır. + +## Kod düzeni + +``` +src/ + components/ paylaşılan bileşenler (ui/, shared/, template/, layouts/, list/form editörleri…) + views/ rota bileşenleri (import.meta.glob ile lazy); views/shared/ hariç + routes/ dinamik router ve route çözümü + store/ easy-peasy modelleri (abpConfig, auth, locale, base…) + services/ axios istemcileri (authApi, platformApi) ve SignalR servisleri + proxy/ API DTO tipleri + utils/ yardımcılar ve hook'lar + sw.ts service worker +``` + +Grid/Tree popup'ı ile form sayfası editör yeteneklerini `views/list/shared/*` ve +`form/editors/*` üzerinden paylaşır; `scriptRecipes.ts` C# `Domain.Shared/Editors/*` ile +birebir aynı çıktıyı üretmek zorundadır (bkz. CLAUDE.md "Cross-cutting"). Grid ve Tree'nin ortak +iç seçenekleri (düzenleme, araç çubuğu, süzgeçler, hücre editörü şablonları) tek yerde çizilir: +`views/list/shared/listOptionChildren.tsx`. + +Ortak yardımcılar; aynı işin ikinci bir kopyası yazılmaz: + +| İş | Yardımcı | +| --- | --- | +| Kullanıcıya görünen metin | `useLocalization().translate('::Anahtar')`; React dışı kodda `utils/translate.ts` `translateStatic` | +| Toast bildirimi | `utils/notify.tsx` `notifySuccess/Info/Warning/Danger(başlık?, { message })` | +| API hatasının bildirimi | `services/apiError.ts` `pushApiError` | +| Kimlik üretimi | `utils/id.ts` `createId` (UUID), `createShortId` | +| Tarih/sayı/para/boyut | `utils/localeFormat.ts` (aşağıda) | + +## Tarih ve sayı biçimi + +Kullanıcıya görünen her tarih ve sayı **aktif kültürle** biçimlenir; kültür `useLocale` ile store'dan +``'e yazılır ve tek giriş `src/utils/localeFormat.ts`'tir: + +| İhtiyaç | Çağrı | +| --- | --- | +| Tarih / saat / tarih+saat | `formatLocaleDate`, `formatLocaleTime`, `formatLocaleDateTime` (dayjs `L`, `LT`, `L LT`); uzun tarih `formatLocaleLongDate` (`LL`) | +| Sayı | `formatLocaleNumber(v, { minimumFractionDigits, maximumFractionDigits, useGrouping })` — varsayılan binlik ayracı **yok** (yıl, kimlik, adet), tutar için `useGrouping: true` | +| Sabit ondalık / dosya boyutu | `formatLocaleFixed(v, 2)`, `formatLocaleBytes(bytes)` | +| Para | `formatLocaleCurrency(v, currencyCode)` — kod veriden gelir; verisi olmayan fiyat `DEFAULT_CURRENCY` (`constants/app.constant.ts`) | +| Ham endpoint değeri | `formatLocaleValue(v)` (ISO tarih ve sayıyı çevirir, diğerini olduğu gibi bırakır) | + +Hepsi boş/geçersiz değer için `''` döner ve `Date | string | number | Dayjs` alır. Tarih seçiciler +(`DatePicker`, `DateTimepicker`) `inputFormat` verilmeyince kültürün desenini kullanır; prop yalnızca +makine biçimi gerektiğinde verilir. + +DevExtreme kolon/editör biçimleri **adlı** biçimdir: `shortDate`, `shortDateShortTime`, `shortTime`, +`decimal`, `fixedPoint`, `currency` (`EditorOptions.Date/DateTime/Time` ↔ `presets.ts`). Kültür +`DevExtremeBoundary` → `configureDevExtreme` ile verilir: `locale(cultureName)` tarih/sayıyı, +`loadMessages` ile yüklenen `devextreme/localization/messages/{dil}.json` arayüz metinlerini (Evet/Hayır, +"Veri yok", filtre satırı) çevirir; paket dışı dil İngilizce metinle kalır. + +Yazılmayanlar: `'DD.MM.YYYY'`, `'HH:mm'` gibi sabit dayjs desenleri, `toLocaleString()` / +`toLocaleDateString('tr-TR')`, `Intl.*Format('tr-TR')`, görüntülenen ondalık için `toFixed`. Sunucu tarafı +karşılığı `CultureFormat` + `CultureInfo.CurrentCulture`'dır. + +**Makine biçimi serbesttir** ve `localeFormat`'tan geçmez: `` değeri ve +API yüküne giden ISO metin (`YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss`), gruplama/sorgu anahtarı, DevExtreme +`dateSerializationFormat`, yakınlaştırma yüzdesi gibi sayısal hesap yuvarlaması (`Number(x.toFixed(2))`), +metin karşılaştırması için `toLocaleLowerCase('tr-TR')`. `AppUpdateDialog` uygulama ağacının dışında, +kültür bilinmeden açıldığı için kendi `formatBytes` kopyasını taşır. diff --git a/ui/Sozsoft.Platform.Ui.Dockerfile b/ui/Sozsoft.Platform.Ui.Dockerfile index d09d5e17..3ea1bf9f 100644 --- a/ui/Sozsoft.Platform.Ui.Dockerfile +++ b/ui/Sozsoft.Platform.Ui.Dockerfile @@ -1,7 +1,10 @@ ARG ENV="dev" -FROM node:24.22-alpine -ARG ENV=$ENV +# --------------------------------------------------------------------------- +# 1. Derleme: Vite build (rolldown). Çıktı yalnızca dist/ olarak ikinci aşamaya geçer. +# --------------------------------------------------------------------------- +FROM node:24.22-alpine AS build +ARG ENV # Node/npm sürümü package.json `volta` alanıyla aynıdır. RUN apk add --no-cache ca-certificates && update-ca-certificates && \ npm install -g npm@12.2.0 @@ -38,4 +41,25 @@ RUN if [ -n "$DevExpress_License" ]; then \ status=$?; \ rm -rf "$HOME/.config/DevExpress"; \ exit $status -CMD ["npm", "run", "preview"] + +# sitemap.xml / robots.txt proxy hedefi: .env.{ENV} dosyasındaki VITE_API_URL tek kaynaktır, +# nginx yapılandırmasına ikinci bir adres yazılmaz. +RUN grep -E '^VITE_API_URL=' ".env.$ENV" | cut -d= -f2- | tr -d "'\"\r\n" > /tmp/api-url && \ + test -s /tmp/api-url + +# --------------------------------------------------------------------------- +# 2. Çalışma: nginx statik sunucu. `vite preview` üretim sunucusu değildir (Vite bunu +# yalnızca yerel önizleme için tanımlar ve her dosyaya no-cache basar); başlıklar, SPA +# fallback'i ve eski sürüm chunk arşivi nginx/default.conf + nginx/40-archive-assets.sh ile +# burada verilir. +# --------------------------------------------------------------------------- +FROM nginx:1.27-alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY --from=build /tmp/api-url /tmp/api-url +COPY nginx/default.conf /tmp/default.conf.template +COPY --chmod=755 nginx/40-archive-assets.sh /docker-entrypoint.d/40-archive-assets.sh +RUN API_URL="$(cat /tmp/api-url)" envsubst '${API_URL}' < /tmp/default.conf.template > /etc/nginx/conf.d/default.conf && \ + rm -f /tmp/default.conf.template /tmp/api-url && \ + nginx -t + +EXPOSE 80 diff --git a/ui/nginx/40-archive-assets.sh b/ui/nginx/40-archive-assets.sh new file mode 100644 index 00000000..810a4025 --- /dev/null +++ b/ui/nginx/40-archive-assets.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# nginx imajı açılışta /docker-entrypoint.d/*.sh dosyalarını çalıştırır. +# +# İmajın hash'li chunk'larını (`dist/assets`) arşive ekler ve arşivi /srv/assets volume'unda +# biriktirir: var olan dosya ezilmez (hash'li ad = aynı içerik), yeni dosya kopyalanır. Böylece +# deploy'dan önce açılmış sekmeler kendi sürümlerinin chunk'larını bulmaya devam eder. +# +# Temizlik: imajda artık bulunmayan ve 30 günden eski dosyalar silinir. Çalışan sürümün dosyası +# ne kadar eski olursa olsun silinmez (kontrol imajdaki varlığa göredir, tarihe göre değil). +set -eu + +SRC=/usr/share/nginx/html/assets +DST=/srv/assets +KEEP_DAYS=30 + +mkdir -p "$DST" + +cd "$SRC" +find . -type f | while IFS= read -r file; do + [ -e "$DST/$file" ] && continue + mkdir -p "$DST/$(dirname "$file")" + cp "$file" "$DST/$file" +done + +cd "$DST" +find . -type f -mtime +"$KEEP_DAYS" | while IFS= read -r file; do + [ -e "$SRC/$file" ] || rm -f "$file" +done +find . -type d -empty -delete 2>/dev/null || true + +echo "[assets] arşiv: $(find "$DST" -type f | wc -l) dosya" diff --git a/ui/nginx/default.conf b/ui/nginx/default.conf new file mode 100644 index 00000000..39e077de --- /dev/null +++ b/ui/nginx/default.conf @@ -0,0 +1,72 @@ +# Sözsoft Platform UI konteyneri (nginx). +# +# Hash'li chunk'lar `/srv/assets` altından servis edilir: imajın `dist/assets` içeriği + +# önceki sürümlerin arşivi (bkz. nginx/40-archive-assets.sh). Deploy sonrası açık sekmeler +# kendi sürümlerinin chunk'larını bulmaya devam eder; "eksik js" hatası oluşmaz. +# +# Sürüm kapısı olan dosyalar (index.html, sw.js, warmup.json, manifest) her istekte sunucudan +# doğrulanır; hash'li dosyalar bir yıl değişmez kabul edilir. `${API_URL}` build sırasında +# `.env.{ENV}` dosyasındaki VITE_API_URL ile doldurulur (Dockerfile). +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/css application/javascript application/json application/manifest+json image/svg+xml text/plain application/wasm; + + # Hash'li dosyalar: ad değişmeden içerik değişmez. + location /assets/ { + root /srv; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Sürüm kapısı: tarayıcı ve service worker bu dosyaları her zaman sunucudan alır. + location = /index.html { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + location = /sw.js { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + location = /warmup.json { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + location = /manifest.webmanifest { + # nginx'in mime.types dosyasında .webmanifest yoktur; tarayıcı doğru tipi ister. + types { application/manifest+json webmanifest; } + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + + # DevExtreme temaları hash'sizdir; service worker stale-while-revalidate ile tazeler. + location /css/ { + add_header Cache-Control "public, max-age=86400"; + } + + location /img/ { + add_header Cache-Control "public, max-age=604800"; + } + + # Arama motoru dosyaları API tarafından üretilir (SeoController); sitenin kökünden yayınlanır. + location = /sitemap.xml { + proxy_pass ${API_URL}/sitemap.xml; + proxy_ssl_server_name on; + } + + location = /robots.txt { + proxy_pass ${API_URL}/robots.txt; + proxy_ssl_server_name on; + } + + # SPA: fiziksel dosya yoksa index.html (iç yönlendirme `= /index.html` bloğuna düşer, + # no-store başlıkları oradan gelir). + location / { + try_files $uri /index.html; + } +} diff --git a/ui/src/components/codeLayout/data/componentDefinitions.ts b/ui/src/components/codeLayout/data/componentDefinitions.ts index ee9c238d..93cf4b10 100644 --- a/ui/src/components/codeLayout/data/componentDefinitions.ts +++ b/ui/src/components/codeLayout/data/componentDefinitions.ts @@ -67,7 +67,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'alt', type: 'string', - value: 'Kullanıcı Avatarı', + value: 'App.ComponentDefault.UserAvatar', category: 'properties', }, { name: 'icon', type: 'string', value: '', category: 'properties' }, @@ -97,7 +97,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'title', type: 'string', - value: 'Kullanıcı Avatarı', + value: 'App.ComponentDefault.UserAvatar', category: 'properties', }, { name: 'className', type: 'string', value: '', category: 'styling' }, @@ -114,7 +114,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'New', + value: 'App.Platform.New', category: 'properties', }, { @@ -144,7 +144,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Buton', + value: 'App.ComponentDefault.Button', category: 'properties', }, { name: 'active', type: 'boolean', value: false, category: 'properties' }, @@ -198,8 +198,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: - 'Kart içeriği buraya gelir. Lorem ipsum dolor sit amet consectetur adipisicing elit.', + value: 'App.ComponentDefault.CardContent', category: 'properties', }, { @@ -218,7 +217,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'header', type: 'string', - value: 'Kart Başlığı', + value: 'App.ComponentDefault.CardTitle', category: 'properties', }, { name: 'headerClass', type: 'string', value: '', category: 'styling' }, @@ -256,7 +255,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Onay Kutusu', + value: 'App.ComponentDefault.Checkbox', category: 'properties', }, { @@ -320,12 +319,6 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ category: 'properties', }, { name: 'value', type: 'string', value: '', category: 'properties' }, - { - name: 'inputFormat', - type: 'string', - value: 'YYYY-MM-DD', - category: 'properties', - }, { name: 'inputtableBlurClose', type: 'boolean', @@ -423,7 +416,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'placeholder', type: 'string', - value: 'Tarih seçin', + value: 'App.ComponentDefault.SelectDate', category: 'properties', }, { @@ -619,9 +612,9 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'items', type: 'array', value: [ - { eventKey: 'a', children: 'Öğe A', to: '' }, - { eventKey: 'b', children: 'Öğe B', to: '' }, - { eventKey: 'c', children: 'Öğe C', to: '' }, + { eventKey: 'a', children: 'App.ComponentDefault.ItemA', to: '' }, + { eventKey: 'b', children: 'App.ComponentDefault.ItemB', to: '' }, + { eventKey: 'c', children: 'App.ComponentDefault.ItemC', to: '' }, ], category: 'properties', }, @@ -715,7 +708,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'placeholder', type: 'string', - value: 'Metin girin...', + value: 'App.ComponentDefault.EnterText', category: 'properties', }, { name: 'value', type: 'string', value: '', category: 'properties' }, @@ -800,22 +793,22 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { eventKey: 'ayarlar', icon: '', - children: 'Ayarlar', - label: 'Ayarlar', + children: 'App.Setting', + label: 'App.Setting', disabled: false, }, { eventKey: 'mesaj', icon: '', - children: 'Mesaj', - label: 'Mesaj', + children: 'App.Listform.ListformField.Message', + label: 'App.Listform.ListformField.Message', disabled: false, }, { eventKey: 'galeri', icon: '', - children: 'Galeri', - label: 'Galeri', + children: 'App.ComponentDefault.Gallery', + label: 'App.ComponentDefault.Gallery', disabled: false, }, ], @@ -834,7 +827,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Bu bir bildirim mesajıdır', + value: 'App.ComponentDefault.NotificationMessage', category: 'properties', }, { @@ -848,7 +841,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'title', type: 'string', - value: 'Bildirim Başlığı', + value: 'App.ComponentDefault.NotificationTitle', category: 'properties', }, { @@ -944,7 +937,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'Radio.Group', icon: 'Circle', category: 'form', - description: 'Radio buton grubu', + description: 'App.ComponentRadioGroup.RadioGroupDescription', properties: [ { name: 'checked', @@ -989,9 +982,9 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'items', type: 'array', value: [ - { value: 'secenekA', label: 'Seçenek A', disabled: false }, - { value: 'secenekB', label: 'Seçenek B', disabled: false }, - { value: 'secenekC', label: 'Seçenek C', disabled: false }, + { value: 'secenekA', label: 'App.ComponentDefault.OptionA', disabled: false }, + { value: 'secenekB', label: 'App.ComponentDefault.OptionB', disabled: false }, + { value: 'secenekC', label: 'App.ComponentDefault.OptionC', disabled: false }, ], category: 'properties', }, @@ -1032,9 +1025,24 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'items', type: 'array', value: [ - { label: 'Seçenek 1', value: 'secenekA', icon: '', disabled: false }, - { label: 'Seçenek 2', value: 'secenekB', icon: '', disabled: false }, - { label: 'Seçenek 3', value: 'secenekC', icon: '', disabled: false }, + { + label: 'App.CatalogOptions.OptionsLabel', + value: 'secenekA', + icon: '', + disabled: false, + }, + { + label: 'App.CatalogOptions.OptionsLabel2', + value: 'secenekB', + icon: '', + disabled: false, + }, + { + label: 'App.CatalogOptions.OptionsLabel3', + value: 'secenekC', + icon: '', + disabled: false, + }, ], category: 'properties', }, @@ -1086,9 +1094,9 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'items', type: 'array', value: [ - { label: 'Seçenek 1', value: 'secenekA', disabled: false }, - { label: 'Seçenek 2', value: 'secenekB', disabled: false }, - { label: 'Seçenek 3', value: 'secenekC', disabled: false }, + { label: 'App.CatalogOptions.OptionsLabel', value: 'secenekA', disabled: false }, + { label: 'App.CatalogOptions.OptionsLabel2', value: 'secenekB', disabled: false }, + { label: 'App.CatalogOptions.OptionsLabel3', value: 'secenekC', disabled: false }, ], category: 'properties', }, @@ -1259,7 +1267,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'Table', icon: 'Table', category: 'data', - description: 'Veri tablosu', + description: 'App.ComponentTable.TableDescription', properties: [ { name: 'borderlessRow', @@ -1313,7 +1321,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ name: 'Tabs', icon: 'Tabs', category: 'navigation', - description: 'Sekmeli navigasyon', + description: 'App.ComponentTabs.TabsDescription', properties: [ { name: 'defaultValue', @@ -1336,19 +1344,19 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ value: [ { key: 'tab1', - label: 'Sekme 1', + label: 'App.ComponentDefault.Tab1', children: '', disabled: false, }, { key: 'tab2', - label: 'Sekme 2', + label: 'App.ComponentDefault.Tab2', children: '', disabled: false, }, { key: 'tab3', - label: 'Sekme 3', + label: 'App.ComponentDefault.Tab3', children: '', disabled: false, }, @@ -1368,7 +1376,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Etiket', + value: 'App.ComponentDefault.Tag', category: 'properties', }, { name: 'prefix', type: 'boolean', value: false, category: 'properties' }, @@ -1474,7 +1482,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Üzerime gel', + value: 'App.ComponentDefault.HoverMe', category: 'properties', }, { @@ -1500,7 +1508,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'title', type: 'string', - value: 'Tooltip içeriği', + value: 'App.ComponentDefault.TooltipContent', category: 'properties', }, { name: 'wrapperClass', type: 'string', value: '', category: 'styling' }, @@ -1517,7 +1525,7 @@ export const CUSTOM_COMPONENTS: ComponentDefinition[] = [ { name: 'children', type: 'string', - value: 'Dosya yükle', + value: 'App.Platform.UploadFile', category: 'properties', }, { name: 'accept', type: 'string', value: '', category: 'properties' }, @@ -2057,7 +2065,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Text content', + description: 'App.ComponentHtml.TextContentDescription', category: 'properties', }, { @@ -2091,7 +2099,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Text content', + description: 'App.ComponentHtml.TextContentDescription', category: 'properties', }, { @@ -2125,7 +2133,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Heading text', + description: 'App.ComponentHtml.HeadingTextDescription', category: 'properties', }, { @@ -2138,7 +2146,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'onClick', type: 'function', value: '', - description: 'Click event handler', + description: 'App.ComponentHtml.ClickHandlerDescription', category: 'events', }, ], @@ -2160,7 +2168,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Heading text', + description: 'App.ComponentHtml.HeadingTextDescription', category: 'properties', }, { @@ -2173,7 +2181,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'onClick', type: 'function', value: '', - description: 'Click event handler', + description: 'App.ComponentHtml.ClickHandlerDescription', category: 'events', }, ], @@ -2195,7 +2203,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Heading text', + description: 'App.ComponentHtml.HeadingTextDescription', category: 'properties', }, { @@ -2229,7 +2237,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Heading text', + description: 'App.ComponentHtml.HeadingTextDescription', category: 'properties', }, { @@ -2257,7 +2265,7 @@ export const HTML_ELEMENTS: ComponentDefinition[] = [ name: 'children', type: 'string', value: '', - description: 'Heading text', + description: 'App.ComponentHtml.HeadingTextDescription', category: 'properties', }, { diff --git a/ui/src/components/common/Widget.tsx b/ui/src/components/common/Widget.tsx deleted file mode 100644 index 032ddf64..00000000 --- a/ui/src/components/common/Widget.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useEffect, useState } from "react"; -import classNames from "classnames"; -import { iconList } from "../../utils/iconList"; - -export type colorType = - | "blue" - | "green" - | "purple" - | "gray" - | "red" - | "yellow" - | "pink" - | "indigo" - | "teal" - | "orange"; - -interface WidgetProps { - title: string; - value: string | number; - valueClassName?: string; - color?: colorType; - icon?: (typeof iconList)[number]; - subTitle?: string; - onClick?: () => void; - className?: string; -} - -export default function Widget({ - title, - value, - valueClassName = "text-3xl", - color = "blue", - icon = "FaChartBar", - subTitle = "-", - onClick, - className, -}: WidgetProps) { - const [IconComponent, setIconComponent] = useState | null>(null); - - useEffect(() => { - let isMounted = true; - import("react-icons/fa").then((icons) => { - if (isMounted && icon && icon in icons) { - setIconComponent(() => (icons as any)[icon]); - } - }); - return () => { - isMounted = false; - }; - }, [icon]); - - const colorMap: Record = { - blue: { bg: 'from-blue-100 to-blue-200', text: 'text-blue-600', darkBg: 'from-blue-900 to-blue-800', darkText: 'text-blue-300' }, - green: { bg: 'from-green-100 to-green-200', text: 'text-green-600', darkBg: 'from-green-900 to-green-800', darkText: 'text-green-300' }, - purple: { bg: 'from-purple-100 to-purple-200', text: 'text-purple-600', darkBg: 'from-purple-900 to-purple-800', darkText: 'text-purple-300' }, - gray: { bg: 'from-gray-100 to-gray-200', text: 'text-gray-600', darkBg: 'from-gray-800 to-gray-700', darkText: 'text-gray-300' }, - red: { bg: 'from-red-100 to-red-200', text: 'text-red-600', darkBg: 'from-red-900 to-red-800', darkText: 'text-red-300' }, - yellow: { bg: 'from-yellow-100 to-yellow-200', text: 'text-yellow-600', darkBg: 'from-yellow-900 to-yellow-800', darkText: 'text-yellow-300' }, - pink: { bg: 'from-pink-100 to-pink-200', text: 'text-pink-600', darkBg: 'from-pink-900 to-pink-800', darkText: 'text-pink-300' }, - indigo: { bg: 'from-indigo-100 to-indigo-200', text: 'text-indigo-600', darkBg: 'from-indigo-900 to-indigo-800', darkText: 'text-indigo-300' }, - teal: { bg: 'from-teal-100 to-teal-200', text: 'text-teal-600', darkBg: 'from-teal-900 to-teal-800', darkText: 'text-teal-300' }, - orange: { bg: 'from-orange-100 to-orange-200', text: 'text-orange-600', darkBg: 'from-orange-900 to-orange-800', darkText: 'text-orange-300' }, - }; - - const safeColor = color && colorMap[color] ? color : "green"; - - return ( -
-
-
-

- {title} -

-

- {value} -

-

{subTitle}

-
-
- {IconComponent ? ( - - ) : null} -
-
-
- ); -} diff --git a/ui/src/components/devExtreme/DevExtremeBoundary.tsx b/ui/src/components/devExtreme/DevExtremeBoundary.tsx index 4980faf5..a9c671be 100644 --- a/ui/src/components/devExtreme/DevExtremeBoundary.tsx +++ b/ui/src/components/devExtreme/DevExtremeBoundary.tsx @@ -1,5 +1,6 @@ import Loading from '@/components/shared/Loading' import { useStoreState } from '@/store' +import { useLocalization } from '@/utils/hooks/useLocalization' import { useEffect, useState, type ReactNode } from 'react' import { useLocation } from 'react-router-dom' import { configureDevExtreme } from './devExtremeConfig' @@ -10,6 +11,7 @@ interface DevExtremeBoundaryProps { const DevExtremeBoundary = ({ children }: DevExtremeBoundaryProps) => { const { pathname } = useLocation() + const { translate } = useLocalization() const cultureName = useStoreState((state) => state.locale.currentLang) || 'en' const [configuredCulture, setConfiguredCulture] = useState() const [error, setError] = useState() @@ -39,7 +41,11 @@ const DevExtremeBoundary = ({ children }: DevExtremeBoundaryProps) => { if (!requiresDevExtreme) return children if (error) { - return
DevExtreme yüklenemedi: {error.message}
+ return ( +
+ {translate('::App.Platform.DevExtremeLoadFailed', { 0: error.message })} +
+ ) } if (configuredCulture !== cultureName) { diff --git a/ui/src/components/devExtreme/devExtremeConfig.ts b/ui/src/components/devExtreme/devExtremeConfig.ts index 85e1b251..cb8e0981 100644 --- a/ui/src/components/devExtreme/devExtremeConfig.ts +++ b/ui/src/components/devExtreme/devExtremeConfig.ts @@ -1,7 +1,34 @@ +import { DEFAULT_CURRENCY } from '@/constants/app.constant' +import { toTwoLetterCulture } from '@/utils/localeFormat' + +type DevExtremeMessages = Record> + +/** + * DevExtreme ships its UI strings (Yes/No, Cancel, "No data", filter row labels…) as one JSON + * per language; English is built in. Only languages the package provides are listed — a + * missing entry leaves the English strings while dates and numbers still follow `locale()`. + */ +const devExtremeMessages: Record Promise<{ default: DevExtremeMessages }>> = { + ar: () => import('devextreme/localization/messages/ar.json'), + cs: () => import('devextreme/localization/messages/cs.json'), + de: () => import('devextreme/localization/messages/de.json'), + es: () => import('devextreme/localization/messages/es.json'), + fi: () => import('devextreme/localization/messages/fi.json'), + fr: () => import('devextreme/localization/messages/fr.json'), + hu: () => import('devextreme/localization/messages/hu.json'), + it: () => import('devextreme/localization/messages/it.json'), + pt: () => import('devextreme/localization/messages/pt.json'), + ru: () => import('devextreme/localization/messages/ru.json'), + sl: () => import('devextreme/localization/messages/sl.json'), + tr: () => import('devextreme/localization/messages/tr.json'), + zh: () => import('devextreme/localization/messages/zh.json'), +} + let devExtremeModulesPromise: | Promise<{ config: typeof import('devextreme/core/config').default locale: typeof import('devextreme/localization').locale + loadMessages: typeof import('devextreme/localization').loadMessages licenseKey: string }> | undefined @@ -14,18 +41,35 @@ const loadDevExtremeModules = () => { ]).then(([configModule, localizationModule, licenseModule]) => ({ config: configModule.default, locale: localizationModule.locale, + loadMessages: localizationModule.loadMessages, licenseKey: licenseModule.licenseKey, })) return devExtremeModulesPromise } +const loadedMessageCultures = new Set() + export const configureDevExtreme = async (cultureName: string) => { - const { config, locale, licenseKey } = await loadDevExtremeModules() + const { config, locale, loadMessages, licenseKey } = await loadDevExtremeModules() config({ licenseKey, - defaultCurrency: cultureName.startsWith('tr') ? 'TRY' : 'USD', + defaultCurrency: DEFAULT_CURRENCY, }) + + const language = toTwoLetterCulture(cultureName) + const loadLanguageMessages = devExtremeMessages[language] + if (loadLanguageMessages && !loadedMessageCultures.has(language)) { + try { + loadMessages((await loadLanguageMessages()).default) + loadedMessageCultures.add(language) + } catch (error) { + // Chunk could not be fetched (offline, deploy): widgets keep the English strings. + console.warn('DevExtreme messages could not be loaded.', error) + } + } + + // Messages, dates and numbers all resolve from this; "tr-TR" falls back to "tr" for messages. locale(cultureName) } diff --git a/ui/src/components/importManager/FileUploadArea.tsx b/ui/src/components/importManager/FileUploadArea.tsx index b926e04d..ed5ef556 100644 --- a/ui/src/components/importManager/FileUploadArea.tsx +++ b/ui/src/components/importManager/FileUploadArea.tsx @@ -1,4 +1,5 @@ import { useLocalization } from '@/utils/hooks/useLocalization' +import { formatLocaleBytes } from '@/utils/localeFormat' import React, { useCallback, useState } from 'react' import { FaUpload, FaFile, FaTimes, FaRegCircle } from 'react-icons/fa' import { Button } from '@/components/ui' @@ -60,7 +61,7 @@ export const FileUploadArea: React.FC = ({ } }, []) - const handleDrop = useCallback((e: React.DragEvent) => { + const handleDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDragActive(false) @@ -68,7 +69,7 @@ export const FileUploadArea: React.FC = ({ if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]) } - }, []) + } const handleChange = (e: React.ChangeEvent) => { e.preventDefault() @@ -88,14 +89,6 @@ export const FileUploadArea: React.FC = ({ } } - const formatFileSize = (bytes: number): string => { - if (bytes === 0) return '0 Bytes' - const k = 1024 - const sizes = ['Bytes', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] - } - return (
{!selectedFile ? ( @@ -148,7 +141,7 @@ export const FileUploadArea: React.FC = ({ {selectedFile.name}
- {formatFileSize(selectedFile.size)} + {formatLocaleBytes(selectedFile.size)}
@@ -180,9 +173,7 @@ export const FileUploadArea: React.FC = ({ color="blue-600" size="sm" > - {loading - ? translate('::App.Platform.Uploading') - : translate('::App.Platform.UploadFile')} + {loading ? translate('::App.Platform.Uploading') : translate('::App.Platform.UploadFile')} )} diff --git a/ui/src/components/importManager/ImportDashboard.tsx b/ui/src/components/importManager/ImportDashboard.tsx index 83091b56..254c0416 100644 --- a/ui/src/components/importManager/ImportDashboard.tsx +++ b/ui/src/components/importManager/ImportDashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from 'react' +import React, { useState, useEffect, useMemo, useCallback } from 'react' import classNames from 'classnames' import { FaUpload, @@ -22,8 +22,9 @@ import { ListFormImportDto, ListFormImportLogDto } from '@/proxy/imports/models' import { ImportService } from '@/services/import.service' import { GridDto } from '@/proxy/form/models' import { useLocalization } from '@/utils/hooks/useLocalization' -import { useDialogContext } from '@/components/ui/Dialog/Dialog' +import { useDialogContext } from '@/components/ui/Dialog/Dialog.context' import { Button } from '@/components/ui' +import { formatLocaleDateTime } from '@/utils/localeFormat' interface ImportDashboardProps { gridDto: GridDto @@ -44,15 +45,11 @@ export const ImportDashboard: React.FC = ({ gridDto }) => const [sessionExecutes, setSessionExecutes] = useState>({}) const [loadingExecutes, setLoadingExecutes] = useState>(new Set()) - useEffect(() => { - loadImportHistory() - }, []) + const listFormCode = gridDto.gridOptions.listFormCode - const loadImportHistory = async () => { + const loadImportHistory = useCallback(async () => { try { - const history = await importService.getListFormImportByListFormCode( - gridDto.gridOptions.listFormCode || '', - ) + const history = await importService.getListFormImportByListFormCode(listFormCode || '') setImportHistory(history) // Execute cache'ini temizle çünkü history değişmiş olabilir @@ -61,7 +58,11 @@ export const ImportDashboard: React.FC = ({ gridDto }) => } catch (error) { console.error('Failed to load import history:', error) } - } + }, [importService, listFormCode]) + + useEffect(() => { + loadImportHistory() + }, [loadImportHistory]) const toggleSessionExecutes = async (sessionId: string) => { // Toggle expanded state @@ -543,7 +544,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {session.blobName}
- {new Date(session.creationTime).toLocaleString()} + {formatLocaleDateTime(session.creationTime)}
{currentSession?.id === session.id && ( @@ -678,7 +679,7 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* Sol: Tarih */}
- {new Date(execute.creationTime).toLocaleString()} + {formatLocaleDateTime(execute.creationTime)}
diff --git a/ui/src/components/importManager/ImportPreview.tsx b/ui/src/components/importManager/ImportPreview.tsx index d364c72b..fe1068d1 100644 --- a/ui/src/components/importManager/ImportPreview.tsx +++ b/ui/src/components/importManager/ImportPreview.tsx @@ -97,7 +97,8 @@ export const ImportPreview: React.FC = ({ // Reset the flag when session changes hasCalledOnPreviewLoaded.current = false loadData() - }, [session.id, importService, gridDto]) // Remove session.status, onSessionUpdate and onPreviewLoaded from dependencies + // oxlint-disable-next-line react-hooks/exhaustive-deps -- reload only per session; the parent's onPreviewLoaded is recreated each render and would refetch in a loop + }, [session.id, importService, gridDto]) const getStatusColor = (status: string) => { switch (status) { @@ -318,11 +319,7 @@ export const ImportPreview: React.FC = ({
- diff --git a/ui/src/components/layouts/Layouts.tsx b/ui/src/components/layouts/Layouts.tsx index 1348203d..092fc074 100644 --- a/ui/src/components/layouts/Layouts.tsx +++ b/ui/src/components/layouts/Layouts.tsx @@ -12,7 +12,7 @@ import { import useAuth from '@/utils/hooks/useAuth' import useDirection from '@/utils/hooks/useDirection' import useLocale from '@/utils/hooks/useLocale' -import { useDynamicRoutes } from '@/routes/dynamicRoutesContext' +import { useDynamicRoutes } from '@/routes/dynamicRoutes.context' import { useLocation } from 'react-router-dom' import { hasSubdomain } from '@/utils/subdomain' import { ROUTES_ENUM } from '@/routes/route.constant' diff --git a/ui/src/components/orders/Basket.tsx b/ui/src/components/orders/Basket.tsx index a4e483e3..7c823c94 100644 --- a/ui/src/components/orders/Basket.tsx +++ b/ui/src/components/orders/Basket.tsx @@ -3,6 +3,8 @@ import { FaTimes, FaMinus, FaPlus, FaShoppingBag, FaTrash } from 'react-icons/fa import { BillingCycle, BasketItem } from '@/proxy/order/models' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button } from '@/components/ui' +import { DEFAULT_CURRENCY } from '@/constants/app.constant' +import { formatLocaleCurrency } from '@/utils/localeFormat' interface BasketData { items: BasketItem[] @@ -40,13 +42,8 @@ export const Basket: React.FC = ({ } } - const formatPrice = (price: number) => { - return new Intl.NumberFormat('tr-TR', { - style: 'currency', - currency: 'TRY', - minimumFractionDigits: 0, - }).format(price) - } + const formatPrice = (price: number) => + formatLocaleCurrency(price, DEFAULT_CURRENCY, { minimumFractionDigits: 0 }) if (!isOpen) return null diff --git a/ui/src/components/orders/OrderSuccess.tsx b/ui/src/components/orders/OrderSuccess.tsx index ab50f3ec..1e78c82c 100644 --- a/ui/src/components/orders/OrderSuccess.tsx +++ b/ui/src/components/orders/OrderSuccess.tsx @@ -11,6 +11,8 @@ import { useNavigate } from 'react-router-dom' import { ROUTES_ENUM } from '@/routes/route.constant' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button } from '@/components/ui' +import { DEFAULT_CURRENCY } from '@/constants/app.constant' +import { formatLocaleCurrency, formatLocaleDate, formatLocaleFixed } from '@/utils/localeFormat' import type { OrderSuccessData } from '@/store/client.model' import type { BasketItem } from '@/proxy/order/models' @@ -24,21 +26,9 @@ export const OrderSuccess: React.FC = ({ orderId, order }) => const navigate = useNavigate() const { translate } = useLocalization() const formatPrice = (price: number) => - new Intl.NumberFormat('tr-TR', { - style: 'currency', - currency: 'TRY', - minimumFractionDigits: 2, - }).format(price) + formatLocaleCurrency(price, DEFAULT_CURRENCY, { minimumFractionDigits: 2 }) - const formatDate = (value?: string) => { - if (!value) return '-' - - return new Intl.DateTimeFormat('tr-TR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }).format(new Date(value)) - } + const formatDate = (value?: string) => formatLocaleDate(value) || '-' const billingCycleLabel = order?.billingCycle === 'monthly' @@ -174,7 +164,8 @@ export const OrderSuccess: React.FC = ({ orderId, order }) => )}
- %{((item.vatRate ?? 0) * 100).toFixed(0)} / {formatPrice(item.vatAmount ?? 0)} + %{formatLocaleFixed((item.vatRate ?? 0) * 100, 0)} /{' '} + {formatPrice(item.vatAmount ?? 0)}
{formatPrice(item.totalPrice)} diff --git a/ui/src/components/orders/PaymentForm.tsx b/ui/src/components/orders/PaymentForm.tsx index 0c1f1c2c..0262da80 100644 --- a/ui/src/components/orders/PaymentForm.tsx +++ b/ui/src/components/orders/PaymentForm.tsx @@ -11,6 +11,8 @@ import { CustomTenantDto } from '@/proxy/config/models' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button } from '@/components/ui' import Input from '@/components/ui/Input' +import { DEFAULT_CURRENCY } from '@/constants/app.constant' +import { formatLocaleCurrency, formatLocaleDate, formatLocaleFixed } from '@/utils/localeFormat' interface BasketData { items: BasketItem[] @@ -84,11 +86,7 @@ export const PaymentForm: React.FC = ({ onBack, onComplete, ba const licenseEndTime = calculateLicenseEndTime(new Date(licenseStartTime), billingCycle, period) const formatPrice = (price: number) => - new Intl.NumberFormat('tr-TR', { - style: 'currency', - currency: 'TRY', - minimumFractionDigits: 2, - }).format(price) + formatLocaleCurrency(price, DEFAULT_CURRENCY, { minimumFractionDigits: 2 }) const vatRateLabel = (() => { const rates = Array.from( @@ -134,12 +132,7 @@ export const PaymentForm: React.FC = ({ onBack, onComplete, ba return stopTime } - const formatDate = (date: Date) => - new Intl.DateTimeFormat('tr-TR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }).format(date) + const formatDate = (date: Date) => formatLocaleDate(date) const billingCycleLabel = billingCycle === 'monthly' @@ -151,7 +144,9 @@ export const PaymentForm: React.FC = ({ onBack, onComplete, ba return (
{loading ? ( -
{translate('::App.Platform.LoadingWithThreeDot')}
+
+ {translate('::App.Platform.LoadingWithThreeDot')} +
) : (
{/* 3 Sütun: Ödeme Yöntemi | Taksit Seçenekleri | Sipariş Özeti */} @@ -284,7 +279,7 @@ export const PaymentForm: React.FC = ({ onBack, onComplete, ba
{translate('::App.Listform.ListformField.Commission')}{' '} - %{(option.commission * 100).toFixed(1)} + %{formatLocaleFixed(option.commission * 100, 1)}
diff --git a/ui/src/components/orders/ProductCard.tsx b/ui/src/components/orders/ProductCard.tsx index 61030d88..db0edcc5 100644 --- a/ui/src/components/orders/ProductCard.tsx +++ b/ui/src/components/orders/ProductCard.tsx @@ -4,6 +4,8 @@ import { BillingCycle, ProductDto } from '@/proxy/order/models' import { BasketData } from '@/utils/basketUtils' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button } from '@/components/ui' +import { DEFAULT_CURRENCY } from '@/constants/app.constant' +import { formatLocaleCurrency, formatLocaleFixed } from '@/utils/localeFormat' interface ProductCardProps { product: ProductDto @@ -27,13 +29,8 @@ export const ProductCard: React.FC = ({ (item) => item.product.id === product.id && item.billingCycle === globalBillingCycle, ) - const formatPrice = (price: number) => { - return new Intl.NumberFormat('tr-TR', { - style: 'currency', - currency: 'TRY', - minimumFractionDigits: 0, - }).format(price) - } + const formatPrice = (price: number) => + formatLocaleCurrency(price, DEFAULT_CURRENCY, { minimumFractionDigits: 0 }) const getCurrentPrice = () => { return globalBillingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice! @@ -169,7 +166,7 @@ export const ProductCard: React.FC = ({
- {translate('::App.PublicProducts.Kdv')} (%{(getVatRate() * 100).toFixed(0)}):{' '} + {translate('::App.PublicProducts.Kdv')} (%{formatLocaleFixed(getVatRate() * 100, 0)}):{' '} {formatPrice(getVatAmount())}
{globalPeriod > 1 && ( diff --git a/ui/src/components/orders/TenantForm.tsx b/ui/src/components/orders/TenantForm.tsx index 3b848eb9..9ff72ab7 100644 --- a/ui/src/components/orders/TenantForm.tsx +++ b/ui/src/components/orders/TenantForm.tsx @@ -622,7 +622,7 @@ export const TenantForm: React.FC = ({ onSubmit }) => { type="text" required autoFocus - placeholder="Enter your organization code" + placeholder={translate('::App.Tenant.EnterOrgCode')} value={formData.name || ''} onChange={(e) => resetExistingTenantDetails(e.target.value)} onKeyDown={async (e) => { @@ -665,15 +665,15 @@ export const TenantForm: React.FC = ({ onSubmit }) => {
- - {translate('::App.Platform.Company')} - + {translate('::App.Platform.Company')} {formData.organizationName}
- {translate('::App.Listform.ListformField.Founder')} + + {translate('::App.Listform.ListformField.Founder')} + {formData.founder}
@@ -696,7 +696,9 @@ export const TenantForm: React.FC = ({ onSubmit }) => {
- {translate('::App.Listform.ListformField.Address')}: + + {translate('::App.Listform.ListformField.Address')}: +
{formData.address1}
@@ -711,7 +713,9 @@ export const TenantForm: React.FC = ({ onSubmit }) => {
- {translate('::App.Listform.ListformField.City')}: + + {translate('::App.Listform.ListformField.City')}: + {formData.city}
@@ -780,7 +784,7 @@ export const TenantForm: React.FC = ({ onSubmit }) => { type="text" required autoFocus - placeholder="Enter your organization name" + placeholder={translate('::App.PublicDemo.OrganizationName')} value={formData.organizationName || ''} onChange={(e) => handleInputChange('organizationName', e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" @@ -798,7 +802,7 @@ export const TenantForm: React.FC = ({ onSubmit }) => { unstyle type="text" required - placeholder="Enter founder's name" + placeholder={translate('::App.Tenant.EnterFounderName')} value={formData.founder || ''} onChange={(e) => handleInputChange('founder', e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent" @@ -926,7 +930,7 @@ export const TenantForm: React.FC = ({ onSubmit }) => {
ProductPricePeriodQtyCycleVATAmount{Html(Translate("App.Listform.ListformField.ProductId"))}{Html(Translate("App.Listform.ListformField.Price"))}{Html(Translate("App.Listform.ListformField.Period"))}{Html(Translate("App.PublicProducts.Quantity"))}{Html(Translate("App.Listform.ListformField.BillingCycle"))}{Html(Translate("App.PublicProducts.Kdv"))}{Html(Translate("App.Listform.ListformField.Amount"))}