This commit is contained in:
2025-11-29 13:30:28 +01:00
parent 824a761bf6
commit bb2d0729e1
16 changed files with 3102 additions and 37 deletions

View File

@@ -0,0 +1,85 @@
namespace Apollinare.Domain.Entities;
/// <summary>
/// Rappresenta un modulo dell'applicazione (es. Magazzino, Acquisti, Vendite).
/// I moduli possono essere attivati/disattivati per gestire licenze e funzionalità.
/// </summary>
public class AppModule : BaseEntity
{
/// <summary>
/// Codice univoco del modulo (es. "warehouse", "purchases", "sales")
/// </summary>
public required string Code { get; set; }
/// <summary>
/// Nome visualizzato del modulo (es. "Magazzino", "Acquisti")
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Descrizione estesa delle funzionalità del modulo
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Nome dell'icona Material UI (es. "Warehouse", "ShoppingCart")
/// </summary>
public string? Icon { get; set; }
/// <summary>
/// Prezzo base annuale del modulo in EUR
/// </summary>
public decimal BasePrice { get; set; }
/// <summary>
/// Moltiplicatore per abbonamento mensile (es. 1.2 = 20% in più rispetto all'annuale/12)
/// </summary>
public decimal MonthlyMultiplier { get; set; } = 1.2m;
/// <summary>
/// Ordine di visualizzazione nel menu (più basso = prima)
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// Se true, il modulo fa parte del core e non può essere disattivato
/// </summary>
public bool IsCore { get; set; }
/// <summary>
/// Lista di codici modulo prerequisiti separati da virgola (es. "warehouse,purchases")
/// </summary>
public string? Dependencies { get; set; }
/// <summary>
/// Path base per le route frontend del modulo (es. "/warehouse")
/// </summary>
public string? RoutePath { get; set; }
/// <summary>
/// Se false, il modulo è nascosto e non disponibile per l'acquisto
/// </summary>
public bool IsAvailable { get; set; } = true;
// Navigation property
public ModuleSubscription? Subscription { get; set; }
/// <summary>
/// Restituisce la lista dei codici modulo prerequisiti
/// </summary>
public IEnumerable<string> GetDependencies()
{
if (string.IsNullOrWhiteSpace(Dependencies))
return Enumerable.Empty<string>();
return Dependencies.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
}
/// <summary>
/// Calcola il prezzo mensile basato su BasePrice e MonthlyMultiplier
/// </summary>
public decimal GetMonthlyPrice()
{
return Math.Round((BasePrice / 12) * MonthlyMultiplier, 2);
}
}

View File

@@ -0,0 +1,108 @@
namespace Apollinare.Domain.Entities;
/// <summary>
/// Tipo di abbonamento per un modulo
/// </summary>
public enum SubscriptionType
{
/// <summary>Nessun abbonamento attivo</summary>
None = 0,
/// <summary>Abbonamento mensile</summary>
Monthly = 1,
/// <summary>Abbonamento annuale</summary>
Annual = 2
}
/// <summary>
/// Rappresenta lo stato di abbonamento/attivazione di un modulo per questa istanza dell'applicazione.
/// Ogni ModuleSubscription è collegata 1:1 con un AppModule.
/// </summary>
public class ModuleSubscription : BaseEntity
{
/// <summary>
/// ID del modulo associato
/// </summary>
public int ModuleId { get; set; }
/// <summary>
/// Se true, il modulo è attualmente attivo e accessibile
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// Tipo di abbonamento corrente
/// </summary>
public SubscriptionType SubscriptionType { get; set; } = SubscriptionType.None;
/// <summary>
/// Data di inizio dell'abbonamento corrente
/// </summary>
public DateTime? StartDate { get; set; }
/// <summary>
/// Data di scadenza dell'abbonamento (null = nessuna scadenza, es. licenza perpetua)
/// </summary>
public DateTime? EndDate { get; set; }
/// <summary>
/// Se true, l'abbonamento si rinnova automaticamente alla scadenza
/// </summary>
public bool AutoRenew { get; set; }
/// <summary>
/// Note aggiuntive sull'abbonamento (es. codice ordine, riferimento contratto)
/// </summary>
public string? Notes { get; set; }
/// <summary>
/// Data dell'ultimo rinnovo effettuato
/// </summary>
public DateTime? LastRenewalDate { get; set; }
/// <summary>
/// Prezzo pagato per l'abbonamento corrente (può differire da BasePrice per sconti)
/// </summary>
public decimal? PaidPrice { get; set; }
// Navigation property
public AppModule Module { get; set; } = null!;
/// <summary>
/// Verifica se l'abbonamento è attualmente valido (attivo e non scaduto)
/// </summary>
public bool IsValid()
{
if (!IsEnabled)
return false;
// Se non c'è data di scadenza, è valido (licenza perpetua o core module)
if (!EndDate.HasValue)
return true;
return EndDate.Value >= DateTime.UtcNow;
}
/// <summary>
/// Calcola i giorni rimanenti alla scadenza (null se nessuna scadenza)
/// </summary>
public int? GetDaysRemaining()
{
if (!EndDate.HasValue)
return null;
var remaining = (EndDate.Value - DateTime.UtcNow).Days;
return remaining < 0 ? 0 : remaining;
}
/// <summary>
/// Verifica se l'abbonamento sta per scadere (entro i prossimi N giorni)
/// </summary>
public bool IsExpiringSoon(int daysThreshold = 30)
{
if (!EndDate.HasValue)
return false;
var daysRemaining = GetDaysRemaining();
return daysRemaining.HasValue && daysRemaining.Value <= daysThreshold && daysRemaining.Value > 0;
}
}