Satınalma Modülü için Tanımlamalar

This commit is contained in:
Sedat ÖZTÜRK 2026-10-06 10:19:26 +03:00
parent 5ded5d1229
commit 9e83d6dabe
51 changed files with 24003 additions and 82 deletions

View file

@ -218,6 +218,12 @@ Toolbar butonları seçili satırlarla çalışır (`lowcode` §4.9); onay akı
`Type: "custom"` tercih edilir: tercih sunucuda `ListFormCustomization` kaydı olarak `Type: "custom"` tercih edilir: tercih sunucuda `ListFormCustomization` kaydı olarak
(`ListFormCustomizationTypeEnum.GridState = 2`) kullanıcı bazlı saklanır, bu yüzden başka (`ListFormCustomizationTypeEnum.GridState = 2`) kullanıcı bazlı saklanır, bu yüzden başka
tarayıcıdan girildiğinde de korunur. `localStorage`/`sessionStorage` yalnızca o tarayıcıdadır. tarayıcıdan girildiğinde de korunur. `localStorage`/`sessionStorage` yalnızca o tarayıcıdadır.
Aynı `GridState` kaydı görünüm başına ayrı `FilterName` taşır: `list-`, `tree-`, `pivot-`, `todo-`,
`gantt-` ve form sayfası için `form-{StorageKey}` (`useListFormStateStoring`). Form sayfasının state'i
kullanıcının **alan sırasıdır** (`{"fieldOrder": {"Alan": 1, …}}`, grup içi sıra): araç çubuğundaki
**Form Düzeni** paneli (`FormFieldOrderPanel`) alanları sürükleyerek sıralar, form her taşımada canlı
değişir, Kaydet state'e yazar, Sıfırla son kayda, Fabrika Ayarlarına Dön `EditOrderNo` sırasına döner
(state boşaltılır). `EditOrderNo` ve alanın grubu değişmez; panel yalnızca `Enabled` açıkken görünür.
Aynı tablonun diğer türleri: `UserUiFilter = 1` (kaydedilmiş filtre, §2.6), `ServerJoin = 3` ve Aynı tablonun diğer türleri: `UserUiFilter = 1` (kaydedilmiş filtre, §2.6), `ServerJoin = 3` ve
`ServerWhere = 4` — `UserId`/`RoleId` boşsa herkese, doluysa o rol/kullanıcıya sunucu tarafında `ServerWhere = 4` — `UserId`/`RoleId` boşsa herkese, doluysa o rol/kullanıcıya sunucu tarafında
eklenen join ve `WHERE` parçası (`SelectQueryManager`; `ServerWhere` token'ları `DefaultValueHelper` eklenen join ve `WHERE` parçası (`SelectQueryManager`; `ServerWhere` token'ları `DefaultValueHelper`
@ -291,7 +297,7 @@ Düzenleme formundaki grupların listesi. Alanların hangi gruba düştüğü `L
| Anahtar | Varsayılan | Anlam | | Anahtar | Varsayılan | Anlam |
| --- | --- | --- | | --- | --- | --- |
| `Order` | `1` | Grubun sırası (`EditGroupOrderNo` ile eşleşir) | | `Order` | `1` | Grubun sırası (`EditGroupOrderNo` ile eşleşir) |
| `ItemType` | `"group"` | `group` (kullanılan) \| `tabbed` \| `simple` \| `empty` \| `button` | | `ItemType` | `"group"` | `group` (kullanılan) \| `tabbed` \| `simple` \| `empty` \| `button` — bütün `tabbed` gruplar ilk `tabbed` grubun yerinde tek sekme şeridi olur (liste popup'ı ve form sayfası aynı) |
| `Caption` | — | Grup başlığı (dil anahtarı) | | `Caption` | — | Grup başlığı (dil anahtarı) |
| `ColCount` | `2` | Grup içi kolon sayısı | | `ColCount` | `2` | Grup içi kolon sayısı |
| `ColSpan` | `2` | Grubun formda kapladığı kolon | | `ColSpan` | `2` | Grubun formda kapladığı kolon |

View file

@ -1354,7 +1354,7 @@ anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir.
| `dxCheckBox` | Evet/hayır | Boolean (3) | `text` (kutunun yanındaki etiket) | | `dxCheckBox` | Evet/hayır | Boolean (3) | `text` (kutunun yanındaki etiket) |
| `dxSwitch` | Evet/hayır (anahtar) | Boolean (3) | `switchedOnText`, `switchedOffText` | | `dxSwitch` | Evet/hayır (anahtar) | Boolean (3) | `switchedOnText`, `switchedOffText` |
| `dxRadioGroup` | Az sayıda seçenek | String/Int32 | `layout` (`horizontal`/`vertical`) + **lookup zorunlu** | | `dxRadioGroup` | Az sayıda seçenek | String/Int32 | `layout` (`horizontal`/`vertical`) + **lookup zorunlu** |
| `dxSelectBox` | Tek seçim (az kayıt) | FK tipi | `acceptCustomValue`, `searchEnabled`, `minSearchLength`, `searchExpr`, `searchMode`, `searchTimeout`, `noDataText`, `showDataBeforeSearch` + **lookup zorunlu** | | `dxSelectBox` | Tek seçim (az kayıt) | FK tipi | `acceptCustomValue`, `searchEnabled` (lookup'lı alanda varsayılan açık — liste popup'ı ve form sayfası), `minSearchLength`, `searchExpr`, `searchMode`, `searchTimeout`, `noDataText`, `showDataBeforeSearch` + **lookup zorunlu** |
| `dxLookup` | Tek seçim (çok kayıt, mobil dostu) | FK tipi | `dropDownOptions.*`, `searchEnabled`, `applyValueMode` + **lookup zorunlu** | | `dxLookup` | Tek seçim (çok kayıt, mobil dostu) | FK tipi | `dropDownOptions.*`, `searchEnabled`, `applyValueMode` + **lookup zorunlu** |
| `dxDropDownBox` | Özel içerikli açılır kutu | FK tipi | `dropDownOptions.width/height/hideOnOutsideClick`, `deferRendering`, `openOnFieldClick` | | `dxDropDownBox` | Özel içerikli açılır kutu | FK tipi | `dropDownOptions.width/height/hideOnOutsideClick`, `deferRendering`, `openOnFieldClick` |
| `dxGridBox` | Çok kolonlu seçim ızgarası | FK tipi | `columns`\*, `filterRowVisible`\*, `selectionMode`\*, `acceptCustomValue`\* + **lookup zorunlu** | | `dxGridBox` | Çok kolonlu seçim ızgarası | FK tipi | `columns`\*, `filterRowVisible`\*, `selectionMode`\*, `acceptCustomValue`\* + **lookup zorunlu** |
@ -1462,6 +1462,12 @@ cascade eksik yetenek değildir ve analizde `Ready to Start`'ı engellemez. Tam
| `CascadeFilterOperator` | `StaticData` cascade filtresinin operatörü; varsayılan `=` | | `CascadeFilterOperator` | `StaticData` cascade filtresinin operatörü; varsayılan `=` |
| `CascadeEmptyFields` | **Ebeveyn alanda** doldurulur; bu alan değişince boşaltılacak çocuk alanlar, virgülle. Ara halka (İl) hem çocuk hem ebeveyn olduğu için iki tarafı birden taşır | | `CascadeEmptyFields` | **Ebeveyn alanda** doldurulur; bu alan değişince boşaltılacak çocuk alanlar, virgülle. Ara halka (İl) hem çocuk hem ebeveyn olduğu için iki tarafı birden taşır |
Cascade liste popup'ında ve form sayfasında (`FormDevExpress`) aynı davranır. Form sayfasında çocuk
yalnızca ebeveynin değeri **gerçekten değiştiğinde** temizlenir ve listesi yeni ebeveyn değeriyle
yeniden kurulur; formun aynı değeri tekrar bildirmesi (veri forma geri verilince) yok sayılır.
Adres zincirinin hazır örneği Kiracılar ekranıdır: Ülke › İl › İlçe › Mahalle; mahalle lookup'ı
`PostalCode`'u `group` olarak döndürür ve `copy("Group", "PostalCode")` tarifi posta kodunu doldurur.
**`LookupQuery` biçimleri:** **`LookupQuery` biçimleri:**
| Tip | Biçim | Not | | Tip | Biçim | Not |
@ -1997,7 +2003,16 @@ Wizard Manager'daki talep silmesini kullanır.
`AllowUpdating: true` ile üretilir: kayıt yokken sekme "Yeni" düğmesi gösterir, kayıt varken `AllowUpdating: true` ile üretilir: kayıt yokken sekme "Yeni" düğmesi gösterir, kayıt varken
yalnızca düzenleme açıktır ve ikinci kayıt açılmaz (`FormView` + `FormButtons`, `isSubForm`). yalnızca düzenleme açıktır ve ikinci kayıt açılmaz (`FormView` + `FormButtons`, `isSubForm`).
`AllowAdding` kapalıysa sekme boş "kayıt bulunamadı" mesajında kalır ve kayıt girilemez. `AllowAdding` kapalıysa sekme boş "kayıt bulunamadı" mesajında kalır ve kayıt girilemez.
`AllowDeleting` istek açıkça istemedikçe kapalıdır. `AllowDeleting` istek açıkça istemedikçe kapalıdır. Sekme okuma yetkisi olmayan kullanıcıda da
"kayıt bulunamadı" gösterir; yeni alt formun yetkisi role verilmeden sekme boş görünür.
- **Aynı satırı sekmelere bölmek (`Id → Id`):** Kalabalık bir kartın bölümleri (Sağlık Kartları,
Tedarikçiler'in Satış / Satınalma / Muhasebe… sekmeleri) ana ekranla **aynı tabloya** bağlı,
menüsüz `Form` sekmeleridir; her biri yalnızca kendi alanlarını taşır, ilişki `Id → Id`'dir. Satır
ana ekrandan eklendiği için bu sekmelerde yalnızca `AllowUpdating` açıktır — `AllowAdding` ve
`AllowDeleting` kapalı yazılır (eklemek tabloya ikinci bir ana kayıt açardı). Taşınan alanlar ana
ekranın `Fields`'ından çıkar; yeni kayıt önce ana formda oluşur, sekmeler kayıttan sonra açılır.
Tek formda sekme şeridi isteniyorsa (alt form yerine) grupların `ItemType`'ı `tabbed` yazılır
(`lowcode-reference` §3.2).
- **`TabType: "List"` — ana kayda bağlı çok kayıt:** `AllowAdding`/`AllowUpdating`/`AllowDeleting` - **`TabType: "List"` — ana kayda bağlı çok kayıt:** `AllowAdding`/`AllowUpdating`/`AllowDeleting`
açık gelir; ilişki kolonu (`ChildFieldName`) yeni satıra ana kayıttan yazılır. açık gelir; ilişki kolonu (`ChildFieldName`) yeni satıra ana kayıttan yazılır.
- **Widget:** Alt form oluşturulurken widget oluşturulmaz — menüsüz ya da menülü, alt form olarak - **Widget:** Alt form oluşturulurken widget oluşturulmaz — menüsüz ya da menülü, alt form olarak

View file

@ -400,7 +400,7 @@ Ekran davranışı büyük ölçüde JSON kolonlarında saklanır. Öne çıkanl
| `PermissionJson` | Ekranın create/read/update/delete/export/import/note yetki eşlemesi. | | `PermissionJson` | Ekranın create/read/update/delete/export/import/note yetki eşlemesi. |
| `EditingOptionJson` / `EditingFormJson` | Düzenleme modu (row/cell/batch/form/popup) ve düzenleme formu düzeni. | | `EditingOptionJson` / `EditingFormJson` | Düzenleme modu (row/cell/batch/form/popup) ve düzenleme formu düzeni. |
| `FilterRowJson`, `HeaderFilterJson`, `FilterPanelJson`, `SearchPanelJson`, `GroupPanelJson` | Filtreleme, arama ve gruplama davranışı. | | `FilterRowJson`, `HeaderFilterJson`, `FilterPanelJson`, `SearchPanelJson`, `GroupPanelJson` | Filtreleme, arama ve gruplama davranışı. |
| `ColumnOptionJson`, `PagerOptionJson`, `SelectionJson`, `StateStoringJson` | Sütun, sayfalama, seçim ve durum saklama ayarları. | | `ColumnOptionJson`, `PagerOptionJson`, `SelectionJson`, `StateStoringJson` | Sütun, sayfalama, seçim ve durum saklama ayarları. Durum kullanıcı bazlıdır ve grid, pivot, ağaç, gantt, todo ile form sayfasının alan sırasını (Form Düzeni) kapsar. |
| `Insert/Update/DeleteCommand` (+ `Before` / `After`) | CRUD komutları ve öncesi/sonrası çalışacak SQL kancaları. | | `Insert/Update/DeleteCommand` (+ `Before` / `After`) | CRUD komutları ve öncesi/sonrası çalışacak SQL kancaları. |
| `Insert/Update/DeleteServiceAddress` | Varsayılan olarak `list-form-data/*`; özel endpoint'e yönlendirilebilir. | | `Insert/Update/DeleteServiceAddress` | Varsayılan olarak `list-form-data/*`; özel endpoint'e yönlendirilebilir. |
| `SeedFilePath` | Doluysa seed sırasında listenin verisi seed klasöründeki bu hedeften geri yüklenir: `.json` ile biten değer tek dosya (`data/{ListFormCode}.json`), uzantısız değer satır başına dosya tutan klasördür (`data/{ListFormCode}/{anahtar}.json`). | | `SeedFilePath` | Doluysa seed sırasında listenin verisi seed klasöründeki bu hedeften geri yüklenir: `.json` ile biten değer tek dosya (`data/{ListFormCode}.json`), uzantısız değer satır başına dosya tutan klasördür (`data/{ListFormCode}/{anahtar}.json`). |
@ -437,7 +437,10 @@ ve atanan kullanıcı alanlarından kurulur.
`dxDropDownBox`, `dxGridBox`, `dxAutocomplete`, `dxColorBox`, `dxHtmlEditor`, `dxSlider`, `dxDropDownBox`, `dxGridBox`, `dxAutocomplete`, `dxColorBox`, `dxHtmlEditor`, `dxSlider`,
`dxRangeSlider` — ayrıca görsel yükleme/görüntüleme editörleri. `dxRangeSlider` — ayrıca görsel yükleme/görüntüleme editörleri.
- **Lookup kaynakları:** `StaticData`, `Query`, `WebService`; ebeveyn–çocuk (cascade) desteklidir. - **Lookup kaynakları:** `StaticData`, `Query`, `WebService`; ebeveyn–çocuk (cascade) desteklidir.
- **Doğrulama:** `ValidationRuleJson` ile required/range/pattern/custom kuralları. Cascade liste popup'ında ve form sayfasında (`/admin/form/...`, `FormDevExpress`) aynı çalışır.
Lookup'lı seçim kutusunda yazarak arama ikisinde de varsayılan açıktır; alanın `EditorOptions`'ında
`{"searchEnabled": false}` kapatır.
- **Doğrulama:** `ValidationRuleJson` ile required/range/pattern/custom kuralları. Kurallar liste popup'ında ve form sayfasında (`/admin/form/...`) aynı uygulanır; maske (`mask`) `EditorOptions` ile verilir.
- **Editör script:** `EditorScript` alanı ile alan değişiminde çalışan JS (örn. tarih farkından - **Editör script:** `EditorScript` alanı ile alan değişiminde çalışan JS (örn. tarih farkından
gün hesaplama, miktar × birim fiyat toplamı). Çalıştırma `utils/editorScriptRuntime.ts` üzerinden yapılır. gün hesaplama, miktar × birim fiyat toplamı). Çalıştırma `utils/editorScriptRuntime.ts` üzerinden yapılır.
- **Editör seçenekleri:** `EditorOptions` alanı DevExtreme editörüne geçirilen JSON'dur - **Editör seçenekleri:** `EditorOptions` alanı DevExtreme editörüne geçirilen JSON'dur
@ -1262,6 +1265,7 @@ başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını g
| Olay Günlüğü | İK › Olay Günlüğü (`App.Hr.EventLog`) | [HrEventLog.md](configs/seeds/docs/modules/HrEventLog.md) | | Olay Günlüğü | İK › Olay Günlüğü (`App.Hr.EventLog`) | [HrEventLog.md](configs/seeds/docs/modules/HrEventLog.md) |
| Personel Kaydı (özlük, sözleşme, eğitim, aile, mali bilgiler) | Kimlik › Kullanıcılar › detay | [HrPersonnel.md](configs/seeds/docs/modules/HrPersonnel.md) | | Personel Kaydı (özlük, sözleşme, eğitim, aile, mali bilgiler) | Kimlik › Kullanıcılar › detay | [HrPersonnel.md](configs/seeds/docs/modules/HrPersonnel.md) |
| Intranet (pano, duyuru, anket, sosyal duvar, etkinlik, doküman, mesajlaşma) | İK › Intranet (`App.Hr.Intranet`) | [HrIntranet.md](configs/seeds/docs/modules/HrIntranet.md) | | Intranet (pano, duyuru, anket, sosyal duvar, etkinlik, doküman, mesajlaşma) | İK › Intranet (`App.Hr.Intranet`) | [HrIntranet.md](configs/seeds/docs/modules/HrIntranet.md) |
| Satınalma Tanımlamaları (ödeme/teslimat koşulları, tedarikçi kartı ve sekmeleri) | Satınalma › Tanımlamalar (`App.Prc.Definitions`) | [PrcDefinitions.md](configs/seeds/docs/modules/PrcDefinitions.md) |
--- ---
@ -1836,6 +1840,9 @@ Hangfire panosu geliştirmede serbest, üretimde `AbpHangfireAuthorizationFilter
araçları §13.9. araçları §13.9.
- **Dinamik raporlar** (`HttpApi.Host/PredefinedReports`): ListForm tanımından otomatik üretilen - **Dinamik raporlar** (`HttpApi.Host/PredefinedReports`): ListForm tanımından otomatik üretilen
grid, form ve ağaç raporları — grid durumunu (sütun sırası, gruplama, filtre) rapora yansıtır. grid, form ve ağaç raporları — grid durumunu (sütun sırası, gruplama, filtre) rapora yansıtır.
Form raporu (`DynamicFormReport`) alt formları ekrandaki sekme sırasıyla basar: `List` sekmesi grid
alt raporu, `Form` sekmesi (aynı satırı bölen `Id → Id` sekmeleri dahil) form alt raporu olur; iç içe
form sekmeleri en fazla 3 seviye iner.
- Linux konteynerlerde font sorunlarını önlemek için `DevExpressFontConfiguration` devrededir. - Linux konteynerlerde font sorunlarını önlemek için `DevExpressFontConfiguration` devrededir.
--- ---

View file

@ -34,8 +34,15 @@ public class DynamicFormReport : XtraReport
private const float RowHeight = 24; private const float RowHeight = 24;
private const float ColumnGap = 10; private const float ColumnGap = 10;
private static readonly System.Drawing.Color FieldBorderColor = System.Drawing.Color.FromArgb(226, 232, 240); private static readonly System.Drawing.Color FieldBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
private const string SubReportDepthParameterName = "subReportDepth";
// Form sekmesi kendi alt formlarini da basar (Saglik Karti › Genel › Is Gecmisi); dongusel tanimda
// rapor sonsuza inmesin diye derinlik sinirlidir.
private const int MaxSubReportDepth = 3;
private static IServiceScopeFactory _scopeFactory; private static IServiceScopeFactory _scopeFactory;
public bool RequiresLandscapeLayout { get; private set; }
public static void SetScopeFactory(IServiceScopeFactory scopeFactory) public static void SetScopeFactory(IServiceScopeFactory scopeFactory)
{ {
_scopeFactory = scopeFactory; _scopeFactory = scopeFactory;
@ -82,11 +89,15 @@ public class DynamicFormReport : XtraReport
DynamicReportImageHelper.GetParameter(parameters, "filter")); DynamicReportImageHelper.GetParameter(parameters, "filter"));
var formGroups = GetFormGroups(listForm, reportFields).ToList(); var formGroups = GetFormGroups(listForm, reportFields).ToList();
var listSubForms = GetListSubForms(listForm, row).ToList(); var depth = int.TryParse(DynamicReportImageHelper.GetParameter(parameters, SubReportDepthParameterName), out var parsedDepth)
var subReports = await CreateSubReportsAsync(listSubForms); ? parsedDepth
: 0;
var subForms = depth < MaxSubReportDepth ? GetSubForms(listForm, row).ToList() : [];
var subReports = await CreateSubReportsAsync(subForms, depth + 1);
var requiresLandscape = HasWideFormLayout(formGroups) || var requiresLandscape = HasWideFormLayout(formGroups) ||
subReports.Any(x => x.Report.RequiresLandscapeLayout); subReports.Any(x => x.RequiresLandscape);
RequiresLandscapeLayout = requiresLandscape;
ConfigurePage(requiresLandscape); ConfigurePage(requiresLandscape);
ConfigureDataSource(selectQuery, connectionString, dataSourceType); ConfigureDataSource(selectQuery, connectionString, dataSourceType);
BuildLayout(listForm, formGroups, row, subReports, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport); BuildLayout(listForm, formGroups, row, subReports, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration), currentUser.UserName, isSubReport);
@ -295,14 +306,29 @@ public class DynamicFormReport : XtraReport
return formGroups.Any(x => GetSafeColumnCount(x.ColumnCount) >= LandscapeColumnThreshold); return formGroups.Any(x => GetSafeColumnCount(x.ColumnCount) >= LandscapeColumnThreshold);
} }
/// <summary>
/// Alt formlar ekrandaki sekme sirasiyla basilir: <c>List</c> sekmesi grid raporu, <c>Form</c> sekmesi
/// (ana kayda bagli tek kayit; ayni satiri bolumlere ayiran Id → Id sekmeleri dahil) form raporu olur.
/// </summary>
private static async Task<List<SubReportSource>> CreateSubReportsAsync( private static async Task<List<SubReportSource>> CreateSubReportsAsync(
IReadOnlyList<(SubFormDto Form, string Filter)> subForms) IReadOnlyList<(SubFormDto Form, string Filter, object ParentValue)> subForms,
int depth)
{ {
var reports = new List<SubReportSource>(); var reports = new List<SubReportSource>();
foreach (var subForm in subForms) foreach (var subForm in subForms)
{ {
reports.Add(new SubReportSource( var parameters = CreateSubReportParameters(subForm.Form, subForm.Filter);
await DynamicGridReport.CreateAsync(CreateSubReportParameters(subForm.Form, subForm.Filter)))); if (string.Equals(subForm.Form.TabType, ListFormTabTypeEnum.Form, StringComparison.OrdinalIgnoreCase))
{
parameters["id"] = Convert.ToString(subForm.ParentValue, CultureInfo.InvariantCulture);
parameters[SubReportDepthParameterName] = depth.ToString(CultureInfo.InvariantCulture);
var formReport = await CreateAsync(parameters);
reports.Add(new SubReportSource(formReport, formReport.RequiresLandscapeLayout));
continue;
}
var gridReport = await DynamicGridReport.CreateAsync(parameters);
reports.Add(new SubReportSource(gridReport, gridReport.RequiresLandscapeLayout));
} }
return reports; return reports;
@ -641,7 +667,7 @@ public class DynamicFormReport : XtraReport
} }
} }
private static IEnumerable<(SubFormDto Form, string Filter)> GetListSubForms( private static IEnumerable<(SubFormDto Form, string Filter, object ParentValue)> GetSubForms(
ListForm listForm, ListForm listForm,
IDictionary<string, object> row) IDictionary<string, object> row)
{ {
@ -660,7 +686,9 @@ public class DynamicFormReport : XtraReport
yield break; yield break;
} }
foreach (var subForm in subForms.Where(x => string.Equals(x.TabType, ListFormTabTypeEnum.List, StringComparison.OrdinalIgnoreCase))) foreach (var subForm in subForms.Where(x =>
string.Equals(x.TabType, ListFormTabTypeEnum.List, StringComparison.OrdinalIgnoreCase) ||
string.Equals(x.TabType, ListFormTabTypeEnum.Form, StringComparison.OrdinalIgnoreCase)))
{ {
var relation = subForm.Relation? var relation = subForm.Relation?
.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) && .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) &&
@ -675,7 +703,7 @@ public class DynamicFormReport : XtraReport
continue; continue;
} }
yield return (subForm, CreateFilter(relation.ChildFieldName, "=", parentValue)); yield return (subForm, CreateFilter(relation.ChildFieldName, "=", parentValue), parentValue);
} }
} }
@ -765,7 +793,7 @@ public class DynamicFormReport : XtraReport
private sealed record FieldWithEditor(ListFormField Field, string EditorType, int ColumnSpan); private sealed record FieldWithEditor(ListFormField Field, string EditorType, int ColumnSpan);
private sealed record FormGroup(string Caption, int ColumnCount, IReadOnlyList<FormReportField> Fields); private sealed record FormGroup(string Caption, int ColumnCount, IReadOnlyList<FormReportField> Fields);
private sealed record FormLayoutItem(FormReportField Field, int ColumnIndex, int ColumnSpan); private sealed record FormLayoutItem(FormReportField Field, int ColumnIndex, int ColumnSpan);
private sealed record SubReportSource(DynamicGridReport Report); private sealed record SubReportSource(XtraReport Report, bool RequiresLandscape);
private sealed record FormReportField( private sealed record FormReportField(
ListFormField Field, ListFormField Field,
string BindingName, string BindingName,

View file

@ -18,6 +18,11 @@
"Name": "App.Hr", "Name": "App.Hr",
"DisplayName": "App.Hr", "DisplayName": "App.Hr",
"Order": 3 "Order": 3
},
{
"Name": "App.Prc",
"DisplayName": "App.Prc",
"Order": 4
} }
] ]
} }

View file

@ -12163,6 +12163,926 @@
"IsEnabled": true, "IsEnabled": true,
"MultiTenancySide": 3, "MultiTenancySide": 3,
"Modules": "Sas|Hr" "Modules": "Sas|Hr"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Definitions",
"ParentName": null,
"DisplayName": "App.Prc.Definitions",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.PaymentTerm",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Create",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Update",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Delete",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Export",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Import",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentTerm.Note",
"ParentName": "App.Prc.PaymentTerm",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.PaymentMethod",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Create",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Update",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Delete",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Export",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Import",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PaymentMethod.Note",
"ParentName": "App.Prc.PaymentMethod",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.Incoterm",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Create",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Update",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Delete",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Export",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Import",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Incoterm.Note",
"ParentName": "App.Prc.Incoterm",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.DeliveryMethod",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Create",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Update",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Delete",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Export",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Import",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.DeliveryMethod.Note",
"ParentName": "App.Prc.DeliveryMethod",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.SupplierTag",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Create",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Update",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Delete",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Export",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Import",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierTag.Note",
"ParentName": "App.Prc.SupplierTag",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierContact",
"Order": 12,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Create",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Update",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Delete",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Export",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Import",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierContact.Note",
"ParentName": "App.Prc.SupplierContact",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierBankAccount",
"Order": 13,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Create",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Update",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Delete",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Export",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Import",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierBankAccount.Note",
"ParentName": "App.Prc.SupplierBankAccount",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier",
"ParentName": "App.Prc.Definitions",
"DisplayName": "App.Prc.Supplier",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Create",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Update",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Delete",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Export",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Import",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.Supplier.Note",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierSales",
"Order": 7,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Create",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Update",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Delete",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Export",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Import",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierSales.Note",
"ParentName": "App.Prc.SupplierSales",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierPurchasing",
"Order": 8,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Create",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Update",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Delete",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Export",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Import",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierPurchasing.Note",
"ParentName": "App.Prc.SupplierPurchasing",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierFinancial",
"Order": 9,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Create",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Update",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Delete",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Export",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Import",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierFinancial.Note",
"ParentName": "App.Prc.SupplierFinancial",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierAccounting",
"Order": 10,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Create",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Update",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Delete",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Export",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Import",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierAccounting.Note",
"ParentName": "App.Prc.SupplierAccounting",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes",
"ParentName": "App.Prc.Supplier",
"DisplayName": "App.Prc.SupplierNotes",
"Order": 11,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Create",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Update",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Delete",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Export",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Import",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.SupplierNotes.Note",
"ParentName": "App.Prc.SupplierNotes",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
} }
] ]
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -2283,6 +2283,94 @@
"IsDisabled": false, "IsDisabled": false,
"ElementId": null, "ElementId": null,
"ModuleId": "Hr" "ModuleId": "Hr"
},
{
"ParentCode": null,
"Code": "App.Prc",
"DisplayName": "App.Prc",
"Order": 5,
"Url": null,
"Icon": "FcShop",
"RequiredPermissionName": null,
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc",
"Code": "App.Prc.Definitions",
"DisplayName": "App.Prc.Definitions",
"Order": 1,
"Url": null,
"Icon": "FcSettings",
"RequiredPermissionName": null,
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.PaymentTerm",
"DisplayName": "App.Prc.PaymentTerm",
"Order": 1,
"Url": "/admin/list/App.Prc.PaymentTerm",
"Icon": "FcCalendar",
"RequiredPermissionName": "App.Prc.PaymentTerm",
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.PaymentMethod",
"DisplayName": "App.Prc.PaymentMethod",
"Order": 2,
"Url": "/admin/list/App.Prc.PaymentMethod",
"Icon": "FcMoneyTransfer",
"RequiredPermissionName": "App.Prc.PaymentMethod",
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.Incoterm",
"DisplayName": "App.Prc.Incoterm",
"Order": 3,
"Url": "/admin/list/App.Prc.Incoterm",
"Icon": "FcShipped",
"RequiredPermissionName": "App.Prc.Incoterm",
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.DeliveryMethod",
"DisplayName": "App.Prc.DeliveryMethod",
"Order": 4,
"Url": "/admin/list/App.Prc.DeliveryMethod",
"Icon": "FcInTransit",
"RequiredPermissionName": "App.Prc.DeliveryMethod",
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.SupplierTag",
"DisplayName": "App.Prc.SupplierTag",
"Order": 5,
"Url": "/admin/list/App.Prc.SupplierTag",
"Icon": "FcBookmark",
"RequiredPermissionName": "App.Prc.SupplierTag",
"IsDisabled": false,
"ModuleId": "Prc"
},
{
"ParentCode": "App.Prc.Definitions",
"Code": "App.Prc.Supplier",
"DisplayName": "App.Prc.Supplier",
"Order": 6,
"Url": "/admin/list/App.Prc.Supplier",
"Icon": "FcFactory",
"RequiredPermissionName": "App.Prc.Supplier",
"IsDisabled": false,
"ModuleId": "Prc"
} }
] ]
} }

View file

@ -19,6 +19,10 @@
{ {
"Id": "Hr", "Id": "Hr",
"Name": "Hr" "Name": "Hr"
},
{
"Id": "Prc",
"Name": "Prc"
} }
] ]
} }

View file

@ -0,0 +1,23 @@
{
"ListFormCode": "App.Prc.DeliveryMethod",
"KeyFieldName": "Id",
"GeneratedAt": "2026-10-05T19:15:23.2368208Z",
"Order": 0,
"Rows": [
{
"Name": "Yerel teslimat",
"IsActive": true,
"Id": "a32f6724-7719-53c3-e31e-3a241faad5e4"
},
{
"Name": "Kargo",
"IsActive": true,
"Id": "55150f40-09e9-d848-6d93-3a241faad5e4"
},
{
"Name": "Kurye",
"IsActive": true,
"Id": "f130f748-9f91-6e8c-0539-3a241faad5e4"
}
]
}

View file

@ -0,0 +1,56 @@
{
"ListFormCode": "App.Prc.Incoterm",
"KeyFieldName": "Id",
"GeneratedAt": "2026-10-05T19:15:06.9187087Z",
"Order": 0,
"Rows": [
{
"Code": "EXW",
"Name": "İşyerinde Teslim",
"IsActive": true,
"Id": "6debaba4-ef7d-b685-5c95-3a241faa9626"
},
{
"Code": "FCA",
"Name": "Taşıyıcıya Masrafsız",
"IsActive": true,
"Id": "8fe85a76-1339-e376-941d-3a241faa9626"
},
{
"Code": "FAS",
"Name": "Gemi Doğrultusunda Masrafsız",
"IsActive": true,
"Id": "ce33b9ae-c512-92c9-af29-3a241faa9626"
},
{
"Code": "FOB",
"Name": "Gemide Masrafsız",
"IsActive": true,
"Id": "66d19086-0f33-c10c-1551-3a241faa9626"
},
{
"Code": "CFR",
"Name": "Masraflar ve Navlun",
"IsActive": true,
"Id": "af7b612c-c660-5a9b-1a11-3a241faa9626"
},
{
"Code": "CIF",
"Name": "Masraflar, Sigorta ve Navlun",
"IsActive": true,
"Id": "077e7115-fbc5-4a31-05d1-3a241faa9626"
},
{
"Code": "CPT",
"Name": "Taşıma Ödenmiş Olarak",
"IsActive": true,
"Id": "d0e49be0-29ac-29c7-14e4-3a241faa9626"
},
{
"Code": "CIP",
"Name": "Taşıma ve Sigorta Ödenmiş Olarak",
"IsActive": true,
"Id": "205d23d2-b542-3f7e-0385-3a241faa9626"
}
]
}

View file

@ -0,0 +1,56 @@
{
"ListFormCode": "App.Prc.PaymentMethod",
"KeyFieldName": "Id",
"GeneratedAt": "2026-10-05T19:14:47.9190173Z",
"Order": 0,
"Rows": [
{
"Name": "Manual Payment (Banka)",
"Journal": "Banka",
"IsActive": true,
"Id": "48536396-3e9d-4ab9-8b47-3a241faa4bee"
},
{
"Name": "Checks (Banka)",
"Journal": "Banka",
"IsActive": true,
"Id": "c86d9495-1414-9802-163a-3a241faa4bee"
},
{
"Name": "Manual Payment (Cash Furn. Shop)",
"Journal": "Cash Furn. Shop",
"IsActive": true,
"Id": "9a62d59a-f2ce-3641-74a2-3a241faa4bee"
},
{
"Name": "Manual Payment (Cash Clothes Shop)",
"Journal": "Cash Clothes Shop",
"IsActive": true,
"Id": "019a45c2-020a-5676-7e96-3a241faa4bee"
},
{
"Name": "Manual Payment (Cash Bakery)",
"Journal": "Cash Bakery",
"IsActive": true,
"Id": "4a6e45f8-89b9-2d0d-686a-3a241faa4bee"
},
{
"Name": "Manual Payment (Cash Restaurant)",
"Journal": "Cash Restaurant",
"IsActive": true,
"Id": "2359e25d-a4cd-f586-9d8b-3a241faa4bee"
},
{
"Name": "Manual Payment (Cash Bar)",
"Journal": "Cash Bar",
"IsActive": true,
"Id": "79834259-efc5-650e-b380-3a241faa4bee"
},
{
"Name": "NACHA (Banka)",
"Journal": "Banka",
"IsActive": true,
"Id": "d634c059-3a19-ea3b-679f-3a241faa4bee"
}
]
}

View file

@ -0,0 +1,60 @@
{
"ListFormCode": "App.Prc.PaymentTerm",
"KeyFieldName": "Id",
"GeneratedAt": "2026-10-05T19:14:28.0228729Z",
"Order": 0,
"Rows": [
{
"Name": "Peşin Ödeme",
"DueDays": 0,
"Description": "Teslimde peşin ödeme",
"IsActive": true,
"Id": "d895cbf9-42de-c7a3-8486-3a241fa9fe36"
},
{
"Name": "15 Gün",
"DueDays": 15,
"IsActive": true,
"Id": "6307ad22-aff8-e11d-31a0-3a241fa9fe36"
},
{
"Name": "21 Gün",
"DueDays": 21,
"IsActive": true,
"Id": "28e18634-b01b-d6da-ca35-3a241fa9fe36"
},
{
"Name": "30 Gün",
"DueDays": 30,
"IsActive": true,
"Id": "243ddb60-fc2d-3c31-6e79-3a241fa9fe36"
},
{
"Name": "45 Gün",
"DueDays": 45,
"IsActive": true,
"Id": "68b3fa10-f61b-164d-9fb6-3a241fa9fe36"
},
{
"Name": "Sonraki Ayın Sonu",
"DueDays": 30,
"Description": "Fatura tarihini izleyen ayın son günü",
"IsActive": true,
"Id": "4760432d-dde9-efe9-5591-3a241fa9fe36"
},
{
"Name": "Gelecek Ayın Sonundan 10 gün sonra",
"DueDays": 40,
"Description": "İzleyen ayın sonundan 10 gün sonra",
"IsActive": true,
"Id": "2fa512c5-49fc-3338-5c5f-3a241fa9fe36"
},
{
"Name": "%30 Peşin, Bakiye 60 Gün Sonra",
"DueDays": 60,
"Description": "%30 peşin, kalan %70 60 gün sonra",
"IsActive": true,
"Id": "4fdace7c-556d-9e1f-f8fd-3a241fa9fe36"
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,26 @@
{
"ListFormCode": "App.Prc.SupplierTag",
"KeyFieldName": "Id",
"GeneratedAt": "2026-10-05T19:15:40.5102342Z",
"Order": 0,
"Rows": [
{
"Name": "Vendor",
"Color": "#22c55e",
"IsActive": true,
"Id": "93ef7dcb-c397-66d0-4eb0-3a241fab195e"
},
{
"Name": "Desk Manufacturers",
"Color": "#3b82f6",
"IsActive": true,
"Id": "10339df4-976e-c3ea-9a0c-3a241fab195e"
},
{
"Name": "Office Supplies",
"Color": "#f59e0b",
"IsActive": true,
"Id": "9ed87c26-95c1-2a48-2b9c-3a241fab195e"
}
]
}

View file

@ -0,0 +1,412 @@
# Tanımlamalar (`App.Prc.Definitions`)
> Bu dosya ilk deploy'da otomatik açıldı. "Ek — Ekran envanteri" bölümü wizard dosyalarından üretilir, elle düzenlenmez.
## 1. Amaç ve kapsam
Satınalma modülünün ana verisi: tedarikçi kartları ile satınalma belgelerinde kullanılacak ödeme koşulu, ödeme yöntemi, teslimat koşulu (Incoterm), teslim yöntemi ve tedarikçi etiketi tanımları. Kullanıcılar satınalma ve muhasebe personelidir. Satınalma talebi, teklif, sipariş ve fatura belgeleri bu modülün kapsamında değildir; bu ekranlar onların lookup kaynağıdır.
## 2. Analiz ve kararlar
- Modül kodu `Prc`, tablo öneki `Prc_T_`; tüm ekranlar talep gereği Satınalma › Tanımlamalar altında.
- Tedarikçi listesinde yalnızca firma/şahıs satırları durur; Odoo'daki "Firma, Kişi" satırları Kontaklar alt formundadır.
- Odoo'ya özgü 1099 Box, "Firma (herkes tarafından görülebilir)", Aktiviteler/sayaç ikonları ve Invoice follow-ups uyarısı alınmadı.
- "Adrese dayalı hesaplama" butonu yok (geocoding servisi yok). Enlem/boylam kolonları tabloda durur ama formdan kaldırıldı; ekranda girilmez.
- Hesap planı tablosu olmadığından Alacak/Borç hesabı metin alanıdır; Fiyat Listesi para birimi seçimidir.
- Etiketler `Prc_T_SupplierTag` adlarından dxTagBox ile seçilir ve `Tags` kolonunda tutulur.
- Platform kuralı gereği: enlem/boylam `decimal(10,7)` yerine `float` açıldı (`create_table` decimal'i 18,4 sabitler, koordinat hassasiyeti kaybolurdu).
- Kart bölümleri (Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar) önce aynı formda başlıklı gruplardı. Form uzadığı için Sağlık Kartları'ndaki kalıba geçildi: her bölüm `Prc_T_Supplier` satırının kendisini düzenleyen menüsüz bir `Form` sekmesidir (`App.Prc.Supplier{Sales,Purchasing,Financial,Accounting,Notes}`, ilişki `Id → Id`). Kontaklar ve Banka Hesapları liste sekmesi olarak kaldı.
- Adres, Kiracılar ekranıyla aynı zinciri kullanır: Ülke › Şehir › İlçe › Mahalle (`Township` kolonu bu yüzden eklendi); mahalle seçimi posta kodunu doldurur.
- Logo tek görseldir (`multiple: false`) ve formda 120×120 gösterilir.
- Listede ve formda İş Pozisyonu yerine Sektör vardır: `SectorId`, Tanımlamalar › Sektörler ekranının (`App.Definitions.Sector`, `Adm_T_Sector`) kiracıya ait aktif kayıtlarından seçilir (`TenantId = '@TENANTID'`). `JobPosition` kolonu tabloda kalır, ekranda yoktur.
- Telefon ve cep (`Phone`, `Mobile` — Kontaklar dahil) `phoneGlobal` maskesi taşır: `(000) 000-0000` ile girilir, listede `(555) 123-4567` biçiminde görünür; 10 haneye uymayan değer listede boş görünür. Maskenin eksik giriş denetimi Kaydet'e girmediği için alan ayrıca `pattern` `^[0-9]{10}$` kuralı taşır (maskelenmemiş değer 10 rakam); eksik numara kaydedilmez. Web sitesi `pattern`, e-posta `email` doğrulaması taşır; maske alanın EditorOptions'ında, kurallar Validation ayarındadır; ikisi de liste popup'ında ve form sayfasında çalışır.
## 3. Menü ve ekranlar
Satınalma (`App.Prc`) › Tanımlamalar (`App.Prc.Definitions`):
| Ekran | Kod | Not |
| --- | --- | --- |
| Ödeme Koşulları | `App.Prc.PaymentTerm` | tanım, demo veri |
| Ödeme Yöntemleri | `App.Prc.PaymentMethod` | tanım, demo veri |
| Teslimat Koşulları | `App.Prc.Incoterm` | tanım, demo veri |
| Teslim Yöntemleri | `App.Prc.DeliveryMethod` | tanım, demo veri |
| Tedarikçi Etiketleri | `App.Prc.SupplierTag` | tanım, demo veri |
| Tedarikçiler | `App.Prc.Supplier` | ana kart; sekmeler: Satış (`App.Prc.SupplierSales`), Satınalma (`App.Prc.SupplierPurchasing`), Mali Bilgi ve Diğer (`App.Prc.SupplierFinancial`), Muhasebe (`App.Prc.SupplierAccounting`), Notlar (`App.Prc.SupplierNotes`), Kontaklar (`App.Prc.SupplierContact`), Banka Hesapları (`App.Prc.SupplierBankAccount`) — hepsi menüsüz |
## 4. Veri modeli
Tüm tablolar kiracı (`TenantId`) + tam audit + soft delete taşır.
- `Prc_T_PaymentTerm` (Name, DueDays, Description, IsActive)
- `Prc_T_PaymentMethod` (Name, Journal, IsActive)
- `Prc_T_Incoterm` (Code, Name, IsActive)
- `Prc_T_DeliveryMethod` (Name, IsActive)
- `Prc_T_SupplierTag` (Name, Color, IsActive)
- `Prc_T_Supplier` — kimlik/adres (Country, City, District, Township, PostalCode)/vergi, satış ve satınalma koşulları (Prc tanım tablolarına Id ile), kullanıcılar (`AbpUsers`), para birimi (`Sas_H_Currency.Name`), sektör (`Adm_T_Sector`), dil (`Sas_H_Language.CultureName`), muhasebe alanları, Latitude/Longitude (float), Notes (HTML, nvarchar(MAX)), IsActive.
- `Prc_T_SupplierContact` (SupplierId → `Prc_T_Supplier.Id`, Photo, Name, JobTitle, Email, Phone, Mobile, Notes)
- `Prc_T_SupplierBankAccount` (SupplierId → `Prc_T_Supplier.Id`, BankName, Iban, AccountHolder, Currency, IsDefault)
## 5. İş kuralları ve hesaplar
- Tanım lookup'ları yalnızca `IsActive = 1` ve silinmemiş kayıtları listeler; kullanılan tanım silinmek yerine pasife alınır.
- Ülke › İl › İlçe › Mahalle cascade (Kiracılar ekranıyla aynı): üst alan değişince alttakiler temizlenir; mahalle seçilince posta kodu `copy` tarifiyle dolar.
- Kartın formu yalnızca kimlik + adres alanlarını taşır (tek logo, 120×120). Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe ve Notlar, Sağlık Kartları'ndaki gibi aynı `Prc_T_Supplier` satırını düzenleyen menüsüz `Form` sekmeleridir (`Id → Id`, yalnızca düzenleme; ekleme/silme kapalı). Yeni tedarikçi önce ana formdan eklenir, sekmeler kayıttan sonra açılır.
- IBAN doğrulaması: `^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$` (boşluksuz, büyük harf). E-posta alanları e-posta doğrulaması taşır.
- Ödeme koşulu `DueDays` vade gününü tutar; ay sonu kuralları açıklamada yazılıdır, otomatik vade hesabı yoktur.
- Hesap yok; tedarikçi formunda yeni kayıtta Tür = Şirket, Grup Teklif Talebi = Sipariş Üzerine, Fatura onayı = 3 doğrulamadan sonra sor varsayılanları tablodan gelir.
## 6. Yetki ve görünürlük
Yetki ağacı: `App.Prc` › `App.Prc.Definitions` › her ekran (+ Create/Update/Delete/Export/Import/Note). Alt formların yetkisi `App.Prc.Supplier` altındadır; sıra: Satış, Satınalma, Mali Bilgi ve Diğer, Muhasebe, Notlar (7–11), Kontaklar (12), Banka Hesapları (13). Sekme ekranlarında yalnızca Update anlamlıdır (ekleme/silme kapalı). Tüm ekranlar kiracı kapsamlıdır, şube filtresi yoktur. Widget'lar: her menülü ekranda Toplam · Aktif · Pasif; alt formlarda widget yok.
## 7. Entegrasyonlar
Yok. Var olan Ülke/Şehir/İlçe, Para Birimi, Dil, Sektör ve Kullanıcılar ekranları yalnızca okunur.
## 8. Akış örneği
1. Satınalma › Tanımlamalar › Ödeme Koşulları'nda "30 Gün" aktif durur.
2. Tedarikçiler'de "Acme Corporation" açılır; Satınalma sekmesinde Düzenle ile Ödeme Koşulu = 30 Gün, Alım Hatırlatıcısı açık, 1 gün seçilip kaydedilir.
3. Kontaklar sekmesinde Addison Olson eklenir; SupplierId kartın Id'sinden yazılır.
4. Banka Hesapları sekmesinde IBAN girilir ve Varsayılan işaretlenir.
## 9. Sık sorulanlar
- **Kişiler neden listede ayrı satır değil?** Kişi tedarikçi değildir; tedarikçi kartının Kontaklar sekmesindedir.
- **Etiket nasıl eklenir?** Önce Tedarikçi Etiketleri ekranına eklenir, sonra karttaki Etiketler kutusundan seçilir.
- **Koordinat neden formda yok?** Adresten koordinat bulan servis yoktur; alanlar formdan kaldırıldı, kolonlar tabloda boş durur.
- **Yeni tedarikçide Satış/Satınalma sekmeleri neden yok?** Sekmeler kaydın kendisini düzenler; kayıt ana formdan eklenip açıldıktan sonra görünür.
- **Sekme "Kayıt Bulunamadı" gösteriyor.** Kullanıcının o sekmenin okuma yetkisi yoktur (`App.Prc.SupplierSales` vb.); yetkiler rol ekranından verilir, otomatik atanmaz.
## 10. Dosyalar ve geri alma
- Tablolar: `sql/object/Prc_T_PaymentTerm.sql`, `Prc_T_PaymentMethod.sql`, `Prc_T_Incoterm.sql`, `Prc_T_DeliveryMethod.sql`, `Prc_T_SupplierTag.sql`, `Prc_T_Supplier.sql`, `Prc_T_SupplierContact.sql`, `Prc_T_SupplierBankAccount.sql`.
- Wizard'lar: `PaymentTerm.json`, `PaymentMethod.json`, `Incoterm.json`, `DeliveryMethod.json`, `SupplierTag.json`, `SupplierContact.json`, `SupplierBankAccount.json`, `SupplierSales.json`, `SupplierPurchasing.json`, `SupplierFinancial.json`, `SupplierAccounting.json`, `SupplierNotes.json`, `Supplier.json`.
- Demo veri: `data/App.Prc.{PaymentTerm,PaymentMethod,Incoterm,DeliveryMethod,SupplierTag,Supplier}.json`.
- Geri alma: önce `Supplier.json`, sonra alt formlar ve tanımlar `delete_wizard` ile silinir; tablolar yalnızca açık onayla `dropTables: true` ile düşürülür.
<!-- module-doc:auto:begin -->
## Ek — Ekran envanteri (otomatik)
> Wizard dosyalarından üretilir; deploy, silme, tasarımcı kaydı ve import'ta yenilenir. Elle düzenleme bir sonraki yenilemede kaybolur.
### Ödeme Koşulları — `App.Prc.PaymentTerm`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/PaymentTerm.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.PaymentTerm` (sıra 1) |
| Yetki | `App.Prc.PaymentTerm` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ödeme Koşulları — tedarikçi kartlarında ve satınalma belgelerinde kullanılan vade tanımları (peşin, 15/30/45 gün, ay sonu, peşinat + bakiye). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_PaymentTerm` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.PaymentTerm.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `DueDays` | Vade (Gün) | dxNumberBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
| `Description` | Açıklama | dxTextArea | | ✓ | |
### Ödeme Yöntemleri — `App.Prc.PaymentMethod`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/PaymentMethod.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.PaymentMethod` (sıra 2) |
| Yetki | `App.Prc.PaymentMethod` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Ödeme Yöntemleri — tedarikçiye ödemenin nasıl yapılacağı (banka havalesi, çek, nakit kasa, NACHA). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_PaymentMethod` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.PaymentMethod.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `Journal` | Yevmiye | dxTextBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Teslimat Koşulları — `App.Prc.Incoterm`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Incoterm.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.Incoterm` (sıra 3) |
| Yetki | `App.Prc.Incoterm` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Teslimat Koşulları — taşıma, sigorta ve riskin kimde olduğunu belirleyen uluslararası teslim şekilleri (EXW, FCA, FAS, FOB, CFR, CIF, CPT, CIP). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Incoterm` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.Incoterm.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Code` | Kod | dxTextBox | ✓ | ✓ | |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Teslim Yöntemleri — `App.Prc.DeliveryMethod`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/DeliveryMethod.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.DeliveryMethod` (sıra 4) |
| Yetki | `App.Prc.DeliveryMethod` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Teslim Yöntemleri — malın nasıl teslim edileceği (yerel teslimat, kargo, kurye). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_DeliveryMethod` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.DeliveryMethod.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Tedarikçi Etiketleri — `App.Prc.SupplierTag`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierTag.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.SupplierTag` (sıra 5) |
| Yetki | `App.Prc.SupplierTag` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Etiketleri — tedarikçileri sınıflandırmak için kullanılan etiketler (Vendor, Desk Manufacturers, Ofis Malzemeleri…). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_SupplierTag` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.SupplierTag.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Name` | Ad | dxTextBox | ✓ | ✓ | |
| `Color` | Renk | dxColorBox | | ✓ | |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Tedarikçiler — `App.Prc.Supplier`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Supplier.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Definitions` › `App.Prc.Supplier` (sıra 6) |
| Yetki | `App.Prc.Supplier` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçiler — satınalmanın kullandığı tedarikçi kartları (şirket ya da bireysel): kimlik, adres, vergi no ve iletişim bilgileri. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alt formlar | `App.Prc.SupplierSales` (Id → Id), `App.Prc.SupplierPurchasing` (Id → Id), `App.Prc.SupplierFinancial` (Id → Id), `App.Prc.SupplierAccounting` (Id → Id), `App.Prc.SupplierNotes` (Id → Id), `App.Prc.SupplierContact` (Id → SupplierId), `App.Prc.SupplierBankAccount` (Id → SupplierId) |
| Widget'lar | `App.Widget.Total`, `App.Widget.Active`, `App.Widget.Passive` |
| Veri aynası | `data/App.Prc.Supplier.json` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Logo` | Logo | dxImageUpload | | ✓ | |
| `Name` | Adı | dxTextBox | ✓ | ✓ | |
| `CompanyType` | Tür | dxSelectBox | ✓ | ✓ | liste: Company, Individual |
| `Email` | E-Posta | dxTextBox | | ✓ | |
| `Phone` | Telefon | dxTextBox | | ✓ | |
| `Country` | Ülke | dxSelectBox | | ✓ | sorgu: Sas_H_Country |
| `City` | İl / Eyalet | dxSelectBox | | ✓ | sorgu: Sas_H_City (← Country) |
| `District` | Semt / İlçe | dxSelectBox | | ✓ | sorgu: Sas_H_District (← Country,City) |
| `PostalCode` | Posta Kodu | dxTextBox | | ✓ | |
| `TaxNumber` | Vergi Kimlik Numarası | dxTextBox | | ✓ | |
| `TaxOffice` | Vergi Dairesi | dxTextBox | | ✓ | |
| `Address1` | Adres | dxTextBox | | ✓ | |
| `Address2` | Adres 2 | dxTextBox | | ✓ | |
| `SectorId` | Sektör | dxSelectBox | | ✓ | sorgu: Adm_T_Sector |
| `Website` | Websitesi | dxTextBox | | ✓ | |
| `CultureName` | Dil | dxSelectBox | | ✓ | sorgu: Sas_H_Language |
| `Tags` | Etiketler | dxTagBox | | ✓ | sorgu: Prc_T_SupplierTag |
| `IsActive` | Aktif | dxCheckBox | ✓ | ✓ | |
### Kontakları — `App.Prc.SupplierContact`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierContact.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierContact` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Kontakları — tedarikçi firmada çalışan kişiler (satış temsilcisi, danışman, analist…). |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_SupplierContact` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `SupplierId` | Tedarikçi | dxSelectBox | | | sorgu: Prc_T_Supplier |
| `Photo` | Fotoğraf | dxImageUpload | | ✓ | |
| `Name` | Ad Soyad | dxTextBox | ✓ | ✓ | |
| `JobTitle` | Görev | dxTextBox | | ✓ | |
| `Email` | E-Posta | dxTextBox | | ✓ | |
| `Phone` | Telefon | dxTextBox | | ✓ | |
| `Mobile` | Cep Telefonu | dxTextBox | | ✓ | |
| `Notes` | Notlar | dxTextArea | | ✓ | |
### Banka Hesapları — `App.Prc.SupplierBankAccount`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierBankAccount.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierBankAccount` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Banka Hesapları — tedarikçiye ödeme yapılan IBAN'lar. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_SupplierBankAccount` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✓ · düzenle ✓ · sil ✓ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `SupplierId` | Tedarikçi | dxSelectBox | | | sorgu: Prc_T_Supplier |
| `BankName` | Banka | dxTextBox | | ✓ | |
| `Iban` | IBAN | dxTextBox | ✓ | ✓ | |
| `AccountHolder` | Hesap Sahibi | dxTextBox | | ✓ | |
| `Currency` | Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `IsDefault` | Varsayılan | dxCheckBox | | ✓ | |
### Tedarikçi Muhasebe — `App.Prc.SupplierAccounting`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierAccounting.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierAccounting` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Muhasebe — tedarikçi kartının muhasebe ayarları. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `ReceivableAccount` | Alacak Hesabı | dxTextBox | | ✓ | |
| `PayableAccount` | Borç Hesabı | dxTextBox | | ✓ | |
| `InvoiceAutoApprove` | Faturaları Otomatik Onayla | dxSelectBox | | ✓ | liste: Never, AfterThree, Always |
| `PaymentQrType` | Ödeme QR Kodu | dxSelectBox | | ✓ | liste: None, Sepa, Emv |
### Tedarikçi Mali Bilgi ve Diğer — `App.Prc.SupplierFinancial`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierFinancial.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierFinancial` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Mali Bilgi ve Diğer — tedarikçi kartının mali durumu ve diğer tanımlayıcıları. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `FinancialStatus` | Mali Durum | dxSelectBox | | ✓ | liste: Good, Watch, Risky |
| `Reference` | Referans | dxTextBox | | ✓ | |
| `Barcode` | Barkod | dxTextBox | | ✓ | |
### Tedarikçi Notları — `App.Prc.SupplierNotes`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierNotes.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierNotes` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Notları — tedarikçi kartında tutulan serbest notlar. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `Notes` | Notlar | dxHtmlEditor | | ✓ | |
### Tedarikçi Satınalma — `App.Prc.SupplierPurchasing`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierPurchasing.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierPurchasing` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Satınalma — tedarikçi kartının satınalma tarafındaki koşulları. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `RfqGrouping` | Grup Teklif Talebi | dxSelectBox | ✓ | ✓ | liste: OnOrder, Weekly, Always |
| `BuyerUserId` | Alıcı | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `PurchasePaymentTermId` | Ödeme Koşulları (Satınalma) | dxSelectBox | | ✓ | sorgu: Prc_T_PaymentTerm |
| `PurchasePaymentMethodId` | Ödeme Yöntemi (Satınalma) | dxSelectBox | | ✓ | sorgu: Prc_T_PaymentMethod |
| `ReceiptReminder` | Alım Hatırlatıcısı | dxSwitch | | ✓ | |
| `ReminderDaysBefore` | Günler Önce Alındı | dxNumberBox | | ✓ | |
| `SupplierCurrency` | Tedarikçi Para Birimi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `PurchaseIncotermId` | Teslimat Koşulu (Satınalma) | dxSelectBox | | ✓ | sorgu: Prc_T_Incoterm |
| `PurchaseIncotermLocation` | Incoterm Konumu (Satınalma) | dxTextBox | | ✓ | |
### Tedarikçi Satış — `App.Prc.SupplierSales`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/SupplierSales.json` |
| Yol | Liste (Wizard) |
| Menü | Menüsüz (alt form) |
| Yetki | `App.Prc.SupplierSales` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Tedarikçi Satış — tedarikçi kartının satış tarafındaki ticari koşulları. |
| Geliştirme talebi | Satınalma Modülü |
| Kaynak | `Prc_T_Supplier` · filtre `"IsDeleted" = 'false'` |
| Görünümler | Grid, Card, Pivot, Chart (açılış grid) |
| Düzenleme | ekle ✗ · düzenle ✓ · sil ✗ · popup |
| Kapsam | Kiracı |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `SalesUserId` | Satış Temsilcisi | dxSelectBox | | ✓ | sorgu: AbpUsers |
| `SalesCurrency` | Fiyat Listesi | dxSelectBox | | ✓ | sorgu: Sas_H_Currency |
| `SalesPaymentTermId` | Ödeme Koşulları (Satış) | dxSelectBox | | ✓ | sorgu: Prc_T_PaymentTerm |
| `SalesPaymentMethodId` | Ödeme Yöntemi (Satış) | dxSelectBox | | ✓ | sorgu: Prc_T_PaymentMethod |
| `DeliveryMethodId` | Teslim Yöntemi | dxSelectBox | | ✓ | sorgu: Prc_T_DeliveryMethod |
| `SalesIncotermId` | Teslimat Koşulu (Satış) | dxSelectBox | | ✓ | sorgu: Prc_T_Incoterm |
| `SalesIncotermLocation` | Incoterm Konumu (Satış) | dxTextBox | | ✓ | |
<!-- module-doc:auto:end -->

View file

@ -0,0 +1,22 @@
IF OBJECT_ID(N'[dbo].[Prc_T_DeliveryMethod]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_DeliveryMethod]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Name] nvarchar(128) NOT NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_DeliveryMethod] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,23 @@
IF OBJECT_ID(N'[dbo].[Prc_T_Incoterm]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_Incoterm]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Code] nvarchar(8) NOT NULL,
[Name] nvarchar(128) NOT NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_Incoterm] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,23 @@
IF OBJECT_ID(N'[dbo].[Prc_T_PaymentMethod]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_PaymentMethod]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Name] nvarchar(128) NOT NULL,
[Journal] nvarchar(64) NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_PaymentMethod] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,24 @@
IF OBJECT_ID(N'[dbo].[Prc_T_PaymentTerm]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_PaymentTerm]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Name] nvarchar(128) NOT NULL,
[DueDays] int NULL,
[Description] nvarchar(512) NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_PaymentTerm] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,66 @@
IF OBJECT_ID(N'[dbo].[Prc_T_Supplier]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_Supplier]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Logo] nvarchar(MAX) NULL,
[Name] nvarchar(256) NOT NULL,
[CompanyType] nvarchar(16) NOT NULL DEFAULT 'Company',
[Email] nvarchar(256) NULL,
[Phone] nvarchar(32) NULL,
[Website] nvarchar(256) NULL,
[Address1] nvarchar(256) NULL,
[Address2] nvarchar(256) NULL,
[Country] nvarchar(128) NULL,
[City] nvarchar(128) NULL,
[District] nvarchar(128) NULL,
[Township] nvarchar(128) NULL,
[PostalCode] nvarchar(16) NULL,
[TaxNumber] nvarchar(32) NULL,
[TaxOffice] nvarchar(128) NULL,
[JobPosition] nvarchar(128) NULL,
[CultureName] nvarchar(16) NULL,
[Tags] nvarchar(512) NULL,
[SalesUserId] uniqueidentifier NULL,
[SalesCurrency] nvarchar(16) NULL,
[SalesPaymentTermId] uniqueidentifier NULL,
[SalesPaymentMethodId] uniqueidentifier NULL,
[DeliveryMethodId] uniqueidentifier NULL,
[SalesIncotermId] uniqueidentifier NULL,
[SalesIncotermLocation] nvarchar(128) NULL,
[RfqGrouping] nvarchar(16) NOT NULL DEFAULT 'OnOrder',
[BuyerUserId] uniqueidentifier NULL,
[PurchasePaymentTermId] uniqueidentifier NULL,
[PurchasePaymentMethodId] uniqueidentifier NULL,
[ReceiptReminder] bit NOT NULL DEFAULT 0,
[ReminderDaysBefore] int NULL DEFAULT 1,
[SupplierCurrency] nvarchar(16) NULL,
[PurchaseIncotermId] uniqueidentifier NULL,
[PurchaseIncotermLocation] nvarchar(128) NULL,
[FinancialStatus] nvarchar(16) NULL,
[Reference] nvarchar(64) NULL,
[Barcode] nvarchar(64) NULL,
[SectorId] uniqueidentifier NULL,
[ReceivableAccount] nvarchar(64) NULL,
[PayableAccount] nvarchar(64) NULL,
[InvoiceAutoApprove] nvarchar(16) NULL DEFAULT 'AfterThree',
[PaymentQrType] nvarchar(16) NULL,
[Latitude] float NULL,
[Longitude] float NULL,
[Notes] nvarchar(MAX) NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_Supplier] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,26 @@
IF OBJECT_ID(N'[dbo].[Prc_T_SupplierBankAccount]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_SupplierBankAccount]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[SupplierId] uniqueidentifier NOT NULL,
[BankName] nvarchar(128) NULL,
[Iban] nvarchar(34) NOT NULL,
[AccountHolder] nvarchar(256) NULL,
[Currency] nvarchar(16) NULL,
[IsDefault] bit NOT NULL DEFAULT 0,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_SupplierBankAccount] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,28 @@
IF OBJECT_ID(N'[dbo].[Prc_T_SupplierContact]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_SupplierContact]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[SupplierId] uniqueidentifier NOT NULL,
[Photo] nvarchar(MAX) NULL,
[Name] nvarchar(256) NOT NULL,
[JobTitle] nvarchar(128) NULL,
[Email] nvarchar(256) NULL,
[Phone] nvarchar(32) NULL,
[Mobile] nvarchar(32) NULL,
[Notes] nvarchar(MAX) NULL,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_SupplierContact] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

View file

@ -0,0 +1,23 @@
IF OBJECT_ID(N'[dbo].[Prc_T_SupplierTag]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Prc_T_SupplierTag]
(
[Id] uniqueidentifier NOT NULL DEFAULT NEWID(),
[TenantId] uniqueidentifier NULL,
[Name] nvarchar(128) NOT NULL,
[Color] nvarchar(16) NULL,
[IsActive] bit NOT NULL DEFAULT 1,
[CreationTime] datetime2 NOT NULL DEFAULT GETUTCDATE(),
[CreatorId] uniqueidentifier NULL,
[LastModificationTime] datetime2 NULL,
[LastModifierId] uniqueidentifier NULL,
[IsDeleted] bit NOT NULL DEFAULT 0,
[DeletionTime] datetime2 NULL,
[DeleterId] uniqueidentifier NULL,
CONSTRAINT [PK_Prc_T_SupplierTag] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,924 @@
{
"Wizard": {
"ComponentKind": 0,
"WizardName": "SupplierNotes",
"WizardCode": "App.Prc.SupplierNotes",
"SeededAt": "2026-10-05T20:19:57.374139Z",
"CustomComponentName": null,
"PermissionGroupName": "App.Prc",
"ModuleId": "Prc",
"Menu": {
"MenuParentPath": [
{
"Code": "App.Prc",
"Icon": "FcShop",
"En": "Purchasing",
"Tr": "Satınalma"
}
],
"MenuParentCode": "App.Prc.Definitions",
"MenuParentIcon": "FcSettings",
"CreateMenu": false,
"MenuCode": "App.Prc.SupplierNotes",
"MenuOrder": 0,
"MenuUrl": "/admin/list/App.Prc.SupplierNotes",
"MenuIcon": "FcDocument"
},
"Language": {
"MenuParentEn": "Definitions",
"MenuParentTr": "Tanımlamalar",
"PermissionGroupEn": "Purchasing",
"PermissionGroupTr": "Satınalma",
"MenuEn": "Supplier Notes",
"MenuTr": "Tedarikçi Notları",
"TitleEn": "Notes",
"TitleTr": "Notlar",
"DescEn": "Supplier Notes — free-form notes kept on the supplier card.\nWhere: Purchasing › Definitions › Suppliers › Notes tab (no menu of its own; edits the supplier record itself).\nFields: notes (HTML editor) for agreements, warnings and other information about the supplier.",
"DescTr": "Tedarikçi Notları — tedarikçi kartında tutulan serbest notlar.\nNerede: Satınalma › Tanımlamalar › Tedarikçiler › Notlar sekmesi (kendi menüsü yoktur; tedarikçi kaydının kendisini düzenler).\nAlanlar: anlaşmalar, uyarılar ve tedarikçiyle ilgili diğer bilgiler için notlar (HTML editör)."
},
"Groups": [
{
"Items": [
{
"FieldName": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"En": "Id",
"Tr": "Kimlik"
},
{
"FieldName": "Notes",
"CaptionName": "App.Listform.ListformField.Notes",
"En": "Notes",
"Tr": "Notlar"
}
]
}
]
},
"ListForm": {
"EditType": null,
"DataSourceCode": "Default",
"KeyFieldDbSourceType": 9,
"TableName": null,
"DefaultFilter": "\"IsDeleted\" = 'false'",
"SelectCommand": "Prc_T_Supplier",
"SelectCommandType": 1,
"SelectFieldsDefaultValueDto": [],
"SeedFilePath": null,
"SeedSyncInsert": false,
"SeedSyncUpdate": false,
"SeedSyncDelete": false,
"DeleteCommand": "UPDATE \"Prc_T_Supplier\" SET \"DeleterId\"=@DeleterId, \"DeletionTime\"=CURRENT_TIMESTAMP, \"IsDeleted\"='true' WHERE \"Id\" IN @Id",
"DeleteBeforeCommand": null,
"DeleteAfterCommand": null,
"DeleteFieldsDefaultValueDto": [
{
"FieldName": "DeleterId",
"FieldDbType": 9,
"Value": "@USERID",
"CustomValueType": 2,
"SqlQuery": null
},
{
"FieldName": "Id",
"FieldDbType": 9,
"Value": "@ID",
"CustomValueType": 2,
"SqlQuery": null
}
],
"InsertCommand": null,
"InsertBeforeCommand": null,
"InsertAfterCommand": null,
"InsertFieldsDefaultValueDto": [
{
"FieldName": "CreationTime",
"FieldDbType": 6,
"Value": "@NOW",
"CustomValueType": 2,
"SqlQuery": null
},
{
"FieldName": "CreatorId",
"FieldDbType": 9,
"Value": "@USERID",
"CustomValueType": 2,
"SqlQuery": null
},
{
"FieldName": "IsDeleted",
"FieldDbType": 3,
"Value": "false",
"CustomValueType": 1,
"SqlQuery": null
},
{
"FieldName": "Id",
"FieldDbType": 9,
"Value": "@NEWID",
"CustomValueType": 2,
"SqlQuery": null
}
],
"UpdateCommand": null,
"UpdateBeforeCommand": null,
"UpdateAfterCommand": null,
"UpdateFieldsDefaultValueDto": [],
"FormFieldsDefaultValueDto": [],
"WidgetsDto": [],
"ExtraFilterEditDto": [],
"ListFormCode": "App.Prc.SupplierNotes",
"CultureName": "en",
"Name": "App.Prc.SupplierNotes",
"KeyFieldName": "Id",
"Title": "App.Prc.SupplierNotes.Title",
"SortMode": "single",
"PageSize": 10,
"Width": 0,
"Height": 0,
"FullHeight": false,
"Description": "App.Prc.SupplierNotes.Desc",
"FilterRowDto": {
"Visible": true,
"ApplyFilter": "auto",
"AllowUserUiFilterSave": true
},
"RowDto": {
"RowHeight": "auto",
"WhiteSpace": "normal",
"OverflowWrap": "break-word"
},
"HeaderFilterDto": {
"Visible": true,
"AllowSearch": false,
"Height": 325,
"SearchTimeout": 500,
"Width": 252
},
"FilterPanelDto": {
"Visible": false,
"Texts": null,
"FilterEnabled": false,
"CustomizeText": null
},
"SearchPanelDto": {
"Visible": true,
"Width": 160
},
"GroupPanelDto": {
"Visible": true,
"AutoExpandAll": false
},
"SelectionDto": {
"Mode": "none",
"AllowSelectAll": false,
"SelectAllMode": "allPages",
"ShowCheckBoxesMode": "always"
},
"ColumnOptionDto": {
"ShowBorders": true,
"ShowRowLines": true,
"ShowColumnLines": false,
"RowAlternationEnabled": true,
"AllowColumnReordering": true,
"AllowColumnResizing": true,
"ColumnResizingMode": "widget",
"ColumnAutoWidth": true,
"RtlEnabled": false,
"ColumnFixingEnabled": true,
"ColumnChooserEnabled": true,
"ColumnChooserMode": "dragAndDrop",
"HoverStateEnabled": false,
"ColumnHidingEnabled": true,
"FocusedRowEnabled": true,
"ShowColumnHeaders": true
},
"PivotOptionDto": {
"ColumnChooserEnabled": false,
"AllowFieldDragging": true,
"ShowFieldPanel": true,
"ShowDataFields": true,
"ShowColumnFields": true,
"ShowRowFields": true,
"ShowFilterFields": true,
"ShowChart": false,
"ChartHeight": 320,
"ChartCommonSeriesType": "bar"
},
"TreeOptionDto": {
"KeyExpr": null,
"ParentIdExpr": null,
"HasItemsExpr": null,
"RootValue": null,
"ExpandedRowKeys": [],
"AutoExpandAll": false,
"RecursiveSelection": false,
"TitleExpr": null,
"StartExpr": null,
"EndExpr": null,
"ProgressExpr": null
},
"GanttOptionDto": {
"KeyExpr": null,
"ParentIdExpr": null,
"HasItemsExpr": null,
"RootValue": null,
"ExpandedRowKeys": [],
"AutoExpandAll": false,
"RecursiveSelection": false,
"TitleExpr": null,
"ScaleType": "weeks",
"StartExpr": null,
"EndExpr": null,
"ProgressExpr": null,
"EditingEnabled": false,
"AllowTaskAdding": false,
"AllowTaskUpdating": false,
"AllowTaskResourceUpdating": false,
"AllowTaskDeleting": false,
"AllowDependencyAdding": false,
"AllowDependencyDeleting": false,
"AllowResourceAdding": false,
"AllowResourceUpdating": false,
"AllowResourceDeleting": false
},
"SchedulerOptionDto": {
"TextExpr": null,
"UserNameExpr": null,
"DescriptionExpr": null,
"StartDateExpr": null,
"EndDateExpr": null,
"AllDayExpr": null,
"RecurrenceRuleExpr": null,
"RecurrenceExceptionExpr": null,
"StartDayHour": 8,
"EndDayHour": 20,
"DefaultView": "week",
"ShowAllDayPanel": true,
"CellDuration": 30,
"FirstDayOfWeek": 1,
"CrossScrollingEnabled": false,
"AllowResizing": false,
"AllowDragging": false,
"AllowDeleting": false,
"AllowEditing": false,
"AllowAdding": false
},
"TodoOptionDto": {
"TitleExpr": null,
"StatusExpr": null,
"DescriptionExpr": null,
"AnalysisExpr": null,
"DueDateExpr": null,
"TagExpr": null,
"AssigneeExpr": null,
"PriorityExpr": null,
"CompletedExpr": null,
"OrderExpr": null,
"TimesheetExpr": null,
"SubTaskExpr": null,
"StatusOrder": null,
"AllowDragging": true
},
"PagerOptionDto": {
"Visible": true,
"ShowPageSizeSelector": true,
"ShowInfo": false,
"ShowNavigationButtons": true,
"AllowedPageSizes": "10,20,50,100",
"InfoText": "Page {0} of {1} ({2} items)",
"DisplayMode": "adaptive",
"ScrollingMode": "standard",
"LoadPanelEnabled": "auto",
"LoadPanelText": "Loading..."
},
"EditingOptionDto": {
"Mode": "popup",
"RefreshMode": "full",
"AllowUpdating": true,
"AllowDetail": false,
"AllowDeleting": false,
"AllowAllDeleting": false,
"AllowAdding": false,
"AllowDuplicate": false,
"UseIcons": false,
"ConfirmDelete": true,
"NewRowPosition": "viewportTop",
"SelectTextOnEditStart": false,
"StartEditAction": "click",
"EditColumnName": null,
"Popup": {
"Title": "App.Prc.SupplierNotes.Title",
"ShowTitle": true,
"Width": 1000,
"Height": 500,
"FullScreen": false,
"HideOnOutsideClick": true,
"ResizeEnabled": true,
"DragEnabled": true,
"RestorePosition": true,
"Position": "center"
},
"SendOnlyChangedFormValuesUpdate": false,
"AddPageUrl": null,
"AddDialogName": null,
"AddDialogParameters": null
},
"EditingFormDto": [
{
"Order": 1,
"ItemType": "group",
"Caption": "",
"ColCount": 3,
"ColSpan": 1
}
],
"PermissionDto": {
"C": "App.Prc.SupplierNotes.Create",
"R": "App.Prc.SupplierNotes",
"U": "App.Prc.SupplierNotes.Update",
"D": "App.Prc.SupplierNotes.Delete",
"E": "App.Prc.SupplierNotes.Export",
"I": "App.Prc.SupplierNotes.Import",
"N": "App.Prc.SupplierNotes.Note"
},
"CommandColumnDto": [],
"StateStoringDto": {
"Enabled": true,
"Type": "custom",
"StorageKey": "listFormGridStorage-App.Prc.SupplierNotes",
"SavingTimeout": 5000
},
"CustomJsSources": [],
"CustomStyleSources": [],
"UpdateServiceAddress": "list-form-data/update",
"InsertServiceAddress": "list-form-data/insert",
"DeleteServiceAddress": "list-form-data/delete",
"IsTenant": true,
"IsBranch": false,
"IsOrganizationUnit": false,
"ListFormType": "List",
"IsSubForm": false,
"ShowNote": false,
"SubFormsListFormType": "Form",
"SubFormsDto": [],
"ExtraFilterDto": [],
"LayoutDto": {
"Grid": true,
"Card": true,
"Pivot": true,
"Chart": true,
"Tree": false,
"Gantt": false,
"Scheduler": false,
"Todo": false,
"DefaultLayout": "grid"
},
"WorkflowDto": {
"ApprovalUserFieldName": null,
"ApprovalDateFieldName": null,
"ApprovalStatusFieldName": null,
"ApprovalDescriptionFieldName": null,
"ApprovalIsFilterUserName": false,
"ApprovalIsResetWorkflow": false,
"Criteria": []
},
"UserId": null,
"RoleId": null,
"CommonDto": {
"AdjustOnZoom": true,
"ContainerBackgroundColor": "#FFFFFF",
"Palette": "Material",
"PaletteExtensionMode": "blend",
"DefaultPane": null,
"Disabled": false,
"Theme": "generic.light"
},
"AdaptivelayoutDto": {
"Height": 80,
"Width": 80,
"KeepLabels": true
},
"AnimationDto": {
"Duration": 1000,
"Easing": "easeOutCubic",
"Enabled": true,
"MaxPointCountSupported": 300
},
"AnnotationsDto": [],
"ArgumentAxisDto": {
"ArgumentType": null,
"AggregationInterval": null,
"Grid": {
"Color": "#d3d3d3",
"Visible": false,
"Width": 1
},
"HoverMode": "none",
"Label": {
"DisplayMode": "standard",
"RotationAngle": 90,
"OverlappingBehavior": "hide"
},
"Position": "bottom",
"Title": null,
"Visible": true,
"Width": 1,
"WorkdaysOnly": false
},
"CommonAnnotationSettingsDto": {
"Argument": null,
"Border": {
"Color": "#d3d3d3",
"CornerRadius": 0,
"DashStyle": "solid",
"Visible": false,
"Width": 1
},
"Color": "#ffffff",
"Description": null,
"Font": {
"Color": "#FFFFFF",
"Family": "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif",
"Size": 12,
"Weight": 400
},
"Height": -1,
"Image": null,
"OffsetX": -1,
"OffsetY": -1,
"PaddingLeftRight": 10,
"PaddingTopBottom": 10,
"Series": null,
"Text": null,
"TooltipEnabled": true,
"Type": null,
"Value": null,
"Width": 0,
"WordWrap": "normal",
"X": 0,
"Y": 0
},
"CommonAxisSettingsDto": {
"Grid": {
"Color": "#d3d3d3",
"Visible": false,
"Width": 1
},
"Title": null,
"Visible": true,
"Width": 1
},
"CommonPaneSettingsDto": {
"BackgroundColor": null
},
"CommonSeriesSettingsDto": {
"ArgumentField": "arg",
"Axis": null,
"BarOverlapGroup": null,
"BarPadding": 0,
"BarWidth": 0,
"Color": null,
"CornerRadius": 0,
"DashStyle": "solid",
"IgnoreEmptyPoints": false,
"Pane": null,
"RangeValue1Field": "val1",
"RangeValue2Field": "val2",
"SelectionMode": "none",
"ShowInLegend": true,
"Type": null,
"ValueField": null,
"Visible": true,
"Width": 2,
"Label": null
},
"CrosshairDto": {
"Color": "#f05b41",
"DashStyle": "dot",
"Enabled": false,
"Width": 1,
"HorizontalLine": {
"Color": "#f05b41",
"DashStyle": "dot",
"Label": {
"BackgroundColor": "#f05b41",
"CustomizeText": null,
"Font": {
"Color": "#FFFFFF",
"Family": "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif",
"Size": 12,
"Weight": 400
},
"Visible": false,
"Format": null
},
"Visible": true,
"Width": 1
},
"Label": {
"BackgroundColor": "#f05b41",
"CustomizeText": null,
"Font": {
"Color": "#FFFFFF",
"Family": "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif",
"Size": 12,
"Weight": 400
},
"Visible": false,
"Format": null
},
"VerticalLine": {
"Color": "#f05b41",
"DashStyle": "dot",
"Label": {
"BackgroundColor": "#f05b41",
"CustomizeText": null,
"Font": {
"Color": "#FFFFFF",
"Family": "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif",
"Size": 12,
"Weight": 400
},
"Visible": false,
"Format": null
},
"Visible": true,
"Width": 1
}
},
"ExportDto": {
"BackgroundColor": "#FFFFFF",
"Enabled": true,
"AllowExportSelectedData": false,
"Margin": 10,
"PrintingEnabled": true
},
"LegendDto": {
"BackgroundColor": null,
"Border": {
"Color": "#d3d3d3",
"CornerRadius": 0,
"DashStyle": "solid",
"Visible": false,
"Width": 1
},
"ColumnCount": 0,
"Orientation": "vertical",
"Position": "outside",
"RowCount": 0,
"Title": null,
"Visible": true
},
"MarginDto": {
"Bottom": 0,
"Left": 0,
"Right": 0,
"Top": 0
},
"PanesDto": [],
"ScrollBarDto": {
"Color": "gray",
"Offset": 5,
"Position": "top",
"Visible": false,
"Width": 10
},
"SeriesDto": [],
"SizeDto": {
"UseSize": false,
"Width": 400,
"Height": 200
},
"TitleDto": {
"Text": null,
"VerticalAlignment": "top",
"HorizontalAlignment": "center",
"Subtitle": null,
"TextOverflow": "ellipsis",
"WordWrap": null
},
"TooltipDto": {
"ArgumentFormat": null,
"ArrowLength": 10,
"Border": {
"Color": "#d3d3d3",
"CornerRadius": 0,
"DashStyle": "solid",
"Visible": false,
"Width": 1
},
"Color": "#ffffff",
"Enabled": false,
"Font": {
"Color": "#000000",
"Family": "\"Segoe UI\", \"Helvetica Neue\", \"Trebuchet MS\", Verdana, sans-serif",
"Size": 12,
"Weight": 400
},
"Format": null,
"Location": "center",
"PaddingLeftRight": 18,
"PaddingTopBottom": 15,
"Shared": false
},
"ValueAxisDto": [],
"ZoomAndPanDto": {
"AllowMouseWheel": true,
"AllowTouchGestures": true,
"ArgumentAxis": "none",
"DragToZoom": false,
"PanKey": "shift",
"ValueAxis": "none",
"DragBoxStyle": {
"Color": null,
"Opacity": 0
}
},
"LastModificationTime": null,
"LastModifierId": null,
"CreationTime": "0001-01-01T00:00:00",
"CreatorId": null,
"Id": "00000000-0000-0000-0000-000000000000"
},
"Fields": [
{
"EditType": null,
"ListFormCode": "App.Prc.SupplierNotes",
"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": "Id",
"CaptionName": "App.Listform.ListformField.Id",
"PlaceHolder": null,
"ReadOnly": false,
"Visible": false,
"IsActive": true,
"Width": 0,
"ListOrderNo": 1,
"SourceDbType": 9,
"CultureName": "en",
"DataType": "string",
"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.Prc.SupplierNotes.Create",
"R": "App.Prc.SupplierNotes",
"U": "App.Prc.SupplierNotes.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": null,
"EditGroupOrderNo": null,
"EditorType2": "dxTextBox",
"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.Prc.SupplierNotes",
"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": "Notes",
"CaptionName": "App.Listform.ListformField.Notes",
"PlaceHolder": null,
"ReadOnly": false,
"Visible": true,
"IsActive": true,
"Width": 0,
"ListOrderNo": 46,
"SourceDbType": 16,
"CultureName": "en",
"DataType": "string",
"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.Prc.SupplierNotes.Create",
"R": "App.Prc.SupplierNotes",
"U": "App.Prc.SupplierNotes.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": 1,
"EditGroupOrderNo": 1,
"EditorType2": "dxHtmlEditor",
"ColSpan": 3,
"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"
}
],
"IsDeletedField": true,
"IsCreatedField": true,
"InsertedRecords": {
"LanguageKeys": [
"App.Prc.SupplierNotes",
"App.Prc.SupplierNotes.Title",
"App.Prc.SupplierNotes.Desc"
],
"PermissionGroupNames": [],
"PermissionNames": [
"App.Prc.SupplierNotes",
"App.Prc.SupplierNotes.Create",
"App.Prc.SupplierNotes.Update",
"App.Prc.SupplierNotes.Delete",
"App.Prc.SupplierNotes.Export",
"App.Prc.SupplierNotes.Import",
"App.Prc.SupplierNotes.Note"
],
"MenuCodes": [],
"DataSourceCodes": [],
"ModuleIds": []
},
"DevRequest": {
"Id": "fcda5fdc-afab-4006-abcd-eb9846359e05",
"Title": "Satınalma Modülü"
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,15 @@ import { usePermission } from '@/utils/hooks/usePermission'
import { usePWA } from '@/utils/hooks/usePWA' import { usePWA } from '@/utils/hooks/usePWA'
import { layoutTypes } from '../admin/listForm/edit/types' import { layoutTypes } from '../admin/listForm/edit/types'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import FormFieldOrderPanel from './FormFieldOrderPanel'
import type { FormFieldOrder } from './shared/formItems'
/** Form sayfasının kullanıcı bazlı alan sırası (`useGridData`). */
export type FormFieldOrderControl = {
value?: FormFieldOrder
preview: (order: FormFieldOrder | undefined) => void
save: (order: FormFieldOrder | undefined) => Promise<void>
}
const FormButtons = (props: { const FormButtons = (props: {
isSubForm?: boolean isSubForm?: boolean
@ -33,6 +42,8 @@ const FormButtons = (props: {
onActionEdit?: () => void onActionEdit?: () => void
onActionNew?: () => void onActionNew?: () => void
onActionView?: () => void onActionView?: () => void
/** Verilirse araç çubuğunda alan sırası paneli açılır (ekranda state saklama açıksa). */
fieldOrder?: FormFieldOrderControl
}) => { }) => {
const { const {
isSubForm, isSubForm,
@ -51,9 +62,11 @@ const FormButtons = (props: {
onActionEdit, onActionEdit,
onActionNew, onActionNew,
onActionView, onActionView,
fieldOrder,
} = props } = props
const [deleteRowId, setDeleteRowId] = useState<string>() const [deleteRowId, setDeleteRowId] = useState<string>()
const [fieldOrderOpen, setFieldOrderOpen] = useState(false)
const { checkPermission } = usePermission() const { checkPermission } = usePermission()
const isPwaMode = usePWA() const isPwaMode = usePWA()
@ -301,10 +314,14 @@ const FormButtons = (props: {
if (id && !isSubForm) { if (id && !isSubForm) {
toolbarItems.push( toolbarItems.push(
buildButtonItem( buildButtonItem('doc', translate('::App.Reports.Form'), openDynamicGridReport),
'doc', )
translate('::App.Reports.Form'), }
openDynamicGridReport,
if (fieldOrder) {
toolbarItems.push(
buildButtonItem('sorted', translate('::App.ListForm.FormFieldOrder'), () =>
setFieldOrderOpen(true),
), ),
) )
} }
@ -313,10 +330,7 @@ const FormButtons = (props: {
toolbarItems.push( toolbarItems.push(
buildButtonItem('preferences', translate('::App.Platform.Manage'), () => { buildButtonItem('preferences', translate('::App.Platform.Manage'), () => {
window.open( window.open(
ROUTES_ENUM.protected.saas.listFormManagement.edit.replace( ROUTES_ENUM.protected.saas.listFormManagement.edit.replace(':listFormCode', listFormCode),
':listFormCode',
listFormCode,
),
isPwaMode ? '_self' : '_blank', isPwaMode ? '_self' : '_blank',
) )
}), }),
@ -326,6 +340,16 @@ const FormButtons = (props: {
return ( return (
<> <>
<Toolbar className="list-view-toolbar-host flex-1" items={toolbarItems} /> <Toolbar className="list-view-toolbar-host flex-1" items={toolbarItems} />
{fieldOrder && (
<FormFieldOrderPanel
isOpen={fieldOrderOpen}
gridDto={gridDto}
fieldOrder={fieldOrder.value}
onClose={() => setFieldOrderOpen(false)}
onPreview={fieldOrder.preview}
onSave={fieldOrder.save}
/>
)}
<Dialog <Dialog
isOpen={toolbarModalData?.open || false} isOpen={toolbarModalData?.open || false}
onClose={() => setToolbarModalData(undefined)} onClose={() => setToolbarModalData(undefined)}

View file

@ -6,6 +6,8 @@ import {
type FormRef, type FormRef,
GroupItem as GroupItemDx, GroupItem as GroupItemDx,
SimpleItem as SimpleItemDx, SimpleItem as SimpleItemDx,
Tab as TabDx,
TabbedItem as TabbedItemDx,
} from 'devextreme-react/form' } from 'devextreme-react/form'
import type { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form' import type { FieldDataChangedEvent, GroupItem } from 'devextreme/ui/form'
import { Dispatch, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react' import { Dispatch, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react'
@ -25,6 +27,18 @@ import {
import { isTouchLikeDevice } from '../list/shared/editPopup' import { isTouchLikeDevice } from '../list/shared/editPopup'
import { flattenFormGroups, getCascadeParentFields } from './shared/formItems' import { flattenFormGroups, getCascadeParentFields } from './shared/formItems'
/** Boş çok değerli alan için sabit dizi; her render'da yeni `[]` editöre değişiklik gibi görünür. */
const EMPTY_VALUES: any[] = []
/** Dizi/nesne değerleri (TagBox, GridBox) her seferinde yeni referansla gelir; içerikleri karşılaştırılır. */
const isSameValue = (left: any, right: any) =>
Object.is(left, right) ||
(typeof left === 'object' &&
typeof right === 'object' &&
left !== null &&
right !== null &&
JSON.stringify(left) === JSON.stringify(right))
/** Kendi render'ını yapan platform editörleri. */ /** Kendi render'ını yapan platform editörleri. */
const CUSTOM_EDITOR_TYPES = [ const CUSTOM_EDITOR_TYPES = [
PlatformEditorTypes.dxTagBox, PlatformEditorTypes.dxTagBox,
@ -53,6 +67,15 @@ const FormDevExpress = (props: {
const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({}) const [runtimeReadOnlyFields, setRuntimeReadOnlyFields] = useState<Record<string, boolean>>({})
const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({}) const runtimeReadOnlyFieldsRef = useRef<Record<string, boolean>>({})
// DevExtreme seçilen değeri formData nesnesine yerinde yazar; değişiklik, nesnenin render
// anındaki kopyasıyla karşılaştırılarak bulunur.
const formDataSourceRef = useRef<any>(undefined)
const formDataSnapshotRef = useRef<any>(undefined)
if (formDataSourceRef.current !== formData) {
formDataSourceRef.current = formData
formDataSnapshotRef.current = formData ? { ...formData } : formData
}
formDataRef.current = formData formDataRef.current = formData
formItemsRef.current = formItems formItemsRef.current = formItems
@ -92,6 +115,14 @@ const FormDevExpress = (props: {
/** Editör script'i / özel editör değişikliklerini forma ve state'e yazar. */ /** Editör script'i / özel editör değişikliklerini forma ve state'e yazar. */
const commitFormData = useCallback( const commitFormData = useCallback(
(patch: Record<string, any>, form?: any) => { (patch: Record<string, any>, form?: any) => {
// Değişmeyen değer state'e yazılmaz; yazılırsa form yeniden çizilir ve editörler aynı değeri
// tekrar bildirerek döngü kurar.
if (
Object.keys(patch).every((field) => isSameValue(formDataRef.current?.[field], patch[field]))
) {
return formDataRef.current
}
const nextFormData = { ...(formDataRef.current || {}), ...patch } const nextFormData = { ...(formDataRef.current || {}), ...patch }
formDataRef.current = nextFormData formDataRef.current = nextFormData
@ -136,6 +167,7 @@ const FormDevExpress = (props: {
/** Özel editörlerin ortak değer değişimi akışı. */ /** Özel editörlerin ortak değer değişimi akışı. */
const handleCustomEditorChange = (formItem: SimpleItemWithColData, value: any) => { const handleCustomEditorChange = (formItem: SimpleItemWithColData, value: any) => {
if (!formItem.dataField) return if (!formItem.dataField) return
if (isSameValue(formDataRef.current?.[formItem.dataField], value)) return
formDataRef.current = { ...formDataRef.current, [formItem.dataField]: value } formDataRef.current = { ...formDataRef.current, [formItem.dataField]: value }
setFormData(formDataRef.current) setFormData(formDataRef.current)
runEditorScript(formItem, value) runEditorScript(formItem, value)
@ -325,7 +357,7 @@ const FormDevExpress = (props: {
case PlatformEditorTypes.dxTagBox: case PlatformEditorTypes.dxTagBox:
return ( return (
<TagBoxEditorComponent <TagBoxEditorComponent
value={value || []} value={value || EMPTY_VALUES}
setDefaultValue={false} setDefaultValue={false}
values={formData} values={formData}
options={formItem.tagBoxOptions} options={formItem.tagBoxOptions}
@ -337,7 +369,7 @@ const FormDevExpress = (props: {
case PlatformEditorTypes.dxGridBox: case PlatformEditorTypes.dxGridBox:
return ( return (
<GridBoxEditorComponent <GridBoxEditorComponent
value={value || []} value={value || EMPTY_VALUES}
values={formData} values={formData}
options={formItem.gridBoxOptions} options={formItem.gridBoxOptions}
col={formItem.colData} col={formItem.colData}
@ -367,6 +399,17 @@ const FormDevExpress = (props: {
} }
} }
const tabbedGroups = formItems.filter((group) => group.itemType === 'tabbed')
const renderGroupItems = (group: GroupItem) =>
(group.items as SimpleItemWithColData[])
?.filter((formItem) => {
if (mode === 'edit') return formItem.allowEditing !== false
if (mode === 'new') return formItem.allowAdding !== false
return true
})
.map(renderFormItem)
const renderFormItem = (formItem: SimpleItemWithColData, index: number) => { const renderFormItem = (formItem: SimpleItemWithColData, index: number) => {
const key = getFormItemKey(formItem, index) const key = getFormItemKey(formItem, index)
const label = { const label = {
@ -411,8 +454,15 @@ const FormDevExpress = (props: {
) )
} }
// Alan sırası değişince form baştan kurulur; DevExtreme sırası değişen item'ları yerinde günceller
// ve bir alanın editörü/etiketi diğerinin değerleriyle karışır.
const layoutKey = flattenFormGroups(formItems)
.map((item) => item.dataField)
.join('|')
return ( return (
<FormDx <FormDx
key={layoutKey}
ref={refForm} ref={refForm}
className={`${DX_CLASSNAMES} ${!isSubForm ? 'px-2' : ''} pb-2`} className={`${DX_CLASSNAMES} ${!isSubForm ? 'px-2' : ''} pb-2`}
formData={formData} formData={formData}
@ -424,20 +474,19 @@ const FormDevExpress = (props: {
// yoksa sonraki commit script'siz alanların değerini (ör. lookup seçimi) eski kopyayla ezerdi. // yoksa sonraki commit script'siz alanların değerini (ör. lookup seçimi) eski kopyayla ezerdi.
formDataRef.current = { ...formDataRef.current, [e.dataField]: e.value } formDataRef.current = { ...formDataRef.current, [e.dataField]: e.value }
// Aynı değer tekrar geldiğinde (formData forma geri verilince) state yenilenmez; aksi hâlde
// her yenileme yeni bir olay doğurur ve döngüye girer.
if (!isSameValue(formDataSnapshotRef.current?.[e.dataField], e.value)) {
const newFormData = { ...formData, [e.dataField]: e.value } const newFormData = { ...formData, [e.dataField]: e.value }
let hasChanges = !Object.is(formData?.[e.dataField], e.value)
// Parent alan değiştiğinde bağımlı (cascade) alanları temizle. // Parent alan değiştiğinde bağımlı (cascade) alanları temizle.
flattenFormGroups(formItemsRef.current) flattenFormGroups(formItemsRef.current)
.filter((item) => getCascadeParentFields(item).includes(e.dataField!)) .filter((item) => getCascadeParentFields(item).includes(e.dataField!))
.forEach((child) => { .forEach((child) => {
if (!Object.is(newFormData[child.dataField!], null)) {
newFormData[child.dataField!] = null newFormData[child.dataField!] = null
hasChanges = true
}
}) })
if (hasChanges) { formDataSnapshotRef.current = { ...newFormData }
formDataRef.current = newFormData formDataRef.current = newFormData
setFormData(newFormData) setFormData(newFormData)
} }
@ -461,22 +510,38 @@ const FormDevExpress = (props: {
} }
}} }}
> >
{formItems.map((formGroupItem, groupIndex) => ( {formItems.map((formGroupItem, groupIndex) => {
if (formGroupItem.itemType !== 'tabbed') {
return (
<GroupItemDx <GroupItemDx
key={'formGroupItem-' + groupIndex} key={'formGroupItem-' + groupIndex}
colCount={formGroupItem.colCount} colCount={formGroupItem.colCount}
colSpan={formGroupItem.colSpan} colSpan={formGroupItem.colSpan}
caption={translateCaption(translate, formGroupItem.caption)} caption={translateCaption(translate, formGroupItem.caption)}
> >
{(formGroupItem.items as SimpleItemWithColData[]) {renderGroupItems(formGroupItem)}
?.filter((formItem) => {
if (mode === 'edit') return formItem.allowEditing !== false
if (mode === 'new') return formItem.allowAdding !== false
return true
})
.map(renderFormItem)}
</GroupItemDx> </GroupItemDx>
)
}
// Ardışık olmasa da bütün tabbed gruplar ilkinin yerinde tek sekme şeridi olur
// (liste popup'ındaki buildEditingFormItems ile aynı yerleşim).
if (formGroupItem !== tabbedGroups[0]) return null
return (
<TabbedItemDx key={'formGroupItem-' + groupIndex} colSpan={1}>
{tabbedGroups.map((tabbedGroup, tabIndex) => (
<TabDx
key={'formTabItem-' + tabIndex}
title={translateCaption(translate, tabbedGroup.caption)}
colCount={tabbedGroup.colCount}
>
{renderGroupItems(tabbedGroup)}
</TabDx>
))} ))}
</TabbedItemDx>
)
})}
</FormDx> </FormDx>
) )
} }

View file

@ -24,6 +24,10 @@ const FormEdit = (props: FormProps = { isSubForm: false }) => {
formItems, formItems,
refForm, refForm,
permissionResults, permissionResults,
fieldOrder,
previewFieldOrder,
saveFieldOrder,
canStoreFieldOrder,
} = useGridData({ } = useGridData({
mode, mode,
listFormCode, listFormCode,
@ -49,6 +53,11 @@ const FormEdit = (props: FormProps = { isSubForm: false }) => {
actions={ actions={
permissionResults && ( permissionResults && (
<FormButtons <FormButtons
fieldOrder={
canStoreFieldOrder
? { value: fieldOrder, preview: previewFieldOrder, save: saveFieldOrder }
: undefined
}
isSubForm={isSubForm} isSubForm={isSubForm}
mode={mode} mode={mode}
listFormCode={listFormCode} listFormCode={listFormCode}

View file

@ -0,0 +1,276 @@
import { Button } from '@/components/ui'
import type { ColumnFormatDto, GridDto } from '@/proxy/form/models'
import { useLocalization } from '@/utils/hooks/useLocalization'
import {
closestCenter,
DndContext,
type DragEndEvent,
DragOverlay,
KeyboardSensor,
type Modifier,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core'
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import classNames from 'classnames'
import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { FaGripVertical, FaTimes } from 'react-icons/fa'
import { getEditingFormGroups } from '../list/Utils'
import { translateCaption } from '../list/shared/editingForm'
import { FormFieldOrder, sortByFieldOrder } from './shared/formItems'
type OrderGroup = { order: number; caption: string; fields: ColumnFormatDto[] }
const ROW_CLASS =
'flex select-none items-center gap-2 rounded border px-2 py-1.5 outline-none dark:bg-gray-700'
/** Liste dikeydir; yatay kayma satırı yan gruba taşıyormuş gibi gösterir. */
const restrictToVerticalAxis: Modifier = ({ transform }) => ({ ...transform, x: 0 })
const FieldRowContent = ({ field, label }: { field: ColumnFormatDto; label: string }) => (
<>
<FaGripVertical className="shrink-0 text-gray-400" />
<span className="truncate font-medium">{label}</span>
<span className="ml-auto shrink-0 text-xs text-gray-400">{field.fieldName}</span>
</>
)
const SortableField = ({ field, label }: { field: ColumnFormatDto; label: string }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: field.fieldName,
})
// Sürüklenen satırı imleçle DragOverlay taşır; listedeki asıl satır yalnızca yer tutucudur.
// Asıl satır da kaydırılsaydı komşusuyla üst üste binip iki satır birlikte taşınıyor görünürdü.
return (
<li
ref={setNodeRef}
style={{ transform: CSS.Translate.toString(transform), transition }}
className={classNames(
ROW_CLASS,
'cursor-grab bg-white focus-visible:ring-2 focus-visible:ring-indigo-400',
isDragging
? 'border-dashed border-indigo-400 opacity-40'
: 'border-gray-200 dark:border-gray-600',
)}
{...attributes}
{...listeners}
>
<FieldRowContent field={field} label={label} />
</li>
)
}
const toFieldOrder = (groups: OrderGroup[]): FormFieldOrder =>
Object.fromEntries(
groups.flatMap((group) => group.fields.map((field, index) => [field.fieldName, index + 1])),
)
/**
* Form sayfasındaki alanların grup içi sırasını kullanıcı bazında değiştirir. Panel formun
* üstünü kapatmaz; her yer değişikliği formu kaydetmeden yeniden dizer (`onPreview`). Sıra
* veritabanındaki `EditOrderNo`'ya yazılmaz, ListForm state'inde (`form-{storageKey}`) saklanır.
*/
const FormFieldOrderPanel = ({
isOpen,
gridDto,
fieldOrder,
onClose,
onPreview,
onSave,
}: {
isOpen: boolean
gridDto: GridDto
/** Kullanıcının kayıtlı sırası; yoksa `EditOrderNo`. */
fieldOrder?: FormFieldOrder
onClose: () => void
onPreview: (order: FormFieldOrder | undefined) => void
/** `undefined` kayıtlı sırayı siler, form `EditOrderNo` sırasına döner. */
onSave: (order: FormFieldOrder | undefined) => Promise<void>
}) => {
const { translate } = useLocalization()
const [saving, setSaving] = useState(false)
const [activeId, setActiveId] = useState<string>()
const fieldLabel = (field: ColumnFormatDto) =>
field.captionName ? translate('::' + field.captionName) : field.fieldName
const buildGroups = (order?: FormFieldOrder): OrderGroup[] =>
getEditingFormGroups(gridDto).map((group) => ({
order: group.order,
caption: translateCaption(translate, group.caption) || '',
fields: sortByFieldOrder(group.items, order),
}))
const [groups, setGroups] = useState<OrderGroup[]>(() => buildGroups(fieldOrder))
// Panel her açılışta kayıtlı sırayla başlar.
useEffect(() => {
if (isOpen) setGroups(buildGroups(fieldOrder))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen, gridDto, fieldOrder])
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 5 } }),
useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
)
if (!isOpen) return null
const applyGroups = (next: OrderGroup[]) => {
setGroups(next)
// Form bırakma anında baştan kurulursa dnd-kit sürüklemeyi kapatamaz; öğe seçili kalır ve
// sonraki tıklama onu taşır. Önizleme sürükleme kapandıktan sonra çizilir.
const order = toFieldOrder(next)
setTimeout(() => onPreview(order), 0)
}
// Taşıma grup içindedir; alanın hangi grupta durduğu ekranın tanımıdır, kullanıcı tercihi değil.
const handleDragEnd = (groupOrder: number) => (event: DragEndEvent) => {
setActiveId(undefined)
const { active, over } = event
if (!over || active.id === over.id) return
applyGroups(
groups.map((group) => {
if (group.order !== groupOrder) return group
const from = group.fields.findIndex((field) => field.fieldName === active.id)
const to = group.fields.findIndex((field) => field.fieldName === over.id)
return from < 0 || to < 0 ? group : { ...group, fields: arrayMove(group.fields, from, to) }
}),
)
}
const close = () => {
// Kaydedilmemiş önizleme geri alınır.
onPreview(fieldOrder)
onClose()
}
const save = async (order: FormFieldOrder | undefined) => {
setSaving(true)
try {
await onSave(order)
onClose()
} finally {
setSaving(false)
}
}
// Toolbar kapsayıcısının olay işleyicilerinden ayrı durması için body'ye çizilir.
return createPortal(
<div className="fixed bottom-4 right-4 top-20 z-40 flex w-[380px] max-w-[calc(100vw-2rem)] flex-col rounded-lg border border-gray-200 bg-white p-4 shadow-xl dark:border-gray-600 dark:bg-gray-800">
<div className="mb-1 flex items-center">
<h5>{translate('::App.ListForm.FormFieldOrder')}</h5>
<Button
type="button"
variant="plain"
shape="circle"
size="xs"
className="ml-auto"
icon={<FaTimes />}
title={translate('::App.Platform.Cancel')}
onClick={close}
/>
</div>
<p className="mb-3 text-sm text-gray-500">
{translate('::App.ListForm.FormFieldOrder.Description')}
</p>
<div className="flex-1 overflow-y-auto pr-1">
{groups.map((group) => (
<div key={group.order} className="mb-4">
{groups.length > 1 && group.caption && (
<h6 className="mb-2 text-sm font-semibold">{group.caption}</h6>
)}
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
modifiers={[restrictToVerticalAxis]}
onDragStart={(event) => setActiveId(String(event.active.id))}
onDragCancel={() => setActiveId(undefined)}
onDragEnd={handleDragEnd(group.order)}
>
<SortableContext
items={group.fields.map((field) => field.fieldName)}
strategy={verticalListSortingStrategy}
>
<ul className="flex flex-col gap-1">
{group.fields.map((field) => (
<SortableField key={field.fieldName} field={field} label={fieldLabel(field)} />
))}
</ul>
</SortableContext>
{/* Overlay body'ye çizilir; panelin kaydırma kabı onu kırpmaz. */}
{createPortal(
<DragOverlay zIndex={60}>
{group.fields
.filter((field) => field.fieldName === activeId)
.map((field) => (
<div
key={field.fieldName}
className={classNames(
ROW_CLASS,
'cursor-grabbing border-indigo-400 bg-white shadow-lg',
)}
>
<FieldRowContent field={field} label={fieldLabel(field)} />
</div>
))}
</DragOverlay>,
document.body,
)}
</DndContext>
</div>
))}
</div>
<div className="mt-3 flex flex-wrap items-center gap-2 border-t border-gray-200 pt-3 dark:border-gray-600">
<Button
type="button"
size="sm"
variant="plain"
disabled={saving}
onClick={() => save(undefined)}
>
{translate('::App.ListForm.FormFieldOrder.FactoryReset')}
</Button>
<div className="ml-auto flex gap-2">
<Button
type="button"
size="sm"
variant="plain"
disabled={saving}
onClick={() => applyGroups(buildGroups(fieldOrder))}
>
{translate('::App.ListForm.FormFieldOrder.Reset')}
</Button>
<Button
type="button"
size="sm"
variant="solid"
loading={saving}
onClick={() => save(toFieldOrder(groups))}
>
{translate('::App.Platform.Save')}
</Button>
</div>
</div>
</div>,
document.body,
)
}
export default FormFieldOrderPanel

View file

@ -31,6 +31,10 @@ export const FormNewContent = (props: FormProps = { isSubForm: false }) => {
formItems, formItems,
refForm, refForm,
permissionResults, permissionResults,
fieldOrder,
previewFieldOrder,
saveFieldOrder,
canStoreFieldOrder,
} = useGridData({ } = useGridData({
mode, mode,
listFormCode, listFormCode,
@ -138,6 +142,11 @@ export const FormNewContent = (props: FormProps = { isSubForm: false }) => {
actions={ actions={
permissionResults && ( permissionResults && (
<FormButtons <FormButtons
fieldOrder={
canStoreFieldOrder
? { value: fieldOrder, preview: previewFieldOrder, save: saveFieldOrder }
: undefined
}
isSubForm={isSubForm} isSubForm={isSubForm}
mode={mode} mode={mode}
listFormCode={listFormCode} listFormCode={listFormCode}

View file

@ -22,6 +22,10 @@ const FormView = (props: FormProps = { isSubForm: false }) => {
refForm, refForm,
permissionResults, permissionResults,
fetchData, fetchData,
fieldOrder,
previewFieldOrder,
saveFieldOrder,
canStoreFieldOrder,
} = useGridData({ mode, listFormCode, id, isSubForm }) } = useGridData({ mode, listFormCode, id, isSubForm })
// Form tipi liste formları ID olmadan da açılabildiği için kayıt anahtarı // Form tipi liste formları ID olmadan da açılabildiği için kayıt anahtarı
@ -46,6 +50,11 @@ const FormView = (props: FormProps = { isSubForm: false }) => {
actions={ actions={
permissionResults && ( permissionResults && (
<FormButtons <FormButtons
fieldOrder={
canStoreFieldOrder
? { value: fieldOrder, preview: previewFieldOrder, save: saveFieldOrder }
: undefined
}
isSubForm={isSubForm} isSubForm={isSubForm}
mode={mode} mode={mode}
listFormCode={listFormCode} listFormCode={listFormCode}

View file

@ -115,9 +115,17 @@ const SubForms = (props: {
// Form tipi sekmelerin görüntüle/düzenle/yeni modu; kod bazında tutulur. // Form tipi sekmelerin görüntüle/düzenle/yeni modu; kod bazında tutulur.
const [tabModes, setTabModes] = useState<Record<string, SubFormTabMode>>({}) const [tabModes, setTabModes] = useState<Record<string, SubFormTabMode>>({})
// Alt formlar yalnızca ilişki alanlarına bağlıdır; formData her alan değişiminde yenilenir ve
// ona bağlanınca alt listeler her tuşta yeni searchParams alıp yeniden yüklenir.
const relationKey = JSON.stringify(
(gridDto?.gridOptions?.subFormsDto ?? []).flatMap((sub) =>
(sub.relation ?? []).map((item) => formData?.[item.parentFieldName] ?? null),
),
)
const subForms = useMemo( const subForms = useMemo(
() => (gridDto?.gridOptions?.subFormsDto ?? []).map((sub) => withParentRelation(sub, formData)), () => (gridDto?.gridOptions?.subFormsDto ?? []).map((sub) => withParentRelation(sub, formData)),
[gridDto, formData], // eslint-disable-next-line react-hooks/exhaustive-deps
[gridDto, relationKey],
) )
useEffect(() => { useEffect(() => {
@ -235,7 +243,7 @@ const SubForms = (props: {
))} ))}
</TabList> </TabList>
{subForms.map((subForm) => ( {subForms.map((subForm) => (
<TabContent key={subForm.code} value={subForm.code} className="pt-1"> <TabContent key={subForm.code} value={subForm.code}>
{renderTabContent(subForm)} {renderTabContent(subForm)}
</TabContent> </TabContent>
))} ))}

View file

@ -1,6 +1,6 @@
import { ColumnFormatDto, TagBoxOptionsDto } from '@/proxy/form/models' import { ColumnFormatDto, TagBoxOptionsDto } from '@/proxy/form/models'
import TagBox from 'devextreme-react/tag-box' import TagBox from 'devextreme-react/tag-box'
import { ReactElement } from 'react' import { ReactElement, useMemo } from 'react'
const TagBoxEditorComponent = ({ const TagBoxEditorComponent = ({
value, value,
@ -18,7 +18,8 @@ const TagBoxEditorComponent = ({
col?: ColumnFormatDto col?: ColumnFormatDto
editorOptions: any editorOptions: any
}): ReactElement => { }): ReactElement => {
const val = Array.isArray(value) ? value : [value] // Her render'da yeni dizi verilirse TagBox bunu değer değişikliği sayar ve onValueChanged döngüye girer.
const val = useMemo(() => (Array.isArray(value) ? value : [value]), [value])
const lookupDto = col?.lookupDto const lookupDto = col?.lookupDto
// const dataSource = // const dataSource =

View file

@ -108,12 +108,12 @@ export const FormPageShell = ({
)} )}
<div <div
className={`flex items-center pb-2 px-2 ${isSubForm ? 'justify-end' : 'justify-between'}`} className={`flex items-center px-2 ${isSubForm ? 'justify-end' : 'justify-between'}`}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{MenuIcon}
{!isSubForm && ( {!isSubForm && (
<> <>
{MenuIcon}
{description ? ( {description ? (
<button <button
className="flex items-center gap-1 leading-none hover:underline" className="flex items-center gap-1 leading-none hover:underline"

View file

@ -4,6 +4,7 @@
* ayrı ayrı tanımlıydı. * ayrı ayrı tanımlıydı.
*/ */
import { captionize } from 'devextreme/core/utils/inflector' import { captionize } from 'devextreme/core/utils/inflector'
import type { ValidationRule } from 'devextreme/common'
import type { GroupItem } from 'devextreme/ui/form' import type { GroupItem } from 'devextreme/ui/form'
import type { ColumnFormatDto, GridDto } from '@/proxy/form/models' import type { ColumnFormatDto, GridDto } from '@/proxy/form/models'
import { getEditingFormGroups } from '../../list/Utils' import { getEditingFormGroups } from '../../list/Utils'
@ -41,12 +42,33 @@ type BuildFormItemsParams = {
/** Lookup alanları için DevExtreme dataSource üretici. */ /** Lookup alanları için DevExtreme dataSource üretici. */
getLookupDataSource: (colData: ColumnFormatDto, formData: any) => any getLookupDataSource: (colData: ColumnFormatDto, formData: any) => any
formData: any formData: any
/** Kullanıcının kaydettiği alan sırası (alan adı → grup içi sıra); yoksa `editOrderNo`. */
fieldOrder?: FormFieldOrder
} }
/** Form sayfasının kullanıcı bazlı alan sırası; ListForm state'inde `form-{storageKey}` adıyla saklanır. */
export type FormFieldOrder = Record<string, number>
/** Alanları kullanıcının sırasıyla, sırası olmayanları `editOrderNo` ile sıralar. */
export const sortByFieldOrder = <T extends Pick<ColumnFormatDto, 'fieldName' | 'editOrderNo'>>(
fields: T[],
fieldOrder?: FormFieldOrder,
) =>
[...fields].sort((left, right) => {
const leftOrder = fieldOrder?.[left.fieldName] ?? left.editOrderNo ?? 0
const rightOrder = fieldOrder?.[right.fieldName] ?? right.editOrderNo ?? 0
return leftOrder - rightOrder
})
/** Tek bir ColumnFormatDto'yu DevExtreme form item'ına çevirir. */ /** Tek bir ColumnFormatDto'yu DevExtreme form item'ına çevirir. */
const toFormItem = ( const toFormItem = (
colFormat: ColumnFormatDto, colFormat: ColumnFormatDto,
{ mode, columns, getLookupDataSource, formData }: Omit<BuildFormItemsParams, 'gridDto'>, {
mode,
columns,
getLookupDataSource,
formData,
}: Omit<BuildFormItemsParams, 'gridDto' | 'fieldOrder'>,
): SimpleItemWithColData => { ): SimpleItemWithColData => {
let parsedEditorOptions: Record<string, any> = {} let parsedEditorOptions: Record<string, any> = {}
try { try {
@ -60,6 +82,8 @@ const toFormItem = (
dataSource: getLookupDataSource(colFormat, formData), dataSource: getLookupDataSource(colFormat, formData),
valueExpr: colFormat.lookupDto?.valueExpr?.toLowerCase(), valueExpr: colFormat.lookupDto?.valueExpr?.toLowerCase(),
displayExpr: colFormat.lookupDto?.displayExpr?.toLowerCase(), displayExpr: colFormat.lookupDto?.displayExpr?.toLowerCase(),
// Grid popup'ındaki lookup kolonu aramayı varsayılan açar; form sayfası aynı davranır.
searchEnabled: true,
} }
: {} : {}
@ -79,10 +103,12 @@ const toFormItem = (
...lookupEditorOptions, ...lookupEditorOptions,
...parsedEditorOptions, ...parsedEditorOptions,
}), }),
colData: colFormat, // DevExtreme item seçeneklerini yerinde (deep extend) günceller; ekran tanımı (gridDto) paylaşılırsa
tagBoxOptions: colFormat.tagBoxOptions, // sıra değişince bir alanın tanımı diğerininkiyle ezilir. Item'a tanımın kopyası verilir.
gridBoxOptions: colFormat.gridBoxOptions, colData: structuredClone(colFormat),
imageUploadOptions: colFormat.imageUploadOptions, tagBoxOptions: structuredClone(colFormat.tagBoxOptions),
gridBoxOptions: structuredClone(colFormat.gridBoxOptions),
imageUploadOptions: structuredClone(colFormat.imageUploadOptions),
editorScript: colFormat.editorScript, editorScript: colFormat.editorScript,
} }
@ -102,12 +128,20 @@ const toFormItem = (
colFormat.validationRuleDto?.some((rule) => rule.type === 'required'), colFormat.validationRuleDto?.some((rule) => rule.type === 'required'),
) ?? false ) ?? false
// Grid popup'ı kuralları kolondan alır; form sayfasında zorunluluk isRequired ile, diğer kurallar
// (pattern, email, stringLength…) item üzerinden çalışır.
const otherRules = colFormat.validationRuleDto?.filter((rule) => rule.type !== 'required')
if (otherRules?.length) {
item.validationRules = otherRules as ValidationRule[]
}
return item return item
} }
/** /**
* EditingFormDto gruplarını DevExtreme `GroupItem` listesine çevirir. * EditingFormDto gruplarını DevExtreme `GroupItem` listesine çevirir.
* Alanlar `editOrderNo`'ya göre sıralanır ve moda göre yetki filtresinden geçer. * Alanlar kullanıcının sırasına, yoksa `editOrderNo`'ya göre sıralanır ve moda göre yetki
* filtresinden geçer.
*/ */
export const buildFormGroupItems = ({ export const buildFormGroupItems = ({
gridDto, gridDto,
@ -115,6 +149,7 @@ export const buildFormGroupItems = ({
columns, columns,
getLookupDataSource, getLookupDataSource,
formData, formData,
fieldOrder,
}: BuildFormItemsParams): GroupItem[] => }: BuildFormItemsParams): GroupItem[] =>
getEditingFormGroups(gridDto).map( getEditingFormGroups(gridDto).map(
(group: any) => (group: any) =>
@ -123,8 +158,7 @@ export const buildFormGroupItems = ({
colCount: group.colCount, colCount: group.colCount,
colSpan: group.colSpan, colSpan: group.colSpan,
caption: group.caption, caption: group.caption,
items: [...(group.items ?? [])] items: sortByFieldOrder<ColumnFormatDto>(group.items ?? [], fieldOrder)
.sort((left, right) => (left.editOrderNo ?? 0) - (right.editOrderNo ?? 0))
.map((colFormat: ColumnFormatDto) => .map((colFormat: ColumnFormatDto) =>
toFormItem(colFormat, { mode, columns, getLookupDataSource, formData }), toFormItem(colFormat, { mode, columns, getLookupDataSource, formData }),
) )

View file

@ -5,7 +5,7 @@ import { usePermission } from '@/utils/hooks/usePermission'
import type { FormRef } from 'devextreme-react/form' import type { FormRef } from 'devextreme-react/form'
import CustomStore from 'devextreme/data/custom_store' import CustomStore from 'devextreme/data/custom_store'
import type { GroupItem } from 'devextreme/ui/form' import type { GroupItem } from 'devextreme/ui/form'
import { useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { GridColumnData } from '../list/GridColumnData' import { GridColumnData } from '../list/GridColumnData'
import { PermissionResults, RowMode, SimpleItemWithColData } from './types' import { PermissionResults, RowMode, SimpleItemWithColData } from './types'
@ -17,9 +17,11 @@ import { layoutTypes } from '../admin/listForm/edit/types'
import { useListFormCustomDataSource } from '../list/useListFormCustomDataSource' import { useListFormCustomDataSource } from '../list/useListFormCustomDataSource'
import { useListFormColumns } from '../list/useListFormColumns' import { useListFormColumns } from '../list/useListFormColumns'
import { useListFormCustomSources } from '../list/shared/hooks' import { useListFormCustomSources } from '../list/shared/hooks'
import { useListFormStateStoring } from '../list/useListFormStateStoring'
import { import {
buildFormGroupItems, buildFormGroupItems,
flattenFormGroups, flattenFormGroups,
FormFieldOrder,
getCascadeParentFields, getCascadeParentFields,
} from './shared/formItems' } from './shared/formItems'
@ -43,9 +45,13 @@ const useGridData = (props: {
const [formData, setFormData] = useState<any>() const [formData, setFormData] = useState<any>()
const [formItems, setFormItems] = useState<GroupItem[]>([]) const [formItems, setFormItems] = useState<GroupItem[]>([])
const [permissionResults, setPermissionResults] = useState<PermissionResults>() const [permissionResults, setPermissionResults] = useState<PermissionResults>()
const [fieldOrder, setFieldOrder] = useState<FormFieldOrder>()
const refForm = useRef<FormRef>(null) const refForm = useRef<FormRef>(null)
const previousFormDataRef = useRef<any | undefined>(undefined) const previousFormDataRef = useRef<any | undefined>(undefined)
// Alan sırası kaydedilince form o anki veriyle yeniden kurulur.
const currentFormDataRef = useRef<any>(formData)
currentFormDataRef.current = formData
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
@ -62,6 +68,16 @@ const useGridData = (props: {
useListFormCustomSources(gridDto) useListFormCustomSources(gridDto)
// Alan sırası grid/pivot görünümleriyle aynı ListForm state'inde, kullanıcı bazında tutulur.
const stateStoring = gridDto?.gridOptions?.stateStoringDto
const { customLoadState, customSaveState, storageKey } = useListFormStateStoring({
listFormCode,
storageKey: stateStoring?.enabled ? stateStoring.storageKey : undefined,
filterPrefix: 'form',
showSaveToast: false,
saveDebounceMs: 0,
})
const fetchData = async () => { const fetchData = async () => {
setLoading(true) setLoading(true)
try { try {
@ -202,6 +218,15 @@ const useGridData = (props: {
), ),
) )
// Kayıtlı alan sırası form kurulmadan okunur; aksi hâlde form önce varsayılan sırayla çizilip yer değiştirirdi.
let isActive = true
customLoadState()
.catch(() => null)
.then((state: any) => {
if (!isActive) return
const savedOrder: FormFieldOrder | undefined = state?.fieldOrder ?? undefined
setFieldOrder(savedOrder)
setFormItems( setFormItems(
buildFormGroupItems({ buildFormGroupItems({
gridDto, gridDto,
@ -209,20 +234,61 @@ const useGridData = (props: {
columns, columns,
getLookupDataSource, getLookupDataSource,
formData, formData,
fieldOrder: savedOrder,
}), }),
) )
setGridReady(true) setGridReady(true)
})
return () => {
isActive = false
}
// getBandedColumns/getLookupDataSource her render'da yeniden oluşabilir. // getBandedColumns/getLookupDataSource her render'da yeniden oluşabilir.
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [gridDto]) }, [gridDto])
/** Formu verilen alan sırasıyla yeniden kurar; kaydetmez (sürükleme sırasında canlı önizleme). */
const previewFieldOrder = useCallback(
(order: FormFieldOrder | undefined) => {
if (!gridDto) return
setFormItems(
buildFormGroupItems({
gridDto,
mode,
columns: getBandedColumns(),
getLookupDataSource,
formData: currentFormDataRef.current,
fieldOrder: order,
}),
)
},
// getBandedColumns/getLookupDataSource her render'da yeniden oluşabilir.
// eslint-disable-next-line react-hooks/exhaustive-deps
[gridDto, mode],
)
/** Alan sırasını kullanıcının state'ine yazar; `undefined` fabrika sırasına (`EditOrderNo`) döner. */
const saveFieldOrder = useCallback(
async (order: FormFieldOrder | undefined) => {
await customSaveState(order ? { fieldOrder: order } : null)
setFieldOrder(order)
previewFieldOrder(order)
},
[customSaveState, previewFieldOrder],
)
/** /**
* formData değiştiğinde yalnızca etkilenen cascading lookup dataSource'larını * formData değiştiğinde yalnızca etkilenen cascading lookup dataSource'larını
* yeniden üretir; tüm form item ağacını gereksiz yere yenilemez. * yeniden üretir; tüm form item ağacını gereksiz yere yenilemez.
*/ */
useEffect(() => { useEffect(() => {
// DevExtreme formData nesnesine yerinde yazar; referans tutulursa önceki değer de yeni
// değere dönüşür ve değişen parent alan hiç bulunamaz.
const snapshotFormData = (data: any) => (data ? { ...data } : data)
if (!gridDto || !formItems.length) { if (!gridDto || !formItems.length) {
previousFormDataRef.current = formData previousFormDataRef.current = snapshotFormData(formData)
return return
} }
@ -242,7 +308,7 @@ const useGridData = (props: {
) )
if (!hasAffectedLookup) { if (!hasAffectedLookup) {
previousFormDataRef.current = formData previousFormDataRef.current = snapshotFormData(formData)
return return
} }
@ -285,7 +351,7 @@ const useGridData = (props: {
return item return item
}) })
previousFormDataRef.current = formData previousFormDataRef.current = snapshotFormData(formData)
setFormItems((currentItems) => setFormItems((currentItems) =>
currentItems.map((groupItem) => ({ currentItems.map((groupItem) => ({
...groupItem, ...groupItem,
@ -347,6 +413,10 @@ const useGridData = (props: {
fetchData, fetchData,
setFormData, setFormData,
handleSubmit, handleSubmit,
fieldOrder,
previewFieldOrder,
saveFieldOrder,
canStoreFieldOrder: !!storageKey,
} }
} }

View file

@ -20,7 +20,7 @@ type PendingSave = {
type UseListFormStateStoringParams = { type UseListFormStateStoringParams = {
listFormCode: string listFormCode: string
storageKey?: string | null storageKey?: string | null
filterPrefix: 'list' | 'tree' | 'pivot' | 'todo' | 'gantt' filterPrefix: 'list' | 'tree' | 'pivot' | 'todo' | 'gantt' | 'form'
skipSaveRef?: MutableRefObject<boolean> skipSaveRef?: MutableRefObject<boolean>
showSaveToast?: boolean showSaveToast?: boolean
saveDebounceMs?: number saveDebounceMs?: number