Add Stock Module MVP Divisiones
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 7m45s

This commit is contained in:
Leandro Hernan Rojas 2025-06-27 13:52:22 -03:00
parent ca229ed6cb
commit 85fedf79af
22 changed files with 1447 additions and 15 deletions

View 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);
}
}

View 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
}
}

View 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;
}
}

View File

@ -0,0 +1,7 @@
namespace Domain.Generics
{
public class ProductDivisionSearchParams : PagedRequest
{
public string? Term { get; set; }
}
}

View 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);
}
}

View 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!;
}

View 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>();
}

View 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!;
}

View 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!;
}

View 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>();
}

View 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!;
}

View 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>();
}

View File

@ -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")

View 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;
}
}
}

View 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);
}
}
}
}

View File

@ -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>();
}

View File

@ -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",

View File

@ -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;

View 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);
}

View 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");
}

View File

@ -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>();
}

View 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);
}
}
}