first commit
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user