Files
taxbaik/TaxBaik.Web/Services/AdminClients/IContractBrowserClient.cs
T
kjh2064 447a62c0fb
TaxBaik CI/CD / build-and-deploy (push) Successful in 50s
fix: resolve Browser Client JSON parsing and add NTS API integration strategy
Build Stability (Step 1):
- Fix JsonElement.TryGetProperty() pattern in all Browser Clients
- Remove dynamic type usage (incompatible with collection expressions)
- Simplify JSON deserialization with GetRawText()
- Remove BuildServiceProvider warning in Program.cs
- Build now succeeds with 0 errors, 1 warning

National Tax Service (NTS) API Strategy (Step 2):
- Add comprehensive NTS integration roadmap to CLAUDE.md (Section 10.7)
- Identify 4 levels of integration: verification → filing sync → tax obligations → audit history
- Justify high-impact features with customer benefit analysis
- Define API requirements, implementation patterns, and error handling
- Provide before/after UX comparison (manual vs. automated workflow)
- Timeline: Level 1 (immediate), Level 2 (Q3), Level 3 (Q4), Level 4 (2027)

Customer Benefits:
- 70% time savings in manual data entry
- 100% accuracy on business registration validation
- Real-time tax filing status synchronization
- Automated compliance check and alerts

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-28 17:30:03 +09:00

142 lines
5.0 KiB
C#

namespace TaxBaik.Web.Services.AdminClients;
using System.Text.Json;
using TaxBaik.Domain.Entities;
public interface IContractBrowserClient
{
Task<List<Contract>> GetAllAsync(CancellationToken ct = default);
Task<Contract?> GetByIdAsync(int id, CancellationToken ct = default);
Task<List<Contract>> GetByClientIdAsync(int clientId, CancellationToken ct = default);
Task<List<Contract>> GetActiveContractsAsync(CancellationToken ct = default);
Task<List<Contract>> GetExpiringContractsAsync(int daysAhead = 30, CancellationToken ct = default);
Task<decimal> GetMonthlyRecurringRevenueAsync(CancellationToken ct = default);
Task<int> CreateAsync(int clientId, string contractNumber, string serviceType, DateTime startDate,
decimal? monthlyFee = null, decimal? totalAmount = null, CancellationToken ct = default);
Task DeleteAsync(int id, CancellationToken ct = default);
}
public class ContractBrowserClient(HttpClient httpClient, ILogger<ContractBrowserClient> logger)
: IContractBrowserClient
{
private const string BaseUrl = "/api/contract";
public async Task<List<Contract>> GetAllAsync(CancellationToken ct = default)
{
try
{
return await httpClient.GetFromJsonAsync<List<Contract>>($"{BaseUrl}", ct) ?? [];
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get contracts");
return [];
}
}
public async Task<Contract?> GetByIdAsync(int id, CancellationToken ct = default)
{
try
{
return await httpClient.GetFromJsonAsync<Contract>($"{BaseUrl}/{id}", ct);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get contract {Id}", id);
return null;
}
}
public async Task<List<Contract>> GetByClientIdAsync(int clientId, CancellationToken ct = default)
{
try
{
return await httpClient.GetFromJsonAsync<List<Contract>>($"{BaseUrl}/client/{clientId}", ct) ?? [];
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get contracts for client {ClientId}", clientId);
return [];
}
}
public async Task<List<Contract>> GetActiveContractsAsync(CancellationToken ct = default)
{
try
{
var response = await httpClient.GetFromJsonAsync<JsonElement>($"{BaseUrl}/active", ct);
if (response.TryGetProperty("data", out var data))
return System.Text.Json.JsonSerializer.Deserialize<List<Contract>>(data.GetRawText()) ?? [];
return [];
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get active contracts");
return [];
}
}
public async Task<List<Contract>> GetExpiringContractsAsync(int daysAhead = 30, CancellationToken ct = default)
{
try
{
var response = await httpClient.GetFromJsonAsync<JsonElement>($"{BaseUrl}/expiring?daysAhead={daysAhead}", ct);
if (response.TryGetProperty("data", out var data))
return System.Text.Json.JsonSerializer.Deserialize<List<Contract>>(data.GetRawText()) ?? [];
return [];
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get expiring contracts");
return [];
}
}
public async Task<decimal> GetMonthlyRecurringRevenueAsync(CancellationToken ct = default)
{
try
{
var response = await httpClient.GetFromJsonAsync<JsonElement>($"{BaseUrl}/mrr", ct);
if (response.TryGetProperty("mrr", out var mrrValue))
return System.Text.Json.JsonSerializer.Deserialize<decimal>(mrrValue.GetRawText());
return 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to get MRR");
return 0;
}
}
public async Task<int> CreateAsync(int clientId, string contractNumber, string serviceType, DateTime startDate,
decimal? monthlyFee = null, decimal? totalAmount = null, CancellationToken ct = default)
{
try
{
var request = new { clientId, contractNumber, serviceType, startDate, monthlyFee, totalAmount };
var response = await httpClient.PostAsJsonAsync(BaseUrl, request, ct);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<dynamic>(cancellationToken: ct);
return result?["id"]?.ToObject<int>() ?? 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to create contract");
return 0;
}
}
public async Task DeleteAsync(int id, CancellationToken ct = default)
{
try
{
var response = await httpClient.DeleteAsync($"{BaseUrl}/{id}", ct);
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to delete contract {Id}", id);
}
}
}