Split Features to create space for more projects
continuous-integration/drone/pr Build is passing

This commit is contained in:
Khwezi Mngoma
2026-05-24 13:19:09 +02:00
parent 032b9e1818
commit 70c6e0bfbc
95 changed files with 621 additions and 314 deletions
@@ -0,0 +1,133 @@
using LiteCharms.Features.Extensions;
using LiteCharms.Features.Models;
using LiteCharms.Features.TechShop.Customers.Models;
using LiteCharms.Features.TechShop.Extensions;
using LiteCharms.Features.TechShop.Postgres;
namespace LiteCharms.Features.TechShop.Customers;
public class CustomerService(IDbContextFactory<ShopDbContext> contextFactory)
{
public async ValueTask<Result<Guid>> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var customerEmail = request.Email.ToLower().Trim();
if (await context.Customers.AnyAsync(c => c.Email == customerEmail, cancellationToken))
return Result.Fail<Guid>(new Error($"A customer with the email {customerEmail} already exists"));
var newCustomer = context.Customers.Add(new Entities.Customer
{
Company = request.Company,
Name = request.Name,
LastName = request.LastName,
Tax = request.Tax,
Email = customerEmail,
Discord = request.Discord,
Slack = request.Slack,
LinkedIn = request.LinkedIn,
Whatsapp = request.Whatsapp,
Website = request.Website,
Phone = request.Phone,
Address = request.Address,
City = request.City,
Region = request.Region,
Country = request.Country,
PostalCode = request.PostalCode,
Active = true,
});
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok(newCustomer.Entity.Id)
: Result.Fail<Guid>(new Error($"Failed to create customer {customerEmail}"));
}
catch (Exception ex)
{
return Result.Fail<Guid>(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Customer>> GetCustomerAsync(Guid customerId, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken);
return customer is not null
? Result.Ok(customer.ToModel())
: Result.Fail<Customer>($"Customer not found with id {customerId}");
}
catch (Exception ex)
{
return Result.Fail<Customer>(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Customer[]>> GetCustomersAsync(DateRange range, CancellationToken cancellationToken = default)
{
try
{
var fromDate = range.From.ToDateTime(TimeOnly.MinValue);
var toDate = range.To.ToDateTime(TimeOnly.MaxValue);
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var customers = await context.Customers.AsNoTracking()
.OrderByDescending(o => o.CreatedAt)
.Where(c => c.CreatedAt >= fromDate && c.CreatedAt <= toDate)
.Take(range.MaxRecords)
.ToArrayAsync(cancellationToken);
return customers?.Length > 0
? Result.Ok(customers.Select(c => c.ToModel()).ToArray())
: Result.Fail<Customer[]>(new Error("No customers found in the specified date range."));
}
catch (Exception ex)
{
return Result.Fail<Customer[]>(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> UpdateCustomerAsync(UpdateCustomer request, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == request.CustomerId, cancellationToken);
if (customer is null)
return Result.Fail(new Error($"Customer with ID {request.CustomerId} not found."));
customer.Name = request.Name;
customer.LastName = request.LastName;
customer.Email = request.Email;
customer.Company = request.Company;
customer.Address = request.Address;
customer.City = request.City;
customer.Region = request.Region;
customer.Country = request.Country;
customer.PostalCode = request.PostalCode;
customer.Phone = request.Phone;
customer.Tax = request.Tax;
customer.City = request.City;
customer.Discord = request.Discord;
customer.Slack = request.Slack;
customer.LinkedIn = request.LinkedIn;
customer.Whatsapp = request.Whatsapp;
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok()
: Result.Fail(new Error($"Failed to update the customer {request.CustomerId}."));
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -0,0 +1,18 @@
using LiteCharms.Features.TechShop.Leads.Entities;
using LiteCharms.Features.TechShop.Orders.Entities;
using LiteCharms.Features.TechShop.Quotes.Entities;
using LiteCharms.Features.TechShop.ShoppingCarts.Entities;
namespace LiteCharms.Features.TechShop.Customers.Entities;
[EntityTypeConfiguration<CustomerConfiguration, Customer>]
public class Customer : Models.Customer
{
public virtual ICollection<Lead>? Leads { get; set; }
public virtual ICollection<Order>? Orders { get; set; }
public virtual ICollection<Quote>? Quotes { get; set; }
public virtual ICollection<ShoppingCart>? ShoppingCarts { get; set; }
}
@@ -0,0 +1,30 @@
namespace LiteCharms.Features.TechShop.Customers.Entities;
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.ToTable("Customers");
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("now()");
builder.Property(f => f.UpdatedAt).IsRequired(false).HasDefaultValueSql(null);
builder.Property(f => f.Company);
builder.Property(f => f.Name).IsRequired();
builder.Property(f => f.LastName).IsRequired();
builder.Property(f => f.Email).IsRequired();
builder.Property(f => f.Tax);
builder.Property(f => f.Discord);
builder.Property(f => f.Slack);
builder.Property(f => f.LinkedIn);
builder.Property(f => f.Whatsapp);
builder.Property(f => f.Website);
builder.Property(f => f.Phone);
builder.Property(f => f.Address);
builder.Property(f => f.City);
builder.Property(f => f.Region);
builder.Property(f => f.Country);
builder.Property(f => f.PostalCode);
builder.Property(f => f.Active).HasDefaultValue(true);
}
}
@@ -0,0 +1,44 @@
namespace LiteCharms.Features.TechShop.Customers.Models;
public class Customer
{
public Guid Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public string? Company { get; set; }
public string? Name { get; set; }
public string? LastName { get; set; }
public string? Tax { get; set; }
public string? Email { get; set; }
public string? Discord { get; set; }
public string? Slack { get; set; }
public string? LinkedIn { get; set; }
public string? Whatsapp { get; set; }
public string? Website { get; set; }
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Region { get; set; }
public string? Country { get; set; }
public string? PostalCode { get; set; }
public bool Active { get; set; }
}
@@ -0,0 +1,73 @@
namespace LiteCharms.Features.TechShop.Customers.Models;
public record CreateCustomer
{
public string? Company { get; set; }
public required string Name { get; set; }
public required string LastName { get; set; }
public string? Tax { get; set; }
public required string Email { get; set; }
public string? Discord { get; set; }
public string? Slack { get; set; }
public string? LinkedIn { get; set; }
public string? Whatsapp { get; set; }
public string? Website { get; set; }
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Region { get; set; }
public string? Country { get; set; }
public string? PostalCode { get; set; }
}
public record UpdateCustomer
{
public required Guid CustomerId { get; set; }
public string? Company { get; set; }
public string? Name { get; set; }
public string? LastName { get; set; }
public string? Tax { get; set; }
public string? Email { get; set; }
public string? Discord { get; set; }
public string? Slack { get; set; }
public string? LinkedIn { get; set; }
public string? Whatsapp { get; set; }
public string? Website { get; set; }
public string? Phone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? Region { get; set; }
public string? Country { get; set; }
public string? PostalCode { get; set; }
}