Administration -> Onaycılar

This commit is contained in:
Sedat ÖZTÜRK 2026-10-09 14:23:24 +03:00
parent 2a449a158d
commit c2e6ef8a37
17 changed files with 1827 additions and 23 deletions

View file

@ -980,7 +980,7 @@ Tutarlılık zorunludur; menü, route, yetki ve dil anahtarı aynı kökten tür
| Dil anahtarı (alan) | `App.Listform.ListformField.{Alan}` | `App.Listform.ListformField.OrderNo` |
| Route (List) | `/admin/list/{ListFormCode}` — statik `:listFormCode` route'u karşılar, DB'ye kayıt yazılmaz | `/admin/list/App.Mrp.Orders` |
| Route (Custom) | Bileşenin `RoutePath` değeri; route doğrudan bundan üretilir | `/admin/order-board` |
| Wizard seed dosyası | `{WizardName}.json` | `Orders.json` |
| Wizard seed dosyası | `{WizardName}.json`; yeni wizard'ın adı `{ModuleId}_{Ad}` — modül öneki sunucuda eklenir (§4.4 Adım 2) | `Mrp_Orders.json` |
| Custom component seed | `data/App.DeveloperKit.CustomComponents/{Id}.json` — dosya adı satırın `Id`'si, bileşen adı satırın `Name` alanı | `…/8389ccbc-3e7b-….json` |
| CRUD endpoint seed | `crud/{EntityName}.json` | `Mrp_T_Order.json` |
| Liste verisi seed | `data/{ListFormCode}.json` (tek dosya) ya da `data/{ListFormCode}/{anahtar}.json` (klasör) — §9.15 | `App.Definitions.Sequence.json` |
@ -1222,7 +1222,7 @@ saklanır. Dosya okunurken bu alanlar `ListForm`/`Fields` üzerinden geri doldur
| Alan | Anlam |
| --- | --- |
| `ComponentKind` | `0` List (varsayılan) · `1` Custom. Dosyadaki ilk alan olmalı. |
| `WizardName` | Dosya adının (`{WizardName}.json`) ve export zip adının kaynağı. |
| `WizardName` | Dosya adının (`{WizardName}.json`) ve export zip adının kaynağı. Bütün modüllerde tekildir; yeni wizard'da `{ModuleId}_{Ad}` biçimindedir (`ListFormWizardDto.ResolveWizardName`). |
| `SeededAt` | İlk deploy anı; seeder dosyaları bu damgaya göre sıralar. Sunucu üretir, düzenlemede korunur. |
| `ListFormCode` | Ekranın kodu; route, yetki adları ve dil anahtarları bu koddan türer. Dosyada `WizardCode` adıyla, üçüncü sırada yazılır. |
| `MenuCode` | Menü kaydının kodu; yetki adlarının kökü de budur. |
@ -2544,7 +2544,14 @@ Custom yolunda 3–11 adımları **hiç görünmez**; menü + yetki + dil + bile
Bir görünüm bayrağı sonradan kapatılırsa aktif adım listeden düşer, sihirbaz en yakın geçerli
adıma döner.
**Adım 2 — Menü.** `WizardName` her yolda zorunludur. `CreateMenu` anahtarı kapatılırsa menü
**Adım 2 — Menü.** `WizardName` her yolda zorunludur. Wizard adı seed dosyasının adıdır ve bütün
modüllerde tekil olmak zorundadır; aynı ekran adı birden çok modülde geçebilir (İK ve Satınalma
altında `RejectionReason`). Bu yüzden yeni wizard'ın adı modül önekini taşır: `{ModuleId}_{Ad}`
(`Hr_RejectionReason`, `Prc_RejectionReason`, `Inventory_Warehouse`). Önek `MenuParentModuleId`'den
gelir ve deploy'da eklenir (`ListFormWizardDto.ResolveWizardName`); ad zaten önekle başlıyorsa
tekrar eklenmez, önceden üretilmiş öneksiz wizard'ın düzenlemesinde adı korunur. Ekran, menü ve
yetki kodları (`ListFormCode`, `MenuCode`) önekten etkilenmez. Üretim planında ve `replaceFileName`'de
dosya adı önekli yazılır (`deploy_wizard: Prc_RejectionReason.json`). `CreateMenu` anahtarı kapatılırsa menü
alanlarının tamamı gizlenir ve menü/üst menü kaydı üretilmez (alt form ekranı kalıbı, §4.6).
Açıkken zorunlular: üst menü (ağaçtan seçilir ya da yeni kök tanımlanır), `MenuCode`, EN+TR menü
etiketi, izin grubu (listeden seçilir ya da yeni ad; görünen adları EN+TR yazılır), `MenuIcon`

View file

@ -1565,7 +1565,7 @@ liste süzgeci ve çağrı kapısı aynı kuralı kullanır.
| `save_sql_object` | View/procedure/function scriptini `sql/object/{Ad}.sql` ve `postgres/object/{Ad}.sql` dosyalarına yazar (`McpSqlObjectWriter`); veritabanında çalıştırılmaz, nesneyi DB Migrate kurar. Gövde diyalektler arasında güvenle çevrilemediği için iki script de istemciden gelir ve aynı nesneyi tanımlamalıdır (`SqlObjectInvalid`); ad scriptten okunur, başlık `CREATE OR ALTER` / `CREATE OR REPLACE`'e çevrilir. Tablo scriptinin üstüne yazmaz. `.Write` + `App.SqlQueryManager` ister. |
| `generate_crud_endpoints` | SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini (`CrudEndpointSeedFileDto.Generate` — diyaloğun servisi de bunu kullanır) yalnızca `crud/{Entity}.json` dosyasına yazar; kayıtları DB Migrate ekler (`CrudEndpointSeedApplier`). `.Write` + `App.SqlQueryManager.CrudEndpoints` ister. |
| `list_wizards` / `get_wizard` | Wizard File Manager dosyaları; `get_wizard` seed dosyasını diskteki biçimiyle döndürür. |
| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …, "ListForm": …, "Fields": […]}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromDeployJson`'dan geçer: Wizard bölümünde yazılan cevap korunur, yazılmayan ekran alanı `ListForm`/`Fields`'tan tamamlanır, iki bölüm de ekrana katman olarak uygulanır (`WizardScreenOverlay`, üç yönlü: bölümdeki değer wizard'ın kendi ürettiğiyle aynıysa cevap kazanır, farklıysa editör ayarıdır ve korunur; kimlik alanları wizard'dan gelir, tanınmayan/hesaplanan anahtar reddedilir). Böylece MCP, ListForm editörünün yapabildiği her ayarı wizard dosyasına yazar ve `replaceFileName` ile güncelleme editör ayarlarını silmez. Yeni kaydın form varsayılanı kolonun tablodaki sabit `DEFAULT`'udur (katalog ya da seed scripti, `SqlSeedColumn.ConstantValue`; IsActive → true, UomId → Adet, para birimi → TRY tablonun `DEFAULT`'larından gelir); alana özel değer öğenin `DefaultValue`'su ile verilir (`lowcode.instructions.md` §4.3.5.1). Girdide sözleşmede olmayan anahtar adıyla reddedilir, sessizce yok sayılmaz. `StaticData` lookup seçeneklerinin düz metni `App.StaticLookup.*` dil anahtarına çevrilir ve EN + TR satırı yazılır (`LocalizeStaticLookupsAsync`, `lowcode.instructions.md` §4.3.4.1). Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur (`SeedSync*` bayrakları demo veriyle açılmaz; yalnızca istekte açıkça istenirse wizard'ın `SeedSync: true` cevabıyla) — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. |
| `deploy_wizard` | Wizard'ı Wizard ekranıyla aynı yoldan (`IListFormWizardAppService.Create`) deploy eder. Girdi seed dosyası biçimi (`{"Wizard": …, "ListForm": …, "Fields": […]}`) ya da düz cevaplardır; ikisi de `WizardSeedFileDto.FromDeployJson`'dan geçer: Wizard bölümünde yazılan cevap korunur, yazılmayan ekran alanı `ListForm`/`Fields`'tan tamamlanır, iki bölüm de ekrana katman olarak uygulanır (`WizardScreenOverlay`, üç yönlü: bölümdeki değer wizard'ın kendi ürettiğiyle aynıysa cevap kazanır, farklıysa editör ayarıdır ve korunur; kimlik alanları wizard'dan gelir, tanınmayan/hesaplanan anahtar reddedilir). Böylece MCP, ListForm editörünün yapabildiği her ayarı wizard dosyasına yazar ve `replaceFileName` ile güncelleme editör ayarlarını silmez. Yeni kaydın form varsayılanı kolonun tablodaki sabit `DEFAULT`'udur (katalog ya da seed scripti, `SqlSeedColumn.ConstantValue`; IsActive → true, UomId → Adet, para birimi → TRY tablonun `DEFAULT`'larından gelir); alana özel değer öğenin `DefaultValue`'su ile verilir (`lowcode.instructions.md` §4.3.5.1). Girdide sözleşmede olmayan anahtar adıyla reddedilir, sessizce yok sayılmaz. `StaticData` lookup seçeneklerinin düz metni `App.StaticLookup.*` dil anahtarına çevrilir ve EN + TR satırı yazılır (`LocalizeStaticLookupsAsync`, `lowcode.instructions.md` §4.3.4.1). Dosyaya kullanıcının etkin talebi `DevRequest` olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki `isPending` o zamana kadar `true` kalır (model migrate'i beklemez; `verify_wizard` bekleyen dosyada `install` kontrolünde durur). `replaceFileName` doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard `SeedData: true` ile deploy edilir — ekranın `SeedFilePath`'i `data/{ListFormCode}.json` olur (`SeedSync*` bayrakları demo veriyle açılmaz; yalnızca istekte açıkça istenirse wizard'ın `SeedSync: true` cevabıyla) — satırlar aynı çağrıda `seedRows` ile bu dosyaya yazılır (alan adları doğrulanır, `Guid` anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (`lowcode.instructions.md` §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın `Language.Texts` alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (`ValidateWidgetLanguageKeysAsync`). `uploadUrl`'süz `dxImageUpload` alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı `dxImageViewer` alanı da reddedilir — görsel kolonunun editörü her zaman `dxImageUpload`'dır (`ValidateImageColumnLengths`). Yeni wizard'ın adına (= dosya adı) modül öneki eklenir: `{ModuleId}_{Ad}` (`ListFormWizardDto.ResolveWizardName`; aynı ekran adı başka modülde de geçebilir), çıkıştaki `fileName` gerçek adı verir. `MenuOrder` yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır. |
| `delete_wizard` | Wizard Manager'daki silmeyle aynı: `InsertedRecords` + ekranın veri aynası, bağımlı wizard varsa reddedilir. `dropTables: true` ekranın tablosunu (yalnızca tasarımcı/`create_table` ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, `sql/object` ve `crud` dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (`WizardTableCleaner`, `lowcode.instructions.md` §4.5). |
| `verify_wizard` | Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (`AbpPermissions`), List yolunda ekran sorgusu (`GetSelectAsync`, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (`ExportAnalysis` uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; `App.DevRequests` döngüsünün adımı değildir — döngü `Testing`'de biter, kullanıcı elle test edip `Staging`'e alır (`lowcode.instructions.md` §0.11). |
| `start_dev_request` | Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca `Ready to Start` durumundakini `In Progress` durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png|jpeg|gif|webp`) metinden ayıklanır (`McpDevRequestImages`), yerlerinde `[image-N]` kalır ve her biri ayrı MCP resim içeriği (`ImageContentBlock`) olarak döner — model görseli resim olarak görür, görsel hiçbir yere yüklenmez. Sonuç istemcinin 1 MB sınırına sığdırılır: büyük görseller küçültülüp JPEG'e çevrilir (`IMcpImageShrinker`), sığmayan gönderilmez ve metinde bildirilir. `.Write` + `App.DevRequests.Update` ister. |

View file

@ -950,9 +950,10 @@ public class McpDevelopAppService(
return (string.IsNullOrWhiteSpace(json) ? null : WizardSeedFileDto.FromDeployJson(json))
?? throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
}
catch (JsonException)
catch (JsonException ex)
{
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
// Genel mesajla model hangi degerin bozuk oldugunu bilemez ve ayni JSON'u tekrar gonderir.
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizardValue, ex.Path ?? "$", ex.Message]);
}
}

View file

@ -33,6 +33,7 @@ public static class McpErrorCodes
public const string DevGuideSectionNotFound = Prefix + "DevGuideSectionNotFound";
public const string DevGuideSearchMatches = Prefix + "DevGuideSearchMatches";
public const string InvalidWizard = Prefix + "InvalidWizard";
public const string InvalidWizardValue = Prefix + "InvalidWizardValue";
public const string SeedRowsInvalid = Prefix + "SeedRowsInvalid";
public const string SeedRowsNotAllowed = Prefix + "SeedRowsNotAllowed";
public const string SeedRowsUnknownFields = Prefix + "SeedRowsUnknownFields";

View file

@ -218,6 +218,26 @@ public class ListFormWizardDto
return chain;
}
/// <summary>
/// Wizard adi seed dosyasinin adidir ve bütün modüllerde tekil olmak zorundadir; ayni ekran adi
/// birden cok modülde gecebildigi icin (Hr ve Prc altinda RejectionReason) yeni ad modül onekini
/// tasir: <c>{ModuleId}_{Ad}</c>. Duzenlenen wizard'in onekli olmayan eski adi korunur.
/// </summary>
public string ResolveWizardName(string? previousName)
{
var name = WizardName?.Trim() ?? string.Empty;
var moduleId = MenuParentModuleId?.Trim();
if (string.Equals(name, previousName, StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(moduleId) ||
name.StartsWith(moduleId + "_", StringComparison.OrdinalIgnoreCase))
{
return name;
}
return $"{moduleId}_{name}";
}
/// <summary>Wizard'in ListForm kodu; bos gelirse menu kodu ile aynidir.</summary>
public string ResolveListFormCode()
{

View file

@ -128,6 +128,9 @@ public class ListFormWizardAppService(
input.ListFormCode = listFormCode;
input.MenuCode = code;
// Kodlar onekten once cozulur; onek yalnizca dosya adini tekillestirir.
input.WizardName = wizardName = input.ResolveWizardName(previous?.Wizard?.WizardName);
// Ilk deploy zamani wizard'in kimligidir; duzenleme onu degistirmez.
if (previous?.Wizard != null)
input.SeededAt = previous.Wizard.SeededAt;

View file

@ -239,6 +239,12 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
script hatası kullanıcıya bildirilir. Ayrıntı: `lowcode.instructions.md` §4.4, §5.3, §5.6.
21. **Demo veri ≠ seed senkronu** → `SeedData: true` yalnızca `SeedFilePath` açar;
`SeedSyncInsert/Update/Delete` istekte açıkça istenmedikçe `false`'tur (wizard cevabı `SeedSync`).
22. **Wizard adı modül önekli** → yeni wizard'ın adı (= seed dosyasının adı) `{ModuleId}_{Ad}`'dır
(`Prc_RejectionReason.json`, `Hr_RejectionReason.json`); aynı ekran adı başka modülde de geçebildiği
için öneksiz ad dosya çakışması (`'X' adında bir Wizard zaten var`) üretir. Önek deploy'da
`MenuParentModuleId`'den eklenir (`ListFormWizardDto.ResolveWizardName`); analiz ve üretim planında
dosya adı önekli yazılır. Var olan öneksiz wizard düzenlenirken adı değişmez. Ayrıntı:
`lowcode.instructions.md` §4.4 Adım 2.
### Cross-cutting: touching one thing means touching these too

View file

@ -16323,6 +16323,76 @@
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Inventory"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver",
"ParentName": "App.Administration.Definitions",
"DisplayName": "App.Definitions.Approver",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Create",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Update",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Delete",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Export",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Import",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
},
{
"GroupName": "App.Administration",
"Name": "App.Definitions.Approver.Note",
"ParentName": "App.Definitions.Approver",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Adm"
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -92781,6 +92781,132 @@
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.DestinationLocation",
"Value": "Hedef Konum"
},
{
"Id": "b12988e0-9bb1-56d9-ad96-e3a0f5b8027e",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver",
"Value": "Approvers"
},
{
"Id": "43df96d2-6c32-5577-9b7b-6dde369115e5",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver",
"Value": "Onaycılar"
},
{
"Id": "60002287-6449-5d31-83b2-8eabcb816021",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver.Title",
"Value": "Approvers"
},
{
"Id": "239634a5-c96c-5ebf-9cd1-608490546ab1",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver.Title",
"Value": "Onaycılar"
},
{
"Id": "49ea6342-1061-58ca-a160-acbda4a503d9",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver.Desc",
"Value": "Approvers — the single list of who approves what for each department across the application.\nWhere: Administration › Definitions › Approvers.\nFields: department (shown as Branch - Department), approval area (Purchasing / Warehouse / Human Resources / Finance), approver user, active.\nUsed by: approval workflows read the approvers of the record's department and area (e.g. Purchasing › Material Requests department approval uses area Purchasing). Several approvers may be defined for the same department and area; any one of them can approve. Passive rows are ignored by workflows."
},
{
"Id": "3663c5aa-4036-5381-810f-8acee720724c",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Definitions.Approver.Desc",
"Value": "Onaycılar — uygulamadaki tüm onay adımlarında her departman için kimin onaylayacağını tutan tek liste.\nNerede: Yönetim › Tanımlamalar › Onaycılar.\nAlanlar: departman (Şube - Departman olarak gösterilir), onay yeri (Satınalma / Depo / İnsan Kaynakları / Finans), onaylayacak kişi, aktif.\nKullanıldığı yer: onay akışları kaydın departmanı ve onay yerine göre onaycıları buradan okur (ör. Satınalma › Malzeme Talepleri departman onayı Satınalma yerini kullanır). Aynı departman ve onay yerine birden çok onaycı girilebilir; herhangi birinin onayı yeterlidir. Pasif satırlar akışlarda kullanılmaz."
},
{
"Id": "cd7ff644-eefa-516e-8c8c-8b4fa0ec76f8",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Purchase",
"Value": "Purchasing"
},
{
"Id": "03ef0361-7c2b-512d-b310-dd00f96e936d",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Purchase",
"Value": "Satınalma"
},
{
"Id": "e8adf283-fb09-58f3-9b51-72fc0862d993",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Warehouse",
"Value": "Warehouse"
},
{
"Id": "466de337-6d88-5cd9-a511-be7a812292a0",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Warehouse",
"Value": "Depo"
},
{
"Id": "67a7b0d6-b957-5f7c-b9d0-44320b925949",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.HumanResources",
"Value": "Human Resources"
},
{
"Id": "bc378aba-8759-5acb-93bb-0bffac2e8ef3",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.HumanResources",
"Value": "İnsan Kaynakları"
},
{
"Id": "fbceb79e-b476-57d2-ae05-c9e5ec8e2093",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Finance",
"Value": "Finance"
},
{
"Id": "e5a89704-6b60-5476-8610-e527d1636854",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.StaticLookup.Finance",
"Value": "Finans"
},
{
"Id": "1e00f113-afdf-5ffb-a8c0-72ce5976c3ae",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.ApprovalArea",
"Value": "Approval Area"
},
{
"Id": "ba50322d-5b32-5079-8b9f-b354de4a5691",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.ApprovalArea",
"Value": "Onay Yeri"
},
{
"Id": "ad247eac-ef52-5ac1-a9bd-5073f98ba1c0",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.ApproverUser",
"Value": "Approver"
},
{
"Id": "909e87f0-d960-5041-9af7-1e7b6d01b044",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Listform.ListformField.ApproverUser",
"Value": "Onaylayacak Kişi"
}
]
}

View file

@ -2796,6 +2796,17 @@
"RequiredPermissionName": "App.Inventory.OperationType",
"IsDisabled": false,
"ModuleId": "Inventory"
},
{
"ParentCode": "App.Administration.Definitions",
"Code": "App.Definitions.Approver",
"DisplayName": "App.Definitions.Approver",
"Order": 6,
"Url": "/admin/list/App.Definitions.Approver",
"Icon": "FcApproval",
"RequiredPermissionName": "App.Definitions.Approver",
"IsDisabled": false,
"ModuleId": "Adm"
}
]
}

View file

@ -9,7 +9,7 @@
### 1. Amaç ve kapsam
Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve tedarikçi; şirket ya da bireysel) ve onu sınıflandıran **Kontak Etiketleri**. İleride yapılacak Satış ve Satınalma belgeleri (teklif, sipariş, fatura) kontağı bu ekrandan seçer. Kart başlangıçta Satınalma › Tanımlamalar altında `App.Prc.Supplier` olarak üretildi; satış tarafı da kullanacağı için Yönetim modülüne taşındı.
Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve tedarikçi; şirket ya da bireysel), onu sınıflandıran **Kontak Etiketleri** ve uygulamadaki tüm onay adımlarının onaycılarını tek yerde tutan **Onaycılar** listesi. İleride yapılacak Satış ve Satınalma belgeleri (teklif, sipariş, fatura) kontağı bu ekrandan seçer; onay akışları onaycıyı Onaycılar'dan okur. Kart başlangıçta Satınalma › Tanımlamalar altında `App.Prc.Supplier` olarak üretildi; satış tarafı da kullanacağı için Yönetim modülüne taşındı.
### 2. Analiz ve kararlar
@ -27,6 +27,13 @@ Satış ve satınalmanın ortak kullandığı **Kontaklar** kartı (müşteri ve
- Sektör (`SectorId`) Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir. `JobPosition` kolonu tabloda kalır, ekranda yoktur.
- Kontak telefonu (`Phone`) `phoneCountry` biçimindedir: maske seçilen ülkenin kodu ve numara kalıbıyla değişir, `pattern` `^[0-9]{4,15}$`. Ülke alanı olmayan İlgili Kişiler telefonu `phoneGlobal` maskesi ve `^[0-9]{10}$` kuralı taşır; web sitesi `pattern`, e-posta `email` doğrulaması taşır.
Onaylanan analiz (talep "Satınalma Onaycıları"):
- Ekran adı "Onaycılar" (`App.Definitions.Approver`, `Adm_T_Approver`): başlık "Satınalma Onaycıları" olsa da açıklama tüm onayların burada toplanacağını söyler.
- Onaycı departman + onay yeri bazındadır; platform tablosu `Hr_T_Department`'a kolon eklenmez (entity tablosu, çerçeve dışı).
- Onay Yeri seçenekleri: Satınalma (`Purchase`), Depo (`Warehouse`), İnsan Kaynakları (`HumanResources`), Finans (`Finance`) (onaylı).
- Şube kolonu tutulmaz; departmandan gelir. Demo veri yazılmadı (departman/kullanıcı Id'leri kiracıya özgü).
### 3. Menü ve ekranlar
Yönetim (`App.Administration`) › Tanımlamalar (`App.Administration.Definitions`):
@ -38,6 +45,7 @@ Yönetim (`App.Administration`) › Tanımlamalar (`App.Administration.Definitio
| 3 | Kontak Etiketleri | `App.Definitions.ContactTag` | tanım, demo veri |
| 4 | Diziler | `App.Definitions.Sequence` | kod seed'i |
| 5 | Ölçü Kategorileri | `App.Definitions.UomCategory` | kod seed'i |
| 6 | Onaycılar | `App.Definitions.Approver` | tanım; onay akışlarının onaycı kaynağı |
### 4. Veri modeli
@ -47,6 +55,7 @@ Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- `Adm_T_Contact` — kimlik/adres (Country, City, District, Township, PostalCode)/vergi, satış ve satınalma koşulları (`Prc_T_*` tanım tablolarına Id ile), kullanıcılar (`AbpUsers`), para birimi (`Sas_H_Currency.Name`), sektör (`Adm_T_Sector`), dil (`Sas_H_Language.CultureName`), muhasebe alanları, Latitude/Longitude (float), Notes (HTML, nvarchar(MAX)), IsActive.
- `Adm_T_ContactPerson` (ContactId → `Adm_T_Contact.Id`, Photo, Name, JobTitle, Email, Phone, Mobile, Notes)
- `Adm_T_ContactBankAccount` (ContactId → `Adm_T_Contact.Id`, BankName, Iban, AccountHolder, Currency, IsDefault)
- `Adm_T_Approver` (DepartmentId → `Hr_T_Department.Id` FK, ApprovalArea nvarchar(32) DEFAULT 'Purchase', ApproverUserId → `AbpUsers`, IsActive); tekil `TenantId + DepartmentId + ApprovalArea + ApproverUserId`.
### 5. İş kuralları ve hesaplar
@ -55,14 +64,15 @@ Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- Ana form yalnızca kimlik + adres alanlarını taşır. Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe ve Notlar sekmelerinde yalnızca düzenleme açıktır; yeni kontak önce ana formdan eklenir, sekmeler kayıttan sonra açılır.
- IBAN doğrulaması: `^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$` (boşluksuz, büyük harf).
- Yeni kayıtta Tür = Şirket, Grup Teklif Talebi = Sipariş Üzerine, Fatura onayı = 3 doğrulamadan sonra sor varsayılanları tablodan gelir.
- **Onaycılar:** aynı departman ve onay yerine birden çok onaycı girilebilir; akış bunları `|` ile birleştirir ve herhangi birinin onayını kabul eder. Pasif satırlar akışta kullanılmaz. Departman lookup'ı aktif şubelerin departmanlarını "Şube - Departman" olarak, onaycı lookup'ı aktif kullanıcıları ad soyad ile listeler. Yeni satırda Onay Yeri = Satınalma, Aktif = evet.
### 6. Yetki ve görünürlük
Yetki ağacı: `App.Administration` grubu › `App.Administration.Definitions` › `App.Definitions.Contact` (sıra 2) ve `App.Definitions.ContactTag` (sıra 3), her biri + Create/Update/Delete/Export/Import/Note. Alt formların yetkisi `App.Definitions.Contact` altındadır: Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar (7–11), İlgili Kişiler (12), Banka Hesapları (13). `Modules = Sas|Adm`, `MultiTenancySide = Both`. Yetkiler hiçbir role otomatik atanmaz. Widget'lar: Kontaklar ve Kontak Etiketleri'nde Toplam · Aktif · Pasif; alt formlarda widget yok.
Yetki ağacı: `App.Administration` grubu › `App.Administration.Definitions` › `App.Definitions.Contact` (sıra 2), `App.Definitions.ContactTag` (sıra 3) ve `App.Definitions.Approver`, her biri + Create/Update/Delete/Export/Import/Note. Alt formların yetkisi `App.Definitions.Contact` altındadır: Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar (7–11), İlgili Kişiler (12), Banka Hesapları (13). `Modules = Sas|Adm`, `MultiTenancySide = Both`. Yetkiler hiçbir role otomatik atanmaz. Onaycılar'ın yazma yetkisi yalnız yöneticilerde olmalıdır; akışlar tabloyu ekran yetkisi olmadan okur. Widget'lar: Kontaklar, Kontak Etiketleri ve Onaycılar'da Toplam · Aktif · Pasif; alt formlarda widget yok.
### 7. Entegrasyonlar
Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalma tanımları yalnızca okunur.
Dış entegrasyon yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar, Departmanlar, Şubeler ve Satınalma tanımları yalnızca okunur. Onaycılar'ı Satınalma › Malzeme Talepleri departman onayı (`ApprovalArea = 'Purchase'`) okuyacaktır; diğer onay yerleri ilgili modüllerin akışları için hazırdır.
### 8. Akış örneği
@ -71,6 +81,7 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
3. Satınalma sekmesinde Düzenle ile Ödeme Koşulu = 30 Gün seçilir.
4. İlgili Kişiler sekmesinde Addison Olson eklenir; `ContactId` kartın Id'sinden yazılır.
5. Banka Hesapları sekmesinde IBAN girilir ve Varsayılan işaretlenir.
6. Onaycılar'da "Branch1 - Yönetim" departmanı için Onay Yeri = Satınalma, Onaylayacak Kişi = BT eklenir; bu departmanın malzeme talepleri BT'nin onayına düşer.
### 9. Sık sorulanlar
@ -78,13 +89,15 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
- **Kişiler neden listede ayrı satır değil?** Kişi kontak değildir; kartın İlgili Kişiler sekmesindedir.
- **Yeni kontakta Satış/Satınalma sekmeleri neden yok?** Sekmeler kaydın kendisini düzenler; kayıt ana formdan eklendikten sonra görünür.
- **Sekme "Kayıt Bulunamadı" gösteriyor.** Kullanıcının o sekmenin okuma yetkisi yoktur (`App.Definitions.ContactSales` vb.); yetkiler rol ekranından verilir.
- **Aynı kişiyi aynı departmana ikinci kez ekleyemiyorum.** Departman + onay yeri + kişi tekildir; mevcut satırı aktifleştirin.
- **Departmana onaycı tanımlanmadıysa ne olur?** Onaycı okuyan akış kaydı durdurur; boş onaycı herkese onay yetkisi verirdi.
### 10. Dosyalar ve geri alma
- Tablolar: `sql/object/Adm_T_Contact.sql`, `Adm_T_ContactPerson.sql`, `Adm_T_ContactBankAccount.sql`, `Adm_T_ContactTag.sql`.
- Wizard'lar: `ContactTag.json`, `ContactPerson.json`, `ContactBankAccount.json`, `ContactSales.json`, `ContactPurchasing.json`, `ContactFinancial.json`, `ContactAccounting.json`, `ContactNotes.json`, `Contact.json`.
- Tablolar: `sql/object/Adm_T_Contact.sql`, `Adm_T_ContactPerson.sql`, `Adm_T_ContactBankAccount.sql`, `Adm_T_ContactTag.sql`, `Adm_T_Approver.sql`.
- Wizard'lar: `ContactTag.json`, `ContactPerson.json`, `ContactBankAccount.json`, `ContactSales.json`, `ContactPurchasing.json`, `ContactFinancial.json`, `ContactAccounting.json`, `ContactNotes.json`, `Contact.json`, `Approver.json`.
- Demo veri: `data/App.Definitions.{ContactTag,Contact}.json`.
- Geri alma: önce `Contact.json`, sonra alt formlar ve `ContactTag.json` `delete_wizard` ile silinir; tablolar yalnızca açık onayla `dropTables: true` ile düşürülür.
- Geri alma: önce `Contact.json`, sonra alt formlar ve `ContactTag.json` `delete_wizard` ile silinir; `Approver.json` bağımsızdır ama onu okuyan akışlar önce kaldırılmalıdır. Tablolar yalnızca açık onayla `dropTables: true` ile düşürülür.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
@ -156,6 +169,30 @@ Yok. Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör, Kullanıcılar ve Satınalm
| `Color` | Renk | dxColorBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
#### Onaycılar — `App.Definitions.Approver`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Approver.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Administration.Definitions` › `App.Definitions.Approver` (sıra 6) |
| Yetki | `App.Definitions.Approver` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Onaycılar — uygulamadaki tüm onay adımlarında her departman için kimin onaylayacağını tutan tek liste. |
| Geliştirme talebi | Satınalma Onaycıları |
| Kaynak | `Adm_T_Approver` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Form varsayılanları | ApprovalArea=Purchase, IsActive=true |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `DepartmentId` | Departman | dxSelectBox | ✓ | ✓ | sorgu: Hr_T_Department |
| `ApprovalArea` | Onay Yeri | dxSelectBox | ✓ | ✓ | liste: Purchase, Warehouse, HumanResources, Finance |
| `ApproverUserId` | Onaylayacak Kişi | dxSelectBox | ✓ | ✓ | sorgu: AbpUsers |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
#### İlgili Kişiler — `App.Definitions.ContactPerson`
| Özellik | Değer |

View file

@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS public."Adm_T_Approver"
(
"Id" uuid NOT NULL DEFAULT gen_random_uuid(),
"TenantId" uuid NULL,
"DepartmentId" uuid NOT NULL,
"ApprovalArea" varchar(32) NOT NULL DEFAULT 'Purchase',
"ApproverUserId" uuid NOT NULL,
"IsActive" boolean NOT NULL DEFAULT TRUE,
"CreationTime" timestamp NOT NULL DEFAULT (NOW() AT TIME ZONE 'UTC'),
"CreatorId" uuid NULL,
"LastModificationTime" timestamp NULL,
"LastModifierId" uuid NULL,
"IsDeleted" boolean NOT NULL DEFAULT FALSE,
"DeletionTime" timestamp NULL,
"DeleterId" uuid NULL,
CONSTRAINT "PK_Adm_T_Approver" PRIMARY KEY ("Id")
);
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'UQ_Adm_T_Approver_TenantId_DepartmentId_ApprovalArea_ApproverUserId') THEN
ALTER TABLE public."Adm_T_Approver" ADD CONSTRAINT "UQ_Adm_T_Approver_TenantId_DepartmentId_ApprovalArea_ApproverUserId" UNIQUE ("TenantId", "DepartmentId", "ApprovalArea", "ApproverUserId");
END IF;
END
$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'FK_Adm_T_Approver_DepartmentId') THEN
ALTER TABLE public."Adm_T_Approver" ADD CONSTRAINT "FK_Adm_T_Approver_DepartmentId" FOREIGN KEY ("DepartmentId") REFERENCES public."Hr_T_Department" ("Id") ON DELETE NO ACTION ON UPDATE NO ACTION;
END IF;
END
$$;

View file

@ -0,0 +1,36 @@
IF OBJECT_ID(N'[dbo].[Adm_T_Approver]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Adm_T_Approver]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[DepartmentId] uniqueidentifier NOT NULL,
[ApprovalArea] nvarchar(32) NOT NULL DEFAULT 'Purchase',
[ApproverUserId] uniqueidentifier NOT NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Adm_T_Approver] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
IF OBJECT_ID(N'[dbo].[UQ_Adm_T_Approver_TenantId_DepartmentId_ApprovalArea_ApproverUserId]', 'UQ') IS NULL
BEGIN
ALTER TABLE [dbo].[Adm_T_Approver] ADD CONSTRAINT [UQ_Adm_T_Approver_TenantId_DepartmentId_ApprovalArea_ApproverUserId] UNIQUE NONCLUSTERED ([TenantId] ASC, [DepartmentId] ASC, [ApprovalArea] ASC, [ApproverUserId] ASC);
END
GO
IF OBJECT_ID(N'[dbo].[FK_Adm_T_Approver_DepartmentId]', 'F') IS NULL
BEGIN
ALTER TABLE [dbo].[Adm_T_Approver] ADD CONSTRAINT [FK_Adm_T_Approver_DepartmentId] FOREIGN KEY ([DepartmentId]) REFERENCES [dbo].[Hr_T_Department] ([Id]) ON DELETE NO ACTION ON UPDATE NO ACTION;
END
GO

File diff suppressed because it is too large Load diff