316 lines
10 KiB
C#
316 lines
10 KiB
C#
using Lagerbestand.Data;
|
|
using Lagerbestand.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Lagerbestand.Services;
|
|
|
|
public interface ICustomerService
|
|
{
|
|
Task<List<Customer>> GetAllAsync(bool includeInactive = false);
|
|
Task<Customer?> GetByIdAsync(Guid id);
|
|
Task<Customer?> GetByCustomerNumberAsync(string customerNumber);
|
|
Task<List<Customer>> SearchAsync(string searchTerm);
|
|
Task<Customer?> FindByVehicleAsync(string searchTerm);
|
|
Task<Customer> CreateAsync(Customer customer);
|
|
Task<Customer> UpdateAsync(Customer customer);
|
|
Task<bool> DeleteAsync(Guid id);
|
|
Task<string> GenerateCustomerNumberAsync();
|
|
Task<int> GetStoredTireStackCountAsync(Guid customerId);
|
|
|
|
// Vehicle methods
|
|
Task<List<Vehicle>> GetVehiclesAsync(Guid customerId);
|
|
Task<Vehicle?> GetVehicleByIdAsync(Guid vehicleId);
|
|
Task<Vehicle> AddVehicleAsync(Guid customerId, Vehicle vehicle);
|
|
Task<Vehicle> UpdateVehicleAsync(Vehicle vehicle);
|
|
Task<bool> DeleteVehicleAsync(Guid vehicleId);
|
|
Task SetPrimaryVehicleAsync(Guid customerId, Guid vehicleId);
|
|
Task<Customer> CreateWithVehicleAsync(Customer customer, Vehicle vehicle);
|
|
}
|
|
|
|
public class CustomerService : ICustomerService
|
|
{
|
|
private readonly AppDbContext _context;
|
|
private readonly ILogger<CustomerService> _logger;
|
|
|
|
public CustomerService(AppDbContext context, ILogger<CustomerService> logger)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<List<Customer>> 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<Customer?> 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<Customer?> GetByCustomerNumberAsync(string customerNumber)
|
|
{
|
|
return await _context.Customers
|
|
.Include(c => c.Vehicles.Where(v => v.IsActive))
|
|
.FirstOrDefaultAsync(c => c.CustomerNumber == customerNumber);
|
|
}
|
|
|
|
public async Task<List<Customer>> 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<Customer?> 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<Customer> 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<Customer> 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<Customer> 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<bool> 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<string> 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<int> GetStoredTireStackCountAsync(Guid customerId)
|
|
{
|
|
return await _context.TireStacks
|
|
.CountAsync(ts => ts.IsActive && ts.CustomerId == customerId && ts.Status == TireStackStatus.Stored);
|
|
}
|
|
|
|
// Vehicle methods
|
|
|
|
public async Task<List<Vehicle>> 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<Vehicle?> 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<Vehicle> 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<Vehicle> 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<bool> 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);
|
|
}
|
|
}
|