831b868d9d
Add PayerEin (nullable string, max 20) to ChurchProfile entity, AppDbContext config, ChurchProfileDto response, UpdateChurchProfileRequest, and ChurchProfileService round-trip — mirroring the Phone/BankRoutingNumber nullable-string pattern. Migration AddPayerEinToChurchProfile adds only the one nullable column to ChurchProfiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.7 KiB
C#
66 lines
2.7 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using ROLAC.API.Data;
|
|
using ROLAC.API.DTOs.Disbursement;
|
|
using ROLAC.API.Entities;
|
|
|
|
namespace ROLAC.API.Services;
|
|
|
|
public class ChurchProfileService : IChurchProfileService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
public ChurchProfileService(AppDbContext db) => _db = db;
|
|
|
|
public async Task<ChurchProfileDto> GetAsync()
|
|
{
|
|
var p = await GetOrCreateAsync();
|
|
return new ChurchProfileDto
|
|
{
|
|
Id = p.Id, Name = p.Name, NameZh = p.NameZh, Phone = p.Phone, Email = p.Email,
|
|
Website = p.Website, Address = p.Address, City = p.City, State = p.State,
|
|
ZipCode = p.ZipCode, BankName = p.BankName, BankAccountNumber = p.BankAccountNumber,
|
|
BankRoutingNumber = p.BankRoutingNumber, PayerEin = p.PayerEin, NextCheckNumber = p.NextCheckNumber,
|
|
AiProvider = p.AiProvider,
|
|
ClaudeModel = p.ClaudeModel,
|
|
ClaudeApiKeyMasked = Mask(p.ClaudeApiKey),
|
|
GeminiModel = p.GeminiModel,
|
|
GeminiApiKeyMasked = Mask(p.GeminiApiKey),
|
|
};
|
|
}
|
|
|
|
public async Task UpdateAsync(UpdateChurchProfileRequest r)
|
|
{
|
|
var p = await GetOrCreateAsync();
|
|
p.Name = r.Name; p.NameZh = r.NameZh; p.Phone = r.Phone; p.Email = r.Email;
|
|
p.Website = r.Website; p.Address = r.Address; p.City = r.City; p.State = r.State;
|
|
p.ZipCode = r.ZipCode; p.BankName = r.BankName; p.BankAccountNumber = r.BankAccountNumber;
|
|
p.BankRoutingNumber = r.BankRoutingNumber; p.PayerEin = r.PayerEin; p.NextCheckNumber = r.NextCheckNumber;
|
|
p.AiProvider = string.IsNullOrWhiteSpace(r.AiProvider) ? "Claude" : r.AiProvider;
|
|
p.ClaudeModel = r.ClaudeModel;
|
|
p.GeminiModel = r.GeminiModel;
|
|
// Leave-unchanged semantics: only overwrite a stored key when a new value is supplied.
|
|
if (!string.IsNullOrWhiteSpace(r.ClaudeApiKey)) p.ClaudeApiKey = r.ClaudeApiKey;
|
|
if (!string.IsNullOrWhiteSpace(r.GeminiApiKey)) p.GeminiApiKey = r.GeminiApiKey;
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
|
|
private async Task<ChurchProfile> GetOrCreateAsync()
|
|
{
|
|
var p = await _db.ChurchProfiles.OrderBy(x => x.Id).FirstOrDefaultAsync();
|
|
if (p is null)
|
|
{
|
|
p = new ChurchProfile { Name = "Church", NextCheckNumber = 1001 };
|
|
_db.ChurchProfiles.Add(p);
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/// <summary>Mask a stored secret for display: 6 bullets + last 4 chars; fully masked when ≤4 chars.</summary>
|
|
private static string Mask(string? key)
|
|
{
|
|
if (string.IsNullOrEmpty(key)) return "";
|
|
if (key.Length <= 4) return new string('•', key.Length);
|
|
return new string('•', 6) + key[^4..];
|
|
}
|
|
}
|