Files
ROLAC/API/ROLAC.API/Services/GivingService.cs
2026-05-28 17:24:47 -07:00

133 lines
5.6 KiB
C#

using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Giving;
using ROLAC.API.DTOs.Shared;
using ROLAC.API.Entities;
namespace ROLAC.API.Services;
public class GivingService : IGivingService
{
private readonly AppDbContext _db;
public GivingService(AppDbContext db) => _db = db;
public async Task<PagedResult<GivingListItemDto>> GetPagedAsync(
int page, int pageSize, string? search, int? categoryId, DateOnly? from, DateOnly? to)
{
var query = _db.Givings.AsNoTracking().AsQueryable();
if (categoryId.HasValue) query = query.Where(g => g.GivingCategoryId == categoryId.Value);
if (from.HasValue) query = query.Where(g => g.GivingDate >= from.Value);
if (to.HasValue) query = query.Where(g => g.GivingDate <= to.Value);
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.Trim().ToLower();
var term = search.Trim();
query = query.Where(g =>
(g.CheckNumber != null && g.CheckNumber.ToLower().Contains(s)) ||
(g.Notes != null && g.Notes.ToLower().Contains(s)) ||
(g.Member != null && (
(g.Member.FirstName_en + " " + g.Member.LastName_en).ToLower().Contains(s) ||
(g.Member.FirstName_zh != null && g.Member.FirstName_zh.Contains(term)) ||
(g.Member.LastName_zh != null && g.Member.LastName_zh.Contains(term)))));
}
var total = await query.CountAsync();
var rows = await query
.OrderByDescending(g => g.GivingDate).ThenByDescending(g => g.Id)
.Skip((page - 1) * pageSize).Take(pageSize)
.ToListAsync();
var catNames = await _db.GivingCategories.AsNoTracking()
.ToDictionaryAsync(c => c.Id, c => c.Name_en);
var memberIds = rows.Where(r => r.MemberId != null).Select(r => r.MemberId!.Value).ToHashSet();
var memberNames = await _db.Members.AsNoTracking()
.Where(m => memberIds.Contains(m.Id))
.ToDictionaryAsync(m => m.Id, m => $"{m.FirstName_en} {m.LastName_en}");
var items = rows.Select(g => new GivingListItemDto
{
Id = g.Id, MemberId = g.MemberId,
MemberName = g.MemberId != null && memberNames.TryGetValue(g.MemberId.Value, out var n) ? n : null,
GivingCategoryId = g.GivingCategoryId,
CategoryName = catNames.TryGetValue(g.GivingCategoryId, out var cn) ? cn : "",
Amount = g.Amount, PaymentMethod = g.PaymentMethod,
GivingDate = g.GivingDate.ToString("yyyy-MM-dd"),
IsAnonymous = g.IsAnonymous, OfferingSessionId = g.OfferingSessionId,
}).ToList();
return new PagedResult<GivingListItemDto>
{ Items = items, TotalCount = total, Page = page, PageSize = pageSize };
}
public async Task<GivingDto?> GetByIdAsync(int id)
{
var g = await _db.Givings.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
if (g is null) return null;
string? memberName = null;
if (g.MemberId != null)
memberName = await _db.Members.AsNoTracking()
.Where(m => m.Id == g.MemberId)
.Select(m => m.FirstName_en + " " + m.LastName_en)
.FirstOrDefaultAsync();
return new GivingDto
{
Id = g.Id, MemberId = g.MemberId, MemberName = memberName,
GivingCategoryId = g.GivingCategoryId, OfferingSessionId = g.OfferingSessionId,
Amount = g.Amount, PaymentMethod = g.PaymentMethod, CheckNumber = g.CheckNumber,
ZelleReferenceCode = g.ZelleReferenceCode, PayPalTransactionId = g.PayPalTransactionId,
GivingDate = g.GivingDate, IsAnonymous = g.IsAnonymous, Notes = g.Notes,
};
}
public async Task<int> CreateAsync(CreateGivingRequest r)
{
var g = MapFromRequest(new Giving(), r);
g.OfferingSessionId = null;
_db.Givings.Add(g);
await _db.SaveChangesAsync();
return g.Id;
}
public async Task UpdateAsync(int id, UpdateGivingRequest r)
{
var g = await _db.Givings.FindAsync(id)
?? throw new KeyNotFoundException($"Giving {id} not found.");
await GuardSessionNotLockedAsync(g.OfferingSessionId);
MapFromRequest(g, r);
await _db.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var g = await _db.Givings.FindAsync(id)
?? throw new KeyNotFoundException($"Giving {id} not found.");
await GuardSessionNotLockedAsync(g.OfferingSessionId);
_db.Givings.Remove(g);
await _db.SaveChangesAsync();
}
private async Task GuardSessionNotLockedAsync(int? sessionId)
{
if (sessionId is null) return;
var status = await _db.OfferingSessions
.Where(s => s.Id == sessionId).Select(s => s.Status).FirstOrDefaultAsync();
if (status is "Submitted" or "Reconciled")
throw new InvalidOperationException(
"This giving belongs to a locked offering session. Reopen the session to edit.");
}
private static Giving MapFromRequest(Giving g, CreateGivingRequest r)
{
g.MemberId = r.IsAnonymous ? null : r.MemberId;
g.GivingCategoryId = r.GivingCategoryId;
g.Amount = r.Amount; g.PaymentMethod = r.PaymentMethod;
g.CheckNumber = r.CheckNumber; g.ZelleReferenceCode = r.ZelleReferenceCode;
g.PayPalTransactionId = r.PayPalTransactionId; g.GivingDate = r.GivingDate;
g.IsAnonymous = r.IsAnonymous; g.Notes = r.Notes;
return g;
}
}