Merge pull request 'feat(sales): add sales document draft review and validation close #74' (#75) from feature/leandro/74-sales-document-draft-review into master
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 3m39s
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 3m39s
Reviewed-on: #75
This commit is contained in:
commit
ab4f73f941
@ -18,6 +18,9 @@ namespace Core.Interfaces
|
||||
|
||||
Task<SalesDocumentCreateResponse> CreateAsync(SalesDocumentCreateRequest request);
|
||||
Task<SalesDocumentCreateResponse> CreateFromDeliveryNotesAsync(SalesDocumentCreateFromDeliveryNotesRequest request);
|
||||
Task<SalesDocumentDraftPreviewDto?> GetDraftPreviewAsync(int id);
|
||||
Task<SalesDocumentDraftPreviewDto?> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto review);
|
||||
Task<SalesDocumentDraftPreviewDto?> ValidateDraftAsync(int id);
|
||||
Task<PagedResult<SalesDocumentDeliveryNoteCandidateDto>> SearchDeliveryNoteCandidatesAsync(
|
||||
int? customerId,
|
||||
string? customerText,
|
||||
|
||||
@ -317,6 +317,174 @@ namespace Core.Services
|
||||
|
||||
return _salesDocumentRepository.GetDtoByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto?> GetDraftPreviewAsync(int id)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
|
||||
var document = await _salesDocumentRepository.GetDtoByIdAsync(id);
|
||||
return document == null ? null : BuildDraftPreview(document);
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto?> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto review)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
|
||||
ArgumentNullException.ThrowIfNull(review);
|
||||
|
||||
var current = await _salesDocumentRepository.GetDtoByIdAsync(id);
|
||||
if (current == null)
|
||||
return null;
|
||||
|
||||
if (current.Status != (int)SalesDocumentStatus.Draft)
|
||||
throw new InvalidOperationException("Solo se pueden revisar Sales Documents en estado Draft.");
|
||||
|
||||
var updated = await _salesDocumentRepository.UpdateDraftReviewAsync(id, review);
|
||||
return updated == null ? null : BuildDraftPreview(updated);
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto?> ValidateDraftAsync(int id)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
|
||||
var current = await _salesDocumentRepository.GetDtoByIdAsync(id);
|
||||
if (current == null)
|
||||
return null;
|
||||
|
||||
var validation = BuildDraftValidation(current);
|
||||
if (!validation.IsValid)
|
||||
throw new InvalidOperationException(string.Join(" ", validation.Errors));
|
||||
|
||||
var validated = await _salesDocumentRepository.ValidateDraftAsync(id);
|
||||
return validated == null ? null : BuildDraftPreview(validated);
|
||||
}
|
||||
|
||||
private static SalesDocumentDraftPreviewDto BuildDraftPreview(SalesDocumentDto document)
|
||||
{
|
||||
return new SalesDocumentDraftPreviewDto
|
||||
{
|
||||
Document = document,
|
||||
OriginDeliveryNotes = BuildOriginDeliveryNotes(document),
|
||||
Validation = BuildDraftValidation(document)
|
||||
};
|
||||
}
|
||||
|
||||
private static SalesDocumentDraftValidationDto BuildDraftValidation(SalesDocumentDto document)
|
||||
{
|
||||
var validation = new SalesDocumentDraftValidationDto
|
||||
{
|
||||
HasDetails = document.Details.Any(),
|
||||
HasValidAmounts = document.TotalAmount > 0 && document.NetAmount >= 0 && document.TaxAmount >= 0,
|
||||
HasCustomer = document.CustomerId > 0,
|
||||
IsDraft = document.Status == (int)SalesDocumentStatus.Draft
|
||||
};
|
||||
|
||||
if (!validation.HasDetails)
|
||||
validation.Errors.Add("El documento debe tener detalles.");
|
||||
|
||||
if (!validation.HasValidAmounts)
|
||||
validation.Errors.Add("El documento debe tener importes validos.");
|
||||
|
||||
if (!validation.HasCustomer)
|
||||
validation.Errors.Add("El documento debe tener cliente asignado.");
|
||||
|
||||
if (!validation.IsDraft)
|
||||
validation.Errors.Add("El documento debe permanecer en estado Draft.");
|
||||
|
||||
return validation;
|
||||
}
|
||||
|
||||
private static List<DeliveryNoteSummaryDto> BuildOriginDeliveryNotes(SalesDocumentDto document)
|
||||
{
|
||||
return document.Details
|
||||
.Where(x => string.Equals(x.OriginType, SalesDocumentOriginType.DeliveryNote.ToStorageCode(), StringComparison.OrdinalIgnoreCase))
|
||||
.Select(TryBuildDeliveryNoteSummary)
|
||||
.Where(x => x is not null)
|
||||
.Select(x => x!)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.OrderBy(x => x.IssueDate)
|
||||
.ThenBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static DeliveryNoteSummaryDto? TryBuildDeliveryNoteSummary(SalesDocumentDetailDto detail)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(detail.OriginSnapshotJson))
|
||||
{
|
||||
return detail.OriginId.HasValue
|
||||
? new DeliveryNoteSummaryDto
|
||||
{
|
||||
Id = detail.OriginId.Value,
|
||||
DeliveryNoteNumber = $"Remito #{detail.OriginId.Value}"
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var jsonDocument = JsonDocument.Parse(detail.OriginSnapshotJson);
|
||||
var root = jsonDocument.RootElement;
|
||||
|
||||
var deliveryNoteId = root.TryGetProperty("deliveryNoteId", out var idProperty) && idProperty.TryGetInt32(out var parsedId)
|
||||
? parsedId
|
||||
: detail.OriginId;
|
||||
|
||||
if (!deliveryNoteId.HasValue)
|
||||
return null;
|
||||
|
||||
var issueDate = DateTime.MinValue;
|
||||
if (root.TryGetProperty("deliveryNoteIssueDate", out var dateProperty) &&
|
||||
dateProperty.ValueKind == JsonValueKind.String &&
|
||||
DateTime.TryParse(dateProperty.GetString(), out var parsedDate))
|
||||
{
|
||||
issueDate = parsedDate;
|
||||
}
|
||||
|
||||
return new DeliveryNoteSummaryDto
|
||||
{
|
||||
Id = deliveryNoteId.Value,
|
||||
DeliveryNoteNumber = ReadString(root, "deliveryNoteNumber") ?? $"Remito #{deliveryNoteId.Value}",
|
||||
QuoteId = ReadInt(root, "quoteId"),
|
||||
QuoteNumber = ReadString(root, "quoteNumber"),
|
||||
IssueDate = issueDate,
|
||||
CustomerId = ReadInt(root, "customerId") ?? 0,
|
||||
CustomerName = ReadString(root, "customerName") ?? string.Empty,
|
||||
Status = "Emitido"
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return detail.OriginId.HasValue
|
||||
? new DeliveryNoteSummaryDto
|
||||
{
|
||||
Id = detail.OriginId.Value,
|
||||
DeliveryNoteNumber = $"Remito #{detail.OriginId.Value}"
|
||||
}
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ReadString(JsonElement root, string propertyName)
|
||||
{
|
||||
return root.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
|
||||
? property.GetString()
|
||||
: null;
|
||||
}
|
||||
|
||||
private static int? ReadInt(JsonElement root, string propertyName)
|
||||
{
|
||||
if (!root.TryGetProperty(propertyName, out var property))
|
||||
return null;
|
||||
|
||||
if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var value))
|
||||
return value;
|
||||
|
||||
return null;
|
||||
}
|
||||
private static void ValidateDetail(SalesDocumentCreateDetailRequest detail)
|
||||
{
|
||||
if (detail.LineNumber <= 0)
|
||||
|
||||
9
Domain/Dtos/Sales/SalesDocumentDraftPreviewDto.cs
Normal file
9
Domain/Dtos/Sales/SalesDocumentDraftPreviewDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Domain.Dtos.Sales
|
||||
{
|
||||
public class SalesDocumentDraftPreviewDto
|
||||
{
|
||||
public SalesDocumentDto Document { get; set; } = new();
|
||||
public List<DeliveryNoteSummaryDto> OriginDeliveryNotes { get; set; } = new();
|
||||
public SalesDocumentDraftValidationDto Validation { get; set; } = new();
|
||||
}
|
||||
}
|
||||
13
Domain/Dtos/Sales/SalesDocumentDraftReviewDto.cs
Normal file
13
Domain/Dtos/Sales/SalesDocumentDraftReviewDto.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Domain.Dtos.Sales
|
||||
{
|
||||
public class SalesDocumentDraftReviewDto
|
||||
{
|
||||
public DateTime? IssueDate { get; set; }
|
||||
public string? AssociatedDocumentType { get; set; }
|
||||
public string? AssociatedDocumentNumber { get; set; }
|
||||
public DateTime? AssociatedDocumentDate { get; set; }
|
||||
public string? Observations { get; set; }
|
||||
public DateTime? PeriodFrom { get; set; }
|
||||
public DateTime? PeriodTo { get; set; }
|
||||
}
|
||||
}
|
||||
12
Domain/Dtos/Sales/SalesDocumentDraftValidationDto.cs
Normal file
12
Domain/Dtos/Sales/SalesDocumentDraftValidationDto.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Domain.Dtos.Sales
|
||||
{
|
||||
public class SalesDocumentDraftValidationDto
|
||||
{
|
||||
public bool HasDetails { get; set; }
|
||||
public bool HasValidAmounts { get; set; }
|
||||
public bool HasCustomer { get; set; }
|
||||
public bool IsDraft { get; set; }
|
||||
public bool IsValid => HasDetails && HasValidAmounts && HasCustomer && IsDraft;
|
||||
public List<string> Errors { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@ -30,5 +30,7 @@ namespace Models.Interfaces
|
||||
int page = 1,
|
||||
int pageSize = 50);
|
||||
Task<SalesDocumentDto?> GetDtoByIdAsync(int id);
|
||||
Task<SalesDocumentDto?> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto review);
|
||||
Task<SalesDocumentDto?> ValidateDraftAsync(int id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,6 +313,9 @@ namespace Models.Repositories
|
||||
NetAmount = entity.NetAmount,
|
||||
TaxAmount = entity.TaxAmount,
|
||||
TotalAmount = entity.TotalAmount,
|
||||
AssociatedDocumentType = entity.AssociatedDocumentType,
|
||||
AssociatedDocumentNumber = entity.AssociatedDocumentNumber,
|
||||
AssociatedDocumentDate = entity.AssociatedDocumentDate,
|
||||
Observations = entity.Observations,
|
||||
ExtraInfoJson = entity.ExtraInfoJson,
|
||||
PeriodFrom = entity.PeriodFrom,
|
||||
@ -359,5 +362,48 @@ namespace Models.Repositories
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDto?> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto review)
|
||||
{
|
||||
var entity = await _context.PhSSalesDocuments
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
if (entity.Status != (int)Domain.Constants.SalesDocumentStatus.Draft)
|
||||
throw new InvalidOperationException("Solo se pueden revisar Sales Documents en estado Draft.");
|
||||
|
||||
entity.IssueDate = review.IssueDate;
|
||||
entity.AssociatedDocumentType = string.IsNullOrWhiteSpace(review.AssociatedDocumentType) ? null : review.AssociatedDocumentType.Trim();
|
||||
entity.AssociatedDocumentNumber = string.IsNullOrWhiteSpace(review.AssociatedDocumentNumber) ? null : review.AssociatedDocumentNumber.Trim();
|
||||
entity.AssociatedDocumentDate = review.AssociatedDocumentDate;
|
||||
entity.Observations = string.IsNullOrWhiteSpace(review.Observations) ? null : review.Observations.Trim();
|
||||
entity.PeriodFrom = review.PeriodFrom;
|
||||
entity.PeriodTo = review.PeriodTo;
|
||||
entity.Modifiedat = DateTime.Now;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return await GetDtoByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDto?> ValidateDraftAsync(int id)
|
||||
{
|
||||
var entity = await _context.PhSSalesDocuments
|
||||
.Include(x => x.PhSSalesDocumentDetails)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
if (entity.Status != (int)Domain.Constants.SalesDocumentStatus.Draft)
|
||||
throw new InvalidOperationException("Solo se pueden validar Sales Documents en estado Draft.");
|
||||
|
||||
entity.Status = (int)Domain.Constants.SalesDocumentStatus.Validated;
|
||||
entity.Modifiedat = DateTime.Now;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return await GetDtoByIdAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
165
docs/stories/story-74-sales-document-draft-review-validation.md
Normal file
165
docs/stories/story-74-sales-document-draft-review-validation.md
Normal file
@ -0,0 +1,165 @@
|
||||
# PhronCare — Story #74: Sales Document Draft Review & Validation
|
||||
|
||||
## Objetivo
|
||||
|
||||
Incorporar una etapa formal de revisión administrativa para Sales Documents en estado Draft antes de su futura utilización en procesos de facturación fiscal.
|
||||
|
||||
La story debe permitir revisar, completar, validar y aprobar un documento comercial sin realizar ninguna emisión fiscal.
|
||||
|
||||
---
|
||||
|
||||
## Contexto funcional
|
||||
|
||||
Actualmente:
|
||||
|
||||
Delivery Note
|
||||
↓
|
||||
Sales Document
|
||||
|
||||
El documento se crea correctamente y queda almacenado con estado Draft.
|
||||
|
||||
Sin embargo:
|
||||
|
||||
- no existe una pantalla de revisión;
|
||||
- no existe un proceso formal de validación;
|
||||
- no existe una separación clara entre creación y aprobación;
|
||||
- el usuario no puede revisar fácilmente la información antes de continuar el circuito comercial.
|
||||
|
||||
Antes de implementar integración ARCA se requiere formalizar esta etapa.
|
||||
|
||||
---
|
||||
|
||||
## Alcance
|
||||
|
||||
### Domain
|
||||
|
||||
Incorporar DTOs específicos para revisión y validación:
|
||||
|
||||
- SalesDocumentDraftPreviewDto
|
||||
- SalesDocumentDraftReviewDto
|
||||
- SalesDocumentDraftValidationDto
|
||||
|
||||
### Core
|
||||
|
||||
Extender SalesDocumentService para:
|
||||
|
||||
- Obtener preview completo.
|
||||
- Actualizar información editable del draft.
|
||||
- Validar draft.
|
||||
- Cambiar estado Draft → Validated.
|
||||
|
||||
### Data
|
||||
|
||||
- Extender repositorios existentes.
|
||||
- No crear nuevas tablas.
|
||||
- No modificar modelos scaffold.
|
||||
- Reutilizar PhS_SalesDocuments como fuente de verdad.
|
||||
|
||||
### API
|
||||
|
||||
Agregar endpoints:
|
||||
|
||||
GET /api/SalesDocument/{id}/draft-preview
|
||||
PUT /api/SalesDocument/{id}/draft-review
|
||||
POST /api/SalesDocument/{id}/validate
|
||||
|
||||
### UI BackOffice
|
||||
|
||||
Nueva pantalla:
|
||||
|
||||
SalesDocumentReview.razor
|
||||
|
||||
Ruta:
|
||||
|
||||
/salesdocuments/{id}/review
|
||||
|
||||
Debe mostrar:
|
||||
|
||||
- Cabecera del documento
|
||||
- Estado
|
||||
- Remitos origen
|
||||
- Cliente
|
||||
- Bill To Customer
|
||||
- Coverage
|
||||
- Items
|
||||
- Importes
|
||||
- Observaciones
|
||||
|
||||
Permitir:
|
||||
|
||||
- Guardar revisión
|
||||
- Validar documento
|
||||
|
||||
---
|
||||
|
||||
## Reglas de negocio
|
||||
|
||||
Un documento podrá validarse únicamente si:
|
||||
|
||||
- Tiene detalles.
|
||||
- Tiene importes válidos.
|
||||
- Posee cliente asignado.
|
||||
- Permanece en estado Draft.
|
||||
|
||||
Una vez validado:
|
||||
|
||||
Draft → Validated
|
||||
|
||||
No podrá modificarse mediante la pantalla de revisión.
|
||||
|
||||
---
|
||||
|
||||
## Fuera de alcance
|
||||
|
||||
- ARCA
|
||||
- AFIP
|
||||
- CAE
|
||||
- WSFE
|
||||
- Numeración fiscal
|
||||
- Factura A/B/C
|
||||
- Cálculo IVA
|
||||
- Condición fiscal
|
||||
- Notas de crédito
|
||||
- Notas de débito
|
||||
- PDF fiscal
|
||||
|
||||
---
|
||||
|
||||
## Criterios de aceptación
|
||||
|
||||
- Se puede visualizar un Draft completo.
|
||||
- Se puede revisar un Draft.
|
||||
- Se puede guardar la revisión.
|
||||
- Se puede validar un Draft.
|
||||
- El estado cambia a Validated.
|
||||
- No se modifican modelos EF generados.
|
||||
- Se mantienen contratos existentes.
|
||||
- Compila correctamente.
|
||||
|
||||
---
|
||||
|
||||
## Decisiones de diseño
|
||||
|
||||
- Sales Document continúa siendo la entidad principal.
|
||||
- No se crea entidad SalesDocumentDraft.
|
||||
- El concepto Draft se representa mediante Status.
|
||||
- La revisión administrativa se implementa como workflow sobre el documento existente.
|
||||
- La futura emisión fiscal se implementará en una story posterior.
|
||||
|
||||
---
|
||||
|
||||
## Entregable esperado
|
||||
|
||||
- Domain/*
|
||||
- Core/*
|
||||
- Models/*
|
||||
- API/*
|
||||
- UIBlazor/Pages/Sales/SalesDocuments/SalesDocumentReview.razor
|
||||
|
||||
### Branch sugerido
|
||||
|
||||
feature/leandro/74-sales-document-draft-review
|
||||
|
||||
### Commit sugerido
|
||||
|
||||
feat(sales): add sales document draft review and validation close #74
|
||||
@ -69,6 +69,79 @@ namespace phronCare.API.Controllers.Sales
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/draft-preview")]
|
||||
public async Task<ActionResult<SalesDocumentDraftPreviewDto>> GetDraftPreview(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var preview = await _salesDocumentService.GetDraftPreviewAsync(id);
|
||||
if (preview == null)
|
||||
return NotFound($"Sales Document con ID {id} no encontrado.");
|
||||
|
||||
return Ok(preview);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}/draft-review")]
|
||||
public async Task<ActionResult<SalesDocumentDraftPreviewDto>> UpdateDraftReview(int id, [FromBody] SalesDocumentDraftReviewDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request == null)
|
||||
return BadRequest("El payload no puede ser nulo.");
|
||||
|
||||
var preview = await _salesDocumentService.UpdateDraftReviewAsync(id, request);
|
||||
if (preview == null)
|
||||
return NotFound($"Sales Document con ID {id} no encontrado.");
|
||||
|
||||
return Ok(preview);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id:int}/validate")]
|
||||
public async Task<ActionResult<SalesDocumentDraftPreviewDto>> ValidateDraft(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var preview = await _salesDocumentService.ValidateDraftAsync(id);
|
||||
if (preview == null)
|
||||
return NotFound($"Sales Document con ID {id} no encontrado.");
|
||||
|
||||
return Ok(preview);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var methodName = MethodBase.GetCurrentMethod()?.Name ?? "UnknownMethod";
|
||||
return StatusCode(500, $"{methodName} Message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("delivery-note-candidates")]
|
||||
public async Task<ActionResult<PagedResult<SalesDocumentDeliveryNoteCandidateDto>>> SearchDeliveryNoteCandidates(
|
||||
|
||||
@ -9,9 +9,17 @@
|
||||
<div class="container mt-4" style="zoom:.8;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-0">Sales Document</h3>
|
||||
<button type="button" class="btn btn-secondary rounded-pill" @onclick="BackToList">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@if (Document?.Status == (int)SalesDocumentStatus.Draft)
|
||||
{
|
||||
<button type="button" class="btn btn-success rounded-pill" @onclick="ReviewDraft">
|
||||
<i class="fas fa-clipboard-check me-1"></i> Revisar draft
|
||||
</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary rounded-pill" @onclick="BackToList">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (IsLoading)
|
||||
@ -198,6 +206,7 @@
|
||||
}
|
||||
|
||||
private void BackToList() => Navigation.NavigateTo("/salesdocuments");
|
||||
private void ReviewDraft() => Navigation.NavigateTo($"/salesdocuments/{Id}/review");
|
||||
|
||||
private string GetOriginSummary()
|
||||
{
|
||||
|
||||
@ -0,0 +1,444 @@
|
||||
@page "/salesdocuments/{Id:int}/review"
|
||||
@using Domain.Constants
|
||||
@using Domain.Dtos.Sales
|
||||
@using phronCare.UIBlazor.Services.Sales.SalesDocuments
|
||||
@inject NavigationManager Navigation
|
||||
@inject ISalesDocumentService SalesDocumentService
|
||||
@inject IToastService toastService
|
||||
|
||||
<div class="sales-document-review container-fluid" style="zoom:.8;">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
|
||||
<div>
|
||||
<h3 class="mb-1">Revision administrativa</h3>
|
||||
<div class="text-muted">Sales Document Draft Review & Validation</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-secondary rounded-pill" @onclick="BackToList">
|
||||
<i class="fas fa-arrow-left me-1"></i> Volver
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-pill" @onclick="ViewDetail" disabled="@(Document is null)">
|
||||
<i class="fas fa-eye me-1"></i> Detalle
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body text-center text-muted py-4">Cargando...</div>
|
||||
</div>
|
||||
}
|
||||
else if (Document is null)
|
||||
{
|
||||
<div class="alert alert-warning">No se pudo cargar el Sales Document.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert @(IsDraft ? "alert-info" : "alert-secondary")">
|
||||
@if (IsDraft)
|
||||
{
|
||||
<span>Este documento esta en Draft y puede revisarse visualmente. Guardar y validar requieren los endpoints de backend fuera del alcance UI-only.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Este documento no esta en Draft. La revision queda en modo solo lectura.</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-8">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Cabecera del documento</h5>
|
||||
<span class="badge @GetStatusBadge(Document.Status)">@GetStatusLabel(Document.Status)</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold mb-1">Documento</label>
|
||||
<div class="form-control bg-white">@DocumentNumber</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold mb-1">Tipo</label>
|
||||
<div class="form-control bg-white">@GetDocumentTypeLabel(Document.DocumentType)</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold mb-1">Emision</label>
|
||||
<div class="form-control bg-white">@FormatDate(Document.IssueDate)</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold mb-1">Moneda</label>
|
||||
<div class="form-control bg-white">@Document.Currency</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Cliente</label>
|
||||
<div class="form-control bg-white">@Document.CustomerName</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Bill To Customer</label>
|
||||
<div class="form-control bg-white">@Document.BillToCustomerName</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Presupuesto</label>
|
||||
<div class="form-control bg-white">@(Document.QuoteId?.ToString() ?? "-")</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Periodo desde</label>
|
||||
<div class="form-control bg-white">@FormatDate(Document.PeriodFrom)</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Periodo hasta</label>
|
||||
<div class="form-control bg-white">@FormatDate(Document.PeriodTo)</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold mb-1">Cotizacion</label>
|
||||
<div class="form-control bg-white">@Document.ExchangeRate.ToString("N4")</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold mb-1">Observaciones</label>
|
||||
<textarea class="form-control" rows="4" @bind="ReviewObservations" disabled="@(!IsDraft)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Remitos origen</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (OriginDeliveryNotes.Any())
|
||||
{
|
||||
<div class="row g-2">
|
||||
@foreach (var item in OriginDeliveryNotes)
|
||||
{
|
||||
<div class="col-md-6">
|
||||
<div class="origin-card">
|
||||
<strong>@item.DeliveryNoteNumber</strong>
|
||||
<small>Remito ID @item.Id</small>
|
||||
<span>@FormatDate(item.IssueDate)</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-muted">No se detectaron remitos origen en el snapshot del documento.</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Items</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Origen</th>
|
||||
<th>Descripcion</th>
|
||||
<th class="text-end">Cantidad</th>
|
||||
<th class="text-end">Unitario</th>
|
||||
<th class="text-end">Neto</th>
|
||||
<th class="text-end">Impuesto</th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Document.Details.Any())
|
||||
{
|
||||
@foreach (var item in Document.Details.OrderBy(x => x.LineNumber))
|
||||
{
|
||||
<tr>
|
||||
<td class="text-center">@item.LineNumber</td>
|
||||
<td>@GetOriginTypeLabel(item.OriginType)</td>
|
||||
<td>@item.Description</td>
|
||||
<td class="text-end">@item.Quantity.ToString("N2")</td>
|
||||
<td class="text-end">@item.UnitPrice.ToString("N2")</td>
|
||||
<td class="text-end">@item.NetAmount.ToString("N2")</td>
|
||||
<td class="text-end">@item.TaxAmount.ToString("N2")</td>
|
||||
<td class="text-end">@item.TotalAmount.ToString("N2")</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">Sin items.</td></tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Coverage</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Presupuesto</th>
|
||||
<th>Quote Detail</th>
|
||||
<th class="text-end">Porcentaje</th>
|
||||
<th class="text-end">Importe</th>
|
||||
<th>Desde</th>
|
||||
<th>Hasta</th>
|
||||
<th>Notas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (Document.Coverage.Any())
|
||||
{
|
||||
@foreach (var coverage in Document.Coverage)
|
||||
{
|
||||
<tr>
|
||||
<td>@GetCoverageTypeLabel(coverage.CoverageType)</td>
|
||||
<td>@coverage.QuoteId</td>
|
||||
<td>@(coverage.QuoteDetailId?.ToString() ?? "-")</td>
|
||||
<td class="text-end">@(coverage.CoveragePercentage?.ToString("N2") ?? "-")</td>
|
||||
<td class="text-end">@(coverage.CoverageAmount?.ToString("N2") ?? "-")</td>
|
||||
<td>@FormatDate(coverage.PeriodFrom)</td>
|
||||
<td>@FormatDate(coverage.PeriodTo)</td>
|
||||
<td>@(string.IsNullOrWhiteSpace(coverage.Notes) ? "-" : coverage.Notes)</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">Sin coverage informado.</td></tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Validacion del Draft</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="validation-list">
|
||||
@foreach (var item in ValidationItems)
|
||||
{
|
||||
<li class="@(item.IsValid ? "valid" : "invalid")">
|
||||
<i class="fas @(item.IsValid ? "fa-check-circle" : "fa-circle-exclamation")"></i>
|
||||
<span>@item.Message</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Importes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="amount-row">
|
||||
<span>Neto</span>
|
||||
<strong>@Document.NetAmount.ToString("N2")</strong>
|
||||
</div>
|
||||
<div class="amount-row">
|
||||
<span>Impuestos</span>
|
||||
<strong>@Document.TaxAmount.ToString("N2")</strong>
|
||||
</div>
|
||||
<div class="amount-row total">
|
||||
<span>Total</span>
|
||||
<strong>@Document.Currency @Document.TotalAmount.ToString("N2")</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Acciones</h5>
|
||||
</div>
|
||||
<div class="card-body d-grid gap-2">
|
||||
<button type="button" class="btn btn-primary rounded-pill" @onclick="SaveReviewAsync" disabled="@(!IsDraft || IsSaving)">
|
||||
<i class="fas fa-save me-1"></i> Guardar revision
|
||||
</button>
|
||||
<button type="button" class="btn btn-success rounded-pill" @onclick="ValidateDraftAsync" disabled="@(!CanValidate || IsSaving)">
|
||||
<i class="fas fa-check-circle me-1"></i> Validar documento
|
||||
</button>
|
||||
<div class="small text-muted mt-2">
|
||||
Guardar revision persiste los campos editables del Draft. Validar cambia el estado a Validated.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public int Id { get; set; }
|
||||
|
||||
private SalesDocumentDto? Document;
|
||||
private bool IsLoading;
|
||||
private bool IsSaving;
|
||||
private string? ReviewObservations;
|
||||
private List<DeliveryNoteSummaryDto> OriginDeliveryNotes = new();
|
||||
private SalesDocumentDraftValidationDto DraftValidation = new();
|
||||
|
||||
private bool IsDraft => Document?.Status == (int)SalesDocumentStatus.Draft;
|
||||
private bool CanValidate => IsDraft && ValidationItems.All(x => x.IsValid);
|
||||
private string DocumentNumber => string.IsNullOrWhiteSpace(Document?.InternalDocumentNumber) ? $"#{Document?.Id}" : Document.InternalDocumentNumber;
|
||||
|
||||
private List<ValidationItem> ValidationItems =>
|
||||
[
|
||||
new(DraftValidation.HasDetails, "Tiene detalles"),
|
||||
new(DraftValidation.HasValidAmounts, "Tiene importes validos"),
|
||||
new(DraftValidation.HasCustomer, "Posee cliente asignado"),
|
||||
new(DraftValidation.IsDraft, "Permanece en estado Draft")
|
||||
];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task LoadAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
var preview = await SalesDocumentService.GetDraftPreviewAsync(Id);
|
||||
ApplyPreview(preview);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toastService.ShowError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveReviewAsync()
|
||||
{
|
||||
if (Document is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IsSaving = true;
|
||||
var preview = await SalesDocumentService.UpdateDraftReviewAsync(Id, new SalesDocumentDraftReviewDto
|
||||
{
|
||||
IssueDate = Document.IssueDate,
|
||||
AssociatedDocumentType = Document.AssociatedDocumentType,
|
||||
AssociatedDocumentNumber = Document.AssociatedDocumentNumber,
|
||||
AssociatedDocumentDate = Document.AssociatedDocumentDate,
|
||||
Observations = ReviewObservations,
|
||||
PeriodFrom = Document.PeriodFrom,
|
||||
PeriodTo = Document.PeriodTo
|
||||
});
|
||||
|
||||
ApplyPreview(preview);
|
||||
toastService.ShowSuccess("Revision guardada correctamente.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toastService.ShowError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateDraftAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsSaving = true;
|
||||
var preview = await SalesDocumentService.ValidateDraftAsync(Id);
|
||||
ApplyPreview(preview);
|
||||
toastService.ShowSuccess("Sales Document validado correctamente.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toastService.ShowError(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void BackToList() => Navigation.NavigateTo("/salesdocuments");
|
||||
private void ViewDetail() => Navigation.NavigateTo($"/salesdocuments/{Id}");
|
||||
|
||||
private void ApplyPreview(SalesDocumentDraftPreviewDto? preview)
|
||||
{
|
||||
Document = preview?.Document;
|
||||
ReviewObservations = Document?.Observations;
|
||||
OriginDeliveryNotes = preview?.OriginDeliveryNotes ?? new List<DeliveryNoteSummaryDto>();
|
||||
DraftValidation = preview?.Validation ?? new SalesDocumentDraftValidationDto();
|
||||
}
|
||||
|
||||
private static string FormatDate(DateTime? value) => value.HasValue ? value.Value.ToString("dd/MM/yyyy") : "-";
|
||||
|
||||
private static string GetDocumentTypeLabel(int value) => Enum.IsDefined(typeof(SalesDocumentType), value)
|
||||
? ((SalesDocumentType)value) switch
|
||||
{
|
||||
SalesDocumentType.Invoice => "Factura",
|
||||
SalesDocumentType.DebitNote => "Nota de debito",
|
||||
SalesDocumentType.CreditNote => "Nota de credito",
|
||||
SalesDocumentType.CreditInvoice => "Factura credito",
|
||||
SalesDocumentType.CreditDebitNote => "N/D credito",
|
||||
SalesDocumentType.CreditCreditNote => "N/C credito",
|
||||
_ => value.ToString()
|
||||
}
|
||||
: value.ToString();
|
||||
|
||||
private static string GetStatusLabel(int value) => Enum.IsDefined(typeof(SalesDocumentStatus), value)
|
||||
? ((SalesDocumentStatus)value) switch
|
||||
{
|
||||
SalesDocumentStatus.Draft => "Borrador",
|
||||
SalesDocumentStatus.Validated => "Validado",
|
||||
SalesDocumentStatus.Issued => "Emitido",
|
||||
SalesDocumentStatus.Cancelled => "Anulado",
|
||||
_ => value.ToString()
|
||||
}
|
||||
: value.ToString();
|
||||
|
||||
private static string GetCoverageTypeLabel(int value) => Enum.IsDefined(typeof(SalesDocumentCoverageType), value)
|
||||
? ((SalesDocumentCoverageType)value) switch
|
||||
{
|
||||
SalesDocumentCoverageType.Direct => "Directa",
|
||||
SalesDocumentCoverageType.Capita => "Capita",
|
||||
SalesDocumentCoverageType.Adjustment => "Ajuste",
|
||||
SalesDocumentCoverageType.Manual => "Manual",
|
||||
_ => value.ToString()
|
||||
}
|
||||
: value.ToString();
|
||||
|
||||
private static string GetOriginTypeLabel(string value) => value switch
|
||||
{
|
||||
"MANUAL" => "Manual",
|
||||
"QUOTE" => "Presupuesto",
|
||||
"ADJUSTMENT" => "Ajuste",
|
||||
"CAPITA" => "Capita",
|
||||
"DELIVERY_NOTE" => "Remito",
|
||||
_ => string.IsNullOrWhiteSpace(value) ? "-" : value
|
||||
};
|
||||
|
||||
private static string GetStatusBadge(int value) => value switch
|
||||
{
|
||||
(int)SalesDocumentStatus.Draft => "bg-secondary text-white",
|
||||
(int)SalesDocumentStatus.Validated => "bg-info text-dark",
|
||||
(int)SalesDocumentStatus.Issued => "bg-primary text-white",
|
||||
(int)SalesDocumentStatus.Cancelled => "bg-danger text-white",
|
||||
_ => "bg-light text-dark"
|
||||
};
|
||||
|
||||
private sealed record ValidationItem(bool IsValid, string Message);
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
.sales-document-review {
|
||||
color: var(--text-background);
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sales-document-review .card {
|
||||
background-color: var(--background-highlight);
|
||||
border-color: var(--background-highlight-light);
|
||||
}
|
||||
|
||||
.sales-document-review .card-header,
|
||||
.sales-document-review .table-light {
|
||||
background-color: var(--background-highlight-light);
|
||||
color: var(--text-background);
|
||||
}
|
||||
|
||||
.sales-document-review .card-body,
|
||||
.sales-document-review .table,
|
||||
.sales-document-review .form-control {
|
||||
color: var(--text-background);
|
||||
}
|
||||
|
||||
.sales-document-review .form-control {
|
||||
background-color: var(--background);
|
||||
border-color: var(--background-highlight-light);
|
||||
}
|
||||
|
||||
.origin-card {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
min-height: 86px;
|
||||
padding: 0.85rem;
|
||||
}
|
||||
|
||||
.origin-card strong {
|
||||
color: var(--text-background);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.origin-card small,
|
||||
.origin-card span {
|
||||
color: var(--text-background);
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.validation-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.65rem;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.validation-list li {
|
||||
align-items: center;
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--background-highlight-light);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 0.55rem;
|
||||
padding: 0.7rem 0.8rem;
|
||||
}
|
||||
|
||||
.validation-list li.valid i {
|
||||
color: #138f77;
|
||||
}
|
||||
|
||||
.validation-list li.invalid i {
|
||||
color: #cf4435;
|
||||
}
|
||||
|
||||
.amount-row {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--background-highlight-light);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.amount-row:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.amount-row.total {
|
||||
border-bottom: 0;
|
||||
font-size: 1.05rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sales-document-review {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
@ -100,6 +100,10 @@
|
||||
<td>@document.TotalAmount.ToString("N2")</td>
|
||||
<td class="text-center align-middle">
|
||||
<button class="btn btn-link btn-lg p-0 text-primary ms-2" title="Ver detalle" @onclick="() => Detail(document.Id)"><i class="fas fa-eye"></i></button>
|
||||
@if (document.Status == (int)SalesDocumentStatus.Draft)
|
||||
{
|
||||
<button class="btn btn-link btn-lg p-0 text-success ms-2" title="Revisar draft" @onclick="() => Review(document.Id)"><i class="fas fa-clipboard-check"></i></button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -203,6 +207,7 @@
|
||||
|
||||
private void Create() => Navigation.NavigateTo("/salesdocuments/create");
|
||||
private void Detail(int id) => Navigation.NavigateTo($"/salesdocuments/{id}");
|
||||
private void Review(int id) => Navigation.NavigateTo($"/salesdocuments/{id}/review");
|
||||
|
||||
private void OnClear()
|
||||
{
|
||||
|
||||
@ -7,6 +7,9 @@ namespace phronCare.UIBlazor.Services.Sales.SalesDocuments
|
||||
{
|
||||
Task<PagedResult<SalesDocumentSummaryDto>> SearchAsync(SalesDocumentSearchParams searchParams);
|
||||
Task<SalesDocumentDto?> GetByIdAsync(int id);
|
||||
Task<SalesDocumentDraftPreviewDto?> GetDraftPreviewAsync(int id);
|
||||
Task<SalesDocumentDraftPreviewDto> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto request);
|
||||
Task<SalesDocumentDraftPreviewDto> ValidateDraftAsync(int id);
|
||||
Task<SalesDocumentDto> CreateAsync(SalesDocumentCreateRequest request);
|
||||
Task<SalesDocumentDto> CreateFromDeliveryNotesAsync(SalesDocumentCreateFromDeliveryNotesRequest request);
|
||||
Task<PagedResult<SalesDocumentDeliveryNoteCandidateDto>> SearchDeliveryNoteCandidatesAsync(
|
||||
|
||||
@ -102,6 +102,45 @@ namespace phronCare.UIBlazor.Services.Sales.SalesDocuments
|
||||
return await _http.GetFromJsonAsync<SalesDocumentDto>($"/api/SalesDocument/{id}");
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto?> GetDraftPreviewAsync(int id)
|
||||
{
|
||||
return await _http.GetFromJsonAsync<SalesDocumentDraftPreviewDto>($"/api/SalesDocument/{id}/draft-preview");
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto> UpdateDraftReviewAsync(int id, SalesDocumentDraftReviewDto request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var response = await _http.PutAsJsonAsync($"/api/SalesDocument/{id}/draft-review", request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var serverMessage = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception(string.IsNullOrWhiteSpace(serverMessage)
|
||||
? "No se pudo guardar la revision del Sales Document."
|
||||
: serverMessage);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<SalesDocumentDraftPreviewDto>();
|
||||
return result ?? throw new Exception("Respuesta vacia del servidor.");
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDraftPreviewDto> ValidateDraftAsync(int id)
|
||||
{
|
||||
var response = await _http.PostAsync($"/api/SalesDocument/{id}/validate", null);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var serverMessage = await response.Content.ReadAsStringAsync();
|
||||
throw new Exception(string.IsNullOrWhiteSpace(serverMessage)
|
||||
? "No se pudo validar el Sales Document."
|
||||
: serverMessage);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<SalesDocumentDraftPreviewDto>();
|
||||
return result ?? throw new Exception("Respuesta vacia del servidor.");
|
||||
}
|
||||
|
||||
public async Task<SalesDocumentDto> CreateAsync(SalesDocumentCreateRequest request)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user