mcp Lookup Cascading özelliği eklendi.

This commit is contained in:
Sedat ÖZTÜRK 2026-10-01 16:42:56 +03:00
parent c1bdc47d28
commit 81b2202df0
9 changed files with 104 additions and 12 deletions

View file

@ -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). 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 | | `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). Bağımlı lookup öğedeki `Cascade*` alanları (§4.3.4). Çı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 |
@ -1341,7 +1341,11 @@ başlangıçlar bu anahtarların doğru bileşimlerini üretir.
### 4.3.4 Lookup ve cascade (`LookupJson`) ### 4.3.4 Lookup ve cascade (`LookupJson`)
Wizard'daki `LookupDataSourceType` + `LookupQuery` + `ValueExpr` + `DisplayExpr` alanları alanın Wizard'daki `LookupDataSourceType` + `LookupQuery` + `ValueExpr` + `DisplayExpr` alanları alanın
`LookupJson` kolonuna dönüşür. Tam sözleşme: `LookupJson` kolonuna dönüşür. **Cascade de wizard girdisidir:** öğedeki `CascadeParentFields`,
`CascadeRelationField`, `CascadeFilterOperator`, `CascadeEmptyFields` aynı adla `LookupJson`'a
yazılır (`WizardConsts.DefaultLookupJson`; yalnızca dolu olanlar). Wizard ekranında bu alanların
adımı yoktur, düzenlemede korunur. MCP `deploy_wizard` ile cascade'li ekran doğrudan üretilir;
cascade eksik yetenek değildir ve analizde `Ready to Start`'ı engellemez. Tam sözleşme:
| Alan | Anlam | | Alan | Anlam |
| --- | --- | | --- | --- |
@ -1386,6 +1390,25 @@ kaçışlanmış JSON string** olarak yazılır; canlı örnek Tenants ekranı,
Tablo adlarını tahmin etme: platform tablolarının adı `TableNameResolver`'dan gelir (reference Tablo adlarını tahmin etme: platform tablolarının adı `TableNameResolver`'dan gelir (reference
§12.1); yukarıdakiler kalıbı gösterir, üretimden önce `sql/object/` ya da mevcut sorgudan doğrula. §12.1); yukarıdakiler kalıbı gösterir, üretimden önce `sql/object/` ya da mevcut sorgudan doğrula.
Wizard öğesi olarak (Eğitim Tipi → Eğitim Konusu; ebeveyn StaticData, çocuk Query):
```jsonc
{ "FieldName": "TrainingType", "EditorType": "dxSelectBox", "LookupDataSourceType": 1,
"LookupQuery": "[{\"key\":\"Orientation\",\"name\":\"Oryantasyon Eğitimi\"}]",
"ValueExpr": "key", "DisplayExpr": "name",
"CascadeEmptyFields": "TrainingTopicId" },
{ "FieldName": "TrainingTopicId", "EditorType": "dxSelectBox", "LookupDataSourceType": 2,
"LookupQuery": "SELECT Id AS [Key], Name AS [Name] FROM Hr_T_TrainingTopic WHERE (TrainingType = @param0 OR @param0 IS NULL) AND IsDeleted = 'false' ORDER BY Name",
"ValueExpr": "Key", "DisplayExpr": "Name",
"CascadeParentFields": "TrainingType" }
```
**Lookup'ta Id görünmesin.** Açılır listede metin yerine Id görünüyorsa iki nedeni vardır:
`DisplayExpr` sorgunun döndürdüğü kolon adıyla birebir (büyük/küçük harf dahil) eşleşmiyordur,
ya da `EditorOptions`'ta `grouped: true` vardır ve öğeler `group` alanı taşımaz. `Query` lookup'ı
`[Key]` + `[Name]` takma adlarını döndürür ve `ValueExpr: "Key"`, `DisplayExpr: "Name"` yazılır.
Bağımlı liste `grouped` ile değil cascade ile kurulur.
> Lookup verisi önbelleklenir; aynı sorgu aynı filtrelerle bir kez yüklenir. > Lookup verisi önbelleklenir; aynı sorgu aynı filtrelerle bir kez yüklenir.
### 4.3.5 Varsayılan değerler — `*FieldsDefaultValueJson` ### 4.3.5 Varsayılan değerler — `*FieldsDefaultValueJson`
@ -2469,7 +2492,7 @@ tanımsız kalır**. Yardımcı adlarını yeniden tanımlama.
- **Alan gizleme yok.** Runtime'da `visible` yardımcısı yoktur; koşullu görünürlük yerine - **Alan gizleme yok.** Runtime'da `visible` yardımcısı yoktur; koşullu görünürlük yerine
`readOnly` kullan, kalıcı gizleme `EditorOptions.visible` ya da `IncludeInEditingForm`'dur. `readOnly` kullan, kalıcı gizleme `EditorOptions.visible` ya da `IncludeInEditingForm`'dur.
- **Lookup sorgusu değiştirilemez**; cascade davranışı ListForm lookup ayarlarından kurulur. - **Lookup sorgusu değiştirilemez**; bağımlı liste wizard öğesinin `Cascade*` alanlarıyla kurulur (§4.3.4).
- **Sunucu tarafı doğrulama değildir.** `ask`/`notify` yalnızca tarayıcıdadır; iş kuralı - **Sunucu tarafı doğrulama değildir.** `ask`/`notify` yalnızca tarayıcıdadır; iş kuralı
zorunluysa SQL/endpoint tarafında da uygula. zorunluysa SQL/endpoint tarafında da uygula.
- **Satırlar arası hesap yok**: script yalnızca düzenlenen kaydı ve seçili lookup kaydını görür. - **Satırlar arası hesap yok**: script yalnızca düzenlenen kaydı ve seçili lookup kaydını görür.

View file

@ -377,7 +377,12 @@ public class ListFormWizardDto
field.ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson; field.ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson;
field.PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson; field.PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson;
field.LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery) field.LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery)
? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery) ? WizardConsts.DefaultLookupJson(
item.LookupDataSourceType,
item.DisplayExpr,
item.ValueExpr,
item.LookupQuery,
new WizardLookupCascade(item.CascadeParentFields, item.CascadeRelationField, item.CascadeFilterOperator, item.CascadeEmptyFields))
: null; : null;
field.ValidationRuleJson = inEditingForm && item.IsRequired field.ValidationRuleJson = inEditingForm && item.IsRequired
? WizardConsts.DefaultValidationRuleRequiredJson ? WizardConsts.DefaultValidationRuleRequiredJson
@ -490,6 +495,10 @@ public class ListFormWizardDto
ValueExpr = lookup.ValueExpr, ValueExpr = lookup.ValueExpr,
DisplayExpr = lookup.DisplayExpr, DisplayExpr = lookup.DisplayExpr,
LookupQuery = lookup.LookupQuery, LookupQuery = lookup.LookupQuery,
CascadeParentFields = lookup.CascadeParentFields,
CascadeRelationField = lookup.CascadeRelationField,
CascadeFilterOperator = string.IsNullOrWhiteSpace(lookup.CascadeParentFields) ? null : lookup.CascadeFilterOperator,
CascadeEmptyFields = lookup.CascadeEmptyFields,
EnglishCaption = previous?.EnglishCaption, EnglishCaption = previous?.EnglishCaption,
TurkishCaption = previous?.TurkishCaption, TurkishCaption = previous?.TurkishCaption,
}); });

View file

@ -20,4 +20,16 @@ public class WizardColumnItemInputDto
public string ValueExpr { get; set; } public string ValueExpr { get; set; }
public string DisplayExpr { get; set; } public string DisplayExpr { get; set; }
public string LookupQuery { get; set; } public string LookupQuery { get; set; }
/// <summary>Cocuk alanda: degeri lookup sorgusuna <c>@param0…</c> olarak giden ebeveyn alan(lar), virgulle.</summary>
public string CascadeParentFields { get; set; }
/// <summary>Cocuk alanda: StaticData ogesinde ebeveyn degeriyle karsilastirilan alan (<c>group</c>).</summary>
public string CascadeRelationField { get; set; }
/// <summary>StaticData cascade filtresinin operatoru; bos ise <c>=</c>.</summary>
public string CascadeFilterOperator { get; set; }
/// <summary>Ebeveyn alanda: bu alan degisince bosaltilacak cocuk alanlar, virgulle.</summary>
public string CascadeEmptyFields { get; set; }
} }

View file

@ -109,7 +109,9 @@ public class WizardSeedFileDto
nameof(WizardColumnItemInputDto.IsRequired), nameof(WizardColumnItemInputDto.IncludeInEditingForm), nameof(WizardColumnItemInputDto.IsRequired), nameof(WizardColumnItemInputDto.IncludeInEditingForm),
nameof(WizardColumnItemInputDto.DbSourceType), nameof(WizardColumnItemInputDto.LookupDataSourceType), nameof(WizardColumnItemInputDto.DbSourceType), nameof(WizardColumnItemInputDto.LookupDataSourceType),
nameof(WizardColumnItemInputDto.ValueExpr), nameof(WizardColumnItemInputDto.DisplayExpr), nameof(WizardColumnItemInputDto.ValueExpr), nameof(WizardColumnItemInputDto.DisplayExpr),
nameof(WizardColumnItemInputDto.LookupQuery), nameof(WizardColumnItemInputDto.LookupQuery), nameof(WizardColumnItemInputDto.CascadeParentFields),
nameof(WizardColumnItemInputDto.CascadeRelationField), nameof(WizardColumnItemInputDto.CascadeFilterOperator),
nameof(WizardColumnItemInputDto.CascadeEmptyFields),
]; ];
/// <summary>Dosyada `Menu` basligi altinda toplanan wizard alanlari; adlari degismez.</summary> /// <summary>Dosyada `Menu` basligi altinda toplanan wizard alanlari; adlari degismez.</summary>

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Data; using System.Data;
using System.Text.Json; using System.Text.Json;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
@ -128,13 +129,38 @@ public static class WizardConsts
new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) } new { Type = Enum.GetName(UiColumnValidationRuleTypeEnum.required) }
}); });
public static string DefaultLookupJson(UiLookupDataSourceTypeEnum dataSourceType, string displayExpr = "name", string valueExpr = "key", string lookupQuery = "") => JsonSerializer.Serialize(new /// <summary>
/// Cascade alanlari yalnizca doluysa yazilir; cascade'siz lookup'in JSON'u onceki bicimiyle
/// ayni kalir, boylece yeniden deploy edilen ekranlarin seed dosyalari sahte fark uretmez.
/// </summary>
public static string DefaultLookupJson(
UiLookupDataSourceTypeEnum dataSourceType,
string displayExpr = "name",
string valueExpr = "key",
string lookupQuery = "",
WizardLookupCascade cascade = null)
{ {
DataSourceType = dataSourceType, var lookup = new Dictionary<string, object>
DisplayExpr = displayExpr, {
ValueExpr = valueExpr, ["DataSourceType"] = dataSourceType,
LookupQuery = lookupQuery, ["DisplayExpr"] = displayExpr,
}); ["ValueExpr"] = valueExpr,
["LookupQuery"] = lookupQuery,
};
if (!string.IsNullOrWhiteSpace(cascade?.ParentFields))
{
lookup["CascadeParentFields"] = cascade.ParentFields.Trim();
lookup["CascadeFilterOperator"] = string.IsNullOrWhiteSpace(cascade.FilterOperator) ? "=" : cascade.FilterOperator.Trim();
if (!string.IsNullOrWhiteSpace(cascade.RelationField))
lookup["CascadeRelationField"] = cascade.RelationField.Trim();
}
if (!string.IsNullOrWhiteSpace(cascade?.EmptyFields))
lookup["CascadeEmptyFields"] = cascade.EmptyFields.Trim();
return JsonSerializer.Serialize(lookup);
}
/// <summary> /// <summary>
/// Query kaynagi turetilmis tablo olarak sarilir ve kolonlar bu takma adla nitelenir; /// Query kaynagi turetilmis tablo olarak sarilir ve kolonlar bu takma adla nitelenir;
@ -217,3 +243,6 @@ public static class WizardConsts
AllowDetail = AllowDetail AllowDetail = AllowDetail
}); });
} }
/// <summary>Wizard alanindan lookup'a yazilan cascade ayarlari (<c>LookupDto.Cascade*</c>).</summary>
public sealed record WizardLookupCascade(string ParentFields, string RelationField, string FilterOperator, string EmptyFields);

File diff suppressed because one or more lines are too long

View file

@ -285,6 +285,10 @@ export interface WizardSeedFileItemDto {
valueExpr?: string valueExpr?: string
displayExpr?: string displayExpr?: string
lookupQuery?: string lookupQuery?: string
cascadeParentFields?: string
cascadeRelationField?: string
cascadeFilterOperator?: string
cascadeEmptyFields?: string
} }
export interface WizardSeedFileGroupDto { export interface WizardSeedFileGroupDto {

View file

@ -797,6 +797,10 @@ const Wizard = () => {
valueExpr: it.valueExpr ?? 'Key', valueExpr: it.valueExpr ?? 'Key',
displayExpr: it.displayExpr ?? 'Name', displayExpr: it.displayExpr ?? 'Name',
lookupQuery: it.lookupQuery ?? '', lookupQuery: it.lookupQuery ?? '',
cascadeParentFields: it.cascadeParentFields,
cascadeRelationField: it.cascadeRelationField,
cascadeFilterOperator: it.cascadeFilterOperator,
cascadeEmptyFields: it.cascadeEmptyFields,
}) as WizardGroupItem, }) as WizardGroupItem,
), ),
})) }))
@ -1173,6 +1177,10 @@ const Wizard = () => {
displayExpr: item.displayExpr, displayExpr: item.displayExpr,
valueExpr: item.valueExpr, valueExpr: item.valueExpr,
lookupQuery: item.lookupQuery, lookupQuery: item.lookupQuery,
cascadeParentFields: item.cascadeParentFields,
cascadeRelationField: item.cascadeRelationField,
cascadeFilterOperator: item.cascadeFilterOperator,
cascadeEmptyFields: item.cascadeEmptyFields,
} }
}), }),
})), })),

View file

@ -54,6 +54,11 @@ export interface WizardGroupItem {
valueExpr?: string valueExpr?: string
displayExpr?: string displayExpr?: string
lookupQuery?: string lookupQuery?: string
/** Wizard ekraninda sorulmaz; MCP deploy_wizard ya da alan detayindan gelir, duzenlemede korunur. */
cascadeParentFields?: string
cascadeRelationField?: string
cascadeFilterOperator?: string
cascadeEmptyFields?: string
} }
export interface WizardGroup { export interface WizardGroup {