Add Authorization Quote
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 7m16s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 7m16s
This commit is contained in:
parent
69ea0a6a98
commit
e276e9672c
@ -21,5 +21,9 @@ namespace Models.Interfaces
|
||||
Task<(int Id, string Quotenumber)> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||
Task<QuoteDto?> GetDtoByIdAsync(int id);
|
||||
#endregion
|
||||
#region Autorización
|
||||
Task<bool> AuthorizeQuoteAsync(int quoteId, List<QuoteAuthorizationDto> items);
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,19 @@ namespace Core.Services
|
||||
}
|
||||
#endregion
|
||||
|
||||
public async Task<bool> AuthorizeQuoteAsync(int quoteId, List<QuoteAuthorizationDto> items)
|
||||
{
|
||||
var approvedDetails = items.Select(i => new EQuoteDetail
|
||||
{
|
||||
Id = i.Id,
|
||||
Approved = i.Approved,
|
||||
Approvedquantity = i.ApprovedQuantity,
|
||||
Approvedunitprice = i.ApprovedUnitPrice
|
||||
}).ToList();
|
||||
|
||||
return await _quoteRepository.AuthorizeQuoteAsync(quoteId, approvedDetails);
|
||||
}
|
||||
|
||||
#region Validaciones QuoteCreate
|
||||
private void ValidateQuote(EQuoteHeader quote)
|
||||
{
|
||||
|
||||
10
Domain/Dtos/QuoteAuthorizationDto.cs
Normal file
10
Domain/Dtos/QuoteAuthorizationDto.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Domain.Dtos
|
||||
{
|
||||
public class QuoteAuthorizationDto
|
||||
{
|
||||
public int Id { get; set; } // Id del detalle
|
||||
public bool Approved { get; set; } // ¿Aprobado?
|
||||
public int? ApprovedQuantity { get; set; } // Cantidad aprobada
|
||||
public decimal? ApprovedUnitPrice { get; set; } // Precio aprobado
|
||||
}
|
||||
}
|
||||
7
Domain/Dtos/QuoteAuthorizationRequest.cs
Normal file
7
Domain/Dtos/QuoteAuthorizationRequest.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using Domain.Dtos;
|
||||
|
||||
public class QuoteAuthorizationRequest
|
||||
{
|
||||
public int QuoteId { get; set; }
|
||||
public List<QuoteAuthorizationDto>? Items { get; set; } = new();
|
||||
}
|
||||
@ -2,10 +2,15 @@
|
||||
{
|
||||
public class QuoteItemDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Identificador único del ítem dentro del presupuesto.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Descripción del producto o servicio cotizado.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = "";
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad de unidades cotizadas.
|
||||
|
||||
@ -37,6 +37,16 @@
|
||||
/// </summary>
|
||||
public bool Approved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad aprobada para este ítem del presupuesto.
|
||||
/// </summary>
|
||||
public int? Approvedquantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Precio unitario aprobado (puede diferir del original).
|
||||
/// </summary>
|
||||
public decimal? Approvedunitprice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe aprobado final
|
||||
/// </summary>
|
||||
@ -52,8 +62,8 @@
|
||||
/// </summary>
|
||||
public DateTime? Modifiedat { get; set; }
|
||||
|
||||
//public virtual EProduct Product { get; set; } = null!;
|
||||
//public virtual PhSProduct Product { get; set; } = null!;
|
||||
|
||||
//public virtual EQuoteHeader PhSQuoteheader { get; set; } = null!;
|
||||
//public virtual PhSQuoteHeader Quoteheader { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
/// <summary>
|
||||
/// Fecha de aprobación
|
||||
/// </summary>
|
||||
public DateOnly? Approvaldate { get; set; }
|
||||
public DateTime? Approvaldate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha tentativa (de cirugía por ej.)
|
||||
|
||||
@ -12,6 +12,7 @@ namespace Models.Interfaces
|
||||
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
|
||||
Task<(int Id, string Quotenumber)> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId);
|
||||
Task<QuoteDto?> GetDtoByIdAsync(int id);
|
||||
Task<bool> AuthorizeQuoteAsync(int quoteId, List<EQuoteDetail> approvedItems);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,16 @@ public partial class PhSQuoteDetail
|
||||
/// </summary>
|
||||
public bool Approved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cantidad aprobada para este ítem del presupuesto.
|
||||
/// </summary>
|
||||
public int? Approvedquantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Precio unitario aprobado (puede diferir del original).
|
||||
/// </summary>
|
||||
public decimal? Approvedunitprice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Importe aprobado final
|
||||
/// </summary>
|
||||
|
||||
@ -46,7 +46,7 @@ public partial class PhSQuoteHeader
|
||||
/// <summary>
|
||||
/// Fecha de aprobación
|
||||
/// </summary>
|
||||
public DateOnly? Approvaldate { get; set; }
|
||||
public DateTime? Approvaldate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fecha tentativa (de cirugía por ej.)
|
||||
|
||||
@ -74,15 +74,8 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
public virtual DbSet<PhSQuoteTaxis> PhSQuoteTaxes { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
#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");
|
||||
#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");
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@ -888,6 +881,13 @@ public partial class PhronCareOperationsHubContext : DbContext
|
||||
.HasComment("Importe aprobado final")
|
||||
.HasColumnType("decimal(18, 2)")
|
||||
.HasColumnName("approvedamount");
|
||||
entity.Property(e => e.Approvedquantity)
|
||||
.HasComment("Cantidad aprobada para este ítem del presupuesto.")
|
||||
.HasColumnName("approvedquantity");
|
||||
entity.Property(e => e.Approvedunitprice)
|
||||
.HasComment("Precio unitario aprobado (puede diferir del original).")
|
||||
.HasColumnType("decimal(18, 2)")
|
||||
.HasColumnName("approvedunitprice");
|
||||
entity.Property(e => e.Createdat)
|
||||
.HasDefaultValueSql("(getdate())")
|
||||
.HasComment("Fecha de creación del registro")
|
||||
|
||||
@ -296,6 +296,7 @@ namespace Models.Repositories
|
||||
|
||||
return new QuoteItemDto
|
||||
{
|
||||
Id = d.Id,
|
||||
Description = d.ProductDescription,
|
||||
Quantity = d.Quantity,
|
||||
UnitPrice = d.Unitprice,
|
||||
@ -378,5 +379,56 @@ namespace Models.Repositories
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#region Autorización de presupuesto (aprobar/rechazar ítems)
|
||||
public async Task<bool> AuthorizeQuoteAsync(int quoteId, List<EQuoteDetail> approvedItems)
|
||||
{
|
||||
var header = await _context.PhSQuoteHeaders
|
||||
.Include(h => h.PhSQuoteDetails)
|
||||
.FirstOrDefaultAsync(h => h.Id == quoteId);
|
||||
|
||||
if (header == null)
|
||||
return false;
|
||||
|
||||
bool anyApproved = false;
|
||||
|
||||
foreach (var item in approvedItems)
|
||||
{
|
||||
var detail = header.PhSQuoteDetails.FirstOrDefault(d => d.Id == item.Id);
|
||||
if (detail == null)
|
||||
continue;
|
||||
|
||||
detail.Approved = item.Approved;
|
||||
detail.Approvedquantity = item.Approved ? item.Approvedquantity : null;
|
||||
detail.Approvedunitprice = item.Approved ? item.Approvedunitprice : null;
|
||||
detail.Approvedamount = item.Approved && item.Approvedquantity.HasValue && item.Approvedunitprice.HasValue
|
||||
? item.Approvedquantity.Value * item.Approvedunitprice.Value
|
||||
: null;
|
||||
|
||||
if (item.Approved)
|
||||
anyApproved = true;
|
||||
|
||||
detail.Modifiedat = DateTime.Now;
|
||||
}
|
||||
|
||||
if (anyApproved)
|
||||
{
|
||||
header.Status = "Aprobado";
|
||||
header.Approvaldate = DateTime.Now;
|
||||
header.Approvedamount = header.PhSQuoteDetails
|
||||
.Where(d => d.Approved && d.Approvedamount.HasValue)
|
||||
.Sum(d => d.Approvedamount.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Status = "Anulado";
|
||||
header.Approvaldate = DateTime.Now;
|
||||
header.Approvedamount = 0;
|
||||
}
|
||||
header.Modifiedat = DateTime.Now;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -72,6 +72,29 @@ namespace phronCare.API.Controllers.Sales
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene un presupuesto completo por su ID.
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<QuoteDto>> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var quote = await _quoteService.GetDtoByIdAsync(id);
|
||||
|
||||
if (quote == null)
|
||||
return NotFound($"Presupuesto con ID {id} no encontrado.");
|
||||
|
||||
return Ok(quote);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{id}/pdf")]
|
||||
public async Task<IActionResult> GetQuotePdf(int id)
|
||||
{
|
||||
@ -130,6 +153,20 @@ namespace phronCare.API.Controllers.Sales
|
||||
|
||||
#endregion
|
||||
|
||||
#region Autorizacion de presupuestos
|
||||
[HttpPost("authorize")]
|
||||
public async Task<IActionResult> AuthorizeQuote([FromBody] QuoteAuthorizationRequest request)
|
||||
{
|
||||
if (request == null || request.Items == null)
|
||||
return BadRequest("No se recibió información válida para autorizar o anular.");
|
||||
|
||||
var result = await _quoteService.AuthorizeQuoteAsync(request.QuoteId, request.Items);
|
||||
|
||||
return result
|
||||
? Ok(new { success = true, message = "Presupuesto procesado correctamente." })
|
||||
: BadRequest(new { success = false, message = "No se pudo procesar el presupuesto." });
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1606,6 +1606,32 @@
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||
"Method": "GetById",
|
||||
"RelativePath": "api/Quote/{id}",
|
||||
"HttpMethod": "GET",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "id",
|
||||
"Type": "System.Int32",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": [
|
||||
{
|
||||
"Type": "Domain.Dtos.QuoteDto",
|
||||
"MediaTypes": [
|
||||
"text/plain",
|
||||
"application/json",
|
||||
"text/json"
|
||||
],
|
||||
"StatusCode": 200
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||
"Method": "GetQuotePdf",
|
||||
@ -1622,6 +1648,22 @@
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||
"Method": "AuthorizeQuote",
|
||||
"RelativePath": "api/Quote/authorize",
|
||||
"HttpMethod": "POST",
|
||||
"IsController": true,
|
||||
"Order": 0,
|
||||
"Parameters": [
|
||||
{
|
||||
"Name": "request",
|
||||
"Type": "QuoteAuthorizationRequest",
|
||||
"IsRequired": true
|
||||
}
|
||||
],
|
||||
"ReturnTypes": []
|
||||
},
|
||||
{
|
||||
"ContainingType": "phronCare.API.Controllers.Sales.QuoteController",
|
||||
"Method": "CreateFullQuote",
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
using Domain.Dtos;
|
||||
|
||||
namespace phronCare.UIBlazor.Models.Sales
|
||||
{
|
||||
public class QuoteAuthorizationViewItem : QuoteAuthorizationDto
|
||||
{
|
||||
public string ProductDescription { get; set; } = string.Empty;
|
||||
public int Quantity { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal Subtotal => Quantity * UnitPrice;
|
||||
}
|
||||
}
|
||||
227
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteAuthorize.razor
Normal file
227
phronCare.UIBlazor/Pages/Sales/Quotes/QuoteAuthorize.razor
Normal file
@ -0,0 +1,227 @@
|
||||
@page "/quotes/authorize/{QuoteId:int}"
|
||||
|
||||
@using Domain.Dtos
|
||||
@using phronCare.UIBlazor.Models.Sales
|
||||
@using phronCare.UIBlazor.Services.Sales.Quotes
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@inject QuoteService quoteService
|
||||
@inject NavigationManager nav
|
||||
@inject IToastService toast
|
||||
@inject IModalService Modal
|
||||
|
||||
<div class="card" style="zoom: 80%">
|
||||
<div class="card-header d-flex justify-content-center align-items-center">
|
||||
<h3 class="card-title m-0">
|
||||
<i class="fas fa-file-signature text-secondary me-2"></i> Autorización de Presupuesto
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (IsLoading)
|
||||
{
|
||||
<p>Cargando información del presupuesto...</p>
|
||||
}
|
||||
else if (LoadError)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
Ocurrió un error al intentar cargar el presupuesto.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-4">
|
||||
<div class="row">
|
||||
<div class="col"><strong>N°:</strong> @QuoteHeader.Quotenumber</div>
|
||||
<div class="col"><strong>Fecha:</strong> @QuoteHeader.IssueDate.ToShortDateString()</div>
|
||||
<div class="col"><strong>Cliente:</strong> @QuoteHeader.CustomerName</div>
|
||||
</div>
|
||||
<div class="row mt-1">
|
||||
<div class="col"><strong>Médico:</strong> @QuoteHeader.ProfessionalName</div>
|
||||
<div class="col"><strong>Hospital:</strong> @QuoteHeader.InstitutionName</div>
|
||||
<div class="col"><strong>Paciente:</strong> @QuoteHeader.PatientName</div>
|
||||
</div>
|
||||
<div class="row mt-1">
|
||||
<div class="col"><strong>Cirugía estimada:</strong> @QuoteHeader.EstimatedDate?.ToShortDateString()</div>
|
||||
<div class="col"><strong>Importe total:</strong> @QuoteHeader.Total.ToString("C")</div>
|
||||
<div class="col">
|
||||
<strong>Estado:</strong>
|
||||
<span class="badge @GetStatusColor(QuoteHeader.Status)">
|
||||
@QuoteHeader.Status
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditForm Model="FormModel" OnValidSubmit="OpenConfirmation">
|
||||
<div class="text-end mb-2">
|
||||
@if (FormModel.Items.Any(i => i.Approved))
|
||||
{
|
||||
<span class="badge bg-success px-3 py-2"><i class="fas fa-check-circle me-1"></i> Al menos un ítem será autorizado</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger px-3 py-2"><i class="fas fa-ban me-1"></i> Todos los ítems serán anulados</span>
|
||||
}
|
||||
</div>
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light text-center">
|
||||
<tr>
|
||||
<th style="width: 40px;">¿Aprobar?</th>
|
||||
<th style="width: 60%;">Descripción</th>
|
||||
<th>Cant.</th>
|
||||
<th>Precio U.</th>
|
||||
<th>Subtotal</th>
|
||||
<th>Cant. Aprob.</th>
|
||||
<th>Precio Aprob.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in FormModel.Items)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
@bind="item.Approved" />
|
||||
</td>
|
||||
<td>@item.ProductDescription</td>
|
||||
<td class="text-center">@item.Quantity</td>
|
||||
<td class="text-end">@item.UnitPrice.ToString("N2")</td>
|
||||
<td class="text-end">@item.Subtotal.ToString("N2")</td>
|
||||
<td class="text-center">
|
||||
<InputNumber @bind-Value="item.ApprovedQuantity"
|
||||
class="form-control form-control-sm text-center"
|
||||
disabled="@(item.Approved == false)" />
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<InputNumber @bind-Value="item.ApprovedUnitPrice"
|
||||
class="form-control form-control-sm text-end"
|
||||
disabled="@(item.Approved == false)" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button type="submit" class="btn text-white me-2 @(FormModel.Items.Any(i => i.Approved) ? "btn-success" : "btn-danger")">
|
||||
@(FormModel.Items.Any(i => i.Approved) ? "Confirmar Autorización" : "Confirmar Anulación")
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public int QuoteId { get; set; }
|
||||
|
||||
private QuoteDto? QuoteHeader;
|
||||
private QuoteAuthorizationFormModel FormModel = new();
|
||||
private bool IsLoading = true;
|
||||
private bool LoadError = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var quote = await quoteService.GetDtoByIdAsync(QuoteId);
|
||||
if (quote?.Items == null || !quote.Items.Any())
|
||||
{
|
||||
LoadError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
QuoteHeader = quote;
|
||||
|
||||
FormModel.Items = quote.Items.Select(x => new QuoteAuthorizationViewItem
|
||||
{
|
||||
Id = x.Id,
|
||||
ProductDescription = x.Description,
|
||||
Quantity = x.Quantity,
|
||||
UnitPrice = x.UnitPrice,
|
||||
Approved = false,
|
||||
ApprovedQuantity = null,
|
||||
ApprovedUnitPrice = null
|
||||
}).ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
LoadError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OpenConfirmation()
|
||||
{
|
||||
var options = new ModalOptions()
|
||||
{
|
||||
HideHeader = true
|
||||
};
|
||||
var parameters = new ModalParameters();
|
||||
parameters.Add("Message", "¿Desea continuar con esta operación?");
|
||||
var modal = Modal.Show<Shared.Modals.ConfirmModal>("Confirmación", parameters,options);
|
||||
var result = await modal.Result;
|
||||
|
||||
if (!result.Cancelled)
|
||||
{
|
||||
await AuthorizeQuote();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AuthorizeQuote()
|
||||
{
|
||||
var approvedItems = FormModel.Items
|
||||
.Where(i => i.Approved)
|
||||
.Select(i => new QuoteAuthorizationDto
|
||||
{
|
||||
Id = i.Id,
|
||||
Approved = true,
|
||||
ApprovedQuantity = i.ApprovedQuantity,
|
||||
ApprovedUnitPrice = i.ApprovedUnitPrice
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var payload = new QuoteAuthorizationRequest
|
||||
{
|
||||
QuoteId = QuoteId,
|
||||
Items = approvedItems
|
||||
};
|
||||
|
||||
var success = await quoteService.AuthorizeQuoteAsync(payload);
|
||||
if (success)
|
||||
{
|
||||
if (!approvedItems.Any())
|
||||
toast.ShowInfo("Presupuesto anulado correctamente.");
|
||||
else
|
||||
toast.ShowSuccess("Presupuesto autorizado con éxito.");
|
||||
nav.NavigateTo("/quotes");
|
||||
}
|
||||
else
|
||||
{
|
||||
toast.ShowError("No se pudo procesar el presupuesto.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel() => nav.NavigateTo("/quotes");
|
||||
|
||||
private string GetStatusColor(string status) => status.ToLower() switch
|
||||
{
|
||||
"Anulado" => "bg-danger text-white",
|
||||
"Emitido" => "bg-primary text-white",
|
||||
"Aprobado" => "bg-success",
|
||||
"Despacho" => "bg-info text-white",
|
||||
"SinConsumo" => "bg-warning text-dark",
|
||||
"Transito" => "bg-secondary text-white",
|
||||
"Cerrado" => "bg-dark text-white",
|
||||
_ => "bg-light text-dark"
|
||||
};
|
||||
|
||||
public class QuoteAuthorizationFormModel
|
||||
{
|
||||
public List<QuoteAuthorizationViewItem> Items { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
@page "/quote/create"
|
||||
@page "/quotes/create"
|
||||
@using System.Globalization;
|
||||
@using System.Net.Http.Json
|
||||
@using Blazored.Typeahead
|
||||
|
||||
@ -87,13 +87,13 @@
|
||||
<th>Total</th>
|
||||
<th>Estado</th>
|
||||
<th>Vendedor</th>
|
||||
<th>Acciones</th>
|
||||
<th style="width:80px;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var quote in PagedQuotes.Items)
|
||||
{
|
||||
<tr>
|
||||
<tr class="text-center">
|
||||
<td>@quote.Quotenumber</td>
|
||||
<td>@quote.IssueDate.ToString("dd/MM/yyyy")</td>
|
||||
<td>@(quote.EstimatedDate.HasValue ? quote.EstimatedDate.Value.ToString("dd/MM/yyyy") : "—")</td>
|
||||
@ -108,9 +108,17 @@
|
||||
<span class="badge @GetStatusBadge(quote.Status)">@quote.Status</span>
|
||||
</td>
|
||||
<td>@quote.SalespersonName</td>
|
||||
<td>
|
||||
<button class="btn btn-link btn-lg p-0 text-success ms-2" @onclick="() => ToggleDetail(quote)"><i class="fas fa-eye"></i></button>
|
||||
<button class="btn btn-link btn-lg p-0 text-primary ms-2" @onclick="() => PrintPdf(quote.Id,quote.Quotenumber)"><i class="fas fa-print"></i></button>
|
||||
<td class="text-center align-middle">
|
||||
<button class="btn btn-link btn-lg p-0 text-primary ms-2" title="Ver detalle" @onclick="() => ToggleDetail(quote)"><i class="fas fa-eye"></i></button>
|
||||
<button class="btn btn-link btn-lg p-0 text-success ms-2" title="Generar PDF" @onclick="() => PrintPdf(quote.Id, quote.Quotenumber)"><i class="fas fa-print"></i></button>
|
||||
@if (quote.Status?.ToLower() == "emitido")
|
||||
{
|
||||
<button class="btn btn-link btn-lg p-0 text-danger ms-2"
|
||||
title="Autorizar presupuesto"
|
||||
@onclick="() => Autorizar(quote.Id)">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -359,7 +367,7 @@
|
||||
|
||||
private void Create()
|
||||
{
|
||||
Navigation.NavigateTo("/quote/create/");
|
||||
Navigation.NavigateTo("/quotes/create/");
|
||||
}
|
||||
private void OnClear()
|
||||
{
|
||||
@ -369,6 +377,7 @@
|
||||
}
|
||||
private string GetStatusBadge(string status) => status switch
|
||||
{
|
||||
"Anulado" => "bg-danger text-white",
|
||||
"Emitido" => "bg-primary text-white",
|
||||
"Aprobado" => "bg-success",
|
||||
"Despacho" => "bg-info text-white",
|
||||
@ -389,4 +398,9 @@
|
||||
toastService.ShowError(ex.Message);
|
||||
}
|
||||
}
|
||||
private void Autorizar(int id)
|
||||
{
|
||||
Navigation.NavigateTo($"/quotes/authorize/{id}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -124,7 +124,34 @@ namespace phronCare.UIBlazor.Services.Sales.Quotes
|
||||
throw new Exception($"ExportPdfAsync: {message}", ex);
|
||||
}
|
||||
}
|
||||
public async Task<bool> AuthorizeQuoteAsync(QuoteAuthorizationRequest request)
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync("/api/quote/authorize", request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var serverMessage = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception($"Error al autorizar el presupuesto: {serverMessage}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Obtiene un presupuesto completo por ID para su visualización y autorización.
|
||||
/// </summary>
|
||||
public async Task<QuoteDto?> GetDtoByIdAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<QuoteDto>($"/api/quote/{id}");
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error al obtener QuoteDto por ID: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateQuoteResult
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user