Files
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

234 lines
11 KiB
C#

using Fitness_core_BE.Exceptions;
using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Interfaces.Services;
using Fitness_core_BE.Models;
using Fitness_core_BE.Models.Receipt;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace Fitness_core_BE.Services
{
public class ReceiptService : IReceiptService
{
private readonly ICustomerInvoiceRepository _customerInvoiceRepository;
private readonly ICustomerSubscriptionRepository _customerSubscriptionRepository;
private readonly IClientRepository _clientRepository;
private readonly IClientInvoiceRepository _clientInvoiceRepository;
private readonly IPaymentRepository _paymentRepository;
private readonly IThirdPartyService _thirdPartyService;
public ReceiptService(
ICustomerInvoiceRepository customerInvoiceRepository,
ICustomerSubscriptionRepository customerSubscriptionRepository,
IClientRepository clientRepository,
IClientInvoiceRepository clientInvoiceRepository,
IPaymentRepository paymentRepository,
IThirdPartyService thirdPartyService)
{
_customerInvoiceRepository = customerInvoiceRepository;
_customerSubscriptionRepository = customerSubscriptionRepository;
_clientRepository = clientRepository;
_clientInvoiceRepository = clientInvoiceRepository;
_paymentRepository = paymentRepository;
_thirdPartyService = thirdPartyService;
}
public async Task<ReceiptModel> BuildForInvoiceAsync(Guid customerInvoiceId, decimal? paymentAmount = null, Guid? requireClientId = null)
{
var invoice = await _customerInvoiceRepository.GetCustomerInvoiceById(customerInvoiceId)
?? throw new NotFoundException("Invoice not found");
if (requireClientId.HasValue && invoice.FKClientId != requireClientId.Value)
throw new UnauthorizedException("This invoice does not belong to your gym.");
var subscription = await _customerSubscriptionRepository.GetCustomerSubscriptionById(invoice.FKCustomerSubscriptionId);
string planName = subscription?.CustomerPlan?.PlanName ?? "Membership";
Guid? customerId = subscription?.FKCustomerId;
Customer? customer = customerId.HasValue ? await _clientRepository.GetCustomerById(customerId.Value) : null;
var client = await _clientRepository.GetClientByIdAsync(invoice.FKClientId);
string gymName = client?.ClientBusinesses?.FirstOrDefault()?.BusinessName
?? client?.ClientName
?? "GYM OS";
decimal paid = await _paymentRepository.GetApprovedPaidAmountAsync(customerInvoiceId);
return new ReceiptModel
{
ClientId = invoice.FKClientId,
ReceiptNo = $"GYMOS-{customerInvoiceId.ToString("N")[..8].ToUpperInvariant()}",
Date = DateTime.UtcNow,
GymName = gymName,
MemberName = customer?.CusName ?? "Member",
MemberPhone = customer?.PrimaryPH,
MemberEmail = customer?.Email,
PlanName = planName,
AmountPaid = paymentAmount ?? paid,
InvoiceAmount = invoice.Amount,
Balance = invoice.Amount - paid,
Currency = "LKR"
};
}
public async Task<ReceiptModel> BuildForClientInvoiceAsync(Guid clientInvoiceId, decimal? paymentAmount = null, Guid? requireClientId = null)
{
var invoice = await _clientInvoiceRepository.GetClientInvoiceById(clientInvoiceId)
?? throw new NotFoundException("Invoice not found");
var clientId = invoice.ClientSubscription?.FKClientId ?? Guid.Empty;
if (requireClientId.HasValue && clientId != requireClientId.Value)
throw new UnauthorizedException("This invoice does not belong to your gym.");
var client = invoice.ClientSubscription?.Client ?? await _clientRepository.GetClientByIdAsync(clientId);
string gymName = client?.ClientBusinesses?.FirstOrDefault()?.BusinessName
?? client?.ClientName
?? "Gym";
var payments = await _paymentRepository.GetPaymentsByClientInvoiceIdAsync(clientInvoiceId);
decimal paid = payments.Where(p => p.IsApproved == true).Sum(p => p.PayAmount);
string itemSummary = invoice.Items?.FirstOrDefault()?.ItemName ?? "Platform fee";
return new ReceiptModel
{
ClientId = clientId,
ReceiptNo = $"HEXA-{clientInvoiceId.ToString("N")[..8].ToUpperInvariant()}",
Date = DateTime.UtcNow,
GymName = "HexaFitness",
MemberName = gymName,
MemberPhone = client?.ClientPhone,
MemberEmail = client?.ClientEmail,
PlanName = itemSummary,
AmountPaid = paymentAmount ?? paid,
InvoiceAmount = invoice.Amount,
Balance = invoice.Amount - paid,
Currency = "LKR"
};
}
public byte[] GeneratePdf(ReceiptModel m)
{
string money(decimal v) => $"{m.Currency} {v:N2}";
var document = QuestPDF.Fluent.Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A5);
page.Margin(28);
page.DefaultTextStyle(x => x.FontSize(11).FontColor(Colors.Grey.Darken3));
page.Header().Column(col =>
{
col.Item().Text("GYM OS").FontSize(20).Bold().FontColor(Colors.Blue.Darken2);
col.Item().Text(m.GymName).FontSize(13).SemiBold();
col.Item().PaddingTop(2).Text("Payment Receipt").FontSize(10).FontColor(Colors.Grey.Medium);
col.Item().PaddingTop(6).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
});
page.Content().PaddingVertical(12).Column(col =>
{
col.Spacing(6);
col.Item().Row(row =>
{
row.RelativeItem().Text($"Receipt No: {m.ReceiptNo}");
row.RelativeItem().AlignRight().Text($"Date: {m.Date:yyyy-MM-dd}");
});
col.Item().PaddingTop(4).Text("Billed to").FontColor(Colors.Grey.Medium).FontSize(9);
col.Item().Text(m.MemberName).SemiBold();
if (!string.IsNullOrWhiteSpace(m.MemberPhone))
col.Item().Text(m.MemberPhone!).FontSize(10);
if (!string.IsNullOrWhiteSpace(m.MemberEmail))
col.Item().Text(m.MemberEmail!).FontSize(10);
col.Item().PaddingTop(10).Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(3);
c.RelativeColumn(2);
});
table.Cell().Element(HeaderCell).Text("Description");
table.Cell().Element(HeaderCell).AlignRight().Text("Amount");
table.Cell().Element(BodyCell).Text(m.PlanName);
table.Cell().Element(BodyCell).AlignRight().Text(money(m.AmountPaid));
static IContainer HeaderCell(IContainer c) =>
c.Background(Colors.Grey.Lighten3).PaddingVertical(5).PaddingHorizontal(8).DefaultTextStyle(t => t.SemiBold());
static IContainer BodyCell(IContainer c) =>
c.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5).PaddingHorizontal(8);
});
col.Item().PaddingTop(10).AlignRight().Column(totals =>
{
totals.Item().Text($"Amount paid: {money(m.AmountPaid)}").SemiBold();
totals.Item().Text($"Invoice total: {money(m.InvoiceAmount)}").FontSize(10).FontColor(Colors.Grey.Medium);
totals.Item().Text($"Balance due: {money(m.Balance)}").FontSize(10)
.FontColor(m.Balance > 0 ? Colors.Red.Medium : Colors.Green.Darken1);
});
});
page.Footer().AlignCenter().Column(col =>
{
col.Item().PaddingTop(6).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
col.Item().PaddingTop(6).Text("Thank you for your payment.").FontSize(10);
col.Item().Text($"{m.GymName} · powered by GYM OS").FontSize(8).FontColor(Colors.Grey.Medium);
});
});
});
return document.GeneratePdf();
}
public async Task SendReceiptAsync(ReceiptModel m)
{
byte[] pdf;
try
{
pdf = GeneratePdf(m);
}
catch (Exception ex)
{
Console.WriteLine($"Receipt PDF generation failed (non-critical): {ex.Message}");
return;
}
if (!string.IsNullOrWhiteSpace(m.MemberEmail))
{
try
{
string html =
$"<p>Hi {m.MemberName},</p>" +
$"<p>Thank you for your payment of <b>{m.Currency} {m.AmountPaid:N2}</b> for <b>{m.PlanName}</b> at {m.GymName}.</p>" +
(m.Balance > 0 ? $"<p>Outstanding balance: {m.Currency} {m.Balance:N2}.</p>" : "") +
$"<p>Your receipt ({m.ReceiptNo}) is attached.</p>" +
$"<p>— {m.GymName}<br/><small>powered by GYM OS</small></p>";
await _thirdPartyService.SendEmailAsync(m.MemberEmail!, $"Receipt {m.ReceiptNo} — {m.GymName}", html, pdf, $"{m.ReceiptNo}.pdf");
}
catch (Exception ex)
{
Console.WriteLine($"Receipt email failed (non-critical): {ex.Message}");
}
}
if (!string.IsNullOrWhiteSpace(m.MemberPhone))
{
try
{
string sms = $"You paid {m.Currency} {m.AmountPaid:N2} for {m.PlanName} at {m.GymName}. Thank you! - GYM OS";
await _thirdPartyService.SendSmsAsync(m.MemberPhone!, null, sms);
}
catch (Exception ex)
{
Console.WriteLine($"Receipt SMS failed (non-critical): {ex.Message}");
}
}
}
}
}