Merge pull request 'feature/leandro/35-issue-delivery-note' (#36) from feature/leandro/35-issue-delivery-note into master
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 2m48s

Reviewed-on: http://saludlab.com.ar:3000/leandro/phronCare/pulls/36
This commit is contained in:
Leandro Hernan Rojas 2026-03-24 13:15:19 +00:00
commit 3f0e3d425b
10 changed files with 135 additions and 15 deletions

View File

@ -49,4 +49,9 @@ public interface IDeliveryNoteDom
/// Puede estar vacía si no existen registros. /// Puede estar vacía si no existen registros.
/// </returns> /// </returns>
Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId); Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId);
/// <summary>
/// Valida y prepara la emisión de un Delivery Note.
/// </summary>
Task<DeliveryNoteCreateResponse> CreateAndIssueDeliveryNoteAsync(DeliveryNoteCreateRequest request);
} }

View File

@ -1,5 +1,6 @@
using Core.Interfaces; using Core.Interfaces;
using Domain.Dtos.Sales; using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Models.Interfaces; using Models.Interfaces;
@ -60,5 +61,44 @@ namespace Core.Services
return _deliveryNoteRepository.GetDtosByQuoteIdAsync(quoteId); return _deliveryNoteRepository.GetDtosByQuoteIdAsync(quoteId);
} }
public Task<DeliveryNoteCreateResponse> CreateAndIssueDeliveryNoteAsync(DeliveryNoteCreateRequest request)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.DeliveryNoteNumber))
throw new ArgumentException("El número de remito es obligatorio.", nameof(request.DeliveryNoteNumber));
if (request.CustomerId <= 0)
throw new ArgumentException("Debe seleccionar un cliente.", nameof(request.CustomerId));
if (request.IssueDate == default)
throw new ArgumentException("La fecha de emisión es obligatoria.", nameof(request.IssueDate));
if (request.Items is null || request.Items.Count == 0)
throw new InvalidOperationException("Debe incluir al menos un ítem.");
if (request.Items.Any(i => i.Quantity <= 0))
throw new InvalidOperationException("Todas las cantidades deben ser mayores a cero.");
if (request.Items.Any(i => string.IsNullOrWhiteSpace(i.Description)))
throw new InvalidOperationException("Todos los ítems deben incluir descripción.");
var entity = new EDeliveryNote
{
Deliverynotenumber = request.DeliveryNoteNumber.Trim(),
QuoteId = request.QuoteId,
Issuedate = request.IssueDate,
CustomerId = request.CustomerId,
Observations = request.Observations,
ExtrainfoJson = request.ExtraInfoJson
};
return Task.FromResult(new DeliveryNoteCreateResponse
{
Id = entity.Id,
DeliveryNoteNumber = entity.Deliverynotenumber
});
}
} }
} }

View File

@ -0,0 +1,12 @@
namespace Domain.Dtos.Sales
{
public class DeliveryNoteCreateItemRequest
{
public byte OriginType { get; set; }
public int? OriginId { get; set; }
public int? QuoteDetailId { get; set; }
public string Description { get; set; } = string.Empty;
public decimal Quantity { get; set; }
public string? Notes { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Domain.Dtos.Sales
{
public class DeliveryNoteCreateRequest
{
public string DeliveryNoteNumber { get; set; } = string.Empty;
public int? QuoteId { get; set; }
public DateTime IssueDate { get; set; }
public int CustomerId { get; set; }
public string? Observations { get; set; }
public string? ExtraInfoJson { get; set; }
public List<DeliveryNoteCreateItemRequest> Items { get; set; } = new();
}
}

View File

@ -0,0 +1,8 @@
namespace Domain.Dtos.Sales
{
public class DeliveryNoteCreateResponse
{
public int Id { get; set; }
public string DeliveryNoteNumber { get; set; } = string.Empty;
}
}

View File

@ -1,34 +1,31 @@
using System;
using System.Collections.Generic;
namespace Domain.Entities namespace Domain.Entities
{ {
public class EDeliveryNote public class EDeliveryNote
{ {
public int Id { get; set; } public int Id { get; set; }
public string DeliveryNoteNumber { get; set; } = string.Empty; public string Deliverynotenumber { get; set; } = null!;
public int? QuoteId { get; set; } public int? QuoteId { get; set; }
public int? SalesInvoiceId { get; set; } public int? SalesinvoiceId { get; set; }
public DateTime IssueDate { get; set; } public DateTime Issuedate { get; set; }
public int CustomerId { get; set; } public int CustomerId { get; set; }
public string Status { get; set; } = string.Empty; public string Status { get; set; } = null!;
public string? Observations { get; set; } public string? Observations { get; set; }
public string? ExtraInfoJson { get; set; } public string? ExtrainfoJson { get; set; }
public int PrintCount { get; set; } public int Printcount { get; set; }
public DateTime CreatedAt { get; set; } public DateTime Createdat { get; set; }
public DateTime? ModifiedAt { get; set; } public DateTime? Modifiedat { get; set; }
public List<EDeliveryNoteDetail> PhSDeliveryNoteDetails { get; set; } = new List<EDeliveryNoteDetail>(); public virtual ICollection<EDeliveryNoteDetail> PhSDeliveryNoteDetails { get; set; } = new List<EDeliveryNoteDetail>();
} }
} }

View File

@ -1,6 +1,6 @@
namespace Domain.Entities namespace Domain.Entities
{ {
public class EDeliveryNoteDetail public partial class EDeliveryNoteDetail
{ {
public int Id { get; set; } public int Id { get; set; }
@ -24,9 +24,9 @@
public DateTime? Modifiedat { get; set; } public DateTime? Modifiedat { get; set; }
//public virtual PhSDeliveryNote Deliverynote { get; set; } = null!; //public virtual EDeliveryNote Deliverynote { get; set; } = null!;
//public virtual PhSQuoteDetail? QuoteDetail { get; set; } //public virtual EQuoteDetail? QuoteDetail { get; set; }
} }
} }

View File

@ -1,4 +1,5 @@
using Domain.Dtos.Sales; using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics; using Domain.Generics;
namespace Models.Interfaces namespace Models.Interfaces
@ -20,5 +21,8 @@ namespace Models.Interfaces
Task<DeliveryNoteDto?> GetDtoByIdAsync(int id); Task<DeliveryNoteDto?> GetDtoByIdAsync(int id);
Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber); Task<DeliveryNoteDto?> GetDtoByDeliveryNoteNumberAsync(string deliveryNoteNumber);
Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId); Task<IEnumerable<DeliveryNoteDto>> GetDtosByQuoteIdAsync(int quoteId);
Task<bool> ExistsByDeliveryNoteNumberAsync(string deliveryNoteNumber);
Task<EDeliveryNote> CreateAsync(EDeliveryNote entity);
} }
} }

View File

@ -1,4 +1,5 @@
using Domain.Dtos.Sales; using Domain.Dtos.Sales;
using Domain.Entities;
using Domain.Generics; using Domain.Generics;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Models.Helpers; using Models.Helpers;
@ -115,6 +116,23 @@ namespace Models.Repositories
return entities.Select(MapDeliveryNoteDto); return entities.Select(MapDeliveryNoteDto);
} }
public async Task<bool> ExistsByDeliveryNoteNumberAsync(string deliveryNoteNumber)
{
return await _context.PhSDeliveryNotes
.AsNoTracking()
.AnyAsync(x => x.Deliverynotenumber == deliveryNoteNumber);
}
public async Task<EDeliveryNote> CreateAsync(EDeliveryNote entity)
{
var mapped = EntityMapper.MapEntity<EDeliveryNote, PhSDeliveryNote>(entity);
await _context.PhSDeliveryNotes.AddAsync(mapped);
await _context.SaveChangesAsync();
return EntityMapper.MapEntity<PhSDeliveryNote, EDeliveryNote>(mapped);
}
private static DeliveryNoteDto MapDeliveryNoteDto(PhSDeliveryNote source) private static DeliveryNoteDto MapDeliveryNoteDto(PhSDeliveryNote source)
{ {
return new DeliveryNoteDto return new DeliveryNoteDto

View File

@ -103,5 +103,28 @@ namespace phronCare.API.Controllers.Sales
return StatusCode(500, $"{methodName} Message: {ex.Message}"); return StatusCode(500, $"{methodName} Message: {ex.Message}");
} }
} }
[HttpPost("issue")]
public async Task<ActionResult<DeliveryNoteCreateResponse>> Issue([FromBody] DeliveryNoteCreateRequest request)
{
try
{
var result = await _deliveryNoteService.CreateAndIssueDeliveryNoteAsync(request);
return Ok(result);
}
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}");
}
}
} }
} }