421 lines
17 KiB
C#
421 lines
17 KiB
C#
using LiteCharms.Features.Abstractions;
|
|
using LiteCharms.Features.MidrandBooks.Customers.Models;
|
|
using LiteCharms.Features.MidrandBooks.Extensions;
|
|
using LiteCharms.Features.MidrandBooks.Postgres;
|
|
|
|
namespace LiteCharms.Features.MidrandBooks.Customers;
|
|
|
|
public sealed class CustomerService(IDbContextFactory<MidrandBooksDbContext> contextFactory) : IService
|
|
{
|
|
public async ValueTask<Result<long>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken))
|
|
return Result.Fail<long>(new Error($"Customer with email '{request.Email}' already exists."));
|
|
|
|
var customer = context.Customers.Add(new Entities.Customer
|
|
{
|
|
Company = request.Company,
|
|
VatNumber = request.VatNumber,
|
|
Email = request.Email,
|
|
Website = request.Website,
|
|
Phone = request.Phone,
|
|
SocialMedia = request.SocialMedia,
|
|
Enabled = true
|
|
});
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok(customer.Entity.Id)
|
|
: Result.Fail<long>(new Error("Failed to create customer."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<long>> CreateCustomerContactAsync(long customerId, CreateCustomerContact request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
|
|
if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken))
|
|
return Result.Fail<long>(new Error($"Contact with email '{request.Email}' already exists for this customer."));
|
|
|
|
var contact = context.Contacts.Add(new Entities.Contact
|
|
{
|
|
CustomerId = customerId,
|
|
Name = request.Name,
|
|
Email = request.Email,
|
|
Phone = request.Phone,
|
|
LastName = request.LastName,
|
|
Type = request.Type,
|
|
Enabled = true
|
|
});
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok(contact.Entity.Id)
|
|
: Result.Fail<long>(new Error("Failed to create customer contact."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<long>> CreateCustomerAddressAsync(long customerId, CreateCustomerAddress request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
return Result.Fail<long>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
|
|
var address = context.Addresses.Add(new Entities.Address
|
|
{
|
|
CustomerId = customerId,
|
|
Street = request.Street,
|
|
City = request.City,
|
|
State = request.State,
|
|
PostalCode = request.PostalCode,
|
|
Country = request.Country,
|
|
Type = request.Type,
|
|
Enabled = true,
|
|
BuildingType = request.BuildingType,
|
|
IsPrimary = request.IsPrimary,
|
|
Name = request.Name
|
|
});
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok(address.Entity.Id)
|
|
: Result.Fail<long>(new Error("Failed to create customer address."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<long>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerAsync(long customerId, UpdateCustomer request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
|
|
|
if (customer is null)
|
|
return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
|
|
customer.UpdatedAt = DateTime.UtcNow;
|
|
customer.Company = request.Company;
|
|
customer.VatNumber = request.VatNumber;
|
|
customer.Email = request.Email;
|
|
customer.Website = request.Website;
|
|
customer.Phone = request.Phone;
|
|
customer.SocialMedia = request.SocialMedia;
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error("Failed to update customer."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerContactAsync(long contactId, UpdateCustomerContact request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var rowsUpdated = await context.Contacts
|
|
.Where(cc => cc.Id == contactId)
|
|
.ExecuteUpdateAsync(setters => setters
|
|
.SetProperty(cc => cc.Name, request.Name)
|
|
.SetProperty(cc => cc.LastName, request.LastName)
|
|
.SetProperty(cc => cc.Email, request.Email)
|
|
.SetProperty(cc => cc.Phone, request.Phone)
|
|
.SetProperty(cc => cc.Type, request.Type)
|
|
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
|
|
return rowsUpdated > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerAddressAsync(long addressId, UpdateCustomerAddress request, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var rowsUpdated = await context.Addresses
|
|
.Where(a => a.Id == addressId)
|
|
.ExecuteUpdateAsync(setters => setters
|
|
.SetProperty(a => a.Street, request.Street)
|
|
.SetProperty(a => a.City, request.City)
|
|
.SetProperty(a => a.State, request.State)
|
|
.SetProperty(a => a.PostalCode, request.PostalCode)
|
|
.SetProperty(a => a.Country, request.Country)
|
|
.SetProperty(a => a.Type, request.Type)
|
|
.SetProperty(a => a.BuildingType, request.BuildingType)
|
|
.SetProperty(a => a.IsPrimary, request.IsPrimary)
|
|
.SetProperty(a => a.Name, request.Name)
|
|
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
|
|
return rowsUpdated > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerStatusAsync(long customerId, bool enabled, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var rowsUpdated = await context.Customers
|
|
.Where(c => c.Id == customerId)
|
|
.ExecuteUpdateAsync(setters => setters
|
|
.SetProperty(c => c.Enabled, enabled)
|
|
.SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
|
|
return rowsUpdated > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerContactStatusAsync(long contactId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var rowsUpdated = await context.Contacts
|
|
.Where(cc => cc.Id == contactId)
|
|
.ExecuteUpdateAsync(setters => setters
|
|
.SetProperty(cc => cc.Enabled, enabled)
|
|
.SetProperty(cc => cc.IsPrimary, isPrimary)
|
|
.SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
|
|
return rowsUpdated > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> UpdateCustomerAddressStatusAsync(long addressId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var rowsUpdated = await context.Addresses
|
|
.Where(a => a.Id == addressId)
|
|
.ExecuteUpdateAsync(setters => setters
|
|
.SetProperty(a => a.Enabled, enabled)
|
|
.SetProperty(a => a.IsPrimary, isPrimary)
|
|
.SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
|
|
|
return rowsUpdated > 0
|
|
? Result.Ok()
|
|
: Result.Fail(new Error($"Address with ID '{addressId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Customer[]>> GetCustomersAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var customers = await context.Customers
|
|
.AsNoTracking()
|
|
.Include(c => c.Contacts)
|
|
.Include(c => c.Addresses)
|
|
.OrderByDescending(c => c.CreatedAt)
|
|
.ThenByDescending(c => c.UpdatedAt)
|
|
.AsSplitQuery()
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return customers?.Count > 0
|
|
? Result.Ok(customers.Select(c => c.ToModel()).ToArray())
|
|
: Result.Fail<Customer[]>(new Error("No customers found."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Customer[]>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Contact[]>> GetCustomerContactsAsync(long customerId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
return Result.Fail<Contact[]>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
|
|
var contacts = await context.Contacts
|
|
.AsNoTracking()
|
|
.Where(cc => cc.CustomerId == customerId)
|
|
.OrderByDescending(cc => cc.CreatedAt)
|
|
.ThenByDescending(cc => cc.UpdatedAt)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return contacts?.Count > 0
|
|
? Result.Ok(contacts.Select(cc => cc.ToModel()).ToArray())
|
|
: Result.Fail<Contact[]>(new Error("No contacts found for the specified customer."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Contact[]>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Address[]>> GetCustomerAddressesAsync(long customerId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken))
|
|
return Result.Fail<Address[]>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
|
|
var addresses = await context.Addresses
|
|
.AsNoTracking()
|
|
.Where(a => a.CustomerId == customerId)
|
|
.OrderByDescending(a => a.CreatedAt)
|
|
.ThenByDescending(a => a.UpdatedAt)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return addresses?.Count > 0
|
|
? Result.Ok(addresses.Select(a => a.ToModel()).ToArray())
|
|
: Result.Fail<Address[]>(new Error($"No addresses found for customer with ID '{customerId}'."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Address[]>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Customer>> GetCustomerAsync(string email, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var customer = await context.Customers
|
|
.AsNoTracking()
|
|
.Include(c => c.Contacts)
|
|
.Include(c => c.Addresses)
|
|
.FirstOrDefaultAsync(c => c.Email == email, cancellationToken);
|
|
|
|
return customer is not null
|
|
? Result.Ok(customer.ToModel())
|
|
: Result.Fail<Customer>(new Error($"Customer with email '{email}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Customer>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Customer>> GetCustomerAsync(long customerId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var customer = await context.Customers
|
|
.AsNoTracking()
|
|
.Include(c => c.Contacts)
|
|
.Include(c => c.Addresses)
|
|
.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
|
|
|
|
return customer is not null
|
|
? Result.Ok(customer.ToModel())
|
|
: Result.Fail<Customer>(new Error($"Customer with ID '{customerId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Customer>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Contact>> GetCustomerContactAsync(long contactId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var contact = await context.Contacts
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken);
|
|
|
|
return contact is not null
|
|
? Result.Ok(contact.ToModel())
|
|
: Result.Fail<Contact>(new Error($"Contact with ID '{contactId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Contact>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Address>> GetCustomerAddressAsync(long addressId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var address = await context.Addresses
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken);
|
|
|
|
return address is not null
|
|
? Result.Ok(address.ToModel())
|
|
: Result.Fail<Address>(new Error($"Address with ID '{addressId}' does not exist."));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<Address>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
}
|