Add CargoPartner/CargoTruck entities and logistics links

Introduced CargoPartner and CargoTruck entities for logistics, updated Shipping and IShipping to link to carrier and vehicles, and enhanced UI grids for selection and validation. Updated documentation and glossary to reflect new logistics model and terminology. Removed obsolete Shippings navigation from CargoPartner.
This commit is contained in:
Loretta 2026-05-31 14:00:44 +02:00
parent c9b0bf8334
commit 3cb5efe2d2
9 changed files with 170 additions and 13 deletions

View File

@ -7,7 +7,7 @@ using Mango.Nop.Core.Entities;
namespace FruitBank.Common.Entities;
[AcBinarySerializable(false, true, false, true, false, false)]
//[ToonDescription("Business partner with address and tax information", Purpose = "Represents an external legal entity, specifically a Supplier who provides goods or a business partner involved in the procurement chain")]
[ToonDescription("Transport / haulage company (carrier) with its vehicle fleet", Purpose = "A carrier that delivers goods to the warehouse and owns the CargoTrucks (both trucks and trailers). Distinct from Partner, which is the goods supplier. Name, address and tax fields are inherited from PartnerBase.")]
[Table(Name = FruitBankConstClient.CargoPartnerDbTableName)]
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.CargoPartnerDbTableName)]
public sealed class CargoPartner : PartnerBase, ICargoPartner
@ -16,6 +16,5 @@ public sealed class CargoPartner : PartnerBase, ICargoPartner
public List<CargoTruck>? CargoTrucks { get; set; }
//[Association(ThisKey = nameof(Id), OtherKey = nameof(Shipping.CargoPartnerId), CanBeNull = true)]
public List<Shipping>? Shippings { get; set; }
//public decimal DecProp{get; set; }
//public List<Shipping>? Shippings { get; set; }
}

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,11 @@ Domain entities for inbound/outbound goods tracking and inventory. All map to `f
- **`ShippingDocumentToFiles.cs`** — Many-to-many link: document ↔ file with DocumentType. Table: `fbShippingDocumentToFiles`.
- **`Partner.cs`** — External supplier with address and tax info. Table: `fbPartner`.
## Cargo / Logistics
- **`CargoPartner.cs`** — Freight/haulage partner (carrier). Distinct from `Partner` (supplier) — this is the transport side. Has `CargoTrucks` and `Shippings` collections. Table: `fbCargoPartner`.
- **`CargoTruck.cs`** — Individual truck belonging to a `CargoPartner` (`LicencePlate`, `CountryCode`, `IsTrailer` for trailers). Table: `fbCargoTruck`.
## Order (Outbound)
- **`OrderItemPallet.cs`** — Measurement record for outgoing goods with RevisorId for audit. Table: `fbOrderItemPallet`.

View File

@ -13,6 +13,10 @@ namespace FruitBank.Common.Entities;
[System.ComponentModel.DataAnnotations.Schema.Table(FruitBankConstClient.ShippingDbTableName)]
public sealed class Shipping : MgEntityBase, IShipping, IEntityComment
{
public int CargoPartnerId { get; set; }
public int? CargoTruckId { get; set; }
public int? CargoTrailerId { get; set; }
public DateTime ShippingDate { get; set; } = DateTime.Now;
public string LicencePlate { get; set; }
public bool IsAllMeasured { get; set; }
@ -21,6 +25,18 @@ public sealed class Shipping : MgEntityBase, IShipping, IEntityComment
public DateTime? MeasuredDate { get; set; }
[ToonDescription(Purpose = "The transport company (carrier) that hauled this delivery — distinct from the goods supplier, which is reached via ShippingDocument.Partner.")]
[Association(ThisKey = nameof(CargoPartnerId), OtherKey = nameof(CargoPartner.Id), CanBeNull = true)]
public CargoPartner CargoPartner { get; set; }
[ToonDescription(Purpose = "The tractor/truck unit — a CargoTruck row with IsTrailer=false.")]
[Association(ThisKey = nameof(CargoTruckId), OtherKey = nameof(CargoTruck.Id), CanBeNull = true)]
public CargoTruck CargoTruck { get; set; }
[ToonDescription(Purpose = "The trailer — the SAME CargoTruck table as CargoTruck, but the row with IsTrailer=true (CargoTrailerId → CargoTruck.Id).")]
[Association(ThisKey = nameof(CargoTrailerId), OtherKey = nameof(CargoTruck.Id), CanBeNull = true)]
public CargoTruck CargoTrailer { get; set; }
[Association(ThisKey = nameof(Id), OtherKey = nameof(ShippingDocument.ShippingId), CanBeNull = true)]
public List<ShippingDocument>? ShippingDocuments { get; set; }

View File

@ -14,4 +14,6 @@ public interface ICargoTruck : IEntityInt, ITimeStampInfo
public string LicencePlate { get; set; }
public bool IsTrailer { get; set; }
public string? CargoPartnerName { get; }
}

View File

@ -6,6 +6,10 @@ namespace FruitBank.Common.Interfaces;
public interface IShipping : IEntityInt, ITimeStampInfo//, IMeasured
{
public int CargoPartnerId { get; set; }
public int? CargoTruckId { get; set; }
public int? CargoTrailerId { get; set; }
DateTime ShippingDate { get; set; }
string LicencePlate { get; set; }
bool IsAllMeasured { get; set; }

View File

@ -45,7 +45,7 @@
@if (IsMasterGrid)
{
var cargoPartner = ((CargoPartner)context.DataItem);
var shippings = cargoPartner?.Shippings ?? [];
var shippings = new AcObservableCollection<Shipping>(); //cargoPartner?.Shippings ?? [];
var cargoTrucks = cargoPartner?.CargoTrucks ?? [];
<DxTabs>
@ -56,13 +56,13 @@
}
</DxTabPage>
<DxTabPage Text="Szállítmányok">
@* <DxTabPage Text="Szállítmányok">
@{
var observableShippings = new AcObservableCollection<Shipping>(shippings);
<GridShipping Shippings="@observableShippings" IsMasterGrid="false" />
//var observableShippings = new AcObservableCollection<Shipping>(shippings);
<GridShipping Shippings="@shippings" IsMasterGrid="false" />
}
</DxTabPage>
</DxTabs>
*@ </DxTabs>
}
</DetailRowTemplate>
<ToolbarTemplate>
@ -83,6 +83,7 @@
[Parameter] public AcObservableCollection<CargoPartner>? CargoPartners { get; set; }
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
const string ExportFileName = "ExportResult";
string GridSearchText = "";
bool EditItemsEnabled { get; set; }

View File

@ -7,6 +7,7 @@
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.Shippings
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Extensions
@using FruitBankHybrid.Shared.Services.Loggers
@using FruitBankHybrid.Shared.Services.SignalRs
@ -19,11 +20,68 @@
<GridContent>
<GridShippingBase @ref="Grid" DataSource="Shippings" AutoSaveLayoutName="GridShipping" SignalRClient="FruitBankSignalRClient" Logger="_logger"
CssClass="@GridCss" ValidationEnabled="false"
OnGridEditModelSaving="Grid_EditModelSaving"
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
<DxGridDataColumn FieldName="ShippingDate" Caption="Beérkezés" />
<DxGridDataColumn FieldName="CargoPartnerId" Caption="Fuvarozó">
<EditSettings>
<DxComboBoxSettings Data="CargoPartners"
ValueFieldName="Id"
TextFieldName="Name"
DropDownBodyCssClass="dd-body-class"
ListRenderMode="ListRenderMode.Entire"
SearchMode="ListSearchMode.AutoSearch"
SearchFilterCondition="ListSearchFilterCondition.Contains"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
<Columns>
<DxListEditorColumn FieldName="@nameof(CargoPartner.Id)" />
<DxListEditorColumn FieldName="@nameof(CargoPartner.Name)" />
<DxListEditorColumn FieldName="@nameof(CargoPartner.TaxId)" />
</Columns>
</DxComboBoxSettings>
</EditSettings>
</DxGridDataColumn>
<DxGridDataColumn FieldName="CargoTruckId" Caption="Vontató">
<EditSettings>
<DxComboBoxSettings Data="CargoTrucks.Where(x => !x.IsTrailer)"
ValueFieldName="Id"
TextFieldName="LicencePlate"
DropDownBodyCssClass="dd-body-class"
ListRenderMode="ListRenderMode.Entire"
SearchMode="ListSearchMode.AutoSearch"
SearchFilterCondition="ListSearchFilterCondition.Contains"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
<Columns>
<DxListEditorColumn FieldName="@nameof(CargoTruck.Id)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.CargoPartnerName)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.LicencePlate)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
</Columns>
</DxComboBoxSettings>
</EditSettings>
</DxGridDataColumn>
<DxGridDataColumn FieldName="CargoTrailerId" Caption="Utánfutó">
<EditSettings>
<DxComboBoxSettings Data="CargoTrucks.Where(x => x.IsTrailer)"
ValueFieldName="Id"
TextFieldName="LicencePlate"
DropDownBodyCssClass="dd-body-class"
ListRenderMode="ListRenderMode.Entire"
SearchMode="ListSearchMode.AutoSearch"
SearchFilterCondition="ListSearchFilterCondition.Contains"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto">
<Columns>
<DxListEditorColumn FieldName="@nameof(CargoTruck.Id)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.CargoPartnerName)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.LicencePlate)" />
<DxListEditorColumn FieldName="@nameof(CargoTruck.IsTrailer)" />
</Columns>
</DxComboBoxSettings>
</EditSettings>
</DxGridDataColumn>
<DxGridDataColumn FieldName="ShippingDate" Caption="Beérkezés"/>
<DxGridDataColumn FieldName="LicencePlate" Caption="Rendszám" />
<DxGridDataColumn FieldName="Comment" Caption="Megjegyzés" ReadOnly="false" />
<DxGridDataColumn FieldName="CargoCompany" Caption="Fuvarozó" ReadOnly="false" />
@ -71,7 +129,11 @@
[Parameter] public bool IsMasterGrid { get; set; } = false;
[Parameter] public AcObservableCollection<Partner>? Partners { get; set; }
[Parameter] public AcObservableCollection<CargoPartner>? CargoPartners { get; set; }
[Parameter] public AcObservableCollection<Shipping>? Shippings { get; set; }
[Inject] public required IDialogService DialogService { get; set; } = null!;
public List<CargoTruck> CargoTrucks { get; set; } = [];
const string ExportFileName = "ExportResult";
string GridSearchText = "";
@ -91,6 +153,23 @@
private async Task ReloadDataFromDb(bool forceReload = false)
{
using (await ObjectLock.GetSemaphore<CargoPartner>().UseWaitAsync())
{
if (CargoPartners == null) CargoPartners = new AcObservableCollection<CargoPartner>(await FruitBankSignalRClient.GetCargoPartners() ?? []);
else if (CargoPartners.Count == 0 || forceReload)
{
CargoPartners.Replace(await FruitBankSignalRClient.GetCargoPartners() ?? []);
}
}
CargoTrucks = CargoPartners.Where(x => x.CargoTrucks != null).SelectMany(x =>
{
if (x.CargoTrucks == null) return null;
foreach (var cargoTruck in x.CargoTrucks) cargoTruck.CargoPartner = x;
return x.CargoTrucks;
}).ToList();
if (!IsMasterGrid) return;
using (await ObjectLock.GetSemaphore<Partner>().UseWaitAsync())
@ -121,7 +200,23 @@
EditItemsEnabled = true;
}
protected async Task OnActiveTabChanged(int activeTabIndex)
async Task Grid_EditModelSaving(GridEditModelSavingEventArgs e)
{
var shipping = (Shipping)e.EditModel; // a szerkesztett sor
if (ValidateCargoConsistency(shipping, out string error)) return;
e.Cancel = true;
await DialogService.ShowMessageBoxAsync("Hibás adat", error, MessageBoxRenderStyle.Danger);
// e.IsNew → add vs update
// ... before-save logika (validáció, számított mezők, CargoTrailer beállítás stb.)
// megszakítás, ha kell:
// e.Cancel = true;
}
async protected Task OnActiveTabChanged(int activeTabIndex)
{
_activeTabIndex = activeTabIndex;
return;
@ -142,5 +237,29 @@
// break;
// }
}
private bool ValidateCargoConsistency(Shipping shipping, out string? error)
{
error = null;
if (shipping.CargoTruckId != null)
{
var truck = CargoTrucks.FirstOrDefault(t => t.Id == shipping.CargoTruckId);
if (truck == null || truck.CargoPartnerId != shipping.CargoPartnerId)
{
error = $"A vontató ({truck?.LicencePlate}) nem a kiválasztott fuvarozóhoz tartozik!";
return false;
}
}
if (shipping.CargoTrailerId == null) return true;
var trailer = CargoTrucks.FirstOrDefault(t => t.Id == shipping.CargoTrailerId);
if (trailer != null && trailer.CargoPartnerId == shipping.CargoPartnerId) return true;
error = $"Az utánfutó ({trailer?.LicencePlate}) nem a kiválasztott fuvarozóhoz tartozik!";
return false;
}
}

View File

@ -14,6 +14,8 @@ Domain terminology for the FruitBank system. **Read this before making changes.*
| **Order** | Megrendelés | **OUTBOUND** delivery: warehouse → customer. |
| **Pallet** (XxxItemPallet) | Mérési rekord | A **measurement record**, NOT a physical pallet. Always created, even for non-measurable products. |
| **Partner** | Partner / Beszállító | External supplier providing goods. |
| **CargoPartner** | Fuvarozó | Freight carrier company that transports goods. **Distinct from Partner** (supplier). |
| **CargoTruck** | Kamion | A specific truck belonging to a CargoPartner (licence plate, trailer flag). |
| **ShippingDocument** | Szállítólevél | Supplier's delivery note or invoice, linked to a Shipping. |
| **ShippingItem** | Szállítólevél tétel | Product line on a shipping document. Tracks declared vs measured discrepancies. |
| **StockTaking** | Leltározás | Inventory session that freezes logical stock and reconciles with physical count. |