Fable5 bakışı ile düzenlemeler
This commit is contained in:
parent
a56bdc3e79
commit
5e8ee03fc8
538 changed files with 11418 additions and 12315 deletions
8
.github/instructions/dotnet.instructions.md
vendored
8
.github/instructions/dotnet.instructions.md
vendored
|
|
@ -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<TSource, TDest>` /
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
16
.github/instructions/lowcode.instructions.md
vendored
16
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -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}}` |
|
||||
|
|
|
|||
|
|
@ -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, `<input type=date>`) `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`
|
||||
43
README.md
43
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`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,15 @@
|
|||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
ABP paketleri System.Security.Cryptography.Pkcs 10.0.0'ı geçişli olarak getiriyor (GHSA-846f-f764-76mx,
|
||||
NU1903). Paket çerçeveden budanmadığı için yamalı sürüm bütün projelere tek yerden verilir; ABP yamalı
|
||||
sürümü kendisi çekince satır kaldırılır (dotnet list package - -vulnerable - -include-transitive).
|
||||
Projelerin kendi doğrudan referansı olan paketler (MessagePack, Cryptography.Xml) kendi csproj'larında güncel tutulur.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.13" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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: <access 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/<phone-number-id>/register' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer <access token>' \
|
||||
--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/<phone-number-id>/messages
|
||||
|
||||
```
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,14 +11,6 @@ public partial class LanguageToLanguageInfoMapper : MapperBase<Language, Languag
|
|||
public override partial LanguageInfo Map(Language source);
|
||||
|
||||
public override partial void Map(Language source, LanguageInfo destination);
|
||||
|
||||
public override void BeforeMap(Language source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(Language source, LanguageInfo destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -27,14 +19,6 @@ public partial class LanguageToLanguageDtoMapper : MapperBase<Language, Language
|
|||
public override partial LanguageDto Map(Language source);
|
||||
|
||||
public override partial void Map(Language source, LanguageDto destination);
|
||||
|
||||
public override void BeforeMap(Language source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(Language source, LanguageDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -43,14 +27,6 @@ public partial class LanguageKeyToLanguageKeyDtoMapper : MapperBase<LanguageKey,
|
|||
public override partial LanguageKeyDto Map(LanguageKey source);
|
||||
|
||||
public override partial void Map(LanguageKey source, LanguageKeyDto destination);
|
||||
|
||||
public override void BeforeMap(LanguageKey source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(LanguageKey source, LanguageKeyDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -59,14 +35,6 @@ public partial class LanguageTextToLanguageTextDtoMapper : MapperBase<LanguageTe
|
|||
public override partial LanguageTextDto Map(LanguageText source);
|
||||
|
||||
public override partial void Map(LanguageText source, LanguageTextDto destination);
|
||||
|
||||
public override void BeforeMap(LanguageText source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(LanguageText source, LanguageTextDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -75,14 +43,6 @@ public partial class LanguageTextToLanguageTextEtoMapper : MapperBase<LanguageTe
|
|||
public override partial LanguageTextEto Map(LanguageText source);
|
||||
|
||||
public override partial void Map(LanguageText source, LanguageTextEto destination);
|
||||
|
||||
public override void BeforeMap(LanguageText source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(LanguageText source, LanguageTextEto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -93,14 +53,6 @@ public partial class LanguageTextCreateUpdateDtoToLanguageTextMapper : MapperBas
|
|||
|
||||
[MapperIgnoreTarget(nameof(LanguageText.LanguageKey))]
|
||||
public override partial void Map(LanguageTextCreateUpdateDto source, LanguageText destination);
|
||||
|
||||
public override void BeforeMap(LanguageTextCreateUpdateDto source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(LanguageTextCreateUpdateDto source, LanguageText destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -133,12 +85,4 @@ public partial class LanguageKeyToLanguageTextTranslatedDtoMapper : MapperBase<L
|
|||
[MapperIgnoreTarget(nameof(LanguageTextTranslatedDto.CreationTime))]
|
||||
[MapperIgnoreTarget(nameof(LanguageTextTranslatedDto.CreatorId))]
|
||||
public override partial void Map(LanguageKey source, LanguageTextTranslatedDto destination);
|
||||
|
||||
public override void BeforeMap(LanguageKey source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(LanguageKey source, LanguageTextTranslatedDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ public abstract class MailQueueAppService : ApplicationService
|
|||
{
|
||||
protected MailQueueAppService()
|
||||
{
|
||||
LocalizationResource = typeof(ErpMailQueueResource);
|
||||
ObjectMapperContext = typeof(ErpMailQueueModule);
|
||||
LocalizationResource = typeof(SozsoftMailQueueResource);
|
||||
ObjectMapperContext = typeof(SozsoftMailQueueModule);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
namespace Sozsoft.MailQueue.Application;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
namespace Sozsoft.MailQueue.Domain.Shared;
|
||||
|
||||
[LocalizationResourceName(MailQueueConsts.AppName)]
|
||||
public class ErpMailQueueResource
|
||||
public class SozsoftMailQueueResource
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Sozsoft.MailQueue.Domain.MailGeneration;
|
||||
|
||||
public interface IAttachmentExtractor
|
||||
{
|
||||
IList<AttachmentModel> Extract(string attachment, string attachmentParametre);
|
||||
}
|
||||
|
||||
public class AttachmentExtractor : IAttachmentExtractor, ITransientDependency
|
||||
{
|
||||
protected readonly IParameterExtractor ParameterExtractor;
|
||||
|
||||
public AttachmentExtractor(
|
||||
IParameterExtractor parameterExtractor)
|
||||
{
|
||||
ParameterExtractor = parameterExtractor;
|
||||
}
|
||||
|
||||
public IList<AttachmentModel> Extract(string attachment, string attachmentParametre)
|
||||
{
|
||||
var result = new List<AttachmentModel>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<Dictionary<string, string>> GenerateAsync(
|
||||
Guid kuyrukId,
|
||||
string attachment,
|
||||
string attachmentParametre,
|
||||
string jobId = null);
|
||||
|
||||
Task<Dictionary<string, string>> GenerateAsync(List<MailTemplateTableModel> tables, Guid? kuyrukId = null, string jobId = null);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kuyruk satırındaki tablo şablonlarından (<c>DosyaTipi</c> PDF/XLS/TXT) mail eki üretir.
|
||||
/// Ek üretiminin tek yolu budur; eski <c>Attachment</c>/<c>AttachmentParameter</c> 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verilen parametreler (attachment parametreleri) ile KIM raporlari ile istenilen formatta dosya uretir
|
||||
/// </summary>
|
||||
/// <param name="attachment">Export edilecek olan attachmentlar</param>
|
||||
/// <param name="attachmentParametre">Her attachment için parametreler</param>
|
||||
/// <returns>Uretilen ekleri Key: MailEkAdi, Value: Base64 encode edilmis dosya olan dictionary olarak döner</returns>
|
||||
public async Task<Dictionary<string, string>> GenerateAsync(
|
||||
Guid kuyrukId,
|
||||
string attachment,
|
||||
string attachmentParametre,
|
||||
string jobId)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verilan tablo modeli ile istenilen dosya tipinde (PDF, XLS, TXT) ek üretir
|
||||
/// </summary>
|
||||
|
|
@ -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<string> 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<bool>("App:LogMailToFile"))
|
||||
|
|
@ -224,4 +111,3 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<string, string> Filtreler { get; set; }
|
||||
public string MailEkAdi { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// Hucre metni: <c>DataFormat</c> deseni aktif kulturde uygulanir (HTML/PDF/TXT ortak yol).
|
||||
/// XLS bu yoldan gecmez; orada desen Excel bicim kodu olarak hucreye yazilir.
|
||||
/// </summary>
|
||||
public string FormatValue(object value) => CultureFormat.Value(value, VeriFormati);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Entities.BackgroundWorker_MailQueue> Repository { get; }
|
||||
protected IRepository<BackgroundWorker_MailQueue> 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<Entities.BackgroundWorker_MailQueue> repository,
|
||||
IRepository<BackgroundWorker_MailQueue> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<IUnitOfWorkManager>();
|
||||
using var uow = uowManager.Begin();
|
||||
var jobId = $"{TemplateId}-{Clock.Now:yyyy-MM-dd-HH-mm-ss}";
|
||||
var attachmentsBasePath = Configuration.GetValue<string>("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<string, string>(
|
||||
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<string, string>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public async Task<DataTable> QueryAsync(string tabloAdi, Dictionary<string, string> 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<string>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ table.tablo tbody tr:hover {
|
|||
{
|
||||
<tr>
|
||||
@foreach (var sutun in tablo0.TabloSutunlari){
|
||||
<td>@satir.GetValueOrDefault(sutun.SutunAdi)</td>
|
||||
<td>@sutun.FormatValue(satir.GetValueOrDefault(sutun.SutunAdi))</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<XLSFileGenerator> Logger { get; set; } = NullLogger<XLSFileGenerator>.Instance;
|
||||
|
||||
string sheetPassword = "Erp123";
|
||||
|
||||
public async Task<byte[]> 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Sozsoft.Sender\Sozsoft.Sender.csproj" />
|
||||
<ProjectReference Include="..\..\src\Sozsoft.Platform.Domain.Shared\Sozsoft.Platform.Domain.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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<ErpMailQueueModule>();
|
||||
context.Services.AddMapperlyObjectMapper<SozsoftMailQueueModule>();
|
||||
|
||||
Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources.Add<ErpMailQueueResource>("en");
|
||||
options.Resources.Add<SozsoftMailQueueResource>("en");
|
||||
});
|
||||
|
||||
Configure<AbpExceptionLocalizationOptions>(options =>
|
||||
{
|
||||
options.MapCodeNamespace("MailQueue", typeof(ErpMailQueueResource));
|
||||
options.MapCodeNamespace("MailQueue", typeof(SozsoftMailQueueResource));
|
||||
});
|
||||
|
||||
context.Services.AddAbpDbContext<MailQueueDbContext>(options =>
|
||||
|
|
@ -51,7 +51,7 @@ public class ErpMailQueueModule : AbpModule
|
|||
|
||||
Configure<AbpRazorTemplateCSharpCompilerOptions>(options =>
|
||||
{
|
||||
options.References.Add(MetadataReference.CreateFromFile(typeof(ErpMailQueueModule).Assembly.Location));
|
||||
options.References.Add(MetadataReference.CreateFromFile(typeof(SozsoftMailQueueModule).Assembly.Location));
|
||||
});
|
||||
|
||||
Configure<AbpCompiledViewProviderOptions>(options =>
|
||||
|
|
@ -69,7 +69,7 @@ public class ErpMailQueueModule : AbpModule
|
|||
// FileGenerationModule
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<ErpMailQueueModule>();
|
||||
options.FileSets.AddEmbedded<SozsoftMailQueueModule>();
|
||||
});
|
||||
|
||||
context.Services.AddWkhtmltopdf($"FileGeneration{Path.DirectorySeparatorChar}Rotativa");
|
||||
|
|
|
|||
|
|
@ -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<List<McpSavedQueryDto>> 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<string, object>(), 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) ||
|
||||
|
|
|
|||
|
|
@ -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<string>(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))];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static async Task<HashSet<string>> GetGrantedNamesAsync(
|
||||
this IPermissionChecker permissionChecker,
|
||||
IEnumerable<string> names)
|
||||
{
|
||||
var distinct = names.Distinct(StringComparer.Ordinal).ToArray();
|
||||
if (distinct.Length == 0)
|
||||
{
|
||||
return new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
var grants = await permissionChecker.IsGrantedAsync(distinct);
|
||||
|
||||
return grants.Result
|
||||
.Where(result => result.Value == PermissionGrantResult.Granted)
|
||||
.Select(result => result.Key)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
|
@ -55,17 +55,7 @@ public class McpListFormAccessFilter(
|
|||
}
|
||||
}
|
||||
|
||||
var distinctPermissions = readPermissions.Values.Distinct(StringComparer.Ordinal).ToArray();
|
||||
if (distinctPermissions.Length == 0)
|
||||
{
|
||||
return new HashSet<string>(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))
|
||||
|
|
|
|||
|
|
@ -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<DictionaryDocument>(json, DictionaryJsonOptions);
|
||||
var document = JsonSerializer.Deserialize<DictionaryDocument>(json, PlatformJsonOptions.CaseInsensitive);
|
||||
|
||||
return document?.ListForms ?? [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
/// <summary>Sema/nesne adini dogrular ve saglayiciya gore tirnaklar.</summary>
|
||||
McpOutcome<string> QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType);
|
||||
|
||||
/// <summary>Saglayicinin varsayilan semasi.</summary>
|
||||
string DefaultSchemaName(DataSourceTypeEnum dataSourceType);
|
||||
|
||||
/// <summary>
|
||||
/// Satir tavanini sorgunun kendisine yazar: SQL Server'da <c>TOP (n)</c>, PostgreSQL'de
|
||||
/// <c>LIMIT n</c>. Sorgu kendi sinirini zaten tasiyorsa dokunulmaz.
|
||||
|
|
@ -39,11 +37,6 @@ public interface IMcpSqlGuard
|
|||
|
||||
public class McpSqlGuard(IStringLocalizer<PlatformResource> localizer) : DomainService, IMcpSqlGuard
|
||||
{
|
||||
/// <summary>Tek parcali SQL tanimlayicisi; sema ve nesne adlari bu kalibi gecmek zorundadir.</summary>
|
||||
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<PlatformResource> 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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// Istemcinin SQL bicimiyle yazdigi tip: <c>nvarchar(50)</c>, <c>nvarchar (MAX)</c>,
|
||||
/// <c>decimal(18, 4)</c>. 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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -12,14 +12,6 @@ public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<No
|
|||
public override partial NotificationTypeDto Map(NotificationType source);
|
||||
|
||||
public override partial void Map(NotificationType source, NotificationTypeDto destination);
|
||||
|
||||
public override void BeforeMap(NotificationType source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(NotificationType source, NotificationTypeDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -28,14 +20,6 @@ public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<No
|
|||
public override partial NotificationRuleDto Map(NotificationRule source);
|
||||
|
||||
public override partial void Map(NotificationRule source, NotificationRuleDto destination);
|
||||
|
||||
public override void BeforeMap(NotificationRule source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(NotificationRule source, NotificationRuleDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -53,14 +37,6 @@ public partial class NotificationToNotificationDtoMapper : MapperBase<Notificati
|
|||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
||||
public override partial void Map(Notification source, NotificationDto destination);
|
||||
|
||||
public override void BeforeMap(Notification source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(Notification source, NotificationDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -95,12 +71,4 @@ public partial class CreateUpdateNotificationRuleDtoToNotificationRuleMapper : M
|
|||
[MapperIgnoreTarget(nameof(NotificationRule.TenantId))]
|
||||
[MapperIgnoreTarget(nameof(NotificationRule.RecipientType))]
|
||||
public override partial void Map(CreateUpdateNotificationRuleDto source, NotificationRule destination);
|
||||
|
||||
public override void BeforeMap(CreateUpdateNotificationRuleDto source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(CreateUpdateNotificationRuleDto source, NotificationRule destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Localization;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Notifications.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Bildirim metninin (tarih, sayi, dil anahtari) uretildigi kultur. Istek disinda (worker) is
|
||||
/// parcaciginin kulturu surec varsayilanidir; alicinin kendi dil tercihi platformda saklanmaz
|
||||
/// (dil istekte <c>Accept-Language</c> ile gelir), bu yuzden ulasilabilen en yakin deger
|
||||
/// kiracinin <c>Abp.Localization.DefaultLanguage</c> ayaridir (kiraci → global → varsayilan).
|
||||
/// </summary>
|
||||
public interface INotificationCulture
|
||||
{
|
||||
Task<IDisposable> UseAsync();
|
||||
}
|
||||
|
||||
public class NotificationCulture(ISettingProvider settingProvider) : INotificationCulture, ITransientDependency
|
||||
{
|
||||
public async Task<IDisposable> UseAsync()
|
||||
{
|
||||
var culture = await settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
|
||||
return culture.IsNullOrWhiteSpace() || !CultureHelper.IsValidCultureCode(culture!)
|
||||
? NullDisposable.Instance
|
||||
: CultureHelper.Use(culture!);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<NotificationRule> 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<Notification>();
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace Sozsoft.Sender.Localization;
|
||||
|
||||
[LocalizationResourceName("SozsoftSender")]
|
||||
public class ErpSenderResource
|
||||
public class SozsoftMailingResource
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<EmailSendResult> SendEmailAsync(
|
||||
public async Task<SendResult> SendEmailAsync(
|
||||
string[] to,
|
||||
KeyValuePair<string, string>? 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<EmailSendResult> SendEmailAsync(
|
||||
public async Task<SendResult> SendEmailAsync(
|
||||
string to,
|
||||
KeyValuePair<string, string>? sender,
|
||||
dynamic? @params,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -7,8 +7,7 @@ namespace Sozsoft.Sender.Mail;
|
|||
/// </summary>
|
||||
public interface ISozsoftEmailSender : IEmailSender
|
||||
{
|
||||
//ErpEmailTemplate template
|
||||
Task<EmailSendResult> SendEmailAsync(
|
||||
Task<SendResult> SendEmailAsync(
|
||||
string to,
|
||||
KeyValuePair<string, string>? sender,
|
||||
dynamic? @params,
|
||||
|
|
@ -16,7 +15,7 @@ public interface ISozsoftEmailSender : IEmailSender
|
|||
string? subject = null,
|
||||
Dictionary<string, string>? attachments = null,
|
||||
bool IsBodyHtml = false);
|
||||
Task<EmailSendResult> SendEmailAsync(
|
||||
Task<SendResult> SendEmailAsync(
|
||||
string[] to,
|
||||
KeyValuePair<string, string>? sender,
|
||||
dynamic? @params,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ using Volo.Abp.MultiTenancy;
|
|||
|
||||
namespace Sozsoft.Sender.Mail;
|
||||
|
||||
public class ErpBackgroundEmailSendingJob :
|
||||
AsyncBackgroundJob<ErpBackgroundEmailSendingJobArgs>, ITransientDependency
|
||||
public class SozsoftBackgroundEmailSendingJob :
|
||||
AsyncBackgroundJob<SozsoftBackgroundEmailSendingJobArgs>, 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,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
namespace Sozsoft.Sender.Mail;
|
||||
|
||||
[Serializable]
|
||||
public class ErpBackgroundEmailSendingJobArgs : IMultiTenant
|
||||
public class SozsoftBackgroundEmailSendingJobArgs : IMultiTenant
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ namespace Sozsoft.Sender.Rocket;
|
|||
|
||||
public interface ISozsoftRocketSender : ITransientDependency
|
||||
{
|
||||
Task<RocketSendResult> SendAsync(string Username, string Message);
|
||||
Task<SendResult> SendAsync(string Username, string Message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<RocketSendResultDto> SendMessageAsync(string Username, string Message);
|
||||
Task<SendResult?> 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<RocketSendResultDto> SendMessageAsync(string Username, string Message)
|
||||
public async Task<SendResult?> 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<RocketSendResultDto>();
|
||||
// Rocket.Chat cevabı yalnızca { success } taşır; SendResult aynı sözleşmedir, ayrı DTO gerekmez.
|
||||
return await PostJsonAsync<SendResult>(
|
||||
$"{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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Sozsoft.Sender.Rocket;
|
||||
|
||||
public class RocketSendResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Sozsoft.Sender.Rocket;
|
||||
|
||||
public class RocketSendResultDto
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -15,11 +15,12 @@ public class RocketSender : ISozsoftRocketSender
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<RocketSendResult> SendAsync(string Username, string Message)
|
||||
public async Task<SendResult> 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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
16
api/modules/Sozsoft.Sender/SendResult.cs
Normal file
16
api/modules/Sozsoft.Sender/SendResult.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
namespace Sozsoft.Sender;
|
||||
|
||||
/// <summary>
|
||||
/// Her kanalın (e-posta, SMS, Rocket, WhatsApp, Telegram) tek gönderim sonucu. Kanal yalnızca
|
||||
/// başarı bilgisi veriyorsa <see cref="MessageId"/> ve <see cref="ErrorMessage"/> boş kalır.
|
||||
/// </summary>
|
||||
public class SendResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>Başarılı gönderimde sağlayıcının verdiği ileti kimliği.</summary>
|
||||
public string? MessageId { get; set; }
|
||||
|
||||
/// <summary>Başarısız gönderimde sağlayıcının açıklaması.</summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
52
api/modules/Sozsoft.Sender/SettingHttpClientBase.cs
Normal file
52
api/modules/Sozsoft.Sender/SettingHttpClientBase.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System.Net.Http.Json;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Sender;
|
||||
|
||||
/// <summary>
|
||||
/// Sağlayıcı istemcilerinin ortak iskeleti: <see cref="IHttpClientFactory"/> ile verilen
|
||||
/// <see cref="HttpClient"/>, 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.
|
||||
/// </summary>
|
||||
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<string?> GetSettingAsync(string name) => SettingProvider.GetOrNullAsync(name);
|
||||
|
||||
/// <summary>
|
||||
/// Gövdeyi JSON olarak gönderir ve cevabı <typeparamref name="TResponse"/> olarak okur.
|
||||
/// <paramref name="ensureSuccess"/> kapalıyken 4xx gövdesi de okunur: bazı API'ler hatayı
|
||||
/// durum koduyla değil gövdedeki açıklamayla verir.
|
||||
/// </summary>
|
||||
protected async Task<TResponse?> PostJsonAsync<TResponse>(
|
||||
string url,
|
||||
object body,
|
||||
Action<HttpRequestMessage>? 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<TResponse>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
HttpClient.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,6 @@ namespace Sozsoft.Sender.Sms;
|
|||
|
||||
public interface ISozsoftSmsSender : ISmsSender, ITransientDependency
|
||||
{
|
||||
Task<SmsSendResult> SendAsync(string PhoneNumber, string Message);
|
||||
Task<SendResult> SendAsync(string PhoneNumber, string Message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,61 +7,51 @@ public interface IPostaGuverciniHttpClient
|
|||
Task<PostaGuverciniSmsSendResultDto> 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<PostaGuverciniSmsSendResultDto> 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class PostaGuverciniSmsSender : ISozsoftSmsSender
|
|||
|
||||
public Task SendAsync(SmsMessage smsMessage) => SendAsync(smsMessage.PhoneNumber, smsMessage.Text);
|
||||
|
||||
public async Task<SmsSendResult> SendAsync(string PhoneNumber, string Message)
|
||||
public async Task<SendResult> 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}",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources.Add<ErpSenderResource>("en");
|
||||
options.Resources.Add<SozsoftMailingResource>("en");
|
||||
});
|
||||
|
||||
Configure<AbpBackgroundJobOptions>(options =>
|
||||
{
|
||||
options.AddJob<ErpBackgroundEmailSendingJob>();
|
||||
options.AddJob<SozsoftBackgroundEmailSendingJob>();
|
||||
});
|
||||
|
||||
//Register a factory method that resolves from IServiceProvider
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Sozsoft.Sender.Telegram;
|
|||
|
||||
public interface ISozsoftTelegramSender : ITransientDependency
|
||||
{
|
||||
Task<TelegramSendResult> SendAsync(string ChatId, string Message);
|
||||
Task<SendResult> SendAsync(string ChatId, string Message);
|
||||
}
|
||||
|
||||
public class SozsoftTelegramSender : ISozsoftTelegramSender
|
||||
|
|
@ -21,7 +21,7 @@ public class SozsoftTelegramSender : ISozsoftTelegramSender
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<TelegramSendResult> SendAsync(string ChatId, string Message)
|
||||
public async Task<SendResult> 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,28 @@
|
|||
using System.Net.Http.Json;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Sender.Telegram;
|
||||
|
||||
public interface ITelegramHttpClient
|
||||
{
|
||||
Task<TelegramSendResultDto> SendMessageAsync(string ChatId, string Message);
|
||||
Task<TelegramSendResultDto?> 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<TelegramSendResultDto> SendMessageAsync(string ChatId, string Message)
|
||||
public async Task<TelegramSendResultDto?> 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<TelegramSendResultDto>(
|
||||
$"{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<TelegramSendResultDto>();
|
||||
}
|
||||
|
||||
public void Dispose() => httpClient?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Sozsoft.Sender.Telegram;
|
||||
|
||||
public class TelegramSendResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ namespace Sozsoft.Sender.WhatsApp;
|
|||
|
||||
public interface ISozsoftWhatsAppSender : ITransientDependency
|
||||
{
|
||||
Task<WhatsAppSendResult> SendAsync(string To, string Message, string LanguageCode = "tr");
|
||||
Task<SendResult> 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<WhatsAppSendResult> SendAsync(string To, string Message, string LanguageCode = "tr")
|
||||
public async Task<SendResult> 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<WhatsAppSendResultDto> SendMessageAsync(string To, string Message, string LanguageCode = "tr");
|
||||
Task<WhatsAppSendResultDto?> 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<WhatsAppSendResultDto> SendMessageAsync(string To, string Message, string LanguageCode = "tr")
|
||||
public async Task<WhatsAppSendResultDto?> 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<WhatsAppSendResultDto>(
|
||||
$"{url}/{phoneId}/messages",
|
||||
new WhatsAppPostMessageInput
|
||||
{
|
||||
messaging_product = "whatsapp",
|
||||
|
|
@ -59,10 +52,7 @@ public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
|
|||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return await response.Content.ReadFromJsonAsync<WhatsAppSendResultDto>();
|
||||
},
|
||||
request => request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token));
|
||||
}
|
||||
|
||||
public void Dispose() => httpClient?.Dispose();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Sozsoft.Sender.WhatsApp;
|
||||
|
||||
public class WhatsAppSendResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -13,27 +13,26 @@ namespace Sozsoft.Settings;
|
|||
[Authorize]
|
||||
public class SettingUiAppService : ApplicationService, ISettingUiAppService
|
||||
{
|
||||
//private readonly IRepository<SettingDefinition, Guid> 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<List<MainGroupedSettingDto>> GetListAsync()
|
||||
{
|
||||
var groups = await ErpSettingDefinitionManager.GetGroupedSettingDefinitionsAsync();
|
||||
var groups = await platformSettingDefinitionManager.GetGroupedSettingDefinitionsAsync();
|
||||
return groups.Select(group => new MainGroupedSettingDto
|
||||
{
|
||||
GroupName = group.GroupName,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition;
|
|||
|
||||
namespace Sozsoft.Settings;
|
||||
|
||||
public class ErpSettingDefinitionManager : DomainService
|
||||
public class SettingDefinitionManager : DomainService
|
||||
{
|
||||
private readonly IRepository<SettingDefinition, Guid> repository;
|
||||
private readonly IStringLocalizer<SettingsResource> l;
|
||||
private readonly ISettingProvider settingProvider;
|
||||
private readonly IAuthorizationService authorizationService;
|
||||
|
||||
public ErpSettingDefinitionManager(
|
||||
public SettingDefinitionManager(
|
||||
IRepository<SettingDefinition, Guid> repository,
|
||||
IStringLocalizer<SettingsResource> 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<List<SettingGroupValueObject>> GetGroupedSettingDefinitionsAsync()
|
||||
|
|
@ -88,24 +86,5 @@ public class ErpSettingDefinitionManager : DomainService
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
//private async Task<bool> CheckPermissionsAsync(List<string> permissions)
|
||||
//{
|
||||
// //TODO: permisson tanimli degilse hata firlatma
|
||||
// var results = new List<bool>();
|
||||
// try
|
||||
// {
|
||||
// foreach (var item in permissions)
|
||||
// {
|
||||
// results.Add(await authorizationService.IsGrantedAsync(item));
|
||||
// }
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// results.Add(false);
|
||||
// }
|
||||
|
||||
// return results.Any(a => a);
|
||||
//}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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<List<SqlNativeObjectDto>> GetNativeObjectsAsync(
|
||||
/// <summary>
|
||||
/// Katalog bir kez okunur; tür başına liste, katalog sırasını (şema + ad) koruyarak süzülür.
|
||||
/// </summary>
|
||||
private async Task<List<(string Kind, SqlNativeObjectDto Dto)>> 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<SqlNativeObjectDto>();
|
||||
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<string, object>;
|
||||
if (dict != null)
|
||||
if (row is IDictionary<string, object> 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<string>();
|
||||
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<SqlNativeObjectDto> OfKinds(List<(string Kind, SqlNativeObjectDto Dto)> objects, params string[] kinds)
|
||||
=> [.. objects.Where(o => kinds.Contains(o.Kind)).Select(o => o.Dto)];
|
||||
|
||||
private async Task<List<DatabaseTableDto>> 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
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||
|
||||
[LocalizationResourceName("SqlQueryManager")]
|
||||
public class ErpSqlQueryManagerResource
|
||||
public class SozsoftSqlQueryManagerResource
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ public class SqlQueryManagerDomainSharedModule : AbpModule
|
|||
|
||||
Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources.Add<ErpSqlQueryManagerResource>("en");
|
||||
options.Resources.Add<SozsoftSqlQueryManagerResource>("en");
|
||||
});
|
||||
|
||||
Configure<AbpExceptionLocalizationOptions>(options =>
|
||||
{
|
||||
options.MapCodeNamespace("SqlQueryManager", typeof(ErpSqlQueryManagerResource));
|
||||
options.MapCodeNamespace("SqlQueryManager", typeof(SozsoftSqlQueryManagerResource));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PlatformResource> _localizer;
|
||||
|
||||
public SqlExecutorService(
|
||||
IDataSourceManager dataSourceManager,
|
||||
IServiceProvider serviceProvider)
|
||||
IServiceProvider serviceProvider,
|
||||
IStringLocalizer<PlatformResource> localizer)
|
||||
{
|
||||
_dataSourceManager = dataSourceManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
private async Task<IDynamicDataRepository> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Sozsoft.Platform.Dashboard;
|
||||
|
||||
public class DashboardWidgetsRequestDto
|
||||
{
|
||||
public DateTime Tarih1 { get; set; }
|
||||
public DateTime Tarih2 { get; set; }
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ public class CreateUpdateCustomComponentDto
|
|||
/// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz.
|
||||
/// </summary>
|
||||
[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; }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public class ChartAnimationDto
|
|||
{
|
||||
/// <summary> Animasyonun milisaniye cinsinden ne kadar süreceğini belirtir.
|
||||
/// </summary>
|
||||
[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;
|
||||
/// <summary> Animasyonun kolaylaştırma işlevini belirtir.
|
||||
/// Accepted Values: 'easeOutCubic' | 'linear'
|
||||
|
|
|
|||
|
|
@ -86,21 +86,4 @@ public class ChartAnnotationDto
|
|||
/// <summary> 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.
|
||||
/// </summary>
|
||||
public int Y { get; set; }
|
||||
|
||||
// allowDragging
|
||||
// arrowLength
|
||||
// arrowWidth
|
||||
// axis
|
||||
// component
|
||||
// customizeTooltip
|
||||
// data
|
||||
// opacity
|
||||
// render for react
|
||||
// shadow {}
|
||||
// template
|
||||
// textOverflow
|
||||
// tooltipComponent for react
|
||||
// tooltipRender
|
||||
// tooltipTemplate
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,48 +74,4 @@ public class ChartArgumentAxisDto
|
|||
/// <summary> 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.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class ChartBorderDto
|
|||
public string Color { get; set; } = "#d3d3d3";
|
||||
/// <summary> Legend in tüm köşelerini yuvarlatır.
|
||||
/// </summary>
|
||||
[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;
|
||||
/// <summary> Açıklamanın kenarlığı için bir tire stili ayarlar
|
||||
/// Accepted Values: 'dash' | 'dot' | 'longDash' | 'solid'
|
||||
|
|
@ -31,7 +31,6 @@ public class ChartBorderDto
|
|||
///// <summary> 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.
|
||||
///// </summary>
|
||||
//[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; }
|
||||
/// <summary> Açıklamanın kenarlığının genişliğini piksel cinsinden belirtir
|
||||
|
|
|
|||
|
|
@ -81,21 +81,4 @@ public class ChartCommonAnnotationDto
|
|||
/// <summary> 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.
|
||||
/// </summary>
|
||||
public int Y { get; set; }
|
||||
|
||||
// allowDragging
|
||||
// arrowLength
|
||||
// arrowWidth
|
||||
// axis
|
||||
// component
|
||||
// customizeTooltip
|
||||
// data
|
||||
// opacity
|
||||
// render for react
|
||||
// shadow {}
|
||||
// template
|
||||
// textOverflow
|
||||
// tooltipComponent for react
|
||||
// tooltipRender
|
||||
// tooltipTemplate
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,25 +21,4 @@ public class ChartCommonAxisDto
|
|||
/// <summary> Eksen çizgisinin genişliğini piksel cinsinden belirtir.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,49 +99,5 @@ public class ChartCommonSeriesSettingsDto
|
|||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,33 +40,4 @@ public class ChartLegendDto
|
|||
/// <summary> Göstergenin görünürlüğünü belirtir
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
////İhtiyaç olması durumunda açılacak diğer property ler
|
||||
|
||||
///// <summary>horizontalAlignment ile birlikte göstergenin konumunu belirtir
|
||||
///// Accepted Values: 'bottom' | 'top'
|
||||
///// </summary>
|
||||
//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; }
|
||||
///// <summary> Öğe sütunları arasında piksel cinsinden boş bir alan belirtir
|
||||
///// </summary>
|
||||
//public int ColumnItemSpacing { get; set; } = 20;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -101,25 +101,6 @@ public class ChartSeriesDto
|
|||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary> Değer özetleme işlevini belirtir.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,6 @@ namespace Sozsoft.Platform.ListForms;
|
|||
|
||||
public class ColumnFormatDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
private static readonly JsonSerializerOptions EditorOptionsSerializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public ColumnFormatDto() { }
|
||||
|
||||
public string FieldName { get; set; }
|
||||
|
|
@ -282,7 +277,7 @@ public class ColumnFormatDto : AuditedEntityDto<Guid>
|
|||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(EditorOptions, EditorOptionsSerializerOptions);
|
||||
return JsonSerializer.Deserialize<T>(EditorOptions, PlatformJsonOptions.CaseInsensitive);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Sozsoft.Platform.ListForms.Select;
|
||||
|
||||
public class WidgetDto
|
||||
{
|
||||
public int ColGap { get; set; }
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ public class WizardScreenOverlay
|
|||
[nameof(WizardColumnItemInputDto.DbSourceType)] = nameof(ColumnFormatEditDto.SourceDbType),
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions ReadOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
/// <summary>
|
||||
/// Katmani wizard'in urettigi ListForm'a uygular. <paramref name="baseline"/> ayni donusumun
|
||||
/// <see cref="Baseline"/> wizard'i icin ciktisidir.
|
||||
|
|
@ -145,7 +143,7 @@ public class WizardScreenOverlay
|
|||
target[key] = value;
|
||||
}
|
||||
|
||||
return target.Deserialize<T>(ReadOptions);
|
||||
return target.Deserialize<T>(PlatformJsonOptions.CaseInsensitive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -187,8 +187,6 @@ public class WizardSeedFileDto
|
|||
WriteIndented = true,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
private static readonly JsonSerializerOptions SeedReadOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
/// <summary>
|
||||
/// 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<WizardSeedFileDto>(SeedReadOptions)?.Rehydrate();
|
||||
return node.Deserialize<WizardSeedFileDto>(PlatformJsonOptions.CaseInsensitive)?.Rehydrate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -326,7 +324,7 @@ public class WizardSeedFileDto
|
|||
return null;
|
||||
|
||||
FlattenWizard(wizardNode);
|
||||
var wizard = wizardNode.Deserialize<ListFormWizardDto>(SeedReadOptions);
|
||||
var wizard = wizardNode.Deserialize<ListFormWizardDto>(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<WizardSeedFileDto>(SeedReadOptions).Rehydrate().Wizard;
|
||||
}.Deserialize<WizardSeedFileDto>(PlatformJsonOptions.CaseInsensitive).Rehydrate().Wizard;
|
||||
|
||||
if (listForm != null)
|
||||
FillMissing(wizard, sections, wizardNode, ListFormOwnedWizardKeys);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.Entities;
|
||||
|
||||
public class NoteListRequestDto : PagedAndSortedResultRequestDto
|
||||
{
|
||||
public string EntityName { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.Public;
|
||||
|
||||
public class CountryGroupDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.Skills;
|
||||
|
||||
public class SkillDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string TypeName { get; set; }
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.Skills;
|
||||
|
||||
public class SkillLevelDto : AuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Progress { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
public string TypeName { get; set; }
|
||||
}
|
||||
|
|
@ -25,14 +25,6 @@ public partial class AiBotToAiBotDtoMapper : TwoWayMapperBase<AiBot, AiBotDto>
|
|||
[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);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,6 @@ public partial class AuditLogToAuditLogDtoMapper : MapperBase<AuditLog, AuditLog
|
|||
|
||||
[MapperIgnoreTarget(nameof(AuditLogDto.EntityChangeCount))]
|
||||
public override partial void Map(AuditLog source, AuditLogDto destination);
|
||||
|
||||
public override void BeforeMap(AuditLog source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(AuditLog source, AuditLogDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -28,14 +20,6 @@ public partial class AuditLogActionToAuditLogActionDtoMapper : MapperBase<AuditL
|
|||
public override partial AuditLogActionDto Map(AuditLogAction source);
|
||||
|
||||
public override partial void Map(AuditLogAction source, AuditLogActionDto destination);
|
||||
|
||||
public override void BeforeMap(AuditLogAction source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(AuditLogAction source, AuditLogActionDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -44,14 +28,6 @@ public partial class EntityChangeToEntityChangeDtoMapper : MapperBase<EntityChan
|
|||
public override partial EntityChangeDto Map(EntityChange source);
|
||||
|
||||
public override partial void Map(EntityChange source, EntityChangeDto destination);
|
||||
|
||||
public override void BeforeMap(EntityChange source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(EntityChange source, EntityChangeDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -60,12 +36,4 @@ public partial class EntityPropertyChangeToEntityPropertyChangeDtoMapper : Mappe
|
|||
public override partial EntityPropertyChangeDto Map(EntityPropertyChange source);
|
||||
|
||||
public override partial void Map(EntityPropertyChange source, EntityPropertyChangeDto destination);
|
||||
|
||||
public override void BeforeMap(EntityPropertyChange source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(EntityPropertyChange source, EntityPropertyChangeDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
using Hangfire.Storage;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sozsoft.MailQueue.Domain.Shared;
|
||||
using Sozsoft.Platform.BackgroundWorkers;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.Enums;
|
||||
|
|
@ -22,20 +20,17 @@ public class BackgroundWorkerInitializer : ITransientDependency
|
|||
{
|
||||
private readonly IRepository<BackgroundWorker, Guid> _repo;
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly PlatformTemplateManager _templateManager;
|
||||
private readonly ILogger<BackgroundWorkerInitializer> _logger;
|
||||
private readonly IUnitOfWorkManager _unitOfWorkManager;
|
||||
|
||||
public BackgroundWorkerInitializer(
|
||||
IRepository<BackgroundWorker, Guid> repo,
|
||||
ITenantRepository tenantRepository,
|
||||
PlatformTemplateManager templateManager,
|
||||
ILogger<BackgroundWorkerInitializer> 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<MailQueueWorkerOptions>(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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Branch, Guid> _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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,14 +9,6 @@ public partial class BranchSeedResultToBranchSeedResultDtoMapper : MapperBase<Br
|
|||
public override partial BranchSeedResultDto Map(BranchSeedResult source);
|
||||
|
||||
public override partial void Map(BranchSeedResult source, BranchSeedResultDto destination);
|
||||
|
||||
public override void BeforeMap(BranchSeedResult source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(BranchSeedResult source, BranchSeedResultDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
|
|
@ -25,12 +17,4 @@ public partial class BranchSeedDetailToSeedDetailDtoMapper : MapperBase<BranchSe
|
|||
public override partial SeedDetailDto Map(BranchSeedDetail source);
|
||||
|
||||
public override partial void Map(BranchSeedDetail source, SeedDetailDto destination);
|
||||
|
||||
public override void BeforeMap(BranchSeedDetail source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(BranchSeedDetail source, SeedDetailDto destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sozsoft.Platform.DynamicData;
|
||||
using Sozsoft.Platform.OrganizationUnits;
|
||||
using Volo.Abp.Users;
|
||||
|
||||
namespace Sozsoft.Platform.Dashboard;
|
||||
|
||||
[Authorize()]
|
||||
public class DashboardAppService : PlatformAppService
|
||||
{
|
||||
public ISpRepository SpRepository { get; }
|
||||
public IDynamicDataManager DynamicDataManager { get; }
|
||||
public IPlatformOuRepository OuRepository { get; }
|
||||
|
||||
public DashboardAppService(
|
||||
ISpRepository spRepository,
|
||||
IDynamicDataManager dynamicDataManager,
|
||||
IPlatformOuRepository ouRepository)
|
||||
{
|
||||
SpRepository = spRepository;
|
||||
DynamicDataManager = dynamicDataManager;
|
||||
OuRepository = ouRepository;
|
||||
}
|
||||
|
||||
public async Task<List<DashboardWidgetsDto>> GetWidgetsAsync(DashboardWidgetsRequestDto requestDto)
|
||||
{
|
||||
var ids = await OuRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.GetId());
|
||||
|
||||
var results = await SpRepository.CallSpAsync("PDashboard_Widgets", "KIM",
|
||||
new Dictionary<string, object>() {
|
||||
{ "tarih1", requestDto.Tarih1 },
|
||||
{ "tarih2", requestDto.Tarih2 },
|
||||
{ "ids", ids.IsNullOrEmpty() ? Guid.Empty : ids.JoinAsString("','")}
|
||||
}
|
||||
);
|
||||
|
||||
var dtoResults = new List<DashboardWidgetsDto>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -11,14 +11,6 @@ public partial class DataSourceToDataSourceDtoMapper : TwoWayMapperBase<DataSour
|
|||
|
||||
public override partial void Map(DataSource source, DataSourceDto destination);
|
||||
|
||||
public override void BeforeMap(DataSource source)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AfterMap(DataSource source, DataSourceDto destination)
|
||||
{
|
||||
}
|
||||
|
||||
public override partial DataSource ReverseMap(DataSourceDto destination);
|
||||
|
||||
public override partial void ReverseMap(DataSourceDto destination, DataSource source);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Sozsoft.Platform.Domain.DeveloperKit;
|
||||
using Sozsoft.Platform.Entities;
|
||||
using Sozsoft.Platform.Localization;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
|
@ -31,6 +32,7 @@ public class CrudEndpointAppService : ApplicationService
|
|||
{
|
||||
_endpointRepository = endpointRepository;
|
||||
_dynamicManager = dynamicManager;
|
||||
LocalizationResource = typeof(PlatformResource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string, object?> 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' }
|
||||
|
||||
*/
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue