using Lagerbestand.Data; using Lagerbestand.Models; using Microsoft.EntityFrameworkCore; namespace Lagerbestand.Services; public interface ICustomerService { Task> GetAllAsync(bool includeInactive = false); Task GetByIdAsync(Guid id); Task GetByCustomerNumberAsync(string customerNumber); Task> SearchAsync(string searchTerm); Task FindByVehicleAsync(string searchTerm); Task CreateAsync(Customer customer); Task UpdateAsync(Customer customer); Task DeleteAsync(Guid id); Task GenerateCustomerNumberAsync(); Task GetStoredTireStackCountAsync(Guid customerId); // Vehicle methods Task> GetVehiclesAsync(Guid customerId); Task GetVehicleByIdAsync(Guid vehicleId); Task AddVehicleAsync(Guid customerId, Vehicle vehicle); Task UpdateVehicleAsync(Vehicle vehicle); Task DeleteVehicleAsync(Guid vehicleId); Task SetPrimaryVehicleAsync(Guid customerId, Guid vehicleId); Task CreateWithVehicleAsync(Customer customer, Vehicle vehicle); } public class CustomerService : ICustomerService { private readonly AppDbContext _context; private readonly ILogger _logger; public CustomerService(AppDbContext context, ILogger logger) { _context = context; _logger = logger; } public async Task> GetAllAsync(bool includeInactive = false) { var query = _context.Customers .Include(c => c.Vehicles.Where(v => v.IsActive)) .AsQueryable(); if (!includeInactive) query = query.Where(c => c.IsActive); return await query .OrderBy(c => c.LastName) .ThenBy(c => c.FirstName) .ToListAsync(); } public async Task GetByIdAsync(Guid id) { return await _context.Customers .Include(c => c.Vehicles.Where(v => v.IsActive)) .Include(c => c.TireStacks.Where(ts => ts.Status == TireStackStatus.Stored)) .ThenInclude(ts => ts.StorageLocation) .Include(c => c.TireStacks.Where(ts => ts.Status == TireStackStatus.Stored)) .ThenInclude(ts => ts.Vehicle) .AsSplitQuery() .FirstOrDefaultAsync(c => c.Id == id); } public async Task GetByCustomerNumberAsync(string customerNumber) { return await _context.Customers .Include(c => c.Vehicles.Where(v => v.IsActive)) .FirstOrDefaultAsync(c => c.CustomerNumber == customerNumber); } public async Task> SearchAsync(string searchTerm) { if (string.IsNullOrWhiteSpace(searchTerm)) return await GetAllAsync(); var term = searchTerm.Trim().ToLower(); return await _context.Customers .AsNoTracking() .AsSplitQuery() .Include(c => c.Vehicles.Where(v => v.IsActive)) .Include(c => c.TireStacks.Where(ts => ts.Status == TireStackStatus.Stored)) .Where(c => c.IsActive && ( c.FirstName != null && c.FirstName.ToLower().Contains(term) || c.LastName.ToLower().Contains(term) || (c.Company != null && c.Company.ToLower().Contains(term)) || c.CustomerNumber.ToLower().Contains(term) || (c.Phone != null && c.Phone.Contains(term)) || // Search in vehicles c.Vehicles.Any(v => v.IsActive && ( v.Make.ToLower().Contains(term) || (v.Model != null && v.Model.ToLower().Contains(term)))) )) .OrderBy(c => c.LastName) .ThenBy(c => c.FirstName) .Take(50) .ToListAsync(); } public async Task FindByVehicleAsync(string searchTerm) { if (string.IsNullOrWhiteSpace(searchTerm)) return null; var term = searchTerm.Trim().ToLower(); // Server-side search in Vehicles table by Make or Model var matchingVehicle = await _context.Vehicles .Include(v => v.Customer) .Where(v => v.IsActive && v.Customer != null && v.Customer.IsActive) .FirstOrDefaultAsync(v => v.Make.ToLower().Contains(term) || (v.Model != null && v.Model.ToLower().Contains(term))); if (matchingVehicle?.Customer != null) { return await GetByIdAsync(matchingVehicle.Customer.Id); } return null; } public async Task CreateAsync(Customer customer) { if (string.IsNullOrEmpty(customer.CustomerNumber)) customer.CustomerNumber = await GenerateCustomerNumberAsync(); customer.CreatedAt = DateTime.UtcNow; _context.Customers.Add(customer); await _context.SaveChangesAsync(); _logger.LogInformation("Customer created: {CustomerNumber} - {Name}", customer.CustomerNumber, customer.FullName); return customer; } public async Task CreateWithVehicleAsync(Customer customer, Vehicle vehicle) { if (string.IsNullOrEmpty(customer.CustomerNumber)) customer.CustomerNumber = await GenerateCustomerNumberAsync(); customer.CreatedAt = DateTime.UtcNow; vehicle.IsPrimary = true; vehicle.CreatedAt = DateTime.UtcNow; customer.Vehicles.Add(vehicle); _context.Customers.Add(customer); await _context.SaveChangesAsync(); _logger.LogInformation("Customer created with vehicle: {CustomerNumber} - {Name}, Vehicle: {Make}", customer.CustomerNumber, customer.FullName, vehicle.Make); return customer; } public async Task UpdateAsync(Customer customer) { customer.UpdatedAt = DateTime.UtcNow; _context.Customers.Update(customer); await _context.SaveChangesAsync(); _logger.LogInformation("Customer updated: {CustomerNumber}", customer.CustomerNumber); return customer; } public async Task DeleteAsync(Guid id) { var customer = await _context.Customers.FindAsync(id); if (customer == null) return false; // Soft delete - just mark as inactive customer.IsActive = false; customer.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Customer deactivated: {CustomerNumber}", customer.CustomerNumber); return true; } public async Task GenerateCustomerNumberAsync() { var year = DateTime.UtcNow.Year; var prefix = $"K-{year}-"; var lastNumber = await _context.Customers .Where(c => c.CustomerNumber.StartsWith(prefix)) .OrderByDescending(c => c.CustomerNumber) .Select(c => c.CustomerNumber) .FirstOrDefaultAsync(); var nextNumber = 1; if (lastNumber != null) { var numPart = lastNumber.Replace(prefix, ""); if (int.TryParse(numPart, out var lastNum)) nextNumber = lastNum + 1; } return $"{prefix}{nextNumber:D4}"; } public async Task GetStoredTireStackCountAsync(Guid customerId) { return await _context.TireStacks .CountAsync(ts => ts.IsActive && ts.CustomerId == customerId && ts.Status == TireStackStatus.Stored); } // Vehicle methods public async Task> GetVehiclesAsync(Guid customerId) { return await _context.Vehicles .Where(v => v.CustomerId == customerId && v.IsActive) .OrderByDescending(v => v.IsPrimary) .ThenBy(v => v.CreatedAt) .ToListAsync(); } public async Task GetVehicleByIdAsync(Guid vehicleId) { return await _context.Vehicles .Include(v => v.Customer) .Include(v => v.TireStacks.Where(ts => ts.Status == TireStackStatus.Stored)) .FirstOrDefaultAsync(v => v.Id == vehicleId); } public async Task AddVehicleAsync(Guid customerId, Vehicle vehicle) { vehicle.CustomerId = customerId; vehicle.CreatedAt = DateTime.UtcNow; // If this is the first vehicle, make it primary var existingVehicles = await _context.Vehicles .CountAsync(v => v.CustomerId == customerId && v.IsActive); if (existingVehicles == 0) vehicle.IsPrimary = true; _context.Vehicles.Add(vehicle); await _context.SaveChangesAsync(); _logger.LogInformation("Vehicle added for customer {CustomerId}: {Make}", customerId, vehicle.Make); return vehicle; } public async Task UpdateVehicleAsync(Vehicle vehicle) { vehicle.UpdatedAt = DateTime.UtcNow; _context.Vehicles.Update(vehicle); await _context.SaveChangesAsync(); _logger.LogInformation("Vehicle updated: {VehicleId} - {Make}", vehicle.Id, vehicle.Make); return vehicle; } public async Task DeleteVehicleAsync(Guid vehicleId) { var vehicle = await _context.Vehicles.FindAsync(vehicleId); if (vehicle == null) return false; // Soft delete vehicle.IsActive = false; vehicle.UpdatedAt = DateTime.UtcNow; // If this was primary, assign primary to another vehicle if (vehicle.IsPrimary) { vehicle.IsPrimary = false; var nextVehicle = await _context.Vehicles .Where(v => v.CustomerId == vehicle.CustomerId && v.IsActive && v.Id != vehicleId) .FirstOrDefaultAsync(); if (nextVehicle != null) nextVehicle.IsPrimary = true; } await _context.SaveChangesAsync(); _logger.LogInformation("Vehicle deactivated: {VehicleId}", vehicleId); return true; } public async Task SetPrimaryVehicleAsync(Guid customerId, Guid vehicleId) { var vehicles = await _context.Vehicles .Where(v => v.CustomerId == customerId && v.IsActive) .ToListAsync(); foreach (var v in vehicles) { v.IsPrimary = v.Id == vehicleId; v.UpdatedAt = DateTime.UtcNow; } await _context.SaveChangesAsync(); _logger.LogInformation("Primary vehicle set for customer {CustomerId}: {VehicleId}", customerId, vehicleId); } }