refactor: Migrate backend and frontend architecture from a module-based to an app-based structure.

This commit is contained in:
2025-12-05 22:08:52 +01:00
parent ad0ea0c7f8
commit 82d2680f5b
166 changed files with 6171 additions and 1211 deletions

View File

@@ -30,3 +30,5 @@ File riassuntivo dello stato di sviluppo di Zentral.
- Refactoring Report Designer in modulo autonomo e abilitazione stampa PDF condizionale. - Refactoring Report Designer in modulo autonomo e abilitazione stampa PDF condizionale.
- [2025-12-04 Fix Report Designer Imports](./devlog/2025-12-04-212500_fix_report_designer_imports.md) - **Completato** - [2025-12-04 Fix Report Designer Imports](./devlog/2025-12-04-212500_fix_report_designer_imports.md) - **Completato**
- Correzione import path nel modulo Report Designer e registrazione modulo nel backend. - Correzione import path nel modulo Report Designer e registrazione modulo nel backend.
- [2025-12-05 Rename Modules to Apps](./devlog/2025-12-05-194100_rename_modules_to_apps.md) - **Completato**
- Rinomina terminologia "Modulo" in "Applicazione" (App) su Backend e Frontend.

View File

@@ -0,0 +1,32 @@
# Rename Modules to Apps
**Stato:** Completato
**Data:** 2025-12-05
## Descrizione
Rinomina completa della terminologia "Modulo" (Module) in "Applicazione" (App) in tutto il progetto (Backend, Frontend, Database).
## Modifiche Apportate
### Backend
- Rinominate entità `AppModule` -> `App`, `ModuleSubscription` -> `AppSubscription`.
- Rinominate tabelle database `AppModules` -> `Apps`, `ModuleSubscriptions` -> `AppSubscriptions`.
- Rinominati servizi `ModuleService` -> `AppService`.
- Rinominati controller `ModulesController` -> `AppsController`.
- Aggiornati namespace da `Zentral.API.Modules` a `Zentral.API.Apps`.
- Aggiornate rotte API da `api/modules` a `api/apps`.
- Creata e applicata migrazione `RenameModulesToApps`.
### Frontend
- Rinominate directory `src/frontend/src/modules` -> `src/frontend/src/apps`.
- Rinominati file e componenti principali (es. `ModuleContext` -> `AppContext`, `ModulesAdminPage` -> `AppsAdminPage`).
- Aggiornati tutti i riferimenti nel codice (variabili, interfacce, hook).
- Aggiornati i file di traduzione (i18n) per usare "Applicazione" invece di "Modulo".
### Documentazione
- Aggiornato `docs/development/development-folders.md` con la nuova struttura.
- Aggiornato `docs/development/ZENTRAL.md`.
## Note
- La build del frontend è passata con successo.
- Il backend è stato aggiornato e la migrazione applicata.

View File

@@ -3,7 +3,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.HR.Controllers; namespace Zentral.API.Apps.HR.Controllers;
[ApiController] [ApiController]
[Route("api/hr/[controller]")] [Route("api/hr/[controller]")]

View File

@@ -3,7 +3,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.HR.Controllers; namespace Zentral.API.Apps.HR.Controllers;
[ApiController] [ApiController]
[Route("api/hr/[controller]")] [Route("api/hr/[controller]")]

View File

@@ -3,7 +3,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.HR.Controllers; namespace Zentral.API.Apps.HR.Controllers;
[ApiController] [ApiController]
[Route("api/hr/[controller]")] [Route("api/hr/[controller]")]

View File

@@ -3,7 +3,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.HR.Controllers; namespace Zentral.API.Apps.HR.Controllers;
[ApiController] [ApiController]
[Route("api/hr/[controller]")] [Route("api/hr/[controller]")]

View File

@@ -3,7 +3,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.HR.Controllers; namespace Zentral.API.Apps.HR.Controllers;
[ApiController] [ApiController]
[Route("api/hr/[controller]")] [Route("api/hr/[controller]")]

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Production.Controllers; namespace Zentral.API.Apps.Production.Controllers;
[ApiController] [ApiController]
[Route("api/production/bom")] [Route("api/production/bom")]

View File

@@ -1,9 +1,9 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Production.Controllers; namespace Zentral.API.Apps.Production.Controllers;
[ApiController] [ApiController]
[Route("api/production/mrp")] [Route("api/production/mrp")]

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Production.Controllers; namespace Zentral.API.Apps.Production.Controllers;
[ApiController] [ApiController]
[Route("api/production/cycles")] [Route("api/production/cycles")]

View File

@@ -1,9 +1,9 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Production.Controllers; namespace Zentral.API.Apps.Production.Controllers;
[ApiController] [ApiController]
[Route("api/production/orders")] [Route("api/production/orders")]

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Production.Controllers; namespace Zentral.API.Apps.Production.Controllers;
[ApiController] [ApiController]
[Route("api/production/work-centers")] [Route("api/production/work-centers")]

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class BillOfMaterialsDto public class BillOfMaterialsDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class CreateBillOfMaterialsDto public class CreateBillOfMaterialsDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class CreateProductionOrderDto public class CreateProductionOrderDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class MrpConfigurationDto public class MrpConfigurationDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class ProductionCycleDto public class ProductionCycleDto
{ {

View File

@@ -1,6 +1,6 @@
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class ProductionOrderDto public class ProductionOrderDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class UpdateBillOfMaterialsDto public class UpdateBillOfMaterialsDto
{ {

View File

@@ -1,6 +1,6 @@
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class UpdateProductionOrderDto public class UpdateProductionOrderDto
{ {

View File

@@ -1,4 +1,4 @@
namespace Zentral.API.Modules.Production.Dtos; namespace Zentral.API.Apps.Production.Dtos;
public class WorkCenterDto public class WorkCenterDto
{ {

View File

@@ -1,7 +1,7 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
namespace Zentral.API.Modules.Production.Services; namespace Zentral.API.Apps.Production.Services;
public interface IMrpService public interface IMrpService
{ {

View File

@@ -1,7 +1,7 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
namespace Zentral.API.Modules.Production.Services; namespace Zentral.API.Apps.Production.Services;
public interface IProductionService public interface IProductionService
{ {

View File

@@ -2,9 +2,9 @@ using Zentral.Domain.Entities.Production;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
namespace Zentral.API.Modules.Production.Services; namespace Zentral.API.Apps.Production.Services;
public class MrpService : IMrpService public class MrpService : IMrpService
{ {

View File

@@ -1,11 +1,11 @@
using Zentral.API.Modules.Production.Dtos; using Zentral.API.Apps.Production.Dtos;
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Production; using Zentral.Domain.Entities.Production;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.Production.Services; namespace Zentral.API.Apps.Production.Services;
public class ProductionService : IProductionService public class ProductionService : IProductionService
{ {

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Purchases.Dtos; using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Modules.Purchases.Services; using Zentral.API.Apps.Purchases.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Purchases.Controllers; namespace Zentral.API.Apps.Purchases.Controllers;
[ApiController] [ApiController]
[Route("api/purchases/orders")] [Route("api/purchases/orders")]

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Purchases.Dtos; using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Modules.Purchases.Services; using Zentral.API.Apps.Purchases.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Purchases.Controllers; namespace Zentral.API.Apps.Purchases.Controllers;
[ApiController] [ApiController]
[Route("api/purchases/suppliers")] [Route("api/purchases/suppliers")]

View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Zentral.Domain.Entities.Purchases; using Zentral.Domain.Entities.Purchases;
namespace Zentral.API.Modules.Purchases.Dtos; namespace Zentral.API.Apps.Purchases.Dtos;
public class PurchaseOrderDto public class PurchaseOrderDto
{ {

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace Zentral.API.Modules.Purchases.Dtos; namespace Zentral.API.Apps.Purchases.Dtos;
public class SupplierDto public class SupplierDto
{ {

View File

@@ -2,15 +2,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Purchases.Dtos; using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.API.Services; using Zentral.API.Services;
using Zentral.Domain.Entities.Purchases; using Zentral.Domain.Entities.Purchases;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.Purchases.Services; namespace Zentral.API.Apps.Purchases.Services;
public class PurchaseService public class PurchaseService
{ {

View File

@@ -2,13 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Purchases.Dtos; using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Services; using Zentral.API.Services;
using Zentral.Domain.Entities.Purchases; using Zentral.Domain.Entities.Purchases;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.Purchases.Services; namespace Zentral.API.Apps.Purchases.Services;
public class SupplierService public class SupplierService
{ {

View File

@@ -4,7 +4,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.ReportDesigner.Controllers; namespace Zentral.API.Apps.ReportDesigner.Controllers;
[ApiController] [ApiController]
[Route("api/report-designer/resources")] [Route("api/report-designer/resources")]

View File

@@ -4,7 +4,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.ReportDesigner.Controllers; namespace Zentral.API.Apps.ReportDesigner.Controllers;
[ApiController] [ApiController]
[Route("api/report-designer/templates")] [Route("api/report-designer/templates")]

View File

@@ -4,7 +4,7 @@ using Zentral.Infrastructure.Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.ReportDesigner.Controllers; namespace Zentral.API.Apps.ReportDesigner.Controllers;
[ApiController] [ApiController]
[Route("api/report-designer/reports")] [Route("api/report-designer/reports")]

View File

@@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Sales.Dtos; using Zentral.API.Apps.Sales.Dtos;
using Zentral.API.Modules.Sales.Services; using Zentral.API.Apps.Sales.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Sales.Controllers; namespace Zentral.API.Apps.Sales.Controllers;
[ApiController] [ApiController]
[Route("api/sales/orders")] [Route("api/sales/orders")]

View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Zentral.Domain.Entities.Sales; using Zentral.Domain.Entities.Sales;
namespace Zentral.API.Modules.Sales.Dtos; namespace Zentral.API.Apps.Sales.Dtos;
public class SalesOrderDto public class SalesOrderDto
{ {

View File

@@ -2,15 +2,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Zentral.API.Modules.Sales.Dtos; using Zentral.API.Apps.Sales.Dtos;
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.API.Services; using Zentral.API.Services;
using Zentral.Domain.Entities.Sales; using Zentral.Domain.Entities.Sales;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Modules.Sales.Services; namespace Zentral.API.Apps.Sales.Services;
public class SalesService public class SalesService
{ {

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione delle partite/lotti /// Controller per la gestione delle partite/lotti

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione degli inventari fisici /// Controller per la gestione degli inventari fisici

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione dei seriali/matricole /// Controller per la gestione dei seriali/matricole

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione delle giacenze e valorizzazione /// Controller per la gestione delle giacenze e valorizzazione

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione dei movimenti di magazzino /// Controller per la gestione dei movimenti di magazzino

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione degli articoli di magazzino /// Controller per la gestione degli articoli di magazzino

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione delle categorie articoli /// Controller per la gestione delle categorie articoli

View File

@@ -1,8 +1,8 @@
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Modules.Warehouse.Controllers; namespace Zentral.API.Apps.Warehouse.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione dei magazzini /// Controller per la gestione dei magazzini

View File

@@ -1,6 +1,6 @@
using Zentral.Domain.Entities.Warehouse; using Zentral.Domain.Entities.Warehouse;
namespace Zentral.API.Modules.Warehouse.Services; namespace Zentral.API.Apps.Warehouse.Services;
/// <summary> /// <summary>
/// Interfaccia servizio principale per il modulo Magazzino /// Interfaccia servizio principale per il modulo Magazzino

View File

@@ -6,7 +6,7 @@ using Microsoft.Extensions.Caching.Memory;
using Zentral.API.Hubs; using Zentral.API.Hubs;
namespace Zentral.API.Modules.Warehouse.Services; namespace Zentral.API.Apps.Warehouse.Services;
/// <summary> /// <summary>
/// Implementazione del servizio principale per il modulo Magazzino /// Implementazione del servizio principale per il modulo Magazzino

View File

@@ -5,87 +5,87 @@ using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Controllers; namespace Zentral.API.Controllers;
/// <summary> /// <summary>
/// Controller per la gestione dei moduli applicativi e delle subscription /// Controller per la gestione delle applicazioni e delle subscription
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class ModulesController : ControllerBase public class AppsController : ControllerBase
{ {
private readonly ModuleService _moduleService; private readonly AppService _appService;
private readonly ILogger<ModulesController> _logger; private readonly ILogger<AppsController> _logger;
public ModulesController(ModuleService moduleService, ILogger<ModulesController> logger) public AppsController(AppService appService, ILogger<AppsController> logger)
{ {
_moduleService = moduleService; _appService = appService;
_logger = logger; _logger = logger;
} }
/// <summary> /// <summary>
/// Ottiene tutti i moduli disponibili con stato subscription /// Ottiene tutte le applicazioni disponibili con stato subscription
/// </summary> /// </summary>
[HttpGet] [HttpGet]
public async Task<ActionResult<List<ModuleDto>>> GetAllModules() public async Task<ActionResult<List<AppDto>>> GetAllApps()
{ {
var modules = await _moduleService.GetAllModulesAsync(); var apps = await _appService.GetAllAppsAsync();
return Ok(modules.Select(MapToDto).ToList()); return Ok(apps.Select(MapToDto).ToList());
} }
/// <summary> /// <summary>
/// Ottiene solo i moduli attivi (per costruzione menu) /// Ottiene solo le applicazioni attive (per costruzione menu)
/// </summary> /// </summary>
[HttpGet("active")] [HttpGet("active")]
public async Task<ActionResult<List<ModuleDto>>> GetActiveModules() public async Task<ActionResult<List<AppDto>>> GetActiveApps()
{ {
var modules = await _moduleService.GetActiveModulesAsync(); var apps = await _appService.GetActiveAppsAsync();
return Ok(modules.Select(MapToDto).ToList()); return Ok(apps.Select(MapToDto).ToList());
} }
/// <summary> /// <summary>
/// Ottiene un modulo specifico per codice /// Ottiene un'applicazione specifica per codice
/// </summary> /// </summary>
[HttpGet("{code}")] [HttpGet("{code}")]
public async Task<ActionResult<ModuleDto>> GetModule(string code) public async Task<ActionResult<AppDto>> GetApp(string code)
{ {
var module = await _moduleService.GetModuleByCodeAsync(code); var app = await _appService.GetAppByCodeAsync(code);
if (module == null) if (app == null)
return NotFound(new { message = $"Modulo '{code}' non trovato" }); return NotFound(new { message = $"Applicazione '{code}' non trovata" });
return Ok(MapToDto(module)); return Ok(MapToDto(app));
} }
/// <summary> /// <summary>
/// Verifica se un modulo è abilitato /// Verifica se un'applicazione è abilitata
/// </summary> /// </summary>
[HttpGet("{code}/enabled")] [HttpGet("{code}/enabled")]
public async Task<ActionResult<ModuleStatusDto>> IsModuleEnabled(string code) public async Task<ActionResult<AppStatusDto>> IsAppEnabled(string code)
{ {
var module = await _moduleService.GetModuleByCodeAsync(code); var app = await _appService.GetAppByCodeAsync(code);
if (module == null) if (app == null)
return NotFound(new { message = $"Modulo '{code}' non trovato" }); return NotFound(new { message = $"Applicazione '{code}' non trovata" });
var isEnabled = await _moduleService.IsModuleEnabledAsync(code); var isEnabled = await _appService.IsAppEnabledAsync(code);
var hasValidSubscription = await _moduleService.HasValidSubscriptionAsync(code); var hasValidSubscription = await _appService.HasValidSubscriptionAsync(code);
return Ok(new ModuleStatusDto return Ok(new AppStatusDto
{ {
Code = code, Code = code,
IsEnabled = isEnabled, IsEnabled = isEnabled,
HasValidSubscription = hasValidSubscription, HasValidSubscription = hasValidSubscription,
IsCore = module.IsCore, IsCore = app.IsCore,
DaysRemaining = module.Subscription?.GetDaysRemaining(), DaysRemaining = app.Subscription?.GetDaysRemaining(),
IsExpiringSoon = module.Subscription?.IsExpiringSoon() ?? false IsExpiringSoon = app.Subscription?.IsExpiringSoon() ?? false
}); });
} }
/// <summary> /// <summary>
/// Attiva un modulo /// Attiva un'applicazione
/// </summary> /// </summary>
[HttpPut("{code}/enable")] [HttpPut("{code}/enable")]
public async Task<ActionResult<SubscriptionDto>> EnableModule(string code, [FromBody] EnableModuleRequest request) public async Task<ActionResult<AppSubscriptionDto>> EnableApp(string code, [FromBody] EnableAppRequest request)
{ {
try try
{ {
var subscription = await _moduleService.EnableModuleAsync( var subscription = await _appService.EnableAppAsync(
code, code,
request.SubscriptionType, request.SubscriptionType,
request.StartDate, request.StartDate,
@@ -107,15 +107,15 @@ public class ModulesController : ControllerBase
} }
/// <summary> /// <summary>
/// Disattiva un modulo /// Disattiva un'applicazione
/// </summary> /// </summary>
[HttpPut("{code}/disable")] [HttpPut("{code}/disable")]
public async Task<ActionResult> DisableModule(string code) public async Task<ActionResult> DisableApp(string code)
{ {
try try
{ {
await _moduleService.DisableModuleAsync(code); await _appService.DisableAppAsync(code);
return Ok(new { message = $"Modulo '{code}' disattivato" }); return Ok(new { message = $"Applicazione '{code}' disattivata" });
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
@@ -131,21 +131,21 @@ public class ModulesController : ControllerBase
/// Ottiene tutte le subscription /// Ottiene tutte le subscription
/// </summary> /// </summary>
[HttpGet("subscriptions")] [HttpGet("subscriptions")]
public async Task<ActionResult<List<SubscriptionDto>>> GetAllSubscriptions() public async Task<ActionResult<List<AppSubscriptionDto>>> GetAllAppSubscriptions()
{ {
var subscriptions = await _moduleService.GetAllSubscriptionsAsync(); var subscriptions = await _appService.GetAllSubscriptionsAsync();
return Ok(subscriptions.Select(MapSubscriptionToDto).ToList()); return Ok(subscriptions.Select(MapSubscriptionToDto).ToList());
} }
/// <summary> /// <summary>
/// Aggiorna la subscription di un modulo /// Aggiorna la subscription di un'applicazione
/// </summary> /// </summary>
[HttpPut("{code}/subscription")] [HttpPut("{code}/subscription")]
public async Task<ActionResult<SubscriptionDto>> UpdateSubscription(string code, [FromBody] UpdateSubscriptionRequest request) public async Task<ActionResult<AppSubscriptionDto>> UpdateAppSubscription(string code, [FromBody] UpdateAppSubscriptionRequest request)
{ {
try try
{ {
var subscription = await _moduleService.UpdateSubscriptionAsync( var subscription = await _appService.UpdateSubscriptionAsync(
code, code,
request.SubscriptionType, request.SubscriptionType,
request.EndDate, request.EndDate,
@@ -165,14 +165,14 @@ public class ModulesController : ControllerBase
} }
/// <summary> /// <summary>
/// Rinnova la subscription di un modulo /// Rinnova la subscription di un'applicazione
/// </summary> /// </summary>
[HttpPost("{code}/subscription/renew")] [HttpPost("{code}/subscription/renew")]
public async Task<ActionResult<SubscriptionDto>> RenewSubscription(string code, [FromBody] RenewSubscriptionRequest? request = null) public async Task<ActionResult<AppSubscriptionDto>> RenewAppSubscription(string code, [FromBody] RenewAppSubscriptionRequest? request = null)
{ {
try try
{ {
var subscription = await _moduleService.RenewSubscriptionAsync(code, request?.PaidPrice); var subscription = await _appService.RenewSubscriptionAsync(code, request?.PaidPrice);
return Ok(MapSubscriptionToDto(subscription)); return Ok(MapSubscriptionToDto(subscription));
} }
catch (ArgumentException ex) catch (ArgumentException ex)
@@ -186,77 +186,77 @@ public class ModulesController : ControllerBase
} }
/// <summary> /// <summary>
/// Ottiene i moduli in scadenza /// Ottiene le applicazioni in scadenza
/// </summary> /// </summary>
[HttpGet("expiring")] [HttpGet("expiring")]
public async Task<ActionResult<List<ModuleDto>>> GetExpiringModules([FromQuery] int daysThreshold = 30) public async Task<ActionResult<List<AppDto>>> GetExpiringApps([FromQuery] int daysThreshold = 30)
{ {
var modules = await _moduleService.GetExpiringModulesAsync(daysThreshold); var apps = await _appService.GetExpiringAppsAsync(daysThreshold);
return Ok(modules.Select(MapToDto).ToList()); return Ok(apps.Select(MapToDto).ToList());
} }
/// <summary> /// <summary>
/// Inizializza i moduli di default (per setup iniziale) /// Inizializza le applicazioni di default (per setup iniziale)
/// </summary> /// </summary>
[HttpPost("seed")] [HttpPost("seed")]
public async Task<ActionResult> SeedDefaultModules() public async Task<ActionResult> SeedDefaultApps()
{ {
await _moduleService.SeedDefaultModulesAsync(); await _appService.SeedDefaultAppsAsync();
return Ok(new { message = "Moduli di default inizializzati" }); return Ok(new { message = "Applicazioni di default inizializzate" });
} }
/// <summary> /// <summary>
/// Forza il controllo delle subscription scadute /// Forza il controllo delle subscription scadute
/// </summary> /// </summary>
[HttpPost("check-expired")] [HttpPost("check-expired")]
public async Task<ActionResult> CheckExpiredSubscriptions() public async Task<ActionResult> CheckExpiredAppSubscriptions()
{ {
var count = await _moduleService.CheckExpiredSubscriptionsAsync(); var count = await _appService.CheckExpiredSubscriptionsAsync();
return Ok(new { message = $"Controllate le subscription, {count} moduli disattivati per scadenza" }); return Ok(new { message = $"Controllate le subscription, {count} applicazioni disattivate per scadenza" });
} }
/// <summary> /// <summary>
/// Invalida la cache dei moduli /// Invalida la cache delle applicazioni
/// </summary> /// </summary>
[HttpPost("invalidate-cache")] [HttpPost("invalidate-cache")]
public ActionResult InvalidateCache() public ActionResult InvalidateAppsCache()
{ {
_moduleService.InvalidateCache(); _appService.InvalidateCache();
return Ok(new { message = "Cache moduli invalidata" }); return Ok(new { message = "Cache applicazioni invalidata" });
} }
#region Mapping #region Mapping
private static ModuleDto MapToDto(AppModule module) private static AppDto MapToDto(App app)
{ {
return new ModuleDto return new AppDto
{ {
Id = module.Id, Id = app.Id,
Code = module.Code, Code = app.Code,
Name = module.Name, Name = app.Name,
Description = module.Description, Description = app.Description,
Icon = module.Icon, Icon = app.Icon,
BasePrice = module.BasePrice, BasePrice = app.BasePrice,
MonthlyPrice = module.GetMonthlyPrice(), MonthlyPrice = app.GetMonthlyPrice(),
MonthlyMultiplier = module.MonthlyMultiplier, MonthlyMultiplier = app.MonthlyMultiplier,
SortOrder = module.SortOrder, SortOrder = app.SortOrder,
IsCore = module.IsCore, IsCore = app.IsCore,
Dependencies = module.GetDependencies().ToList(), Dependencies = app.GetDependencies().ToList(),
RoutePath = module.RoutePath, RoutePath = app.RoutePath,
IsAvailable = module.IsAvailable, IsAvailable = app.IsAvailable,
IsEnabled = module.IsCore || ((module.Subscription?.IsEnabled ?? false) && (module.Subscription?.IsValid() ?? false)), IsEnabled = app.IsCore || ((app.Subscription?.IsEnabled ?? false) && (app.Subscription?.IsValid() ?? false)),
Subscription = module.Subscription != null ? MapSubscriptionToDto(module.Subscription) : null Subscription = app.Subscription != null ? MapSubscriptionToDto(app.Subscription) : null
}; };
} }
private static SubscriptionDto MapSubscriptionToDto(ModuleSubscription subscription) private static AppSubscriptionDto MapSubscriptionToDto(AppSubscription subscription)
{ {
return new SubscriptionDto return new AppSubscriptionDto
{ {
Id = subscription.Id, Id = subscription.Id,
ModuleId = subscription.ModuleId, AppId = subscription.AppId,
ModuleCode = subscription.Module?.Code, AppCode = subscription.App?.Code,
ModuleName = subscription.Module?.Name, AppName = subscription.App?.Name,
IsEnabled = subscription.IsEnabled, IsEnabled = subscription.IsEnabled,
SubscriptionType = subscription.SubscriptionType, SubscriptionType = subscription.SubscriptionType,
SubscriptionTypeName = subscription.SubscriptionType.ToString(), SubscriptionTypeName = subscription.SubscriptionType.ToString(),
@@ -277,7 +277,7 @@ public class ModulesController : ControllerBase
#region DTOs #region DTOs
public class ModuleDto public class AppDto
{ {
public int Id { get; set; } public int Id { get; set; }
public string Code { get; set; } = string.Empty; public string Code { get; set; } = string.Empty;
@@ -293,15 +293,15 @@ public class ModuleDto
public string? RoutePath { get; set; } public string? RoutePath { get; set; }
public bool IsAvailable { get; set; } public bool IsAvailable { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public SubscriptionDto? Subscription { get; set; } public AppSubscriptionDto? Subscription { get; set; }
} }
public class SubscriptionDto public class AppSubscriptionDto
{ {
public int Id { get; set; } public int Id { get; set; }
public int ModuleId { get; set; } public int AppId { get; set; }
public string? ModuleCode { get; set; } public string? AppCode { get; set; }
public string? ModuleName { get; set; } public string? AppName { get; set; }
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
public SubscriptionType SubscriptionType { get; set; } public SubscriptionType SubscriptionType { get; set; }
public string SubscriptionTypeName { get; set; } = string.Empty; public string SubscriptionTypeName { get; set; } = string.Empty;
@@ -316,7 +316,7 @@ public class SubscriptionDto
public bool IsExpiringSoon { get; set; } public bool IsExpiringSoon { get; set; }
} }
public class ModuleStatusDto public class AppStatusDto
{ {
public string Code { get; set; } = string.Empty; public string Code { get; set; } = string.Empty;
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
@@ -326,7 +326,7 @@ public class ModuleStatusDto
public bool IsExpiringSoon { get; set; } public bool IsExpiringSoon { get; set; }
} }
public class EnableModuleRequest public class EnableAppRequest
{ {
public SubscriptionType SubscriptionType { get; set; } = SubscriptionType.Annual; public SubscriptionType SubscriptionType { get; set; } = SubscriptionType.Annual;
public DateTime? StartDate { get; set; } public DateTime? StartDate { get; set; }
@@ -336,7 +336,7 @@ public class EnableModuleRequest
public string? Notes { get; set; } public string? Notes { get; set; }
} }
public class UpdateSubscriptionRequest public class UpdateAppSubscriptionRequest
{ {
public SubscriptionType? SubscriptionType { get; set; } public SubscriptionType? SubscriptionType { get; set; }
public DateTime? EndDate { get; set; } public DateTime? EndDate { get; set; }
@@ -344,7 +344,7 @@ public class UpdateSubscriptionRequest
public string? Notes { get; set; } public string? Notes { get; set; }
} }
public class RenewSubscriptionRequest public class RenewAppSubscriptionRequest
{ {
public decimal? PaidPrice { get; set; } public decimal? PaidPrice { get; set; }
} }

View File

@@ -2,10 +2,10 @@ using Zentral.API.Hubs;
using Zentral.API.Services; using Zentral.API.Services;
// Trigger rebuild // Trigger rebuild
using Zentral.API.Services.Reports; using Zentral.API.Services.Reports;
using Zentral.API.Modules.Warehouse.Services; using Zentral.API.Apps.Warehouse.Services;
using Zentral.API.Modules.Purchases.Services; using Zentral.API.Apps.Purchases.Services;
using Zentral.API.Modules.Sales.Services; using Zentral.API.Apps.Sales.Services;
using Zentral.API.Modules.Production.Services; using Zentral.API.Apps.Production.Services;
using Zentral.Infrastructure.Data; using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -22,7 +22,7 @@ builder.Services.AddDbContext<ZentralDbContext>(options =>
builder.Services.AddScoped<EventoCostiService>(); builder.Services.AddScoped<EventoCostiService>();
builder.Services.AddScoped<DemoDataService>(); builder.Services.AddScoped<DemoDataService>();
builder.Services.AddScoped<ReportGeneratorService>(); builder.Services.AddScoped<ReportGeneratorService>();
builder.Services.AddScoped<ModuleService>(); builder.Services.AddScoped<AppService>();
builder.Services.AddScoped<AutoCodeService>(); builder.Services.AddScoped<AutoCodeService>();
builder.Services.AddScoped<CustomFieldService>(); builder.Services.AddScoped<CustomFieldService>();
builder.Services.AddSingleton<DataNotificationService>(); builder.Services.AddSingleton<DataNotificationService>();
@@ -110,9 +110,14 @@ using (var scope = app.Services.CreateScope())
// Seed data (only in development or if database is empty) // Seed data (only in development or if database is empty)
DbSeeder.Seed(db); DbSeeder.Seed(db);
// Seed default modules // Seed default apps
var moduleService = scope.ServiceProvider.GetRequiredService<ModuleService>(); var appService = scope.ServiceProvider.GetRequiredService<AppService>();
await moduleService.SeedDefaultModulesAsync(); await appService.SeedDefaultAppsAsync();
if (app.Environment.IsDevelopment())
{
await appService.SeedDevSubscriptionsAsync();
}
// Seed warehouse default data // Seed warehouse default data
var warehouseService = scope.ServiceProvider.GetRequiredService<IWarehouseService>(); var warehouseService = scope.ServiceProvider.GetRequiredService<IWarehouseService>();

View File

@@ -6,22 +6,22 @@ using Microsoft.Extensions.Caching.Memory;
namespace Zentral.API.Services; namespace Zentral.API.Services;
/// <summary> /// <summary>
/// Service per la gestione dei moduli applicativi e delle relative subscription /// Service per la gestione delle applicazioni e delle relative subscription
/// </summary> /// </summary>
public class ModuleService public class AppService
{ {
private readonly ZentralDbContext _context; private readonly ZentralDbContext _context;
private readonly IMemoryCache _cache; private readonly IMemoryCache _cache;
private readonly ILogger<ModuleService> _logger; private readonly ILogger<AppService> _logger;
private const string MODULES_CACHE_KEY = "modules_all"; private const string APPS_CACHE_KEY = "apps_all";
private const string ACTIVE_MODULES_CACHE_KEY = "modules_active"; private const string ACTIVE_APPS_CACHE_KEY = "apps_active";
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
public ModuleService( public AppService(
ZentralDbContext context, ZentralDbContext context,
IMemoryCache cache, IMemoryCache cache,
ILogger<ModuleService> logger) ILogger<AppService> logger)
{ {
_context = context; _context = context;
_cache = cache; _cache = cache;
@@ -29,80 +29,80 @@ public class ModuleService
} }
/// <summary> /// <summary>
/// Ottiene tutti i moduli con lo stato della subscription /// Ottiene tutte le applicazioni con lo stato della subscription
/// </summary> /// </summary>
public async Task<List<AppModule>> GetAllModulesAsync() public async Task<List<App>> GetAllAppsAsync()
{ {
return await _cache.GetOrCreateAsync(MODULES_CACHE_KEY, async entry => return await _cache.GetOrCreateAsync(APPS_CACHE_KEY, async entry =>
{ {
entry.AbsoluteExpirationRelativeToNow = CacheDuration; entry.AbsoluteExpirationRelativeToNow = CacheDuration;
return await _context.AppModules return await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.Where(m => m.IsAvailable) .Where(m => m.IsAvailable)
.OrderBy(m => m.SortOrder) .OrderBy(m => m.SortOrder)
.ThenBy(m => m.Name) .ThenBy(m => m.Name)
.ToListAsync(); .ToListAsync();
}) ?? new List<AppModule>(); }) ?? new List<App>();
} }
/// <summary> /// <summary>
/// Ottiene solo i moduli attivi (per la costruzione del menu) /// Ottiene solo le applicazioni attive (per la costruzione del menu)
/// </summary> /// </summary>
public async Task<List<AppModule>> GetActiveModulesAsync() public async Task<List<App>> GetActiveAppsAsync()
{ {
return await _cache.GetOrCreateAsync(ACTIVE_MODULES_CACHE_KEY, async entry => return await _cache.GetOrCreateAsync(ACTIVE_APPS_CACHE_KEY, async entry =>
{ {
entry.AbsoluteExpirationRelativeToNow = CacheDuration; entry.AbsoluteExpirationRelativeToNow = CacheDuration;
var modules = await _context.AppModules var apps = await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.Where(m => m.IsAvailable) .Where(m => m.IsAvailable)
.OrderBy(m => m.SortOrder) .OrderBy(m => m.SortOrder)
.ThenBy(m => m.Name) .ThenBy(m => m.Name)
.ToListAsync(); .ToListAsync();
return modules.Where(m => m.IsCore || ((m.Subscription?.IsEnabled ?? false) && (m.Subscription?.IsValid() ?? false))).ToList(); return apps.Where(m => m.IsCore || ((m.Subscription?.IsEnabled ?? false) && (m.Subscription?.IsValid() ?? false))).ToList();
}) ?? new List<AppModule>(); }) ?? new List<App>();
} }
/// <summary> /// <summary>
/// Ottiene un modulo specifico per codice /// Ottiene un'applicazione specifica per codice
/// </summary> /// </summary>
public async Task<AppModule?> GetModuleByCodeAsync(string code) public async Task<App?> GetAppByCodeAsync(string code)
{ {
return await _context.AppModules return await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.FirstOrDefaultAsync(m => m.Code == code); .FirstOrDefaultAsync(m => m.Code == code);
} }
/// <summary> /// <summary>
/// Verifica se un modulo è attualmente abilitato /// Verifica se un'applicazione è attualmente abilitata
/// </summary> /// </summary>
public async Task<bool> IsModuleEnabledAsync(string code) public async Task<bool> IsAppEnabledAsync(string code)
{ {
var module = await GetModuleByCodeAsync(code); var app = await GetAppByCodeAsync(code);
if (module == null) if (app == null)
return false; return false;
// I moduli core sono sempre abilitati // Le applicazioni core sono sempre abilitate
if (module.IsCore) if (app.IsCore)
return true; return true;
return (module.Subscription?.IsEnabled ?? false) && (module.Subscription?.IsValid() ?? false); return (app.Subscription?.IsEnabled ?? false) && (app.Subscription?.IsValid() ?? false);
} }
/// <summary> /// <summary>
/// Verifica se un modulo ha una subscription valida (non scaduta) /// Verifica se un'applicazione ha una subscription valida (non scaduta)
/// </summary> /// </summary>
public async Task<bool> HasValidSubscriptionAsync(string code) public async Task<bool> HasValidSubscriptionAsync(string code)
{ {
var module = await GetModuleByCodeAsync(code); var app = await GetAppByCodeAsync(code);
return module?.Subscription?.IsValid() ?? false; return app?.Subscription?.IsValid() ?? false;
} }
/// <summary> /// <summary>
/// Attiva un modulo creando o aggiornando la subscription /// Attiva un'applicazione creando o aggiornando la subscription
/// </summary> /// </summary>
public async Task<ModuleSubscription> EnableModuleAsync( public async Task<AppSubscription> EnableAppAsync(
string code, string code,
SubscriptionType subscriptionType, SubscriptionType subscriptionType,
DateTime? startDate = null, DateTime? startDate = null,
@@ -111,21 +111,21 @@ public class ModuleService
decimal? paidPrice = null, decimal? paidPrice = null,
string? notes = null) string? notes = null)
{ {
var module = await _context.AppModules var app = await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.FirstOrDefaultAsync(m => m.Code == code); .FirstOrDefaultAsync(m => m.Code == code);
if (module == null) if (app == null)
throw new ArgumentException($"Modulo con codice '{code}' non trovato"); throw new ArgumentException($"Applicazione con codice '{code}' non trovata");
if (module.IsCore) if (app.IsCore)
throw new InvalidOperationException("I moduli core non possono essere attivati/disattivati manualmente"); throw new InvalidOperationException("Le applicazioni core non possono essere attivate/disattivate manualmente");
// Verifica dipendenze // Verifica dipendenze
var missingDeps = await CheckDependenciesAsync(module); var missingDeps = await CheckDependenciesAsync(app);
if (missingDeps.Any()) if (missingDeps.Any())
throw new InvalidOperationException( throw new InvalidOperationException(
$"Il modulo richiede i seguenti moduli attivi: {string.Join(", ", missingDeps)}"); $"L'applicazione richiede le seguenti applicazioni attive: {string.Join(", ", missingDeps)}");
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var effectiveStartDate = startDate ?? now; var effectiveStartDate = startDate ?? now;
@@ -142,178 +142,178 @@ public class ModuleService
}; };
} }
if (module.Subscription == null) if (app.Subscription == null)
{ {
// Crea nuova subscription // Crea nuova subscription
module.Subscription = new ModuleSubscription app.Subscription = new AppSubscription
{ {
ModuleId = module.Id, AppId = app.Id,
IsEnabled = true, IsEnabled = true,
SubscriptionType = subscriptionType, SubscriptionType = subscriptionType,
StartDate = effectiveStartDate, StartDate = effectiveStartDate,
EndDate = effectiveEndDate, EndDate = effectiveEndDate,
AutoRenew = autoRenew, AutoRenew = autoRenew,
PaidPrice = paidPrice ?? module.BasePrice, PaidPrice = paidPrice ?? app.BasePrice,
Notes = notes, Notes = notes,
CreatedAt = now, CreatedAt = now,
UpdatedAt = now UpdatedAt = now
}; };
_context.ModuleSubscriptions.Add(module.Subscription); _context.AppSubscriptions.Add(app.Subscription);
} }
else else
{ {
// Aggiorna subscription esistente // Aggiorna subscription esistente
module.Subscription.IsEnabled = true; app.Subscription.IsEnabled = true;
module.Subscription.SubscriptionType = subscriptionType; app.Subscription.SubscriptionType = subscriptionType;
module.Subscription.StartDate = effectiveStartDate; app.Subscription.StartDate = effectiveStartDate;
module.Subscription.EndDate = effectiveEndDate; app.Subscription.EndDate = effectiveEndDate;
module.Subscription.AutoRenew = autoRenew; app.Subscription.AutoRenew = autoRenew;
module.Subscription.PaidPrice = paidPrice ?? module.Subscription.PaidPrice ?? module.BasePrice; app.Subscription.PaidPrice = paidPrice ?? app.Subscription.PaidPrice ?? app.BasePrice;
if (notes != null) module.Subscription.Notes = notes; if (notes != null) app.Subscription.Notes = notes;
module.Subscription.UpdatedAt = now; app.Subscription.UpdatedAt = now;
} }
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
InvalidateCache(); InvalidateCache();
_logger.LogInformation( _logger.LogInformation(
"Modulo {ModuleCode} attivato con subscription {Type} fino a {EndDate}", "Applicazione {AppCode} attivata con subscription {Type} fino a {EndDate}",
code, subscriptionType, effectiveEndDate); code, subscriptionType, effectiveEndDate);
return module.Subscription; return app.Subscription;
} }
/// <summary> /// <summary>
/// Disattiva un modulo (mantiene i dati ma rimuove l'accesso) /// Disattiva un'applicazione (mantiene i dati ma rimuove l'accesso)
/// </summary> /// </summary>
public async Task DisableModuleAsync(string code) public async Task DisableAppAsync(string code)
{ {
var module = await _context.AppModules var app = await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.FirstOrDefaultAsync(m => m.Code == code); .FirstOrDefaultAsync(m => m.Code == code);
if (module == null) if (app == null)
throw new ArgumentException($"Modulo con codice '{code}' non trovato"); throw new ArgumentException($"Applicazione con codice '{code}' non trovata");
if (module.IsCore) if (app.IsCore)
throw new InvalidOperationException("I moduli core non possono essere disattivati"); throw new InvalidOperationException("Le applicazioni core non possono essere disattivate");
// Verifica se altri moduli dipendono da questo // Verifica se altre applicazioni dipendono da questa
var dependentModules = await GetDependentModulesAsync(code); var dependentApps = await GetDependentAppsAsync(code);
var activeDependents = dependentModules.Where(m => (m.Subscription?.IsEnabled ?? false) && (m.Subscription?.IsValid() ?? false)).ToList(); var activeDependents = dependentApps.Where(m => (m.Subscription?.IsEnabled ?? false) && (m.Subscription?.IsValid() ?? false)).ToList();
if (activeDependents.Any()) if (activeDependents.Any())
throw new InvalidOperationException( throw new InvalidOperationException(
$"I seguenti moduli attivi dipendono da questo modulo: {string.Join(", ", activeDependents.Select(m => m.Name))}"); $"Le seguenti applicazioni attive dipendono da questa applicazione: {string.Join(", ", activeDependents.Select(m => m.Name))}");
if (module.Subscription != null) if (app.Subscription != null)
{ {
module.Subscription.IsEnabled = false; app.Subscription.IsEnabled = false;
module.Subscription.UpdatedAt = DateTime.UtcNow; app.Subscription.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
} }
InvalidateCache(); InvalidateCache();
_logger.LogInformation("Modulo {ModuleCode} disattivato", code); _logger.LogInformation("Applicazione {AppCode} disattivata", code);
} }
/// <summary> /// <summary>
/// Aggiorna i dettagli della subscription /// Aggiorna i dettagli della subscription
/// </summary> /// </summary>
public async Task<ModuleSubscription> UpdateSubscriptionAsync( public async Task<AppSubscription> UpdateSubscriptionAsync(
string code, string code,
SubscriptionType? subscriptionType = null, SubscriptionType? subscriptionType = null,
DateTime? endDate = null, DateTime? endDate = null,
bool? autoRenew = null, bool? autoRenew = null,
string? notes = null) string? notes = null)
{ {
var module = await _context.AppModules var app = await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.FirstOrDefaultAsync(m => m.Code == code); .FirstOrDefaultAsync(m => m.Code == code);
if (module == null) if (app == null)
throw new ArgumentException($"Modulo con codice '{code}' non trovato"); throw new ArgumentException($"Applicazione con codice '{code}' non trovata");
if (module.Subscription == null) if (app.Subscription == null)
throw new InvalidOperationException($"Il modulo '{code}' non ha una subscription attiva"); throw new InvalidOperationException($"L'applicazione '{code}' non ha una subscription attiva");
if (subscriptionType.HasValue) if (subscriptionType.HasValue)
module.Subscription.SubscriptionType = subscriptionType.Value; app.Subscription.SubscriptionType = subscriptionType.Value;
if (endDate.HasValue) if (endDate.HasValue)
module.Subscription.EndDate = endDate.Value; app.Subscription.EndDate = endDate.Value;
if (autoRenew.HasValue) if (autoRenew.HasValue)
module.Subscription.AutoRenew = autoRenew.Value; app.Subscription.AutoRenew = autoRenew.Value;
if (notes != null) if (notes != null)
module.Subscription.Notes = notes; app.Subscription.Notes = notes;
module.Subscription.UpdatedAt = DateTime.UtcNow; app.Subscription.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
InvalidateCache(); InvalidateCache();
return module.Subscription; return app.Subscription;
} }
/// <summary> /// <summary>
/// Rinnova una subscription esistente /// Rinnova una subscription esistente
/// </summary> /// </summary>
public async Task<ModuleSubscription> RenewSubscriptionAsync(string code, decimal? paidPrice = null) public async Task<AppSubscription> RenewSubscriptionAsync(string code, decimal? paidPrice = null)
{ {
var module = await _context.AppModules var app = await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.FirstOrDefaultAsync(m => m.Code == code); .FirstOrDefaultAsync(m => m.Code == code);
if (module == null) if (app == null)
throw new ArgumentException($"Modulo con codice '{code}' non trovato"); throw new ArgumentException($"Applicazione con codice '{code}' non trovata");
if (module.Subscription == null) if (app.Subscription == null)
throw new InvalidOperationException($"Il modulo '{code}' non ha una subscription da rinnovare"); throw new InvalidOperationException($"L'applicazione '{code}' non ha una subscription da rinnovare");
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var currentEnd = module.Subscription.EndDate ?? now; var currentEnd = app.Subscription.EndDate ?? now;
var newStart = currentEnd > now ? currentEnd : now; var newStart = currentEnd > now ? currentEnd : now;
var newEnd = module.Subscription.SubscriptionType switch var newEnd = app.Subscription.SubscriptionType switch
{ {
SubscriptionType.Monthly => newStart.AddMonths(1), SubscriptionType.Monthly => newStart.AddMonths(1),
SubscriptionType.Annual => newStart.AddYears(1), SubscriptionType.Annual => newStart.AddYears(1),
_ => newStart.AddYears(1) // Default to annual _ => newStart.AddYears(1) // Default to annual
}; };
module.Subscription.StartDate = newStart; app.Subscription.StartDate = newStart;
module.Subscription.EndDate = newEnd; app.Subscription.EndDate = newEnd;
module.Subscription.LastRenewalDate = now; app.Subscription.LastRenewalDate = now;
module.Subscription.IsEnabled = true; app.Subscription.IsEnabled = true;
module.Subscription.PaidPrice = paidPrice ?? module.Subscription.PaidPrice; app.Subscription.PaidPrice = paidPrice ?? app.Subscription.PaidPrice;
module.Subscription.UpdatedAt = now; app.Subscription.UpdatedAt = now;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
InvalidateCache(); InvalidateCache();
_logger.LogInformation( _logger.LogInformation(
"Modulo {ModuleCode} rinnovato fino a {EndDate}", "Applicazione {AppCode} rinnovata fino a {EndDate}",
code, newEnd); code, newEnd);
return module.Subscription; return app.Subscription;
} }
/// <summary> /// <summary>
/// Ottiene tutte le subscription /// Ottiene tutte le subscription
/// </summary> /// </summary>
public async Task<List<ModuleSubscription>> GetAllSubscriptionsAsync() public async Task<List<AppSubscription>> GetAllSubscriptionsAsync()
{ {
return await _context.ModuleSubscriptions return await _context.AppSubscriptions
.Include(s => s.Module) .Include(s => s.App)
.OrderBy(s => s.Module.SortOrder) .OrderBy(s => s.App.SortOrder)
.ToListAsync(); .ToListAsync();
} }
/// <summary> /// <summary>
/// Verifica e disattiva i moduli con subscription scaduta (per job schedulato) /// Verifica e disattiva le applicazioni con subscription scaduta (per job schedulato)
/// </summary> /// </summary>
public async Task<int> CheckExpiredSubscriptionsAsync() public async Task<int> CheckExpiredSubscriptionsAsync()
{ {
var expiredSubscriptions = await _context.ModuleSubscriptions var expiredSubscriptions = await _context.AppSubscriptions
.Include(s => s.Module) .Include(s => s.App)
.Where(s => s.IsEnabled && .Where(s => s.IsEnabled &&
s.EndDate.HasValue && s.EndDate.HasValue &&
s.EndDate.Value < DateTime.UtcNow && s.EndDate.Value < DateTime.UtcNow &&
@@ -325,8 +325,8 @@ public class ModuleService
subscription.IsEnabled = false; subscription.IsEnabled = false;
subscription.UpdatedAt = DateTime.UtcNow; subscription.UpdatedAt = DateTime.UtcNow;
_logger.LogWarning( _logger.LogWarning(
"Modulo {ModuleCode} disattivato per scadenza subscription", "Applicazione {AppCode} disattivata per scadenza subscription",
subscription.Module.Code); subscription.App.Code);
} }
if (expiredSubscriptions.Any()) if (expiredSubscriptions.Any())
@@ -339,13 +339,13 @@ public class ModuleService
} }
/// <summary> /// <summary>
/// Ottiene i moduli in scadenza entro N giorni /// Ottiene le applicazioni in scadenza entro N giorni
/// </summary> /// </summary>
public async Task<List<AppModule>> GetExpiringModulesAsync(int daysThreshold = 30) public async Task<List<App>> GetExpiringAppsAsync(int daysThreshold = 30)
{ {
var thresholdDate = DateTime.UtcNow.AddDays(daysThreshold); var thresholdDate = DateTime.UtcNow.AddDays(daysThreshold);
return await _context.AppModules return await _context.Apps
.Include(m => m.Subscription) .Include(m => m.Subscription)
.Where(m => m.Subscription != null && .Where(m => m.Subscription != null &&
m.Subscription.IsEnabled && m.Subscription.IsEnabled &&
@@ -357,11 +357,11 @@ public class ModuleService
} }
/// <summary> /// <summary>
/// Verifica le dipendenze mancanti per un modulo /// Verifica le dipendenze mancanti per un'applicazione
/// </summary> /// </summary>
private async Task<List<string>> CheckDependenciesAsync(AppModule module) private async Task<List<string>> CheckDependenciesAsync(App app)
{ {
var dependencies = module.GetDependencies().ToList(); var dependencies = app.GetDependencies().ToList();
if (!dependencies.Any()) if (!dependencies.Any())
return new List<string>(); return new List<string>();
@@ -369,10 +369,10 @@ public class ModuleService
foreach (var depCode in dependencies) foreach (var depCode in dependencies)
{ {
if (!await IsModuleEnabledAsync(depCode)) if (!await IsAppEnabledAsync(depCode))
{ {
var depModule = await GetModuleByCodeAsync(depCode); var depApp = await GetAppByCodeAsync(depCode);
missingDeps.Add(depModule?.Name ?? depCode); missingDeps.Add(depApp?.Name ?? depCode);
} }
} }
@@ -380,34 +380,34 @@ public class ModuleService
} }
/// <summary> /// <summary>
/// Ottiene i moduli che dipendono da un determinato modulo /// Ottiene le applicazioni che dipendono da una determinata applicazione
/// </summary> /// </summary>
private async Task<List<AppModule>> GetDependentModulesAsync(string code) private async Task<List<App>> GetDependentAppsAsync(string code)
{ {
var allModules = await GetAllModulesAsync(); var allApps = await GetAllAppsAsync();
return allModules return allApps
.Where(m => m.GetDependencies().Contains(code)) .Where(m => m.GetDependencies().Contains(code))
.ToList(); .ToList();
} }
/// <summary> /// <summary>
/// Invalida la cache dei moduli /// Invalida la cache delle applicazioni
/// </summary> /// </summary>
public void InvalidateCache() public void InvalidateCache()
{ {
_cache.Remove(MODULES_CACHE_KEY); _cache.Remove(APPS_CACHE_KEY);
_cache.Remove(ACTIVE_MODULES_CACHE_KEY); _cache.Remove(ACTIVE_APPS_CACHE_KEY);
_logger.LogDebug("Cache moduli invalidata"); _logger.LogDebug("Cache applicazioni invalidata");
} }
/// <summary> /// <summary>
/// Inizializza i moduli di default se non esistono /// Inizializza le applicazioni di default se non esistono
/// </summary> /// </summary>
public async Task SeedDefaultModulesAsync() public async Task SeedDefaultAppsAsync()
{ {
var defaultModules = new List<AppModule> var defaultApps = new List<App>
{ {
new AppModule new App
{ {
Code = "warehouse", Code = "warehouse",
Name = "Magazzino", Name = "Magazzino",
@@ -421,7 +421,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "purchases", Code = "purchases",
Name = "Acquisti", Name = "Acquisti",
@@ -436,7 +436,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "sales", Code = "sales",
Name = "Vendite", Name = "Vendite",
@@ -451,7 +451,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "production", Code = "production",
Name = "Produzione", Name = "Produzione",
@@ -466,7 +466,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "quality", Code = "quality",
Name = "Qualità", Name = "Qualità",
@@ -480,7 +480,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "events", Code = "events",
Name = "Gestione Eventi", Name = "Gestione Eventi",
@@ -494,7 +494,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "hr", Code = "hr",
Name = "Gestione Personale", Name = "Gestione Personale",
@@ -508,7 +508,7 @@ public class ModuleService
IsAvailable = true, IsAvailable = true,
CreatedAt = DateTime.UtcNow CreatedAt = DateTime.UtcNow
}, },
new AppModule new App
{ {
Code = "report-designer", Code = "report-designer",
Name = "Report Designer", Name = "Report Designer",
@@ -524,15 +524,64 @@ public class ModuleService
} }
}; };
var existingCodes = await _context.AppModules.Select(m => m.Code).ToListAsync(); var existingCodes = await _context.Apps.Select(m => m.Code).ToListAsync();
var newModules = defaultModules.Where(m => !existingCodes.Contains(m.Code)).ToList(); var newApps = defaultApps.Where(m => !existingCodes.Contains(m.Code)).ToList();
if (newModules.Any()) if (newApps.Any())
{ {
_context.AppModules.AddRange(newModules); _context.Apps.AddRange(newApps);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInformation("Added {Count} new default modules: {Modules}", _logger.LogInformation("Added {Count} new default apps: {Apps}",
newModules.Count, string.Join(", ", newModules.Select(m => m.Code))); newApps.Count, string.Join(", ", newApps.Select(m => m.Code)));
}
}
/// <summary>
/// Attiva tutte le applicazioni per ambiente di sviluppo
/// </summary>
public async Task SeedDevSubscriptionsAsync()
{
var apps = await _context.Apps
.Include(a => a.Subscription)
.ToListAsync();
var now = DateTime.UtcNow;
bool changes = false;
foreach (var app in apps)
{
if (app.Subscription == null)
{
app.Subscription = new AppSubscription
{
AppId = app.Id,
IsEnabled = true,
SubscriptionType = SubscriptionType.Annual,
StartDate = now,
EndDate = now.AddYears(1),
AutoRenew = true,
PaidPrice = 0,
Notes = "Auto-generated for Development",
CreatedAt = now,
UpdatedAt = now
};
_context.AppSubscriptions.Add(app.Subscription);
changes = true;
}
else if (!app.Subscription.IsEnabled)
{
app.Subscription.IsEnabled = true;
app.Subscription.EndDate = now.AddYears(1);
app.Subscription.UpdatedAt = now;
changes = true;
}
}
if (changes)
{
await _context.SaveChangesAsync();
InvalidateCache();
_logger.LogInformation("Dev subscriptions seeded for all apps");
} }
} }
} }

View File

@@ -1,23 +1,23 @@
namespace Zentral.Domain.Entities; namespace Zentral.Domain.Entities;
/// <summary> /// <summary>
/// Rappresenta un modulo dell'applicazione (es. Magazzino, Acquisti, Vendite). /// Rappresenta un'applicazione (es. Magazzino, Acquisti, Vendite).
/// I moduli possono essere attivati/disattivati per gestire licenze e funzionalità. /// Le applicazioni possono essere attivate/disattivate per gestire licenze e funzionalità.
/// </summary> /// </summary>
public class AppModule : BaseEntity public class App : BaseEntity
{ {
/// <summary> /// <summary>
/// Codice univoco del modulo (es. "warehouse", "purchases", "sales") /// Codice univoco dell'applicazione (es. "warehouse", "purchases", "sales")
/// </summary> /// </summary>
public required string Code { get; set; } public required string Code { get; set; }
/// <summary> /// <summary>
/// Nome visualizzato del modulo (es. "Magazzino", "Acquisti") /// Nome visualizzato dell'applicazione (es. "Magazzino", "Acquisti")
/// </summary> /// </summary>
public required string Name { get; set; } public required string Name { get; set; }
/// <summary> /// <summary>
/// Descrizione estesa delle funzionalità del modulo /// Descrizione estesa delle funzionalità dell'applicazione
/// </summary> /// </summary>
public string? Description { get; set; } public string? Description { get; set; }
@@ -27,7 +27,7 @@ public class AppModule : BaseEntity
public string? Icon { get; set; } public string? Icon { get; set; }
/// <summary> /// <summary>
/// Prezzo base annuale del modulo in EUR /// Prezzo base annuale dell'applicazione in EUR
/// </summary> /// </summary>
public decimal BasePrice { get; set; } public decimal BasePrice { get; set; }
@@ -42,30 +42,30 @@ public class AppModule : BaseEntity
public int SortOrder { get; set; } public int SortOrder { get; set; }
/// <summary> /// <summary>
/// Se true, il modulo fa parte del core e non può essere disattivato /// Se true, l'applicazione fa parte del core e non può essere disattivata
/// </summary> /// </summary>
public bool IsCore { get; set; } public bool IsCore { get; set; }
/// <summary> /// <summary>
/// Lista di codici modulo prerequisiti separati da virgola (es. "warehouse,purchases") /// Lista di codici applicazione prerequisiti separati da virgola (es. "warehouse,purchases")
/// </summary> /// </summary>
public string? Dependencies { get; set; } public string? Dependencies { get; set; }
/// <summary> /// <summary>
/// Path base per le route frontend del modulo (es. "/warehouse") /// Path base per le route frontend dell'applicazione (es. "/warehouse")
/// </summary> /// </summary>
public string? RoutePath { get; set; } public string? RoutePath { get; set; }
/// <summary> /// <summary>
/// Se false, il modulo è nascosto e non disponibile per l'acquisto /// Se false, l'applicazione è nascosta e non disponibile per l'acquisto
/// </summary> /// </summary>
public bool IsAvailable { get; set; } = true; public bool IsAvailable { get; set; } = true;
// Navigation property // Navigation property
public ModuleSubscription? Subscription { get; set; } public AppSubscription? Subscription { get; set; }
/// <summary> /// <summary>
/// Restituisce la lista dei codici modulo prerequisiti /// Restituisce la lista dei codici applicazione prerequisiti
/// </summary> /// </summary>
public IEnumerable<string> GetDependencies() public IEnumerable<string> GetDependencies()
{ {

View File

@@ -1,7 +1,7 @@
namespace Zentral.Domain.Entities; namespace Zentral.Domain.Entities;
/// <summary> /// <summary>
/// Tipo di abbonamento per un modulo /// Tipo di abbonamento per un'applicazione
/// </summary> /// </summary>
public enum SubscriptionType public enum SubscriptionType
{ {
@@ -14,18 +14,18 @@ public enum SubscriptionType
} }
/// <summary> /// <summary>
/// Rappresenta lo stato di abbonamento/attivazione di un modulo per questa istanza dell'applicazione. /// Rappresenta lo stato di abbonamento/attivazione di un'applicazione.
/// Ogni ModuleSubscription è collegata 1:1 con un AppModule. /// Ogni AppSubscription è collegata 1:1 con un'App.
/// </summary> /// </summary>
public class ModuleSubscription : BaseEntity public class AppSubscription : BaseEntity
{ {
/// <summary> /// <summary>
/// ID del modulo associato /// ID dell'applicazione associata
/// </summary> /// </summary>
public int ModuleId { get; set; } public int AppId { get; set; }
/// <summary> /// <summary>
/// Se true, il modulo è attualmente attivo e accessibile /// Se true, l'applicazione è attualmente attiva e accessibile
/// </summary> /// </summary>
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
@@ -65,7 +65,7 @@ public class ModuleSubscription : BaseEntity
public decimal? PaidPrice { get; set; } public decimal? PaidPrice { get; set; }
// Navigation property // Navigation property
public AppModule Module { get; set; } = null!; public App App { get; set; } = null!;
/// <summary> /// <summary>
/// Verifica se l'abbonamento è attualmente valido (attivo e non scaduto) /// Verifica se l'abbonamento è attualmente valido (attivo e non scaduto)

View File

@@ -42,9 +42,9 @@ public class ZentralDbContext : DbContext
public DbSet<ReportImage> ReportImages => Set<ReportImage>(); public DbSet<ReportImage> ReportImages => Set<ReportImage>();
public DbSet<VirtualDataset> VirtualDatasets => Set<VirtualDataset>(); public DbSet<VirtualDataset> VirtualDatasets => Set<VirtualDataset>();
// Module system entities // App system entities
public DbSet<AppModule> AppModules => Set<AppModule>(); public DbSet<App> Apps => Set<App>();
public DbSet<ModuleSubscription> ModuleSubscriptions => Set<ModuleSubscription>(); public DbSet<AppSubscription> AppSubscriptions => Set<AppSubscription>();
// Auto Code system // Auto Code system
public DbSet<AutoCode> AutoCodes => Set<AutoCode>(); public DbSet<AutoCode> AutoCodes => Set<AutoCode>();
@@ -348,8 +348,8 @@ public class ZentralDbContext : DbContext
entity.HasIndex(e => e.Categoria); entity.HasIndex(e => e.Categoria);
}); });
// AppModule // App
modelBuilder.Entity<AppModule>(entity => modelBuilder.Entity<App>(entity =>
{ {
entity.HasIndex(e => e.Code).IsUnique(); entity.HasIndex(e => e.Code).IsUnique();
entity.HasIndex(e => e.SortOrder); entity.HasIndex(e => e.SortOrder);
@@ -361,17 +361,17 @@ public class ZentralDbContext : DbContext
.HasPrecision(5, 2); .HasPrecision(5, 2);
}); });
// ModuleSubscription // AppSubscription
modelBuilder.Entity<ModuleSubscription>(entity => modelBuilder.Entity<AppSubscription>(entity =>
{ {
entity.HasIndex(e => e.ModuleId).IsUnique(); entity.HasIndex(e => e.AppId).IsUnique();
entity.Property(e => e.PaidPrice) entity.Property(e => e.PaidPrice)
.HasPrecision(18, 2); .HasPrecision(18, 2);
entity.HasOne(e => e.Module) entity.HasOne(e => e.App)
.WithOne(m => m.Subscription) .WithOne(m => m.Subscription)
.HasForeignKey<ModuleSubscription>(e => e.ModuleId) .HasForeignKey<AppSubscription>(e => e.AppId)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Zentral.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class RenameModulesToApps : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ModuleSubscriptions");
migrationBuilder.DropTable(
name: "AppModules");
migrationBuilder.CreateTable(
name: "Apps",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Code = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Description = table.Column<string>(type: "TEXT", nullable: true),
Icon = table.Column<string>(type: "TEXT", nullable: true),
BasePrice = table.Column<decimal>(type: "TEXT", precision: 18, scale: 2, nullable: false),
MonthlyMultiplier = table.Column<decimal>(type: "TEXT", precision: 5, scale: 2, nullable: false),
SortOrder = table.Column<int>(type: "INTEGER", nullable: false),
IsCore = table.Column<bool>(type: "INTEGER", nullable: false),
Dependencies = table.Column<string>(type: "TEXT", nullable: true),
RoutePath = table.Column<string>(type: "TEXT", nullable: true),
IsAvailable = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
CustomFieldsJson = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Apps", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AppSubscriptions",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AppId = table.Column<int>(type: "INTEGER", nullable: false),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
SubscriptionType = table.Column<int>(type: "INTEGER", nullable: false),
StartDate = table.Column<DateTime>(type: "TEXT", nullable: true),
EndDate = table.Column<DateTime>(type: "TEXT", nullable: true),
AutoRenew = table.Column<bool>(type: "INTEGER", nullable: false),
Notes = table.Column<string>(type: "TEXT", nullable: true),
LastRenewalDate = table.Column<DateTime>(type: "TEXT", nullable: true),
PaidPrice = table.Column<decimal>(type: "TEXT", precision: 18, scale: 2, nullable: true),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true),
CustomFieldsJson = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AppSubscriptions", x => x.Id);
table.ForeignKey(
name: "FK_AppSubscriptions_Apps_AppId",
column: x => x.AppId,
principalTable: "Apps",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Apps_Code",
table: "Apps",
column: "Code",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Apps_SortOrder",
table: "Apps",
column: "SortOrder");
migrationBuilder.CreateIndex(
name: "IX_AppSubscriptions_AppId",
table: "AppSubscriptions",
column: "AppId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AppSubscriptions");
migrationBuilder.DropTable(
name: "Apps");
migrationBuilder.CreateTable(
name: "AppModules",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
BasePrice = table.Column<decimal>(type: "TEXT", precision: 18, scale: 2, nullable: false),
Code = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
CustomFieldsJson = table.Column<string>(type: "TEXT", nullable: true),
Dependencies = table.Column<string>(type: "TEXT", nullable: true),
Description = table.Column<string>(type: "TEXT", nullable: true),
Icon = table.Column<string>(type: "TEXT", nullable: true),
IsAvailable = table.Column<bool>(type: "INTEGER", nullable: false),
IsCore = table.Column<bool>(type: "INTEGER", nullable: false),
MonthlyMultiplier = table.Column<decimal>(type: "TEXT", precision: 5, scale: 2, nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
RoutePath = table.Column<string>(type: "TEXT", nullable: true),
SortOrder = table.Column<int>(type: "INTEGER", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AppModules", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ModuleSubscriptions",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ModuleId = table.Column<int>(type: "INTEGER", nullable: false),
AutoRenew = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: true),
CustomFieldsJson = table.Column<string>(type: "TEXT", nullable: true),
EndDate = table.Column<DateTime>(type: "TEXT", nullable: true),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
LastRenewalDate = table.Column<DateTime>(type: "TEXT", nullable: true),
Notes = table.Column<string>(type: "TEXT", nullable: true),
PaidPrice = table.Column<decimal>(type: "TEXT", precision: 18, scale: 2, nullable: true),
StartDate = table.Column<DateTime>(type: "TEXT", nullable: true),
SubscriptionType = table.Column<int>(type: "INTEGER", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
UpdatedBy = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ModuleSubscriptions", x => x.Id);
table.ForeignKey(
name: "FK_ModuleSubscriptions_AppModules_ModuleId",
column: x => x.ModuleId,
principalTable: "AppModules",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AppModules_Code",
table: "AppModules",
column: "Code",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AppModules_SortOrder",
table: "AppModules",
column: "SortOrder");
migrationBuilder.CreateIndex(
name: "IX_ModuleSubscriptions_ModuleId",
table: "ModuleSubscriptions",
column: "ModuleId",
unique: true);
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Zentral.Infrastructure.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
modelBuilder.Entity("Zentral.Domain.Entities.AppModule", b => modelBuilder.Entity("Zentral.Domain.Entities.App", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@@ -82,7 +82,64 @@ namespace Zentral.Infrastructure.Migrations
b.HasIndex("SortOrder"); b.HasIndex("SortOrder");
b.ToTable("AppModules"); b.ToTable("Apps");
});
modelBuilder.Entity("Zentral.Domain.Entities.AppSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AppId")
.HasColumnType("INTEGER");
b.Property<bool>("AutoRenew")
.HasColumnType("INTEGER");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasColumnType("TEXT");
b.Property<string>("CustomFieldsJson")
.HasColumnType("TEXT");
b.Property<DateTime?>("EndDate")
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastRenewalDate")
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<decimal?>("PaidPrice")
.HasPrecision(18, 2)
.HasColumnType("TEXT");
b.Property<DateTime?>("StartDate")
.HasColumnType("TEXT");
b.Property<int>("SubscriptionType")
.HasColumnType("INTEGER");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("AppId")
.IsUnique();
b.ToTable("AppSubscriptions");
}); });
modelBuilder.Entity("Zentral.Domain.Entities.Articolo", b => modelBuilder.Entity("Zentral.Domain.Entities.Articolo", b =>
@@ -1250,63 +1307,6 @@ namespace Zentral.Infrastructure.Migrations
b.ToTable("Location"); b.ToTable("Location");
}); });
modelBuilder.Entity("Zentral.Domain.Entities.ModuleSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("AutoRenew")
.HasColumnType("INTEGER");
b.Property<DateTime?>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasColumnType("TEXT");
b.Property<string>("CustomFieldsJson")
.HasColumnType("TEXT");
b.Property<DateTime?>("EndDate")
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastRenewalDate")
.HasColumnType("TEXT");
b.Property<int>("ModuleId")
.HasColumnType("INTEGER");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<decimal?>("PaidPrice")
.HasPrecision(18, 2)
.HasColumnType("TEXT");
b.Property<DateTime?>("StartDate")
.HasColumnType("TEXT");
b.Property<int>("SubscriptionType")
.HasColumnType("INTEGER");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("UpdatedBy")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ModuleId")
.IsUnique();
b.ToTable("ModuleSubscriptions");
});
modelBuilder.Entity("Zentral.Domain.Entities.Production.BillOfMaterials", b => modelBuilder.Entity("Zentral.Domain.Entities.Production.BillOfMaterials", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -3839,6 +3839,17 @@ namespace Zentral.Infrastructure.Migrations
b.ToTable("WarehouseLocations", (string)null); b.ToTable("WarehouseLocations", (string)null);
}); });
modelBuilder.Entity("Zentral.Domain.Entities.AppSubscription", b =>
{
b.HasOne("Zentral.Domain.Entities.App", "App")
.WithOne("Subscription")
.HasForeignKey("Zentral.Domain.Entities.AppSubscription", "AppId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("App");
});
modelBuilder.Entity("Zentral.Domain.Entities.Articolo", b => modelBuilder.Entity("Zentral.Domain.Entities.Articolo", b =>
{ {
b.HasOne("Zentral.Domain.Entities.CodiceCategoria", "Categoria") b.HasOne("Zentral.Domain.Entities.CodiceCategoria", "Categoria")
@@ -4025,17 +4036,6 @@ namespace Zentral.Infrastructure.Migrations
b.Navigation("Dipendente"); b.Navigation("Dipendente");
}); });
modelBuilder.Entity("Zentral.Domain.Entities.ModuleSubscription", b =>
{
b.HasOne("Zentral.Domain.Entities.AppModule", "Module")
.WithOne("Subscription")
.HasForeignKey("Zentral.Domain.Entities.ModuleSubscription", "ModuleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Module");
});
modelBuilder.Entity("Zentral.Domain.Entities.Production.BillOfMaterials", b => modelBuilder.Entity("Zentral.Domain.Entities.Production.BillOfMaterials", b =>
{ {
b.HasOne("Zentral.Domain.Entities.Warehouse.WarehouseArticle", "Article") b.HasOne("Zentral.Domain.Entities.Warehouse.WarehouseArticle", "Article")
@@ -4519,7 +4519,7 @@ namespace Zentral.Infrastructure.Migrations
b.Navigation("ParentCategory"); b.Navigation("ParentCategory");
}); });
modelBuilder.Entity("Zentral.Domain.Entities.AppModule", b => modelBuilder.Entity("Zentral.Domain.Entities.App", b =>
{ {
b.Navigation("Subscription"); b.Navigation("Subscription");
}); });

View File

@@ -49,7 +49,7 @@
"production": "Production", "production": "Production",
"hr": "Human Resources", "hr": "Human Resources",
"reports": "Reports", "reports": "Reports",
"modules": "Modules", "apps": "Apps",
"autoCodes": "Auto Codes", "autoCodes": "Auto Codes",
"customFields": "Custom Fields" "customFields": "Custom Fields"
}, },
@@ -257,7 +257,7 @@
"preventivo": "Quote", "preventivo": "Quote",
"confermato": "Confirmed" "confermato": "Confirmed"
}, },
"modules": { "apps": {
"warehouse": { "warehouse": {
"title": "Warehouse Management", "title": "Warehouse Management",
"inventory": "Inventory", "inventory": "Inventory",
@@ -274,13 +274,13 @@
"rimborsi": "Reimbursements" "rimborsi": "Reimbursements"
}, },
"admin": { "admin": {
"title": "Module Management", "title": "App Management",
"subtitle": "Configure active modules and manage subscriptions", "subtitle": "Configure active apps and manage subscriptions",
"checkExpired": "Check Expired", "checkExpired": "Check Expired",
"refresh": "Refresh", "refresh": "Refresh",
"expiringWarning": "{{count}} module(s) expiring in the next 30 days:", "expiringWarning": "{{count}} app(s) expiring in the next 30 days:",
"disableConfirmTitle": "Confirm Deactivation", "disableConfirmTitle": "Confirm Deactivation",
"disableConfirmText": "Are you sure you want to deactivate the module", "disableConfirmText": "Are you sure you want to deactivate the app",
"disableConfirmSubtext": "Entered data will remain in the system but will not be accessible until reactivation.", "disableConfirmSubtext": "Entered data will remain in the system but will not be accessible until reactivation.",
"disable": "Deactivate", "disable": "Deactivate",
"enable": "Activate", "enable": "Activate",
@@ -300,9 +300,9 @@
"autoRenew": "Auto Renew", "autoRenew": "Auto Renew",
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"purchaseTitle": "Activate Module", "purchaseTitle": "Activate App",
"purchaseSubtitle": "Choose the subscription plan for the module {{name}}", "purchaseSubtitle": "Choose the subscription plan for the app {{name}}",
"missingDependencies": "This module requires the following modules which are not active:", "missingDependencies": "This app requires the following apps which are not active:",
"subscriptionType": "Subscription Type", "subscriptionType": "Subscription Type",
"monthly": "Monthly", "monthly": "Monthly",
"annual": "Annual", "annual": "Annual",
@@ -313,16 +313,16 @@
"orderSummary": "Order Summary", "orderSummary": "Order Summary",
"total": "Total", "total": "Total",
"activating": "Activating...", "activating": "Activating...",
"activateModule": "Activate Module", "activateModule": "Activate App",
"purchaseNote": "You can deactivate the module at any time from the settings. Entered data will remain available.", "purchaseNote": "You can deactivate the app at any time from the settings. Entered data will remain available.",
"includedFeatures": "Included Features", "includedFeatures": "Included Features",
"moduleNotFound": "Module Not Found", "moduleNotFound": "App Not Found",
"moduleNotFoundText": "The requested module does not exist.", "moduleNotFoundText": "The requested app does not exist.",
"backToHome": "Back to Home", "backToHome": "Back to Home",
"status": "Status", "status": "Status",
"module": "Module", "module": "App",
"subscription": "Subscription", "subscription": "Subscription",
"activationError": "Error during module activation" "activationError": "Error during app activation"
}, },
"features": { "features": {
"warehouse": { "warehouse": {
@@ -373,7 +373,7 @@
"4": "Expense reports and reimbursements", "4": "Expense reports and reimbursements",
"5": "Personnel cost analysis" "5": "Personnel cost analysis"
}, },
"default": "Complete module features" "default": "Complete app features"
} }
}, },
"autoCodes": { "autoCodes": {

View File

@@ -45,7 +45,7 @@
"production": "Produzione", "production": "Produzione",
"hr": "Gestione Personale", "hr": "Gestione Personale",
"reports": "Report", "reports": "Report",
"modules": "Moduli", "apps": "Applicazioni",
"autoCodes": "Codici Auto", "autoCodes": "Codici Auto",
"customFields": "Campi Personalizzati" "customFields": "Campi Personalizzati"
}, },
@@ -253,7 +253,7 @@
"preventivo": "Preventivo", "preventivo": "Preventivo",
"confermato": "Confermato" "confermato": "Confermato"
}, },
"modules": { "apps": {
"warehouse": { "warehouse": {
"title": "Gestione Magazzino", "title": "Gestione Magazzino",
"inventory": "Inventario", "inventory": "Inventario",
@@ -270,13 +270,13 @@
"rimborsi": "Rimborsi" "rimborsi": "Rimborsi"
}, },
"admin": { "admin": {
"title": "Gestione Moduli", "title": "Gestione Applicazioni",
"subtitle": "Configura i moduli attivi e gestisci le subscription", "subtitle": "Configura le applicazioni attive e gestisci le subscription",
"checkExpired": "Controlla Scadenze", "checkExpired": "Controlla Scadenze",
"refresh": "Aggiorna", "refresh": "Aggiorna",
"expiringWarning": "{{count}} modulo/i in scadenza nei prossimi 30 giorni:", "expiringWarning": "{{count}} applicazione/i in scadenza nei prossimi 30 giorni:",
"disableConfirmTitle": "Conferma disattivazione", "disableConfirmTitle": "Conferma disattivazione",
"disableConfirmText": "Sei sicuro di voler disattivare il modulo", "disableConfirmText": "Sei sicuro di voler disattivare l'applicazione",
"disableConfirmSubtext": "I dati inseriti rimarranno nel sistema ma non saranno più accessibili fino alla riattivazione.", "disableConfirmSubtext": "I dati inseriti rimarranno nel sistema ma non saranno più accessibili fino alla riattivazione.",
"disable": "Disattiva", "disable": "Disattiva",
"enable": "Attiva", "enable": "Attiva",
@@ -297,9 +297,9 @@
"autoRenew": "Rinnova automatico", "autoRenew": "Rinnova automatico",
"yes": "Sì", "yes": "Sì",
"no": "No", "no": "No",
"purchaseTitle": "Attiva Modulo", "purchaseTitle": "Attiva Applicazione",
"purchaseSubtitle": "Scegli il piano di abbonamento per il modulo {{name}}", "purchaseSubtitle": "Scegli il piano di abbonamento per l'applicazione {{name}}",
"missingDependencies": "Questo modulo richiede i seguenti moduli che non sono attivi:", "missingDependencies": "Questa applicazione richiede le seguenti applicazioni che non sono attive:",
"subscriptionType": "Tipo di abbonamento", "subscriptionType": "Tipo di abbonamento",
"monthly": "Mensile", "monthly": "Mensile",
"annual": "Annuale", "annual": "Annuale",
@@ -310,16 +310,16 @@
"orderSummary": "Riepilogo ordine", "orderSummary": "Riepilogo ordine",
"total": "Totale", "total": "Totale",
"activating": "Attivazione in corso...", "activating": "Attivazione in corso...",
"activateModule": "Attiva Modulo", "activateModule": "Attiva Applicazione",
"purchaseNote": "Potrai disattivare il modulo in qualsiasi momento dalle impostazioni. I dati inseriti rimarranno disponibili.", "purchaseNote": "Potrai disattivare l'applicazione in qualsiasi momento dalle impostazioni. I dati inseriti rimarranno disponibili.",
"includedFeatures": "Funzionalità incluse", "includedFeatures": "Funzionalità incluse",
"moduleNotFound": "Modulo non trovato", "moduleNotFound": "Applicazione non trovata",
"moduleNotFoundText": "Il modulo richiesto non esiste.", "moduleNotFoundText": "L'applicazione richiesta non esiste.",
"backToHome": "Torna alla Home", "backToHome": "Torna alla Home",
"status": "Stato", "status": "Stato",
"module": "Modulo", "module": "Applicazione",
"subscription": "Abbonamento", "subscription": "Abbonamento",
"activationError": "Errore durante l'attivazione del modulo" "activationError": "Errore durante l'attivazione dell'applicazione"
}, },
"features": { "features": {
"warehouse": { "warehouse": {
@@ -370,7 +370,7 @@
"4": "Note spese e rimborsi", "4": "Note spese e rimborsi",
"5": "Analisi costi personale" "5": "Analisi costi personale"
}, },
"default": "Funzionalità complete del modulo" "default": "Funzionalità complete dell'applicazione"
} }
}, },
"autoCodes": { "autoCodes": {

View File

@@ -8,21 +8,21 @@ import { AppLanguageProvider } from "./contexts/LanguageContext";
import Layout from "./components/Layout"; import Layout from "./components/Layout";
import Dashboard from "./pages/Dashboard"; import Dashboard from "./pages/Dashboard";
import ReportDesignerRoutes from "./modules/report-designer/routes"; import ReportDesignerRoutes from "./apps/report-designer/routes";
import ModulesAdminPage from "./pages/ModulesAdminPage"; import AppsAdminPage from "./pages/AppsAdminPage";
import ModulePurchasePage from "./pages/ModulePurchasePage"; import ModulePurchasePage from "./pages/AppPurchasePage";
import AutoCodesAdminPage from "./pages/AutoCodesAdminPage"; import AutoCodesAdminPage from "./pages/AutoCodesAdminPage";
import CustomFieldsAdminPage from "./pages/CustomFieldsAdminPage"; import CustomFieldsAdminPage from "./pages/CustomFieldsAdminPage";
import WarehouseRoutes from "./modules/warehouse/routes"; import WarehouseRoutes from "./apps/warehouse/routes";
import PurchasesRoutes from "./modules/purchases/routes"; import PurchasesRoutes from "./apps/purchases/routes";
import SalesRoutes from "./modules/sales/routes"; import SalesRoutes from "./apps/sales/routes";
import ProductionRoutes from "./modules/production/routes"; import ProductionRoutes from "./apps/production/routes";
import EventsRoutes from "./modules/events/routes"; import EventsRoutes from "./apps/events/routes";
import HRRoutes from "./modules/hr/routes"; import HRRoutes from "./apps/hr/routes";
import { ModuleGuard } from "./components/ModuleGuard"; import { AppGuard } from "./components/AppGuard";
import { useRealTimeUpdates } from "./hooks/useRealTimeUpdates"; import { useRealTimeUpdates } from "./hooks/useRealTimeUpdates";
import { CollaborationProvider } from "./contexts/CollaborationContext"; import { CollaborationProvider } from "./contexts/CollaborationContext";
import { ModuleProvider } from "./contexts/ModuleContext"; import { AppProvider } from "./contexts/AppContext";
import { TabProvider } from "./contexts/TabContext"; import { TabProvider } from "./contexts/TabContext";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
@@ -50,7 +50,7 @@ function App() {
<AppLanguageProvider> <AppLanguageProvider>
<AppThemeProvider> <AppThemeProvider>
<BrowserRouter> <BrowserRouter>
<ModuleProvider> <AppProvider>
<CollaborationProvider> <CollaborationProvider>
<RealTimeProvider> <RealTimeProvider>
<TabProvider> <TabProvider>
@@ -62,15 +62,15 @@ function App() {
<Route <Route
path="report-designer/*" path="report-designer/*"
element={ element={
<ModuleGuard moduleCode="report-designer"> <AppGuard appCode="report-designer">
<ReportDesignerRoutes /> <ReportDesignerRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* Admin */} {/* Admin */}
<Route path="modules" element={<ModulesAdminPage />} /> <Route path="apps" element={<AppsAdminPage />} />
<Route <Route
path="modules/purchase/:code" path="apps/purchase/:code"
element={<ModulePurchasePage />} element={<ModulePurchasePage />}
/> />
<Route <Route
@@ -85,54 +85,54 @@ function App() {
<Route <Route
path="warehouse/*" path="warehouse/*"
element={ element={
<ModuleGuard moduleCode="warehouse"> <AppGuard appCode="warehouse">
<WarehouseRoutes /> <WarehouseRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* Purchases Module */} {/* Purchases Module */}
<Route <Route
path="purchases/*" path="purchases/*"
element={ element={
<ModuleGuard moduleCode="purchases"> <AppGuard appCode="purchases">
<PurchasesRoutes /> <PurchasesRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* Sales Module */} {/* Sales Module */}
<Route <Route
path="sales/*" path="sales/*"
element={ element={
<ModuleGuard moduleCode="sales"> <AppGuard appCode="sales">
<SalesRoutes /> <SalesRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* Production Module */} {/* Production Module */}
<Route <Route
path="production/*" path="production/*"
element={ element={
<ModuleGuard moduleCode="production"> <AppGuard appCode="production">
<ProductionRoutes /> <ProductionRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* Events Module */} {/* Events Module */}
<Route <Route
path="events/*" path="events/*"
element={ element={
<ModuleGuard moduleCode="events"> <AppGuard appCode="events">
<EventsRoutes /> <EventsRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
{/* HR Module */} {/* HR Module */}
<Route <Route
path="hr/*" path="hr/*"
element={ element={
<ModuleGuard moduleCode="hr"> <AppGuard appCode="hr">
<HRRoutes /> <HRRoutes />
</ModuleGuard> </AppGuard>
} }
/> />
</Route> </Route>
@@ -140,7 +140,7 @@ function App() {
</TabProvider> </TabProvider>
</RealTimeProvider> </RealTimeProvider>
</CollaborationProvider> </CollaborationProvider>
</ModuleProvider> </AppProvider>
</BrowserRouter> </BrowserRouter>
</AppThemeProvider> </AppThemeProvider>
</AppLanguageProvider> </AppLanguageProvider>

View File

@@ -31,9 +31,9 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useTranslation, Trans } from "react-i18next"; import { useTranslation, Trans } from "react-i18next";
import { eventiService } from "../services/eventiService"; import { eventiService } from "../../../services/eventiService";
import { demoService, DemoDataResult } from "../services/demoService"; import { demoService, DemoDataResult } from "../../../services/demoService";
import { StatoEvento } from "../types"; import { StatoEvento, Evento } from "../../../types";
const StatCard = ({ const StatCard = ({
title, title,
@@ -104,7 +104,7 @@ export default function Dashboard() {
const [result, setResult] = useState<DemoDataResult | null>(null); const [result, setResult] = useState<DemoDataResult | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { data: eventi = [] } = useQuery({ const { data: eventi = [] } = useQuery<Evento[]>({
queryKey: ["eventi"], queryKey: ["eventi"],
queryFn: () => eventiService.getAll(), queryFn: () => eventiService.getAll(),
}); });
@@ -152,19 +152,19 @@ export default function Dashboard() {
const eventiProssimi = eventi const eventiProssimi = eventi
.filter( .filter(
(e) => (e: Evento) =>
dayjs(e.dataEvento).isAfter(oggi) && dayjs(e.dataEvento).isAfter(oggi) &&
dayjs(e.dataEvento).isBefore(prossimi30Giorni), dayjs(e.dataEvento).isBefore(prossimi30Giorni),
) )
.sort((a, b) => dayjs(a.dataEvento).diff(dayjs(b.dataEvento))); .sort((a: Evento, b: Evento) => dayjs(a.dataEvento).diff(dayjs(b.dataEvento)));
const eventiConfermati = eventi.filter( const eventiConfermati = eventi.filter(
(e) => e.stato === StatoEvento.Confermato, (e: Evento) => e.stato === StatoEvento.Confermato,
).length; ).length;
const eventiPreventivo = eventi.filter( const eventiPreventivo = eventi.filter(
(e) => e.stato === StatoEvento.Preventivo, (e: Evento) => e.stato === StatoEvento.Preventivo,
).length; ).length;
const eventiOggi = eventi.filter((e) => const eventiOggi = eventi.filter((e: Evento) =>
dayjs(e.dataEvento).isSame(oggi, "day"), dayjs(e.dataEvento).isSame(oggi, "day"),
).length; ).length;
@@ -241,7 +241,7 @@ export default function Dashboard() {
{t("dashboard.upcomingEvents")} {t("dashboard.upcomingEvents")}
</Typography> </Typography>
<List> <List>
{eventiProssimi.slice(0, 10).map((evento) => ( {eventiProssimi.slice(0, 10).map((evento: Evento) => (
<ListItem <ListItem
key={evento.id} key={evento.id}
component="div" component="div"
@@ -290,7 +290,7 @@ export default function Dashboard() {
<List> <List>
{eventi {eventi
.filter( .filter(
(e) => (e: Evento) =>
e.stato === StatoEvento.Preventivo && e.stato === StatoEvento.Preventivo &&
e.dataScadenzaPreventivo, e.dataScadenzaPreventivo,
) )
@@ -300,7 +300,7 @@ export default function Dashboard() {
), ),
) )
.slice(0, 5) .slice(0, 5)
.map((evento) => ( .map((evento: Evento) => (
<ListItem <ListItem
key={evento.id} key={evento.id}
component="div" component="div"
@@ -321,7 +321,7 @@ export default function Dashboard() {
/> />
</ListItem> </ListItem>
))} ))}
{eventi.filter((e) => e.stato === StatoEvento.Preventivo) {eventi.filter((e: Evento) => e.stato === StatoEvento.Preventivo)
.length === 0 && ( .length === 0 && (
<ListItem> <ListItem>
<ListItemText primary={t("dashboard.noQuotes")} /> <ListItemText primary={t("dashboard.noQuotes")} />

View File

@@ -64,7 +64,7 @@ export default function DipendentiPage() {
const [openDialog, setOpenDialog] = useState(false); const [openDialog, setOpenDialog] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null); const [editingId, setEditingId] = useState<number | null>(null);
const [formData, setFormData] = useState<Partial<Dipendente>>({}); const [formData, setFormData] = useState<Partial<Dipendente>>({});
const [search, setSearch] = useState(''); const [search] = useState('');
const { data: dipendenti = [], isLoading } = useQuery({ const { data: dipendenti = [], isLoading } = useQuery({
queryKey: ['dipendenti', search], queryKey: ['dipendenti', search],

Some files were not shown because too many files have changed in this diff Show More