Add Stock Module MVP Divisiones
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 7m45s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 7m45s
This commit is contained in:
parent
ca229ed6cb
commit
85fedf79af
15
Core/Interfaces/Stock/IProductDivisionDom.cs
Normal file
15
Core/Interfaces/Stock/IProductDivisionDom.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
|
||||
namespace Core.Interfaces.Stock
|
||||
{
|
||||
public interface IProductDivisionDom
|
||||
{
|
||||
Task<PagedResult<EProductDivision>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||
Task<EProductDivision?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50);
|
||||
Task<EProductDivision> CreateAsync(EProductDivision entity);
|
||||
Task<bool> UpdateAsync(EProductDivision entity);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
}
|
||||
}
|
||||
100
Core/Services/Stock/ProductDivisionService.cs
Normal file
100
Core/Services/Stock/ProductDivisionService.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using Core.Interfaces;
|
||||
using Core.Interfaces.Stock;
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using Models.Interfaces;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Core.Services.Stock
|
||||
{
|
||||
public class ProductDivisionService : IProductDivisionDom
|
||||
{
|
||||
#region Declaraciones y Constructor
|
||||
private readonly IPhLSMProductDivisionRepository _repository;
|
||||
public ProductDivisionService(IPhLSMProductDivisionRepository repository)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Métodos de clase
|
||||
public async Task<PagedResult<EProductDivision>> GetAllAsync(int page = 1, int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetAllAsync(page, pageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<EProductDivision?> GetByIdAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PagedResult<EProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.SearchAsync(term, page, pageSize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<EProductDivision> CreateAsync(EProductDivision entity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.CreateAsync(entity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(EProductDivision entity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.UpdateAsync(entity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.DeleteAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
throw new Exception($"{method} Message: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
25
Domain/Entities/EProductDivision.cs
Normal file
25
Domain/Entities/EProductDivision.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Domain.Entities
|
||||
{
|
||||
public class EProductDivision
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único de la división de productos
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código breve de la división, coincide con la línea original (ej: TCLO, CMF_J)
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nombre de la división o familia técnica de productos (ej: columna, trauma, descartables, etc.)
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Descripción adicional de la división de productos (opcional)
|
||||
/// </summary>
|
||||
public string? Description { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
7
Domain/Generics/ProductDivisionSearchParams.cs
Normal file
7
Domain/Generics/ProductDivisionSearchParams.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Domain.Generics
|
||||
{
|
||||
public class ProductDivisionSearchParams : PagedRequest
|
||||
{
|
||||
public string? Term { get; set; }
|
||||
}
|
||||
}
|
||||
15
Models/Interfaces/IPhLSMProductDivisionRepository.cs
Normal file
15
Models/Interfaces/IPhLSMProductDivisionRepository.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
|
||||
namespace Models.Interfaces
|
||||
{
|
||||
public interface IPhLSMProductDivisionRepository
|
||||
{
|
||||
Task<EProductDivision> CreateAsync(EProductDivision entity);
|
||||
Task<bool> DeleteAsync(int id);
|
||||
Task<PagedResult<EProductDivision>> GetAllAsync(int page = 1, int pageSize = 50);
|
||||
Task<EProductDivision?> GetByIdAsync(int id);
|
||||
Task<PagedResult<EProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50);
|
||||
Task<bool> UpdateAsync(EProductDivision entity);
|
||||
}
|
||||
}
|
||||
67
Models/Models/PhLsmProduct.cs
Normal file
67
Models/Models/PhLsmProduct.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmProduct
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del producto médico o industrial
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código de producto definido por la fábrica o fabricante. Puede variar según proveedor, presentación o país de origen.
|
||||
/// </summary>
|
||||
public string FactoryCode { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Nombre técnico o estandarizado del producto (ej: ficha técnica, fabricante)
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descripción comercial o práctica del producto (impresión logística, uso cotidiano)
|
||||
/// </summary>
|
||||
public string Descripcion { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tipo de producto: 1=Implantable, 2=Instrumental, 3=Inyectable, etc.
|
||||
/// </summary>
|
||||
public int ProductType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tipo de trazabilidad: 1=No aplica, 2=Por cantidad, 3=Por lote y vencimiento
|
||||
/// </summary>
|
||||
public int TraceabilityType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica si el producto requiere un proceso adicional previo a su uso (ej: esterilización, calibración, limpieza, inspección, etc.)
|
||||
/// </summary>
|
||||
public bool PlusProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código externo estándar del producto (ej: GTIN, código de proveedor, catálogo EAN, etc.)
|
||||
/// </summary>
|
||||
public string? ExternalCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// División o familia técnica del producto (ej: columna, trauma, descartables, etc.)
|
||||
/// </summary>
|
||||
public int? DivisionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unidad de medida base del producto (ej: unidad, mililitro, metro)
|
||||
/// </summary>
|
||||
public int UnitId { get; set; }
|
||||
|
||||
public virtual PhLsmProductDivision? Division { get; set; }
|
||||
|
||||
public virtual ICollection<PhLsmStockEntry> PhLsmStockEntries { get; set; } = new List<PhLsmStockEntry>();
|
||||
|
||||
public virtual ICollection<PhLsmStockItem> PhLsmStockItems { get; set; } = new List<PhLsmStockItem>();
|
||||
|
||||
public virtual ICollection<PhLsmStockOut> PhLsmStockOuts { get; set; } = new List<PhLsmStockOut>();
|
||||
|
||||
public virtual PhLsmUnitOfMeasure Unit { get; set; } = null!;
|
||||
}
|
||||
29
Models/Models/PhLsmProductDivision.cs
Normal file
29
Models/Models/PhLsmProductDivision.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmProductDivision
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único de la división de productos
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código breve de la división, coincide con la línea original (ej: TCLO, CMF_J)
|
||||
/// </summary>
|
||||
public string Code { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Nombre de la división o familia técnica de productos (ej: columna, trauma, descartables, etc.)
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Descripción adicional de la división de productos (opcional)
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
public virtual ICollection<PhLsmProduct> PhLsmProducts { get; set; } = new List<PhLsmProduct>();
|
||||
}
|
||||
62
Models/Models/PhLsmStockEntry.cs
Normal file
62
Models/Models/PhLsmStockEntry.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Registro individual de ingreso de stock con valor y trazabilidad
|
||||
/// </summary>
|
||||
public partial class PhLsmStockEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del ingreso de stock
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referencia al producto ingresado
|
||||
/// </summary>
|
||||
public int ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad ingresada en la unidad correspondiente del producto
|
||||
/// </summary>
|
||||
public double Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Precio unitario del producto en la moneda indicada
|
||||
/// </summary>
|
||||
public double Unitprice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Moneda del ingreso (ej: ars, usd, eur)
|
||||
/// </summary>
|
||||
public string Currency { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tipo de cambio aplicado respecto a ARS
|
||||
/// </summary>
|
||||
public double Exchangerate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha y hora del ingreso físico al stock
|
||||
/// </summary>
|
||||
public DateTime Entrydate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referencia externa: número de remito, factura o documento de ingreso
|
||||
/// </summary>
|
||||
public string? Reference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tipo de origen del ingreso (ej: compra, devolución, ajuste)
|
||||
/// </summary>
|
||||
public string? Sourcetype { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Identificador interno del documento de origen (opcional)
|
||||
/// </summary>
|
||||
public int? SourceId { get; set; }
|
||||
|
||||
public virtual PhLsmProduct Product { get; set; } = null!;
|
||||
}
|
||||
51
Models/Models/PhLsmStockItem.cs
Normal file
51
Models/Models/PhLsmStockItem.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmStockItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del ítem de stock físico
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Producto vinculado al ítem de stock
|
||||
/// </summary>
|
||||
public int ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ubicación física del stock (depósito, valija, etc.)
|
||||
/// </summary>
|
||||
public int LocationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad actual disponible en esta unidad de stock
|
||||
/// </summary>
|
||||
public double Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código de lote (si aplica)
|
||||
/// </summary>
|
||||
public string? Batch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha de vencimiento (si aplica)
|
||||
/// </summary>
|
||||
public DateOnly? Expiration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Estado del ítem (1=Disponible, 2=Reservado, 3=Vencido, etc.)
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Comentario libre u observación sobre este ítem de stock
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
public virtual PhLsmStockLocation Location { get; set; } = null!;
|
||||
|
||||
public virtual PhLsmProduct Product { get; set; } = null!;
|
||||
}
|
||||
24
Models/Models/PhLsmStockLocation.cs
Normal file
24
Models/Models/PhLsmStockLocation.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmStockLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único de la ubicación de stock
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nombre visible de la ubicación (ej: Depósito Central, Cuarentena, Caja A1)
|
||||
/// </summary>
|
||||
public string Nombre { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Descripción o comentario adicional sobre la ubicación
|
||||
/// </summary>
|
||||
public string? Descripcion { get; set; }
|
||||
|
||||
public virtual ICollection<PhLsmStockItem> PhLsmStockItems { get; set; } = new List<PhLsmStockItem>();
|
||||
}
|
||||
59
Models/Models/PhLsmStockOut.cs
Normal file
59
Models/Models/PhLsmStockOut.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmStockOut
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del egreso de stock
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Producto retirado del stock
|
||||
/// </summary>
|
||||
public int ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad retirada del producto
|
||||
/// </summary>
|
||||
public double Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Precio unitario usado para valorizar el egreso
|
||||
/// </summary>
|
||||
public double Unitprice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Moneda utilizada en la valorización (ars, usd, eur)
|
||||
/// </summary>
|
||||
public string Currency { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Tasa de conversión de la moneda a ARS
|
||||
/// </summary>
|
||||
public double Exchangerate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha del egreso de stock
|
||||
/// </summary>
|
||||
public DateTime Outdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Referencia visible del movimiento (NE, devolución, cirugía)
|
||||
/// </summary>
|
||||
public string? Reference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tipo de origen del egreso (surgery, expiration, manual, etc.)
|
||||
/// </summary>
|
||||
public string? Sourcetype { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID de la entidad que generó el egreso (ej: nota de expedición)
|
||||
/// </summary>
|
||||
public int? SourceId { get; set; }
|
||||
|
||||
public virtual PhLsmProduct Product { get; set; } = null!;
|
||||
}
|
||||
26
Models/Models/PhLsmUnitOfMeasure.cs
Normal file
26
Models/Models/PhLsmUnitOfMeasure.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Models.Models;
|
||||
|
||||
public partial class PhLsmUnitOfMeasure
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Código abreviado de unidad de medida (ej: UN, ML, MT)
|
||||
/// </summary>
|
||||
public string Code { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Nombre descriptivo de la unidad de medida
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Descripción extendida o notas adicionales sobre la unidad
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
public virtual ICollection<PhLsmProduct> PhLsmProducts { get; set; } = new List<PhLsmProduct>();
|
||||
}
|
||||
@ -15,6 +15,20 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<PhLsmProduct> PhLsmProducts { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmProductDivision> PhLsmProductDivisions { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmStockEntry> PhLsmStockEntries { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmStockItem> PhLsmStockItems { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmStockLocation> PhLsmStockLocations { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmStockOut> PhLsmStockOuts { get; set; }
|
||||
|
||||
public virtual DbSet<PhLsmUnitOfMeasure> PhLsmUnitOfMeasures { get; set; }
|
||||
|
||||
public virtual DbSet<PhOhArcadocumentType> PhOhArcadocumentTypes { get; set; }
|
||||
|
||||
public virtual DbSet<PhOhArcataxType> PhOhArcataxTypes { get; set; }
|
||||
@ -74,13 +88,277 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
public virtual DbSet<PhSQuoteTaxis> PhSQuoteTaxes { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
|
||||
=> optionsBuilder.UseSqlServer("data source=srv01.saludlab.com.ar,39458;initial catalog=phronCare_OperationsHub;User ID=sa;Password=HS|s[~xxQzTo/n>9jO;encrypt=False;trustServerCertificate=True;MultipleActiveResultSets=True");
|
||||
#region VERSION DOCKER
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
{
|
||||
// Dejarlo vacío para usar la configuración externa desde Program.cs o Startup.cs
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
//=> optionsBuilder.UseSqlServer("data source=srv01.saludlab.com.ar,39458;initial catalog=phronCare_OperationsHub;User ID=sa;Password=HS|s[~xxQzTo/n>9jO;encrypt=False;trustServerCertificate=True;MultipleActiveResultSets=True");
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.UseCollation("Modern_Spanish_CI_AS");
|
||||
|
||||
modelBuilder.Entity<PhLsmProduct>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_Pr__3213E83F874510C5");
|
||||
|
||||
entity.ToTable("PhLSM_Product");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único del producto médico o industrial")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Descripcion)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Descripción comercial o práctica del producto (impresión logística, uso cotidiano)")
|
||||
.HasColumnName("descripcion");
|
||||
entity.Property(e => e.DivisionId)
|
||||
.HasComment("División o familia técnica del producto (ej: columna, trauma, descartables, etc.)")
|
||||
.HasColumnName("division_id");
|
||||
entity.Property(e => e.ExternalCode)
|
||||
.HasMaxLength(50)
|
||||
.HasComment("Código externo estándar del producto (ej: GTIN, código de proveedor, catálogo EAN, etc.)")
|
||||
.HasColumnName("external_code");
|
||||
entity.Property(e => e.FactoryCode)
|
||||
.HasMaxLength(50)
|
||||
.HasComment("Código de producto definido por la fábrica o fabricante. Puede variar según proveedor, presentación o país de origen.")
|
||||
.HasColumnName("factory_code");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Nombre técnico o estandarizado del producto (ej: ficha técnica, fabricante)")
|
||||
.HasColumnName("name");
|
||||
entity.Property(e => e.PlusProcess)
|
||||
.HasComment("Indica si el producto requiere un proceso adicional previo a su uso (ej: esterilización, calibración, limpieza, inspección, etc.)")
|
||||
.HasColumnName("plus_process");
|
||||
entity.Property(e => e.ProductType)
|
||||
.HasComment("Tipo de producto: 1=Implantable, 2=Instrumental, 3=Inyectable, etc.")
|
||||
.HasColumnName("product_type");
|
||||
entity.Property(e => e.TraceabilityType)
|
||||
.HasComment("Tipo de trazabilidad: 1=No aplica, 2=Por cantidad, 3=Por lote y vencimiento")
|
||||
.HasColumnName("traceability_type");
|
||||
entity.Property(e => e.UnitId)
|
||||
.HasComment("Unidad de medida base del producto (ej: unidad, mililitro, metro)")
|
||||
.HasColumnName("unit_id");
|
||||
|
||||
entity.HasOne(d => d.Division).WithMany(p => p.PhLsmProducts)
|
||||
.HasForeignKey(d => d.DivisionId)
|
||||
.HasConstraintName("FK__PhLSM_Pro__divis__2EFAF1E2");
|
||||
|
||||
entity.HasOne(d => d.Unit).WithMany(p => p.PhLsmProducts)
|
||||
.HasForeignKey(d => d.UnitId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("FK_PhLSM_Product_Unit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmProductDivision>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_Pr__3213E83F27FDF540");
|
||||
|
||||
entity.ToTable("PhLSM_ProductDivision");
|
||||
|
||||
entity.HasIndex(e => e.Code, "UQ_PhLSM_ProductDivision_code").IsUnique();
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único de la división de productos")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Code)
|
||||
.HasMaxLength(50)
|
||||
.HasDefaultValue("")
|
||||
.HasComment("Código breve de la división, coincide con la línea original (ej: TCLO, CMF_J)")
|
||||
.HasColumnName("code");
|
||||
entity.Property(e => e.Description)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Descripción adicional de la división de productos (opcional)")
|
||||
.HasColumnName("description");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(100)
|
||||
.HasComment("Nombre de la división o familia técnica de productos (ej: columna, trauma, descartables, etc.)")
|
||||
.HasColumnName("name");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmStockEntry>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_St__3213E83FCFAF3A63");
|
||||
|
||||
entity.ToTable("PhLSM_StockEntry", tb => tb.HasComment("Registro individual de ingreso de stock con valor y trazabilidad"));
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único del ingreso de stock")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Currency)
|
||||
.HasMaxLength(3)
|
||||
.HasComment("Moneda del ingreso (ej: ars, usd, eur)")
|
||||
.HasColumnName("currency");
|
||||
entity.Property(e => e.Entrydate)
|
||||
.HasComment("Fecha y hora del ingreso físico al stock")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("entrydate");
|
||||
entity.Property(e => e.Exchangerate)
|
||||
.HasDefaultValue(1.0)
|
||||
.HasComment("Tipo de cambio aplicado respecto a ARS")
|
||||
.HasColumnName("exchangerate");
|
||||
entity.Property(e => e.ProductId)
|
||||
.HasComment("Referencia al producto ingresado")
|
||||
.HasColumnName("product_id");
|
||||
entity.Property(e => e.Quantity)
|
||||
.HasComment("Cantidad ingresada en la unidad correspondiente del producto")
|
||||
.HasColumnName("quantity");
|
||||
entity.Property(e => e.Reference)
|
||||
.HasMaxLength(100)
|
||||
.HasComment("Referencia externa: número de remito, factura o documento de ingreso")
|
||||
.HasColumnName("reference");
|
||||
entity.Property(e => e.SourceId)
|
||||
.HasComment("Identificador interno del documento de origen (opcional)")
|
||||
.HasColumnName("source_id");
|
||||
entity.Property(e => e.Sourcetype)
|
||||
.HasMaxLength(50)
|
||||
.HasComment("Tipo de origen del ingreso (ej: compra, devolución, ajuste)")
|
||||
.HasColumnName("sourcetype");
|
||||
entity.Property(e => e.Unitprice)
|
||||
.HasComment("Precio unitario del producto en la moneda indicada")
|
||||
.HasColumnName("unitprice");
|
||||
|
||||
entity.HasOne(d => d.Product).WithMany(p => p.PhLsmStockEntries)
|
||||
.HasForeignKey(d => d.ProductId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("FK__PhLSM_Sto__produ__40257DE4");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmStockItem>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_St__3213E83F7C7F442D");
|
||||
|
||||
entity.ToTable("PhLSM_StockItem");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único del ítem de stock físico")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Batch)
|
||||
.HasMaxLength(100)
|
||||
.HasComment("Código de lote (si aplica)")
|
||||
.HasColumnName("batch");
|
||||
entity.Property(e => e.Description)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Comentario libre u observación sobre este ítem de stock")
|
||||
.HasColumnName("description");
|
||||
entity.Property(e => e.Expiration)
|
||||
.HasComment("Fecha de vencimiento (si aplica)")
|
||||
.HasColumnName("expiration");
|
||||
entity.Property(e => e.LocationId)
|
||||
.HasComment("Ubicación física del stock (depósito, valija, etc.)")
|
||||
.HasColumnName("location_id");
|
||||
entity.Property(e => e.ProductId)
|
||||
.HasComment("Producto vinculado al ítem de stock")
|
||||
.HasColumnName("product_id");
|
||||
entity.Property(e => e.Quantity)
|
||||
.HasComment("Cantidad actual disponible en esta unidad de stock")
|
||||
.HasColumnName("quantity");
|
||||
entity.Property(e => e.Status)
|
||||
.HasComment("Estado del ítem (1=Disponible, 2=Reservado, 3=Vencido, etc.)")
|
||||
.HasColumnName("status");
|
||||
|
||||
entity.HasOne(d => d.Location).WithMany(p => p.PhLsmStockItems)
|
||||
.HasForeignKey(d => d.LocationId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("FK__PhLSM_Sto__locat__3C54ED00");
|
||||
|
||||
entity.HasOne(d => d.Product).WithMany(p => p.PhLsmStockItems)
|
||||
.HasForeignKey(d => d.ProductId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("FK__PhLSM_Sto__produ__3B60C8C7");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmStockLocation>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_St__3213E83F68EA6A9A");
|
||||
|
||||
entity.ToTable("PhLSM_StockLocation");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único de la ubicación de stock")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Descripcion)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Descripción o comentario adicional sobre la ubicación")
|
||||
.HasColumnName("descripcion");
|
||||
entity.Property(e => e.Nombre)
|
||||
.HasMaxLength(100)
|
||||
.HasComment("Nombre visible de la ubicación (ej: Depósito Central, Cuarentena, Caja A1)")
|
||||
.HasColumnName("nombre");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmStockOut>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_St__3213E83F96B2D858");
|
||||
|
||||
entity.ToTable("PhLSM_StockOut");
|
||||
|
||||
entity.Property(e => e.Id)
|
||||
.HasComment("Identificador único del egreso de stock")
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Currency)
|
||||
.HasMaxLength(3)
|
||||
.HasComment("Moneda utilizada en la valorización (ars, usd, eur)")
|
||||
.HasColumnName("currency");
|
||||
entity.Property(e => e.Exchangerate)
|
||||
.HasDefaultValue(1.0)
|
||||
.HasComment("Tasa de conversión de la moneda a ARS")
|
||||
.HasColumnName("exchangerate");
|
||||
entity.Property(e => e.Outdate)
|
||||
.HasComment("Fecha del egreso de stock")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("outdate");
|
||||
entity.Property(e => e.ProductId)
|
||||
.HasComment("Producto retirado del stock")
|
||||
.HasColumnName("product_id");
|
||||
entity.Property(e => e.Quantity)
|
||||
.HasComment("Cantidad retirada del producto")
|
||||
.HasColumnName("quantity");
|
||||
entity.Property(e => e.Reference)
|
||||
.HasMaxLength(100)
|
||||
.HasComment("Referencia visible del movimiento (NE, devolución, cirugía)")
|
||||
.HasColumnName("reference");
|
||||
entity.Property(e => e.SourceId)
|
||||
.HasComment("ID de la entidad que generó el egreso (ej: nota de expedición)")
|
||||
.HasColumnName("source_id");
|
||||
entity.Property(e => e.Sourcetype)
|
||||
.HasMaxLength(50)
|
||||
.HasComment("Tipo de origen del egreso (surgery, expiration, manual, etc.)")
|
||||
.HasColumnName("sourcetype");
|
||||
entity.Property(e => e.Unitprice)
|
||||
.HasComment("Precio unitario usado para valorizar el egreso")
|
||||
.HasColumnName("unitprice");
|
||||
|
||||
entity.HasOne(d => d.Product).WithMany(p => p.PhLsmStockOuts)
|
||||
.HasForeignKey(d => d.ProductId)
|
||||
.OnDelete(DeleteBehavior.ClientSetNull)
|
||||
.HasConstraintName("FK__PhLSM_Sto__produ__43F60EC8");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhLsmUnitOfMeasure>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhLSM_Un__3213E83FD70349B6");
|
||||
|
||||
entity.ToTable("PhLSM_UnitOfMeasure");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("id");
|
||||
entity.Property(e => e.Code)
|
||||
.HasMaxLength(10)
|
||||
.HasComment("Código abreviado de unidad de medida (ej: UN, ML, MT)")
|
||||
.HasColumnName("code");
|
||||
entity.Property(e => e.Description)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("Descripción extendida o notas adicionales sobre la unidad")
|
||||
.HasColumnName("description");
|
||||
entity.Property(e => e.Name)
|
||||
.HasMaxLength(50)
|
||||
.HasComment("Nombre descriptivo de la unidad de medida")
|
||||
.HasColumnName("name");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<PhOhArcadocumentType>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__PhOH_ARC__3213E83FF8940395");
|
||||
@ -939,6 +1217,7 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
.HasColumnName("id");
|
||||
entity.Property(e => e.Approvaldate)
|
||||
.HasComment("Fecha de aprobación")
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("approvaldate");
|
||||
entity.Property(e => e.Approvedamount)
|
||||
.HasComment("Importe aprobado")
|
||||
|
||||
108
Models/Repositories/Stock/PhLSMProductDivisionRepository.cs
Normal file
108
Models/Repositories/Stock/PhLSMProductDivisionRepository.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Models.Helpers;
|
||||
using Models.Interfaces;
|
||||
using Models.Models;
|
||||
|
||||
namespace Models.Repositories.Stock
|
||||
{
|
||||
public class PhLSMProductDivisionRepository(PhronCareOperationsHubContext context) : IPhLSMProductDivisionRepository
|
||||
{
|
||||
private readonly PhronCareOperationsHubContext _context = context;
|
||||
|
||||
public async Task<PagedResult<EProductDivision>> GetAllAsync(int page = 1, int pageSize = 50)
|
||||
{
|
||||
var query = _context.PhLsmProductDivisions.AsQueryable();
|
||||
|
||||
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
|
||||
|
||||
return new PagedResult<EProductDivision>
|
||||
{
|
||||
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhLsmProductDivision, EProductDivision>),
|
||||
TotalItems = pagedEntities.TotalItems,
|
||||
Page = pagedEntities.Page,
|
||||
PageSize = pagedEntities.PageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<EProductDivision?> GetByIdAsync(int id)
|
||||
{
|
||||
var entity = await _context.PhLsmProductDivisions.FirstOrDefaultAsync(x => x.Id == id);
|
||||
return entity is null ? null : EntityMapper.MapEntity<PhLsmProductDivision, EProductDivision>(entity);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<EProductDivision>> SearchAsync(string? term, int page = 1, int pageSize = 50)
|
||||
{
|
||||
var query = _context.PhLsmProductDivisions.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
term = term.ToLower();
|
||||
query = query.Where(x => x.Description.ToLower().Contains(term));
|
||||
}
|
||||
|
||||
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
|
||||
|
||||
return new PagedResult<EProductDivision>
|
||||
{
|
||||
Items = pagedEntities.Items.Select(EntityMapper.MapEntity<PhLsmProductDivision, EProductDivision>),
|
||||
TotalItems = pagedEntities.TotalItems,
|
||||
Page = pagedEntities.Page,
|
||||
PageSize = pagedEntities.PageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<EProductDivision> CreateAsync(EProductDivision entity)
|
||||
{
|
||||
if (entity == null)
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
|
||||
try
|
||||
{
|
||||
var mapped = EntityMapper.MapEntity<EProductDivision, PhLsmProductDivision>(entity);
|
||||
_context.PhLsmProductDivisions.Add(mapped);
|
||||
await _context.SaveChangesAsync();
|
||||
return EntityMapper.MapEntity<PhLsmProductDivision, EProductDivision>(mapped);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
throw new Exception("Error al guardar la división de producto. Verificá integridad de datos.", dbEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Error inesperado al crear la división: " + ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(EProductDivision entity)
|
||||
{
|
||||
if (entity == null)
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
|
||||
try
|
||||
{
|
||||
var existing = await _context.PhLsmProductDivisions.FirstOrDefaultAsync(x => x.Id == entity.Id);
|
||||
if (existing == null) return false;
|
||||
|
||||
EntityMapper.MapEntityToExisting(entity, existing);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int id)
|
||||
{
|
||||
var existing = await _context.PhLsmProductDivisions.FindAsync(id);
|
||||
if (existing == null) return false;
|
||||
|
||||
_context.PhLsmProductDivisions.Remove(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
phronCare.API/Controllers/Stock/ProductDivisionController.cs
Normal file
98
phronCare.API/Controllers/Stock/ProductDivisionController.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Core.Interfaces.Stock;
|
||||
using Domain.Entities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace phronCare.API.Controllers.Stock
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ProductDivisionController : ControllerBase
|
||||
{
|
||||
private readonly IProductDivisionDom _service;
|
||||
|
||||
public ProductDivisionController(IProductDivisionDom service)
|
||||
{
|
||||
_service = service ?? throw new ArgumentNullException(nameof(service));
|
||||
}
|
||||
|
||||
[HttpGet("GetAll")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _service.GetAllAsync(page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetById/{id}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var result = await _service.GetByIdAsync(id);
|
||||
if (result == null)
|
||||
return NotFound($"No se encontró una división con ID {id}.");
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("Search")]
|
||||
public async Task<IActionResult> Search([FromQuery] string? term, [FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _service.SearchAsync(term, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create([FromBody] EProductDivision division)
|
||||
{
|
||||
try
|
||||
{
|
||||
var created = await _service.CreateAsync(division);
|
||||
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("Update")]
|
||||
public async Task<IActionResult> Update([FromBody] EProductDivision division)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await _service.UpdateAsync(division);
|
||||
return updated ? Ok() : NotFound($"No se encontró una división con ID {division.Id}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("Delete/{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleted = await _service.DeleteAsync(id);
|
||||
return deleted ? Ok() : NotFound($"No se encontró una división con ID {id}.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,25 +1,28 @@
|
||||
using Core.Interfaces;
|
||||
using Core.Interfaces.Stock;
|
||||
using Core.Services;
|
||||
using Core.Services.Stock;
|
||||
using Documents.Interfaces;
|
||||
using Documents.Services;
|
||||
using Google.Authenticator;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Models.Interfaces;
|
||||
using Services.Interfaces;
|
||||
using Services.Services;
|
||||
using Services.Models;
|
||||
using System.Text;
|
||||
using phronCare.API.Models;
|
||||
using Core.Interfaces;
|
||||
using Core.Services;
|
||||
using phronCare.API.Models.Security;
|
||||
using Models.Repositories;
|
||||
using Models.Models;
|
||||
using Models.Repositories;
|
||||
using Models.Repositories.Stock;
|
||||
using phronCare.API.Models;
|
||||
using phronCare.API.Models.Security;
|
||||
using Services.Interfaces;
|
||||
using Services.Models;
|
||||
using Services.Services;
|
||||
using System.Text;
|
||||
using Transversal.Interfaces;
|
||||
using Transversal.Services;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Documents.Interfaces;
|
||||
using Documents.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@ -253,5 +256,8 @@ static void RepositorysAndServices(WebApplicationBuilder builder)
|
||||
{
|
||||
client.BaseAddress = new Uri("https://api.bcra.gob.ar/");
|
||||
});
|
||||
//Core de Divisiones de Productos
|
||||
builder.Services.AddScoped<IProductDivisionDom, ProductDivisionService>();
|
||||
builder.Services.AddScoped<IPhLSMProductDivisionRepository, PhLSMProductDivisionRepository>();
|
||||
|
||||
}
|
||||
@ -1449,6 +1449,117 @@
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "Create",
|
||||
"RelativePath": "api/ProductDivision/Create",
|
||||
"HttpMethod": "POST",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "division",
|
||||
"Type": "Domain.Entities.EProductDivision",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "Delete",
|
||||
"RelativePath": "api/ProductDivision/Delete/{id}",
|
||||
"HttpMethod": "DELETE",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "GetAll",
|
||||
"RelativePath": "api/ProductDivision/GetAll",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "page",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "pageSize",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": false
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "GetById",
|
||||
"RelativePath": "api/ProductDivision/GetById/{id}",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "Search",
|
||||
"RelativePath": "api/ProductDivision/Search",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "term",
|
||||
"Type": "System.String",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "page",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Name": "pageSize",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": false
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Stock.ProductDivisionController",
|
||||
"Method": "Update",
|
||||
"RelativePath": "api/ProductDivision/Update",
|
||||
"HttpMethod": "PUT",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "division",
|
||||
"Type": "Domain.Entities.EProductDivision",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.ProfessionalController",
|
||||
"Method": "GetById",
|
||||
|
||||
@ -46,6 +46,64 @@
|
||||
</NavLink>
|
||||
</div>
|
||||
</AuthorizeView>
|
||||
<div class="nav-item px-2 py-0">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="() => expStock = !expStock">
|
||||
<span class="oi oi-box" aria-hidden="true"></span>
|
||||
@if (!navMenuService.Minimized)
|
||||
{
|
||||
<label class="mb-0">Stock</label>
|
||||
}
|
||||
</NavLink>
|
||||
|
||||
@if (expStock)
|
||||
{
|
||||
<ul class="nav flex-column">
|
||||
<!-- Productos -->
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/products" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-tags me-2 text-warning"></span> Productos
|
||||
</NavLink>
|
||||
</div>
|
||||
<!-- Divisiones de productos -->
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/productdivisions" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-grid-three-up me-2 text-info"></span> Divisiones
|
||||
</NavLink>
|
||||
</div>
|
||||
<!-- Unidades de medida -->
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/units" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-resize-both me-2 text-success"></span> Unidades
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/locations" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-map-marker me-2 text-success"></span> Ubicaciones
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/current" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-inbox me-2 text-info"></span> Stock actual
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/movements" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-loop-circular me-2 text-primary"></span> Movimientos
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/expeditions" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-data-transfer-download me-2 text-danger"></span> Expediciones
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item ps-4 py-0 border-start border-2 border-white">
|
||||
<NavLink class="nav-link small py-0 px-2 text-start d-flex align-items-center" href="stock/consumptions" activeClass="bg-secondary text-white fw-semibold">
|
||||
<span class="oi oi-check me-2 text-secondary"></span> Consumos
|
||||
</NavLink>
|
||||
</div>
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-2 py-0">
|
||||
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" @onclick="() => expClientes = !expClientes">
|
||||
@ -113,6 +171,7 @@
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
private bool minimizeNavMenu = false;
|
||||
private bool expStock = true;
|
||||
private bool expClientes = true;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
149
phronCare.UIBlazor/Pages/Stock/ProductDivision.razor
Normal file
149
phronCare.UIBlazor/Pages/Stock/ProductDivision.razor
Normal file
@ -0,0 +1,149 @@
|
||||
@page "/stock/productdivisions"
|
||||
@using phronCare.UIBlazor.Services.Stock
|
||||
@using Domain.Entities
|
||||
@using Domain.Generics
|
||||
@inject IToastService toastService
|
||||
@inject NavigationManager Navigation
|
||||
@inject ProductDivisionService productDivisionService
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-center align-items-center" style="zoom:80%;">
|
||||
<h3 class="card-title m-0">Divisiones de productos</h3>
|
||||
</div>
|
||||
<div class="card-body" style="zoom:80%;">
|
||||
<div class="mb-4 space-y-2">
|
||||
<input @bind="SearchParams.Term" placeholder="Descripción" class="border rounded p-1 w-full" />
|
||||
<button class="btn btn-primary rounded-pill" @onclick="Buscar">
|
||||
<i class="fas fa-search me-1"></i> Buscar
|
||||
</button>
|
||||
<button class="btn btn-success rounded-pill" @onclick="Nuevo">
|
||||
<i class="fas fa-plus me-1"></i> Nuevo
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="Volver">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<div style="zoom:90%;">
|
||||
@if (Tabla != null && Tabla.Any())
|
||||
{
|
||||
<PhTable Columns="Columnas"
|
||||
Data="Tabla"
|
||||
SelectionField="Id"
|
||||
RowsPerPage=SearchParams.PageSize
|
||||
RenderButtons="true"
|
||||
Buttons="Botones"
|
||||
ShowPageButtons="false"
|
||||
ShowQuickSearch="false"
|
||||
RenderSelect="false" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>No hay resultados.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-center align-items-center" style="zoom:80%;">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="PrimeraPagina" disabled="@(SearchParams.Page == 1)">
|
||||
<i class="fas fa-angle-double-left me-1"></i> Primera
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="AnteriorPagina" disabled="@(!PuedeRetroceder)">
|
||||
<i class="fas fa-chevron-left me-1"></i> Anterior
|
||||
</button>
|
||||
<span class="mx-2">
|
||||
Página <strong>@SearchParams.Page</strong> de <strong>@TotalPaginas</strong>
|
||||
</span>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="SiguientePagina" disabled="@(!PuedeAvanzar)">
|
||||
Siguiente <i class="fas fa-chevron-right ms-1"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary rounded-pill" @onclick="UltimaPagina" disabled="@(SearchParams.Page == TotalPaginas)">
|
||||
Última <i class="fas fa-angle-double-right ms-1"></i>
|
||||
</button>
|
||||
<div class="d-flex align-items-center ms-3">
|
||||
<input type="number" class="form-control form-control-sm rounded" style="width: 80px;" min="1" max="@TotalPaginas" @bind="PaginaDeseada" />
|
||||
<button class="btn btn-outline-primary btn-sm ms-2 rounded-pill" @onclick="IrAPagina">
|
||||
<i class="fas fa-arrow-right-to-bracket me-1"></i> Ir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private PagedResult<EProductDivision>? Resultado;
|
||||
private List<Dictionary<string, object>> Tabla = new();
|
||||
private List<string> Columnas = new() { "Id", "Codigo", "Nombre", "Descripción" };
|
||||
private ProductDivisionSearchParams SearchParams = new() { PageSize = 10 };
|
||||
private List<PhTable.ButtonOptions> Botones;
|
||||
private int PaginaDeseada = 1;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Botones = new List<PhTable.ButtonOptions>
|
||||
{
|
||||
new PhTable.ButtonOptions
|
||||
{
|
||||
Caption = "Editar",
|
||||
ElementClass = "btn btn-primary btn-sm",
|
||||
UrlAction = "/stock/productdivisionform/",
|
||||
OnClickAction = async (id) =>
|
||||
{
|
||||
Navigation.NavigateTo($"/stock/productdivisionform/{id}");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private async Task Buscar()
|
||||
{
|
||||
Resultado = await productDivisionService.SearchAsync(SearchParams);
|
||||
if (Resultado?.Items != null)
|
||||
{
|
||||
Tabla = Resultado.Items.Select(d => new Dictionary<string, object>
|
||||
{
|
||||
{ "Id", d.Id },
|
||||
{ "Codigo", d.Code ?? string.Empty },
|
||||
{ "Nombre", d.Name ?? string.Empty },
|
||||
{ "Descripción", d.Description ?? string.Empty }
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PrimeraPagina() { SearchParams.Page = 1; await Buscar(); }
|
||||
private async Task UltimaPagina() { SearchParams.Page = TotalPaginas; await Buscar(); }
|
||||
private async Task SiguientePagina() => await CambiarPagina(1);
|
||||
private async Task AnteriorPagina() => await CambiarPagina(-1);
|
||||
|
||||
private async Task CambiarPagina(int delta)
|
||||
{
|
||||
var nuevaPagina = SearchParams.Page + delta;
|
||||
if (nuevaPagina >= 1 && nuevaPagina <= TotalPaginas)
|
||||
{
|
||||
SearchParams.Page = nuevaPagina;
|
||||
await Buscar();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task IrAPagina()
|
||||
{
|
||||
if (PaginaDeseada >= 1 && PaginaDeseada <= TotalPaginas)
|
||||
{
|
||||
SearchParams.Page = PaginaDeseada;
|
||||
await Buscar();
|
||||
}
|
||||
else
|
||||
{
|
||||
toastService.ShowWarning("Número de página fuera de rango.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Nuevo() => Navigation.NavigateTo("/stock/productdivisionform/");
|
||||
private void Volver() => Navigation.NavigateTo("/DashboardPanel");
|
||||
|
||||
private bool PuedeRetroceder => Resultado != null && SearchParams.Page > 1;
|
||||
private bool PuedeAvanzar => Resultado != null && SearchParams.Page < TotalPaginas;
|
||||
private int TotalPaginas => Resultado is null ? 1 :
|
||||
(int)Math.Ceiling((double)(Resultado.TotalItems) / SearchParams.PageSize);
|
||||
}
|
||||
86
phronCare.UIBlazor/Pages/Stock/ProductDivisionForm.razor
Normal file
86
phronCare.UIBlazor/Pages/Stock/ProductDivisionForm.razor
Normal file
@ -0,0 +1,86 @@
|
||||
@page "/stock/productdivisionform/"
|
||||
@page "/stock/productdivisionform/{Id:int?}"
|
||||
@using phronCare.UIBlazor.Services.Stock
|
||||
@using Domain.Entities
|
||||
@inject ProductDivisionService productDivisionService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IToastService toastService
|
||||
|
||||
<EditForm Model="model" OnValidSubmit="HandleValidSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
|
||||
<div class="card mt-4" style="zoom:80%">
|
||||
<div class="card-header d-flex justify-content-center align-items-center">
|
||||
<h3 class="card-title m-0">@((model.Id == 0) ? "Nueva División" : "Editar División")</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-2 mb-3">
|
||||
<label for="Code" class="form-label">Código</label>
|
||||
<InputText id="Code" class="form-control" @bind-Value="model.Code" />
|
||||
<ValidationMessage For="@(() => model.Code)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="Name" class="form-label">Nombre</label>
|
||||
<InputText id="Name" class="form-control" @bind-Value="model.Name" />
|
||||
<ValidationMessage For="@(() => model.Name)" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="Description" class="form-label">Descripción</label>
|
||||
<InputText id="Description" class="form-control" @bind-Value="model.Description" />
|
||||
<ValidationMessage For="@(() => model.Description)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="d-flex justify-content-end align-items-center py-3">
|
||||
<button type="submit" class="btn btn-primary me-2">Guardar</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public int? Id { get; set; }
|
||||
|
||||
private EProductDivision model = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Id.HasValue && Id > 0)
|
||||
{
|
||||
var result = await productDivisionService.GetByIdAsync(Id.Value);
|
||||
if (result != null)
|
||||
model = result;
|
||||
else
|
||||
toastService.ShowError("No se pudo cargar la división de productos.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleValidSubmit()
|
||||
{
|
||||
var response = model.Id == 0
|
||||
? await productDivisionService.CreateAsync(model)
|
||||
: await productDivisionService.UpdateAsync(model);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
toastService.ShowSuccess("División guardada correctamente.");
|
||||
Navigation.NavigateTo("/stock/productdivisions");
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
toastService.ShowError($"Error: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel() => Navigation.NavigateTo("/stock/productdivisions");
|
||||
}
|
||||
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
|
||||
using Blazored.Modal;
|
||||
using Blazored.Toast;
|
||||
using phronCare.UIBlazor.Services.Stock;
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
@ -62,5 +63,5 @@ static void InjectDependencies(WebAssemblyHostBuilder builder)
|
||||
builder.Services.AddScoped<ProfessionalSpecialtyService>();
|
||||
builder.Services.AddScoped<ProductCategoryService>();
|
||||
builder.Services.AddScoped<PatientService>();
|
||||
|
||||
builder.Services.AddScoped<ProductDivisionService>();
|
||||
}
|
||||
55
phronCare.UIBlazor/Services/Stock/ProductDivisionService.cs
Normal file
55
phronCare.UIBlazor/Services/Stock/ProductDivisionService.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Domain.Entities;
|
||||
using Domain.Generics;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace phronCare.UIBlazor.Services.Stock
|
||||
{
|
||||
public class ProductDivisionService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly IJSRuntime _js;
|
||||
|
||||
public ProductDivisionService(HttpClient http, IJSRuntime js)
|
||||
{
|
||||
_http = http;
|
||||
_js = js;
|
||||
}
|
||||
|
||||
public async Task<List<EProductDivision>> GetAllAsync()
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<List<EProductDivision>>("/api/ProductDivision/GetAll");
|
||||
return result ?? new List<EProductDivision>();
|
||||
}
|
||||
|
||||
public async Task<EProductDivision?> GetByIdAsync(int id)
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<EProductDivision>($"/api/ProductDivision/GetById/{id}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> CreateAsync(EProductDivision division)
|
||||
{
|
||||
return await _http.PostAsJsonAsync("/api/ProductDivision/Create", division);
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> UpdateAsync(EProductDivision division)
|
||||
{
|
||||
return await _http.PutAsJsonAsync("/api/ProductDivision/Update", division);
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> DeleteAsync(int id)
|
||||
{
|
||||
return await _http.DeleteAsync($"/api/ProductDivision/Delete/{id}");
|
||||
}
|
||||
|
||||
public async Task<PagedResult<EProductDivision>?> SearchAsync(ProductDivisionSearchParams searchParams)
|
||||
{
|
||||
var url = $"/api/ProductDivision/Search?" +
|
||||
$"term={searchParams.Term}&" +
|
||||
$"page={searchParams.Page}&" +
|
||||
$"pageSize={searchParams.PageSize}";
|
||||
return await _http.GetFromJsonAsync<PagedResult<EProductDivision>>(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user