Přeskočit obsah

Datový model (EF Core)

Kompletní přehled entit a jejich vztahů v databázi EVA.

Schéma: dbo (PostgreSQL schéma)
Primární klíče: vždy long OID


Správa majetku

Article ──→ ArticleCategory   (FK: CategoryId)
Article ──→ ArticleType       (FK: TypeId)
Article ──→ ArticleState      (FK: StateId)
Article ──→ ArticleDisposalMethod (FK: DisposalMethodId, nullable)
Article ──→ ArticleDisposalReason (FK: DisposalReasonId, nullable)
Article ──→ User              (FK: UserId, nullable — přiřazený uživatel)
Article ──→ User              (FK: ResponsibleUserId, nullable — odpovědná osoba)

Article — hlavní entita

public class Article
{
    public long OID { get; set; }
    public string Code { get; set; }          // Inventární číslo
    public string Name { get; set; }          // Název
    public long CategoryId { get; set; }
    public long TypeId { get; set; }
    public long StateId { get; set; }
    public decimal Price { get; set; }
    public string? SerialNumber { get; set; }
    public string? RfId { get; set; }
    public long? UserId { get; set; }
    public long? ResponsibleUserId { get; set; }
    public bool IsDisposal { get; set; }
    public DateTime? DisposalDT { get; set; }
    public long? DisposalMethodId { get; set; }
    public long? DisposalReasonId { get; set; }
    public bool Deleted { get; set; }
    // Audit fields
    public DateTime? CreatedDT { get; set; }
    public DateTime? ModifiedDT { get; set; }
    public string? CreatedBy { get; set; }
    public string? ModifiedBy { get; set; }
    // Navigation
    public virtual ArticleCategory Category { get; set; }
    public virtual ArticleType Type { get; set; }
    public virtual ArticleState State { get; set; }
}

Lokace a umístění majetku

Location ──→ Location           (self-reference: ParentLocationId, nullable = kořen)
ArticleLocation ──→ Article     (FK: ArticleId)
ArticleLocation ──→ Location    (FK: LocationId)

Location — hierarchický strom

public class Location
{
    public long OID { get; set; }
    public string Name { get; set; }
    public string? Code { get; set; }
    public long? ParentLocationId { get; set; }
    public virtual Location? ParentLocation { get; set; }
    public virtual ICollection<Location> ChildLocations { get; set; }
}

ArticleLocation — vazba artikl–lokace

public class ArticleLocation
{
    public long OID { get; set; }
    public long ArticleId { get; set; }
    public long LocationId { get; set; }
    public int Quantity { get; set; }
    public virtual Article Article { get; set; }
    public virtual Location Location { get; set; }
}

Inventarizace

Inventory ──→ InventoryState    (FK: InventoryStateId)
Inventory ──→ Location          (FK: LocationId, nullable)
InventoryItem ──→ Inventory     (FK: InventoryId)
InventoryItem ──→ Article       (FK: ArticleId)
InventoryItem ──→ Location      (FK: LocationId, nullable — původní umístění)
InventoryItem ──→ Location      (FK: InventoryLocationId, nullable — inventurní umístění)
InventoryItem ──→ ArticleDisposalMethod (FK: DisposalMethodId, nullable)
InventoryItem ──→ ArticleDisposalReason (FK: DisposalReasonId, nullable)
PhotoArticleInventory ──→ InventoryItem (FK: InventoryItemId)

Přesuny majetku (Transfers)

ArticleTransfer ──→ ArticleTransferItem (1:N)
ArticleTransferItem ──→ Article
ArticleTransferItem ──→ Location        (zdrojová)
ArticleTransferItem ──→ Location        (cílová)

Identita a uživatelé

ApplicationUser (ASP.NET Identity)
ApplicationRole (ASP.NET Identity)
ApplicationUserRole (junction)
User (doménový uživatel — oddělený od Identity)

Dva uživatelské modely

ApplicationUser je ASP.NET Core Identity uživatel (přihlašování do Manageru, JWT). User je doménový uživatel evidovaný v EVA pro přiřazení majetku a inventur. Tyto dvě entity jsou propojeny přes ApplicationUser.UserId.


Multitenancy — izolace dat

Při Multitenancy:Enabled = true je každá doménová entita izolována per-tenant pomocí EF Core Global Query Filters.

Interface ITenantAware

public interface ITenantAware
{
    long? TenantID { get; set; }
}

Implementují ho všechny doménové entity (Article, Location, Inventory, ArticleTransfer, User, Printer, …). Systémové entity (CurrencyType, PrinterType, PrintRequestState, …) ITenantAware neimplementují — jsou sdílené mezi tenanty.

ICurrentTenantAccessor

public interface ICurrentTenantAccessor
{
    long? TenantID { get; }
    bool IsMultitenancyEnabled { get; }
}

Implementace: - ClaimsPrincipalTenantAccessor (Eva.Manager) — čte claim tenant_id z ASP.NET Identity - HttpContextTenantAccessor (Eva.Mobile.WebApi) — čte claim tenant_id z JWT tokenu

EF Core Global Query Filters

EvaDataContext.OnModelCreating přidá pro každou ITenantAware entitu automatický filtr:

// Zjednodušeně — aplikováno přes reflexi pro všechny ITenantAware entity
modelBuilder.Entity<Article>()
    .HasQueryFilter(e => !_tenantAccessor.IsMultitenancyEnabled
                         || e.TenantID == _tenantAccessor.TenantID);

Filtr se přidává pouze při IsMultitenancyEnabled = true. Při false (výchozí) se chování nemění.

Dapper dotazy

EF Core Global Query Filters se nevztahují na Dapper dotazy. Tam je nutné přidat WHERE "TenantID" = @tenantId ručně — viz InventoryDataService a ArticleTransferDataService.

Entita Tenant

[Table("Tenant", Schema = "dbo")]
public class Tenant
{
    public long OID { get; set; }
    public string Name { get; set; }       // Název organizace
    public string Slug { get; set; }       // URL identifikátor (unique)
    public string? Email { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedDT { get; set; }
    public bool Deleted { get; set; }
}

Konvence databázového modelu

Konvence Příklad
PK long OID
FK číselné long CategoryId
FK nullable long? UserId
Navigační vlastnost virtual ArticleCategory Category
Soft delete bool Deleted
Audit CreatedDT, ModifiedDT, CreatedBy, ModifiedBy
Schéma [Table("Article", Schema = "dbo")]