feat(api): expose sales document application endpoints
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 6m9s

close #64
This commit is contained in:
Leandro Hernan Rojas 2026-06-03 09:00:56 -03:00
parent 36c3e26231
commit adb8eb5c5c
6 changed files with 252 additions and 0 deletions

View File

@ -1,9 +1,21 @@
using Domain.Dtos.Sales;
using Domain.Generics;
namespace Core.Interfaces
{
public interface ISalesDocumentDom
{
Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(
int? customerId,
string? customerText,
int? quoteId,
int? documentType,
int? status,
DateTime? issueDateFrom,
DateTime? issueDateTo,
int page = 1,
int pageSize = 50);
Task<SalesDocumentCreateResponse> CreateAsync(SalesDocumentCreateRequest request);
Task<SalesDocumentDto?> GetDtoByIdAsync(int id);
}

View File

@ -2,6 +2,7 @@ using Core.Interfaces;
using Domain.Constants;
using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
namespace Core.Services
@ -10,6 +11,29 @@ namespace Core.Services
{
private readonly IPhSSalesDocumentRepository _salesDocumentRepository = salesDocumentRepository;
public Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(
int? customerId,
string? customerText,
int? quoteId,
int? documentType,
int? status,
DateTime? issueDateFrom,
DateTime? issueDateTo,
int page = 1,
int pageSize = 50)
{
return _salesDocumentRepository.SearchAsync(
customerId,
customerText,
quoteId,
documentType,
status,
issueDateFrom,
issueDateTo,
page,
pageSize);
}
public async Task<SalesDocumentCreateResponse> CreateAsync(SalesDocumentCreateRequest request)
{
ArgumentNullException.ThrowIfNull(request);

View File

@ -1,10 +1,22 @@
using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics;
namespace Models.Interfaces
{
public interface IPhSSalesDocumentRepository
{
Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(
int? customerId,
string? customerText,
int? quoteId,
int? documentType,
int? status,
DateTime? issueDateFrom,
DateTime? issueDateTo,
int page = 1,
int pageSize = 50);
Task<ESalesDocument> CreateAsync(ESalesDocument entity);
Task<SalesDocumentDto?> GetDtoByIdAsync(int id);
}

View File

@ -1,5 +1,6 @@
using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics;
using Microsoft.EntityFrameworkCore;
using Models.Helpers;
using Models.Interfaces;
@ -11,6 +12,103 @@ namespace Models.Repositories
{
private readonly PhronCareOperationsHubContext _context = context;
public async Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(
int? customerId,
string? customerText,
int? quoteId,
int? documentType,
int? status,
DateTime? issueDateFrom,
DateTime? issueDateTo,
int page = 1,
int pageSize = 50)
{
page = page <= 0 ? 1 : page;
pageSize = pageSize <= 0 ? 50 : pageSize;
var query = _context.PhSSalesDocuments
.Include(x => x.Customer)
.Include(x => x.BillToCustomer)
.AsNoTracking()
.AsQueryable();
if (customerId.HasValue && customerId.Value > 0)
{
query = query.Where(x => x.CustomerId == customerId.Value || x.BillToCustomerId == customerId.Value);
}
if (!string.IsNullOrWhiteSpace(customerText))
{
var normalizedCustomerText = customerText.Trim();
query = query.Where(x =>
(x.Customer.Name ?? string.Empty).Contains(normalizedCustomerText) ||
(x.BillToCustomer.Name ?? string.Empty).Contains(normalizedCustomerText));
}
if (quoteId.HasValue && quoteId.Value > 0)
{
query = query.Where(x => x.QuoteId == quoteId.Value);
}
if (documentType.HasValue && documentType.Value > 0)
{
query = query.Where(x => x.DocumentType == documentType.Value);
}
if (status.HasValue && status.Value > 0)
{
query = query.Where(x => x.Status == status.Value);
}
if (issueDateFrom.HasValue)
{
query = query.Where(x => x.IssueDate >= issueDateFrom.Value);
}
if (issueDateTo.HasValue)
{
query = query.Where(x => x.IssueDate <= issueDateTo.Value);
}
var totalItems = await query.CountAsync();
var items = await query
.OrderByDescending(x => x.IssueDate)
.ThenByDescending(x => x.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new SalesDocumentSummaryDto
{
Id = x.Id,
InternalDocumentNumber = x.InternalDocumentNumber,
DocumentType = x.DocumentType,
Status = x.Status,
QuoteId = x.QuoteId,
CustomerId = x.CustomerId,
CustomerName = x.Customer.Name ?? string.Empty,
BillToCustomerId = x.BillToCustomerId,
BillToCustomerName = x.BillToCustomer.Name ?? string.Empty,
IssueDate = x.IssueDate,
Currency = x.Currency,
NetAmount = x.NetAmount,
TaxAmount = x.TaxAmount,
TotalAmount = x.TotalAmount,
PeriodFrom = x.PeriodFrom,
PeriodTo = x.PeriodTo,
Createdat = x.Createdat,
Modifiedat = x.Modifiedat
})
.ToListAsync();
return new PagedResult<SalesDocumentSummaryDto>
{
Items = items,
TotalItems = totalItems,
Page = page,
PageSize = pageSize
};
}
public async Task<ESalesDocument> CreateAsync(ESalesDocument entity)
{
var mapped = EntityMapper.MapEntity<ESalesDocument, PhSSalesDocument>(entity);

View File

@ -0,0 +1,103 @@
using Core.Interfaces;
using Domain.Dtos.Sales;
using Domain.Generics;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
namespace phronCare.API.Controllers.Sales
{
[Route("api/[controller]")]
[ApiController]
public class SalesDocumentController : ControllerBase
{
private readonly ISalesDocumentDom _salesDocumentService;
public SalesDocumentController(ISalesDocumentDom salesDocumentService)
{
_salesDocumentService = salesDocumentService ?? throw new ArgumentNullException(nameof(salesDocumentService));
}
[HttpGet("search")]
public async Task<ActionResult<PagedResult<SalesDocumentSummaryDto>>> Search(
[FromQuery] int? customerId,
[FromQuery] string? customerText,
[FromQuery] int? quoteId,
[FromQuery] int? documentType,
[FromQuery] int? status,
[FromQuery] DateTime? issueDateFrom,
[FromQuery] DateTime? issueDateTo,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 50)
{
try
{
var result = await _salesDocumentService.SearchAsync(
customerId,
customerText,
quoteId,
documentType,
status,
issueDateFrom,
issueDateTo,
page,
pageSize);
return Ok(result);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
return StatusCode(500, $"{methodName} Message: {ex.Message}");
}
}
[HttpGet("{id:int}")]
public async Task<ActionResult<SalesDocumentDto>> GetById(int id)
{
try
{
var salesDocument = await _salesDocumentService.GetDtoByIdAsync(id);
if (salesDocument == null)
return NotFound($"Sales Document con ID {id} no encontrado.");
return Ok(salesDocument);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
return StatusCode(500, $"{methodName} Message: {ex.Message}");
}
}
[HttpPost]
public async Task<ActionResult<SalesDocumentDto>> Create([FromBody] SalesDocumentCreateRequest request)
{
try
{
if (request == null)
return BadRequest("El payload no puede ser nulo.");
var created = await _salesDocumentService.CreateAsync(request);
var salesDocument = await _salesDocumentService.GetDtoByIdAsync(created.Id);
if (salesDocument == null)
return StatusCode(500, $"No se pudo recuperar el Sales Document creado con ID {created.Id}.");
return CreatedAtAction(nameof(GetById), new { id = salesDocument.Id }, salesDocument);
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
return StatusCode(500, $"{methodName} Message: {ex.Message}");
}
}
}
}

View File

@ -241,6 +241,9 @@ static void RepositorysAndServices(WebApplicationBuilder builder)
builder.Services.AddScoped<IDeliveryNoteDom, DeliveryNoteService>();
builder.Services.AddScoped<IPhSDeliveryNoteRepository, PhSDeliveryNoteRepository>();
builder.Services.AddScoped<ISalesDocumentDom, SalesDocumentService>();
builder.Services.AddScoped<IPhSSalesDocumentRepository, PhSSalesDocumentRepository>();
builder.Services.AddScoped<ITaxTypeDom, TaxTypeService>();
builder.Services.AddScoped<IPhOhArcataxTypeRepository, PhOhArcataxTypeRepository>();