checks
ci-cd-vm / ci-cd (push) Successful in 2m17s

This commit is contained in:
Chris Chen
2026-06-25 15:51:52 -07:00
parent d32eea3523
commit 55543af5e1
5 changed files with 63 additions and 12 deletions
+2 -1
View File
@@ -26,7 +26,8 @@ public class ExpenseListItemDto
public string PrimaryCategoryName { get; set; } = ""; // first line's category (list hint; full breakdown via detail)
public string? VendorName { get; set; }
public int? MemberId { get; set; }
public string? MemberName { get; set; }
public string? MemberName { get; set; } // legal name "FirstName_en LastName_en" (used on the printed check)
public string? MemberNickName { get; set; } // "NickName LastName_en"; null when the member has no distinct nickname
public string ExpenseDate { get; set; } = ""; // yyyy-MM-dd
public bool HasReceipt { get; set; }
public string? CheckNumber { get; set; }
+39 -7
View File
@@ -83,8 +83,12 @@ public class ExpenseService : IExpenseService
var minNames = await _db.Ministries.AsNoTracking().ToDictionaryAsync(m => m.Id, m => $"{m.Name_en} / {m.Name_zh}");
var grpNames = await _db.ExpenseCategoryGroups.AsNoTracking().ToDictionaryAsync(g => g.Id, g => $"{g.Name_en} / {g.Name_zh}");
var memberIds = rows.Where(r => r.MemberId != null).Select(r => r.MemberId!.Value).ToHashSet();
var memNames = await _db.Members.AsNoTracking().Where(m => memberIds.Contains(m.Id))
.ToDictionaryAsync(m => m.Id, m => $"{m.FirstName_en} {m.LastName_en}");
var memberNames = await _db.Members.AsNoTracking().Where(m => memberIds.Contains(m.Id))
.Select(m => new { m.Id, m.FirstName_en, m.LastName_en, m.NickName })
.ToDictionaryAsync(
m => m.Id,
m => new MemberPayeeName($"{m.FirstName_en} {m.LastName_en}",
BuildNickPayeeName(m.NickName, m.FirstName_en, m.LastName_en)));
var reviewerNames = await ResolveUserNamesAsync(rows.Select(r => r.ReviewedBy));
// Line count + first line's category, per expense on this page.
@@ -108,7 +112,8 @@ public class ExpenseService : IExpenseService
LineCount = ls?.Count ?? 0,
PrimaryCategoryName = grpNames.GetValueOrDefault(firstGroupId, ""),
VendorName = e.VendorName, MemberId = e.MemberId,
MemberName = e.MemberId != null ? memNames.GetValueOrDefault(e.MemberId.Value) : null,
MemberName = e.MemberId != null ? memberNames.GetValueOrDefault(e.MemberId.Value)?.Legal : null,
MemberNickName = e.MemberId != null ? memberNames.GetValueOrDefault(e.MemberId.Value)?.Nick : null,
ExpenseDate = e.ExpenseDate.ToString("yyyy-MM-dd"),
HasReceipt = e.ReceiptBlobPath != null,
CheckNumber = e.CheckNumber,
@@ -145,14 +150,41 @@ public class ExpenseService : IExpenseService
: (u.Email ?? u.Id));
}
// Member payee names carried to the frontend: the legal name (printed on the check) and an
// optional friendly "NickName LastName" line shown above it.
private sealed record MemberPayeeName(string Legal, string? Nick);
// Build the friendly "NickName LastName" payee line, or null when the member has no distinct
// nickname (mirrors the frontend memberDisplayName rule: a nickname equal to the first name is not shown).
private static string? BuildNickPayeeName(string? nickName, string firstNameEn, string lastNameEn)
{
bool hasDistinctNickName = !string.IsNullOrWhiteSpace(nickName) && nickName != firstNameEn;
if (!hasDistinctNickName)
{
return null;
}
return $"{nickName} {lastNameEn}";
}
public async Task<ExpenseDto?> GetByIdAsync(int id)
{
var e = await _db.Expenses.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
if (e is null) return null;
var minName = await _db.Ministries.Where(m => m.Id == e.MinistryId).Select(m => m.Name_en).FirstOrDefaultAsync() ?? "";
string? memName = e.MemberId != null
? await _db.Members.Where(m => m.Id == e.MemberId).Select(m => m.FirstName_en + " " + m.LastName_en).FirstOrDefaultAsync()
: null;
string? memberName = null;
string? memberNickName = null;
if (e.MemberId != null)
{
var member = await _db.Members.AsNoTracking()
.Where(m => m.Id == e.MemberId)
.Select(m => new { m.FirstName_en, m.LastName_en, m.NickName })
.FirstOrDefaultAsync();
if (member != null)
{
memberName = $"{member.FirstName_en} {member.LastName_en}";
memberNickName = BuildNickPayeeName(member.NickName, member.FirstName_en, member.LastName_en);
}
}
var reviewerName = e.ReviewedBy != null
? (await ResolveUserNamesAsync(new[] { e.ReviewedBy })).GetValueOrDefault(e.ReviewedBy)
@@ -174,7 +206,7 @@ public class ExpenseService : IExpenseService
MinistryId = e.MinistryId, MinistryName = minName,
LineCount = lineDtos.Count,
PrimaryCategoryName = lineDtos.Count > 0 ? lineDtos[0].CategoryGroupName : "",
VendorName = e.VendorName, MemberId = e.MemberId, MemberName = memName,
VendorName = e.VendorName, MemberId = e.MemberId, MemberName = memberName, MemberNickName = memberNickName,
ExpenseDate = e.ExpenseDate.ToString("yyyy-MM-dd"), HasReceipt = e.ReceiptBlobPath != null,
CheckNumber = e.CheckNumber, Notes = e.Notes, ReviewNotes = e.ReviewNotes,
ReviewedByName = reviewerName, ReviewedAt = e.ReviewedAt,