All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (push) Successful in 3m27s
382 lines
17 KiB
C#
382 lines
17 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) Execute paged query
|
|
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
|
|
{
|
|
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
|
|
}
|
|
} |