EditorScript düzenlemeleri
This commit is contained in:
parent
dea283e7cf
commit
1ede045bac
38 changed files with 837 additions and 447 deletions
49
.github/instructions/lowcode.instructions.md
vendored
49
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -546,7 +546,9 @@ eklenen dosya) bu satırlar ekranla birlikte seed edilir; migrate çalıştığ
|
|||
|
||||
1. `deploy_wizard` cevabına `SeedData: true` yaz, satırların hepsini aynı çağrıda `seedRows` ile
|
||||
gönder (alan adından değere nesne dizisi). Ekranın `SeedFilePath`'i `data/{ListFormCode}.json`
|
||||
olur, `SeedSyncInsert/Update/Delete` açılır (§9.15); satırlar deploy anında bu dosyaya yazılır.
|
||||
olur; satırlar deploy anında bu dosyaya yazılır. `SeedSyncInsert/Update/Delete` (§9.15) demo veriyle
|
||||
**açılmaz** — varsayılan `false`'tur; yalnızca istek "ekrandan girilen kayıtlar seed dosyasına
|
||||
yansısın" diyorsa wizard cevabına `SeedSync: true` yazılır (wizard ekranında da aynı kural).
|
||||
`Guid` anahtarı sunucu üretir; `TenantId` ve audit alanlarını migrate doldurur — yazma. Aynı
|
||||
içerikli satır dosyada varsa (tekrar deploy) yeniden eklenmez.
|
||||
2. DB Migrate'i bekleme. Migrate wizard seeder ile ekranı kurar, ardından `ListFormSeedDataApplier`
|
||||
|
|
@ -1275,7 +1277,8 @@ atanmaz (admin dahil); ekranı görecek roller yetkiyi rol ekranından alır.
|
|||
| --- | --- |
|
||||
| `IsTenant` / `IsBranch` / `IsOrganizationUnit` | `[ListForm]` Otomatik kırılım filtresi. Tabloda `TenantId` varsa `IsTenant: true` **olmalı**. |
|
||||
| `AllowAdding` / `AllowUpdating` / `AllowDeleting` / `ConfirmDelete` | `[ListForm]` Toolbar ve satır aksiyonları (`EditingOptionDto`). |
|
||||
| `SeedData` | `[ListForm]` `true` ise `SeedFilePath = data/{ListFormCode}.json` ve `SeedSyncInsert/Update/Delete` açık üretilir; ekrandan girilen satır dosyaya yazılır, migrate geri yükler. Wizard ekranında sorulmaz; demo veri taşıyan MCP üretimi için (§0.10.1). Varsayılan `false`. |
|
||||
| `SeedData` | `[ListForm]` `true` ise `SeedFilePath = data/{ListFormCode}.json` üretilir; migrate dosyadaki satırları yükler. Wizard ekranında sorulmaz; demo veri taşıyan MCP üretimi için (§0.10.1). Varsayılan `false`. |
|
||||
| `SeedSync` | `[ListForm]` `SeedData` açıkken `SeedSyncInsert/Update/Delete`'i açar: ekrandan eklenen/değiştirilen/silinen kayıt dosyaya yansır. **Varsayılan `false`**; demo veri bunu açmaz, yalnızca istekte açıkça istenirse `true` yazılır. Okurken üç bayraktan biri açıksa `true`; tek tek ayar `ListForm` bölümünde yapılır. |
|
||||
| `AllowDetail` | `[ListForm]` Satırda **Detay** düğmesi çıkar; `/admin/form/{ListFormCode}/{anahtar}` adresini yeni sekmede açar — aynı ekranın tek kayıtlık form görünümüdür. `KeyFieldName` boşsa çalışmaz. **`SubForms` doluysa cevaptan bağımsız `true` üretilir** (`ListFormWizardDto.BuildListForm`): wizard alt formları `Form` tipinde kurar ve sekmeler yalnızca Detay görünümünde açılır. Elle yazılan wizard dosyasında da alt formu olan ekranın `EditingOptionDto.AllowDetail` değeri `true` yazılır. **Alt formu olmayan listede `false` kalır** — MCP/AI üretiminde Detay düğmesi yalnızca `SubForms` taşıyan listeye açılır; kullanıcı açıkça istemedikçe tek tablolu liste ya da alt form ekranının kendisi (`PerformancePeriodSkill` gibi) Detay almaz. |
|
||||
| `DefaultLayout` | `[ListForm]` `"grid"`, `"card"`, `"pivot"`, `"chart"`, `"tree"`, `"gantt"`, `"scheduler"`, `"todo"`. |
|
||||
| `Grid` `Card` `Pivot` `Chart` `Tree` `Gantt` `Scheduler` `Todo` | `[ListForm]` Hangi görünümlerin açık olduğu (`LayoutDto`). **Liste ekranında `Grid`/`Card`/`Pivot`/`Chart` varsayılan `true`**, diğer dördü `false` (§9.8). Açtığın her görünümün option bloğunu da doldur. |
|
||||
|
|
@ -1876,6 +1879,11 @@ bölümlerine düşer (§4.1). `SeededAt` ilk deploy anında sunucu tarafından
|
|||
> tutarlı** olmalıdır: açılışta çalışan bir kural varsa (hesap tarifleri varsayılan `both`)
|
||||
> `// @runOnOpen` satırı gelir ve metin literalleri çift tırnaklıdır (`JSON.stringify`). Elle yazmak yerine C# tarafında `EditorScript.Build(...)` kullan ya da
|
||||
> Script Builder'da üret; uyuşmazsa dialog script'i "elle düzenlenmiş" sayar.
|
||||
> **Wizard/MCP deploy'u script'i kurallardan yeniden üretir** (`EditorScript.Rebuild`): `// @builder`
|
||||
> başlıklı script'in gövdesi atılıp kurallardan yazılır; `EditorScript` alanına yalnızca kural JSON'u
|
||||
> (`{"v":1,"rules":[…]}`) da verilebilir. MCP script'i elle yazmaz, kuralları verir — elle yazılan
|
||||
> gövde (yanlış yardımcı kullanımı) çalışmayan script üretiyordu. Başlıksız (tamamen elle yazılmış)
|
||||
> script olduğu gibi kalır.
|
||||
|
||||
### 4.4.1 Deploy'un ve seeder'ın senin yerine ürettikleri — cevaba yazma
|
||||
|
||||
|
|
@ -2658,7 +2666,7 @@ bağlıdır.
|
|||
| calc | `days` | start, end, target | `set('T', days('Bas', 'Bit'))` — bitiş günü **dahil** |
|
||||
| calc | `hours` | start, end, target | `set('T', hours('Bas', 'Bit'))` — bitiş küçükse ertesi güne taşar |
|
||||
| data | `copy` | source (seçili kayıttaki kolon/yol), target | `copy('unitPrice', 'UnitPrice')` |
|
||||
| data | `setValue` | target, text | `set('T', tpl('{Musteri} - {value}'))` |
|
||||
| data | `setValue` | target, text | `set('T', tpl('{Musteri} - {value}'))`; `text` `true`/`false` ise mantıksal sabit yazılır: `set('T', false)` — onay kutusuna metin `'false'` dolu sayılırdı. Runtime da mevcut değeri mantıksal olan alana gelen `'true'/'false'/'1'/'0'`'ı mantıksala çevirir |
|
||||
| data | `clear` | sources[] | `clear('Il', 'Ilce')` |
|
||||
| view | `readOnly` | target, invert? | `readOnly('Alan', <koşul>)` · invert → `readOnly('Alan', !(<koşul>))` · koşulsuz → `readOnly('Alan', true)` |
|
||||
| interaction | `notify` | message | `notify('Limit aşıldı')` — mesajda `{Alan}` şablonu geçerli |
|
||||
|
|
@ -2726,14 +2734,14 @@ Script `AsyncFunction` ile derlenir, aşağıdaki adlar parametre olarak enjekte
|
|||
| `data` | Script'in üzerinde çalıştığı canlı form verisi (kopya) |
|
||||
| `isReady` | `true` ise script form açılışında çalışıyor |
|
||||
| `get('Alan')` | Form verisinden okur. **Nokta yolu** (`Musteri.Unvan`) ve **büyük/küçük harf duyarsız** eşleşme destekler (SQL kolon adları tutarsız gelebiliyor) |
|
||||
| `num()` · `str()` · `bool()` | Tip dönüşümü. `bool` için `true`/`'true'`/`1`/`'1'` doğrudur |
|
||||
| `num()` · `str()` · `bool()` | **Alan adı** alır, değeri değil: `str('ProductType')`. `str(get('X'))` ya da `str(value)` değeri alan adı sanıp boş döner — koşul hiç tutmaz. Değişen alanın değeri için `str(field)` ya da doğrudan `value` kullanılır. `bool` için `true`/`'true'`/`1`/`'1'` doğrudur |
|
||||
| `dateOf('Alan')` | `Date` ya da `null` |
|
||||
| `pick(nesne, 'a.b')` | Herhangi bir nesneden yol okuma |
|
||||
| `set('Alan', deger)` · `set({A:1, B:2})` | Yazar. Değişmeyen değer yazılmaz; yazmalar biriktirilip **tek flush** ile forma ve düzenlenen grid hücresine uygulanır |
|
||||
| `clear('A','B')` | Verilen alanları `null` yapar |
|
||||
| `copy('kolon','Alan')` | Lookup/GridBox'ta **seçili kayıttan** taşır; hedef verilmezse yolun son parçası hedef olur. Seçili kayıt, olay formdan gelse de (popup form, form sayfası) değişen alanın editöründen okunur; lookup sorgusu taşınacak kolonları döndürmelidir (ör. `SELECT Id AS [Key], …, DepartmentId, JobPositionId`) |
|
||||
| `selected('Yol')` · `snum()` · `sstr()` | Seçili kaydın tamamı / sayı / metin hâli |
|
||||
| `readOnly('Alan', kosul)` | Formdaki **herhangi bir** editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil). Grid popup'ında açılış çalışması (`isReady`) editörler her yeniden çizildiğinde tekrarlanır ve form verisi o anda güncel değildir; koşul başka bir alana bakıyorsa değer `editor.component.cellValue(satır, 'Alan')` ile okunur (örnek: `App.Hr.md` › İzin Yönetimi §5.1) |
|
||||
| `readOnly('Alan', kosul)` | Formdaki **herhangi bir** editörü kilitler/açar (yalnızca script'in bağlı olduğu alanı değil). Grid popup'ında açılış çalışması (`isReady`) form her yeniden çizildiğinde (her `cellValue` yazımı) tekrarlanır; veri grid'in düzenlenen satırından güncel okunur (`getEditingRow`), `str('Alan')` yeterlidir |
|
||||
| `round(x, 2)` · `sum('A','B')` | Matematik |
|
||||
| `days('Bas','Bit')` · `hours('Bas','Bit')` | Gün farkı (bitiş dahil) · saat farkı (negatifse +24) |
|
||||
| `tpl('{Alan} - {value}')` | Şablon: `{Alan}`, `{value}`, `{selected.Yol}` |
|
||||
|
|
@ -2750,6 +2758,37 @@ Eski (uzun) script'ler bozulmasın diye geriye dönük uyum vardır: script içi
|
|||
(`formData, e, editor, runtimeSetEditorReadOnly, setFormData`) derlenir ve **yardımcılar
|
||||
tanımsız kalır**. Yardımcı adlarını yeniden tanımlama.
|
||||
|
||||
**Zincir ve kalıcı readOnly (Grid/Tree popup).** Script'in `set` ile yazdığı alanın kendi script'i de
|
||||
çalışır — kullanıcı o alanı değiştirmiş gibi (`runChainedScripts`; derinlik sınırı yoktur, zincir yazılan kadar ilerler; `ProductType` →
|
||||
`TrackInventory = false` → `TrackingMethod` salt okunur). Grid'e `cellValue` ile yazılan değer editörün
|
||||
değişiklik olayını tetiklemediği için zincir olmadan bağımlı kural ancak kullanıcı alanı elle değiştirince
|
||||
çalışırdı. Yazma popup formunu yeniden çizer (`editForm.repaint`) ve açılış script'leri yeniden çalışır;
|
||||
formun `formData`'sı açılıştaki nesne olduğundan veri grid'in düzenlenen satırından okunur
|
||||
(`getEditingRow`, `list/shared/editingForm.ts` — yeni satırın anahtarı nesnedir, `row.key ===` eşleşmez).
|
||||
Eski veriyle çalışan script kendi `readOnly` kararını geri alırdı. Popup formu
|
||||
`getController('editing')._editForm`'dadır (`getActiveEditingForm`). Form sayfası `updateData` ile alan
|
||||
olayını zaten tetikler.
|
||||
|
||||
Zincirde bir alanın aynı değerle ikinci kez tetiklenmesi döngüdür (A → B → A) ve durdurulur; değeri her turda
|
||||
değişen döngüyü 1000 adımlık emniyet sınırı yakalar. İkisi de kullanıcıya hata olarak gösterilir.
|
||||
|
||||
**Hatalı script kullanıcıya gösterilir** — derleme ya da çalışma hatası konsola düşmekle kalmaz, ekranda
|
||||
`App.EditorScript.Error` bildirimi çıkar (alan adı + hata; `reportEditorScriptError`,
|
||||
`utils/editorScriptRuntime.ts`). Form her yeniden çizildiğinde aynı hata 5 saniyede bir kez gösterilir.
|
||||
|
||||
**Derlenemeyen script hiç çalışmaz** — bir satırdaki söz dizimi hatası (eksik parantez) diğer kuralları da
|
||||
durdurur. Script Builder kaydetmeden önce metni derler ve hatada kaydetmez (`App.ScriptBuilder.SyntaxError`).
|
||||
|
||||
**Script Builder ile MCP aynı kodu üretir.** Kod `// @builder` başlığındaki kurallardan üretilir; MCP/wizard
|
||||
deploy'u gövdeyi atıp kuralları `EditorScript.Rebuild` ile yeniden üretir (C# çıktısı `scriptRecipes.ts` ile
|
||||
birebir). Onay kutusu/anahtar alanına değer yazan `setValue` kuralında dialog serbest metin yerine Evet/Hayır
|
||||
sectirir (`getParamChoices`); deploy aynı alana gelen `0`/`1`/`evet`/`hayır` değerini `true`/`false`'a çevirir.
|
||||
Kod editöründe kurallarla eşleşmeyecek şekilde elle değiştirilen script'te dialog uyarır
|
||||
(`App.ScriptBuilder.StaleHeader`) ve kaydederken bayat başlığı siler; başlık kalsaydı deploy gövdeyi başlıktan
|
||||
yeniden üretip elle yapılan düzeltmeyi geri alırdı.
|
||||
`readOnly` tarifinde `invert: "true"` koşulu tersler: "koşul sağlanmıyorsa salt okunur". "X seçilince kilitle"
|
||||
için `invert` boş bırakılır.
|
||||
|
||||
### 5.7 Yetenek sınırları — script ile yapılamayanlar
|
||||
|
||||
- **Alan gizleme yok.** Runtime'da `visible` yardımcısı yoktur; koşullu görünürlük yerine
|
||||
|
|
|
|||
|
|
@ -1563,7 +1563,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 ve `SeedSync*` bayrakları açılır — 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`). `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. |
|
||||
|
|
|
|||
|
|
@ -950,9 +950,9 @@ public class McpDevelopAppService(
|
|||
return (string.IsNullOrWhiteSpace(json) ? null : WizardSeedFileDto.FromDeployJson(json))
|
||||
?? throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
catch (JsonException)
|
||||
{
|
||||
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizardJson, ex.Message]);
|
||||
throw new UserFriendlyException(L[McpErrorCodes.InvalidWizard]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public sealed class McpDevelopTools(IMcpDevelopAppService developAppService, IMc
|
|||
=> await developAppService.GetWizardAsync(fileName);
|
||||
|
||||
[McpServerTool(Name = McpToolNames.DeployWizard, Destructive = true, Idempotent = false)]
|
||||
[Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari ve menusu seed dosyalarina yazilir. Deploy veritabanina yazmaz: ekran, menu ve yetkiler kullanici Wizard Manager'da DB Migrate calistirinca olusur; cikistaki isPending true iken ekran henuz yoktur; migrate'i bekleme, isini bitir ve kullaniciya yalnizca DB Migrate calistirmasini soyle. Eksik modul, ust menu, izin grubu, yetki ve dil satirlari da deploy'da dosyalara yazilir ve migrate onlari dogru sirayla ekler; bunlarin veritabaninda olmamasi beklenen durumdur, uyari olarak raporlama ve kullanicidan elle eklemesini isteme. Ust menu zinciri istenen derinlikte kurulur: MenuParentCode ekranin dogrudan ust menusu, Menu.MenuParentPath onun ustundeki seviyeler (kokten asagi, her biri {Code, Icon, En, Tr}); eksik her seviye bir ustunun altina kurulur, var olana baglanilir. Liste ekraninda Grid, Card, Pivot ve Chart gorunumleri varsayilan aciktir (DefaultLayout grid); kullanici bir gorunumu istemedigini soylemedikce bu bayraklari false yazma. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. Istek ornek/demo veri tasiyorsa (prompt'taki tablo/liste ya da ekran goruntusunde gorunen satirlar) SeedData true yaz ve satirlarin hepsini ayni cagrida seedRows ile gonder: satirlar ekranin data/{ListFormCode}.json dosyasina yazilir, migrate ekrani kurduktan sonra ekler. Satirlar icin migrate'i bekleyip insert_row kullanma. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir. Wizard cevaplarinin uretmedigi ekran ayarlari (FormFieldsDefaultValueDto/InsertFieldsDefaultValueDto gibi varsayilan degerler, ValidationRuleDto, Format, Alignment, Width, Visible, BandName, ozet/gruplama, ExtraFilter, Insert/Update/Delete komutlari, PageSize, CommandColumnDto …) ayni JSON'un ListForm ve Fields bolumlerine get_wizard ciktisindaki adlarla yazilir. Yeni kaydin form degeri (Aktif = true, Birim = Adet, Kur = TRY, Sure = 60) ayri yazilmaz: deploy kolonun tablodaki sabit DEFAULT degerini okur; tablodan farkli deger gerekiyorsa alan ogesinde DefaultValue yaz (Groups[].Items[].DefaultValue). StaticData lookup seceneklerinde (RadioGroup, SelectBox, TagBox) name dil anahtaridir (App.StaticLookup.{Key}); EN + TR metni bilinen secenegi Language.Texts'te ver, duz metin yazilirsa deploy onu anahtara cevirip TR'yi metinden yazar (get_dev_guide 4.3.4.1). Wizard girdisinde taninmayan anahtar adiyla reddedilir, baska adla tekrar deneme; bolumler kismi olabilir (yalnizca degisen anahtarlar), Fields ogeleri FieldName ile eslesir, *Json adlari (FormFieldsDefaultValueJson) *Dto karsiligina cevrilir. Guncellemede get_wizard ciktisini butunuyle degistirip gonder: tasarimci ayarlari korunur; Wizard bolumunde yazdigin cevap ayni ayarin ListForm'daki eski degerine ustun gelir. Kimlik alanlari (ListFormCode, Name, Title, Description, PermissionDto, FieldName) wizard'dan gelir. Fields'taki her alan Wizard Groups'ta da bulunmalidir; tanınmayan ya da hesaplanan bir anahtar reddedilir. Liste ekraninda DescTr ve DescEn ayrintili olmali (en az 3 satir, 150 karakter): ekran basligindaki bilgi simgesinde gosterilir; ne ise yaradigi, menude nerede ya da hangi ekranin sekmesi oldugu, ana alanlar, otomatik hesaplar ve iliskili ekranlar yazilir (get_dev_guide 4.4.2); kisa aciklama reddedilir. Deploy modulun dokumanini (docs/modules) acar ya da envanterini yeniler; elle yazilan bolumleri save_module_doc ile doldur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")]
|
||||
[Description("Wizard'i deploy eder: ListForm ekrani (ComponentKind 0) ya da bir custom component'i menuye baglayan kayit (ComponentKind 1), yetkileri, dil anahtarlari ve menusu seed dosyalarina yazilir. Deploy veritabanina yazmaz: ekran, menu ve yetkiler kullanici Wizard Manager'da DB Migrate calistirinca olusur; cikistaki isPending true iken ekran henuz yoktur; migrate'i bekleme, isini bitir ve kullaniciya yalnizca DB Migrate calistirmasini soyle. Eksik modul, ust menu, izin grubu, yetki ve dil satirlari da deploy'da dosyalara yazilir ve migrate onlari dogru sirayla ekler; bunlarin veritabaninda olmamasi beklenen durumdur, uyari olarak raporlama ve kullanicidan elle eklemesini isteme. Ust menu zinciri istenen derinlikte kurulur: MenuParentCode ekranin dogrudan ust menusu, Menu.MenuParentPath onun ustundeki seviyeler (kokten asagi, her biri {Code, Icon, En, Tr}); eksik her seviye bir ustunun altina kurulur, var olana baglanilir. Liste ekraninda Grid, Card, Pivot ve Chart gorunumleri varsayilan aciktir (DefaultLayout grid); kullanici bir gorunumu istemedigini soylemedikce bu bayraklari false yazma. Hedef tablo onceden var olmalidir; yoksa once create_table ile olustur. Istek ornek/demo veri tasiyorsa (prompt'taki tablo/liste ya da ekran goruntusunde gorunen satirlar) SeedData true yaz ve satirlarin hepsini ayni cagrida seedRows ile gonder: satirlar ekranin data/{ListFormCode}.json dosyasina yazilir, migrate ekrani kurduktan sonra ekler. SeedData ekrandan girilen kayitlari dosyaya yansitmaz (SeedSyncInsert/Update/Delete kapali); bunu yalnizca istek acikca isterse SeedSync true yaz. Satirlar icin migrate'i bekleyip insert_row kullanma. replaceFileName verilirse o wizard'in dosyalari yeniden yazilir, onceki kayitlarin yerini migrate'te yenileri alir. Wizard cevaplarinin uretmedigi ekran ayarlari (FormFieldsDefaultValueDto/InsertFieldsDefaultValueDto gibi varsayilan degerler, ValidationRuleDto, Format, Alignment, Width, Visible, BandName, ozet/gruplama, ExtraFilter, Insert/Update/Delete komutlari, PageSize, CommandColumnDto …) ayni JSON'un ListForm ve Fields bolumlerine get_wizard ciktisindaki adlarla yazilir. Yeni kaydin form degeri (Aktif = true, Birim = Adet, Kur = TRY, Sure = 60) ayri yazilmaz: deploy kolonun tablodaki sabit DEFAULT degerini okur; tablodan farkli deger gerekiyorsa alan ogesinde DefaultValue yaz (Groups[].Items[].DefaultValue). StaticData lookup seceneklerinde (RadioGroup, SelectBox, TagBox) name dil anahtaridir (App.StaticLookup.{Key}); EN + TR metni bilinen secenegi Language.Texts'te ver, duz metin yazilirsa deploy onu anahtara cevirip TR'yi metinden yazar (get_dev_guide 4.3.4.1). EditorScript'i elle yazma: Script Builder kurallarini ({v:1, rules:[...]}, get_dev_guide 5.3) ver, deploy kodu Script Builder dialogu ile birebir ayni uretir; govde yazsan da atilir. Onay kutusu alanina setValue text 'true'/'false' (0/1 cevrilir). set ile yazilan alanin kurallari da sirayla calisir (derinlik siniri yok); A->B->A dongusu kuran kural yazma, calisma aninda hata olarak gosterilir. Wizard girdisinde taninmayan anahtar adiyla reddedilir, baska adla tekrar deneme; bolumler kismi olabilir (yalnizca degisen anahtarlar), Fields ogeleri FieldName ile eslesir, *Json adlari (FormFieldsDefaultValueJson) *Dto karsiligina cevrilir. Guncellemede get_wizard ciktisini butunuyle degistirip gonder: tasarimci ayarlari korunur; Wizard bolumunde yazdigin cevap ayni ayarin ListForm'daki eski degerine ustun gelir. Kimlik alanlari (ListFormCode, Name, Title, Description, PermissionDto, FieldName) wizard'dan gelir. Fields'taki her alan Wizard Groups'ta da bulunmalidir; tanınmayan ya da hesaplanan bir anahtar reddedilir. Liste ekraninda DescTr ve DescEn ayrintili olmali (en az 3 satir, 150 karakter): ekran basligindaki bilgi simgesinde gosterilir; ne ise yaradigi, menude nerede ya da hangi ekranin sekmesi oldugu, ana alanlar, otomatik hesaplar ve iliskili ekranlar yazilir (get_dev_guide 4.4.2); kisa aciklama reddedilir. Deploy modulun dokumanini (docs/modules) acar ya da envanterini yeniler; elle yazilan bolumleri save_module_doc ile doldur. Ne yazacagini get_dev_guide 4. bolumunden ogren.")]
|
||||
public async Task<WizardFileInfoDto?> DeployWizardAsync(
|
||||
[Description("Wizard JSON'u: seed dosyasi bicimi ({\"Wizard\": {...}, \"ListForm\": {...}, \"Fields\": [...]}, get_wizard cikisi gibi) ya da duz wizard cevaplari. ListForm/Fields istege baglidir ve kismi olabilir. Enum alanlari sayi olarak yazilir.")]
|
||||
string wizard,
|
||||
|
|
|
|||
|
|
@ -100,12 +100,19 @@ public class ListFormWizardDto
|
|||
public bool ConfirmDelete { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ekranin verisi seed klasorundeki <c>data/{ListFormCode}.json</c> dosyasina aynalanir
|
||||
/// (<c>SeedFilePath</c> + ekle/guncelle/sil senkronizasyonu). Istekle gelen ornek/demo satirlar
|
||||
/// ekrandan girildiginde dosyaya yazilir ve migrate ile geri yuklenir.
|
||||
/// Ekranin demo verisi seed klasorundeki <c>data/{ListFormCode}.json</c> dosyasindan migrate ile
|
||||
/// yuklenir (<c>SeedFilePath</c>). Ekrandan yapilan kayitlarin dosyaya yansimasi ayri karardir
|
||||
/// (<see cref="SeedSync"/>).
|
||||
/// </summary>
|
||||
public bool SeedData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ekrandan yapilan ekle/guncelle/sil islemleri veri dosyasina yansisin mi (<c>SeedSyncInsert/Update/Delete</c>).
|
||||
/// Demo veri (<see cref="SeedData"/>) bunu acmaz: varsayilan kapali, yalnizca istekte acikca
|
||||
/// istenirse true yazilir; aksi halde kullanicinin girdigi her kayit seed dosyasina karisirdi.
|
||||
/// </summary>
|
||||
public bool SeedSync { get; set; }
|
||||
|
||||
// Liste ekranı varsayılan olarak dört analiz görünümüyle gelir (UI wizard ve LayoutDto ile aynı);
|
||||
// MCP/prompt ile gelen cevapta bayrak yazılmazsa görünüm sessizce kapanmasın diye.
|
||||
public string DefaultLayout { get; set; } = "grid";
|
||||
|
|
@ -279,9 +286,9 @@ public class ListFormWizardDto
|
|||
// Query'de SelectCommand tablo adi degildir; silme komutu elle yazilir.
|
||||
DeleteCommand = isDeletedField && SelectCommandType != SelectCommandTypeEnum.Query ? WizardConsts.DefaultDeleteCommand(SelectCommand) : null,
|
||||
SeedFilePath = SeedData ? WizardConsts.SeedDataFilePath(listFormCode) : null,
|
||||
SeedSyncInsert = SeedData,
|
||||
SeedSyncUpdate = SeedData,
|
||||
SeedSyncDelete = SeedData,
|
||||
SeedSyncInsert = SeedData && SeedSync,
|
||||
SeedSyncUpdate = SeedData && SeedSync,
|
||||
SeedSyncDelete = SeedData && SeedSync,
|
||||
};
|
||||
|
||||
// Varsayilanlar WizardConsts uzerinden JSON olarak kurulur; DTO'nun nesne karsiliklari
|
||||
|
|
@ -450,6 +457,7 @@ public class ListFormWizardDto
|
|||
IsBranch = listForm.IsBranch;
|
||||
IsOrganizationUnit = listForm.IsOrganizationUnit;
|
||||
SeedData = !string.IsNullOrWhiteSpace(listForm.SeedFilePath);
|
||||
SeedSync = listForm.SeedSyncInsert || listForm.SeedSyncUpdate || listForm.SeedSyncDelete;
|
||||
|
||||
var layout = listForm.LayoutDto;
|
||||
DefaultLayout = layout.DefaultLayout;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public class WizardSeedFileDto
|
|||
[
|
||||
nameof(ListFormWizardDto.IsTenant), nameof(ListFormWizardDto.IsBranch), nameof(ListFormWizardDto.IsOrganizationUnit),
|
||||
nameof(ListFormWizardDto.AllowAdding), nameof(ListFormWizardDto.AllowUpdating), nameof(ListFormWizardDto.AllowDeleting),
|
||||
nameof(ListFormWizardDto.AllowDetail), nameof(ListFormWizardDto.ConfirmDelete), nameof(ListFormWizardDto.SeedData),
|
||||
nameof(ListFormWizardDto.AllowDetail), nameof(ListFormWizardDto.ConfirmDelete), nameof(ListFormWizardDto.SeedData), nameof(ListFormWizardDto.SeedSync),
|
||||
nameof(ListFormWizardDto.DefaultLayout), nameof(ListFormWizardDto.Grid), nameof(ListFormWizardDto.Card),
|
||||
nameof(ListFormWizardDto.Pivot), nameof(ListFormWizardDto.Tree), nameof(ListFormWizardDto.Chart),
|
||||
nameof(ListFormWizardDto.Gantt), nameof(ListFormWizardDto.Scheduler), nameof(ListFormWizardDto.Todo),
|
||||
|
|
|
|||
|
|
@ -348,6 +348,15 @@ public class ListFormWizardAppService(
|
|||
ValidateImageColumnLengths(input, tableColumns);
|
||||
await LocalizeStaticLookupsAsync(input, fieldDtos, rows, owned, inserted);
|
||||
|
||||
// Script Builder kurali tasiyan script'in kodu kurallardan uretilir; MCP'nin elle yazdigi
|
||||
// govdeye guvenilmez (lowcode §4.4, §5.6).
|
||||
var booleanFields = fieldDtos
|
||||
.Where(f => f.SourceDbType == System.Data.DbType.Boolean || f.EditorType2 is "dxCheckBox" or "dxSwitch")
|
||||
.Select(f => f.FieldName)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var field in fieldDtos)
|
||||
field.EditorScript = Sozsoft.Platform.Editors.EditorScript.Rebuild(field.EditorScript, booleanFields.Contains);
|
||||
|
||||
foreach (var item in input.Groups.SelectMany(g => g.Items))
|
||||
await WriteLanguageRowsAsync(rows, item.CaptionName, item.EnglishCaption, item.TurkishCaption, owned, inserted);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,56 @@ public static class EditorScript
|
|||
return string.Join("\n", lines);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Script Builder kurallarini tasiyan script'in kodunu kurallardan yeniden uretir. Girdi
|
||||
/// <c>// @builder {...}</c> basligiyla baslayan bir script ya da yalnizca kural JSON'u
|
||||
/// (<c>{"v":1,"rules":[...]}</c>) olabilir; basligin altindaki elle yazilmis govde atilir —
|
||||
/// yanlis yardimci kullanimi (<c>str(get('X'))</c>) calismayan script uretirdi. Kural tasimayan
|
||||
/// script oldugu gibi doner. <paramref name="isBooleanField"/> verilirse mantiksal alana deger yazan
|
||||
/// kuralin degeri dialogdaki Evet/Hayir seciminin karsiligina (<c>true</c>/<c>false</c>) cevrilir.
|
||||
/// </summary>
|
||||
public static string Rebuild(string script, System.Func<string, bool> isBooleanField = null)
|
||||
{
|
||||
var text = script?.Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
string json;
|
||||
if (text.StartsWith(BuilderHeader, System.StringComparison.Ordinal))
|
||||
{
|
||||
var lineEnd = text.IndexOf('\n');
|
||||
json = (lineEnd < 0 ? text : text[..lineEnd])[BuilderHeader.Length..];
|
||||
}
|
||||
else if (text.StartsWith('{'))
|
||||
{
|
||||
json = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = System.Text.Json.JsonDocument.Parse(json);
|
||||
if (!document.RootElement.TryGetProperty("rules", out var rules) || rules.ValueKind != System.Text.Json.JsonValueKind.Array)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
var built = Build(rules.EnumerateArray()
|
||||
.Select(EditorScriptRule.FromJson)
|
||||
.Select(rule => isBooleanField == null ? rule : rule.WithBooleanValue(isBooleanField)));
|
||||
return built.Length == 0 ? script : built;
|
||||
}
|
||||
catch (System.Text.Json.JsonException)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- *
|
||||
* Ortak yardimcilar (scriptRecipes.ts portu)
|
||||
* ---------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ public sealed class EditorScriptCondition
|
|||
|
||||
internal string Value { get; }
|
||||
|
||||
/// <summary>Kural JSON'undaki kosulu geri okur (<see cref="EditorScriptRule.FromJson"/>).</summary>
|
||||
internal static EditorScriptCondition Of(string @operator, string source, string value)
|
||||
=> new EditorScriptCondition(@operator, source, value);
|
||||
|
||||
/// <summary>Kosulsuz - kural her zaman calisir.</summary>
|
||||
public static EditorScriptCondition Always()
|
||||
=> new EditorScriptCondition("always", string.Empty, string.Empty);
|
||||
|
|
|
|||
|
|
@ -114,6 +114,76 @@ public sealed class EditorScriptRule
|
|||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ToJson"/> ciktisini (dialogun <c>@builder</c> basligindaki kural) geri okur; bilinmeyen
|
||||
/// tarif <see cref="IsComplete"/>'ten gecmez ve uretimde atlanir.
|
||||
/// </summary>
|
||||
internal static EditorScriptRule FromJson(System.Text.Json.JsonElement json)
|
||||
{
|
||||
static string Text(System.Text.Json.JsonElement element, string name) =>
|
||||
element.ValueKind == System.Text.Json.JsonValueKind.Object && element.TryGetProperty(name, out var value)
|
||||
? value.ValueKind == System.Text.Json.JsonValueKind.String ? value.GetString() : value.ToString()
|
||||
: null;
|
||||
|
||||
static EditorScriptCondition Condition(System.Text.Json.JsonElement element) =>
|
||||
EditorScriptCondition.Of(Text(element, "operator") ?? "always", Text(element, "source"), Text(element, "value"));
|
||||
|
||||
var rule = new EditorScriptRule(Text(json, "recipe") ?? string.Empty).WithId(Text(json, "id"));
|
||||
|
||||
if (json.TryGetProperty("params", out var parameters) && parameters.ValueKind == System.Text.Json.JsonValueKind.Object)
|
||||
{
|
||||
foreach (var parameter in parameters.EnumerateObject())
|
||||
rule.Set(parameter.Name, Text(parameters, parameter.Name));
|
||||
}
|
||||
|
||||
if (json.TryGetProperty("fields", out var fields) && fields.ValueKind == System.Text.Json.JsonValueKind.Array)
|
||||
rule.SetFields(fields.EnumerateArray().Select(field => field.ToString()));
|
||||
|
||||
var conditions = new List<EditorScriptCondition>();
|
||||
if (json.TryGetProperty("condition", out var condition))
|
||||
conditions.Add(Condition(condition));
|
||||
if (json.TryGetProperty("conditions", out var more) && more.ValueKind == System.Text.Json.JsonValueKind.Array)
|
||||
conditions.AddRange(more.EnumerateArray().Select(Condition));
|
||||
|
||||
// Kosulsuz kural "always" ile yazilir; ToJson onu kendisi ekler.
|
||||
rule.AddConditions([.. conditions.Where(c => c.Operator != "always")], Text(json, "join") == "or");
|
||||
|
||||
return rule.WithTrigger(Text(json, "trigger") switch
|
||||
{
|
||||
"change" => EditorScriptTrigger.Change,
|
||||
"open" => EditorScriptTrigger.Open,
|
||||
"both" => EditorScriptTrigger.Both,
|
||||
_ => EditorScriptTrigger.Default
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mantiksal alana yazan <c>setValue</c> kuralinin degerini <c>true</c>/<c>false</c>'a cevirir; dialog bu
|
||||
/// alanlarda yalnizca Evet/Hayir sectirir. <c>tpl("0")</c> metni onay kutusunu dolu sayardi.
|
||||
/// </summary>
|
||||
internal EditorScriptRule WithBooleanValue(System.Func<string, bool> isBooleanField)
|
||||
{
|
||||
if (_recipe != "setValue" || !isBooleanField(Param("target")))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var flag = Param("text").Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"true" or "1" or "yes" or "evet" or "on" => "true",
|
||||
"false" or "0" or "no" or "hayir" or "hayır" or "off" => "false",
|
||||
_ => null
|
||||
};
|
||||
|
||||
var index = _params.FindIndex(pair => pair.Key == "text");
|
||||
if (flag != null && index >= 0)
|
||||
{
|
||||
_params[index] = new KeyValuePair<string, string>("text", flag);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Tek kurallik script metni. Birden fazla kural icin <see cref="EditorScript.Build(EditorScriptRule[])"/> kullanin.</summary>
|
||||
public string ToScript() => EditorScript.Build(this);
|
||||
|
||||
|
|
@ -352,7 +422,7 @@ public sealed class EditorScriptRule
|
|||
return "copy(" + q(Param("source")) + ", " + q(Param("target")) + ")";
|
||||
|
||||
case "setValue":
|
||||
return "set(" + q(Param("target")) + ", tpl(" + q(Param("text")) + "))";
|
||||
return "set(" + q(Param("target")) + ", " + ValueLiteral(Param("text")) + ")";
|
||||
|
||||
case "clear":
|
||||
return "clear(" + string.Join(", ", _fields.Select(q)) + ")";
|
||||
|
|
@ -396,6 +466,18 @@ public sealed class EditorScriptRule
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c>/<c>false</c> mantiksal sabit olarak yazilir; metin olarak (<c>tpl('false')</c>) yazilsa
|
||||
/// onay kutusu dolu sayardi. Diger degerler sablondur. scriptRecipes.ts <c>valueLiteral</c> ile ayni.
|
||||
/// </summary>
|
||||
private static string ValueLiteral(string text)
|
||||
{
|
||||
var trimmed = (text ?? string.Empty).Trim();
|
||||
return trimmed.Equals("true", System.StringComparison.OrdinalIgnoreCase) || trimmed.Equals("false", System.StringComparison.OrdinalIgnoreCase)
|
||||
? trimmed.ToLowerInvariant()
|
||||
: "tpl(" + EditorScript.Quote(text) + ")";
|
||||
}
|
||||
|
||||
private static string TriggerName(EditorScriptTrigger trigger)
|
||||
{
|
||||
switch (trigger)
|
||||
|
|
|
|||
10
claude.md
10
claude.md
|
|
@ -232,6 +232,13 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
|||
`name` düz metin değil `App.StaticLookup.*` dil anahtarıdır; EN + TR satırı dil dosyasına yazılır.
|
||||
Deploy düz metni kendisi anahtara çevirir (aynı TR metinli anahtar varsa onu kullanır) —
|
||||
`ListFormWizardAppService.LocalizeStaticLookupsAsync`, `lowcode.instructions.md` §4.3.4.1.
|
||||
20. **EditorScript** → MCP/wizard script'i elle yazmaz, Script Builder kurallarını verir; deploy kodu
|
||||
kurallardan üretir (`EditorScript.Rebuild`). `str/num/bool` alan **adı** alır (`str('X')`, değeri
|
||||
değil). `setValue`'da `true`/`false` mantıksal yazılır (onay kutusu alanında `0`/`1` deploy'da çevrilir).
|
||||
MCP ve Script Builder aynı kuralları aynı koda çevirir; `set` zinciri derinlik sınırsızdır, döngü ve
|
||||
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`).
|
||||
|
||||
### Cross-cutting: touching one thing means touching these too
|
||||
|
||||
|
|
@ -335,7 +342,8 @@ Bir çelişki varsa sıra: `ai.instructions.md` → `dotnet.instructions.md` →
|
|||
adım ekleme. Ayrıntı: `lowcode.instructions.md` §4.0.
|
||||
- **MCP ile demo veri** → geliştirme isteği örnek satır taşıyorsa (prompt'taki tablo/liste, ekran
|
||||
görüntüsü) ekran `deploy_wizard`'a `SeedData: true` ile gider (`SeedFilePath = data/{ListFormCode}.json`
|
||||
+ `SeedSync*` açık, `ListFormWizardDto.BuildListForm`) ve satırlar aynı çağrıda `seedRows` ile
|
||||
; `SeedSync*` bayrakları **kapalı** kalır — yalnızca istek ekrandan girilen kayıtların dosyaya
|
||||
yansımasını açıkça isterse `SeedSync: true`, wizard ekranında da aynı; `ListFormWizardDto.BuildListForm`) ve satırlar aynı çağrıda `seedRows` ile
|
||||
`configs/seeds/data/{ListFormCode}.json` dosyasına yazılır (`McpDevelopAppService`; alan adları
|
||||
ekrana göre doğrulanır, `Guid` anahtarı sunucu üretir). Migrate ekranı kurar, ardından satırları
|
||||
ekler; MCP migrate'i beklemez. Talep döngüsü `Testing`'de biter, `Staging`'e kullanıcı alır. Dosya elle yazılmaz, görselden okunamayan değer uydurulmaz.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -53,6 +53,25 @@
|
|||
"Cost": 0,
|
||||
"IsActive": true,
|
||||
"Id": "4de8b328-a0bf-3b36-96d9-3a2429f022bf"
|
||||
},
|
||||
{
|
||||
"Id": "eb0ce687-3014-4f8b-9482-469f3e6d3993",
|
||||
"Name": "Deneme",
|
||||
"IsFavorite": false,
|
||||
"CanBeSold": true,
|
||||
"CanBeExpensed": false,
|
||||
"CanBePurchased": false,
|
||||
"ProductType": "Service",
|
||||
"CategoryId": "eb9eed73-a7e1-5b5b-c31b-3a2429dfe211",
|
||||
"TrackInventory": false,
|
||||
"TrackingMethod": "Quantity",
|
||||
"SalesPrice": 0.0000,
|
||||
"Cost": 0.0000,
|
||||
"InternalReference": null,
|
||||
"UomId": "Adet",
|
||||
"PurchaseUomId": "Adet",
|
||||
"IsActive": true,
|
||||
"Image": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59742,6 +59742,48 @@
|
|||
"Key": "App.ScriptBuilder.AddOption",
|
||||
"Value": "+ ekle"
|
||||
},
|
||||
{
|
||||
"Id": "92147417-0362-5de6-9fdd-8c45da7e3857",
|
||||
"CultureName": "en",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.ScriptBuilder.SyntaxError",
|
||||
"Value": "The script has a syntax error and was not saved: {message}"
|
||||
},
|
||||
{
|
||||
"Id": "cd1e1fe4-af2d-5042-b258-ae16236da342",
|
||||
"CultureName": "tr",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.ScriptBuilder.SyntaxError",
|
||||
"Value": "Script'te söz dizimi hatası var, kaydedilmedi: {message}"
|
||||
},
|
||||
{
|
||||
"Id": "e0f30407-7383-5a15-ad1d-7beb2c8a264c",
|
||||
"CultureName": "en",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.EditorScript.Error",
|
||||
"Value": "The editor script of field '{field}' is faulty and did not run: {message}"
|
||||
},
|
||||
{
|
||||
"Id": "cf351e16-422f-565e-ae84-ff72de0f8102",
|
||||
"CultureName": "tr",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.EditorScript.Error",
|
||||
"Value": "'{field}' alanının editör script'i hatalı, çalışmadı: {message}"
|
||||
},
|
||||
{
|
||||
"Id": "cf45e07a-0a2e-50bc-92d2-af9850cd22d7",
|
||||
"CultureName": "en",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.ScriptBuilder.StaleHeader",
|
||||
"Value": "The code no longer matches the wizard rules. On save the rules header is removed and the code is kept as hand-written; use Regenerate to return to the rules."
|
||||
},
|
||||
{
|
||||
"Id": "7c7d420a-df72-5d21-b2e4-5f9f95bfe873",
|
||||
"CultureName": "tr",
|
||||
"ResourceName": "Platform",
|
||||
"Key": "App.ScriptBuilder.StaleHeader",
|
||||
"Value": "Kod sihirbaz kurallarıyla artık eşleşmiyor. Kaydedince kural başlığı kaldırılır ve kod elle yazılmış olarak kalır; kurallara dönmek için Yeniden üret'i kullanın."
|
||||
},
|
||||
{
|
||||
"Id": "5c33f8a3-775e-50ba-9e9b-97e0a4032e7a",
|
||||
"CultureName": "en",
|
||||
|
|
|
|||
|
|
@ -252,7 +252,6 @@ Dış entegrasyon yok. Ölçü Birimleri, Kontaklar, Para Birimleri, Ülkeler ve
|
|||
| `InternalReference` | İç Referans | dxTextBox | | ✓ | |
|
||||
| `ProductName` | Adı | dxTextBox | | ✓ | |
|
||||
| `VariantName` | Varyant Değerleri | dxTextBox | | ✓ | |
|
||||
| `VariantKey` | Varyant Anahtarı | dxTextBox | | | |
|
||||
| `SalesPrice` | Satış Fiyatı | dxNumberBox | | ✓ | |
|
||||
| `Cost` | Maliyet | dxNumberBox | | ✓ | |
|
||||
| `UomId` | Birim | dxSelectBox | | ✓ | sorgu: Adm_T_Uom |
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Inventory.Attribute.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Inv_T_Attribute\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Prc.DeliveryMethod.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Prc_T_DeliveryMethod\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -771,7 +771,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -909,7 +909,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1047,7 +1047,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Prc.Incoterm.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Prc_T_Incoterm\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -777,7 +777,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -915,7 +915,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1053,7 +1053,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1191,7 +1191,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Prc.PaymentMethod.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Prc_T_PaymentMethod\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -777,7 +777,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -915,7 +915,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1041,7 +1041,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1179,7 +1179,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Prc.PaymentTerm.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Prc_T_PaymentTerm\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -783,7 +783,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -921,7 +921,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1047,7 +1047,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1185,7 +1185,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1311,7 +1311,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 4,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -171,9 +171,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Inventory.Product.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Inv_T_Product\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -1846,7 +1846,7 @@
|
|||
"GridBoxOptions": null,
|
||||
"TagBoxOptions": null,
|
||||
"ImageUploadOptions": null,
|
||||
"EditorScript": "// @runOnOpen\nreadOnly(\"TrackInventory\", str(get(\"ProductType\")) === \"Service\")\nif (!isReady && str(value) === \"Service\") { set(\"TrackInventory\", false); clear(\"TrackingMethod\"); readOnly(\"TrackingMethod\", true) }",
|
||||
"EditorScript": "// @runOnOpen\nif (str(\"ProductType\") === \"Service\") set(\"TrackInventory\", false)\nreadOnly(\"TrackInventory\", str(\"ProductType\") === \"Service\")",
|
||||
"LastModificationTime": null,
|
||||
"LastModifierId": null,
|
||||
"CreationTime": "0001-01-01T00:00:00",
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -889,7 +889,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -1027,7 +1027,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1121,11 +1121,11 @@
|
|||
"DataSourceType": 2,
|
||||
"ValueExpr": "Key",
|
||||
"DisplayExpr": "Name",
|
||||
"CascadeRelationField": null,
|
||||
"CascadeRelationField": "",
|
||||
"CascadeFilterOperator": "=",
|
||||
"CascadeParentFields": "AttributeId",
|
||||
"CascadeEmptyFields": null,
|
||||
"LookupQuery": "SELECT CAST(Id AS nvarchar(36)) AS [Key], Name AS [Name] FROM Inv_T_AttributeValue WHERE IsDeleted = 'false' AND IsActive = 1 AND (AttributeId = @param0 OR @param0 IS NULL) ORDER BY SortOrder, Name"
|
||||
"CascadeEmptyFields": "",
|
||||
"LookupQuery": "SELECT CAST(Id AS nvarchar(36)) AS [Key], Name AS [Name], ColorCode AS [Color] FROM Inv_T_AttributeValue WHERE IsDeleted = 'false' AND IsActive = 1 AND (AttributeId = @param0 OR @param0 IS NULL) ORDER BY SortOrder, Name"
|
||||
},
|
||||
"ValidationRuleDto": [
|
||||
{
|
||||
|
|
@ -1165,7 +1165,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Inventory.ProductCategory.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Inv_T_ProductCategory\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -777,7 +777,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -915,7 +915,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1041,7 +1041,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1179,7 +1179,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -68,12 +68,6 @@
|
|||
"En": "Variant Values",
|
||||
"Tr": "Varyant Değerleri"
|
||||
},
|
||||
{
|
||||
"FieldName": "VariantKey",
|
||||
"CaptionName": "App.Listform.ListformField.VariantKey",
|
||||
"En": "Variant Key",
|
||||
"Tr": "Varyant Anahtarı"
|
||||
},
|
||||
{
|
||||
"FieldName": "SalesPrice",
|
||||
"CaptionName": "App.Listform.ListformField.SalesPrice",
|
||||
|
|
@ -789,7 +783,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -915,7 +909,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -1041,7 +1035,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1167,7 +1161,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1293,7 +1287,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1326,17 +1320,17 @@
|
|||
"JoinFieldName2": null,
|
||||
"BaseTableJoinFieldName2_OrValue": null
|
||||
},
|
||||
"FieldName": "VariantKey",
|
||||
"CaptionName": "App.Listform.ListformField.VariantKey",
|
||||
"FieldName": "SalesPrice",
|
||||
"CaptionName": "App.Listform.ListformField.SalesPrice",
|
||||
"PlaceHolder": null,
|
||||
"ReadOnly": false,
|
||||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 6,
|
||||
"SourceDbType": 16,
|
||||
"SourceDbType": 7,
|
||||
"CultureName": "en",
|
||||
"DataType": "string",
|
||||
"DataType": "number",
|
||||
"SortIndex": 0,
|
||||
"SortDirection": null,
|
||||
"AllowSearch": true,
|
||||
|
|
@ -1419,13 +1413,13 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
"EditorType2": "dxTextBox",
|
||||
"EditOrderNo": 4,
|
||||
"EditGroupOrderNo": 1,
|
||||
"EditorType2": "dxNumberBox",
|
||||
"ColSpan": 1,
|
||||
"EditorOptions": null,
|
||||
"EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}",
|
||||
"GridBoxOptions": null,
|
||||
"TagBoxOptions": null,
|
||||
"ImageUploadOptions": null,
|
||||
|
|
@ -1452,8 +1446,8 @@
|
|||
"JoinFieldName2": null,
|
||||
"BaseTableJoinFieldName2_OrValue": null
|
||||
},
|
||||
"FieldName": "SalesPrice",
|
||||
"CaptionName": "App.Listform.ListformField.SalesPrice",
|
||||
"FieldName": "Cost",
|
||||
"CaptionName": "App.Listform.ListformField.Cost",
|
||||
"PlaceHolder": null,
|
||||
"ReadOnly": false,
|
||||
"Visible": true,
|
||||
|
|
@ -1545,133 +1539,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Format": null,
|
||||
"EditOrderNo": 4,
|
||||
"EditGroupOrderNo": 1,
|
||||
"EditorType2": "dxNumberBox",
|
||||
"ColSpan": 1,
|
||||
"EditorOptions": "{\"format\":{\"type\":\"fixedPoint\",\"precision\":2}}",
|
||||
"GridBoxOptions": null,
|
||||
"TagBoxOptions": null,
|
||||
"ImageUploadOptions": null,
|
||||
"EditorScript": null,
|
||||
"LastModificationTime": null,
|
||||
"LastModifierId": null,
|
||||
"CreationTime": "0001-01-01T00:00:00",
|
||||
"CreatorId": null,
|
||||
"Id": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
{
|
||||
"EditType": null,
|
||||
"ListFormCode": "App.Inventory.ProductVariant",
|
||||
"RoleId": null,
|
||||
"UserId": null,
|
||||
"ColumnJoinTableDto": {
|
||||
"JoinType": null,
|
||||
"TableName": null,
|
||||
"JoinFieldName": null,
|
||||
"BaseTableJoinFieldName": null,
|
||||
"SelectFieldName": null,
|
||||
"FieldNameAlias": null,
|
||||
"JoinCondition2": "AND",
|
||||
"JoinFieldName2": null,
|
||||
"BaseTableJoinFieldName2_OrValue": null
|
||||
},
|
||||
"FieldName": "Cost",
|
||||
"CaptionName": "App.Listform.ListformField.Cost",
|
||||
"PlaceHolder": null,
|
||||
"ReadOnly": false,
|
||||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 8,
|
||||
"SourceDbType": 7,
|
||||
"CultureName": "en",
|
||||
"DataType": "number",
|
||||
"SortIndex": 0,
|
||||
"SortDirection": null,
|
||||
"AllowSearch": true,
|
||||
"AllowEditing": true,
|
||||
"AllowAdding": true,
|
||||
"ColumnCssClass": null,
|
||||
"ColumnCssValue": null,
|
||||
"BandName": null,
|
||||
"ColumnFilterDto": {
|
||||
"AllowFiltering": true,
|
||||
"FilterOperations": null,
|
||||
"SelectedFilterOperation": null,
|
||||
"FilterValue": null
|
||||
},
|
||||
"ColumnHeaderDto": {
|
||||
"AllowHeaderFiltering": true,
|
||||
"AllowSearch": false,
|
||||
"DataSource": null,
|
||||
"DataSourceString": ""
|
||||
},
|
||||
"ColumnGroupingDto": {
|
||||
"AllowGrouping": true,
|
||||
"GroupIndex": null,
|
||||
"AutoExpandGroup": false
|
||||
},
|
||||
"ColumnCustomizationDto": {
|
||||
"Fixed": false,
|
||||
"FixedPosition": "left",
|
||||
"AllowReordering": true
|
||||
},
|
||||
"ColumnTotalSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"ColumnGroupSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"LookupDto": {
|
||||
"DataSourceType": 0,
|
||||
"ValueExpr": "Key",
|
||||
"DisplayExpr": "Name",
|
||||
"CascadeRelationField": null,
|
||||
"CascadeFilterOperator": "=",
|
||||
"CascadeParentFields": null,
|
||||
"CascadeEmptyFields": null,
|
||||
"LookupQuery": null
|
||||
},
|
||||
"ValidationRuleDto": [],
|
||||
"ColumnStylingDto": [],
|
||||
"PermissionDto": {
|
||||
"Deny": false,
|
||||
"C": "App.Inventory.ProductVariant.Create",
|
||||
"R": "App.Inventory.ProductVariant",
|
||||
"U": "App.Inventory.ProductVariant.Update",
|
||||
"E": true,
|
||||
"I": true
|
||||
},
|
||||
"CanRead": false,
|
||||
"CanCreate": false,
|
||||
"CanUpdate": false,
|
||||
"CanExport": false,
|
||||
"DefaultValue": null,
|
||||
"DefaultValueType": 0,
|
||||
"PivotSettingsDto": {
|
||||
"IsPivot": true,
|
||||
"Expanded": false,
|
||||
"SortOrder": "asc",
|
||||
"Area": "filter",
|
||||
"Format": null,
|
||||
"SummaryType": null,
|
||||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 5,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1711,7 +1579,7 @@
|
|||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 9,
|
||||
"ListOrderNo": 8,
|
||||
"SourceDbType": 16,
|
||||
"CultureName": "en",
|
||||
"DataType": "string",
|
||||
|
|
@ -1797,7 +1665,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 6,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1837,7 +1705,7 @@
|
|||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 10,
|
||||
"ListOrderNo": 9,
|
||||
"SourceDbType": 9,
|
||||
"CultureName": "en",
|
||||
"DataType": "string",
|
||||
|
|
@ -1923,7 +1791,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 7,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1963,6 +1831,132 @@
|
|||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 10,
|
||||
"SourceDbType": 3,
|
||||
"CultureName": "en",
|
||||
"DataType": "boolean",
|
||||
"SortIndex": 0,
|
||||
"SortDirection": null,
|
||||
"AllowSearch": true,
|
||||
"AllowEditing": true,
|
||||
"AllowAdding": true,
|
||||
"ColumnCssClass": null,
|
||||
"ColumnCssValue": null,
|
||||
"BandName": null,
|
||||
"ColumnFilterDto": {
|
||||
"AllowFiltering": true,
|
||||
"FilterOperations": null,
|
||||
"SelectedFilterOperation": null,
|
||||
"FilterValue": null
|
||||
},
|
||||
"ColumnHeaderDto": {
|
||||
"AllowHeaderFiltering": true,
|
||||
"AllowSearch": false,
|
||||
"DataSource": null,
|
||||
"DataSourceString": ""
|
||||
},
|
||||
"ColumnGroupingDto": {
|
||||
"AllowGrouping": true,
|
||||
"GroupIndex": null,
|
||||
"AutoExpandGroup": false
|
||||
},
|
||||
"ColumnCustomizationDto": {
|
||||
"Fixed": false,
|
||||
"FixedPosition": "left",
|
||||
"AllowReordering": true
|
||||
},
|
||||
"ColumnTotalSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"ColumnGroupSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"LookupDto": {
|
||||
"DataSourceType": 0,
|
||||
"ValueExpr": "Key",
|
||||
"DisplayExpr": "Name",
|
||||
"CascadeRelationField": null,
|
||||
"CascadeFilterOperator": "=",
|
||||
"CascadeParentFields": null,
|
||||
"CascadeEmptyFields": null,
|
||||
"LookupQuery": null
|
||||
},
|
||||
"ValidationRuleDto": [],
|
||||
"ColumnStylingDto": [],
|
||||
"PermissionDto": {
|
||||
"Deny": false,
|
||||
"C": "App.Inventory.ProductVariant.Create",
|
||||
"R": "App.Inventory.ProductVariant",
|
||||
"U": "App.Inventory.ProductVariant.Update",
|
||||
"E": true,
|
||||
"I": true
|
||||
},
|
||||
"CanRead": false,
|
||||
"CanCreate": false,
|
||||
"CanUpdate": false,
|
||||
"CanExport": false,
|
||||
"DefaultValue": null,
|
||||
"DefaultValueType": 0,
|
||||
"PivotSettingsDto": {
|
||||
"IsPivot": true,
|
||||
"Expanded": false,
|
||||
"SortOrder": "asc",
|
||||
"Area": "filter",
|
||||
"Format": null,
|
||||
"SummaryType": null,
|
||||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 8,
|
||||
"EditGroupOrderNo": 1,
|
||||
"EditorType2": "dxCheckBox",
|
||||
"ColSpan": 1,
|
||||
"EditorOptions": null,
|
||||
"GridBoxOptions": null,
|
||||
"TagBoxOptions": null,
|
||||
"ImageUploadOptions": null,
|
||||
"EditorScript": null,
|
||||
"LastModificationTime": null,
|
||||
"LastModifierId": null,
|
||||
"CreationTime": "0001-01-01T00:00:00",
|
||||
"CreatorId": null,
|
||||
"Id": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
{
|
||||
"EditType": null,
|
||||
"ListFormCode": "App.Inventory.ProductVariant",
|
||||
"RoleId": null,
|
||||
"UserId": null,
|
||||
"ColumnJoinTableDto": {
|
||||
"JoinType": null,
|
||||
"TableName": null,
|
||||
"JoinFieldName": null,
|
||||
"BaseTableJoinFieldName": null,
|
||||
"SelectFieldName": null,
|
||||
"FieldNameAlias": null,
|
||||
"JoinCondition2": "AND",
|
||||
"JoinFieldName2": null,
|
||||
"BaseTableJoinFieldName2_OrValue": null
|
||||
},
|
||||
"FieldName": "IsDeleted",
|
||||
"CaptionName": "App.Listform.ListformField.IsDeleted",
|
||||
"PlaceHolder": null,
|
||||
"ReadOnly": false,
|
||||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 11,
|
||||
"SourceDbType": 3,
|
||||
"CultureName": "en",
|
||||
|
|
@ -2049,133 +2043,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Format": null,
|
||||
"EditOrderNo": 8,
|
||||
"EditGroupOrderNo": 1,
|
||||
"EditorType2": "dxCheckBox",
|
||||
"ColSpan": 1,
|
||||
"EditorOptions": null,
|
||||
"GridBoxOptions": null,
|
||||
"TagBoxOptions": null,
|
||||
"ImageUploadOptions": null,
|
||||
"EditorScript": null,
|
||||
"LastModificationTime": null,
|
||||
"LastModifierId": null,
|
||||
"CreationTime": "0001-01-01T00:00:00",
|
||||
"CreatorId": null,
|
||||
"Id": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
{
|
||||
"EditType": null,
|
||||
"ListFormCode": "App.Inventory.ProductVariant",
|
||||
"RoleId": null,
|
||||
"UserId": null,
|
||||
"ColumnJoinTableDto": {
|
||||
"JoinType": null,
|
||||
"TableName": null,
|
||||
"JoinFieldName": null,
|
||||
"BaseTableJoinFieldName": null,
|
||||
"SelectFieldName": null,
|
||||
"FieldNameAlias": null,
|
||||
"JoinCondition2": "AND",
|
||||
"JoinFieldName2": null,
|
||||
"BaseTableJoinFieldName2_OrValue": null
|
||||
},
|
||||
"FieldName": "IsDeleted",
|
||||
"CaptionName": "App.Listform.ListformField.IsDeleted",
|
||||
"PlaceHolder": null,
|
||||
"ReadOnly": false,
|
||||
"Visible": true,
|
||||
"IsActive": true,
|
||||
"Width": 0,
|
||||
"ListOrderNo": 12,
|
||||
"SourceDbType": 3,
|
||||
"CultureName": "en",
|
||||
"DataType": "boolean",
|
||||
"SortIndex": 0,
|
||||
"SortDirection": null,
|
||||
"AllowSearch": true,
|
||||
"AllowEditing": true,
|
||||
"AllowAdding": true,
|
||||
"ColumnCssClass": null,
|
||||
"ColumnCssValue": null,
|
||||
"BandName": null,
|
||||
"ColumnFilterDto": {
|
||||
"AllowFiltering": true,
|
||||
"FilterOperations": null,
|
||||
"SelectedFilterOperation": null,
|
||||
"FilterValue": null
|
||||
},
|
||||
"ColumnHeaderDto": {
|
||||
"AllowHeaderFiltering": true,
|
||||
"AllowSearch": false,
|
||||
"DataSource": null,
|
||||
"DataSourceString": ""
|
||||
},
|
||||
"ColumnGroupingDto": {
|
||||
"AllowGrouping": true,
|
||||
"GroupIndex": null,
|
||||
"AutoExpandGroup": false
|
||||
},
|
||||
"ColumnCustomizationDto": {
|
||||
"Fixed": false,
|
||||
"FixedPosition": "left",
|
||||
"AllowReordering": true
|
||||
},
|
||||
"ColumnTotalSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"ColumnGroupSummaryDto": {
|
||||
"SummaryType": null,
|
||||
"ValueFormat": null,
|
||||
"ShowInGroupFooter": false,
|
||||
"AlignByColumn": false,
|
||||
"DisplayFormat": null,
|
||||
"ShowInColumn": null
|
||||
},
|
||||
"LookupDto": {
|
||||
"DataSourceType": 0,
|
||||
"ValueExpr": "Key",
|
||||
"DisplayExpr": "Name",
|
||||
"CascadeRelationField": null,
|
||||
"CascadeFilterOperator": "=",
|
||||
"CascadeParentFields": null,
|
||||
"CascadeEmptyFields": null,
|
||||
"LookupQuery": null
|
||||
},
|
||||
"ValidationRuleDto": [],
|
||||
"ColumnStylingDto": [],
|
||||
"PermissionDto": {
|
||||
"Deny": false,
|
||||
"C": "App.Inventory.ProductVariant.Create",
|
||||
"R": "App.Inventory.ProductVariant",
|
||||
"U": "App.Inventory.ProductVariant.Update",
|
||||
"E": true,
|
||||
"I": true
|
||||
},
|
||||
"CanRead": false,
|
||||
"CanCreate": false,
|
||||
"CanUpdate": false,
|
||||
"CanExport": false,
|
||||
"DefaultValue": null,
|
||||
"DefaultValueType": 0,
|
||||
"PivotSettingsDto": {
|
||||
"IsPivot": true,
|
||||
"Expanded": false,
|
||||
"SortOrder": "asc",
|
||||
"Area": "filter",
|
||||
"Format": null,
|
||||
"SummaryType": null,
|
||||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
|
|||
|
|
@ -94,9 +94,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Inventory.WorkCenter.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Inv_T_WorkCenter\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@
|
|||
"SelectCommandType": 1,
|
||||
"SelectFieldsDefaultValueDto": [],
|
||||
"SeedFilePath": "data/App.Inventory.WorkCenterTag.json",
|
||||
"SeedSyncInsert": true,
|
||||
"SeedSyncUpdate": true,
|
||||
"SeedSyncDelete": true,
|
||||
"SeedSyncInsert": false,
|
||||
"SeedSyncUpdate": false,
|
||||
"SeedSyncDelete": false,
|
||||
"DeleteCommand": "UPDATE \"Inv_T_WorkCenterTag\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
|
||||
"DeleteBeforeCommand": null,
|
||||
"DeleteAfterCommand": null,
|
||||
|
|
@ -777,7 +777,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": null,
|
||||
"EditGroupOrderNo": null,
|
||||
|
|
@ -915,7 +915,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 1,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1041,7 +1041,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 2,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
@ -1179,7 +1179,7 @@
|
|||
"GroupInterval": "",
|
||||
"WordWrapEnabled": false
|
||||
},
|
||||
"Alignment": null,
|
||||
"Alignment": "left",
|
||||
"Format": null,
|
||||
"EditOrderNo": 3,
|
||||
"EditGroupOrderNo": 1,
|
||||
|
|
|
|||
|
|
@ -79,8 +79,12 @@ function ScriptBuilderDialog({
|
|||
const cancelText = cancelLabel ?? translate('::App.Platform.Cancel')
|
||||
const [rules, setRules] = useState<ScriptRule[]>([])
|
||||
const [code, setCode] = useState('')
|
||||
const [syntaxError, setSyntaxError] = useState('')
|
||||
// Sihirbaz bir butona basılarak açılır; varsayılan her zaman kod editörüdür.
|
||||
const [view, setView] = useState<'code' | 'wizard'>('code')
|
||||
|
||||
// Önceki kaydetme denemesinin hatası, metin değişince ya da diyalog yeniden açılınca geçersizdir.
|
||||
useEffect(() => setSyntaxError(''), [code, view, isOpen])
|
||||
const [openRules, setOpenRules] = useState<Record<string, boolean>>({})
|
||||
const [paletteOpen, setPaletteOpen] = useState(true)
|
||||
const [helpOpen, setHelpOpen] = useState(true)
|
||||
|
|
@ -102,6 +106,13 @@ function ScriptBuilderDialog({
|
|||
const generatedScript = useMemo(() => dialect.buildScript(rules), [dialect, rules])
|
||||
const completeRuleCount = rules.filter((rule) => dialect.isRuleComplete(rule)).length
|
||||
const codeDiverged = code.trim() !== '' && code.trim() !== generatedScript.trim()
|
||||
// Kural başlığı taşıyan ama elle değiştirilmiş kod: başlık artık gövdeyi anlatmaz. Kalırsa
|
||||
// dialog ve sunucu (`EditorScript.Rebuild`) gövdeyi başlıktan yeniden üretip elle yapılan
|
||||
// düzeltmeyi geri alır.
|
||||
const staleHeader = useMemo(() => {
|
||||
const parsed = dialect.parseScript(code)
|
||||
return parsed.hasHeader && !parsed.inSync
|
||||
}, [dialect, code])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
|
@ -284,9 +295,7 @@ function ScriptBuilderDialog({
|
|||
menuPortalTarget={document.body}
|
||||
options={options.map((option) => ({ value: option, label: option }))}
|
||||
value={
|
||||
options.includes(selectedValue)
|
||||
? { value: selectedValue, label: selectedValue }
|
||||
: null
|
||||
options.includes(selectedValue) ? { value: selectedValue, label: selectedValue } : null
|
||||
}
|
||||
onChange={(option) => onChange(option?.value ?? '')}
|
||||
/>
|
||||
|
|
@ -341,6 +350,10 @@ function ScriptBuilderDialog({
|
|||
}
|
||||
|
||||
const currentValue = rule.params[param.key] ?? ''
|
||||
const ruleChoices =
|
||||
param.type === 'text' ? dialect.getParamChoices?.(rule, param.key) : undefined
|
||||
const choices = ruleChoices ?? param.choices
|
||||
const inputType = ruleChoices ? 'select' : param.type
|
||||
|
||||
return (
|
||||
<label key={param.key} className="min-w-0">
|
||||
|
|
@ -359,24 +372,22 @@ function ScriptBuilderDialog({
|
|||
(next) => updateParam(rule.id, param.key, next),
|
||||
param.placeholder || translate('::App.ScriptBuilder.Choose'),
|
||||
)}
|
||||
{param.type === 'select' && (
|
||||
{inputType === 'select' && (
|
||||
<Select
|
||||
size="sm"
|
||||
className="w-full"
|
||||
menuPortalTarget={document.body}
|
||||
options={(param.choices ?? []).map((choice) => ({
|
||||
options={(choices ?? []).map((choice) => ({
|
||||
value: choice.value,
|
||||
label: choice.label,
|
||||
}))}
|
||||
value={(param.choices ?? [])
|
||||
value={(choices ?? [])
|
||||
.filter((choice) => choice.value === currentValue)
|
||||
.map((choice) => ({ value: choice.value, label: choice.label }))}
|
||||
onChange={(option) =>
|
||||
option && updateParam(rule.id, param.key, option.value)
|
||||
}
|
||||
onChange={(option) => option && updateParam(rule.id, param.key, option.value)}
|
||||
/>
|
||||
)}
|
||||
{(param.type === 'text' || param.type === 'number') && (
|
||||
{(inputType === 'text' || inputType === 'number') && (
|
||||
<Input
|
||||
unstyle
|
||||
className={controlClass}
|
||||
|
|
@ -635,9 +646,7 @@ function ScriptBuilderDialog({
|
|||
value={triggerOptions.filter(
|
||||
(option) => option.value === (trigger ?? dialect.triggers?.[0]?.value),
|
||||
)}
|
||||
onChange={(option) =>
|
||||
option && updateRule(rule.id, { trigger: option.value })
|
||||
}
|
||||
onChange={(option) => option && updateRule(rule.id, { trigger: option.value })}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
|
|
@ -836,6 +845,13 @@ function ScriptBuilderDialog({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{view === 'code' && staleHeader && (
|
||||
<div className="flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
<FaExclamationTriangle className="shrink-0" />
|
||||
<span className="flex-1">{translate('::App.ScriptBuilder.StaleHeader')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view === 'code' ? (
|
||||
<div className="flex min-h-0 flex-1 gap-3">
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col gap-2">
|
||||
|
|
@ -966,6 +982,11 @@ function ScriptBuilderDialog({
|
|||
</Dialog.Body>
|
||||
|
||||
<Dialog.Footer className="mt-1 flex justify-end gap-2 border-t border-gray-200 pt-3 dark:border-gray-700">
|
||||
{syntaxError && (
|
||||
<p className="mr-auto self-center text-xs text-red-600">
|
||||
{translate('::App.ScriptBuilder.SyntaxError', { message: syntaxError })}
|
||||
</p>
|
||||
)}
|
||||
<Button size="sm" type="button" variant="plain" icon={<FaTimes />} onClick={onClose}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
|
|
@ -975,7 +996,18 @@ function ScriptBuilderDialog({
|
|||
variant="solid"
|
||||
icon={<FaCheck />}
|
||||
onClick={() => {
|
||||
onApply(view === 'wizard' ? generatedScript : code)
|
||||
const script =
|
||||
view === 'wizard'
|
||||
? generatedScript
|
||||
: staleHeader
|
||||
? code.trim().split('\n').slice(1).join('\n').trim()
|
||||
: code
|
||||
// Runtime script'i derleyemezse tamamı (diğer kurallar dahil) sessizce çalışmaz;
|
||||
// bozuk metin kaydedilmeden önce yakalanır.
|
||||
const error = findSyntaxError(script)
|
||||
setSyntaxError(error)
|
||||
if (error) return
|
||||
onApply(script)
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
|
|
@ -986,4 +1018,18 @@ function ScriptBuilderDialog({
|
|||
)
|
||||
}
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function noop() {}).constructor as new (
|
||||
...args: string[]
|
||||
) => unknown
|
||||
|
||||
/** Script runtime'ın derleyebileceği bir gövde mi; değilse derleyicinin mesajı. */
|
||||
const findSyntaxError = (script: string) => {
|
||||
try {
|
||||
new AsyncFunction(script)
|
||||
return ''
|
||||
} catch (error) {
|
||||
return error instanceof SyntaxError ? error.message : ''
|
||||
}
|
||||
}
|
||||
|
||||
export default ScriptBuilderDialog
|
||||
|
|
|
|||
|
|
@ -151,6 +151,11 @@ export interface ScriptDialect {
|
|||
getTrigger?: (rule: ScriptRule) => string
|
||||
/** Kural kartında gösterilecek uyarı; örneğin eksik bağlam. */
|
||||
getRuleWarning?: (rule: ScriptRule) => string
|
||||
/**
|
||||
* Serbest metin parametresinin kurala göre sabit seçenekleri (mantıksal alana yazılan değer için
|
||||
* true/false). Seçenek dönerse dialog metin kutusu yerine seçici gösterir.
|
||||
*/
|
||||
getParamChoices?: (rule: ScriptRule, paramKey: string) => ScriptRecipeParam['choices']
|
||||
}
|
||||
|
||||
/** Kuralın bütün koşulları; ilki her zaman `condition`. */
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ export interface ListFormWizardDto {
|
|||
* Wizard adımlarında sorulmaz (MCP ile demo veri taşıyan ekranlar açar), düzenlemede korunur.
|
||||
*/
|
||||
seedData: boolean
|
||||
/** Ekrandan yapılan kayıtlar veri dosyasına yansısın mı; varsayılan kapalı, düzenlemede korunur. */
|
||||
seedSync: boolean
|
||||
|
||||
defaultLayout: ListViewLayoutType
|
||||
grid: boolean
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
* yeniden derlenir.
|
||||
*/
|
||||
|
||||
import { createElement } from 'react'
|
||||
import Notification from '@/components/ui/Notification'
|
||||
import toast from '@/components/ui/toast'
|
||||
import apiService from '@/services/api.service'
|
||||
import { getLocalization } from '@/services/localization.service'
|
||||
import { store } from '@/store'
|
||||
|
|
@ -69,9 +72,36 @@ const HELPER_PARAMS = [
|
|||
* ------------------------------------------------------------------ */
|
||||
|
||||
/** Script runtime React ağacının dışındadır; dil metni store'dan okunur. */
|
||||
const translateRuntime = (key: string) => {
|
||||
const translateRuntime = (key: string, params?: Record<string, string | number>) => {
|
||||
const { texts, config } = store.getState().abpConfig
|
||||
return getLocalization(texts ?? {}, config?.localization?.defaultResourceName, key)
|
||||
return getLocalization(texts ?? {}, config?.localization?.defaultResourceName, key, params)
|
||||
}
|
||||
|
||||
/** Form her yeniden çizildiğinde aynı script tekrar çalışır; aynı hata kısa sürede bir kez gösterilir. */
|
||||
const REPEATED_ERROR_WINDOW_MS = 5000
|
||||
const lastReportedErrors = new Map<string, number>()
|
||||
|
||||
/**
|
||||
* Script hatasını kullanıcıya gösterir. Konsola düşen hata kullanıcıya görünmez; bozuk bir kural
|
||||
* sessizce çalışmadığında ekran "bozuk" görünür ama nedeni bilinmez.
|
||||
*/
|
||||
export const reportEditorScriptError = (field: string, error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
console.error('Editor script error:', field, error)
|
||||
|
||||
const key = `${field}|${message}`
|
||||
const now = Date.now()
|
||||
if (now - (lastReportedErrors.get(key) ?? 0) < REPEATED_ERROR_WINDOW_MS) return
|
||||
lastReportedErrors.set(key, now)
|
||||
|
||||
toast.push(
|
||||
createElement(
|
||||
Notification,
|
||||
{ type: 'danger', duration: 6000 },
|
||||
translateRuntime('::App.EditorScript.Error', { field, message }),
|
||||
),
|
||||
{ placement: 'top-end' },
|
||||
)
|
||||
}
|
||||
|
||||
/** Alan adını büyük/küçük harf duyarsız arar; SQL kolonları tutarsız gelebiliyor. */
|
||||
|
|
@ -98,8 +128,7 @@ const toDate = (input: any): Date | null => {
|
|||
return Number.isNaN(parsed.getTime()) ? null : parsed
|
||||
}
|
||||
|
||||
const startOfDayUtc = (date: Date) =>
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
const startOfDayUtc = (date: Date) => Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Yardımcı API üretimi
|
||||
|
|
@ -156,9 +185,15 @@ const buildHelpers = ({
|
|||
queueMicrotask(flush)
|
||||
}
|
||||
|
||||
const writeOne = (name: string, next: any) => {
|
||||
const writeOne = (name: string, input: any) => {
|
||||
if (!name) return
|
||||
const key = resolveKey(data, name) ?? name
|
||||
// Mantıksal alana şablondan gelen 'false'/'0' metni dolu sayılırdı; mevcut değer
|
||||
// mantıksalsa yazılan değer de mantıksala çevrilir.
|
||||
const next =
|
||||
typeof data[key] === 'boolean' && /^(true|false|1|0)$/i.test(String(input))
|
||||
? /^(true|1)$/i.test(String(input))
|
||||
: input
|
||||
// Değer değişmediyse yazmıyoruz. Form açılışında (contentReady) çalışan
|
||||
// script'ler setFormData'yı tetikleyip yeniden contentReady doğurabildiği
|
||||
// için bu koruma olmadan sonsuz döngü oluşur.
|
||||
|
|
@ -191,8 +226,7 @@ const buildHelpers = ({
|
|||
|
||||
const clear = (...names: string[]) => set(Object.fromEntries(names.map((name) => [name, null])))
|
||||
|
||||
const readOnly = (name: string, flag: boolean = true) =>
|
||||
runtimeSetEditorReadOnly?.(name, !!flag)
|
||||
const readOnly = (name: string, flag: boolean = true) => runtimeSetEditorReadOnly?.(name, !!flag)
|
||||
|
||||
/** Lookup/GridBox ile seçilen kaydın bir kolonunu form alanına taşır. */
|
||||
const copy = (sourcePath: string, targetName?: string) => {
|
||||
|
|
@ -230,13 +264,14 @@ const buildHelpers = ({
|
|||
const tpl = (text: string) =>
|
||||
String(text ?? '')
|
||||
.replace(/\{value\}/g, () => String(e?.value ?? ''))
|
||||
.replace(/\{selected\.([^}]+)\}/g, (_, key: string) => String(pickPath(selectedItem, key) ?? ''))
|
||||
.replace(/\{selected\.([^}]+)\}/g, (_, key: string) =>
|
||||
String(pickPath(selectedItem, key) ?? ''),
|
||||
)
|
||||
.replace(/\{([^}]+)\}/g, (_, key: string) => String(get(key) ?? ''))
|
||||
|
||||
/** Geçerli alanı değişiklik öncesindeki değerine döndürür. */
|
||||
const revert = () => {
|
||||
const previous =
|
||||
e?.previousValue ?? pickPath(formData, field) ?? null
|
||||
const previous = e?.previousValue ?? pickPath(formData, field) ?? null
|
||||
writeOne(field, previous)
|
||||
const activeEditor = e?.component?.getEditor?.(field)
|
||||
if (activeEditor?.option) activeEditor.option('value', previous)
|
||||
|
|
@ -405,7 +440,15 @@ const compileScript = (script: string): CompiledScript => {
|
|||
|
||||
export const executeEditorScript = (script: string, context: EditorScriptRuntimeContext) => {
|
||||
const { formData, e, editor, runtimeSetEditorReadOnly, setFormData } = context
|
||||
const compiled = compileScript(script)
|
||||
const scriptField = String(editor?.dataField ?? e?.dataField ?? '')
|
||||
|
||||
let compiled: CompiledScript
|
||||
try {
|
||||
compiled = compileScript(script)
|
||||
} catch (error) {
|
||||
reportEditorScriptError(scriptField, error)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const { args, flush } = buildHelpers(context)
|
||||
|
||||
const baseArgs = [formData, e, editor, runtimeSetEditorReadOnly, setFormData]
|
||||
|
|
@ -415,8 +458,6 @@ export const executeEditorScript = (script: string, context: EditorScriptRuntime
|
|||
flush()
|
||||
|
||||
return result
|
||||
.catch((error: unknown) => {
|
||||
console.error('Editor script error:', error)
|
||||
})
|
||||
.catch((error: unknown) => reportEditorScriptError(scriptField, error))
|
||||
.finally(flush)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Field, FieldProps, Form, Formik } from 'formik'
|
|||
import { bool, object, string } from 'yup'
|
||||
import { columnEditorTypeListOptions, sortDirectionOptions } from '../options'
|
||||
import { FormFieldEditProps } from './FormFields'
|
||||
import { EditingFormDto, SortDirectionEnum } from '@/proxy/form/models'
|
||||
import { DbTypeEnum, EditingFormDto, SortDirectionEnum } from '@/proxy/form/models'
|
||||
import { FaCode } from 'react-icons/fa'
|
||||
import { useState } from 'react'
|
||||
import EditorScriptBuilderDialog from '../json-row-operations/EditorScriptBuilderDialog'
|
||||
|
|
@ -52,6 +52,16 @@ function FormFieldTabOptions({
|
|||
value: field.fieldName,
|
||||
label: field.captionName || field.fieldName,
|
||||
}))
|
||||
const booleanFields = fields
|
||||
.filter(
|
||||
(field) =>
|
||||
field.fieldName &&
|
||||
(field.sourceDbType === DbTypeEnum.Boolean ||
|
||||
field.dataType === 'boolean' ||
|
||||
field.editorType2 === 'dxCheckBox' ||
|
||||
field.editorType2 === 'dxSwitch'),
|
||||
)
|
||||
.map((field) => field.fieldName!)
|
||||
|
||||
return (
|
||||
<Formik
|
||||
|
|
@ -189,6 +199,7 @@ function FormFieldTabOptions({
|
|||
value={values.editorScript}
|
||||
currentField={values.fieldName}
|
||||
fields={editorScriptFields}
|
||||
booleanFields={booleanFields}
|
||||
onClose={() => setIsEditorScriptDialogOpen(false)}
|
||||
onApply={(val) => setFieldValue('editorScript', val)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type EditorScriptBuilderDialogProps = {
|
|||
value?: string
|
||||
currentField?: string
|
||||
fields: SelectBoxOption[]
|
||||
booleanFields?: string[]
|
||||
onClose: () => void
|
||||
onApply: (value: string) => void
|
||||
}
|
||||
|
|
@ -22,6 +23,7 @@ function EditorScriptBuilderDialog({
|
|||
value,
|
||||
currentField,
|
||||
fields,
|
||||
booleanFields,
|
||||
onClose,
|
||||
onApply,
|
||||
}: EditorScriptBuilderDialogProps) {
|
||||
|
|
@ -30,9 +32,16 @@ function EditorScriptBuilderDialog({
|
|||
// Çağıran her render'da yeni bir dizi verebilir; lehçe alan adlarına göre
|
||||
// sabitlenmezse editör yazarken sıfırlanırdı.
|
||||
const fieldKey = fields.map((field) => String(field.value || field.label || '')).join('|')
|
||||
const booleanKey = (booleanFields ?? []).join('|')
|
||||
const dialect = useMemo(
|
||||
() => createFormScriptDialect({ currentField, fields: fieldKey.split('|'), translate }),
|
||||
[currentField, fieldKey, translate],
|
||||
() =>
|
||||
createFormScriptDialect({
|
||||
currentField,
|
||||
fields: fieldKey.split('|'),
|
||||
booleanFields: booleanKey.split('|'),
|
||||
translate,
|
||||
}),
|
||||
[currentField, fieldKey, booleanKey, translate],
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -137,6 +137,8 @@ const snippets: ScriptSnippet[] = [
|
|||
export type FormScriptDialectContext = {
|
||||
/** Form kolonları. */
|
||||
fields: string[]
|
||||
/** Mantıksal (onay kutusu / anahtar) alanlar; değer yazan kurallarda true/false seçtirilir. */
|
||||
booleanFields?: string[]
|
||||
/** Script'in bağlı olduğu alan; başlıkta gösterilir. */
|
||||
currentField?: string
|
||||
/** `useLocalization` hook'undan gelen çeviri fonksiyonu. */
|
||||
|
|
@ -150,6 +152,11 @@ export const createFormScriptDialect = (context: FormScriptDialectContext): Scri
|
|||
)
|
||||
|
||||
const asFormRule = (rule: ScriptRule) => rule as unknown as FormScriptRule
|
||||
const booleanFields = new Set((context.booleanFields ?? []).map((field) => field.toLowerCase()))
|
||||
const booleanChoices = [
|
||||
{ value: 'true', label: t('::App.Platform.Yes') },
|
||||
{ value: 'false', label: t('::App.Platform.No') },
|
||||
]
|
||||
|
||||
// Tarif listesi hem "Kural ekle" seçicisinde hem de kural kartında kullanılıyor.
|
||||
// Tek bir çevrilmiş liste üretip ikisini de buradan besliyoruz; aksi halde kart
|
||||
|
|
@ -241,11 +248,16 @@ export const createFormScriptDialect = (context: FormScriptDialectContext): Scri
|
|||
// Kosulsuz kuralda "Her zaman ->" oneki bilgi tasimiyor, yalnizca gurultu.
|
||||
if (!conditions.length) return action
|
||||
const joiner =
|
||||
rule.join === 'or'
|
||||
? ` ${t('::App.Platform.Or')} `
|
||||
: ` ${t('::App.Platform.And')} `
|
||||
rule.join === 'or' ? ` ${t('::App.Platform.Or')} ` : ` ${t('::App.Platform.And')} `
|
||||
return `${conditions.join(joiner)} → ${action}`
|
||||
},
|
||||
getTrigger: (rule) => effectiveTrigger(asFormRule(rule)),
|
||||
// Onay kutusuna metin ('0', 'Hayır') yazmak dolu sayılırdı; değer mantıksal sabit olarak seçilir.
|
||||
getParamChoices: (rule, paramKey) =>
|
||||
rule.recipe === 'setValue' &&
|
||||
paramKey === 'text' &&
|
||||
booleanFields.has(String(rule.params.target ?? '').toLowerCase())
|
||||
? booleanChoices
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,15 @@ export type TranslateFn = (key: string, params?: Record<string, string | number>
|
|||
|
||||
const q = (input: string) => JSON.stringify(input ?? '')
|
||||
|
||||
/**
|
||||
* `true`/`false` mantıksal sabit olarak yazılır; metin (`tpl('false')`) onay kutusunu dolu sayardı.
|
||||
* Diğer değerler şablondur. `EditorScriptRule.ValueLiteral` (C#) ile aynı.
|
||||
*/
|
||||
const valueLiteral = (text: string) => {
|
||||
const trimmed = String(text ?? '').trim()
|
||||
return /^(true|false)$/i.test(trimmed) ? trimmed.toLowerCase() : `tpl(${q(text)})`
|
||||
}
|
||||
|
||||
const label = (input: string, fallback: string) => input || fallback
|
||||
|
||||
const isNumericLiteral = (input: string) => /^-?\d+(\.\d+)?$/.test(String(input).trim())
|
||||
|
|
@ -508,7 +517,7 @@ export const recipes: Recipe[] = [
|
|||
help: 'App.ScriptBuilderText.Help',
|
||||
},
|
||||
],
|
||||
build: ({ params }) => `set(${q(params.target)}, tpl(${q(params.text)}))`,
|
||||
build: ({ params }) => `set(${q(params.target)}, ${valueLiteral(params.text)})`,
|
||||
describe: ({ params }, t) =>
|
||||
t('::App.ScriptBuilderDescribe.SetValue', {
|
||||
target: label(params.target, t('::App.Listform.ListformField.Target')),
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ const initialValues: ListFormWizardDto = {
|
|||
confirmDelete: true,
|
||||
allowDetail: false,
|
||||
seedData: false,
|
||||
seedSync: false,
|
||||
defaultLayout: 'grid',
|
||||
grid: true,
|
||||
card: true,
|
||||
|
|
@ -715,6 +716,7 @@ const Wizard = () => {
|
|||
confirmDelete: w.confirmDelete ?? true,
|
||||
allowDetail: w.allowDetail ?? false,
|
||||
seedData: w.seedData ?? false,
|
||||
seedSync: w.seedSync ?? false,
|
||||
defaultLayout: w.defaultLayout ?? 'grid',
|
||||
grid: w.grid ?? initialValues.grid,
|
||||
card: w.card ?? initialValues.card,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Grid, Tree ve form sayfası aynı kuralları paylaşır.
|
||||
*/
|
||||
import type { ColumnFormatDto, GridDto } from '@/proxy/form/models'
|
||||
import { getActiveEditingForm } from './editingForm'
|
||||
import { getActiveEditingForm, getEditingRow } from './editingForm'
|
||||
|
||||
type CascadeInfo = {
|
||||
parentFields: string[]
|
||||
|
|
@ -99,16 +99,6 @@ export const hasCascadeBehavior = (maps: CascadeMaps, fieldName: string) =>
|
|||
maps.parentToChildrenMap.has(fieldName) ||
|
||||
maps.clearFieldsMap.has(fieldName)
|
||||
|
||||
const getEditingRow = (grid: any) => {
|
||||
const rowKey = grid.option('editing.editRowKey')
|
||||
return {
|
||||
rowIndex: grid.getRowIndexByKey(rowKey) as number,
|
||||
rowData: grid.getVisibleRows().find((row: any) => row.key === rowKey)?.data as
|
||||
| Record<string, any>
|
||||
| undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/** Parent değiştikten sonra etkilenen çocuk editörlerin kilidini yeniler. */
|
||||
const updateChildDisabledStates = (
|
||||
grid: any,
|
||||
|
|
|
|||
|
|
@ -222,11 +222,25 @@ export const setFormEditorReadOnly = (form: any, field: string, readOnly: boolea
|
|||
return true
|
||||
}
|
||||
|
||||
/** DataGrid/TreeList popup düzenleme formu; düzenleme denetleyicisinde yaşar. */
|
||||
export const getActiveEditingForm = (component: any) => {
|
||||
const editForm = component?.getView?.('editingView')?._editForm
|
||||
const editForm = component?.getController?.('editing')?._editForm
|
||||
return editForm?.getEditor || editForm?.itemOption ? editForm : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Düzenlenen satırın güncel verisi (`cellValue` ile yazılanlar dahil). Yeni satırın anahtarı
|
||||
* nesnedir; `row.key === rowKey` eşleşmez, satır grid'in değerle eşleyen indeksinden okunur.
|
||||
*/
|
||||
export const getEditingRow = (grid: any) => {
|
||||
const rowKey = grid?.option?.('editing.editRowKey')
|
||||
const rowIndex: number =
|
||||
rowKey !== undefined && rowKey !== null ? (grid?.getRowIndexByKey?.(rowKey) ?? -1) : -1
|
||||
const rowData: Record<string, any> | undefined =
|
||||
rowIndex >= 0 ? grid?.getVisibleRows?.()?.[rowIndex]?.data : undefined
|
||||
return { rowKey, rowIndex, rowData }
|
||||
}
|
||||
|
||||
/**
|
||||
* Form ilk açıldığında da çalışması gereken script'leri ayıklar.
|
||||
*
|
||||
|
|
@ -242,11 +256,11 @@ export const getActiveEditingForm = (component: any) => {
|
|||
export const shouldRunEditorScriptOnContentReady = (script?: string) =>
|
||||
Boolean(
|
||||
script &&
|
||||
(script.includes('@runOnOpen') ||
|
||||
/\breadOnly\s*\(/.test(script) ||
|
||||
/\bisReady\b/.test(script) ||
|
||||
script.includes('setEditorReadOnly') ||
|
||||
script.includes('runtimeSetEditorReadOnly')),
|
||||
(script.includes('@runOnOpen') ||
|
||||
/\breadOnly\s*\(/.test(script) ||
|
||||
/\bisReady\b/.test(script) ||
|
||||
script.includes('setEditorReadOnly') ||
|
||||
script.includes('runtimeSetEditorReadOnly')),
|
||||
)
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
|
|
@ -537,7 +551,11 @@ export const createFormItemMapper = ({
|
|||
defaultEditorOptions.placeholder = translate('::' + colFormat.placeHolder)
|
||||
}
|
||||
|
||||
if (typeof colFormat.defaultValue === 'string' && colFormat.defaultValue === '@AUTONUMBER' && mode === 'new') {
|
||||
if (
|
||||
typeof colFormat.defaultValue === 'string' &&
|
||||
colFormat.defaultValue === '@AUTONUMBER' &&
|
||||
mode === 'new'
|
||||
) {
|
||||
defaultEditorOptions.value = autoNumber()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* birebir tekrar ediyordu; buradan tek noktadan sağlanır.
|
||||
*/
|
||||
import type { MutableRefObject } from 'react'
|
||||
import { executeEditorScript } from '@/utils/editorScriptRuntime'
|
||||
import { executeEditorScript, reportEditorScriptError } from '@/utils/editorScriptRuntime'
|
||||
import { attachFieldValueBroadcast } from '@/utils/phoneFormat'
|
||||
import type { GridDto } from '@/proxy/form/models'
|
||||
import type { RowMode } from '../../form/types'
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from './cascade'
|
||||
import {
|
||||
getActiveEditingForm,
|
||||
getEditingRow,
|
||||
getValueByField,
|
||||
resolveEditorButtons,
|
||||
setFormEditorReadOnly,
|
||||
|
|
@ -34,14 +35,6 @@ type EditingFormHandlerParams = {
|
|||
editingFormInstanceRef: MutableRefObject<any>
|
||||
}
|
||||
|
||||
const getEditingRowIndex = (grid: any) => {
|
||||
const rowKey = grid?.option?.('editing.editRowKey')
|
||||
return {
|
||||
rowKey,
|
||||
rowIndex: rowKey !== undefined ? (grid?.getRowIndexByKey?.(rowKey) ?? -1) : -1,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid hücrelerine script tarafından yazılan değerleri uygular.
|
||||
* CardView gibi `cellValue` API'si olmayan bileşenlerde sessizce atlanır;
|
||||
|
|
@ -52,6 +45,99 @@ const createGridCellWriter = (grid: any, rowIndex: number) => (newData: Record<s
|
|||
Object.keys(newData).forEach((field) => grid.cellValue(rowIndex, field, newData[field]))
|
||||
}
|
||||
|
||||
/**
|
||||
* Zincirde bir alanın aynı değerle ikinci kez tetiklenmesi döngüdür (A → B → A). Derinlik sınırı
|
||||
* yoktur; değeri her turda değişen döngüyü (sayaç gibi) bu emniyet sınırı yakalar.
|
||||
*/
|
||||
const MAX_CHAIN_STEPS = 1000
|
||||
|
||||
const chainStep = (field: string, value: unknown) => {
|
||||
try {
|
||||
return `${field.toLowerCase()}=${JSON.stringify(value ?? null)}`
|
||||
} catch {
|
||||
return `${field.toLowerCase()}=${String(value)}`
|
||||
}
|
||||
}
|
||||
|
||||
type ChainedScriptParams = {
|
||||
gridDto: GridDto | undefined
|
||||
grid: any
|
||||
form: any
|
||||
before: Record<string, any>
|
||||
after: Record<string, any>
|
||||
sourceField?: string
|
||||
/** Zincirin bu ana kadar çalıştırdığı alan=değer adımları; ilk çağrıda verilmez. */
|
||||
visited?: Set<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Script'in `set` ile yazdığı alanların kendi script'lerini çalıştırır; onların yazdıkları da aynı
|
||||
* şekilde devam eder. Grid'e `cellValue` ile yazılan değer editörün değişiklik olayını tetiklemez;
|
||||
* zincir olmadan "TrackInventory false olunca TrackingMethod salt okunur" gibi bağımlı kurallar
|
||||
* kullanıcı alanı elle değiştirene kadar çalışmazdı. Form sayfası (`updateData`) olayı zaten
|
||||
* tetiklediği için yalnızca Grid/Tree'de gerekir.
|
||||
*/
|
||||
const runChainedScripts = ({
|
||||
gridDto,
|
||||
grid,
|
||||
form,
|
||||
before,
|
||||
after,
|
||||
sourceField,
|
||||
visited = new Set(sourceField ? [chainStep(sourceField, after[sourceField])] : []),
|
||||
}: ChainedScriptParams) => {
|
||||
if (!gridDto) return
|
||||
|
||||
const source = String(sourceField ?? '').toLowerCase()
|
||||
const fields = getEditingFormFields(gridDto)
|
||||
const changed = Object.keys(after).filter(
|
||||
(key) => key.toLowerCase() !== source && !Object.is(before?.[key], after[key]),
|
||||
)
|
||||
|
||||
changed.forEach((key) => {
|
||||
const format = fields.find(
|
||||
(field) => String(field.fieldName ?? '').toLowerCase() === key.toLowerCase(),
|
||||
)
|
||||
if (!format?.editorScript || !format.fieldName) return
|
||||
|
||||
const step = chainStep(format.fieldName, after[key])
|
||||
if (visited.has(step) || visited.size >= MAX_CHAIN_STEPS) {
|
||||
reportEditorScriptError(
|
||||
format.fieldName,
|
||||
new Error(`${[...visited].map((item) => item.split('=')[0]).join(' → ')} → ${key}`),
|
||||
)
|
||||
return
|
||||
}
|
||||
visited.add(step)
|
||||
|
||||
const writeToGrid = createGridCellWriter(grid, getEditingRow(grid).rowIndex)
|
||||
executeEditorScript(format.editorScript, {
|
||||
formData: { ...after },
|
||||
e: {
|
||||
component: form,
|
||||
dataField: format.fieldName,
|
||||
value: after[key],
|
||||
scriptEvent: 'valueChanged',
|
||||
},
|
||||
editor: { dataField: format.fieldName, component: grid },
|
||||
runtimeSetEditorReadOnly: (field: string, flag: boolean) =>
|
||||
setFormEditorReadOnly(form, field, flag),
|
||||
setFormData: (next: Record<string, any>) => {
|
||||
writeToGrid(next)
|
||||
runChainedScripts({
|
||||
gridDto,
|
||||
grid,
|
||||
form,
|
||||
before: after,
|
||||
after: next,
|
||||
sourceField: format.fieldName,
|
||||
visited,
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const createEditingFormHandlers = ({
|
||||
getGridDto,
|
||||
getGridInstance,
|
||||
|
|
@ -89,9 +175,13 @@ export const createEditingFormHandlers = ({
|
|||
return values
|
||||
}, {})
|
||||
|
||||
// Popup formu her `cellValue` yazımında yeniden çizilir ve bu olay yeniden gelir; formun
|
||||
// `formData`'sı açılıştaki nesnedir, güncel değerler grid'in düzenlenen satırındadır. Eski
|
||||
// değerle çalışan script kendi verdiği readOnly kararını geri alırdı.
|
||||
const formData = {
|
||||
...editingFormDataRef.current,
|
||||
...(form?.option?.('formData') || {}),
|
||||
...(getEditingRow(grid).rowData || {}),
|
||||
...editorValues,
|
||||
}
|
||||
editingFormDataRef.current = { ...formData }
|
||||
|
|
@ -99,7 +189,7 @@ export const createEditingFormHandlers = ({
|
|||
const runtimeSetEditorReadOnly = (field: string, readOnly: boolean) =>
|
||||
setFormEditorReadOnly(form, field, readOnly)
|
||||
|
||||
const { rowIndex } = getEditingRowIndex(grid)
|
||||
const { rowIndex } = getEditingRow(grid)
|
||||
const writeToGrid = createGridCellWriter(grid, rowIndex)
|
||||
|
||||
/**
|
||||
|
|
@ -152,7 +242,7 @@ export const createEditingFormHandlers = ({
|
|||
setFormData: applyFormData,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Script exec error on contentReady', field.fieldName, err)
|
||||
reportEditorScriptError(field.fieldName!, err)
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
|
|
@ -177,7 +267,7 @@ export const createEditingFormHandlers = ({
|
|||
|
||||
try {
|
||||
const grid = getGridInstance()
|
||||
const { rowIndex } = getEditingRowIndex(grid)
|
||||
const { rowIndex } = getEditingRow(grid)
|
||||
const formData = {
|
||||
...(e.component?.option?.('formData') || {}),
|
||||
[e.dataField]: e.value,
|
||||
|
|
@ -196,10 +286,18 @@ export const createEditingFormHandlers = ({
|
|||
e.component?.option?.('formData', newData)
|
||||
editingFormDataRef.current = { ...newData }
|
||||
writeToGrid(newData)
|
||||
runChainedScripts({
|
||||
gridDto,
|
||||
grid,
|
||||
form: e.component,
|
||||
before: formData,
|
||||
after: newData,
|
||||
sourceField: e.dataField,
|
||||
})
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Script exec error', formField.fieldName, err)
|
||||
reportEditorScriptError(formField.fieldName!, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +306,7 @@ export const createEditingFormHandlers = ({
|
|||
|
||||
type EditorValueChangedScriptParams = {
|
||||
editor: any
|
||||
gridDto: GridDto
|
||||
editorScript: string
|
||||
fieldName?: string
|
||||
editingFormInstanceRef: MutableRefObject<any>
|
||||
|
|
@ -218,6 +317,7 @@ type EditorValueChangedScriptParams = {
|
|||
*/
|
||||
const attachEditorValueChangedScript = ({
|
||||
editor,
|
||||
gridDto,
|
||||
editorScript,
|
||||
fieldName,
|
||||
editingFormInstanceRef,
|
||||
|
|
@ -230,26 +330,31 @@ const attachEditorValueChangedScript = ({
|
|||
|
||||
try {
|
||||
const grid = editor.component
|
||||
const { rowKey, rowIndex } = getEditingRowIndex(grid)
|
||||
const formData = {
|
||||
...(grid.getVisibleRows().find((row: any) => row.key === rowKey)?.data || {}),
|
||||
[editorDataField]: e.value,
|
||||
}
|
||||
const { rowIndex, rowData } = getEditingRow(grid)
|
||||
const formData = { ...(rowData || {}), [editorDataField]: e.value }
|
||||
|
||||
const form = editingFormInstanceRef.current ?? getActiveEditingForm(grid)
|
||||
const writeToGrid = createGridCellWriter(grid, rowIndex)
|
||||
executeEditorScript(editorScript, {
|
||||
formData,
|
||||
e: { ...e, scriptEvent: 'valueChanged' },
|
||||
editor,
|
||||
runtimeSetEditorReadOnly: (field: string, readOnly: boolean) =>
|
||||
setFormEditorReadOnly(
|
||||
editingFormInstanceRef.current ?? getActiveEditingForm(grid),
|
||||
field,
|
||||
readOnly,
|
||||
),
|
||||
setFormData: createGridCellWriter(grid, rowIndex),
|
||||
setFormEditorReadOnly(form, field, readOnly),
|
||||
setFormData: (newData: Record<string, any>) => {
|
||||
writeToGrid(newData)
|
||||
runChainedScripts({
|
||||
gridDto,
|
||||
grid,
|
||||
form,
|
||||
before: formData,
|
||||
after: newData,
|
||||
sourceField: editorDataField,
|
||||
})
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Script exec error', fieldName, err)
|
||||
reportEditorScriptError(fieldName ?? '', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -263,9 +368,8 @@ const attachEditorButtonContext = (editor: any, mode: RowMode) => {
|
|||
|
||||
editor.editorOptions.buttons = withButtonContext(editor.editorOptions.buttons, () => {
|
||||
const grid = editor.component
|
||||
const { rowKey, rowIndex } = getEditingRowIndex(grid)
|
||||
const formData = grid.getVisibleRows().find((row: any) => row.key === rowKey)?.data || {}
|
||||
return { formData, fieldName: editor.dataField, mode, rowKey, rowIndex }
|
||||
const { rowKey, rowIndex, rowData } = getEditingRow(grid)
|
||||
return { formData: rowData || {}, fieldName: editor.dataField, mode, rowKey, rowIndex }
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +437,7 @@ export const prepareEditingEditor = ({
|
|||
if (columnFormat?.editOrderNo != null && columnFormat.editorScript) {
|
||||
attachEditorValueChangedScript({
|
||||
editor,
|
||||
gridDto,
|
||||
editorScript: columnFormat.editorScript,
|
||||
fieldName: columnFormat.fieldName,
|
||||
editingFormInstanceRef,
|
||||
|
|
@ -395,19 +500,31 @@ export const createCustomEditorCellAdapter =
|
|||
|
||||
if (columnFormat?.editOrderNo == null || !columnFormat.editorScript) return
|
||||
|
||||
const { rowIndex } = getEditingRowIndex(grid)
|
||||
const { rowIndex } = getEditingRow(grid)
|
||||
const activeForm = editingFormInstanceRef.current ?? getActiveEditingForm(grid)
|
||||
const formData = { ...rowData, [fieldName]: value }
|
||||
const writeToGrid = createGridCellWriter(grid, rowIndex)
|
||||
try {
|
||||
executeEditorScript(columnFormat.editorScript, {
|
||||
formData: { ...rowData, [fieldName]: value },
|
||||
formData,
|
||||
e: { component: activeForm, dataField: fieldName, value, scriptEvent: 'valueChanged' },
|
||||
editor: { dataField: fieldName, component: grid },
|
||||
runtimeSetEditorReadOnly: (field: string, flag: boolean) =>
|
||||
setFormEditorReadOnly(activeForm, field, flag),
|
||||
setFormData: createGridCellWriter(grid, rowIndex),
|
||||
setFormData: (newData: Record<string, any>) => {
|
||||
writeToGrid(newData)
|
||||
runChainedScripts({
|
||||
gridDto,
|
||||
grid,
|
||||
form: activeForm,
|
||||
before: formData,
|
||||
after: newData,
|
||||
sourceField: fieldName,
|
||||
})
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Script exec error', fieldName, err)
|
||||
reportEditorScriptError(fieldName ?? '', err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue