phronCare/Models/Repositories/PhSQuoteRepository.cs
Leandro Hernan Rojas ca229ed6cb
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 9m49s
Update UI Chart, Update Quote order
2025-06-09 11:32:32 -03:00

435 lines
19 KiB
C#

using Microsoft.EntityFrameworkCore;
using Domain.Entities;
using Domain.Generics;
using Models.Interfaces;
using Models.Helpers;
using Models.Models;
using Domain.Dtos;
namespace Models.Repositories
{
public class PhSQuoteRepository(PhronCareOperationsHubContext context,
IPhSFormSeriesRepository formSeriesRepository) : IQuoteRepository
{
private readonly PhronCareOperationsHubContext _context = context;
private readonly IPhSFormSeriesRepository _formSeriesRepository = formSeriesRepository;
#region Busqueda usando el QuoteDto
public async Task<PagedResult<QuoteDto>> SearchAsync(
int? customerId,
string? customerText,
string? quoteNumber,
int? professionalId,
string? professionalText,
int? institutionId,
string? institutionText,
int? patientId,
string? patientText,
DateTime? issueDateFrom,
DateTime? issueDateTo,
string? status,
int page = 1,
int pageSize = 50)
{
// 1) Base query with includes
var query = _context.PhSQuoteHeaders
.Include(q => q.PhSQuoteDetails)
.Include(q => q.PhSQuoteRoles)
.Include(q => q.PhSQuoteAdjustments)
.Include(q => q.PhSQuoteTaxes)
.AsQueryable();
// 2) Apply filters
if (customerId.HasValue)
query = query.Where(q => q.CustomerId == customerId.Value);
else if (!string.IsNullOrWhiteSpace(customerText))
query = query.Where(q =>
_context.PhSCustomers.Any(c =>
c.Id == q.CustomerId &&
c.Name.Contains(customerText)));
if (!string.IsNullOrWhiteSpace(quoteNumber))
query = query.Where(q => q.Quotenumber.Contains(quoteNumber));
if (professionalId.HasValue)
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Professional &&
r.EntityId == professionalId.Value));
}
else if (!string.IsNullOrWhiteSpace(professionalText))
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Professional &&
_context.PhSProfessionals.Any(p =>
p.Id == r.EntityId &&
p.Fullname.Contains(professionalText))));
}
if (institutionId.HasValue)
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Institution &&
r.EntityId == institutionId.Value));
}
else if (!string.IsNullOrWhiteSpace(institutionText))
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Institution &&
_context.PhSInstitutions.Any(i =>
i.Id == r.EntityId &&
i.Name.Contains(institutionText))));
}
if (patientId.HasValue)
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Patient &&
r.EntityId == patientId.Value));
}
else if (!string.IsNullOrWhiteSpace(patientText))
{
query = query.Where(q =>
q.PhSQuoteRoles.Any(r =>
r.Entitytype == PhSEntityTypes.Patient &&
(_context.PhSPatients.Any(pt =>
pt.Id == r.EntityId &&
pt.Firstname.Contains(patientText)) ||
_context.PhSPatients.Any(pt =>
pt.Id == r.EntityId &&
pt.Lastname.Contains(patientText)))));
}
if (issueDateFrom.HasValue)
query = query.Where(q => q.Issuedate >= issueDateFrom.Value);
if (issueDateTo.HasValue)
query = query.Where(q => q.Issuedate <= issueDateTo.Value);
if (!string.IsNullOrWhiteSpace(status))
query = query.Where(q => q.Status == status);
// 3) Order by most recent and execute paged query
query = query.OrderByDescending(q => q.Issuedate);
var pagedEntities = await query.ToPagedResultAsync(page, pageSize);
// 4) Project to DTOs
var dtos = pagedEntities.Items.Select(header =>
{
// Precompute tax sums
var totalTaxAmount = header.PhSQuoteTaxes.Sum(t => t.Taxamount);
var netBase = header.Netamount.GetValueOrDefault() != 0m
? header.Netamount.Value
: 1m;
return new QuoteDto
{
Id = header.Id,
Quotenumber = header.Quotenumber,
IssueDate = header.Issuedate,
EstimatedDate = header.Estimateddate,
CustomerName = _context.PhSCustomers
.Where(c => c.Id == header.CustomerId)
.Select(c => c.Name)
.FirstOrDefault() ?? "",
ProfessionalName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Professional)
.Select(r => _context.PhSProfessionals
.Where(p => p.Id == r.EntityId)
.Select(p => p.Fullname)
.FirstOrDefault())
.FirstOrDefault() ?? "",
InstitutionName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Institution)
.Select(r => _context.PhSInstitutions
.Where(i => i.Id == r.EntityId)
.Select(i => i.Name)
.FirstOrDefault())
.FirstOrDefault() ?? "",
PatientName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Patient)
.Select(r => _context.PhSPatients
.Where(pt => pt.Id == r.EntityId)
.Select(pt => (pt.Firstname + " " + pt.Lastname).Trim())
.FirstOrDefault())
.FirstOrDefault() ?? "",
BusinessUnitName = _context.PhSBusinessUnits
.Where(b => b.Id == header.BusinessunitId)
.Select(b => b.Code)
.FirstOrDefault() ?? "",
Currency = header.Currency,
Total = header.Total.GetValueOrDefault(0m),
Status = header.Status.Trim(),
Observations=header.Observations,
SalespersonName = _context.PhSPeople
.Where(u => u.Id == header.PeopleId)
.Select(u => u.Name)
.FirstOrDefault() ?? "",
Items = header.PhSQuoteDetails.Select(d =>
{
var itemBase = d.Quantity * d.Unitprice;
var itemTax = totalTaxAmount * itemBase / netBase;
return new QuoteItemDto
{
Description = d.ProductDescription,
Quantity = d.Quantity,
UnitPrice = d.Unitprice,
Subtotal = itemBase,
TaxAmount = itemTax,
Total = itemBase + itemTax
};
}).ToList(),
Taxes = header.PhSQuoteTaxes.Select(t => new QuoteTaxDto
{
TaxName = t.Taxname,
TaxCode = t.Taxcode,
TaxableAmount = t.TaxableAmount,
TaxRate = t.Taxrate,
TaxAmount = t.Taxamount,
IsIncludedInPrice = t.IsIncludedInPrice
}).ToList(),
Adjustments = header.PhSQuoteAdjustments.Select(a => new QuoteAdjustmentDto
{
Reason = _context.PhSAdjustmentReasons
.Where(r => r.Code == a.ReasonCode)
.Select(r => r.Description)
.FirstOrDefault() ?? "",
Amount = a.Amount.GetValueOrDefault(0m)
}).ToList()
};
}).ToList();
// 5) Return paged DTO result
return new PagedResult<QuoteDto>
{
Items = dtos,
TotalItems = pagedEntities.TotalItems,
Page = pagedEntities.Page,
PageSize = pagedEntities.PageSize
};
}
public async Task<QuoteDto?> GetDtoByIdAsync(int id)
{
var header = await _context.PhSQuoteHeaders
.Include(q => q.PhSQuoteDetails)
.Include(q => q.PhSQuoteRoles)
.Include(q => q.PhSQuoteAdjustments)
.Include(q => q.PhSQuoteTaxes)
.FirstOrDefaultAsync(q => q.Id == id);
if (header == null)
return null;
// Cargar Customer completo con documentos y tipos
var customer = await _context.PhSCustomers
.Include(c => c.PhSCustomerDocuments)
.ThenInclude(d => d.Documenttypes)
.Include(c => c.PhSCustomerAddresses)
.Include(c => c.TaxCondition)
.FirstOrDefaultAsync(c => c.Id == header.CustomerId);
var totalTaxAmount = header.PhSQuoteTaxes.Sum(t => t.Taxamount);
var netBase = header.Netamount.GetValueOrDefault() != 0m
? header.Netamount.Value
: 1m;
var dto = new QuoteDto
{
Id = header.Id,
Quotenumber = header.Quotenumber,
IssueDate = header.Issuedate,
EstimatedDate = header.Estimateddate,
OfferValidityDays = header.Offervaliditydays,
PaymentTermDescription = _context.PhSPaymentTerms
.Where(p => p.Id == header.PaymenttermId)
.Select(p => p.Description)
.FirstOrDefault() ?? "",
BusinessUnitName = _context.PhSBusinessUnits
.Where(b => b.Id == header.BusinessunitId)
.Select(b => b.Code)
.FirstOrDefault() ?? "",
Currency = header.Currency,
Total = header.Total.GetValueOrDefault(0m),
Status = header.Status.Trim(),
Observations = header.Observations ?? "",
CustomerName = customer?.Name ?? "",
ProfessionalName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Professional)
.Select(r => _context.PhSProfessionals
.Where(p => p.Id == r.EntityId)
.Select(p => p.Fullname)
.FirstOrDefault())
.FirstOrDefault() ?? "",
InstitutionName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Institution)
.Select(r => _context.PhSInstitutions
.Where(i => i.Id == r.EntityId)
.Select(i => i.Name)
.FirstOrDefault())
.FirstOrDefault() ?? "",
PatientName = header.PhSQuoteRoles
.Where(r => r.Entitytype == PhSEntityTypes.Patient)
.Select(r => _context.PhSPatients
.Where(pt => pt.Id == r.EntityId)
.Select(pt => (pt.Firstname + " " + pt.Lastname).Trim())
.FirstOrDefault())
.FirstOrDefault() ?? "",
SalespersonName = _context.PhSPeople
.Where(u => u.Id == header.PeopleId)
.Select(u => u.Name)
.FirstOrDefault() ?? "",
Items = header.PhSQuoteDetails.Select(d =>
{
var itemBase = d.Quantity * d.Unitprice;
var itemTax = totalTaxAmount * itemBase / netBase;
return new QuoteItemDto
{
Id = d.Id,
Description = d.ProductDescription,
Quantity = d.Quantity,
UnitPrice = d.Unitprice,
Subtotal = itemBase,
TaxAmount = itemTax,
Total = itemBase + itemTax
};
}).ToList(),
Taxes = header.PhSQuoteTaxes.Select(t => new QuoteTaxDto
{
TaxName = t.Taxname,
TaxCode = t.Taxcode,
TaxableAmount = t.TaxableAmount,
TaxRate = t.Taxrate,
TaxAmount = t.Taxamount,
IsIncludedInPrice = t.IsIncludedInPrice
}).ToList(),
Adjustments = header.PhSQuoteAdjustments.Select(a => new QuoteAdjustmentDto
{
Reason = _context.PhSAdjustmentReasons
.Where(r => r.Code == a.ReasonCode)
.Select(r => r.Description)
.FirstOrDefault() ?? "",
Amount = a.Amount.GetValueOrDefault(0m)
}).ToList(),
Customer = new QuoteCustomerDto
{
Name = customer?.Name ?? "",
Address = customer?.PhSCustomerAddresses
.Select(a => a.Streetaddress1)
.FirstOrDefault() ?? "",
IvaCondition = customer?.TaxCondition?.Description ?? "",
Documents = customer?.PhSCustomerDocuments
.Select(d => new QuoteCustomerDocumentDto
{
DocumentType = d.Documenttypes.Name,
Number = d.DocumentNumber
}).ToList() ?? new()
}
};
return dto;
}
#endregion
#region Guardado completo de presupuesto (encabezado + detalles + roles + ajustes + impuestos)
/// <summary>
/// Crea un nuevo presupuesto, incluyendo encabezado, detalles, roles, ajustes e impuestos asociados.
/// Genera automáticamente el número de presupuesto en base a la serie indicada.
/// <param name="quote">Presupuesto a registrar, incluyendo entidades relacionadas.</param>
/// <param name="formSeriesId">Identificador de la serie de numeración a utilizar.</param>
/// <returns>Cadena con el número generado del presupuesto.</returns>
/// </summary>
public async Task<(int Id, string Quotenumber)> CreateFullQuoteAsync(EQuoteHeader quote, int formSeriesId)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var nextNumber = await _formSeriesRepository.GetNextInternalNumberAsync(formSeriesId);
var series = await _formSeriesRepository.GetByIdAsync(formSeriesId)
?? throw new InvalidOperationException("Serie no encontrada");
int padding = 8; /* format "00000000" */
quote.Status = "Emitido";
quote.Quotenumber = $"{series.Letter}-{nextNumber.ToString($"D{padding}")}";
var headerEntity = EntityMapper.MapEntity<EQuoteHeader, PhSQuoteHeader>(quote);
_context.PhSQuoteHeaders.Add(headerEntity);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
return (headerEntity.Id, headerEntity.Quotenumber); ;
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
#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
}
}