Compare commits

...

2 Commits

Author SHA1 Message Date
b1d48d4eec Merge pull request 'feat(deliverynote): snapshot clínico (ExtraInfo) desde presupuesto' (#42) from feature/leandro/41-deliverynote-extrainfo-snapshot into master
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 9m36s
Reviewed-on: http://saludlab.com.ar:3000/leandro/phronCare/pulls/42
2026-03-25 00:48:30 +00:00
c1aa6827b0 feat(deliverynote): snapshot clínico (ExtraInfo) desde presupuesto
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 9m31s
Se implementa la construcción automática de ExtrainfoJson al seleccionar un presupuesto en la pantalla de emisión de Delivery Note.

- Se genera snapshot clínico con Professional, Institution, Patient y SurgeryDate
- Se serializa a JSON plano utilizando System.Text.Json
- Se asigna a Model.ExtraInfoJson para persistencia
- Se limpia el snapshot al deseleccionar o fallar la carga del presupuesto

Se mantiene consistencia con el patrón implementado en Expeditions.
No se modifican contratos ni capas Core/API/Data.

Closes #41
2026-03-24 21:47:02 -03:00

View File

@ -1,5 +1,6 @@
@page "/deliverynotes/create"
@using System.ComponentModel.DataAnnotations
@using System.Text.Json
@using Blazored.Typeahead
@using Domain.Constants
@using Domain.Dtos
@ -27,12 +28,12 @@
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<div class="col-md-2">
<label for="deliveryNoteNumber" class="form-label">Número de remito</label>
<InputText id="deliveryNoteNumber" class="form-control" @bind-Value="Model.DeliveryNoteNumber" />
<ValidationMessage For="@(() => Model.DeliveryNoteNumber)" />
</div>
<div class="col-md-4">
<div class="col-md-2">
<label for="issueDate" class="form-label">Fecha de emisión</label>
<InputDate id="issueDate" class="form-control" @bind-Value="Model.IssueDate" />
<ValidationMessage For="@(() => Model.IssueDate)" />
@ -51,10 +52,7 @@
<SelectedTemplate Context="item">@item.Nombre</SelectedTemplate>
</BlazoredTypeahead>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="col-md-4">
<label for="customerLookup" class="form-label">Cliente</label>
<BlazoredTypeahead id="customerLookup" TItem="ELookUpItem" TValue="ELookUpItem"
SearchMethod="SalesLookupService.SearchCustomersAsync"
@ -69,16 +67,47 @@
</BlazoredTypeahead>
<ValidationMessage For="@(() => Model.CustomerId)" />
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="observations" class="form-label">Observaciones</label>
<InputTextArea id="observations" class="form-control" rows="3" @bind-Value="Model.Observations" />
</div>
<div class="col-md-6">
@if (SelectedQuote is not null)
{
<label for="observations" class="form-label">Vinculado</label>
<div class="alert alert-dark border mb-3">
<strong rows="3">Presupuesto vinculado:</strong> @SelectedQuote.Nombre
</div>
}
</div>
</div>
@if (SelectedQuote is not null)
{
<div class="alert alert-light border mb-0">
<strong>Presupuesto vinculado:</strong> @SelectedQuote.Nombre
<div class="card border-1 bg-light-subtle">
<div class="card-body py-3">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold mb-1">Profesional</label>
<div class="form-control bg-white">@(string.IsNullOrWhiteSpace(ExtraInfo.Professional) ? "No informado" : ExtraInfo.Professional)</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold mb-1">Institución</label>
<div class="form-control bg-white">@(string.IsNullOrWhiteSpace(ExtraInfo.Institution) ? "No informada" : ExtraInfo.Institution)</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold mb-1">Paciente</label>
<div class="form-control bg-white">@(string.IsNullOrWhiteSpace(ExtraInfo.Patient) ? "No informado" : ExtraInfo.Patient)</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold mb-1">Fecha estimada de cirugía</label>
<div class="form-control bg-white">@(ExtraInfo.SurgeryDate.HasValue ? ExtraInfo.SurgeryDate.Value.ToString("dd/MM/yyyy") : "No informada")</div>
</div>
</div>
</div>
</div>
}
</div>
@ -171,6 +200,7 @@
private ELookUpItem? SelectedCustomer;
private ELookUpItem? SelectedQuote;
private DeliveryNoteExtraInfoModel ExtraInfo = new();
private List<DeliveryNoteItemRow> Items = new();
private bool IsSaving;
@ -214,18 +244,27 @@
Model.QuoteId = quote?.Id;
if (quote is null)
{
ExtraInfo = new();
Model.ExtraInfoJson = null;
return;
}
var quoteDto = await QuoteService.GetDtoByIdAsync(quote.Id);
if (quoteDto is null)
{
ExtraInfo = new();
Model.ExtraInfoJson = null;
toastService.ShowError("No se pudo cargar el presupuesto seleccionado.");
return;
}
ExtraInfo = BuildExtraInfoModel(quoteDto);
var mappedItems = BuildItemsFromApprovedQuote(quoteDto);
if (mappedItems.Count == 0)
{
Model.ExtraInfoJson = JsonSerializer.Serialize(ExtraInfo);
toastService.ShowWarning("El presupuesto seleccionado no tiene ítems aprobados para precargar.");
return;
}
@ -243,10 +282,22 @@
}
Items = mappedItems;
Model.ExtraInfoJson = JsonSerializer.Serialize(ExtraInfo);
ReindexItems();
StateHasChanged();
}
private static DeliveryNoteExtraInfoModel BuildExtraInfoModel(QuoteDto quote)
{
return new DeliveryNoteExtraInfoModel
{
Professional = quote.ProfessionalName,
Institution = quote.InstitutionName,
Patient = quote.PatientName,
SurgeryDate = quote.EstimatedDate
};
}
private List<DeliveryNoteItemRow> BuildItemsFromApprovedQuote(QuoteDto quote)
{
return quote.Items
@ -351,6 +402,14 @@
public string? ExtraInfoJson { get; set; }
}
private sealed class DeliveryNoteExtraInfoModel
{
public string? Professional { get; set; }
public string? Institution { get; set; }
public string? Patient { get; set; }
public DateTime? SurgeryDate { get; set; }
}
private sealed class DeliveryNoteItemRow
{
public int LineNumber { get; set; }