Satınalma Siparişleri Ürünleri

This commit is contained in:
Sedat ÖZTÜRK 2026-10-12 14:07:18 +03:00
parent 55bd2626b4
commit fed76687e3
15 changed files with 4466 additions and 31 deletions

View file

@ -4597,6 +4597,11 @@ taşır — son çaredir, önce `EditorScript`/`EditorOptions` denenmelidir.
Editörün üstündeki dil/rol/kullanıcı seçicileri **varyant** düzenler (ref §5.7): seçim yapınca Editörün üstündeki dil/rol/kullanıcı seçicileri **varyant** düzenler (ref §5.7): seçim yapınca
aynı ekranın o bağlama özgü kopyası üzerinde çalışırsın. aynı ekranın o bağlama özgü kopyası üzerinde çalışırsın.
**Butonlar**, **Form** ve **Alt Formlar** sekmelerinde satırlar tutamaçtan sürüklenerek sıralanır
(`ListFormJsonRowAppService.MoveAsync`, UI `SortableJsonRows`). Buton ve alt form dizideki sırayla
çizilir; form grubu `Order` ile sıralandığı ve alanlar gruba `EditGroupOrderNo` ile bağlı olduğu için
taşımada grup numaraları yeni sıraya göre yeniden verilir, alanlar gruplarıyla birlikte taşınır.
> **Seed'e yansıma:** Bu ekrandan yapılan her kayıt (liste ayarları, alanlar, JSON satır > **Seed'e yansıma:** Bu ekrandan yapılan her kayıt (liste ayarları, alanlar, JSON satır
> editörleri — `ListFormsAppService`, `ListFormFieldsAppService`, `ListFormJsonRowAppService`) > editörleri — `ListFormsAppService`, `ListFormFieldsAppService`, `ListFormJsonRowAppService`)
> ekranın wizard seed dosyasına `WizardSeedSynchronizer` ile geri yazılır; wizard'dan üretilmemiş > ekranın wizard seed dosyasına `WizardSeedSynchronizer` ile geri yazılır; wizard'dan üretilmemiş

View file

@ -20,14 +20,17 @@ namespace Sozsoft.Platform.ListForms.Administration;
public class ListFormJsonRowAppService : PlatformAppService public class ListFormJsonRowAppService : PlatformAppService
{ {
private readonly IRepository<ListForm, Guid> repository; private readonly IRepository<ListForm, Guid> repository;
private readonly IRepository<ListFormField, Guid> fieldRepository;
private readonly IWizardSeedSynchronizer wizardSeedSynchronizer; private readonly IWizardSeedSynchronizer wizardSeedSynchronizer;
public ListFormJsonRowAppService( public ListFormJsonRowAppService(
IRepository<ListForm, Guid> _repository, IRepository<ListForm, Guid> _repository,
IRepository<ListFormField, Guid> _fieldRepository,
IWizardSeedSynchronizer _wizardSeedSynchronizer IWizardSeedSynchronizer _wizardSeedSynchronizer
) : base() ) : base()
{ {
repository = _repository; repository = _repository;
fieldRepository = _fieldRepository;
wizardSeedSynchronizer = _wizardSeedSynchronizer; wizardSeedSynchronizer = _wizardSeedSynchronizer;
} }
@ -305,6 +308,36 @@ public class ListFormJsonRowAppService : PlatformAppService
await wizardSeedSynchronizer.SyncAsync(listForm.ListFormCode); await wizardSeedSynchronizer.SyncAsync(listForm.ListFormCode);
} }
/// <summary>
/// Satırın sırasını değiştirir. Butonlar ve alt formlar dizideki sırayla çizilir; form grupları ise
/// <see cref="EditingFormDto.Order"/> ile sıralanır ve alanlar gruba bu numarayla bağlıdır
/// (<c>EditGroupOrderNo</c>), bu yüzden grup numaraları yeni sıraya göre yeniden verilir ve alanlar
/// gruplarıyla birlikte taşınır.
/// </summary>
public async Task MoveAsync(Guid id, string field, int fromIndex, int toIndex)
{
var listForm = await GetAsync(id);
switch (field)
{
case ListFormEditTabs.CommandColumnsJsonRow:
listForm.CommandColumnJson = MoveRow<CommandColumn>(listForm.CommandColumnJson, fromIndex, toIndex);
break;
case ListFormEditTabs.SubFormJsonRow:
listForm.SubFormsJson = MoveRow<SubForm>(listForm.SubFormsJson, fromIndex, toIndex);
break;
case ListFormEditTabs.Edit.EditingFormFieldsJsonRow:
listForm.EditingFormJson = await MoveEditingFormGroupAsync(listForm, fromIndex, toIndex);
break;
default:
throw new UserFriendlyException(L[AppErrorCodes.ParameterNotValid]);
}
await repository.UpdateAsync(listForm);
// Ekran wizard ile uretildiyse seed dosyasi da guncel halini tasimalidir.
await wizardSeedSynchronizer.SyncAsync(listForm.ListFormCode);
}
private async Task<ListForm> GetAsync(Guid id) private async Task<ListForm> GetAsync(Guid id)
{ {
var listForm = await repository.FirstOrDefaultAsync(c => c.Id == id); var listForm = await repository.FirstOrDefaultAsync(c => c.Id == id);
@ -340,5 +373,54 @@ public class ListFormJsonRowAppService : PlatformAppService
return JsonSerializer.Serialize(listData); return JsonSerializer.Serialize(listData);
} }
private string MoveRow<T>(string json, int fromIndex, int toIndex)
=> JsonSerializer.Serialize(Move(json.IsNullOrWhiteSpace() ? [] : JsonSerializer.Deserialize<List<T>>(json) ?? [], fromIndex, toIndex));
private List<T> Move<T>(List<T> listData, int fromIndex, int toIndex)
{
if (fromIndex < 0 || fromIndex >= listData.Count || toIndex < 0 || toIndex >= listData.Count)
{
throw new UserFriendlyException(L[AppErrorCodes.ParameterNotValid]);
}
var item = listData[fromIndex];
listData.RemoveAt(fromIndex);
listData.Insert(toIndex, item);
return listData;
}
private async Task<string> MoveEditingFormGroupAsync(ListForm listForm, int fromIndex, int toIndex)
{
var groups = Move(
listForm.EditingFormJson.IsNullOrWhiteSpace() ? [] : JsonSerializer.Deserialize<List<EditingFormDto>>(listForm.EditingFormJson) ?? [],
fromIndex, toIndex);
// Eski numara -> yeni numara; numarası değişmeyen grup eşlemeye girmez.
var orderMap = new Dictionary<int, int>();
for (var i = 0; i < groups.Count; i++)
{
if (groups[i].Order != i + 1)
{
orderMap.TryAdd(groups[i].Order, i + 1);
groups[i].Order = i + 1;
}
}
if (orderMap.Count > 0)
{
var oldOrders = orderMap.Keys.Cast<int?>().ToList();
var fields = await fieldRepository.GetListAsync(a =>
a.ListFormCode == listForm.ListFormCode && oldOrders.Contains(a.EditGroupOrderNo));
foreach (var field in fields)
{
field.EditGroupOrderNo = orderMap[field.EditGroupOrderNo!.Value];
}
await fieldRepository.UpdateManyAsync(fields);
}
return JsonSerializer.Serialize(groups);
}
} }

View file

@ -19,24 +19,24 @@
"DisplayName": "App.Hr", "DisplayName": "App.Hr",
"Order": 3 "Order": 3
}, },
{
"Name": "App.Prj",
"DisplayName": "App.Prj",
"Order": 4
},
{ {
"Name": "App.Prc", "Name": "App.Prc",
"DisplayName": "App.Prc", "DisplayName": "App.Prc",
"Order": 4 "Order": 5
}, },
{ {
"Name": "App.Inventory", "Name": "App.Inventory",
"DisplayName": "App.Inventory", "DisplayName": "App.Inventory",
"Order": 5 "Order": 6
}, },
{ {
"Name": "App.Production", "Name": "App.Production",
"DisplayName": "App.Production", "DisplayName": "App.Production",
"Order": 6
},
{
"Name": "App.Prj",
"DisplayName": "App.Prj",
"Order": 7 "Order": 7
} }
] ]

View file

@ -17894,6 +17894,76 @@
"MultiTenancySide": 3, "MultiTenancySide": 3,
"Modules": "Prc" "Modules": "Prc"
}, },
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct",
"ParentName": "App.Prc.Orders",
"DisplayName": "App.Prc.PurchaseOrderProduct",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Create",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Create",
"Order": 1,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Update",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Update",
"Order": 2,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Delete",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Delete",
"Order": 3,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Export",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Export",
"Order": 4,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Import",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Import",
"Order": 5,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{
"GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderProduct.Note",
"ParentName": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Platform.Note",
"Order": 6,
"IsEnabled": true,
"MultiTenancySide": 3,
"Modules": "Prc"
},
{ {
"GroupName": "App.Prc", "GroupName": "App.Prc",
"Name": "App.Prc.PurchaseOrderAlternative.Create", "Name": "App.Prc.PurchaseOrderAlternative.Create",

View file

@ -95512,6 +95512,62 @@
"Key": "App.Prc.PurchaseOrder.ConfirmAction", "Key": "App.Prc.PurchaseOrder.ConfirmAction",
"Value": "Bu işlem seçili belgeye uygulansın mı?" "Value": "Bu işlem seçili belgeye uygulansın mı?"
}, },
{
"Id": "3c988714-b04f-5502-bf37-a15798fc1f46",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct",
"Value": "Order Products"
},
{
"Id": "bb22204d-9728-57f7-a874-63821f82ddde",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct",
"Value": "Sipariş Ürünleri"
},
{
"Id": "028fc75f-869f-53d0-9895-6350f0874902",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.Title",
"Value": "Purchase Order Products"
},
{
"Id": "51d04fe8-b316-50c5-8556-119c7e76986f",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.Title",
"Value": "Sipariş Ürünleri"
},
{
"Id": "a01c0a6b-b212-5f8d-90dc-14b6e63c0ff2",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.Desc",
"Value": "Order Products — the lines of purchase orders next to their order header; each row is one order line.\nWhere: Purchasing › Orders › Order Products. The source is the Prc_V_PurchaseOrderLine view; orders without lines are not listed, all statuses including Draft, Purchase Order and Cancelled are shown.\nFields: order no, status, supplier, branch, warehouse, operation type; from the line product, description, quantity, unit, unit price, tax rate, untaxed amount, tax, amount (tax included), delivery date and request no; also currency, confirmation date, expected arrival, arrival, buyer and source (RFQ or material request numbers).\nUsage: the list is read-only — no add, edit or delete; lines and orders are changed in Purchase Orders. The Open Order button on a row opens the line's order on its detail page. Grouping, filters, pivot and chart track purchasing by supplier, product, branch or warehouse; the list can be exported.\nCards: total lines, lines on draft orders, late lines (confirmed, no arrival entered, delivery date passed) and total amount of confirmed orders.\nRelated: Purchase Orders, Requests for Quotation, All Requests."
},
{
"Id": "a8a8ba1f-e81c-51d1-8da5-2e81a9763661",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.Desc",
"Value": "Sipariş Ürünleri — satınalma siparişlerinin kalemleri, siparişin başlık bilgileriyle yan yana; her satır bir sipariş kalemidir.\nNerede: Satınalma › Siparişler › Sipariş Ürünleri. Kaynak Prc_V_PurchaseOrderLine görünümüdür; kalemi olmayan sipariş listede yer almaz, Taslak, Satınalma Siparişi ve İptal dahil bütün durumlar görünür.\nAlanlar: sipariş no, durum, tedarikçi, şube, depo, operasyon türü; kalemden ürün, açıklama, miktar, birim, birim fiyat, KDV oranı, vergi hariç tutar, KDV, tutar (KDV dahil), teslim tarihi ve talep no; ayrıca para birimi, onaylama tarihi, beklenen geliş, varış, alıcı ve kaynak (teklif talebi ya da malzeme talebi numaraları).\nKullanım: liste salt okunurdur — ekleme, düzenleme ve silme yoktur; kalem ve sipariş Satınalma Siparişleri ekranından değiştirilir. Satırdaki Siparişi Aç butonu kalemin siparişini detay sayfasında açar. Gruplama, filtre, pivot ve grafik ile tedarikçi, ürün, şube ya da depo bazında satınalma izlenir; liste dışa aktarılabilir.\nKartlar: toplam kalem, taslak siparişlerdeki kalemler, teslimi geciken kalemler (onaylı, varışı girilmemiş, teslim tarihi geçmiş) ve onaylı siparişlerin toplam tutarı.\nİlişkili: Satınalma Siparişleri, Teklif Talepleri, Tüm Talepler."
},
{
"Id": "79f4f562-b901-5e68-8053-08d56d825473",
"CultureName": "en",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.OpenOrder",
"Value": "Detail"
},
{
"Id": "067466c5-e033-5a55-aca4-7d334678712f",
"CultureName": "tr",
"ResourceName": "Platform",
"Key": "App.Prc.PurchaseOrderProduct.OpenOrder",
"Value": "Detay"
},
{ {
"Id": "b07c57e5-8140-5406-b5c1-f7686b1a03d6", "Id": "b07c57e5-8140-5406-b5c1-f7686b1a03d6",
"CultureName": "en", "CultureName": "en",

View file

@ -2988,6 +2988,21 @@
"IsDisabled": false, "IsDisabled": false,
"ElementId": null, "ElementId": null,
"ModuleId": "Prc" "ModuleId": "Prc"
},
{
"Id": "e73153b3-b927-5306-a5f5-ce5dea718c81",
"Code": "App.Prc.PurchaseOrderProduct",
"DisplayName": "App.Prc.PurchaseOrderProduct",
"Order": 3,
"Url": "/admin/list/App.Prc.PurchaseOrderProduct",
"Icon": "FcViewDetails",
"ParentCode": "App.Prc.Orders",
"RequiredPermissionName": "App.Prc.PurchaseOrderProduct",
"CssClass": null,
"Target": null,
"IsDisabled": false,
"ElementId": null,
"ModuleId": "Prc"
} }
] ]
} }

View file

@ -230,6 +230,7 @@ Satınalma (`App.Prc`) › Siparişler (`App.Prc.Orders`):
| --- | --- | --- | | --- | --- | --- |
| Teklif Talepleri | `App.Prc.PurchaseRfq` | tüm durumlar; sekmeler Ürünler, Talep Kalemleri, Tedarikçi Teklifi, Diğer Bilgiler, Teklifler (Ekle = alternatif), Fiyat Karşılaştırması (toplu düzenleme) | | Teklif Talepleri | `App.Prc.PurchaseRfq` | tüm durumlar; sekmeler Ürünler, Talep Kalemleri, Tedarikçi Teklifi, Diğer Bilgiler, Teklifler (Ekle = alternatif), Fiyat Karşılaştırması (toplu düzenleme) |
| Satınalma Siparişleri | `App.Prc.PurchaseOrder` | tüm durumlar; ekle/sil (yalnız teklifsiz taslak); sekmeler Ürünler, Talep Kalemleri, Diğer Bilgiler, Teklifler | | Satınalma Siparişleri | `App.Prc.PurchaseOrder` | tüm durumlar; ekle/sil (yalnız teklifsiz taslak); sekmeler Ürünler, Talep Kalemleri, Diğer Bilgiler, Teklifler |
| Sipariş Ürünleri | `App.Prc.PurchaseOrderProduct` | sipariş × kalem, her satır bir kalem; tüm durumlar, kalemsiz sipariş yok; salt okunur; satırdaki Siparişi Aç siparişin detay sayfasını açar (`/admin/form/App.Prc.PurchaseOrder/@PurchaseOrderId`) |
Menüsüz alt formlar: teklif tarafında `App.Prc.PurchaseRfqLine`, `App.Prc.PurchaseRfqImportItem`, `App.Prc.PurchaseRfqQuote`, `App.Prc.PurchaseRfqOther`, `App.Prc.PurchaseRfqAlternative`, `App.Prc.PurchaseRfqAlternativeSupplier` (yalnız Alternatif Oluştur modalında), `App.Prc.PurchaseRfqComparison`; sipariş tarafında `App.Prc.PurchaseOrderLine`, `App.Prc.PurchaseOrderImportItem`, `App.Prc.PurchaseOrderOther`, `App.Prc.PurchaseOrderAlternative`; sözleşmede `App.Prc.PurchaseAgreementLine`. Menüsüz alt formlar: teklif tarafında `App.Prc.PurchaseRfqLine`, `App.Prc.PurchaseRfqImportItem`, `App.Prc.PurchaseRfqQuote`, `App.Prc.PurchaseRfqOther`, `App.Prc.PurchaseRfqAlternative`, `App.Prc.PurchaseRfqAlternativeSupplier` (yalnız Alternatif Oluştur modalında), `App.Prc.PurchaseRfqComparison`; sipariş tarafında `App.Prc.PurchaseOrderLine`, `App.Prc.PurchaseOrderImportItem`, `App.Prc.PurchaseOrderOther`, `App.Prc.PurchaseOrderAlternative`; sözleşmede `App.Prc.PurchaseAgreementLine`.
@ -241,7 +242,7 @@ Tüm tablolar kiracı + tam audit + soft delete.
- `Prc_T_PurchaseRfqLine`: PurchaseRfqId (FK cascade), TenderLineId (alternatiflerde aynı kalemi eşler), ProductId, Description, Quantity, UomId, UnitPrice, TaxRate, Amount, DeliveryDate (tedarikçinin termini; aktarımda talebin istenen termini), MaterialRequestItemId, IsSelected. - `Prc_T_PurchaseRfqLine`: PurchaseRfqId (FK cascade), TenderLineId (alternatiflerde aynı kalemi eşler), ProductId, Description, Quantity, UomId, UnitPrice, TaxRate, Amount, DeliveryDate (tedarikçinin termini; aktarımda talebin istenen termini), MaterialRequestItemId, IsSelected.
- `Prc_T_PurchaseOrder`: OrderNo, PurchaseRfqId (kaynak teklif), SupplierId, SupplierReference, AgreementId, BranchId, OperationTypeId (Inv_T_OperationType), WarehouseId (teslim alınacak depo, Inv_T_Warehouse; üçü de zorunlu; Şube › Depo › Operasyon Türü zinciriyle süzülür), CurrencyId, ExpectedArrival, ConfirmDate, ArrivalDate, BuyerUserId, SourceDocument, ProjectId, IncotermId, IncotermLocation, PaymentTermId, Terms, tutarlar, Status (Draft/Purchase/Cancelled), PendingAction, LastSentDate. - `Prc_T_PurchaseOrder`: OrderNo, PurchaseRfqId (kaynak teklif), SupplierId, SupplierReference, AgreementId, BranchId, OperationTypeId (Inv_T_OperationType), WarehouseId (teslim alınacak depo, Inv_T_Warehouse; üçü de zorunlu; Şube › Depo › Operasyon Türü zinciriyle süzülür), CurrencyId, ExpectedArrival, ConfirmDate, ArrivalDate, BuyerUserId, SourceDocument, ProjectId, IncotermId, IncotermLocation, PaymentTermId, Terms, tutarlar, Status (Draft/Purchase/Cancelled), PendingAction, LastSentDate.
- `Prc_T_PurchaseOrderLine`: PurchaseOrderId (FK cascade), ProductId, Description, Quantity, UomId, UnitPrice, TaxRate, Amount, DeliveryDate, MaterialRequestItemId, PurchaseRfqLineId. - `Prc_T_PurchaseOrderLine`: PurchaseOrderId (FK cascade), ProductId, Description, Quantity, UomId, UnitPrice, TaxRate, Amount, DeliveryDate, MaterialRequestItemId, PurchaseRfqLineId.
- Görünümler: `Prc_V_MaterialRequestAvailableItem` (aktarılabilir talep kalemleri), `Prc_V_PurchaseImportItem` (Talep Kalemleri sekmesi: kalem alabilen açık belge × aynı şubedeki aktarılabilir kalem, `Id` = belge|kalem), `Prc_V_PurchaseRfqComparison` (kıyas ve puan), `Prc_V_PurchaseRfqAlternative` (ihalenin belgeleri; teklif ve sipariş için `BaseId`), `Prc_V_PurchaseRfqAlternativeSupplier` (açık teklif × ihalede belgesi olmayan aktif kontak, `Id` = teklif|kontak), `Prc_V_PurchaseAgreementLine` (sözleşmenin sipariş edilen miktarı). - Görünümler: `Prc_V_MaterialRequestAvailableItem` (aktarılabilir talep kalemleri), `Prc_V_PurchaseImportItem` (Talep Kalemleri sekmesi: kalem alabilen açık belge × aynı şubedeki aktarılabilir kalem, `Id` = belge|kalem), `Prc_V_PurchaseRfqComparison` (kıyas ve puan), `Prc_V_PurchaseRfqAlternative` (ihalenin belgeleri; teklif ve sipariş için `BaseId`), `Prc_V_PurchaseRfqAlternativeSupplier` (açık teklif × ihalede belgesi olmayan aktif kontak, `Id` = teklif|kontak), `Prc_V_PurchaseAgreementLine` (sözleşmenin sipariş edilen miktarı), `Prc_V_PurchaseOrderLine` (Sipariş Ürünleri: silinmemiş sipariş × silinmemiş kalem, `Id` kalemin Id'si; vergi hariç ve KDV kalemin KDV dahil tutarından ayrıştırılır, Talep No kalemin malzeme talebinden).
- Yordamlar: `Prc_P_PurchaseRfq{AfterSave,Recalc,ComparisonSave,CreateAlternative,FromAgreement,Send,SetStatus,Award}`, `Prc_P_PurchaseOrder{AfterSave,Recalc,Send,SetStatus}`, `Prc_P_PurchaseImportItem` (teklif ve sipariş için ortak, kalem bazlı aktarım), `Adm_P_SequenceNext`. - Yordamlar: `Prc_P_PurchaseRfq{AfterSave,Recalc,ComparisonSave,CreateAlternative,FromAgreement,Send,SetStatus,Award}`, `Prc_P_PurchaseOrder{AfterSave,Recalc,Send,SetStatus}`, `Prc_P_PurchaseImportItem` (teklif ve sipariş için ortak, kalem bazlı aktarım), `Adm_P_SequenceNext`.
### 5. İş kuralları ve hesaplar ### 5. İş kuralları ve hesaplar
@ -286,8 +287,8 @@ Yönetim › Tanımlamalar › Kontaklar (tedarikçi, e-posta, Satınalma sekmes
### 10. Dosyalar ve geri alma ### 10. Dosyalar ve geri alma
- Tablolar: `sql/object/Prc_T_PurchaseRfq.sql`, `Prc_T_PurchaseRfqLine.sql`, `Prc_T_PurchaseOrder.sql`, `Prc_T_PurchaseOrderLine.sql`, `Prc_T_PurchaseAgreement.sql`, `Prc_T_PurchaseAgreementLine.sql` (+ postgres). - Tablolar: `sql/object/Prc_T_PurchaseRfq.sql`, `Prc_T_PurchaseRfqLine.sql`, `Prc_T_PurchaseOrder.sql`, `Prc_T_PurchaseOrderLine.sql`, `Prc_T_PurchaseAgreement.sql`, `Prc_T_PurchaseAgreementLine.sql` (+ postgres).
- Görünüm/yordam: `Prc_V_MaterialRequestAvailableItem`, `Prc_V_PurchaseImportItem`, `Prc_V_PurchaseRfqComparison`, `Prc_V_PurchaseRfqAlternative`, `Prc_V_PurchaseRfqAlternativeSupplier`, `Prc_V_PurchaseAgreementLine`, `Prc_P_PurchaseRfq*`, `Prc_P_PurchaseOrder*`, `Prc_P_PurchaseImportItem`, `Adm_P_SequenceNext` (sql + postgres object). - Görünüm/yordam: `Prc_V_MaterialRequestAvailableItem`, `Prc_V_PurchaseImportItem`, `Prc_V_PurchaseRfqComparison`, `Prc_V_PurchaseRfqAlternative`, `Prc_V_PurchaseRfqAlternativeSupplier`, `Prc_V_PurchaseAgreementLine`, `Prc_V_PurchaseOrderLine`, `Prc_P_PurchaseRfq*`, `Prc_P_PurchaseOrder*`, `Prc_P_PurchaseImportItem`, `Adm_P_SequenceNext` (sql + postgres object).
- Wizard'lar: `Prc_PurchaseRfq{,Line,ImportItem,Quote,Other,Alternative,Comparison}.json`, `Prc_PurchaseOrder{,Line,ImportItem,Other,Alternative}.json`, `Prc_PurchaseAgreement{,Line}.json`. - Wizard'lar: `Prc_PurchaseRfq{,Line,ImportItem,Quote,Other,Alternative,Comparison}.json`, `Prc_PurchaseOrder{,Line,ImportItem,Other,Alternative,Product}.json`, `Prc_PurchaseAgreement{,Line}.json`.
- Arayüz: `ui/src/views/purchasing/PurchaseDocGridDialog.tsx` (kayıt: `DialogShowComponent`, ad `PurchaseDocGrid`). - Arayüz: `ui/src/views/purchasing/PurchaseDocGridDialog.tsx` (kayıt: `DialogShowComponent`, ad `PurchaseDocGrid`).
- Veri: Diziler `PurchaseRfq`, `PurchaseOrder1`, `PurchaseAgreement1`; Bildirim Türleri ve Kuralları `TeklifTalebiGonderildi`, `SatinalmaSiparisiGonderildi`. - Veri: Diziler `PurchaseRfq`, `PurchaseOrder1`, `PurchaseAgreement1`; Bildirim Türleri ve Kuralları `TeklifTalebiGonderildi`, `SatinalmaSiparisiGonderildi`.
- Geri alma: önce ana ekranlar (`Prc_PurchaseRfq`, `Prc_PurchaseOrder`, `Prc_PurchaseAgreement`), sonra alt formlar `delete_wizard` ile silinir; tablolar yalnız açık onayla düşürülür. - Geri alma: önce ana ekranlar (`Prc_PurchaseRfq`, `Prc_PurchaseOrder`, `Prc_PurchaseAgreement`), sonra alt formlar `delete_wizard` ile silinir; tablolar yalnız açık onayla düşürülür.
@ -753,6 +754,49 @@ Yönetim › Tanımlamalar › Kontaklar (tedarikçi, e-posta, Satınalma sekmes
| `LastSentDate` | Son Gönderim | dxDateBox | | | | | `LastSentDate` | Son Gönderim | dxDateBox | | | |
| `Terms` | Şartlar | dxHtmlEditor | | ✓ | | | `Terms` | Şartlar | dxHtmlEditor | | ✓ | |
#### Sipariş Ürünleri — `App.Prc.PurchaseOrderProduct`
| Özellik | Değer |
| --- | --- |
| Wizard | `wizard/Prc_PurchaseOrderProduct.json` |
| Yol | Liste (Wizard) |
| Menü | `App.Prc.Orders` › `App.Prc.PurchaseOrderProduct` (sıra 3) |
| Yetki | `App.Prc.PurchaseOrderProduct` + `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| Açıklama | Sipariş Ürünleri — satınalma siparişlerinin kalemleri, siparişin başlık bilgileriyle yan yana; her satır bir sipariş kalemidir. |
| Kaynak | `Prc_V_PurchaseOrderLine` · 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.Draft`, `App.Prc.PurchaseOrder.Widget.LateReceipt`, `App.Widget.TotalRevenue` |
| Butonlar | `App.Prc.PurchaseOrderProduct.OpenOrder` |
| Alan | Başlık | Editör | Zorunlu | Formda | Kaynak |
| --- | --- | --- | --- | --- | --- |
| `PurchaseOrderId` | Satınalma Siparişi | dxTextBox | | | |
| `OrderNo` | Referans | dxTextBox | | | |
| `Status` | Durum | dxSelectBox | | | liste: Draft, Purchase, Cancelled |
| `SupplierId` | Tedarikçi | dxSelectBox | | | sorgu: Adm_T_Contact |
| `BranchId` | Şube Adı | dxSelectBox | | | sorgu: Sas_T_Branch |
| `WarehouseId` | Depo | dxSelectBox | | | sorgu: Inv_T_Warehouse |
| `OperationTypeId` | Operasyon Türü | dxSelectBox | | | sorgu: Inv_T_OperationType |
| `ProductId` | Ürün | dxSelectBox | | | sorgu: Inv_T_Product |
| `Description` | Açıklama | dxTextBox | | | |
| `Quantity` | Miktar | dxNumberBox | | | |
| `UomId` | Birim | dxSelectBox | | | sorgu: Adm_T_Uom |
| `UnitPrice` | Birim Fiyat | dxNumberBox | | | |
| `TaxRate` | Vergi Oran | dxNumberBox | | | |
| `UntaxedAmount` | Vergi Hariç Tutar | dxNumberBox | | | |
| `TaxAmount` | Vergi Tutarı | dxNumberBox | | | |
| `Amount` | Tutar | dxNumberBox | | | |
| `CurrencyId` | Para Birimi | dxSelectBox | | | sorgu: Sas_H_Currency |
| `DeliveryDate` | Teslim Tarihi | dxDateBox | | | |
| `ConfirmDate` | Onaylama Tarihi | dxDateBox | | | |
| `ExpectedArrival` | Beklenen Geliş | dxDateBox | | | |
| `ArrivalDate` | Varış | dxDateBox | | | |
| `BuyerUserId` | Alıcı | dxSelectBox | | | sorgu: AbpUsers |
| `RequestNo` | Talep No | dxTextBox | | | |
| `SourceDocument` | Kaynak | dxTextBox | | | |
#### Teklif Kalemleri — `App.Prc.PurchaseRfqLine` #### Teklif Kalemleri — `App.Prc.PurchaseRfqLine`
| Özellik | Değer | | Özellik | Değer |

View file

@ -0,0 +1,15 @@
CREATE OR REPLACE VIEW public."Prc_V_PurchaseOrderLine"
AS
-- Sipariş Ürünleri listesinin kaynağı: satınalma siparişi başlığı × kalem, her kalem bir satır; kalemi olmayan
-- sipariş gelmez. Vergi hariç ve KDV kalemin KDV dahil tutarından (Prc_P_PurchaseOrderRecalc) ayrıştırılır.
SELECT l."Id", l."PurchaseOrderId", o."TenantId", o."IsDeleted",
o."OrderNo", o."Status", o."SupplierId", o."BranchId", o."WarehouseId", o."OperationTypeId", o."CurrencyId",
o."ConfirmDate", o."ExpectedArrival", o."ArrivalDate", o."BuyerUserId", o."SourceDocument",
l."ProductId", l."Description", l."Quantity", l."UomId", l."UnitPrice", l."TaxRate",
ROUND(l."Quantity" * l."UnitPrice", 2) AS "UntaxedAmount", l."Amount" - ROUND(l."Quantity" * l."UnitPrice", 2) AS "TaxAmount", l."Amount",
l."DeliveryDate", m."RequestNo"
FROM "Prc_T_PurchaseOrderLine" l
INNER JOIN "Prc_T_PurchaseOrder" o ON o."Id" = l."PurchaseOrderId" AND o."IsDeleted" = FALSE
LEFT JOIN "Prc_T_MaterialRequestItem" i ON i."Id" = l."MaterialRequestItemId"
LEFT JOIN "Prc_T_MaterialRequest" m ON m."Id" = i."MaterialRequestId"
WHERE l."IsDeleted" = FALSE;

View file

@ -0,0 +1,16 @@
CREATE OR ALTER VIEW [dbo].[Prc_V_PurchaseOrderLine]
AS
-- Sipariş Ürünleri listesinin kaynağı: satınalma siparişi başlığı × kalem, her kalem bir satır; kalemi olmayan
-- sipariş gelmez. Vergi hariç ve KDV kalemin KDV dahil tutarından (Prc_P_PurchaseOrderRecalc) ayrıştırılır.
SELECT l.Id, l.PurchaseOrderId, o.TenantId, o.IsDeleted,
o.OrderNo, o.Status, o.SupplierId, o.BranchId, o.WarehouseId, o.OperationTypeId, o.CurrencyId,
o.ConfirmDate, o.ExpectedArrival, o.ArrivalDate, o.BuyerUserId, o.SourceDocument,
l.ProductId, l.Description, l.Quantity, l.UomId, l.UnitPrice, l.TaxRate,
ROUND(l.Quantity * l.UnitPrice, 2) AS UntaxedAmount, l.Amount - ROUND(l.Quantity * l.UnitPrice, 2) AS TaxAmount, l.Amount,
l.DeliveryDate, m.RequestNo
FROM Prc_T_PurchaseOrderLine l
INNER JOIN Prc_T_PurchaseOrder o ON o.Id = l.PurchaseOrderId AND o.IsDeleted = 0
LEFT JOIN Prc_T_MaterialRequestItem i ON i.Id = l.MaterialRequestItemId
LEFT JOIN Prc_T_MaterialRequest m ON m.Id = i.MaterialRequestId
WHERE l.IsDeleted = 0;
GO

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,18 @@ export const putListFormJsonRow = (id: string, input: ListFormJsonRowDto) =>
data: input, data: input,
}) })
export const moveListFormJsonRow = (
id: string,
field: string,
fromIndex: number,
toIndex: number,
) =>
apiService.fetchData({
method: 'POST',
url: `/api/admin/list-form-json-row/${id}/move`,
params: { field, fromIndex, toIndex },
})
export const deleteListFormJsonRow = (id: string, field: string, index: number) => export const deleteListFormJsonRow = (id: string, field: string, index: number) =>
apiService.fetchData({ apiService.fetchData({
method: 'DELETE', method: 'DELETE',

View file

@ -1,6 +1,5 @@
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { Button, Card, Table } from '@/components/ui' import { Button, Card, Table } from '@/components/ui'
import TBody from '@/components/ui/Table/TBody'
import THead from '@/components/ui/Table/THead' import THead from '@/components/ui/Table/THead'
import Td from '@/components/ui/Table/Td' import Td from '@/components/ui/Table/Td'
import Th from '@/components/ui/Table/Th' import Th from '@/components/ui/Table/Th'
@ -11,6 +10,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { useState } from 'react' import { useState } from 'react'
import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa' import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa'
import JsonRowOpDialogCommand from './json-row-operations/JsonRowOpDialogCommand' import JsonRowOpDialogCommand from './json-row-operations/JsonRowOpDialogCommand'
import SortableJsonRows from './json-row-operations/SortableJsonRows'
import { JsonRowDialogData } from './json-row-operations/types' import { JsonRowDialogData } from './json-row-operations/types'
import { UiCommandButtonPositionTypeEnum } from '@/proxy/form/models' import { UiCommandButtonPositionTypeEnum } from '@/proxy/form/models'
@ -66,11 +66,17 @@ function FormTabCommands() {
<Th>{translate('::App.ListFormEdit.CommandVisibleExpression')}</Th> <Th>{translate('::App.ListFormEdit.CommandVisibleExpression')}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <SortableJsonRows
{initialValues.commandColumnDto.map((row, index) => ( listFormId={initialValues.id ?? ''}
<Tr key={index}> tabName={ListFormEditTabs.CommandColumnsJsonRow}
storeField="commandColumnDto"
rows={initialValues.commandColumnDto}
>
{(row, index, dragHandle) => (
<>
<Td> <Td>
<div className="flex-wrap inline-flex xl:flex items-center gap-2"> <div className="flex-wrap inline-flex xl:flex items-center gap-2">
{dragHandle}
<Button <Button
shape="circle" shape="circle"
variant="plain" variant="plain"
@ -117,9 +123,9 @@ function FormTabCommands() {
<Td>{row.url}</Td> <Td>{row.url}</Td>
<Td>{row.isVisible ? 'Yes' : 'No'}</Td> <Td>{row.isVisible ? 'Yes' : 'No'}</Td>
<Td>{row.visibleExpression}</Td> <Td>{row.visibleExpression}</Td>
</Tr> </>
))} )}
</TBody> </SortableJsonRows>
</Table> </Table>
</Card> </Card>
<JsonRowOpDialogCommand <JsonRowOpDialogCommand

View file

@ -1,6 +1,5 @@
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { Button, Card, Table } from '@/components/ui' import { Button, Card, Table } from '@/components/ui'
import TBody from '@/components/ui/Table/TBody'
import THead from '@/components/ui/Table/THead' import THead from '@/components/ui/Table/THead'
import Td from '@/components/ui/Table/Td' import Td from '@/components/ui/Table/Td'
import Th from '@/components/ui/Table/Th' import Th from '@/components/ui/Table/Th'
@ -11,6 +10,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { useState } from 'react' import { useState } from 'react'
import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa' import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa'
import JsonRowOpDialogEditForm from './json-row-operations/JsonRowOpDialogEditForm' import JsonRowOpDialogEditForm from './json-row-operations/JsonRowOpDialogEditForm'
import SortableJsonRows from './json-row-operations/SortableJsonRows'
import { JsonRowDialogData } from './json-row-operations/types' import { JsonRowDialogData } from './json-row-operations/types'
function FormTabEditForm(props: { listFormCode: string }) { function FormTabEditForm(props: { listFormCode: string }) {
@ -61,11 +61,17 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Th>{translate('::App.ListFormEdit.EditingFormColumnSpan')}</Th> <Th>{translate('::App.ListFormEdit.EditingFormColumnSpan')}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <SortableJsonRows
{initialValues.editingFormDto.map((row, index) => ( listFormId={initialValues.id ?? ''}
<Tr key={index}> tabName={ListFormEditTabs.Edit.EditingFormFieldsJsonRow}
storeField="editingFormDto"
rows={initialValues.editingFormDto}
>
{(row, index, dragHandle) => (
<>
<Td> <Td>
<div className="flex-wrap inline-flex xl:flex items-center gap-2"> <div className="flex-wrap inline-flex xl:flex items-center gap-2">
{dragHandle}
<Button <Button
shape="circle" shape="circle"
variant="plain" variant="plain"
@ -108,9 +114,9 @@ function FormTabEditForm(props: { listFormCode: string }) {
<Td>{row.caption}</Td> <Td>{row.caption}</Td>
<Td>{row.colCount}</Td> <Td>{row.colCount}</Td>
<Td>{row.colSpan}</Td> <Td>{row.colSpan}</Td>
</Tr> </>
))} )}
</TBody> </SortableJsonRows>
</Table> </Table>
</Card> </Card>
<JsonRowOpDialogEditForm <JsonRowOpDialogEditForm

View file

@ -1,6 +1,5 @@
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { Button, Card, Table } from '@/components/ui' import { Button, Card, Table } from '@/components/ui'
import TBody from '@/components/ui/Table/TBody'
import THead from '@/components/ui/Table/THead' import THead from '@/components/ui/Table/THead'
import Td from '@/components/ui/Table/Td' import Td from '@/components/ui/Table/Td'
import Th from '@/components/ui/Table/Th' import Th from '@/components/ui/Table/Th'
@ -11,6 +10,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
import { useState } from 'react' import { useState } from 'react'
import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa' import { FaEdit, FaFileMedical, FaTrash } from 'react-icons/fa'
import JsonRowOpDialogSubForm from './json-row-operations/JsonRowOpDialogSubForm' import JsonRowOpDialogSubForm from './json-row-operations/JsonRowOpDialogSubForm'
import SortableJsonRows from './json-row-operations/SortableJsonRows'
import { JsonRowDialogData } from './json-row-operations/types' import { JsonRowDialogData } from './json-row-operations/types'
import { dbSourceTypeOptions } from './options' import { dbSourceTypeOptions } from './options'
@ -62,11 +62,17 @@ function FormTabSubForm() {
<Th>{translate('::App.ListFormEdit.SubFormsRelation')}</Th> <Th>{translate('::App.ListFormEdit.SubFormsRelation')}</Th>
</Tr> </Tr>
</THead> </THead>
<TBody> <SortableJsonRows
{initialValues.subFormsDto.map((row, index) => ( listFormId={initialValues.id ?? ''}
<Tr key={index}> tabName={ListFormEditTabs.SubForm}
storeField="subFormsDto"
rows={initialValues.subFormsDto}
>
{(row, index, dragHandle) => (
<>
<Td> <Td>
<div className="flex-wrap inline-flex xl:flex items-center gap-2"> <div className="flex-wrap inline-flex xl:flex items-center gap-2">
{dragHandle}
<Button <Button
shape="circle" shape="circle"
variant="plain" variant="plain"
@ -122,9 +128,9 @@ function FormTabSubForm() {
))} ))}
</ul> </ul>
</Td> </Td>
</Tr> </>
))} )}
</TBody> </SortableJsonRows>
</Table> </Table>
</Card> </Card>
<JsonRowOpDialogSubForm <JsonRowOpDialogSubForm

View file

@ -0,0 +1,149 @@
import { Button, Notification, toast } from '@/components/ui'
import TBody from '@/components/ui/Table/TBody'
import Tr from '@/components/ui/Table/Tr'
import { GridOptionsEditDto } from '@/proxy/form/models'
import { getListFormJsonRow, moveListFormJsonRow } from '@/services/admin/list-form.service'
import { useStoreActions } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import {
DndContext,
DragEndEvent,
KeyboardSensor,
PointerSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core'
import {
SortableContext,
arrayMove,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import classNames from 'classnames'
import { ReactNode, useState } from 'react'
import { FaGripVertical } from 'react-icons/fa'
import { JsonRowDialogData } from './types'
function SortableJsonRow({
id,
disabled,
dragTitle,
children,
}: {
id: string
disabled: boolean
dragTitle: string
children: (dragHandle: ReactNode) => ReactNode
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
disabled,
})
return (
<Tr
ref={setNodeRef}
style={{ transform: CSS.Translate.toString(transform), transition }}
className={classNames(isDragging && 'relative z-10 opacity-60')}
>
{children(
<Button
type="button"
variant="plain"
shape="circle"
size="xs"
title={dragTitle}
disabled={disabled}
icon={<FaGripVertical />}
className="cursor-grab"
{...attributes}
{...listeners}
/>,
)}
</Tr>
)
}
/**
* JSON satır sekmelerinin (Butonlar, Form, Alt Formlar) gövdesi: satırlar tutamaçtan sürüklenerek
* sıralanır, sıra sunucuya yazılır ve sekmenin listesi sunucudan yeniden okunur (form grubunda
* sunucu grup numaralarını da yeniden verir).
*/
function SortableJsonRows<T>({
listFormId,
tabName,
storeField,
rows,
children,
}: {
listFormId: string
tabName: JsonRowDialogData['tabName']
storeField: keyof GridOptionsEditDto
rows: T[]
children: (row: T, index: number, dragHandle: ReactNode) => ReactNode
}) {
const { translate } = useLocalization()
const { setJsonValue } = useStoreActions((a) => a.admin.lists)
const [isSaving, setIsSaving] = useState(false)
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
)
const ids = rows.map((_, index) => String(index))
const onDragEnd = async ({ active, over }: DragEndEvent) => {
if (!over || active.id === over.id) {
return
}
const fromIndex = Number(active.id)
const toIndex = Number(over.id)
setJsonValue({ field: storeField, data: arrayMove(rows, fromIndex, toIndex) })
setIsSaving(true)
try {
await moveListFormJsonRow(listFormId, tabName, fromIndex, toIndex)
toast.push(
<Notification type="success" duration={2000}>
{translate('::App.Platform.FormBilgileriKaydedildi')}
</Notification>,
{ placement: 'bottom-end' },
)
} catch (error: any) {
toast.push(
<Notification type="danger">
{translate('::App.Platform.Error')}
<code>{error}</code>
</Notification>,
{ placement: 'bottom-end' },
)
} finally {
const resp = await getListFormJsonRow(listFormId, tabName)
setJsonValue({ field: storeField, data: resp.data })
setIsSaving(false)
}
}
return (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
<TBody>
{rows.map((row, index) => (
<SortableJsonRow
key={ids[index]}
id={ids[index]}
disabled={isSaving || rows.length < 2}
dragTitle={translate('::App.ListFormField.DragToReorder')}
>
{(dragHandle) => children(row, index, dragHandle)}
</SortableJsonRow>
))}
</TBody>
</SortableContext>
</DndContext>
)
}
export default SortableJsonRows