Files
Core_Be/Services/CustomerInvoiceService.cs
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

232 lines
10 KiB
C#

using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Interfaces.Services;
using Fitness_core_BE.Models;
using Fitness_core_BE.Infra.UoW;
using System.Text.Json;
using Fitness_core_BE.Helpers;
namespace Fitness_core_BE.Services.CustomerInvoice
{
public class CustomerInvoiceService : ICustomerInvoiceService
{
private readonly ICustomerInvoiceRepository _customerInvoiceRepository;
private readonly IPaymentRepository _paymentRepository;
private readonly IUnitOfWork _uow;
private readonly UserRoleHelper _userRoleHelper;
public CustomerInvoiceService(ICustomerInvoiceRepository customerInvoiceRepository, IPaymentRepository paymentRepository, IUnitOfWork uow, UserRoleHelper userRoleHelper)
{
_customerInvoiceRepository = customerInvoiceRepository;
_paymentRepository = paymentRepository;
_uow = uow;
_userRoleHelper = userRoleHelper;
}
public async Task<object> CreateCustomerInvoiceFromSubscription(object payload, HttpContext httpContext)
{
await _uow.BeginAsync();
try
{
var data = DeserializePayload.Deserialize(payload);
if (!data!.ContainsKey("subscriptionId"))
{
throw new KeyNotFoundException("subscriptionId is required");
}
var subscriptionId = Guid.Parse(data["subscriptionId"].GetString()!);
var result = await _customerInvoiceRepository.CreateCustomerInvoiceFromSubscription(subscriptionId);
await _uow.CommitAsync();
return result;
}
catch
{
await _uow.RollbackAsync();
throw;
}
}
public async Task<object> GetCustomerInvoiceById(object payload, HttpContext httpContext)
{
var data = DeserializePayload.Deserialize(payload);
if (!data!.ContainsKey("customerInvoiceId"))
{
throw new KeyNotFoundException("customerInvoiceId is required");
}
var customerInvoiceId = Guid.Parse(data["customerInvoiceId"].GetString()!);
var customerInvoice = await _customerInvoiceRepository.GetCustomerInvoiceById(customerInvoiceId);
if (customerInvoice == null)
{
throw new KeyNotFoundException("CustomerInvoice not found");
}
var paid = await _paymentRepository.GetApprovedPaidAmountAsync(customerInvoice.CustomerInvoiceId);
var carriedForward = await _customerInvoiceRepository.GetCarriedForwardAmountsAsync(
new List<Guid> { customerInvoice.CustomerInvoiceId });
carriedForward.TryGetValue(customerInvoice.CustomerInvoiceId, out var carriedForwardAmount);
var link = customerInvoice.CustomerSubscription?.TenantCustomerLink;
return new
{
customerInvoice.CustomerInvoiceId,
customerInvoice.FKCustomerSubscriptionId,
customerInvoice.FKClientId,
customerInvoice.FKUserId,
customerInvoice.Amount,
// The portion of Amount that's a prior cycle's unpaid balance rolled forward
// (vs. this cycle's own charge) -- see SubscriptionRenewalRunner/BuildAndAddInvoice.
CarriedForwardAmount = carriedForwardAmount,
customerInvoice.Status,
customerInvoice.CustomerInvoiceDate,
PaidAmount = paid,
Balance = customerInvoice.Amount - paid,
// Flattened for callers that don't want to reach through
// CustomerSubscription.TenantCustomerLink for basic display fields.
CustomerName = link?.DisplayName,
CustomerPhone = link?.PrimaryPhoneCached,
customerInvoice.CustomerSubscription
};
}
public async Task<object> GetCustomerInvoicesByCustomerId(object payload, HttpContext httpContext)
{
var data = DeserializePayload.Deserialize(payload);
if (!data!.ContainsKey("customerId"))
{
throw new KeyNotFoundException("customerId is required");
}
var customerId = Guid.Parse(data["customerId"].GetString()!);
var invoices = await _customerInvoiceRepository.GetCustomerInvoicesByCustomerId(customerId);
// Same enriched shape as GetCustomerInvoicesByClientId -- this now backs a public
// "view previous invoices" route the frontend renders as MemberInvoice rows.
var result = new List<object>(invoices.Count);
foreach (var invoice in invoices)
{
var paid = await _paymentRepository.GetApprovedPaidAmountAsync(invoice.CustomerInvoiceId);
var link = invoice.CustomerSubscription?.TenantCustomerLink;
result.Add(new
{
invoice.CustomerInvoiceId,
invoice.FKCustomerSubscriptionId,
invoice.FKClientId,
invoice.Amount,
invoice.Status,
invoice.IsActive,
invoice.CustomerInvoiceDate,
PaidAmount = paid,
Balance = invoice.Amount - paid,
CustomerName = link?.DisplayName,
CustomerPhone = link?.PrimaryPhoneCached,
FKCustomerId = invoice.CustomerSubscription?.FKCustomerId,
invoice.CustomerSubscription
});
}
return result;
}
public async Task<object> GetCustomerInvoicesByClientId(object payload, HttpContext httpContext)
{
Guid userId = DataExtractor.GetUserIdFromContext(httpContext);
Guid clientId = await _userRoleHelper.GetClientId(userId);
// scope=="latest": one row per customer (their latest approved invoice) instead of
// every invoice they've ever had -- used by the regular invoices tab now that the
// renewal-approval workflow can leave older invoices (Paid/RolledOver/Cancelled)
// sitting alongside the current one. Default (no scope) keeps today's full-history
// behavior for any other existing caller of this endpoint.
var data = DeserializePayload.Deserialize(payload);
var scope = data != null && data.ContainsKey("scope") ? data["scope"].GetString() : null;
var invoices = scope == "latest"
? await _customerInvoiceRepository.GetLatestApprovedInvoicePerCustomerAsync(clientId)
: await _customerInvoiceRepository.GetCustomerInvoicesByClientId(clientId);
// Enrich each invoice with its approved paid total and outstanding balance,
// preserving the nested shape the frontend reads.
var result = new List<object>(invoices.Count);
foreach (var invoice in invoices)
{
var paid = await _paymentRepository.GetApprovedPaidAmountAsync(invoice.CustomerInvoiceId);
var link = invoice.CustomerSubscription?.TenantCustomerLink;
result.Add(new
{
invoice.CustomerInvoiceId,
invoice.FKCustomerSubscriptionId,
invoice.FKClientId,
invoice.Amount,
invoice.Status,
invoice.IsActive,
invoice.CustomerInvoiceDate,
PaidAmount = paid,
Balance = invoice.Amount - paid,
// Flattened for callers that don't want to reach through
// CustomerSubscription.TenantCustomerLink for basic display fields.
CustomerName = link?.DisplayName,
CustomerPhone = link?.PrimaryPhoneCached,
FKCustomerId = invoice.CustomerSubscription?.FKCustomerId,
invoice.CustomerSubscription
});
}
return result;
}
public async Task<object> UpdateCustomerInvoiceStatus(object payload, HttpContext httpContext)
{
await _uow.BeginAsync();
try
{
var data = DeserializePayload.Deserialize(payload);
if (!data!.ContainsKey("customerInvoiceId") || !data.ContainsKey("status"))
{
throw new KeyNotFoundException("customerInvoiceId and status are required");
}
var customerInvoiceId = Guid.Parse(data["customerInvoiceId"].GetString()!);
var status = data["status"].GetString()!;
// Validate status
var validStatuses = new[] { "Pending", "Partial", "Paid", "Failed", "Cancelled", "Refunded", "RolledOver" };
if (!validStatuses.Contains(status))
{
throw new InvalidOperationException($"Invalid status. Valid statuses are: {string.Join(", ", validStatuses)}");
}
var result = await _customerInvoiceRepository.UpdateCustomerInvoiceStatus(customerInvoiceId, status);
await _uow.CommitAsync();
return result;
}
catch
{
await _uow.RollbackAsync();
throw;
}
}
public async Task<object> CalculateCustomerInvoiceTotal(object payload, HttpContext httpContext)
{
var data = DeserializePayload.Deserialize(payload);
if (!data!.ContainsKey("customerInvoiceId"))
{
throw new KeyNotFoundException("customerInvoiceId is required");
}
var customerInvoiceId = Guid.Parse(data["customerInvoiceId"].GetString()!);
var customerInvoice = await _customerInvoiceRepository.GetCustomerInvoiceById(customerInvoiceId);
var total = customerInvoice?.Amount ?? 0;
return new { Total = total };
}
}
}