Compare commits

..

2 Commits

Author SHA1 Message Date
587ef24b7d Merge pull request 'feat(sales): agregar servicio core de lectura para Delivery Note' (#18) from feature/leandro/17-delivery-note-core-read-service into master
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 2m0s
Reviewed-on: http://saludlab.com.ar:3000/leandro/phronCare/pulls/18
2026-03-19 04:39:48 +00:00
a498c38d84 feat(sales): agregar servicio core de lectura para Delivery Note
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 4m19s
Closes #17
2026-03-19 01:37:43 -03:00
7 changed files with 71 additions and 244 deletions

View File

@ -0,0 +1,36 @@
using Domain.Entities;
/// <summary>
/// Servicio de dominio para la gestión de consultas de Delivery Note (Remito Ventas).
/// Encapsula el acceso a datos y expone operaciones de lectura para la capa superior.
/// </summary>
public interface IDeliveryNoteDom
{
/// <summary>
/// Obtiene un Delivery Note por su identificador único.
/// </summary>
/// <param name="id">Identificador interno del Delivery Note.</param>
/// <returns>
/// La entidad <see cref="EDeliveryNote"/> si existe; en caso contrario, null.
/// </returns>
Task<EDeliveryNote?> GetByIdAsync(int id);
/// <summary>
/// Obtiene un Delivery Note a partir de su número de documento.
/// </summary>
/// <param name="deliveryNoteNumber">Número del Delivery Note (ej: DN-00000001).</param>
/// <returns>
/// La entidad <see cref="EDeliveryNote"/> si existe; en caso contrario, null.
/// </returns>
Task<EDeliveryNote?> GetByDeliveryNoteNumberAsync(string deliveryNoteNumber);
/// <summary>
/// Obtiene todos los Delivery Notes asociados a un presupuesto (Quote).
/// </summary>
/// <param name="quoteId">Identificador del presupuesto relacionado.</param>
/// <returns>
/// Colección de <see cref="EDeliveryNote"/> asociadas al presupuesto.
/// Puede estar vacía si no existen registros.
/// </returns>
Task<IEnumerable<EDeliveryNote>> GetByQuoteIdAsync(int quoteId);
}

View File

@ -0,0 +1,35 @@
using Core.Interfaces;
using Domain.Entities;
using Models.Interfaces;
namespace Core.Services
{
public class DeliveryNoteService(IPhSDeliveryNoteRepository deliveryNoteRepository) : IDeliveryNoteDom
{
private readonly IPhSDeliveryNoteRepository _deliveryNoteRepository = deliveryNoteRepository;
public Task<EDeliveryNote?> GetByIdAsync(int id)
{
if (id <= 0)
throw new ArgumentOutOfRangeException(nameof(id), "El identificador del remito es inválido.");
return _deliveryNoteRepository.GetByIdAsync(id);
}
public Task<EDeliveryNote?> GetByDeliveryNoteNumberAsync(string deliveryNoteNumber)
{
if (string.IsNullOrWhiteSpace(deliveryNoteNumber))
throw new ArgumentException("El número de remito es obligatorio.", nameof(deliveryNoteNumber));
return _deliveryNoteRepository.GetByDeliveryNoteNumberAsync(deliveryNoteNumber.Trim());
}
public Task<IEnumerable<EDeliveryNote>> GetByQuoteIdAsync(int quoteId)
{
if (quoteId <= 0)
throw new ArgumentOutOfRangeException(nameof(quoteId), "El identificador del presupuesto es inválido.");
return _deliveryNoteRepository.GetByQuoteIdAsync(quoteId);
}
}
}

View File

@ -1,44 +0,0 @@
USE [phronCare_OperationsHub]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Procedure: PhLSM_Expedition_CheckStockItemConflicts
-- Module: Logistics / Stock (PhLSM)
-- Purpose: Detect serialized stock items already assigned
-- to active expeditions.
-- Author: Leandro Rojas
-- Created: 2026-03-05
-- =============================================
ALTER PROCEDURE [dbo].[PhLSM_Expedition_CheckStockItemConflicts]
(
@StockItemIds dbo.PhLSM_StockItemIdList READONLY,
@IgnoreExpeditionId INT = NULL
)
AS
BEGIN
SET NOCOUNT ON;
SELECT
d.stockitem_id AS StockitemId,
h.id AS ExpeditionId,
h.expeditionnumber AS Expeditionnumber,
h.status AS Status
FROM dbo.PhLSM_ExpeditionDetails d
INNER JOIN @StockItemIds ids
ON ids.stockitem_id = d.stockitem_id
INNER JOIN dbo.PhLSM_ExpeditionHeaders h
ON h.id = d.expedition_id
INNER JOIN dbo.PhLSM_StockItem si
ON si.id = d.stockitem_id
WHERE h.status NOT IN (5,6) -- 5=Cerrada, 6=Anulada
AND (@IgnoreExpeditionId IS NULL OR h.id <> @IgnoreExpeditionId)
AND si.serial IS NOT NULL
AND LTRIM(RTRIM(si.serial)) <> '';
END
GO

View File

@ -1,5 +0,0 @@
CREATE TYPE dbo.PhLSM_StockItemIdList AS TABLE
(
stockitem_id INT NOT NULL
);
GO

View File

@ -1,36 +0,0 @@
USE [phronCare_OperationsHub]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Procedure: PhLSM_Stock_GetAvailabilityByStockItemIds
-- Module: Logistics / Stock (PhLSM)
-- Purpose: Returns quantity and availability data
-- for the requested stock items.
-- Author: Leandro Rojas
-- Created: 2026-03-09
-- =============================================
CREATE OR ALTER PROCEDURE [dbo].[PhLSM_Stock_GetAvailabilityByStockItemIds]
(
@StockItemIds dbo.PhLSM_StockItemIdList READONLY
)
AS
BEGIN
SET NOCOUNT ON;
SELECT
si.id AS StockitemId,
si.quantity AS Quantity,
ISNULL(si.reserved_quantity, 0) AS ReservedQuantity,
si.quantity - ISNULL(si.reserved_quantity, 0) AS AvailableQuantity,
si.serial AS Serial
FROM dbo.PhLSM_StockItem si
INNER JOIN @StockItemIds ids
ON ids.stockitem_id = si.id;
END
GO

View File

@ -1,135 +0,0 @@
/* =========================================================
VERIFICACIÓN STORY #9
Expedición -> EnTransito + reservas de stock
========================================================= */
SET NOCOUNT ON;
DECLARE @ExpeditionNumber VARCHAR(50) = 'X-00000054'; -- cambiar
DECLARE @ExpeditionId INT;
SELECT @ExpeditionId = id
FROM dbo.PhLSM_ExpeditionHeaders
WHERE expeditionnumber = @ExpeditionNumber;
IF @ExpeditionId IS NULL
BEGIN
RAISERROR('Expedición no encontrada',16,1);
RETURN;
END
PRINT 'ExpeditionId: ' + CAST(@ExpeditionId AS VARCHAR);
-------------------------------------------------------------
-- 1. CABECERA
-------------------------------------------------------------
PRINT '===== CABECERA =====';
SELECT
id,
expeditionnumber,
issuedate,
status,
observations
FROM dbo.PhLSM_ExpeditionHeaders
WHERE id = @ExpeditionId;
-------------------------------------------------------------
-- 2. DETALLES
-------------------------------------------------------------
PRINT '===== DETALLES =====';
SELECT
d.id,
d.expedition_id,
d.product_id,
d.stockitem_id,
d.quantity,
d.batch,
d.serial,
d.expiration
FROM dbo.PhLSM_ExpeditionDetails d
WHERE d.expedition_id = @ExpeditionId
ORDER BY d.id;
-------------------------------------------------------------
-- 3. RESERVAS CREADAS
-------------------------------------------------------------
PRINT '===== RESERVAS =====';
SELECT
r.id,
r.source_type,
r.source_id,
r.stockitem_id,
r.reserved_quantity,
r.status,
r.createdat
FROM dbo.PhLSM_StockReservation r
WHERE r.source_type = 1
AND r.source_id = @ExpeditionId
ORDER BY r.id;
-------------------------------------------------------------
-- 4. STOCK ITEMS AFECTADOS
-------------------------------------------------------------
PRINT '===== STOCK ITEMS =====';
SELECT
si.id,
si.product_id,
si.location_id,
si.quantity,
si.reserved_quantity,
(si.quantity - si.reserved_quantity) AS available
FROM dbo.PhLSM_StockItem si
WHERE si.id IN
(
SELECT stockitem_id
FROM dbo.PhLSM_ExpeditionDetails
WHERE expedition_id = @ExpeditionId
);
-------------------------------------------------------------
-- 5. VALIDACIÓN DETALLE VS RESERVA
-------------------------------------------------------------
PRINT '===== VALIDACION =====';
SELECT
d.id AS detail_id,
d.stockitem_id,
d.quantity AS requested,
r.reserved_quantity,
CASE
WHEN r.id IS NULL THEN 'FALTA RESERVA'
WHEN r.reserved_quantity <> d.quantity THEN 'CANTIDAD DISTINTA'
ELSE 'OK'
END AS resultado
FROM dbo.PhLSM_ExpeditionDetails d
LEFT JOIN dbo.PhLSM_StockReservation r
ON r.source_id = d.expedition_id
AND r.stockitem_id = d.stockitem_id
AND r.source_type = 1
AND r.status = 1
WHERE d.expedition_id = @ExpeditionId;
-------------------------------------------------------------
-- 6. CHEQUEO DE DUPLICADOS
-------------------------------------------------------------
PRINT '===== DUPLICADOS =====';
SELECT
stockitem_id,
COUNT(*) AS cantidad
FROM dbo.PhLSM_StockReservation
WHERE source_type = 1
AND source_id = @ExpeditionId
AND status = 1
GROUP BY stockitem_id
HAVING COUNT(*) > 1;

View File

@ -43,26 +43,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.5 Documents", "1.5 Docume
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Documents", "Documents\Documents.csproj", "{0EFF27D3-C585-49F3-BBB5-A5E99C52207B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.Database", "2.Database", "{E93C8350-6A6C-40F1-99C0-14CF7459EA8B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Migrations", "Migrations", "{5826BD19-0018-4F9C-B405-9BAB997CC8C7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Types", "Types", "{3C3276F6-7320-4AB8-9C1E-1893E4646FD4}"
ProjectSection(SolutionItems) = preProject
PhLSM_StockItemIdList.sql = PhLSM_StockItemIdList.sql
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Procedures", "Procedures", "{E4F7BA13-B838-42D5-AADD-481DD76DDD5E}"
ProjectSection(SolutionItems) = preProject
PhLSM_Expedition_CheckStockItemConflicts.sql = PhLSM_Expedition_CheckStockItemConflicts.sql
PhLSM_Stock_GetAvailabilityByStockItemIds.sql = PhLSM_Stock_GetAvailabilityByStockItemIds.sql
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Verifications", "Verifications", "{85918AF0-7D8E-41B6-9157-BD2ADF05BD64}"
ProjectSection(SolutionItems) = preProject
Story 9 - Stock Reservation.sql = Story 9 - Stock Reservation.sql
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -124,10 +104,6 @@ Global
{34FC5538-4779-41F5-8355-7866B1395A4F} = {13328F60-28A6-446D-9935-23866F3F3D9D}
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {74280C0B-F2CC-4A3E-86D6-05530F9766D5}
{0EFF27D3-C585-49F3-BBB5-A5E99C52207B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{5826BD19-0018-4F9C-B405-9BAB997CC8C7} = {E93C8350-6A6C-40F1-99C0-14CF7459EA8B}
{3C3276F6-7320-4AB8-9C1E-1893E4646FD4} = {E93C8350-6A6C-40F1-99C0-14CF7459EA8B}
{E4F7BA13-B838-42D5-AADD-481DD76DDD5E} = {E93C8350-6A6C-40F1-99C0-14CF7459EA8B}
{85918AF0-7D8E-41B6-9157-BD2ADF05BD64} = {E93C8350-6A6C-40F1-99C0-14CF7459EA8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {92DE3FEB-7D7E-4C78-BE8C-34931CA1DAED}