From b1ab76aa5a65e64e9286c99481fa9750cf90dfbe Mon Sep 17 00:00:00 2001 From: Leandro Hernan Rojas Date: Tue, 6 May 2025 15:58:35 -0300 Subject: [PATCH] Add TaxSecction on QuoteCreate --- Core/Interfaces/ITaxTypeDom.cs | 14 ++ Core/Services/TaxTypeService.cs | 32 +++++ Domain/Entities/ETaxType.cs | 40 ++++++ Domain/obj/Domain.csproj.nuget.g.props | 2 +- .../Interfaces/IPhOhArcataxTypeRepository.cs | 14 ++ Models/Models/PhOhArcataxType.cs | 5 + .../Models/PhronCareOperationsHubContext.cs | 4 + .../Repositories/PhOhArcataxTypeRepository.cs | 33 +++++ Models/obj/Models.csproj.nuget.g.props | 2 +- .../Controllers/Sales/QuoteController.cs | 113 +++++++++++++--- .../Controllers/Sales/TaxTypeController.cs | 29 +++++ phronCare.API/Program.cs | 3 +- .../obj/Debug/net8.0/ApiEndpoints.json | 79 +++++++++++ .../Pages/Sales/Modals/Component.razor | 0 .../Sales/Modals/QuoteTaxQuickAddModal.razor | 123 ++++++++++++++++++ .../Pages/Sales/Quotes/QuoteCreate.razor | 81 +++++++++++- .../Services/Lookups/ISalesLookupService .cs | 1 + .../Services/Lookups/SalesLookupService.cs | 7 + 18 files changed, 552 insertions(+), 30 deletions(-) create mode 100644 Core/Interfaces/ITaxTypeDom.cs create mode 100644 Core/Services/TaxTypeService.cs create mode 100644 Domain/Entities/ETaxType.cs create mode 100644 Models/Interfaces/IPhOhArcataxTypeRepository.cs create mode 100644 Models/Repositories/PhOhArcataxTypeRepository.cs create mode 100644 phronCare.API/Controllers/Sales/TaxTypeController.cs create mode 100644 phronCare.UIBlazor/Pages/Sales/Modals/Component.razor create mode 100644 phronCare.UIBlazor/Pages/Sales/Modals/QuoteTaxQuickAddModal.razor diff --git a/Core/Interfaces/ITaxTypeDom.cs b/Core/Interfaces/ITaxTypeDom.cs new file mode 100644 index 0000000..ad5f975 --- /dev/null +++ b/Core/Interfaces/ITaxTypeDom.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Core.Interfaces +{ + public interface ITaxTypeDom + { + Task> GetAllActiveAsync(); + } +} diff --git a/Core/Services/TaxTypeService.cs b/Core/Services/TaxTypeService.cs new file mode 100644 index 0000000..66dafb8 --- /dev/null +++ b/Core/Services/TaxTypeService.cs @@ -0,0 +1,32 @@ +using Core.Interfaces; +using Domain.Entities; +using Models.Interfaces; +using System.Reflection; + +namespace Core.Services +{ + public class TaxTypeService: ITaxTypeDom + { + #region Declaraciones y Constructor + private readonly IPhOhArcataxTypeRepository _repository; + public TaxTypeService(IPhOhArcataxTypeRepository repository) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + } + #endregion + #region Metodos de clases + public async Task> GetAllActiveAsync() + { + try + { + return await _repository.GetAllActiveAsync(); + } + catch (Exception ex) + { + var method = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + throw new Exception($"{method} Message: {ex.Message}", ex); + } + } + #endregion + } +} diff --git a/Domain/Entities/ETaxType.cs b/Domain/Entities/ETaxType.cs new file mode 100644 index 0000000..5843a38 --- /dev/null +++ b/Domain/Entities/ETaxType.cs @@ -0,0 +1,40 @@ +namespace Domain.Entities +{ + public class ETaxType + { + /// + /// Identificador único del impuesto. + /// + public int Id { get; set; } + + /// + /// Código oficial del impuesto según ARCA. + /// + public int TaxCode { get; set; } + + /// + /// Descripción del impuesto. + /// + public string Description { get; set; } = null!; + + /// + /// Porcentaje de la alícuota aplicable al impuesto, expresado como un valor decimal. Una alícuota del 21%, se debe almacenar el valor 21.00 + /// + public decimal Taxrate { get; set; } + + /// + /// Indica si el impuesto está activo (1) o inactivo (0). + /// + public bool IsActive { get; set; } + + /// + /// Fecha de creación del registro. + /// + public DateTime CreatedAt { get; set; } + + /// + /// Fecha de última modificación del registro. + /// + public DateTime? ModifiedAt { get; set; } + } +} diff --git a/Domain/obj/Domain.csproj.nuget.g.props b/Domain/obj/Domain.csproj.nuget.g.props index 8b4eb7e..3efed38 100644 --- a/Domain/obj/Domain.csproj.nuget.g.props +++ b/Domain/obj/Domain.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.2 + 6.13.1 diff --git a/Models/Interfaces/IPhOhArcataxTypeRepository.cs b/Models/Interfaces/IPhOhArcataxTypeRepository.cs new file mode 100644 index 0000000..aed87d0 --- /dev/null +++ b/Models/Interfaces/IPhOhArcataxTypeRepository.cs @@ -0,0 +1,14 @@ +using Domain.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Interfaces +{ + public interface IPhOhArcataxTypeRepository + { + Task> GetAllActiveAsync(); + } +} diff --git a/Models/Models/PhOhArcataxType.cs b/Models/Models/PhOhArcataxType.cs index 402d54f..f2d78aa 100644 --- a/Models/Models/PhOhArcataxType.cs +++ b/Models/Models/PhOhArcataxType.cs @@ -23,6 +23,11 @@ public partial class PhOhArcataxType /// public string Description { get; set; } = null!; + /// + /// Porcentaje de la alícuota aplicable al impuesto, expresado como un valor decimal. Una alícuota del 21%, se debe almacenar el valor 21.00 + /// + public decimal Taxrate { get; set; } + /// /// Indica si el impuesto está activo (1) o inactivo (0). /// diff --git a/Models/Models/PhronCareOperationsHubContext.cs b/Models/Models/PhronCareOperationsHubContext.cs index 76a4c4b..ff36e7d 100644 --- a/Models/Models/PhronCareOperationsHubContext.cs +++ b/Models/Models/PhronCareOperationsHubContext.cs @@ -141,6 +141,10 @@ public partial class PhronCareOperationsHubContext : DbContext entity.Property(e => e.TaxCode) .HasComment("Código oficial del impuesto según AFIP.") .HasColumnName("tax_code"); + entity.Property(e => e.Taxrate) + .HasComment("Porcentaje de la alícuota aplicable al impuesto, expresado como un valor decimal. Una alícuota del 21%, se debe almacenar el valor 21.00") + .HasColumnType("decimal(18, 0)") + .HasColumnName("taxrate"); }); modelBuilder.Entity(entity => diff --git a/Models/Repositories/PhOhArcataxTypeRepository.cs b/Models/Repositories/PhOhArcataxTypeRepository.cs new file mode 100644 index 0000000..ff873f7 --- /dev/null +++ b/Models/Repositories/PhOhArcataxTypeRepository.cs @@ -0,0 +1,33 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace Models.Repositories +{ + public class PhOhArcataxTypeRepository (PhronCareOperationsHubContext context) : IPhOhArcataxTypeRepository + { + #region Declaraciones y Constructor + private readonly PhronCareOperationsHubContext _context = context; + #endregion + #region Metodos de clase + public async Task> GetAllActiveAsync() + { + var taxTypes= await _context.PhOhArcataxTypes + .Where(t => t.IsActive) + .Select(t => new PhOhArcataxType + { + Id = t.Id, + TaxCode = t.TaxCode, + Description = t.Description, + Taxrate=t.Taxrate, + IsActive=t.IsActive + }) + .ToListAsync(); + + return taxTypes.Select(EntityMapper.MapEntity); + } + #endregion + } +} diff --git a/Models/obj/Models.csproj.nuget.g.props b/Models/obj/Models.csproj.nuget.g.props index 57bd42c..d21a6b0 100644 --- a/Models/obj/Models.csproj.nuget.g.props +++ b/Models/obj/Models.csproj.nuget.g.props @@ -7,7 +7,7 @@ $(UserProfile)\.nuget\packages\ C:\Users\maski\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages PackageReference - 6.13.2 + 6.13.1 diff --git a/phronCare.API/Controllers/Sales/QuoteController.cs b/phronCare.API/Controllers/Sales/QuoteController.cs index 8a80d0a..230ec23 100644 --- a/phronCare.API/Controllers/Sales/QuoteController.cs +++ b/phronCare.API/Controllers/Sales/QuoteController.cs @@ -152,26 +152,6 @@ namespace phronCare.API.Controllers.Sales #endregion - #region Exportación - - [HttpPost("exportfiltered")] - public async Task ExportFiltered([FromBody] QuoteSearchParams searchParams) - { - try - { - var file = await _quoteService.ExportFilteredQuotesToExcelAsync(searchParams); - return File(file, - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "Presupuestos.xlsx"); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - - #endregion - #region Impuestos (QuoteTaxes) [HttpGet("{quoteId:int}/taxes")] @@ -241,5 +221,96 @@ namespace phronCare.API.Controllers.Sales } #endregion + + #region Ajustes Comerciales (QuoteAdjustments) + + [HttpGet("{quoteId:int}/adjustments")] + public async Task GetAdjustments(int quoteId) + { + try + { + var adjustments = await _quoteService.GetAdjustmentsByQuoteIdAsync(quoteId); + return Ok(adjustments); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpPost("{quoteId:int}/adjustments")] + public async Task AddAdjustment(int quoteId, [FromBody] EQuoteAdjustment adjustment) + { + try + { + if (adjustment == null || quoteId != adjustment.QuoteheaderId) + return BadRequest("Datos inválidos para el ajuste."); + + var result = await _quoteService.AddAdjustmentAsync(adjustment); + return Ok(result); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpPut("adjustments")] + public async Task UpdateAdjustment([FromBody] EQuoteAdjustment adjustment) + { + try + { + if (adjustment == null || adjustment.Id <= 0) + return BadRequest("Datos inválidos para actualizar el ajuste."); + + await _quoteService.UpdateAdjustmentAsync(adjustment); + return Ok("Ajuste actualizado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + [HttpDelete("adjustments/{adjustmentId:int}")] + public async Task DeleteAdjustment(int adjustmentId) + { + try + { + await _quoteService.DeleteAdjustmentAsync(adjustmentId); + return Ok("Ajuste eliminado correctamente."); + } + catch (Exception ex) + { + var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod"; + return StatusCode(500, $"{methodName} Message: {ex.Message}"); + } + } + + #endregion + + #region Exportación + + [HttpPost("exportfiltered")] + public async Task ExportFiltered([FromBody] QuoteSearchParams searchParams) + { + try + { + var file = await _quoteService.ExportFilteredQuotesToExcelAsync(searchParams); + return File(file, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Presupuestos.xlsx"); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + #endregion + } -} +} \ No newline at end of file diff --git a/phronCare.API/Controllers/Sales/TaxTypeController.cs b/phronCare.API/Controllers/Sales/TaxTypeController.cs new file mode 100644 index 0000000..1d269ea --- /dev/null +++ b/phronCare.API/Controllers/Sales/TaxTypeController.cs @@ -0,0 +1,29 @@ +using Core.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace phronCare.API.Controllers.Sales +{ + [Route("api/[controller]")] + [ApiController] + public class TaxTypeController : ControllerBase + { + private readonly ITaxTypeDom _taxTypeDom; + public TaxTypeController(ITaxTypeDom taxTypeDom) + { + _taxTypeDom = taxTypeDom ?? throw new ArgumentNullException(nameof(taxTypeDom)); + } + [HttpGet("GetAll")] + public async Task GetAll() + { + try + { + var result = await _taxTypeDom.GetAllActiveAsync(); + return Ok(result); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + } +} diff --git a/phronCare.API/Program.cs b/phronCare.API/Program.cs index bcc9610..d3c6ec2 100644 --- a/phronCare.API/Program.cs +++ b/phronCare.API/Program.cs @@ -239,7 +239,8 @@ static void RepositorysAndServices(WebApplicationBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); - + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json index 17c91bc..f146b03 100644 --- a/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json +++ b/phronCare.API/obj/Debug/net8.0/ApiEndpoints.json @@ -1556,6 +1556,43 @@ } ] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "GetAdjustments", + "RelativePath": "api/Quote/{quoteId}/adjustments", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "quoteId", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "AddAdjustment", + "RelativePath": "api/Quote/{quoteId}/adjustments", + "HttpMethod": "POST", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "quoteId", + "Type": "System.Int32", + "IsRequired": true + }, + { + "Name": "adjustment", + "Type": "Domain.Entities.EQuoteAdjustment", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "GetTaxes", @@ -1593,6 +1630,38 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "UpdateAdjustment", + "RelativePath": "api/Quote/adjustments", + "HttpMethod": "PUT", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "adjustment", + "Type": "Domain.Entities.EQuoteAdjustment", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, + { + "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", + "Method": "DeleteAdjustment", + "RelativePath": "api/Quote/adjustments/{adjustmentId}", + "HttpMethod": "DELETE", + "IsController": true, + "Order": 0, + "Parameters": [ + { + "Name": "adjustmentId", + "Type": "System.Int32", + "IsRequired": true + } + ], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.Sales.QuoteController", "Method": "GetAll", @@ -1802,6 +1871,16 @@ ], "ReturnTypes": [] }, + { + "ContainingType": "phronCare.API.Controllers.Sales.TaxTypeController", + "Method": "GetAll", + "RelativePath": "api/TaxType/GetAll", + "HttpMethod": "GET", + "IsController": true, + "Order": 0, + "Parameters": [], + "ReturnTypes": [] + }, { "ContainingType": "phronCare.API.Controllers.TestController", "Method": "GetAdministradores", diff --git a/phronCare.UIBlazor/Pages/Sales/Modals/Component.razor b/phronCare.UIBlazor/Pages/Sales/Modals/Component.razor new file mode 100644 index 0000000..e69de29 diff --git a/phronCare.UIBlazor/Pages/Sales/Modals/QuoteTaxQuickAddModal.razor b/phronCare.UIBlazor/Pages/Sales/Modals/QuoteTaxQuickAddModal.razor new file mode 100644 index 0000000..395efad --- /dev/null +++ b/phronCare.UIBlazor/Pages/Sales/Modals/QuoteTaxQuickAddModal.razor @@ -0,0 +1,123 @@ +@using Blazored.Modal +@using Blazored.Modal.Services +@using Blazored.Typeahead +@using Domain.Entities +@inject Services.Lookups.ISalesLookupService SalesLookupService + + + + +
+
+
Agregar Impuesto
+
+ +
+
+ + + + @item.Description (@item.Taxrate.ToString("0.#")%) + + + @item.Description + + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ +@code { + [CascadingParameter] public BlazoredModalInstance ModalInstance { get; set; } = default!; + [Parameter] public decimal NetAmount { get; set; } + private ETaxType? _selectedTaxType; + private EQuoteTax _model = new(); + private List _taxTypes = new(); + + protected override async Task OnInitializedAsync() + { + _taxTypes = (await SalesLookupService.GetTaxTypesAsync()).ToList(); + _model.TaxableAmount = NetAmount; + Recalculate(); + } + + private Task> SearchTaxTypes(string searchTerm) + { + var result = string.IsNullOrWhiteSpace(searchTerm) + ? _taxTypes + : _taxTypes.Where(t => t.Description.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)); + + return Task.FromResult(result); + } + + + private Task OnTaxTypeSelected(ETaxType selected) + { + _model.Taxname = selected.Description; + _model.Taxcode = selected.TaxCode.ToString(); + _model.Taxrate = selected.Taxrate; + Recalculate(); + return Task.CompletedTask; + } + + private void OnValueChanged(decimal value, Action setter) + { + setter(_model, value); + Recalculate(); + } + + private void Recalculate() + { + _model.Taxamount = Math.Round(_model.TaxableAmount * (_model.Taxrate / 100), 2); + } + + private async Task HandleValidSubmit() + { + if (string.IsNullOrWhiteSpace(_model.Taxname)) + { + return; + } + + await ModalInstance.CloseAsync(ModalResult.Ok(_model)); + } + + private async Task Cancelar() + { + await ModalInstance.CancelAsync(); + } +} diff --git a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor index d1f8e3b..3c5c869 100644 --- a/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor +++ b/phronCare.UIBlazor/Pages/Sales/Quotes/QuoteCreate.razor @@ -205,8 +205,12 @@
- - + @if (_quoteModel.PhSQuoteAdjustments.Any()) {
@@ -226,12 +230,52 @@

Sin ajustes.

} - +
+
-
+ + + @if (_quoteModel.PhSQuoteTaxes.Any()) + { +
+ + + + + + + + + + + @foreach (var tax in _quoteModel.PhSQuoteTaxes) + { + + + + + + + } + +
Nombre%Importe
@tax.Taxname@tax.Taxrate.ToString("0.##")%$@tax.Taxamount.ToString("N2") + +
+
+ } + else + { +

No hay impuestos aplicados

+ } +
  • @@ -369,5 +413,30 @@ { _quoteModel.PhSQuoteAdjustments.Remove(adj); } + private async Task AddNewTax() + { + var parameters = new ModalParameters(); + parameters.Add(nameof(QuoteTaxQuickAddModal.NetAmount), _netAmount); + + var options = new ModalOptions + { + HideHeader = true, + Size = ModalSize.Small + }; + + var modal = Modal.Show("", parameters, options); + var result = await modal.Result; + + if (!result.Cancelled && result.Data is EQuoteTax newTax) + { + _quoteModel.PhSQuoteTaxes.Add(newTax); + RecalculateTotals(); + } + } + private void RemoveTax(EQuoteTax tax) + { + _quoteModel.PhSQuoteTaxes.Remove(tax); + RecalculateTotals(); + } } diff --git a/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs b/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs index 39dfab6..ba876ea 100644 --- a/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs +++ b/phronCare.UIBlazor/Services/Lookups/ISalesLookupService .cs @@ -12,5 +12,6 @@ namespace phronCare.UIBlazor.Services.Lookups Task> SearchBussinessUnitsAsync(string filtro); Task> SearchProductsAsync(string filtro); Task> GetAdjustmentReasonsAsync(); + Task> GetTaxTypesAsync(); } } diff --git a/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs b/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs index f9facef..826437e 100644 --- a/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs +++ b/phronCare.UIBlazor/Services/Lookups/SalesLookupService.cs @@ -49,5 +49,12 @@ namespace phronCare.UIBlazor.Services.Lookups var items = await _http.GetFromJsonAsync(url); return items ?? Array.Empty(); } + public async Task> GetTaxTypesAsync() + { + var items = await _http.GetFromJsonAsync("api/taxtype/getall"); + return items ?? Array.Empty(); + } + + } }