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

@@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Apps.Purchases.Services;
using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Apps.Purchases.Controllers;
[ApiController]
[Route("api/purchases/orders")]
public class PurchaseOrdersController : ControllerBase
{
private readonly PurchaseService _service;
public PurchaseOrdersController(PurchaseService service)
{
_service = service;
}
[HttpGet]
public async Task<ActionResult<List<PurchaseOrderDto>>> GetAll()
{
return Ok(await _service.GetAllAsync());
}
[HttpGet("{id}")]
public async Task<ActionResult<PurchaseOrderDto>> GetById(int id)
{
var order = await _service.GetByIdAsync(id);
if (order == null) return NotFound();
return Ok(order);
}
[HttpPost]
public async Task<ActionResult<PurchaseOrderDto>> Create(CreatePurchaseOrderDto dto)
{
var order = await _service.CreateAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
}
[HttpPut("{id}")]
public async Task<ActionResult<PurchaseOrderDto>> Update(int id, UpdatePurchaseOrderDto dto)
{
try
{
var order = await _service.UpdateAsync(id, dto);
if (order == null) return NotFound();
return Ok(order);
}
catch (System.InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
[HttpDelete("{id}")]
public async Task<ActionResult> Delete(int id)
{
try
{
var result = await _service.DeleteAsync(id);
if (!result) return NotFound();
return NoContent();
}
catch (System.InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
[HttpPost("{id}/confirm")]
public async Task<ActionResult<PurchaseOrderDto>> Confirm(int id)
{
try
{
var order = await _service.ConfirmOrderAsync(id);
return Ok(order);
}
catch (KeyNotFoundException)
{
return NotFound();
}
catch (System.InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
[HttpPost("{id}/receive")]
public async Task<ActionResult<PurchaseOrderDto>> Receive(int id)
{
try
{
var order = await _service.ReceiveOrderAsync(id);
return Ok(order);
}
catch (KeyNotFoundException)
{
return NotFound();
}
catch (System.InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Apps.Purchases.Services;
using Microsoft.AspNetCore.Mvc;
namespace Zentral.API.Apps.Purchases.Controllers;
[ApiController]
[Route("api/purchases/suppliers")]
public class SuppliersController : ControllerBase
{
private readonly SupplierService _service;
public SuppliersController(SupplierService service)
{
_service = service;
}
[HttpGet]
public async Task<ActionResult<List<SupplierDto>>> GetAll()
{
return Ok(await _service.GetAllAsync());
}
[HttpGet("{id}")]
public async Task<ActionResult<SupplierDto>> GetById(int id)
{
var supplier = await _service.GetByIdAsync(id);
if (supplier == null) return NotFound();
return Ok(supplier);
}
[HttpPost]
public async Task<ActionResult<SupplierDto>> Create(CreateSupplierDto dto)
{
var supplier = await _service.CreateAsync(dto);
return CreatedAtAction(nameof(GetById), new { id = supplier.Id }, supplier);
}
[HttpPut("{id}")]
public async Task<ActionResult<SupplierDto>> Update(int id, UpdateSupplierDto dto)
{
var supplier = await _service.UpdateAsync(id, dto);
if (supplier == null) return NotFound();
return Ok(supplier);
}
[HttpDelete("{id}")]
public async Task<ActionResult> Delete(int id)
{
var result = await _service.DeleteAsync(id);
if (!result) return NotFound();
return NoContent();
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using Zentral.Domain.Entities.Purchases;
namespace Zentral.API.Apps.Purchases.Dtos;
public class PurchaseOrderDto
{
public int Id { get; set; }
public string OrderNumber { get; set; } = string.Empty;
public DateTime OrderDate { get; set; }
public DateTime? ExpectedDeliveryDate { get; set; }
public int SupplierId { get; set; }
public string SupplierName { get; set; } = string.Empty;
public PurchaseOrderStatus Status { get; set; }
public int? DestinationWarehouseId { get; set; }
public string? DestinationWarehouseName { get; set; }
public string? Notes { get; set; }
public decimal TotalNet { get; set; }
public decimal TotalTax { get; set; }
public decimal TotalGross { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public List<PurchaseOrderLineDto> Lines { get; set; } = new();
}
public class PurchaseOrderLineDto
{
public int Id { get; set; }
public int PurchaseOrderId { get; set; }
public int WarehouseArticleId { get; set; }
public string ArticleCode { get; set; } = string.Empty;
public string ArticleDescription { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Quantity { get; set; }
public decimal ReceivedQuantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TaxRate { get; set; }
public decimal DiscountPercent { get; set; }
public decimal LineTotal { get; set; }
}
public class CreatePurchaseOrderDto
{
public DateTime OrderDate { get; set; } = DateTime.Now;
public DateTime? ExpectedDeliveryDate { get; set; }
public int SupplierId { get; set; }
public int? DestinationWarehouseId { get; set; }
public string? Notes { get; set; }
public List<CreatePurchaseOrderLineDto> Lines { get; set; } = new();
}
public class CreatePurchaseOrderLineDto
{
public int WarehouseArticleId { get; set; }
public string? Description { get; set; } // Optional override
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TaxRate { get; set; }
public decimal DiscountPercent { get; set; }
}
public class UpdatePurchaseOrderDto
{
public DateTime OrderDate { get; set; }
public DateTime? ExpectedDeliveryDate { get; set; }
public int? DestinationWarehouseId { get; set; }
public string? Notes { get; set; }
public List<UpdatePurchaseOrderLineDto> Lines { get; set; } = new();
}
public class UpdatePurchaseOrderLineDto
{
public int? Id { get; set; } // Null if new line
public int WarehouseArticleId { get; set; }
public string? Description { get; set; }
public decimal Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal TaxRate { get; set; }
public decimal DiscountPercent { get; set; }
public bool IsDeleted { get; set; } // To mark for deletion
}

View File

@@ -0,0 +1,63 @@
using System;
namespace Zentral.API.Apps.Purchases.Dtos;
public class SupplierDto
{
public int Id { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? VatNumber { get; set; }
public string? FiscalCode { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
public string? ZipCode { get; set; }
public string? Country { get; set; }
public string? Email { get; set; }
public string? Pec { get; set; }
public string? Phone { get; set; }
public string? Website { get; set; }
public string? PaymentTerms { get; set; }
public string? Notes { get; set; }
public bool IsActive { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
public class CreateSupplierDto
{
public string Name { get; set; } = string.Empty;
public string? VatNumber { get; set; }
public string? FiscalCode { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
public string? ZipCode { get; set; }
public string? Country { get; set; } = "Italia";
public string? Email { get; set; }
public string? Pec { get; set; }
public string? Phone { get; set; }
public string? Website { get; set; }
public string? PaymentTerms { get; set; }
public string? Notes { get; set; }
}
public class UpdateSupplierDto
{
public string Name { get; set; } = string.Empty;
public string? VatNumber { get; set; }
public string? FiscalCode { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Province { get; set; }
public string? ZipCode { get; set; }
public string? Country { get; set; }
public string? Email { get; set; }
public string? Pec { get; set; }
public string? Phone { get; set; }
public string? Website { get; set; }
public string? PaymentTerms { get; set; }
public string? Notes { get; set; }
public bool IsActive { get; set; }
}

View File

@@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Apps.Warehouse.Services;
using Zentral.API.Services;
using Zentral.Domain.Entities.Purchases;
using Zentral.Domain.Entities.Warehouse;
using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Apps.Purchases.Services;
public class PurchaseService
{
private readonly ZentralDbContext _db;
private readonly AutoCodeService _autoCodeService;
private readonly IWarehouseService _warehouseService;
public PurchaseService(ZentralDbContext db, AutoCodeService autoCodeService, IWarehouseService warehouseService)
{
_db = db;
_autoCodeService = autoCodeService;
_warehouseService = warehouseService;
}
public async Task<List<PurchaseOrderDto>> GetAllAsync()
{
return await _db.PurchaseOrders
.AsNoTracking()
.Include(o => o.Supplier)
.Include(o => o.DestinationWarehouse)
.OrderByDescending(o => o.OrderDate)
.Select(o => new PurchaseOrderDto
{
Id = o.Id,
OrderNumber = o.OrderNumber,
OrderDate = o.OrderDate,
ExpectedDeliveryDate = o.ExpectedDeliveryDate,
SupplierId = o.SupplierId,
SupplierName = o.Supplier!.Name,
Status = o.Status,
DestinationWarehouseId = o.DestinationWarehouseId,
DestinationWarehouseName = o.DestinationWarehouse != null ? o.DestinationWarehouse.Name : null,
Notes = o.Notes,
TotalNet = o.TotalNet,
TotalTax = o.TotalTax,
TotalGross = o.TotalGross,
CreatedAt = o.CreatedAt,
UpdatedAt = o.UpdatedAt
})
.ToListAsync();
}
public async Task<PurchaseOrderDto?> GetByIdAsync(int id)
{
var order = await _db.PurchaseOrders
.AsNoTracking()
.Include(o => o.Supplier)
.Include(o => o.DestinationWarehouse)
.Include(o => o.Lines)
.ThenInclude(l => l.WarehouseArticle)
.FirstOrDefaultAsync(o => o.Id == id);
if (order == null) return null;
return new PurchaseOrderDto
{
Id = order.Id,
OrderNumber = order.OrderNumber,
OrderDate = order.OrderDate,
ExpectedDeliveryDate = order.ExpectedDeliveryDate,
SupplierId = order.SupplierId,
SupplierName = order.Supplier!.Name,
Status = order.Status,
DestinationWarehouseId = order.DestinationWarehouseId,
DestinationWarehouseName = order.DestinationWarehouse?.Name,
Notes = order.Notes,
TotalNet = order.TotalNet,
TotalTax = order.TotalTax,
TotalGross = order.TotalGross,
CreatedAt = order.CreatedAt,
UpdatedAt = order.UpdatedAt,
Lines = order.Lines.Select(l => new PurchaseOrderLineDto
{
Id = l.Id,
PurchaseOrderId = l.PurchaseOrderId,
WarehouseArticleId = l.WarehouseArticleId,
ArticleCode = l.WarehouseArticle!.Code,
ArticleDescription = l.WarehouseArticle.Description,
Description = l.Description,
Quantity = l.Quantity,
ReceivedQuantity = l.ReceivedQuantity,
UnitPrice = l.UnitPrice,
TaxRate = l.TaxRate,
DiscountPercent = l.DiscountPercent,
LineTotal = l.LineTotal
}).ToList()
};
}
public async Task<PurchaseOrderDto> CreateAsync(CreatePurchaseOrderDto dto)
{
var code = await _autoCodeService.GenerateNextCodeAsync("purchase_order");
if (string.IsNullOrEmpty(code))
{
code = $"ODA{DateTime.Now:yyyy}-{Guid.NewGuid().ToString().Substring(0, 5).ToUpper()}";
}
var order = new PurchaseOrder
{
OrderNumber = code,
OrderDate = dto.OrderDate,
ExpectedDeliveryDate = dto.ExpectedDeliveryDate,
SupplierId = dto.SupplierId,
DestinationWarehouseId = dto.DestinationWarehouseId,
Notes = dto.Notes,
Status = PurchaseOrderStatus.Draft
};
foreach (var lineDto in dto.Lines)
{
var line = new PurchaseOrderLine
{
WarehouseArticleId = lineDto.WarehouseArticleId,
Description = lineDto.Description ?? string.Empty,
Quantity = lineDto.Quantity,
UnitPrice = lineDto.UnitPrice,
TaxRate = lineDto.TaxRate,
DiscountPercent = lineDto.DiscountPercent
};
// If description is empty, fetch from article
if (string.IsNullOrEmpty(line.Description))
{
var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId);
if (article != null) line.Description = article.Description;
}
// Calculate totals
var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100);
line.LineTotal = Math.Round(netPrice * line.Quantity, 2);
order.Lines.Add(line);
}
CalculateOrderTotals(order);
_db.PurchaseOrders.Add(order);
await _db.SaveChangesAsync();
return await GetByIdAsync(order.Id) ?? throw new InvalidOperationException("Failed to retrieve created order");
}
public async Task<PurchaseOrderDto?> UpdateAsync(int id, UpdatePurchaseOrderDto dto)
{
var order = await _db.PurchaseOrders
.Include(o => o.Lines)
.FirstOrDefaultAsync(o => o.Id == id);
if (order == null) return null;
if (order.Status != PurchaseOrderStatus.Draft)
throw new InvalidOperationException("Solo gli ordini in bozza possono essere modificati");
order.OrderDate = dto.OrderDate;
order.ExpectedDeliveryDate = dto.ExpectedDeliveryDate;
order.DestinationWarehouseId = dto.DestinationWarehouseId;
order.Notes = dto.Notes;
order.UpdatedAt = DateTime.Now;
// Update lines
foreach (var lineDto in dto.Lines)
{
if (lineDto.IsDeleted)
{
if (lineDto.Id.HasValue)
{
var lineToDelete = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value);
if (lineToDelete != null) order.Lines.Remove(lineToDelete);
}
continue;
}
PurchaseOrderLine line;
if (lineDto.Id.HasValue)
{
line = order.Lines.FirstOrDefault(l => l.Id == lineDto.Id.Value)
?? throw new KeyNotFoundException($"Line {lineDto.Id} not found");
}
else
{
line = new PurchaseOrderLine();
order.Lines.Add(line);
}
line.WarehouseArticleId = lineDto.WarehouseArticleId;
line.Description = lineDto.Description ?? string.Empty;
line.Quantity = lineDto.Quantity;
line.UnitPrice = lineDto.UnitPrice;
line.TaxRate = lineDto.TaxRate;
line.DiscountPercent = lineDto.DiscountPercent;
if (string.IsNullOrEmpty(line.Description))
{
var article = await _db.WarehouseArticles.FindAsync(line.WarehouseArticleId);
if (article != null) line.Description = article.Description;
}
var netPrice = line.UnitPrice * (1 - line.DiscountPercent / 100);
line.LineTotal = Math.Round(netPrice * line.Quantity, 2);
}
CalculateOrderTotals(order);
await _db.SaveChangesAsync();
return await GetByIdAsync(id);
}
public async Task<bool> DeleteAsync(int id)
{
var order = await _db.PurchaseOrders.FindAsync(id);
if (order == null) return false;
if (order.Status != PurchaseOrderStatus.Draft && order.Status != PurchaseOrderStatus.Cancelled)
throw new InvalidOperationException("Solo gli ordini in bozza o annullati possono essere eliminati");
_db.PurchaseOrders.Remove(order);
await _db.SaveChangesAsync();
return true;
}
public async Task<PurchaseOrderDto> ConfirmOrderAsync(int id)
{
var order = await _db.PurchaseOrders.FindAsync(id);
if (order == null) throw new KeyNotFoundException("Order not found");
if (order.Status != PurchaseOrderStatus.Draft) throw new InvalidOperationException("Solo gli ordini in bozza possono essere confermati");
order.Status = PurchaseOrderStatus.Confirmed;
order.UpdatedAt = DateTime.Now;
await _db.SaveChangesAsync();
return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order");
}
public async Task<PurchaseOrderDto> ReceiveOrderAsync(int id)
{
var order = await _db.PurchaseOrders
.Include(o => o.Lines)
.FirstOrDefaultAsync(o => o.Id == id);
if (order == null) throw new KeyNotFoundException("Order not found");
if (order.Status != PurchaseOrderStatus.Confirmed && order.Status != PurchaseOrderStatus.PartiallyReceived)
throw new InvalidOperationException("L'ordine deve essere confermato per essere ricevuto");
// Create Stock Movement (Inbound)
var warehouseId = order.DestinationWarehouseId;
if (!warehouseId.HasValue)
{
var defaultWarehouse = await _warehouseService.GetDefaultWarehouseAsync();
warehouseId = defaultWarehouse?.Id;
}
if (!warehouseId.HasValue) throw new InvalidOperationException("Nessun magazzino di destinazione specificato o di default");
// Genera numero documento movimento
var docNumber = await _warehouseService.GenerateDocumentNumberAsync(MovementType.Inbound);
// Trova causale di default per acquisto (se esiste, altrimenti null o crea)
var reason = (await _warehouseService.GetMovementReasonsAsync(MovementType.Inbound))
.FirstOrDefault(r => r.Code == "ACQ" || r.Description.Contains("Acquisto"));
var movement = new StockMovement
{
DocumentNumber = docNumber,
MovementDate = DateTime.Now,
Type = MovementType.Inbound,
Status = MovementStatus.Draft,
DestinationWarehouseId = warehouseId,
ReasonId = reason?.Id,
ExternalReference = order.OrderNumber,
Notes = $"Ricevimento merce da ordine {order.OrderNumber}"
};
movement = await _warehouseService.CreateMovementAsync(movement);
// Add lines to movement
foreach (var line in order.Lines)
{
var remainingQty = line.Quantity - line.ReceivedQuantity;
if (remainingQty <= 0) continue;
// Update received quantity on order line
line.ReceivedQuantity += remainingQty;
// Add movement line directly via DbContext since IWarehouseService doesn't expose AddLine (it exposes UpdateMovement)
// Or better, construct the movement with lines initially if possible.
// Since CreateMovementAsync saves, we need to add lines and save again.
var movementLine = new StockMovementLine
{
MovementId = movement.Id,
ArticleId = line.WarehouseArticleId,
Quantity = remainingQty,
UnitCost = line.UnitPrice * (1 - line.DiscountPercent / 100),
LineValue = Math.Round(remainingQty * (line.UnitPrice * (1 - line.DiscountPercent / 100)), 2)
};
_db.StockMovementLines.Add(movementLine);
}
await _db.SaveChangesAsync();
// Confirm movement to update stock
await _warehouseService.ConfirmMovementAsync(movement.Id);
// Update order status
var allReceived = order.Lines.All(l => l.ReceivedQuantity >= l.Quantity);
order.Status = allReceived ? PurchaseOrderStatus.Received : PurchaseOrderStatus.PartiallyReceived;
order.UpdatedAt = DateTime.Now;
await _db.SaveChangesAsync();
return await GetByIdAsync(id) ?? throw new InvalidOperationException("Failed to retrieve order");
}
private void CalculateOrderTotals(PurchaseOrder order)
{
order.TotalNet = 0;
order.TotalTax = 0;
order.TotalGross = 0;
foreach (var line in order.Lines)
{
order.TotalNet += line.LineTotal;
var taxAmount = line.LineTotal * (line.TaxRate / 100);
order.TotalTax += taxAmount;
}
order.TotalGross = order.TotalNet + order.TotalTax;
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Zentral.API.Apps.Purchases.Dtos;
using Zentral.API.Services;
using Zentral.Domain.Entities.Purchases;
using Zentral.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Zentral.API.Apps.Purchases.Services;
public class SupplierService
{
private readonly ZentralDbContext _db;
private readonly AutoCodeService _autoCodeService;
public SupplierService(ZentralDbContext db, AutoCodeService autoCodeService)
{
_db = db;
_autoCodeService = autoCodeService;
}
public async Task<List<SupplierDto>> GetAllAsync()
{
return await _db.Suppliers
.AsNoTracking()
.OrderBy(s => s.Name)
.Select(s => new SupplierDto
{
Id = s.Id,
Code = s.Code,
Name = s.Name,
VatNumber = s.VatNumber,
FiscalCode = s.FiscalCode,
Address = s.Address,
City = s.City,
Province = s.Province,
ZipCode = s.ZipCode,
Country = s.Country,
Email = s.Email,
Pec = s.Pec,
Phone = s.Phone,
Website = s.Website,
PaymentTerms = s.PaymentTerms,
Notes = s.Notes,
IsActive = s.IsActive,
CreatedAt = s.CreatedAt,
UpdatedAt = s.UpdatedAt
})
.ToListAsync();
}
public async Task<SupplierDto?> GetByIdAsync(int id)
{
var supplier = await _db.Suppliers
.AsNoTracking()
.FirstOrDefaultAsync(s => s.Id == id);
if (supplier == null) return null;
return new SupplierDto
{
Id = supplier.Id,
Code = supplier.Code,
Name = supplier.Name,
VatNumber = supplier.VatNumber,
FiscalCode = supplier.FiscalCode,
Address = supplier.Address,
City = supplier.City,
Province = supplier.Province,
ZipCode = supplier.ZipCode,
Country = supplier.Country,
Email = supplier.Email,
Pec = supplier.Pec,
Phone = supplier.Phone,
Website = supplier.Website,
PaymentTerms = supplier.PaymentTerms,
Notes = supplier.Notes,
IsActive = supplier.IsActive,
CreatedAt = supplier.CreatedAt,
UpdatedAt = supplier.UpdatedAt
};
}
public async Task<SupplierDto> CreateAsync(CreateSupplierDto dto)
{
// Genera codice automatico
var code = await _autoCodeService.GenerateNextCodeAsync("supplier");
if (string.IsNullOrEmpty(code))
{
// Fallback se disabilitato
code = $"FOR-{DateTime.Now:yyyyMMdd}-{Guid.NewGuid().ToString().Substring(0, 4).ToUpper()}";
}
var supplier = new Supplier
{
Code = code,
Name = dto.Name,
VatNumber = dto.VatNumber,
FiscalCode = dto.FiscalCode,
Address = dto.Address,
City = dto.City,
Province = dto.Province,
ZipCode = dto.ZipCode,
Country = dto.Country,
Email = dto.Email,
Pec = dto.Pec,
Phone = dto.Phone,
Website = dto.Website,
PaymentTerms = dto.PaymentTerms,
Notes = dto.Notes,
IsActive = true,
CreatedAt = DateTime.Now
};
_db.Suppliers.Add(supplier);
await _db.SaveChangesAsync();
return await GetByIdAsync(supplier.Id) ?? throw new InvalidOperationException("Failed to retrieve created supplier");
}
public async Task<SupplierDto?> UpdateAsync(int id, UpdateSupplierDto dto)
{
var supplier = await _db.Suppliers.FindAsync(id);
if (supplier == null) return null;
supplier.Name = dto.Name;
supplier.VatNumber = dto.VatNumber;
supplier.FiscalCode = dto.FiscalCode;
supplier.Address = dto.Address;
supplier.City = dto.City;
supplier.Province = dto.Province;
supplier.ZipCode = dto.ZipCode;
supplier.Country = dto.Country;
supplier.Email = dto.Email;
supplier.Pec = dto.Pec;
supplier.Phone = dto.Phone;
supplier.Website = dto.Website;
supplier.PaymentTerms = dto.PaymentTerms;
supplier.Notes = dto.Notes;
supplier.IsActive = dto.IsActive;
supplier.UpdatedAt = DateTime.Now;
await _db.SaveChangesAsync();
return await GetByIdAsync(id);
}
public async Task<bool> DeleteAsync(int id)
{
var supplier = await _db.Suppliers.FindAsync(id);
if (supplier == null) return false;
// Check if used in purchase orders
var hasOrders = await _db.PurchaseOrders.AnyAsync(o => o.SupplierId == id);
if (hasOrders)
{
throw new InvalidOperationException("Impossibile eliminare il fornitore perché ha ordini associati.");
}
_db.Suppliers.Remove(supplier);
await _db.SaveChangesAsync();
return true;
}
}