implemnet customer master used for both B2B and B2C.
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
using ERPCore.Common.Http;
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using ERPCore.Dtos.Common;
|
||||
using ERPCore.Dtos.Customers;
|
||||
using ERPCore.Infra.UoW;
|
||||
using ERPCore.Repositories.Interfaces;
|
||||
using ERPCore.Services.Interfaces;
|
||||
using ERPCore.System.Errors;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ERPCore.Services;
|
||||
|
||||
public sealed class CustomerService : ICustomerService
|
||||
{
|
||||
private readonly IRepository<Customer> _customers;
|
||||
private readonly IRepository<Warehouse> _warehouses;
|
||||
private readonly IUnitOfWork _uow;
|
||||
|
||||
public CustomerService(IRepository<Customer> customers, IRepository<Warehouse> warehouses, IUnitOfWork uow)
|
||||
{
|
||||
_customers = customers;
|
||||
_warehouses = warehouses;
|
||||
_uow = uow;
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<CustomerDto>> ListAsync(PageQuery query, EntityStatus? status, CustomerType? customerType, CancellationToken ct = default)
|
||||
{
|
||||
var q = _customers.Query().AsNoTracking();
|
||||
if (!string.IsNullOrWhiteSpace(query.Q))
|
||||
{
|
||||
var term = query.Q.Trim();
|
||||
q = q.Where(c => EF.Functions.ILike(c.Name, $"%{term}%")
|
||||
|| EF.Functions.ILike(c.CustomerCode, $"%{term}%")
|
||||
|| (c.DisplayName != null && EF.Functions.ILike(c.DisplayName, $"%{term}%")));
|
||||
}
|
||||
if (status is not null) q = q.Where(c => c.Status == status);
|
||||
if (customerType is not null) q = q.Where(c => c.CustomerType == customerType);
|
||||
|
||||
var total = await q.CountAsync(ct);
|
||||
var rows = await q.OrderBy(c => c.Name)
|
||||
.Skip(query.Skip).Take(query.PageSize)
|
||||
.Select(c => new CustomerDto(
|
||||
c.CustomerId, c.CustomerCode, c.CustomerType, c.Name, c.DisplayName, c.Phone, c.Email,
|
||||
c.AddressLine1, c.AddressLine2, c.City, c.Country, c.TaxRegistrationNo,
|
||||
c.CreditLimit, c.CreditDays, c.DefaultWarehouseId, c.Status, c.CreatedAt, c.UpdatedAt))
|
||||
.ToListAsync(ct);
|
||||
|
||||
return PagedResponse<CustomerDto>.Create(rows, query.Page, query.PageSize, total);
|
||||
}
|
||||
|
||||
public async Task<ETagged<CustomerDto>?> GetAsync(int customerId, CancellationToken ct = default)
|
||||
{
|
||||
var customer = await _customers.Query().AsNoTracking()
|
||||
.FirstOrDefaultAsync(c => c.CustomerId == customerId, ct);
|
||||
return customer is null ? null : new ETagged<CustomerDto>(Map(customer), customer.RowVersion);
|
||||
}
|
||||
|
||||
public async Task<ETagged<CustomerDto>> CreateAsync(CreateCustomerRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var code = request.CustomerCode.Trim();
|
||||
var name = request.Name.Trim();
|
||||
|
||||
if (await _customers.Query().AnyAsync(c => c.CustomerCode.ToLower() == code.ToLower(), ct))
|
||||
throw new ConflictException($"A customer code '{code}' already exists.");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Email))
|
||||
{
|
||||
var email = request.Email.Trim();
|
||||
if (await _customers.Query().AnyAsync(c => c.Email != null && c.Email.ToLower() == email.ToLower(), ct))
|
||||
throw new ConflictException($"A customer with email '{email}' already exists.");
|
||||
}
|
||||
|
||||
if (request.DefaultWarehouseId is not null)
|
||||
{
|
||||
var exists = await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.DefaultWarehouseId, ct);
|
||||
if (!exists) throw new NotFoundException($"Warehouse {request.DefaultWarehouseId} was not found.");
|
||||
}
|
||||
|
||||
var customer = new Customer
|
||||
{
|
||||
CustomerCode = code,
|
||||
CustomerType = request.CustomerType,
|
||||
Name = name,
|
||||
DisplayName = Normalize(request.DisplayName),
|
||||
Phone = Normalize(request.Phone),
|
||||
Email = Normalize(request.Email),
|
||||
AddressLine1 = Normalize(request.AddressLine1),
|
||||
AddressLine2 = Normalize(request.AddressLine2),
|
||||
City = Normalize(request.City),
|
||||
Country = Normalize(request.Country),
|
||||
TaxRegistrationNo = Normalize(request.TaxRegistrationNo),
|
||||
CreditLimit = request.CreditLimit,
|
||||
CreditDays = request.CreditDays,
|
||||
DefaultWarehouseId = request.DefaultWarehouseId,
|
||||
Status = EntityStatus.Active,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _customers.AddAsync(customer, ct);
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
|
||||
return new ETagged<CustomerDto>(Map(customer), customer.RowVersion);
|
||||
}
|
||||
|
||||
public async Task<ETagged<CustomerDto>> UpdateAsync(int customerId, UpdateCustomerRequest request, uint expectedRowVersion, CancellationToken ct = default)
|
||||
{
|
||||
var customer = await _customers.GetByIdAsync(customerId, ct)
|
||||
?? throw new NotFoundException($"Customer {customerId} was not found.");
|
||||
|
||||
if (customer.RowVersion != expectedRowVersion)
|
||||
throw new DomainException(ErrorCodes.ConcurrencyConflict, "The customer was modified by another request.", 412);
|
||||
|
||||
var code = request.CustomerCode.Trim();
|
||||
var name = request.Name.Trim();
|
||||
|
||||
if (!string.Equals(customer.CustomerCode, code, StringComparison.Ordinal)
|
||||
&& await _customers.Query().AnyAsync(c => c.CustomerCode.ToLower() == code.ToLower() && c.CustomerId != customerId, ct))
|
||||
throw new ConflictException($"A customer code '{code}' already exists.");
|
||||
|
||||
if (!string.Equals(customer.Email, request.Email?.Trim(), StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrWhiteSpace(request.Email)
|
||||
&& await _customers.Query().AnyAsync(c => c.Email != null && c.Email.ToLower() == request.Email!.Trim().ToLower() && c.CustomerId != customerId, ct))
|
||||
throw new ConflictException($"A customer with email '{request.Email.Trim()}' already exists.");
|
||||
|
||||
if (request.DefaultWarehouseId is not null)
|
||||
{
|
||||
var exists = await _warehouses.Query().AnyAsync(w => w.WarehouseId == request.DefaultWarehouseId, ct);
|
||||
if (!exists) throw new NotFoundException($"Warehouse {request.DefaultWarehouseId} was not found.");
|
||||
}
|
||||
|
||||
customer.CustomerCode = code;
|
||||
customer.CustomerType = request.CustomerType;
|
||||
customer.Name = name;
|
||||
customer.DisplayName = Normalize(request.DisplayName);
|
||||
customer.Phone = Normalize(request.Phone);
|
||||
customer.Email = Normalize(request.Email);
|
||||
customer.AddressLine1 = Normalize(request.AddressLine1);
|
||||
customer.AddressLine2 = Normalize(request.AddressLine2);
|
||||
customer.City = Normalize(request.City);
|
||||
customer.Country = Normalize(request.Country);
|
||||
customer.TaxRegistrationNo = Normalize(request.TaxRegistrationNo);
|
||||
customer.CreditLimit = request.CreditLimit;
|
||||
customer.CreditDays = request.CreditDays;
|
||||
customer.DefaultWarehouseId = request.DefaultWarehouseId;
|
||||
customer.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
return new ETagged<CustomerDto>(Map(customer), customer.RowVersion);
|
||||
}
|
||||
|
||||
public async Task SetStatusAsync(int customerId, EntityStatus status, CancellationToken ct = default)
|
||||
{
|
||||
var customer = await _customers.GetByIdAsync(customerId, ct)
|
||||
?? throw new NotFoundException($"Customer {customerId} was not found.");
|
||||
|
||||
customer.Status = status;
|
||||
customer.UpdatedAt = DateTime.UtcNow;
|
||||
await _uow.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
private static CustomerDto Map(Customer c) => new(
|
||||
c.CustomerId, c.CustomerCode, c.CustomerType, c.Name, c.DisplayName, c.Phone, c.Email,
|
||||
c.AddressLine1, c.AddressLine2, c.City, c.Country, c.TaxRegistrationNo,
|
||||
c.CreditLimit, c.CreditDays, c.DefaultWarehouseId, c.Status, c.CreatedAt, c.UpdatedAt);
|
||||
|
||||
private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using ERPCore.Common.Http;
|
||||
using ERPCore.Dtos.Common;
|
||||
using ERPCore.Dtos.Customers;
|
||||
using ERPCore.Domain.Enums;
|
||||
|
||||
namespace ERPCore.Services.Interfaces;
|
||||
|
||||
public interface ICustomerService
|
||||
{
|
||||
Task<PagedResponse<CustomerDto>> ListAsync(PageQuery query, EntityStatus? status, CustomerType? customerType, CancellationToken ct = default);
|
||||
Task<ETagged<CustomerDto>?> GetAsync(int customerId, CancellationToken ct = default);
|
||||
Task<ETagged<CustomerDto>> CreateAsync(CreateCustomerRequest request, CancellationToken ct = default);
|
||||
Task<ETagged<CustomerDto>> UpdateAsync(int customerId, UpdateCustomerRequest request, uint expectedRowVersion, CancellationToken ct = default);
|
||||
Task SetStatusAsync(int customerId, EntityStatus status, CancellationToken ct = default);
|
||||
}
|
||||
Reference in New Issue
Block a user