mcp üzerinden Report Templates ekleme yeteneği eklendi
This commit is contained in:
parent
c4e90a459f
commit
c1bdc47d28
21 changed files with 950 additions and 53 deletions
|
|
@ -70,7 +70,7 @@ ama hiçbirini görmeden bırakma; kullanıcı "şunu da yapabilir miydik?" dedi
|
||||||
| 16 | **Gösterge** | Üstte KPI kartları, grafik | §5.3 · §8 |
|
| 16 | **Gösterge** | Üstte KPI kartları, grafik | §5.3 · §8 |
|
||||||
| 17 | **Süreç** | Onay akışı, bilgilendirme, durum alanları | `lowcode` §4.7 |
|
| 17 | **Süreç** | Onay akışı, bilgilendirme, durum alanları | `lowcode` §4.7 |
|
||||||
| 18 | **Otomasyon** | Zamanlanmış iş, zamanlanmış e-posta, bildirim kuralı | `lowcode` §9.10–§9.11 · §11 |
|
| 18 | **Otomasyon** | Zamanlanmış iş, zamanlanmış e-posta, bildirim kuralı | `lowcode` §9.10–§9.11 · §11 |
|
||||||
| 19 | **Çıktı** | Hazır grid raporu ya da rapor şablonu | `lowcode` §9.12 |
|
| 19 | **Çıktı** | Hazır grid raporu ya da `Raporlar` kategorisinde `DynamicReport` şablonu (kayıt alanları `{{AlanAdi}}`) | `lowcode` §9.12 |
|
||||||
| 20 | **Yetki** | Ekran, alan, komut ve buton yetkileri; menü görünürlüğü | §5.1 · `lowcode` §4.2 |
|
| 20 | **Yetki** | Ekran, alan, komut ve buton yetkileri; menü görünürlüğü | §5.1 · `lowcode` §4.2 |
|
||||||
| 21 | **Tenant/şube/satır kısıtı** | `IsTenant`, `IsBranch`, `IsOrganizationUnit`, sorgularda kırılım, rol/kullanıcıya `ServerWhere` | `lowcode` §4.2 · §2.5 |
|
| 21 | **Tenant/şube/satır kısıtı** | `IsTenant`, `IsBranch`, `IsOrganizationUnit`, sorgularda kırılım, rol/kullanıcıya `ServerWhere` | `lowcode` §4.2 · §2.5 |
|
||||||
| 22 | **Dil** | Menü, başlık, alan, buton, mesaj metinleri EN + TR | `lowcode` §4.2 |
|
| 22 | **Dil** | Menü, başlık, alan, buton, mesaj metinleri EN + TR | `lowcode` §4.2 |
|
||||||
|
|
@ -1320,7 +1320,7 @@ ekranları ABP tablolarıdır, Licence/Dev Request host tablolarını okur.
|
||||||
| **Identity › Organization Units** | `Abp.Identity.OrganizationUnits` · `/admin/ous` | ABP OU tabloları | Birim ağacı. Ekranı `IsOrganizationUnit = true` yaparsan veri bu ağaca göre süzülür; bildirim alıcısı da OU olabilir. |
|
| **Identity › Organization Units** | `Abp.Identity.OrganizationUnits` · `/admin/ous` | ABP OU tabloları | Birim ağacı. Ekranı `IsOrganizationUnit = true` yaparsan veri bu ağaca göre süzülür; bildirim alıcısı da OU olabilir. |
|
||||||
| **Identity › Audit Logs** | `App.IdentityManagement.AuditLogs` | `AbpAuditLogs` | İstek denetim kaydı + `AuditLogDetail` diyaloğu. "Kim ne zaman ne yaptı" isteği yeni tablo gerektirmez. |
|
| **Identity › Audit Logs** | `App.IdentityManagement.AuditLogs` | `AbpAuditLogs` | İstek denetim kaydı + `AuditLogDetail` diyaloğu. "Kim ne zaman ne yaptı" isteği yeni tablo gerektirmez. |
|
||||||
| **Identity › Sessions** | `App.IdentityManagement.Sessions` | `AbpSessions` | Açık oturumlar; `UiEvalService.ApiKickUser` ile oturum düşürme (`lowcode` §9.13). |
|
| **Identity › Sessions** | `App.IdentityManagement.Sessions` | `AbpSessions` | Açık oturumlar; `UiEvalService.ApiKickUser` ile oturum düşürme (`lowcode` §9.13). |
|
||||||
| **Reports › Categories / Templates** | `App.Reports.Categories` · `App.Reports.ReportTemplates` | `Adm_T_ReportCategory` · `Adm_T_ReportTemplate` | Kendi düzeni olan çıktılar (fatura, teklif); ekrana `CommandColumnJson` butonuyla bağlanır (`lowcode` §9.12). |
|
| **Reports › Categories / Templates** | `App.Reports.Categories` · `App.Reports.ReportTemplates` | `Adm_T_ReportCategory` · `Adm_T_ReportTemplate` | Kendi düzeni olan çıktılar (fatura, teklif, sertifika, taahhütname). `Raporlar` kategorisindeki şablon `DynamicReport` ile basılan HTML'dir; satırdan `/admin/reports/DynamicReport/view/{Id}/{ListFormCode}?recordId=@Id` ile açılır, `{{AlanAdi}}` kayıtla dolar. MCP: `list_report_templates`, `save_report_template`; ekrana wizard `CommandColumns` butonuyla bağlanır (`lowcode` §9.12). Seed bayrakları açık. |
|
||||||
| **Videoroom** | `App.Videoroom` · `/admin/videoroom/dashboard` | `Adm_T_Videoroom` · `Adm_T_VideoroomParticipant` · `Adm_T_VideoroomAttandance` · `Adm_T_VideoroomChat` | Görüntülü oda, katılım ve sohbet kaydı. |
|
| **Videoroom** | `App.Videoroom` · `/admin/videoroom/dashboard` | `Adm_T_Videoroom` · `Adm_T_VideoroomParticipant` · `Adm_T_VideoroomAttandance` · `Adm_T_VideoroomChat` | Görüntülü oda, katılım ve sohbet kaydı. |
|
||||||
| **Forum** | menü kodu `App.Forum` · yetki `App.ForumManagement.Publish` · `/admin/forum` | `Sas_T_Forum*` | Forumun kullanıcı tarafı. |
|
| **Forum** | menü kodu `App.Forum` · yetki `App.ForumManagement.Publish` · `/admin/forum` | `Sas_T_Forum*` | Forumun kullanıcı tarafı. |
|
||||||
|
|
||||||
|
|
@ -1455,7 +1455,7 @@ alanları da doldurur (`lowcode` §0.9).
|
||||||
| "bilgilendirme gitsin" | Administration › Bildirimler (+ MailQueue §11) | Tür + kural (8 kanal); tablolu/ekli e-posta ise MailQueue |
|
| "bilgilendirme gitsin" | Administration › Bildirimler (+ MailQueue §11) | Tür + kural (8 kanal); tablolu/ekli e-posta ise MailQueue |
|
||||||
| "elden bildirim/duyuru gönderelim" | Administration › Bildirimler › Bildirimler (+ Şablonlar) | Ekleme formu = elle gönderim; hazır metin için şablon kaydı |
|
| "elden bildirim/duyuru gönderelim" | Administration › Bildirimler › Bildirimler (+ Şablonlar) | Ekleme formu = elle gönderim; hazır metin için şablon kaydı |
|
||||||
| "onaya düşsün" | `WorkflowJson` + `ListFormWorkflow` (örnek: Sales Orders) | Onay düğümleri; `Inform` düğümü e-posta gönderir |
|
| "onaya düşsün" | `WorkflowJson` + `ListFormWorkflow` (örnek: Sales Orders) | Onay düğümleri; `Inform` düğümü e-posta gönderir |
|
||||||
| "çıktısı alınsın / fatura basılsın" | Administration › Reports | Hazır `DynamicGrid` ya da `ReportTemplate` |
|
| "çıktısı alınsın / fatura / sertifika basılsın" | Administration › Reports | Hazır `DynamicGrid` ya da `Raporlar` kategorisinde `DynamicReport` şablonu (`save_report_template`) |
|
||||||
| "dosya eklensin" | Saas › Developer Kit › Files | Dosya/görsel editörü; ayrı depolama kurma |
|
| "dosya eklensin" | Saas › Developer Kit › Files | Dosya/görsel editörü; ayrı depolama kurma |
|
||||||
| "fotoğraf çekilsin / barkod okutulsun / konum alınsın" | Alan editörü (§9.8 tablosu) · tasarımcıda `BarcodeScanField` | `Capture`, `@SCAN_BARCODE`, `BarcodeScanDialog`, `geoTo`; yeni editör yazma |
|
| "fotoğraf çekilsin / barkod okutulsun / konum alınsın" | Alan editörü (§9.8 tablosu) · tasarımcıda `BarcodeScanField` | `Capture`, `@SCAN_BARCODE`, `BarcodeScanDialog`, `geoTo`; yeni editör yazma |
|
||||||
| "barkod/QR basılsın" | Visual Designer › `BarcodeViewer` | `value`'yu `Form` kolonuna bağla |
|
| "barkod/QR basılsın" | Visual Designer › `BarcodeViewer` | `value`'yu `Form` kolonuna bağla |
|
||||||
|
|
|
||||||
54
.github/instructions/lowcode.instructions.md
vendored
54
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -506,7 +506,7 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz.
|
||||||
| `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla istenir (`4.3`); aynı numarayı iki başlık taşımamalıdır |
|
| `get_dev_guide` | Bu dosyanın (`lowcode`) ya da `reference`'ın başlık listesini / bir bölümünü döner | Bölüm numarayla istenir (`4.3`); aynı numarayı iki başlık taşımamalıdır |
|
||||||
| `create_table` | Yeni SQL Server tablosu kurar, `sql/object/{Tablo}.sql` dosyasını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz |
|
| `create_table` | Yeni SQL Server tablosu kurar, `sql/object/{Tablo}.sql` dosyasını yazar | Ek olarak `App.SqlQueryManager` ister; `Id`/`TenantId`/audit kolonlarını kendi ekler (`fullAudited`, `multiTenant` varsayılan açık); var olan tabloyu reddeder. View/SP/fonksiyon kurulamaz |
|
||||||
| `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` |
|
| `list_wizards` · `get_wizard` | Wizard dosyalarını listeler (menü yolu, bağımlılar) · dosyayı diskteki hâliyle döner | Var olan menü kökünü/izin grubunu bulmak için önce `list_wizards` |
|
||||||
| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). List ekranında `Widgets` dolu gelir — alt form olarak üretilen ekran hariç (`Widgets: []`) — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (`DevRequest`) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez |
|
| `deploy_wizard` | Wizard cevabından ekranın wizard dosyasını ve menü/yetki/dil seed satırlarını yazar; veritabanına yazmaz (§4.0) | Girdi §4.4'teki düz cevap ya da `get_wizard` çıktısıyla aynı dosya biçimi; enum'lar sayı. `replaceFileName` = `EditFileName` (§4.5). List ekranında `Widgets` dolu gelir — alt form olarak üretilen ekran hariç (`Widgets: []`) — en az 3 kart, kiracı tablosunda `"TenantId" = '@TENANTID'` (§4.6.1); kartlardaki her dil anahtarı var olmalı ya da `Language.Texts`'te EN + TR verilmeli, yoksa deploy reddeder. `MenuOrder` yazılmaz — sunucu yeni ekranı kardeşlerinin sonuna koyar, güncellemede yerini korur. Talep bağı (`DevRequest`) da yazılmaz — sunucu etkin talepten doldurur (§4.0). İstek demo veri taşıyorsa `SeedData: true` + `seedRows` (§0.10.1). Satır/toolbar butonları `CommandColumns` (§4.9). Çıkıştaki `isPending: true` ekranın henüz kurulmadığını söyler — kullanıcı Wizard Manager'da DB Migrate çalıştırır; model migrate'i beklemez |
|
||||||
| `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) |
|
| `delete_wizard` | Wizard'ı ve yalnızca onun ürettiği kayıtları siler; `dropTables: true` ekranın tablosunu da düşürür | Bağımlı wizard varsa reddeder. Tablo kullanıcı açıkça onaylamadan düşürülmez; `App.SqlQueryManager` ister (§4.5) |
|
||||||
| `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) |
|
| `start_dev_request` | `App.DevRequests` talebini üstlenir: `Ready to Start` durumundaysa `In Progress` durumuna alır, değilse reddeder | Ek olarak `App.DevRequests.Update` ister. Açıklamadaki görseller ayrı resim içeriği olarak gelir (§0.11) |
|
||||||
| `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister |
|
| `get_dev_request` | Talebi durumuna dokunmadan okur (aynı metin + analiz + resim içerikleri) | Analizden önce ya da yarım kalan talebe dönmek için; `App.DevRequests` ister |
|
||||||
|
|
@ -515,6 +515,7 @@ ister ve kiracı kullanıcısına hiçbiri açılmaz.
|
||||||
| `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) |
|
| `add_dev_request_subtasks` | `SubTasks` listesine tamamlanmış satırlar ekler | Atanan = bağlanan kullanıcı, bitiş = bugün (sunucu yazar); `SubTasks`'ı `update_row` ile yazma (§0.11) |
|
||||||
| `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` |
|
| `verify_wizard` | DB Migrate ile kurulmuş ekranı veri yazmadan doğrular (migrate bekleyen dosyada `install` kontrolü başarısız döner, diğerleri yapılmaz): menü, yetki tanımı, ekran sorgusu (List) ya da bileşen (Custom), seed kapanışı | İsteğe bağlı: kullanıcı migrate sonrası isterse. Talep döngüsünün (§0.11) adımı değildir. `passed` yalnızca engelleyici bir kontrol kalırsa `false` |
|
||||||
| `list_components` · `get_component` · `save_component` · `delete_component` | Custom component okuma/yazma (ada göre upsert) | `save_component` visual designer bileşenini reddeder — yalnız Code modu (§7.1.1) |
|
| `list_components` · `get_component` · `save_component` · `delete_component` | Custom component okuma/yazma (ada göre upsert) | `save_component` visual designer bileşenini reddeder — yalnız Code modu (§7.1.1) |
|
||||||
|
| `list_report_templates` · `get_report_template` · `save_report_category` · `save_report_template` · `delete_report_template` | Rapor kategorisi ve `DynamicReport` şablonu okuma/yazma (ada göre upsert) | Yazma `App.Reports.*` ekranlarının C/U/D sözleşmesinden geçer, seed dosyasına yansır. `listFormCode` ile yer tutucuları doğrular ve satır butonunu döner (§9.12) |
|
||||||
|
|
||||||
**Yetkiler.** MCP'nin iki yetkisi vardır: `.Invoke` (bağlantı + okuma) ve `.Write` (her
|
**Yetkiler.** MCP'nin iki yetkisi vardır: `.Invoke` (bağlantı + okuma) ve `.Write` (her
|
||||||
değişiklik; kiracıda yalnızca satır yazma). İkisi de kapıdır, işlemin kendi uygulama yetkisi ayrıca
|
değişiklik; kiracıda yalnızca satır yazma). İkisi de kapıdır, işlemin kendi uygulama yetkisi ayrıca
|
||||||
|
|
@ -808,7 +809,7 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
|
||||||
| "Kaydederken/silerken ek SQL çalışsın (stok, log, durum)" | `*BeforeCommand` / `*AfterCommand` | 4.8 |
|
| "Kaydederken/silerken ek SQL çalışsın (stok, log, durum)" | `*BeforeCommand` / `*AfterCommand` | 4.8 |
|
||||||
| "Şu iş her gece / her 5 dakikada çalışsın" | `BackgroundWorker` kaydı + stored procedure | 9.10 |
|
| "Şu iş her gece / her 5 dakikada çalışsın" | `BackgroundWorker` kaydı + stored procedure | 9.10 |
|
||||||
| "Şu olayda bildirim gitsin" | Notification Type + Rule | 9.11 |
|
| "Şu olayda bildirim gitsin" | Notification Type + Rule | 9.11 |
|
||||||
| "Fatura/teklif/iş emri çıktısı" | Rapor şablonu (ya da hazır `DynamicGrid`) | 9.12 |
|
| "Fatura/teklif/iş emri/sertifika çıktısı" | `Raporlar` kategorisinde `DynamicReport` şablonu + satır butonu (ya da hazır `DynamicGrid`) | 9.12 |
|
||||||
| "Ayar ekranından açılıp kapansın" | Setting Definition | 9.14 |
|
| "Ayar ekranından açılıp kapansın" | Setting Definition | 9.14 |
|
||||||
| Hiçbiri yetmiyorsa | Dynamic Service → en son çare kod | `ai.instructions.md` §4 |
|
| Hiçbiri yetmiyorsa | Dynamic Service → en son çare kod | `ai.instructions.md` §4 |
|
||||||
|
|
||||||
|
|
@ -1611,6 +1612,8 @@ cevaba yazılmaz.
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SubForms": [],
|
"SubForms": [],
|
||||||
|
// Satır/toolbar butonları (§4.9) — örneğin rapor şablonu açan satır butonu (§9.12).
|
||||||
|
"CommandColumns": [],
|
||||||
// List ekranı widget'la gelir (§4.6.1): kiracı tablosu → her kartta TenantId koşulu.
|
// List ekranı widget'la gelir (§4.6.1): kiracı tablosu → her kartta TenantId koşulu.
|
||||||
"Widgets": [
|
"Widgets": [
|
||||||
{
|
{
|
||||||
|
|
@ -2120,7 +2123,11 @@ olarak da görelim" isteği yeni bir ekran değil, `Todo: true` + `TodoOptionDto
|
||||||
Grid'in araç çubuğuna ve satır aksiyon sütununa **özel butonlar** eklenir. Süreç yönetiminin
|
Grid'in araç çubuğuna ve satır aksiyon sütununa **özel butonlar** eklenir. Süreç yönetiminin
|
||||||
(onaya gönder, iptal et, belge üret, dış sisteme aktar) kod yazmadan yapıldığı yer burasıdır.
|
(onaya gönder, iptal et, belge üret, dış sisteme aktar) kod yazmadan yapıldığı yer burasıdır.
|
||||||
|
|
||||||
`ListForm.CommandColumnJson` bir `CommandColumnDto[]` taşır:
|
`ListForm.CommandColumnJson` bir `CommandColumnDto[]` taşır. Wizard cevabında (§4.4) ve MCP
|
||||||
|
`deploy_wizard` girdisinde aynı dizi **`CommandColumns`** alanıdır; deploy onu
|
||||||
|
`CommandColumnJson`'a yazar, seed dosyasında `ListForm` bölümünde durur ve düzenlemede korunur.
|
||||||
|
Wizard ekranında bu alanın adımı yoktur; buton ekranın detayındaki **Komutlar** sekmesinden ya da
|
||||||
|
`deploy_wizard` ile yazılır.
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
[
|
[
|
||||||
|
|
@ -4237,18 +4244,43 @@ Raporlama DevExpress XtraReports tabanlıdır ve iki katmanı vardır:
|
||||||
| Katman | Ne |
|
| Katman | Ne |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **Hazır (predefined) raporlar** | `DynamicGrid`, `DynamicTree`, `DynamicForm` — o anki ekranın sütunlarını, filtrelerini ve verisini alıp yazdırılabilir çıktı üretir |
|
| **Hazır (predefined) raporlar** | `DynamicGrid`, `DynamicTree`, `DynamicForm` — o anki ekranın sütunlarını, filtrelerini ve verisini alıp yazdırılabilir çıktı üretir |
|
||||||
| **Rapor şablonları** | `App.Reports.ReportTemplates` + `App.Reports.Categories` ekranlarındaki kayıtlar; tasarımcıda çizilir, adıyla açılır |
|
| **Rapor şablonları** | `App.Reports.ReportTemplates` + `App.Reports.Categories` ekranlarındaki kayıtlar. `Raporlar` kategorisindeki şablon `DynamicReport` ile basılan HTML'dir; Id'siyle açılır, MCP `save_report_template` ile eklenir |
|
||||||
|
|
||||||
Route'lar: görüntüleyici `/admin/reports/{report}/view/{id?}/{listFormCode?}`, tasarımcı
|
Route'lar: görüntüleyici `/admin/reports/{report}/view/{id?}/{listFormCode?}`, tasarımcı
|
||||||
`/admin/reports/{report}/design/{id?}/{listFormCode?}`.
|
`/admin/reports/{report}/design/{id?}/{listFormCode?}`.
|
||||||
|
|
||||||
- Her List ekranının araç çubuğundaki rapor aksiyonu `DynamicGrid` (TreeList'te `DynamicTree`)
|
- Her List ekranının araç çubuğundaki rapor aksiyonu `DynamicGrid` (TreeList'te `DynamicTree`)
|
||||||
raporunu **o anki filtrelerle** açar; bunun için hiçbir tanım yapılmaz.
|
raporunu **o anki filtrelerle** açar; bunun için hiçbir tanım yapılmaz.
|
||||||
- Kendi düzeni istenen çıktı (fatura, teklif, iş emri) için `ReportTemplate` kaydı açılır ve
|
- Kendi düzeni istenen kayıt çıktısı (sertifika, taahhütname, teklif, iş emri) **`Raporlar`
|
||||||
tasarımcıdan çizilir; ekrana `CommandColumnJson` butonu ile bağlanır
|
kategorisinde bir `DynamicReport` şablonudur**: `HtmlContent` A4'e basılan HTML'dir, ayrı bir
|
||||||
(`Url: "/admin/reports/{ReportAdi}/view/@Id"`).
|
`.repx` çizilmez. Kayıtsız görüntüleme `/admin/reports/DynamicReport/view/{ŞablonId}`
|
||||||
|
(`Standard Taahhütname` gibi). Bir ekran satırından açılınca adres
|
||||||
|
`/admin/reports/DynamicReport/view/{ŞablonId}/{ListFormCode}?recordId=@{KeyFieldName}` olur ve
|
||||||
|
HTML'deki `{{AlanAdi}}` yer tutucuları o kaydın alanlarıyla doldurulur. Değer, form raporunun
|
||||||
|
bastığı metindir (lookup görünen metni, statik lookup karşılığı, tarih/sayı biçimi) ve HTML
|
||||||
|
olarak kodlanır. Kayıt ekranın okuma yetkisi ve kiracı filtresiyle okunur. `@@date` rapor
|
||||||
|
tarihidir. Ekranda alanı olmayan bir değer yer tutucu olamaz; gerekiyorsa ekrana gizli alan
|
||||||
|
olarak eklenir.
|
||||||
|
- Şablon kiracıda bulunamazsa host şablonu kullanılır: host şablonu ürünle gelen ortak tasarımdır,
|
||||||
|
basılan veri yine kiracının kendi ekranındandır.
|
||||||
|
- Şablon ekrana `CommandColumnJson` satır butonu ile bağlanır; koşullu buton `VisibleExpression`
|
||||||
|
ile yazılır. Örnek: sertifika yalnızca katılan katılımcıda görünür,
|
||||||
|
`((e)=> e.row.data.AttendanceStatus === 'Attended')`.
|
||||||
|
- **MCP ile:** `list_report_templates` → `save_report_template` (`category: "Raporlar"`,
|
||||||
|
`htmlContent`, `listFormCode`). `listFormCode` verilirse yer tutucular o ekranın alanlarına
|
||||||
|
göre doğrulanır ve cevapta `rowButton` döner. Buton ekranın wizard cevabına `CommandColumns`
|
||||||
|
elemanı olarak yazılır (§4.9) ve `deploy_wizard` ile gider. Ekran henüz migrate edilmediyse
|
||||||
|
`listFormCode` verilmez; buton adresi `viewUrl` + `/{ListFormCode}?recordId=@Id` ile yazılır.
|
||||||
|
Sıra: önce ekran(lar) `deploy_wizard`, sonra şablon `save_report_template`, sonra butonlu ekran
|
||||||
|
`deploy_wizard` (`replaceFileName`) — ya da şablon önce kaydedilip Id'si butona yazılır. Yeni
|
||||||
|
kategori (`save_report_category`) yalnızca istenince açılır.
|
||||||
|
- Rapor şablonu **eksik yetenek değildir**: sertifika/taahhütname/belge çıktısı isteyen bir talep
|
||||||
|
bu araçlarla üretilir; analizde "tasarımcıda çizilir" ya da "seed'e yazılmaz" gerekçesiyle
|
||||||
|
`Ready to Start` engellenmez.
|
||||||
- Rapor şablonları ve kategorileri `data/App.Reports.{ReportTemplates,Categories}.json` ile
|
- Rapor şablonları ve kategorileri `data/App.Reports.{ReportTemplates,Categories}.json` ile
|
||||||
seed'lenir; seed bayrakları kapalıdır, tasarımcıda yapılan değişiklik dosyaya yansımaz (§9.14).
|
seed'lenir ve seed bayrakları açıktır: ekrandan ya da MCP'den yapılan kayıt dosyaya yansır
|
||||||
|
(§9.14). `DynamicReport/design` ile kaydedilen sayfa çerçevesi (`.repx`) sunucudaki
|
||||||
|
`PredefinedReports/Reports` klasörüne yazılır, seed'e girmez.
|
||||||
|
|
||||||
### 9.13 `UiEvalService` — buton ve script'ten çağrılabilen platform aksiyonları
|
### 9.13 `UiEvalService` — buton ve script'ten çağrılabilen platform aksiyonları
|
||||||
|
|
||||||
|
|
@ -4275,7 +4307,7 @@ kullanılacağı** `lowcode-reference.instructions.md` §12'dedir.
|
||||||
| "Belge numarası otomatik olsun" | Sequence (§9.9) | `/admin/list/App.Definitions.Sequence` |
|
| "Belge numarası otomatik olsun" | Sequence (§9.9) | `/admin/list/App.Definitions.Sequence` |
|
||||||
| "Şu iş her gece çalışsın" | Recurring Jobs (§9.10) | `/admin/list/App.BackgroundWorkers.RecurringJobs` |
|
| "Şu iş her gece çalışsın" | Recurring Jobs (§9.10) | `/admin/list/App.BackgroundWorkers.RecurringJobs` |
|
||||||
| "Şu olayda bildirim gitsin" | Notification Types + Rules (§9.11) | `/admin/list/App.Notifications.NotificationRules` |
|
| "Şu olayda bildirim gitsin" | Notification Types + Rules (§9.11) | `/admin/list/App.Notifications.NotificationRules` |
|
||||||
| "Fatura/teklif çıktısı" | Report Templates (§9.12) | `/admin/list/App.Reports.ReportTemplates` |
|
| "Fatura/teklif/sertifika çıktısı" | Report Templates — `Raporlar` kategorisi, `DynamicReport` (§9.12); MCP `save_report_template` | `/admin/list/App.Reports.ReportTemplates` |
|
||||||
| "Yeni bir veritabanı bağlantısı" | Data Source | `/admin/list/App.DataSource` |
|
| "Yeni bir veritabanı bağlantısı" | Data Source | `/admin/list/App.DataSource` |
|
||||||
| "Uygulama ayarı (e-posta, limit, açık/kapalı)" | Setting Definitions + Ayarlar ekranı | `/admin/list/App.SettingDefinitions` · `/admin/settings` |
|
| "Uygulama ayarı (e-posta, limit, açık/kapalı)" | Setting Definitions + Ayarlar ekranı | `/admin/list/App.SettingDefinitions` · `/admin/settings` |
|
||||||
| "Metin/çeviri değişsin" | Languages + Language Texts | `/admin/list/App.Languages.LanguageText` |
|
| "Metin/çeviri değişsin" | Languages + Language Texts | `/admin/list/App.Languages.LanguageText` |
|
||||||
|
|
@ -4298,8 +4330,8 @@ kullanılacağı** `lowcode-reference.instructions.md` §12'dedir.
|
||||||
>
|
>
|
||||||
> | Durum | Ekranlar (örnek) | Prompt ile kayıt eklerken |
|
> | Durum | Ekranlar (örnek) | Prompt ile kayıt eklerken |
|
||||||
> | --- | --- | --- |
|
> | --- | --- | --- |
|
||||||
> | Seed bayrakları açık — ekrandan yapılan kayıt dosyaya yansır | RecurringJobs, NotificationTemplates/Types/Rules, SettingDefinitions, AiBot, CustomEndpoints, DynamicServices, CustomComponents, McpServers, IntranetWidgets, `App.Menus.*`, `Abp.Identity.Permission*` | Satırı `data/{ListFormCode}.json`'a yaz |
|
> | Seed bayrakları açık — ekrandan yapılan kayıt dosyaya yansır | RecurringJobs, NotificationTemplates/Types/Rules, SettingDefinitions, AiBot, CustomEndpoints, DynamicServices, CustomComponents, McpServers, IntranetWidgets, ReportTemplates/Categories, `App.Menus.*`, `Abp.Identity.Permission*` | Satırı `data/{ListFormCode}.json`'a yaz (rapor şablonu için `save_report_template`) |
|
||||||
> | `SeedFilePath` dolu, bayraklar kapalı — dosya migrate'te uygulanır, ekrandan yapılan değişiklik dosyaya **yansımaz** | Sequence, GlobalSearch, Currency, ContactTitle, Country/City/District, Uom, Hr Department/JobPosition/SkillType, ReportTemplates/Categories, WorkHour, Intranet, Blog, Forum, Orders tanımları | Satırı dosyaya yaz; ekrandan eklenen kayıt teslim sayılmaz |
|
> | `SeedFilePath` dolu, bayraklar kapalı — dosya migrate'te uygulanır, ekrandan yapılan değişiklik dosyaya **yansımaz** | Sequence, GlobalSearch, Currency, ContactTitle, Country/City/District, Uom, Hr Department/JobPosition/SkillType, WorkHour, Intranet, Blog, Forum, Orders tanımları | Satırı dosyaya yaz; ekrandan eklenen kayıt teslim sayılmaz |
|
||||||
> | `SeedFilePath` boş — yalnızca veritabanı | DataSource, Languages, Branches, Roles/Users/Tenants, AuditLogs, IpRestrictions, Notification (geçmiş), BackgroundWorkers.Jobs | `sql/execute/{Ad}.sql` altında **varsa güncelleyen/yoksa ekleyen** bir prosedür (§8.4) |
|
> | `SeedFilePath` boş — yalnızca veritabanı | DataSource, Languages, Branches, Roles/Users/Tenants, AuditLogs, IpRestrictions, Notification (geçmiş), BackgroundWorkers.Jobs | `sql/execute/{Ad}.sql` altında **varsa güncelleyen/yoksa ekleyen** bir prosedür (§8.4) |
|
||||||
>
|
>
|
||||||
> Tam liste `ListFormSeeder_Saas.cs` / `ListFormSeeder_Administration.cs` / `ListFormSeeder_Hr.cs` içindeki `SeedFilePath`
|
> Tam liste `ListFormSeeder_Saas.cs` / `ListFormSeeder_Administration.cs` / `ListFormSeeder_Hr.cs` içindeki `SeedFilePath`
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -1016,7 +1016,7 @@ içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppSer
|
||||||
| Forum | `/admin/forum` | Forum kullanımı; yönetim `App.ForumManagement` altındaki ListForm ekranlarıyla yapılır. |
|
| Forum | `/admin/forum` | Forum kullanımı; yönetim `App.ForumManagement` altındaki ListForm ekranlarıyla yapılır. |
|
||||||
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Sas_T_IntranetWidget` kaydından gelir; kayıtlar `configs/seeds/{kapsam}/data/App.DeveloperKit.IntranetWidgets.json` ile senkron tutulur. |
|
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Sas_T_IntranetWidget` kaydından gelir; kayıtlar `configs/seeds/{kapsam}/data/App.DeveloperKit.IntranetWidgets.json` ile senkron tutulur. |
|
||||||
| Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. |
|
| Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. |
|
||||||
| Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. |
|
| Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. `Raporlar` kategorisindeki şablon: `/admin/reports/DynamicReport/view/{ŞablonId}[/{ListFormCode}?recordId=...]` (§15). |
|
||||||
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |
|
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |
|
||||||
| Profil | `/admin/profile/*` | Genel bilgiler, şifre, bildirim ayarları. |
|
| Profil | `/admin/profile/*` | Genel bilgiler, şifre, bildirim ayarları. |
|
||||||
|
|
||||||
|
|
@ -1464,11 +1464,13 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
|
||||||
| `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. |
|
| `add_dev_request_subtasks` | Talebin `SubTasks` listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (`userName`), bitiş tarihi bugün (`yyyy-MM-dd`) olarak sunucuda yazılır; var olan satırlar korunur. `.Write` + `App.DevRequests.Update` ister. |
|
||||||
| `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. |
|
| `append_dev_request_note` | Talebin `Analysis` kolonunun sonuna HTML ekler (üretim sonuç notu); `Description` talebin yazıldığı hâliyle kalır. İkisi de `update_row` ile yazılmaz — açıklama base64 görsel taşır. `.Write` + `App.DevRequests.Update` ister. |
|
||||||
| `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. |
|
| `list_components` / `get_component` / `save_component` / `delete_component` | Custom component'ler `ICustomComponentAppService` üzerinden; seed senkronu ve `DataSources` çözümü aynen çalışır. `save_component` ada göre ekler/günceller, tasarımcı belgesi (`Props.visualDesigner`, `sourceMode ≠ code`) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi. |
|
||||||
|
| `list_report_templates` / `get_report_template` / `save_report_category` / `save_report_template` / `delete_report_template` | Rapor kategorileri ve `DynamicReport` şablonları (`McpReportAppService`). Okuma depodan, ekranın okuma yetkisiyle yapılır. Yazma `App.Reports.Categories` / `App.Reports.ReportTemplates` ekranlarının C/U/D sözleşmesinden (`IListFormDataAppService`) geçer; seed senkronu aynen çalışır. Kayıt ada göre ekler/günceller. `listFormCode` verilirse HTML'deki `{{AlanAdi}}` yer tutucuları o ekranın alanlarına göre doğrulanır ve cevapta satır butonu (`rowButton`) döner (§15). |
|
||||||
|
|
||||||
**Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`):
|
**Timesheets.** Talebin `Timesheets` listesini sunucu doldurur (`IMcpDevRequestTimesheetRecorder`):
|
||||||
`get_dev_request` / `start_dev_request` talebi kullanıcının etkin talebi yapar (dağıtık önbellek,
|
`get_dev_request` / `start_dev_request` talebi kullanıcının etkin talebi yapar (dağıtık önbellek,
|
||||||
`McpDevRequestActivityCacheItem`, 12 saat); sonraki her MCP yazma işlemi (`create_table`,
|
`McpDevRequestActivityCacheItem`, 12 saat); sonraki her MCP yazma işlemi (`create_table`,
|
||||||
`deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, satır araçları,
|
`deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `save_report_category`,
|
||||||
|
`save_report_template`, `delete_report_template`, satır araçları,
|
||||||
`save_dev_request_analysis`, `append_dev_request_note`, `add_dev_request_subtasks`) o talebe bir satır
|
`save_dev_request_analysis`, `append_dev_request_note`, `add_dev_request_subtasks`) o talebe bir satır
|
||||||
ekler; talebi açan `start_dev_request` yazmaz. Açıklama `App.Mcp.Timesheet.*` anahtarından
|
ekler; talebi açan `start_dev_request` yazmaz. Açıklama `App.Mcp.Timesheet.*` anahtarından
|
||||||
Türkçe üretilir, süre önceki satırdan ya da talebin açıldığı andan ölçülür (en az 1 dakika, saat
|
Türkçe üretilir, süre önceki satırdan ya da talebin açıldığı andan ölçülür (en az 1 dakika, saat
|
||||||
|
|
@ -1525,7 +1527,7 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `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` | 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`, `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`, `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `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 |
|
| `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`, `deploy_wizard`, `delete_wizard`, `save_component`, `delete_component`, `save_report_category`, `save_report_template`, `delete_report_template`, `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ı)
|
**Araç → gereken yetkiler** (Invoke hepsinde aranır, tabloda tekrar yazılmadı)
|
||||||
|
|
||||||
|
|
@ -1545,6 +1547,10 @@ yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ay
|
||||||
| `list_components`, `get_component` | — | — | `App.DeveloperKit.CustomComponents` |
|
| `list_components`, `get_component` | — | — | `App.DeveloperKit.CustomComponents` |
|
||||||
| `save_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Create` (yeni) / `.Update` |
|
| `save_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Create` (yeni) / `.Update` |
|
||||||
| `delete_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Delete` |
|
| `delete_component` | — | `.Write` | `App.DeveloperKit.CustomComponents.Delete` |
|
||||||
|
| `list_report_templates`, `get_report_template` | — | — | `App.Reports.ReportTemplates` |
|
||||||
|
| `save_report_category` | — | `.Write` | `App.Reports.Categories.Create` (yeni) / `.Update` |
|
||||||
|
| `save_report_template` | — | `.Write` | `App.Reports.ReportTemplates.Create` (yeni) / `.Update` |
|
||||||
|
| `delete_report_template` | — | `.Write` | `App.Reports.ReportTemplates.Delete` |
|
||||||
| `start_dev_request` | — | `.Write` | `App.DevRequests.Update` |
|
| `start_dev_request` | — | `.Write` | `App.DevRequests.Update` |
|
||||||
| `get_dev_request` | — | — | `App.DevRequests` |
|
| `get_dev_request` | — | — | `App.DevRequests` |
|
||||||
| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update` |
|
| `save_dev_request_analysis` | — | `.Write` | `App.DevRequests.Update` |
|
||||||
|
|
@ -1713,8 +1719,16 @@ Hangfire panosu geliştirmede serbest, üretimde `AbpHangfireAuthorizationFilter
|
||||||
|
|
||||||
- **DevExpress Reporting**: Web Document Viewer (`/admin/reports/:report/view/...`) ve
|
- **DevExpress Reporting**: Web Document Viewer (`/admin/reports/:report/view/...`) ve
|
||||||
Report Designer (`/admin/reports/:report/design/...`).
|
Report Designer (`/admin/reports/:report/design/...`).
|
||||||
- **Rapor deposu**: `ReportTemplate` ve `ReportCategory` varlıkları; `CustomReportStorageWebExtension`
|
- **Rapor deposu**: `ReportTemplate` ve `ReportCategory` varlıkları
|
||||||
ile veritabanı üzerinden okuma/yazma.
|
(`data/App.Reports.{ReportTemplates,Categories}.json`, seed senkronu açık). Tasarımcıda
|
||||||
|
kaydedilen `.repx` düzenleri `CustomReportStorageWebExtension` ile sunucudaki
|
||||||
|
`PredefinedReports/Reports` klasörüne yazılır.
|
||||||
|
- **`DynamicReport` şablonları** (`Raporlar` kategorisi): `HtmlContent` HTML'i A4'e basılır
|
||||||
|
(`/admin/reports/DynamicReport/view/{ŞablonId}`). Ekran satırından açılınca
|
||||||
|
(`.../view/{ŞablonId}/{ListFormCode}?recordId=@Id`) `{{AlanAdi}}` yer tutucuları kaydın form
|
||||||
|
raporundaki metniyle doldurulur (`DynamicFormReport.GetRecordTextsAsync`; ekran okuma yetkisi +
|
||||||
|
kiracı filtresi, değerler HTML kodlanır). Kiracıda bulunmayan şablon host'tan okunur. MCP
|
||||||
|
araçları §13.9.
|
||||||
- **Dinamik raporlar** (`HttpApi.Host/PredefinedReports`): ListForm tanımından otomatik üretilen
|
- **Dinamik raporlar** (`HttpApi.Host/PredefinedReports`): ListForm tanımından otomatik üretilen
|
||||||
grid, form ve ağaç raporları — grid durumunu (sütun sırası, gruplama, filtre) rapora yansıtır.
|
grid, form ve ağaç raporları — grid durumunu (sütun sırası, gruplama, filtre) rapora yansıtır.
|
||||||
- Linux konteynerlerde font sorunlarını önlemek için `DevExpressFontConfiguration` devrededir.
|
- Linux konteynerlerde font sorunlarını önlemek için `DevExpressFontConfiguration` devrededir.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
/// <summary>Rapor kategorisi (<c>App.Reports.Categories</c>).</summary>
|
||||||
|
public class McpReportCategoryDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rapor sablonunun listede donen ozeti; HTML icerigi tasimaz.</summary>
|
||||||
|
public class McpReportTemplateSummaryDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string Category { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kategoriler ve sablonlar birlikte; sablon kaydederken kategori adi buradan secilir.</summary>
|
||||||
|
public class McpReportCatalogDto
|
||||||
|
{
|
||||||
|
public List<McpReportCategoryDto> Categories { get; set; } = [];
|
||||||
|
|
||||||
|
public List<McpReportTemplateSummaryDto> Templates { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rapor sablonunun tamami. <see cref="Placeholders"/> HTML icindeki <c>{{AlanAdi}}</c> yer
|
||||||
|
/// tutuculardir; sablon bir ekran kaydiyla acildiginda kaydin alanlariyla doldurulur.
|
||||||
|
/// </summary>
|
||||||
|
public class McpReportTemplateDto : McpReportTemplateSummaryDto
|
||||||
|
{
|
||||||
|
public Guid CategoryId { get; set; }
|
||||||
|
|
||||||
|
public string HtmlContent { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public List<string> Placeholders { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Sablonun kayitsiz goruntuleme adresi.</summary>
|
||||||
|
public string ViewUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaydetme istegi bir ekran verdiyse o ekranin satir butonu (<c>CommandColumnJson</c>
|
||||||
|
/// elemani); ekranin mevcut butonlarina eklenir.
|
||||||
|
/// </summary>
|
||||||
|
public McpReportRowButtonDto? RowButton { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sablonu bir ekran kaydiyla acan satir butonu; <c>CommandColumnDto</c> ile ayni alanlar.</summary>
|
||||||
|
public class McpReportRowButtonDto
|
||||||
|
{
|
||||||
|
public string Text { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Hint { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string UrlTarget { get; set; } = "_blank";
|
||||||
|
|
||||||
|
public string AuthName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpReportCategorySaveRequestDto
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public string? Icon { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McpReportTemplateSaveRequestDto
|
||||||
|
{
|
||||||
|
/// <summary>Sablonun adi; kiraci icinde tekildir ve kimligidir.</summary>
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Kategori adi ya da Id'si. Yeni sablonda zorunlu.</summary>
|
||||||
|
public string? Category { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Basilacak HTML. Yeni sablonda zorunlu; guncellemede bos ise mevcut icerik korunur.</summary>
|
||||||
|
public string? HtmlContent { get; set; }
|
||||||
|
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary><c>active</c> ya da <c>passive</c>; yeni sablonda varsayilan <c>active</c>.</summary>
|
||||||
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sablonun basilacagi ekran. Verilirse yer tutucular ekranin alanlarina gore dogrulanir ve
|
||||||
|
/// cevapta o ekranin satir butonu doner.
|
||||||
|
/// </summary>
|
||||||
|
public string? ListFormCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Satir butonunun dil anahtari; verilmezse <c>App.Platform.View</c>.</summary>
|
||||||
|
public string? ButtonText { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rapor kategorisi ve rapor sablonu uretimi. Yazma <c>App.Reports.Categories</c> ve
|
||||||
|
/// <c>App.Reports.ReportTemplates</c> ekranlarinin C/U/D sozlesmesinden gecer; ekran yetkisi,
|
||||||
|
/// kiraci filtresi ve seed senkronizasyonu oradaki yolla ayni sekilde uygulanir.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMcpReportAppService : IApplicationService
|
||||||
|
{
|
||||||
|
Task<McpReportCatalogDto> GetReportTemplatesAsync(string? search);
|
||||||
|
|
||||||
|
/// <summary>Sablonu Id'si ya da adiyla, HTML icerigi ve yer tutuculariyla doner.</summary>
|
||||||
|
Task<McpReportTemplateDto> GetReportTemplateAsync(string template);
|
||||||
|
|
||||||
|
/// <summary>Kategoriyi ada gore kaydeder: yoksa ekler, varsa gunceller.</summary>
|
||||||
|
Task<McpReportCategoryDto> SaveReportCategoryAsync(McpReportCategorySaveRequestDto input);
|
||||||
|
|
||||||
|
/// <summary>Sablonu ada gore kaydeder: yoksa ekler, varsa gunceller.</summary>
|
||||||
|
Task<McpReportTemplateDto> SaveReportTemplateAsync(McpReportTemplateSaveRequestDto input);
|
||||||
|
|
||||||
|
Task DeleteReportTemplateAsync(string template);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
using Sozsoft.Mcp.Services;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.ListForms.Select;
|
||||||
|
using Volo.Abp;
|
||||||
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rapor kategorisi ve <c>DynamicReport</c> sablonu uretimi. Okuma depodan, yazma ekranlarin
|
||||||
|
/// C/U/D sozlesmesinden (<see cref="IListFormDataAppService"/>) gecer; boylece ekran yetkisi,
|
||||||
|
/// kiraci filtresi ve seed senkronizasyonu ekrandaki kayitla ayni kalir.
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
|
||||||
|
public partial class McpReportAppService(
|
||||||
|
IMcpServerConfigProvider serverConfigProvider,
|
||||||
|
IListFormDataAppService listFormDataAppService,
|
||||||
|
IMcpListFormAccessFilter accessFilter,
|
||||||
|
IRepository<ReportCategory, Guid> categoryRepository,
|
||||||
|
IRepository<ReportTemplate, Guid> templateRepository,
|
||||||
|
IRepository<ListForm, Guid> listFormRepository,
|
||||||
|
IRepository<ListFormField, Guid> listFormFieldRepository,
|
||||||
|
IMcpDevRequestTimesheetRecorder timesheetRecorder)
|
||||||
|
: McpAppService(serverConfigProvider), IMcpReportAppService
|
||||||
|
{
|
||||||
|
private const string CategoriesListFormCode = PlatformConsts.AppCodes.Reports.Categories;
|
||||||
|
private const string TemplatesListFormCode = PlatformConsts.AppCodes.Reports.ReportTemplates;
|
||||||
|
private const string ActiveStatus = "active";
|
||||||
|
private const string DefaultButtonText = "App.Platform.View";
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}")]
|
||||||
|
private static partial Regex PlaceholderRegex();
|
||||||
|
|
||||||
|
public async Task<McpReportCatalogDto> GetReportTemplatesAsync(string? search)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.ListReportTemplates);
|
||||||
|
await EnsureCanReadAsync();
|
||||||
|
|
||||||
|
var categories = await categoryRepository.GetListAsync();
|
||||||
|
var categoryNames = categories.ToDictionary(x => x.Id, x => x.Name);
|
||||||
|
var templates = (await templateRepository.GetListAsync())
|
||||||
|
.Where(x => string.IsNullOrWhiteSpace(search) ||
|
||||||
|
x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
(x.Description?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false))
|
||||||
|
.OrderBy(x => x.Name)
|
||||||
|
.Select(x => new McpReportTemplateSummaryDto
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
Name = x.Name,
|
||||||
|
Description = x.Description,
|
||||||
|
Category = categoryNames.GetValueOrDefault(x.CategoryId, string.Empty),
|
||||||
|
Status = x.Status
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new McpReportCatalogDto
|
||||||
|
{
|
||||||
|
Categories = [.. categories.OrderBy(x => x.Name).Select(ToDto)],
|
||||||
|
Templates = templates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpReportTemplateDto> GetReportTemplateAsync(string template)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.GetReportTemplate);
|
||||||
|
await EnsureCanReadAsync();
|
||||||
|
|
||||||
|
var entity = await FindTemplateAsync(template)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ReportTemplateNotFound, template]);
|
||||||
|
|
||||||
|
return await ToDtoAsync(entity, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpReportCategoryDto> SaveReportCategoryAsync(McpReportCategorySaveRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.SaveReportCategory);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input.Name))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ReportNameRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = input.Name.Trim();
|
||||||
|
var existing = await categoryRepository.FirstOrDefaultAsync(x => x.Name == name);
|
||||||
|
var values = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Name"] = name,
|
||||||
|
["Description"] = input.Description ?? existing?.Description,
|
||||||
|
["Icon"] = input.Icon ?? existing?.Icon
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = existing is null
|
||||||
|
? await InsertAsync(CategoriesListFormCode, values)
|
||||||
|
: await UpdateAsync(CategoriesListFormCode, existing.Id, values);
|
||||||
|
|
||||||
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.SaveReportCategory, name);
|
||||||
|
|
||||||
|
var saved = await categoryRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
return saved is null ? new McpReportCategoryDto { Id = id, Name = name } : ToDto(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<McpReportTemplateDto> SaveReportTemplateAsync(McpReportTemplateSaveRequestDto input)
|
||||||
|
{
|
||||||
|
Check.NotNull(input, nameof(input));
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.SaveReportTemplate);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(input.Name))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ReportNameRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = input.Name.Trim();
|
||||||
|
var existing = await templateRepository.FirstOrDefaultAsync(x => x.Name == name);
|
||||||
|
var htmlContent = string.IsNullOrWhiteSpace(input.HtmlContent) ? existing?.HtmlContent : input.HtmlContent;
|
||||||
|
if (string.IsNullOrWhiteSpace(htmlContent))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ReportHtmlRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var categoryId = string.IsNullOrWhiteSpace(input.Category)
|
||||||
|
? existing?.CategoryId
|
||||||
|
: (await FindCategoryAsync(input.Category))?.Id
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ReportCategoryNotFound, input.Category]);
|
||||||
|
if (categoryId is null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ReportCategoryRequired]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var listForm = await FindListFormAsync(input.ListFormCode);
|
||||||
|
if (listForm is not null)
|
||||||
|
{
|
||||||
|
await EnsurePlaceholdersExistAsync(listForm.ListFormCode, htmlContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["Name"] = name,
|
||||||
|
["Description"] = input.Description ?? existing?.Description,
|
||||||
|
["HtmlContent"] = htmlContent,
|
||||||
|
["CategoryId"] = categoryId,
|
||||||
|
["Status"] = input.Status ?? existing?.Status ?? ActiveStatus
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = existing is null
|
||||||
|
? await InsertAsync(TemplatesListFormCode, values)
|
||||||
|
: await UpdateAsync(TemplatesListFormCode, existing.Id, values);
|
||||||
|
|
||||||
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.SaveReportTemplate, name);
|
||||||
|
|
||||||
|
var saved = await templateRepository.FirstOrDefaultAsync(x => x.Id == id)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ReportTemplateNotFound, name]);
|
||||||
|
|
||||||
|
return await ToDtoAsync(saved, listForm, input.ButtonText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteReportTemplateAsync(string template)
|
||||||
|
{
|
||||||
|
await EnsureToolAllowedAsync(McpToolNames.DeleteReportTemplate);
|
||||||
|
|
||||||
|
var entity = await FindTemplateAsync(template)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ReportTemplateNotFound, template]);
|
||||||
|
|
||||||
|
await listFormDataAppService.PostDeleteAsync(await BuildRequestAsync(
|
||||||
|
TemplatesListFormCode, AuthorizationTypeEnum.Delete, null, entity.Id));
|
||||||
|
await timesheetRecorder.RecordAsync(McpTimesheetTexts.DeleteReportTemplate, entity.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Okuma depodan yapildigi icin ekranin okuma yetkisi burada istenir; MCP kullanicinin
|
||||||
|
/// uygulamada goremedigi sablonu gostermez.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsureCanReadAsync()
|
||||||
|
{
|
||||||
|
var permission = await accessFilter.GetPermissionNameAsync(TemplatesListFormCode, AuthorizationTypeEnum.Read);
|
||||||
|
await AuthorizationService.CheckAsync(permission ?? TemplatesListFormCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Guid> InsertAsync(string listFormCode, Dictionary<string, object?> values)
|
||||||
|
{
|
||||||
|
var key = await listFormDataAppService.PostInsertAsync(await BuildRequestAsync(
|
||||||
|
listFormCode, AuthorizationTypeEnum.Create, values, null));
|
||||||
|
|
||||||
|
// Insert ucu her veri kaynaginda anahtari dondurmez; bu durumda tekil ad uzerinden bulunur.
|
||||||
|
if (Guid.TryParse(((object?)key)?.ToString(), out var id))
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = (string)values["Name"]!;
|
||||||
|
return listFormCode == CategoriesListFormCode
|
||||||
|
? (await categoryRepository.FirstAsync(x => x.Name == name)).Id
|
||||||
|
: (await templateRepository.FirstAsync(x => x.Name == name)).Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Guid> UpdateAsync(string listFormCode, Guid id, Dictionary<string, object?> values)
|
||||||
|
{
|
||||||
|
await listFormDataAppService.PostUpdateAsync(await BuildRequestAsync(
|
||||||
|
listFormCode, AuthorizationTypeEnum.Update, values, id));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ekranin kendi kontrolu reddi adsiz bir hatayla bildirir; yetki burada adiyla istenir ki
|
||||||
|
/// istemci hangi yetkinin eksik oldugunu gorsun.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<DataRequestDto> BuildRequestAsync(
|
||||||
|
string listFormCode,
|
||||||
|
AuthorizationTypeEnum operation,
|
||||||
|
Dictionary<string, object?>? values,
|
||||||
|
Guid? key)
|
||||||
|
{
|
||||||
|
var permission = await accessFilter.GetPermissionNameAsync(listFormCode, operation);
|
||||||
|
if (permission is not null)
|
||||||
|
{
|
||||||
|
await AuthorizationService.CheckAsync(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataRequestDto
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Data = JsonSerializer.Serialize(values ?? []),
|
||||||
|
Keys = key is null ? [] : [key.Value]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ReportTemplate?> FindTemplateAsync(string template)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = template.Trim();
|
||||||
|
return Guid.TryParse(value, out var id)
|
||||||
|
? await templateRepository.FirstOrDefaultAsync(x => x.Id == id)
|
||||||
|
: await templateRepository.FirstOrDefaultAsync(x => x.Name == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ReportCategory?> FindCategoryAsync(string category)
|
||||||
|
{
|
||||||
|
var value = category.Trim();
|
||||||
|
return Guid.TryParse(value, out var id)
|
||||||
|
? await categoryRepository.FirstOrDefaultAsync(x => x.Id == id)
|
||||||
|
: await categoryRepository.FirstOrDefaultAsync(x => x.Name == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ListForm?> FindListFormAsync(string? listFormCode)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(listFormCode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = listFormCode.Trim();
|
||||||
|
return await listFormRepository.FirstOrDefaultAsync(x => x.ListFormCode == code)
|
||||||
|
?? throw new UserFriendlyException(L[McpErrorCodes.ReportListFormNotFound, code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bilinmeyen yer tutucu raporda oldugu gibi basilir; sertifikada "{{AdSoyad}}" gorunmesin
|
||||||
|
/// diye kayitta reddedilir.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsurePlaceholdersExistAsync(string listFormCode, string htmlContent)
|
||||||
|
{
|
||||||
|
var fieldNames = (await listFormFieldRepository.GetListAsync(x => x.ListFormCode == listFormCode))
|
||||||
|
.Select(x => x.FieldName)
|
||||||
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var unknown = GetPlaceholders(htmlContent).Where(x => !fieldNames.Contains(x)).ToList();
|
||||||
|
if (unknown.Count > 0)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException(L[McpErrorCodes.ReportPlaceholdersUnknown, listFormCode, string.Join(", ", unknown)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> GetPlaceholders(string htmlContent)
|
||||||
|
=> [.. PlaceholderRegex().Matches(htmlContent ?? string.Empty)
|
||||||
|
.Select(x => x.Groups[1].Value)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)];
|
||||||
|
|
||||||
|
private async Task<McpReportTemplateDto> ToDtoAsync(ReportTemplate entity, ListForm? listForm, string? buttonText)
|
||||||
|
{
|
||||||
|
var category = await categoryRepository.FirstOrDefaultAsync(x => x.Id == entity.CategoryId);
|
||||||
|
var viewUrl = $"/admin/reports/DynamicReport/view/{entity.Id}";
|
||||||
|
|
||||||
|
return new McpReportTemplateDto
|
||||||
|
{
|
||||||
|
Id = entity.Id,
|
||||||
|
Name = entity.Name,
|
||||||
|
Description = entity.Description,
|
||||||
|
Category = category?.Name ?? string.Empty,
|
||||||
|
CategoryId = entity.CategoryId,
|
||||||
|
Status = entity.Status,
|
||||||
|
HtmlContent = entity.HtmlContent,
|
||||||
|
Placeholders = GetPlaceholders(entity.HtmlContent),
|
||||||
|
ViewUrl = viewUrl,
|
||||||
|
RowButton = listForm is null ? null : new McpReportRowButtonDto
|
||||||
|
{
|
||||||
|
Text = string.IsNullOrWhiteSpace(buttonText) ? DefaultButtonText : buttonText,
|
||||||
|
Hint = string.IsNullOrWhiteSpace(buttonText) ? DefaultButtonText : buttonText,
|
||||||
|
Url = $"{viewUrl}/{listForm.ListFormCode}?recordId=@{listForm.KeyFieldName}",
|
||||||
|
AuthName = listForm.ListFormCode
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static McpReportCategoryDto ToDto(ReportCategory category) => new()
|
||||||
|
{
|
||||||
|
Id = category.Id,
|
||||||
|
Name = category.Name,
|
||||||
|
Description = category.Description,
|
||||||
|
Icon = category.Icon
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -59,6 +59,13 @@ public static class McpErrorCodes
|
||||||
public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired";
|
public const string DevRequestSubTasksRequired = Prefix + "DevRequestSubTasksRequired";
|
||||||
public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid";
|
public const string DevRequestSubTasksInvalid = Prefix + "DevRequestSubTasksInvalid";
|
||||||
public const string VisualDesignerComponent = Prefix + "VisualDesignerComponent";
|
public const string VisualDesignerComponent = Prefix + "VisualDesignerComponent";
|
||||||
|
public const string ReportNameRequired = Prefix + "ReportNameRequired";
|
||||||
|
public const string ReportHtmlRequired = Prefix + "ReportHtmlRequired";
|
||||||
|
public const string ReportCategoryRequired = Prefix + "ReportCategoryRequired";
|
||||||
|
public const string ReportCategoryNotFound = Prefix + "ReportCategoryNotFound";
|
||||||
|
public const string ReportTemplateNotFound = Prefix + "ReportTemplateNotFound";
|
||||||
|
public const string ReportListFormNotFound = Prefix + "ReportListFormNotFound";
|
||||||
|
public const string ReportPlaceholdersUnknown = Prefix + "ReportPlaceholdersUnknown";
|
||||||
public const string TableColumnsRequired = Prefix + "TableColumnsRequired";
|
public const string TableColumnsRequired = Prefix + "TableColumnsRequired";
|
||||||
public const string UnsupportedColumnType = Prefix + "UnsupportedColumnType";
|
public const string UnsupportedColumnType = Prefix + "UnsupportedColumnType";
|
||||||
public const string InvalidColumnLength = Prefix + "InvalidColumnLength";
|
public const string InvalidColumnLength = Prefix + "InvalidColumnLength";
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ public static class McpTimesheetTexts
|
||||||
public const string DeleteWizard = Prefix + "DeleteWizard";
|
public const string DeleteWizard = Prefix + "DeleteWizard";
|
||||||
public const string SaveComponent = Prefix + "SaveComponent";
|
public const string SaveComponent = Prefix + "SaveComponent";
|
||||||
public const string DeleteComponent = Prefix + "DeleteComponent";
|
public const string DeleteComponent = Prefix + "DeleteComponent";
|
||||||
|
public const string SaveReportCategory = Prefix + "SaveReportCategory";
|
||||||
|
public const string SaveReportTemplate = Prefix + "SaveReportTemplate";
|
||||||
|
public const string DeleteReportTemplate = Prefix + "DeleteReportTemplate";
|
||||||
public const string InsertRow = Prefix + "InsertRow";
|
public const string InsertRow = Prefix + "InsertRow";
|
||||||
public const string UpdateRow = Prefix + "UpdateRow";
|
public const string UpdateRow = Prefix + "UpdateRow";
|
||||||
public const string DeleteRow = Prefix + "DeleteRow";
|
public const string DeleteRow = Prefix + "DeleteRow";
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,14 @@ public static class McpToolNames
|
||||||
public const string GetComponent = "get_component";
|
public const string GetComponent = "get_component";
|
||||||
public const string SaveComponent = "save_component";
|
public const string SaveComponent = "save_component";
|
||||||
public const string DeleteComponent = "delete_component";
|
public const string DeleteComponent = "delete_component";
|
||||||
|
public const string ListReportTemplates = "list_report_templates";
|
||||||
|
public const string GetReportTemplate = "get_report_template";
|
||||||
|
public const string SaveReportCategory = "save_report_category";
|
||||||
|
public const string SaveReportTemplate = "save_report_template";
|
||||||
|
public const string DeleteReportTemplate = "delete_report_template";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ekran ve bilesen gelistirme araclari; kiraci baglaminda hicbiri acilmaz. Okuyanlar
|
/// Ekran, bilesen ve rapor sablonu gelistirme araclari; kiraci baglaminda hicbiri acilmaz. Okuyanlar
|
||||||
/// <c>McpServers.Invoke</c> ile, degistirenler <c>McpServers.Write</c> ile acilir
|
/// <c>McpServers.Invoke</c> ile, degistirenler <c>McpServers.Write</c> ile acilir
|
||||||
/// (<see cref="RequiredPermissions"/>).
|
/// (<see cref="RequiredPermissions"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -61,7 +66,12 @@ public static class McpToolNames
|
||||||
ListComponents,
|
ListComponents,
|
||||||
GetComponent,
|
GetComponent,
|
||||||
SaveComponent,
|
SaveComponent,
|
||||||
DeleteComponent
|
DeleteComponent,
|
||||||
|
ListReportTemplates,
|
||||||
|
GetReportTemplate,
|
||||||
|
SaveReportCategory,
|
||||||
|
SaveReportTemplate,
|
||||||
|
DeleteReportTemplate
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -113,6 +123,9 @@ public static class McpToolNames
|
||||||
[AddDevRequestSubTasks] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[AddDevRequestSubTasks] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
[SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[SaveComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
[DeleteComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
[DeleteComponent] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[SaveReportCategory] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[SaveReportTemplate] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
|
[DeleteReportTemplate] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public class McpHttpApiModule : AbpModule
|
||||||
.WithTools<McpQueryTools>()
|
.WithTools<McpQueryTools>()
|
||||||
.WithTools<McpDataTools>()
|
.WithTools<McpDataTools>()
|
||||||
.WithTools<McpDevelopTools>()
|
.WithTools<McpDevelopTools>()
|
||||||
|
.WithTools<McpReportTools>()
|
||||||
.WithPrompts(typeof(McpDevelopPrompts))
|
.WithPrompts(typeof(McpDevelopPrompts))
|
||||||
.WithRequestFilters(filters =>
|
.WithRequestFilters(filters =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Sozsoft.Mcp.Dtos;
|
||||||
|
|
||||||
|
namespace Sozsoft.Mcp.Tools;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rapor kategorisi ve DynamicReport sablonu araclari. Sablon HTML'dir ve
|
||||||
|
/// <c>/admin/reports/DynamicReport/view/{Id}</c> ile basilir; bir ekran kaydiyla acildiginda
|
||||||
|
/// <c>{{AlanAdi}}</c> yer tutuculari o kaydin alanlariyla doldurulur.
|
||||||
|
/// </summary>
|
||||||
|
[McpServerToolType]
|
||||||
|
public sealed class McpReportTools(IMcpReportAppService reportAppService)
|
||||||
|
{
|
||||||
|
[McpServerTool(Name = McpToolNames.ListReportTemplates, ReadOnly = true)]
|
||||||
|
[Description("Rapor kategorilerini ve rapor sablonlarini listeler; HTML dondurmez. Sablon kaydederken kategori adi buradan secilir.")]
|
||||||
|
public async Task<McpReportCatalogDto> ListReportTemplatesAsync(
|
||||||
|
[Description("Sablon adi ya da aciklamasi icinde aranacak metin. Bos birakilabilir.")]
|
||||||
|
string? search = null)
|
||||||
|
=> await reportAppService.GetReportTemplatesAsync(search);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.GetReportTemplate, ReadOnly = true)]
|
||||||
|
[Description("Bir rapor sablonunun HTML icerigini, yer tutucularini ve goruntuleme adresini dondurur. Degistirmeden once oku.")]
|
||||||
|
public async Task<McpReportTemplateDto> GetReportTemplateAsync(
|
||||||
|
[Description("Sablonun Id'si ya da adi.")]
|
||||||
|
string template)
|
||||||
|
=> await reportAppService.GetReportTemplateAsync(template);
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.SaveReportCategory, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("Rapor kategorisini ada gore kaydeder: yoksa ekler, varsa gunceller. Kayit bir ekran kaydindan basilan HTML sablonu ise var olan 'Raporlar' kategorisini kullan; yeni kategori yalnizca istenince acilir.")]
|
||||||
|
public async Task<McpReportCategoryDto> SaveReportCategoryAsync(
|
||||||
|
[Description("Kategorinin adi; kimligidir.")]
|
||||||
|
string name,
|
||||||
|
[Description("Aciklama.")]
|
||||||
|
string? description = null,
|
||||||
|
[Description("Ikon adi.")]
|
||||||
|
string? icon = null)
|
||||||
|
=> await reportAppService.SaveReportCategoryAsync(new McpReportCategorySaveRequestDto
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Description = description,
|
||||||
|
Icon = icon
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.SaveReportTemplate, Destructive = true, Idempotent = true)]
|
||||||
|
[Description("DynamicReport sablonunu ada gore kaydeder: yoksa ekler, varsa gunceller; guncellemede verilmeyen alanlar korunur. Sablon A4'e basilan HTML'dir (Standard Taahhutname gibi). listFormCode verilirse HTML icindeki {{AlanAdi}} yer tutuculari o ekranin alanlarina gore dogrulanir ve cevapta rowButton doner: bu butonu ekranin CommandColumnJson listesine ekle (deploy_wizard), VisibleExpression ile kosullandir. Sozlesme icin get_dev_guide 9.12.")]
|
||||||
|
public async Task<McpReportTemplateDto> SaveReportTemplateAsync(
|
||||||
|
[Description("Sablonun adi; kimligidir.")]
|
||||||
|
string name,
|
||||||
|
[Description("Kategori adi ya da Id'si. Yeni sablonda zorunlu; kayit HTML'i icin 'Raporlar'.")]
|
||||||
|
string? category = null,
|
||||||
|
[Description("Basilacak HTML. Kayit alanlari {{AlanAdi}} ile, rapor tarihi @@date ile yazilir. Yeni sablonda zorunlu, guncellemede bos birakilirsa mevcut icerik korunur.")]
|
||||||
|
string? htmlContent = null,
|
||||||
|
[Description("Aciklama.")]
|
||||||
|
string? description = null,
|
||||||
|
[Description("'active' ya da 'passive'. Yeni sablonda varsayilan 'active'.")]
|
||||||
|
string? status = null,
|
||||||
|
[Description("Sablonun basilacagi ekranin kodu (ornegin katilimci listesi). Verilirse yer tutucular dogrulanir ve satir butonu doner.")]
|
||||||
|
string? listFormCode = null,
|
||||||
|
[Description("Satir butonunun dil anahtari. Verilmezse App.Platform.View.")]
|
||||||
|
string? buttonText = null)
|
||||||
|
=> await reportAppService.SaveReportTemplateAsync(new McpReportTemplateSaveRequestDto
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Category = category,
|
||||||
|
HtmlContent = htmlContent,
|
||||||
|
Description = description,
|
||||||
|
Status = status,
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
ButtonText = buttonText
|
||||||
|
});
|
||||||
|
|
||||||
|
[McpServerTool(Name = McpToolNames.DeleteReportTemplate, Destructive = true, Idempotent = false)]
|
||||||
|
[Description("Rapor sablonunu siler. Sablonu acan satir butonlari ekranlardan ayrica kaldirilir.")]
|
||||||
|
public async Task DeleteReportTemplateAsync(
|
||||||
|
[Description("Sablonun Id'si ya da adi.")]
|
||||||
|
string template)
|
||||||
|
=> await reportAppService.DeleteReportTemplateAsync(template);
|
||||||
|
}
|
||||||
|
|
@ -160,6 +160,9 @@ public class ListFormWizardDto
|
||||||
public List<SubFormDto> SubForms { get; set; } = new();
|
public List<SubFormDto> SubForms { get; set; } = new();
|
||||||
public List<WidgetEditDto> Widgets { get; set; } = new();
|
public List<WidgetEditDto> Widgets { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>Toolbar ve satir butonlari (<c>CommandColumnJson</c>); rapor acan satir butonu da buradadir.</summary>
|
||||||
|
public List<CommandColumnDto> CommandColumns { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>Wizard'in menu/permission kodu; ad ve kod bos gelebildigi icin tek kuraldan cozulur.</summary>
|
/// <summary>Wizard'in menu/permission kodu; ad ve kod bos gelebildigi icin tek kuraldan cozulur.</summary>
|
||||||
public string ResolveMenuCode()
|
public string ResolveMenuCode()
|
||||||
{
|
{
|
||||||
|
|
@ -219,6 +222,7 @@ public class ListFormWizardDto
|
||||||
var groups = Groups ?? [];
|
var groups = Groups ?? [];
|
||||||
var subForms = SubForms ?? [];
|
var subForms = SubForms ?? [];
|
||||||
var widgets = Widgets ?? [];
|
var widgets = Widgets ?? [];
|
||||||
|
var commandColumns = CommandColumns ?? [];
|
||||||
|
|
||||||
var editingFormDtos = groups
|
var editingFormDtos = groups
|
||||||
.Select((g, gi) => new { Group = g, Order = gi + 1 })
|
.Select((g, gi) => new { Group = g, Order = gi + 1 })
|
||||||
|
|
@ -302,6 +306,7 @@ public class ListFormWizardDto
|
||||||
dto.EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null;
|
dto.EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null;
|
||||||
dto.SubFormsJson = subForms.Count > 0 ? JsonSerializer.Serialize(subForms) : null;
|
dto.SubFormsJson = subForms.Count > 0 ? JsonSerializer.Serialize(subForms) : null;
|
||||||
dto.WidgetsJson = widgets.Count > 0 ? JsonSerializer.Serialize(widgets) : null;
|
dto.WidgetsJson = widgets.Count > 0 ? JsonSerializer.Serialize(widgets) : null;
|
||||||
|
dto.CommandColumnJson = commandColumns.Count > 0 ? JsonSerializer.Serialize(commandColumns) : null;
|
||||||
dto.WorkflowJson = HasWorkflow(workflow) ? JsonSerializer.Serialize(workflow) : null;
|
dto.WorkflowJson = HasWorkflow(workflow) ? JsonSerializer.Serialize(workflow) : null;
|
||||||
dto.TreeOptionJson = UsesLayout("tree", Tree) &&
|
dto.TreeOptionJson = UsesLayout("tree", Tree) &&
|
||||||
!string.IsNullOrEmpty(TreeOptionDto?.ParentIdExpr)
|
!string.IsNullOrEmpty(TreeOptionDto?.ParentIdExpr)
|
||||||
|
|
@ -428,6 +433,7 @@ public class ListFormWizardDto
|
||||||
WorkflowDto = listForm.WorkflowDto;
|
WorkflowDto = listForm.WorkflowDto;
|
||||||
SubForms = [.. listForm.SubFormsDto];
|
SubForms = [.. listForm.SubFormsDto];
|
||||||
Widgets = [.. listForm.WidgetsDto];
|
Widgets = [.. listForm.WidgetsDto];
|
||||||
|
CommandColumns = [.. listForm.CommandColumnDto];
|
||||||
Groups = BuildGroups(listForm, fields);
|
Groups = BuildGroups(listForm, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ public class WizardSeedFileDto
|
||||||
nameof(ListFormWizardDto.TreeOptionDto), nameof(ListFormWizardDto.GanttOptionDto),
|
nameof(ListFormWizardDto.TreeOptionDto), nameof(ListFormWizardDto.GanttOptionDto),
|
||||||
nameof(ListFormWizardDto.SchedulerOptionDto), nameof(ListFormWizardDto.TodoOptionDto),
|
nameof(ListFormWizardDto.SchedulerOptionDto), nameof(ListFormWizardDto.TodoOptionDto),
|
||||||
nameof(ListFormWizardDto.WorkflowDto), nameof(ListFormWizardDto.SubForms), nameof(ListFormWizardDto.Widgets),
|
nameof(ListFormWizardDto.WorkflowDto), nameof(ListFormWizardDto.SubForms), nameof(ListFormWizardDto.Widgets),
|
||||||
|
nameof(ListFormWizardDto.CommandColumns),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>Grup ve alan tanimlarinin <see cref="Fields"/> bolumunde karsiligi olan anahtarlari.</summary>
|
/// <summary>Grup ve alan tanimlarinin <see cref="Fields"/> bolumunde karsiligi olan anahtarlari.</summary>
|
||||||
|
|
|
||||||
|
|
@ -4120,9 +4120,9 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportCategory)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportCategory)),
|
||||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
SeedSyncInsert = false,
|
SeedSyncInsert = true,
|
||||||
SeedSyncUpdate = false,
|
SeedSyncUpdate = true,
|
||||||
SeedSyncDelete = false,
|
SeedSyncDelete = true,
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
DefaultFilter = DefaultFilterJson,
|
DefaultFilter = DefaultFilterJson,
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
|
|
@ -4258,9 +4258,9 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.ReportTemplate)),
|
||||||
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
SeedSyncInsert = false,
|
SeedSyncInsert = true,
|
||||||
SeedSyncUpdate = false,
|
SeedSyncUpdate = true,
|
||||||
SeedSyncDelete = false,
|
SeedSyncDelete = true,
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
DefaultFilter = DefaultFilterJson,
|
DefaultFilter = DefaultFilterJson,
|
||||||
|
|
|
||||||
|
|
@ -70,18 +70,72 @@ public class DynamicFormReport : XtraReport
|
||||||
}
|
}
|
||||||
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
using var scope = _scopeFactory.CreateScope();
|
||||||
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
|
|
||||||
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
|
|
||||||
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();
|
|
||||||
var customizationManager = scope.ServiceProvider.GetRequiredService<IListFormCustomizationManager>();
|
|
||||||
var dynamicDataManager = scope.ServiceProvider.GetRequiredService<IDynamicDataManager>();
|
|
||||||
var selectQueryManager = scope.ServiceProvider.GetRequiredService<ISelectQueryManager>();
|
|
||||||
var defaultValueHelper = scope.ServiceProvider.GetRequiredService<DefaultValueHelper>();
|
|
||||||
var localizer = scope.ServiceProvider.GetRequiredService<IStringLocalizer<PlatformResource>>();
|
var localizer = scope.ServiceProvider.GetRequiredService<IStringLocalizer<PlatformResource>>();
|
||||||
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
|
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||||
var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
|
var currentUser = scope.ServiceProvider.GetRequiredService<ICurrentUser>();
|
||||||
var isSubReport = DynamicReportImageHelper.IsSubReport(parameters);
|
var isSubReport = DynamicReportImageHelper.IsSubReport(parameters);
|
||||||
|
|
||||||
|
var (listForm, reportFields, selectQuery, row, connectionString, dataSourceType) = await QueryRecordAsync(
|
||||||
|
scope.ServiceProvider,
|
||||||
|
listFormCode,
|
||||||
|
id,
|
||||||
|
DynamicReportImageHelper.GetParameter(parameters, "filter"));
|
||||||
|
|
||||||
|
var formGroups = GetFormGroups(listForm, reportFields).ToList();
|
||||||
|
var listSubForms = GetListSubForms(listForm, row).ToList();
|
||||||
|
var subReports = await CreateSubReportsAsync(listSubForms);
|
||||||
|
var requiresLandscape = HasWideFormLayout(formGroups) ||
|
||||||
|
subReports.Any(x => x.Report.RequiresLandscapeLayout);
|
||||||
|
|
||||||
|
ConfigurePage(requiresLandscape);
|
||||||
|
ConfigureDataSource(selectQuery, connectionString, dataSourceType);
|
||||||
|
BuildLayout(listForm, formGroups, row, subReports, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport);
|
||||||
|
RequestParameters = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kaydin ekranda okunabilen alanlarini, form raporunun bastigi metinle doner (lookup gorunen
|
||||||
|
/// metni, statik lookup karsiligi, tarih/sayi bicimi). Anahtar alan adidir. Ekran okuma yetkisi
|
||||||
|
/// ve kiraci filtresi form raporundaki yoldan aynen uygulanir; kayit yoksa <c>null</c> doner.
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<IReadOnlyDictionary<string, string>> GetRecordTextsAsync(string listFormCode, string id)
|
||||||
|
{
|
||||||
|
if (_scopeFactory == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DynamicFormReport service provider is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var (_, reportFields, _, row, _, _) = await QueryRecordAsync(scope.ServiceProvider, listFormCode, id, null);
|
||||||
|
if (row == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var texts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var field in reportFields)
|
||||||
|
{
|
||||||
|
texts[field.Field.FieldName] = TryGetFormattedValue(field, row, out var text) ? text : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return texts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<(ListForm ListForm, List<FormReportField> ReportFields, string SelectQuery, IDictionary<string, object> Row, string ConnectionString, DataSourceTypeEnum DataSourceType)> QueryRecordAsync(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
string listFormCode,
|
||||||
|
string id,
|
||||||
|
string filter)
|
||||||
|
{
|
||||||
|
var authManager = serviceProvider.GetRequiredService<IListFormAuthorizationManager>();
|
||||||
|
var listFormManager = serviceProvider.GetRequiredService<IListFormManager>();
|
||||||
|
var listFormFieldManager = serviceProvider.GetRequiredService<IListFormFieldManager>();
|
||||||
|
var customizationManager = serviceProvider.GetRequiredService<IListFormCustomizationManager>();
|
||||||
|
var dynamicDataManager = serviceProvider.GetRequiredService<IDynamicDataManager>();
|
||||||
|
var selectQueryManager = serviceProvider.GetRequiredService<ISelectQueryManager>();
|
||||||
|
var defaultValueHelper = serviceProvider.GetRequiredService<DefaultValueHelper>();
|
||||||
|
var localizer = serviceProvider.GetRequiredService<IStringLocalizer<PlatformResource>>();
|
||||||
|
|
||||||
if (!await authManager.CanAccess(listFormCode))
|
if (!await authManager.CanAccess(listFormCode))
|
||||||
{
|
{
|
||||||
throw new UnauthorizedAccessException($"No read permission for list form '{listFormCode}'.");
|
throw new UnauthorizedAccessException($"No read permission for list form '{listFormCode}'.");
|
||||||
|
|
@ -100,7 +154,7 @@ public class DynamicFormReport : XtraReport
|
||||||
var queryParams = new QueryParameters
|
var queryParams = new QueryParameters
|
||||||
{
|
{
|
||||||
ListFormCode = listFormCode,
|
ListFormCode = listFormCode,
|
||||||
Filter = DynamicReportImageHelper.GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id),
|
Filter = filter ?? CreateKeyFilter(listForm.KeyFieldName, id),
|
||||||
Take = 1,
|
Take = 1,
|
||||||
Skip = 0
|
Skip = 0
|
||||||
};
|
};
|
||||||
|
|
@ -119,17 +173,7 @@ public class DynamicFormReport : XtraReport
|
||||||
dataSourceType);
|
dataSourceType);
|
||||||
|
|
||||||
var row = GetFirstRow(await dynamicDataRepository.QueryAsync(selectQuery, connectionString));
|
var row = GetFirstRow(await dynamicDataRepository.QueryAsync(selectQuery, connectionString));
|
||||||
|
return (listForm, reportFields, selectQuery, row, connectionString, dataSourceType);
|
||||||
var formGroups = GetFormGroups(listForm, reportFields).ToList();
|
|
||||||
var listSubForms = GetListSubForms(listForm, row).ToList();
|
|
||||||
var subReports = await CreateSubReportsAsync(listSubForms);
|
|
||||||
var requiresLandscape = HasWideFormLayout(formGroups) ||
|
|
||||||
subReports.Any(x => x.Report.RequiresLandscapeLayout);
|
|
||||||
|
|
||||||
ConfigurePage(requiresLandscape);
|
|
||||||
ConfigureDataSource(selectQuery, connectionString, dataSourceType);
|
|
||||||
BuildLayout(listForm, formGroups, row, subReports, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport);
|
|
||||||
RequestParameters = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigurePage(bool landscape)
|
private void ConfigurePage(bool landscape)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ using DevExpress.DataAccess.Sql;
|
||||||
using DevExpress.XtraPrinting;
|
using DevExpress.XtraPrinting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Sozsoft.Platform.Entities;
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Users;
|
using Volo.Abp.Users;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ReportServices;
|
namespace Sozsoft.Platform.ReportServices;
|
||||||
|
|
@ -21,6 +23,9 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
{
|
{
|
||||||
readonly string reportDirectory = "PredefinedReports\\Reports";
|
readonly string reportDirectory = "PredefinedReports\\Reports";
|
||||||
const string FileExtension = ".repx";
|
const string FileExtension = ".repx";
|
||||||
|
const string RecordIdParameter = "recordId";
|
||||||
|
private static readonly System.Text.RegularExpressions.Regex RecordPlaceholderRegex =
|
||||||
|
new(@"\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}", System.Text.RegularExpressions.RegexOptions.Compiled);
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
public CustomReportStorageWebExtension(IServiceScopeFactory scopeFactory)
|
public CustomReportStorageWebExtension(IServiceScopeFactory scopeFactory)
|
||||||
|
|
@ -243,13 +248,14 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
}
|
}
|
||||||
|
|
||||||
using var scope = _scopeFactory.CreateScope();
|
using var scope = _scopeFactory.CreateScope();
|
||||||
var repository = scope.ServiceProvider.GetRequiredService<IRepository<ReportTemplate, Guid>>();
|
var template = await FindReportTemplateAsync(scope.ServiceProvider, reportTemplateId);
|
||||||
var template = await repository.FindAsync(reportTemplateId);
|
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Report template '{reportTemplateId}' was not found.");
|
throw new InvalidOperationException($"Report template '{reportTemplateId}' was not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var htmlContent = await MergeRecordAsync(template.HtmlContent, parameters);
|
||||||
|
|
||||||
// A stored DynamicReport layout can contain a named SqlDataSource. The cached document
|
// A stored DynamicReport layout can contain a named SqlDataSource. The cached document
|
||||||
// builder executes that source later on a background thread, where its connection can be
|
// builder executes that source later on a background thread, where its connection can be
|
||||||
// stale or no longer carry the request's tenant context. Embed the tenant-scoped result in
|
// stale or no longer carry the request's tenant context. Embed the tenant-scoped result in
|
||||||
|
|
@ -270,7 +276,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
Name = "DynamicReportData",
|
Name = "DynamicReportData",
|
||||||
JsonSource = new CustomJsonSource(JsonSerializer.Serialize(new[]
|
JsonSource = new CustomJsonSource(JsonSerializer.Serialize(new[]
|
||||||
{
|
{
|
||||||
new { template.HtmlContent }
|
new { HtmlContent = htmlContent }
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
dataSource.Fill();
|
dataSource.Fill();
|
||||||
|
|
@ -280,6 +286,50 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
report.DataMember = null;
|
report.DataMember = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kiracinin kendi sablonu once aranir; yoksa host sablonu kullanilir. Host sablonlari urunle
|
||||||
|
/// gelen ortak tasarimlardir (seed) ve yalnizca HTML tasir; basilan veri her zaman kiracinin
|
||||||
|
/// kendi ekranindan okunur.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<ReportTemplate> FindReportTemplateAsync(IServiceProvider serviceProvider, Guid reportTemplateId)
|
||||||
|
{
|
||||||
|
var repository = serviceProvider.GetRequiredService<IRepository<ReportTemplate, Guid>>();
|
||||||
|
var template = await repository.FindAsync(reportTemplateId);
|
||||||
|
if (template != null)
|
||||||
|
{
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataFilter = serviceProvider.GetRequiredService<IDataFilter>();
|
||||||
|
using (dataFilter.Disable<IMultiTenant>())
|
||||||
|
{
|
||||||
|
return await repository.FindAsync(x => x.Id == reportTemplateId && x.TenantId == null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sablon bir ekran kaydiyla acildiysa (<c>DynamicReport/view/{sablon}/{listFormCode}?recordId=</c>)
|
||||||
|
/// <c>{{AlanAdi}}</c> yer tutuculari kaydin form raporundaki metniyle doldurulur. Degerler HTML
|
||||||
|
/// olarak kodlanir; kayit verisi sablonun isaretlemesine karisamaz.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<string> MergeRecordAsync(string htmlContent, IReadOnlyDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(htmlContent) ||
|
||||||
|
!parameters.TryGetValue("listFormCode", out var listFormCode) || string.IsNullOrWhiteSpace(listFormCode) ||
|
||||||
|
!parameters.TryGetValue(RecordIdParameter, out var recordId) || string.IsNullOrWhiteSpace(recordId))
|
||||||
|
{
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
var texts = await Sozsoft.Reports.PredefinedReports.DynamicFormReport.GetRecordTextsAsync(listFormCode, recordId)
|
||||||
|
?? throw new InvalidOperationException($"Record '{recordId}' was not found in '{listFormCode}'.");
|
||||||
|
|
||||||
|
return RecordPlaceholderRegex.Replace(htmlContent, match =>
|
||||||
|
texts.TryGetValue(match.Groups[1].Value, out var text)
|
||||||
|
? System.Net.WebUtility.HtmlEncode(text)
|
||||||
|
: match.Value);
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyDynamicReportFooter(XtraReport report)
|
private void ApplyDynamicReportFooter(XtraReport report)
|
||||||
{
|
{
|
||||||
var bottomMargin = report.Bands.OfType<BottomMarginBand>().FirstOrDefault();
|
var bottomMargin = report.Bands.OfType<BottomMarginBand>().FirstOrDefault();
|
||||||
|
|
@ -425,6 +475,17 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsDynamicReport(reportName))
|
||||||
|
{
|
||||||
|
parameters.TryAdd("id", pathParameters[0]);
|
||||||
|
if (pathParameters.Length > 1)
|
||||||
|
{
|
||||||
|
parameters.TryAdd("listFormCode", pathParameters[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsDynamicGridReport(reportName) || IsDynamicTreeReport(reportName) || IsDynamicFormReport(reportName))
|
if (IsDynamicGridReport(reportName) || IsDynamicTreeReport(reportName) || IsDynamicFormReport(reportName))
|
||||||
{
|
{
|
||||||
if (pathParameters.Length == 1)
|
if (pathParameters.Length == 1)
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
taşır. `ListForm`/`Fields` içinde karşılığı olan her alan (veri kaynağı, select, anahtar alan,
|
taşır. `ListForm`/`Fields` içinde karşılığı olan her alan (veri kaynağı, select, anahtar alan,
|
||||||
kırılım/düzen/düzenleme bayrakları, görünüm option blokları, alt form, widget, iş akışı, alan
|
kırılım/düzen/düzenleme bayrakları, görünüm option blokları, alt form, widget, iş akışı, alan
|
||||||
tanımları) dosyaya yazılmadan çıkarılır (`WizardSeedFileDto.ToSeedJson`) ve okurken geri
|
tanımları) dosyaya yazılmadan çıkarılır (`WizardSeedFileDto.ToSeedJson`) ve okurken geri
|
||||||
doldurulur (`FromSeedJson`). Wizard'a yeni bir alan eklerken karşılığı ListForm'da varsa listeye ekle.
|
doldurulur (`FromSeedJson`). Wizard'a yeni bir alan eklerken karşılığı ListForm'da varsa listeye ekle (`CommandColumns` →
|
||||||
|
`CommandColumnJson` bu yolla taşınır; wizard ekranında adımı yoktur, UI modeli düzenlemede korur).
|
||||||
Dosyada kalan wizard alanları üç başlık altında toplanır — `Menu`, `Language`, `Groups` — ve
|
Dosyada kalan wizard alanları üç başlık altında toplanır — `Menu`, `Language`, `Groups` — ve
|
||||||
`ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`, `LanguageTexts` → `Language.Texts`
|
`ListFormCode` → `WizardCode`, `MenuParentModuleId` → `ModuleId`, `LanguageTexts` → `Language.Texts`
|
||||||
adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş `DataSourceConnectionString`, boş
|
adıyla yazılır; varsayılanı olan alanlar (`CreateMenu: true`, boş `DataSourceConnectionString`, boş
|
||||||
|
|
@ -279,7 +280,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
||||||
sunucu doldurur, modele bırakılmaz. `Timesheets`'i tamamen sunucu yazar
|
sunucu doldurur, modele bırakılmaz. `Timesheets`'i tamamen sunucu yazar
|
||||||
(`IMcpDevRequestTimesheetRecorder`): `get_dev_request`/`start_dev_request` talebi etkin talep yapar,
|
(`IMcpDevRequestTimesheetRecorder`): `get_dev_request`/`start_dev_request` talebi etkin talep yapar,
|
||||||
sonraki her üretim/veri yazma işlemi (`create_table`, `deploy_wizard`, `delete_wizard`,
|
sonraki her üretim/veri yazma işlemi (`create_table`, `deploy_wizard`, `delete_wizard`,
|
||||||
`save_component`, `delete_component`, satır araçları, `save_dev_request_analysis`,
|
`save_component`, `delete_component`, `save_report_category`, `save_report_template`,
|
||||||
|
`delete_report_template`, satır araçları, `save_dev_request_analysis`,
|
||||||
`append_dev_request_note`, `add_dev_request_subtasks`) ona `App.Mcp.Timesheet.*` anahtarından Türkçe
|
`append_dev_request_note`, `add_dev_request_subtasks`) ona `App.Mcp.Timesheet.*` anahtarından Türkçe
|
||||||
açıklama ve ölçülen süreyle bir satır ekler; yalnızca talebi açan `start_dev_request` satır yazmaz. Yeni bir üretim/veri yazma aracı eklenirse kaydedici çağrısı ve dil anahtarı da eklenir.
|
açıklama ve ölçülen süreyle bir satır ekler; yalnızca talebi açan `start_dev_request` satır yazmaz. Yeni bir üretim/veri yazma aracı eklenirse kaydedici çağrısı ve dil anahtarı da eklenir.
|
||||||
Satır aracın UoW'si commit edildikten sonra ayrı UoW'de yazılır; aynı UoW'de yazmak Dapper
|
Satır aracın UoW'si commit edildikten sonra ayrı UoW'de yazılır; aynı UoW'de yazmak Dapper
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -67951,14 +67951,14 @@
|
||||||
"CultureName": "en",
|
"CultureName": "en",
|
||||||
"ResourceName": "Platform",
|
"ResourceName": "Platform",
|
||||||
"Key": "App.Reports.ReportTemplates.Description",
|
"Key": "App.Reports.ReportTemplates.Description",
|
||||||
"Value": "Report Templates — printouts with their own layout. Table: Adm_T_ReportTemplate · tenant scope · list screen. Reporting is based on DevExpress XtraReports.\nUsage: needed for output with its own layout (invoice, quotation, work order, certificate). The template is drawn in the designer at /admin/reports/{report}/design and viewed at /admin/reports/{report}/view/{id}. It is bound to a screen with a CommandColumnJson button: Url = \"/admin/reports/{ReportName}/view/@Id\".\nFields:\n• Name → the template name; the report is opened with this value\n• CategoryId → the category it belongs to; the lookup comes from the Report Categories screen\n• Description → what the report is for and which data set it uses\n• HtmlContent → the layout definition of the template; produced by the designer, not edited by hand\n• Status → the state of the template; a draft template is not offered to the user\nNote: for a plain printout of the current list no definition is needed — the report action in the toolbar of every list opens the ready DynamicGrid report (DynamicTree in a tree list) with the current filters and columns.\nCaution: report templates are database records; they are not written into configs/seeds/. Take an export before a large change — the designer output cannot be diffed easily."
|
"Value": "Report Templates — printouts with their own layout. Table: Adm_T_ReportTemplate · tenant scope · list screen. Reporting is based on DevExpress XtraReports.\nUsage: needed for output with its own layout (invoice, quotation, work order, certificate). The template is drawn in the designer at /admin/reports/{report}/design and viewed at /admin/reports/{report}/view/{id}. It is bound to a screen with a CommandColumnJson button: Url = \"/admin/reports/{ReportName}/view/@Id\".\nFields:\n• Name → the template name; the report is opened with this value\n• CategoryId → the category it belongs to; the lookup comes from the Report Categories screen\n• Description → what the report is for and which data set it uses\n• HtmlContent → the layout definition of the template; produced by the designer, not edited by hand\n• Status → the state of the template; a draft template is not offered to the user\nNote: for a plain printout of the current list no definition is needed — the report action in the toolbar of every list opens the ready DynamicGrid report (DynamicTree in a tree list) with the current filters and columns.\nCaution: template records are mirrored to configs/seeds/ (data/App.Reports.ReportTemplates.json). A template in the Raporlar category is HTML printed by DynamicReport; when it is opened from a screen row (/admin/reports/DynamicReport/view/{Id}/{ListFormCode}?recordId=@Id) its {{FieldName}} placeholders are filled from that record. Over MCP: list_report_templates, get_report_template, save_report_category, save_report_template."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "eeebfb5c-35b9-5c8e-b23e-556d35948928",
|
"Id": "eeebfb5c-35b9-5c8e-b23e-556d35948928",
|
||||||
"CultureName": "tr",
|
"CultureName": "tr",
|
||||||
"ResourceName": "Platform",
|
"ResourceName": "Platform",
|
||||||
"Key": "App.Reports.ReportTemplates.Description",
|
"Key": "App.Reports.ReportTemplates.Description",
|
||||||
"Value": "Rapor Şablonları — kendi düzeni olan çıktılar. Tablo: Adm_T_ReportTemplate · tenant kapsamlı · liste ekranı. Raporlama DevExpress XtraReports tabanlıdır.\nKullanım: kendi düzeni istenen çıktı (fatura, teklif, iş emri, sertifika) için gerekir. Şablon /admin/reports/{rapor}/design adresindeki tasarımcıda çizilir, /admin/reports/{rapor}/view/{id} adresinde görüntülenir. Ekrana CommandColumnJson butonu ile bağlanır: Url = \"/admin/reports/{RaporAdi}/view/@Id\".\nAlanlar:\n• Name → şablon adı; rapor bu değerle açılır\n• CategoryId → bağlı olduğu kategori; lookup Rapor Kategorileri ekranından gelir\n• Description → raporun ne işe yaradığı ve hangi veri setini kullandığı\n• HtmlContent → şablonun düzen tanımı; tasarımcı tarafından üretilir, elle düzenlenmez\n• Status → şablonun durumu; taslak şablon kullanıcıya sunulmaz\nNot: o anki listenin düz çıktısı için tanım gerekmez — her listenin araç çubuğundaki rapor aksiyonu hazır DynamicGrid raporunu (ağaç listede DynamicTree) mevcut filtreler ve kolonlarla açar.\nDikkat: rapor şablonları veritabanı kaydıdır; configs/seeds/ altına yazılmaz. Büyük bir değişiklikten önce dışa aktarım al — tasarımcı çıktısı kolay karşılaştırılamaz."
|
"Value": "Rapor Şablonları — kendi düzeni olan çıktılar. Tablo: Adm_T_ReportTemplate · tenant kapsamlı · liste ekranı. Raporlama DevExpress XtraReports tabanlıdır.\nKullanım: kendi düzeni istenen çıktı (fatura, teklif, iş emri, sertifika) için gerekir. Şablon /admin/reports/{rapor}/design adresindeki tasarımcıda çizilir, /admin/reports/{rapor}/view/{id} adresinde görüntülenir. Ekrana CommandColumnJson butonu ile bağlanır: Url = \"/admin/reports/{RaporAdi}/view/@Id\".\nAlanlar:\n• Name → şablon adı; rapor bu değerle açılır\n• CategoryId → bağlı olduğu kategori; lookup Rapor Kategorileri ekranından gelir\n• Description → raporun ne işe yaradığı ve hangi veri setini kullandığı\n• HtmlContent → şablonun düzen tanımı; tasarımcı tarafından üretilir, elle düzenlenmez\n• Status → şablonun durumu; taslak şablon kullanıcıya sunulmaz\nNot: o anki listenin düz çıktısı için tanım gerekmez — her listenin araç çubuğundaki rapor aksiyonu hazır DynamicGrid raporunu (ağaç listede DynamicTree) mevcut filtreler ve kolonlarla açar.\nDikkat: şablon kayıtları configs/seeds/ altına (data/App.Reports.ReportTemplates.json) yansır. Raporlar kategorisindeki şablon DynamicReport ile basılan HTML'dir; bir ekran satırından açıldığında (/admin/reports/DynamicReport/view/{Id}/{ListFormCode}?recordId=@Id) {{AlanAdi}} yer tutucuları o kaydın alanlarıyla doldurulur. MCP üzerinden: list_report_templates, get_report_template, save_report_category, save_report_template."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "613a3f66-1426-5a8f-af24-4354b9dc6318",
|
"Id": "613a3f66-1426-5a8f-af24-4354b9dc6318",
|
||||||
|
|
@ -69318,6 +69318,48 @@
|
||||||
"Key": "App.Mcp.Timesheet.DeleteComponent",
|
"Key": "App.Mcp.Timesheet.DeleteComponent",
|
||||||
"Value": "{0} bileşeni silindi"
|
"Value": "{0} bileşeni silindi"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Id": "e7c8404a-ba29-52f2-9f48-53d778dfe2f9",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.SaveReportCategory",
|
||||||
|
"Value": "Report category {0} saved"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "744aed7c-4aa9-5868-a967-362543d4b9da",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.SaveReportCategory",
|
||||||
|
"Value": "{0} rapor kategorisi kaydedildi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "d17347c8-f526-560c-bd68-a9323721ffd9",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.SaveReportTemplate",
|
||||||
|
"Value": "Report template {0} saved"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "3adca18e-583c-51d5-8e4d-4206f58ff3a3",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.SaveReportTemplate",
|
||||||
|
"Value": "{0} rapor şablonu kaydedildi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "c03489b9-1434-5598-bfec-f928e163b0d7",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.DeleteReportTemplate",
|
||||||
|
"Value": "Report template {0} deleted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "93e25831-412f-528b-8fec-6553b5d1137f",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Timesheet.DeleteReportTemplate",
|
||||||
|
"Value": "{0} rapor şablonu silindi"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Id": "11a42b2b-3a91-54ec-92c7-3020e0b3aa14",
|
"Id": "11a42b2b-3a91-54ec-92c7-3020e0b3aa14",
|
||||||
"CultureName": "en",
|
"CultureName": "en",
|
||||||
|
|
@ -69416,6 +69458,104 @@
|
||||||
"Key": "App.Mcp.Error.VisualDesignerComponent",
|
"Key": "App.Mcp.Error.VisualDesignerComponent",
|
||||||
"Value": "\"{0}\" bileşeninin kodu Visual Designer belgesinden üretiliyor, MCP üzerinden yazılamaz. Bileşeni Visual Designer'da düzenleyin."
|
"Value": "\"{0}\" bileşeninin kodu Visual Designer belgesinden üretiliyor, MCP üzerinden yazılamaz. Bileşeni Visual Designer'da düzenleyin."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Id": "e9467c21-1acf-5b7e-bafe-22f5327e80b4",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportNameRequired",
|
||||||
|
"Value": "A name is required."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "d61f69b0-af46-5a66-bb64-c1558fecc904",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportNameRequired",
|
||||||
|
"Value": "Ad zorunludur."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "23b03a6a-9131-5367-a32c-3c4e6506f5aa",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportHtmlRequired",
|
||||||
|
"Value": "HtmlContent is required for a new report template."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "e406540b-d85e-5cf7-9076-6250f1c0532f",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportHtmlRequired",
|
||||||
|
"Value": "Yeni rapor şablonunda HtmlContent zorunludur."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "18d9c5cc-176e-5de9-a5a9-dd03bcc65934",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportCategoryRequired",
|
||||||
|
"Value": "A category is required for a new report template. Read the categories with list_report_templates."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "c31266e3-00be-5f7a-9c23-2cd7842c68fa",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportCategoryRequired",
|
||||||
|
"Value": "Yeni rapor şablonunda kategori zorunludur. Kategorileri list_report_templates ile okuyun."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "28fe03db-7c92-5875-a41f-c5c03299636e",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportCategoryNotFound",
|
||||||
|
"Value": "Report category \"{0}\" was not found. Read the categories with list_report_templates or create it with save_report_category."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "fa2a2230-8000-5326-ba1e-7a014cc45f39",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportCategoryNotFound",
|
||||||
|
"Value": "\"{0}\" rapor kategorisi bulunamadı. Kategorileri list_report_templates ile okuyun ya da save_report_category ile oluşturun."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "aaf964c0-ea3b-54a7-931c-95978e34e05e",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportTemplateNotFound",
|
||||||
|
"Value": "Report template \"{0}\" was not found."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "664b422e-00f3-5b86-a4f0-6a6b6205ccc8",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportTemplateNotFound",
|
||||||
|
"Value": "\"{0}\" rapor şablonu bulunamadı."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "375f2d7b-53c6-51f9-9682-aa5b17ffd334",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportListFormNotFound",
|
||||||
|
"Value": "List form \"{0}\" was not found. A screen that is waiting for DB Migrate cannot be used yet."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "134eb4f8-8902-5da5-a44d-b35a8bab0b5a",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportListFormNotFound",
|
||||||
|
"Value": "\"{0}\" ekranı bulunamadı. DB Migrate bekleyen bir ekran henüz kullanılamaz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "fff89062-9058-5067-a13e-d2cb6f831dca",
|
||||||
|
"CultureName": "en",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportPlaceholdersUnknown",
|
||||||
|
"Value": "These placeholders are not fields of \"{0}\": {1}. Use the field names returned by describe_list."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "f6791258-c8e7-5351-b0e4-12e167be862f",
|
||||||
|
"CultureName": "tr",
|
||||||
|
"ResourceName": "Platform",
|
||||||
|
"Key": "App.Mcp.Error.ReportPlaceholdersUnknown",
|
||||||
|
"Value": "Şu yer tutucular \"{0}\" ekranının alanı değil: {1}. describe_list ile dönen alan adlarını kullanın."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Id": "66fc88f1-60bd-5e69-9dbc-2da3a8db6f57",
|
"Id": "66fc88f1-60bd-5e69-9dbc-2da3a8db6f57",
|
||||||
"CultureName": "en",
|
"CultureName": "en",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { ColumnFormatEditDto } from '@/proxy/admin/list-form-field/models'
|
import { ColumnFormatEditDto } from '@/proxy/admin/list-form-field/models'
|
||||||
import {
|
import {
|
||||||
|
CommandColumnDto,
|
||||||
GanttOptionDto,
|
GanttOptionDto,
|
||||||
GridOptionsEditDto,
|
GridOptionsEditDto,
|
||||||
SchedulerOptionDto,
|
SchedulerOptionDto,
|
||||||
|
|
@ -128,6 +129,8 @@ export interface ListFormWizardDto {
|
||||||
groups?: ListFormWizardColumnGroupDto[]
|
groups?: ListFormWizardColumnGroupDto[]
|
||||||
subForms?: SubFormDto[]
|
subForms?: SubFormDto[]
|
||||||
widgets?: WidgetEditDto[]
|
widgets?: WidgetEditDto[]
|
||||||
|
/** Wizard adımı yoktur; düzenlemede korunur ve MCP deploy_wizard ile yazılır. */
|
||||||
|
commandColumns?: CommandColumnDto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Izin grubu secim listesi ogesi; gorunen adlar grup adiyla ayni olan dil anahtarindan gelir. */
|
/** Izin grubu secim listesi ogesi; gorunen adlar grup adiyla ayni olan dil anahtarindan gelir. */
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ const initialValues: ListFormWizardDto = {
|
||||||
keyFieldDbSourceType: DbTypeEnum.Int32,
|
keyFieldDbSourceType: DbTypeEnum.Int32,
|
||||||
subForms: [],
|
subForms: [],
|
||||||
widgets: [],
|
widgets: [],
|
||||||
|
commandColumns: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const step1ValidationSchema = Yup.object().shape({
|
const step1ValidationSchema = Yup.object().shape({
|
||||||
|
|
@ -753,6 +754,7 @@ const Wizard = () => {
|
||||||
keyFieldDbSourceType: w.keyFieldDbSourceType ?? DbTypeEnum.Int32,
|
keyFieldDbSourceType: w.keyFieldDbSourceType ?? DbTypeEnum.Int32,
|
||||||
subForms: w.subForms ?? [],
|
subForms: w.subForms ?? [],
|
||||||
widgets: w.widgets ?? [],
|
widgets: w.widgets ?? [],
|
||||||
|
commandColumns: w.commandColumns ?? [],
|
||||||
})
|
})
|
||||||
|
|
||||||
// Queue column load to run once dbObjects is available
|
// Queue column load to run once dbObjects is available
|
||||||
|
|
@ -1139,6 +1141,7 @@ const Wizard = () => {
|
||||||
groups: [],
|
groups: [],
|
||||||
subForms: [],
|
subForms: [],
|
||||||
widgets: [],
|
widgets: [],
|
||||||
|
commandColumns: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
returnToManager()
|
returnToManager()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue