Bazı hatalar giderildi

This commit is contained in:
Sedat ÖZTÜRK 2026-09-30 17:57:25 +03:00
parent c62e2c8586
commit 6081131f95
9 changed files with 157 additions and 12 deletions

View file

@ -707,6 +707,11 @@ Kısa ve karar odaklı yaz; talebi tekrar anlatma.
altında tek satırda gösterir — `Yetki: {İzin grubu} › {Ara yetki} › {ListFormCode}` — ve her ara
seviyenin yetkisi için Var / Yeni yazar (var olan `Abp.Identity.Permissions.json`'dan okunur).
Yetkinin yerini menüden ayrı seçen bir wizard alanı yoktur; yetki dalı menü yoluyla birlikte değişir.
**Menüsüz alt formun yetkisi listenin yetkisinin altındadır**: alt formun okuma yetkisinin
`ParentName` değeri onu `SubForms` içinde taşıyan listenin yetki kodudur (grubun kökü değil);
analiz satırı `Yetki: {İzin grubu} › {Ara yetki} › {Liste} › {AltForm}` olur. Deploy bunu kendisi
kurar — alt form önce deploy edilir, liste deploy edildiğinde alt formun yetkisi listenin
altına, listenin alt yetkilerinin (Create/Update/…) ardına taşınır; ayrıca bir alan yazılmaz.
Aynı üst menü altındaki ekranlar **bağımlılık sırasıyla** yazılır: başka bir ekranın lookup
kaynağı olan (tanım) ekran, onu kullanan ekranın üstündedir. Menüdeki sıra deploy sırasından
gelir; madde 9'daki üretim planı da aynı sırayla yazılır.
@ -1115,6 +1120,10 @@ kaynağı odur. Grup yapısı yalnızca düzenleme formunun yerleşimini belirle
]
```
Grup başlığı yalnızca düzenleme formunda **birden fazla grup** varsa yazılır; tek gruplu formda
başlık popup başlığını tekrarlar ve boş bırakılır. MCP `deploy_wizard` bunu kendisi uygular:
düzenleme formuna giren tek grup varsa `Caption` boşaltılır (`McpDevelopAppService.DeployWizardAsync`).
Seed dosyasında `Groups` **kırpılmış** hâlde durur: grup başlığı/kolon sayısı ve alanların
editör bilgileri `ListForm.EditingFormDto` ve `Fields` içinde olduğu için tekrarlanmaz; dosyada
her öğe yalnızca `FieldName`, `CaptionName` ve `En`/`Tr` taşır — çünkü
@ -2159,7 +2168,8 @@ ardından `ListFormSeedDataApplier` menü/yetki/dil satırlarını kurar (§4.0)
1. Konfigürasyon doğrulama
2. Menü kaydı (`CreateMenu: false` ise atlanır)
3. Dil metinleri
4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin altına, §0.12 Menü yeri)
4. İzin grubu + yetkiler (okuma yetkisi menü dalındaki ara yetkinin altına; menüsüz alt formda
listenin yetkisinin altına, §0.12 Menü yeri)
5. List yolunda: veri kaynağı → ListForm → alan grupları · Custom yolunda: bileşen bağlama
6. Deploy + tamamlandı

View file

@ -951,8 +951,13 @@ zincirinden kurar, böylece çocuk her zaman ebeveyninin altında kalır. Değer
yazar: üst menü zincirindeki (`MenuParentPath` + `MenuParentCode`) her seviye, izin grubunun
kendisi hariç, aynı kodlu bir ara yetkidir (`İnsan Kaynakları › Tanımlamalar › Ekran` →
`App.Hr.Definitions › App.Hr.Doctor`). Var olan ara yetki kullanılır, eksik olan menü seviyesinin
görünen adıyla wizard'a ait üretilir; başka bir gruptaki aynı adlı yetki atlanır. Menüsüz wizard'da
(`CreateMenu: false`) okuma yetkisi grubun köküne yazılır.
görünen adıyla wizard'a ait üretilir; başka bir gruptaki aynı adlı yetki atlanır. Menüsüz wizard'ın
(`CreateMenu: false`, alt form) okuma yetkisi onu `SubForms` içinde taşıyan listenin yetkisine
bağlanır: `ParentName` = listenin yetki kodu (`App.Hr.HealthCard › App.Hr.HealthCardFile`), sırası
listenin alt yetkilerinin (Create/Update/…) ardındandır. Alt form listeden önce deploy edildiği için
yetkisi önce grubun köküne yazılır; liste deploy edildiğinde alt formun satırı listenin altına
taşınır (satır alt formun wizard'ına ait kalır, başka bir listeye bağlanmış alt form taşınmaz).
Hiçbir listenin alt formu olmayan menüsüz wizard'ın yetkisi grubun kökünde kalır.
Wizard deploy'u yeni grubu ve yeni yetkileri (seed satırı olarak) veritabanı ile seed
dosyasındaki kardeşlerin en büyük sırasının bir fazlasıyla, alt yetkileri `WizardSeedRowKeys.ChildPermissions` dizilimiyle
(List: Create/Update/Delete/Export/Import/Note, Custom: Create/Update/Delete) 1'den yazar

View file

@ -175,6 +175,14 @@ public class McpDevelopAppService(
? 0
: (await wizardAppService.GetFile(wizard.EditFileName)).Wizard?.MenuOrder ?? 0;
// Tek gruplu duzenleme formunda grup basligi popup basligini tekrarlar; baslik yalnizca
// birden fazla grubu birbirinden ayirmak icin yazilir.
var editGroups = wizard.Groups
.Where(group => group.Items.Any(item => item.IncludeInEditingForm && item.FieldName != wizard.KeyFieldName))
.ToList();
if (editGroups.Count == 1)
editGroups[0].Caption = string.Empty;
// Satirlar deploy'dan once dogrulanir; gecersiz girdi yarim bir deploy birakmaz.
var seedRows = ParseSeedRows(input.SeedRows, wizard);

View file

@ -47,6 +47,17 @@ public class McpTableCreator(
/// Varsayilan deger olarak kabul edilenler: sayi, metin sabiti ve tasarimcinin kendi
/// sablonlarinda kullandigi fonksiyonlar. Baska bir ifade script'e hic girmez.
/// </summary>
/// <summary>
/// Istemcinin SQL bicimiyle yazdigi tip: <c>nvarchar(50)</c>, <c>nvarchar (MAX)</c>,
/// <c>decimal(18, 4)</c>. Boyut tipten ayrilir; tip adi yine beyaz listeden gecer.
/// </summary>
private static readonly Regex SizedTypeRegex = new(
@"^(?<name>[A-Za-z0-9]+)\s*\(\s*(?<size>[^)]*?)\s*\)$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
/// <summary>Tasarimci decimal'i her zaman bu kesinlikle yazar (<c>ToColumnLine</c>).</summary>
private const string DecimalSize = "18,4";
private static readonly Regex SafeDefaultRegex = new(
@"^(?:-?\d{1,18}(?:\.\d{1,18})?|N?'(?:[^']|'')*'|NEWID\(\)|GETDATE\(\)|GETUTCDATE\(\)|SYSUTCDATETIME\(\))$",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
@ -150,15 +161,12 @@ public class McpTableCreator(
var name = (column.Name ?? string.Empty).Trim();
EnsureSafeIdentifier(name);
var type = SupportedTypes.FirstOrDefault(candidate =>
string.Equals(candidate, column.Type?.Trim(), StringComparison.OrdinalIgnoreCase))
?? throw new UserFriendlyException(
localizer[McpErrorCodes.UnsupportedColumnType, column.Type ?? string.Empty, string.Join(", ", SupportedTypes)]);
var (type, sizedLength) = ResolveType(column.Type);
int? maxLength = null;
if (type == "nvarchar")
{
maxLength = column.MaxLength ?? DefaultTextLength;
maxLength = column.MaxLength ?? sizedLength ?? DefaultTextLength;
if (maxLength is < 1 or > MaxTextLength)
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidColumnLength, name, MaxTextLength]);
@ -174,6 +182,49 @@ public class McpTableCreator(
return new McpTableColumn(name, type, maxLength, column.IsNullable, defaultValue);
}
/// <summary>
/// Tipi beyaz listedeki adina indirger. <c>nvarchar(N)</c> uzunlugu dondurur,
/// <c>nvarchar(MAX)</c> kendi tipidir; decimal yalnizca tasarimcinin kesinligiyle kabul edilir —
/// baska bir kesinligi 18,4'e cevirmek sessizce veri kaybi riski tasirdi.
/// </summary>
private (string Type, int? MaxLength) ResolveType(string? rawType)
{
var trimmed = rawType?.Trim() ?? string.Empty;
var name = trimmed;
string? size = null;
var sized = SizedTypeRegex.Match(trimmed);
if (sized.Success)
{
name = sized.Groups["name"].Value;
size = Regex.Replace(sized.Groups["size"].Value, @"\s+", string.Empty);
}
int? maxLength = null;
if (size is not null)
{
if (name.Equals("nvarchar", StringComparison.OrdinalIgnoreCase) && size.Equals("max", StringComparison.OrdinalIgnoreCase))
{
name = "nvarchar(MAX)";
}
else if (name.Equals("nvarchar", StringComparison.OrdinalIgnoreCase) && int.TryParse(size, out var length))
{
maxLength = length;
}
else if (!(name.Equals("decimal", StringComparison.OrdinalIgnoreCase) && size == DecimalSize))
{
name = trimmed;
}
}
var type = SupportedTypes.FirstOrDefault(candidate =>
string.Equals(candidate, name, StringComparison.OrdinalIgnoreCase))
?? throw new UserFriendlyException(
localizer[McpErrorCodes.UnsupportedColumnType, trimmed, string.Join(", ", SupportedTypes)]);
return (type, maxLength);
}
private static string BuildScript(string tableName, List<McpTableColumn> columns)
{
var fullTableName = $"[{SchemaName}].[{tableName}]";

View file

@ -35,7 +35,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService)
public async Task<McpCreateTableResultDto> CreateTableAsync(
[Description("Tablo adi; harf/rakam/alt cizgi. Adlandirma icin get_dev_guide'daki tablo onek kuralina uy (ornegin Adm_T_...).")]
string tableName,
[Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (18,4), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
[Description("Is kolonlari. type: nvarchar, nvarchar(MAX), int, bigint, decimal (her zaman 18,4 yazilir; boyut verme), float, bit, datetime, datetime2, date, uniqueidentifier, money. maxLength yalnizca nvarchar icin (varsayilan 100). defaultValue: sayi, 'metin' ya da NEWID()/GETDATE()/GETUTCDATE()/SYSUTCDATETIME().")]
List<McpTableColumnDto> columns,
[Description("Tam audit kolonlari eklensin mi. Kullanici acikca istemedikce true birak.")]
bool fullAudited = true,

View file

@ -409,7 +409,8 @@ public class ListFormWizardAppService(
/// <summary>
/// Okuma yetkisi ve alt yetkileri. Alt yetkilerin sirasi kardesler arasindadir ve listedeki
/// dizilimi izler (Create = 1, ...); okuma yetkisi grubun kok yetkilerinin sonuna eklenir.
/// dizilimi izler (Create = 1, ...), menusuz alt formlarin okuma yetkileri bunlarin ardindan
/// gelir; okuma yetkisi menu dalinin, menusuz alt formda ise listenin yetkisinin altina eklenir.
/// Yetkiler menuyle ayni modul filtresine tabidir; modul bos kalirsa rol ekraninda modul
/// secimiyle filtrelenen listede gorunmez.
/// </summary>
@ -496,6 +497,24 @@ public class ListFormWizardAppService(
}
}
// Menusuz alt formun menude dali yoktur; yetkisi onu alt form olarak tasiyan listenin
// yetkisinin altindadir. Liste henuz deploy edilmediyse kokte kalir, liste deploy
// edildiginde asagida tasinir.
var wizards = (await ReadSeedFilesAsync()).Select(f => f.Seed?.Wizard).OfType<ListFormWizardDto>().ToList();
bool InGroup(string name) =>
rows.Find(WizardSeedRowStore.PermissionFile, "Name", name) is { } row
? Same(WizardSeedRowStore.Text(row, "GroupName"), groupName)
: dbPermissions.Any(p => Same(p.Name, name));
if (!input.CreateMenu)
{
var listFormCode = input.ResolveListFormCode();
parentName = wizards
.Where(w => !Same(w.ResolveMenuCode(), code) && (w.SubForms ?? []).Any(s => Same(s.Code, listFormCode)))
.Select(w => w.ResolveMenuCode())
.FirstOrDefault(InGroup);
}
if (IsOwnable(code))
Write(code, parentName, code, NextOrder(code, parentName));
@ -508,6 +527,31 @@ public class ListFormWizardAppService(
if (IsOwnable(name))
Write(name, code, langKey, childOrder);
}
// Alt formlar listeden once deploy edilir (bagimlilik sirasi), yetkileri o an kokte yazilir.
// Satir alt formun wizard'ina ait kalir; liste yalnizca yerini degistirir. Baska bir listenin
// altina alinmis alt form orada birakilir.
foreach (var subForm in input.SubForms ?? [])
{
var child = wizards.FirstOrDefault(w => !w.CreateMenu && Same(w.ResolveListFormCode(), subForm.Code));
var childName = child?.ResolveMenuCode();
var row = rows.Find(WizardSeedRowStore.PermissionFile, "Name", childName);
if (child == null || row == null || Same(childName, code) || !InGroup(childName!))
continue;
var currentParent = WizardSeedRowStore.Text(row, "ParentName");
if (!string.IsNullOrWhiteSpace(currentParent) && !Same(currentParent, code))
continue;
childOrder++;
rows.Upsert(WizardSeedRowStore.PermissionFile, new Dictionary<string, object>
{
["Name"] = childName!,
["ParentName"] = code,
[PlatformConsts.Permissions.Order] = childOrder,
}, "Name");
}
}
/// <summary>

View file

@ -128,7 +128,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
değer base64 saklanır, sınırlı boy ve `dxImageViewer` kullanılmaz (deploy ikisini de reddeder).
2. **Yeni ekran** → Wizard yolu. `Definitions` altına otomatik yerleştirme yok; modüle ait yeni
bir kök menü oluştur, `Order = max(Order) + 1`. Yetki ağacı menü ağacını izler: ekranın okuma
yetkisi menüdeki dalın ara yetkisinin altına yazılır (`lowcode.instructions.md` §0.12 Menü yeri).
yetkisi menüdeki dalın ara yetkisinin altına yazılır; menüsüz alt formun yetkisi ise listenin
yetkisine bağlanır (`ParentName` = listenin yetki kodu) (`lowcode.instructions.md` §0.12 Menü yeri).
3. **Yeni ekran/menü** → yetki sözleşmesi olmadan önerilmez. Menü ve route aynı ekran
sözleşmesini göstermeli.
4. **Kullanıcıya görünen her metin** → dil anahtarı üzerinden, EN + TR birlikte. Koda gömülü

File diff suppressed because one or more lines are too long

View file

@ -1964,7 +1964,7 @@ const TodoBoardContent = ({
{canDelete && (
<button
type="button"
className="hidden rounded p-1 text-gray-400 hover:bg-red-50 hover:text-red-500 group-hover/card:block dark:hover:bg-red-950"
className="invisible rounded p-1 text-gray-400 hover:bg-red-50 hover:text-red-500 focus-visible:visible group-hover/card:visible dark:hover:bg-red-950"
title={translate('::App.ListFormTodoBoard.DeleteTodo')}
onClick={(event) => {
event.stopPropagation()