@page "/admin/contracts" @using TaxBaik.Web.Services.AdminClients @inject IContractBrowserClient ContractClient @inject IClientBrowserClient ClientClient @inject IJSRuntime JS @attribute [Authorize] 계약 관리
CRM & 세무관리

계약 관리

고객 계약과 월 정기수익을 함께 관리합니다.

@if (mrr > 0) {

월 정기수익: ₩@mrr.ToString("N0")

}
@if (contracts is null) { } else if (contracts.Count == 0) {
계약이 없습니다.
} else {
@foreach (var item in contracts) { var isActive = !item.EndDate.HasValue || item.EndDate.Value >= DateTime.Today; }
ID 고객 계약번호 서비스 유형 월 수수료 계약기간 상태 작업
@item.Id @(clientMap.TryGetValue(item.ClientId, out var clientName) ? clientName : "") @item.ContractNumber @item.ServiceType @(item.MonthlyFee?.ToString("C") ?? "—") @item.StartDate@if (item.EndDate.HasValue){~ @item.EndDate.Value} @(isActive ? "활성" : "만료")
}

새 계약 추가

@code { [CascadingParameter] private Task? AuthStateTask { get; set; } private List? contracts; private List clients = []; private Dictionary clientMap = new(); private decimal mrr = 0; private bool isDialogOpen; private ContractForm contractForm = new(); private string ClientIdText { get => contractForm.ClientId > 0 ? contractForm.ClientId.ToString() : ""; set => contractForm.ClientId = int.TryParse(value, out var id) ? id : 0; } private string StartDateText { get => contractForm.StartDate?.ToString("yyyy-MM-dd") ?? ""; set => contractForm.StartDate = DateTime.TryParse(value, out var dt) ? dt : null; } private string MonthlyFeeText { get => contractForm.MonthlyFee?.ToString() ?? ""; set => contractForm.MonthlyFee = decimal.TryParse(value, out var amount) ? amount : null; } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender && AuthStateTask != null) { var authState = await AuthStateTask; if (authState.User.Identity?.IsAuthenticated == true) { await LoadData(); StateHasChanged(); } } } private async Task LoadData() { try { contracts = await ContractClient.GetAllAsync(); var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000); clients = clientItems.ToList(); clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName); mrr = await ContractClient.GetMonthlyRecurringRevenueAsync(); } catch (Exception ex) { await JS.InvokeVoidAsync("alert", $"데이터 로드 실패: {ex.Message}"); } } private void OpenCreateDialog() { contractForm = new ContractForm { ClientId = clients.FirstOrDefault()?.Id ?? 0, StartDate = DateTime.Today }; isDialogOpen = true; } private async Task SaveContract() { try { if (contractForm.ClientId <= 0) { await JS.InvokeVoidAsync("alert", "고객을 선택하세요."); return; } var newId = await ContractClient.CreateAsync(contractForm.ClientId, contractForm.ContractNumber, contractForm.ServiceType, contractForm.StartDate ?? DateTime.Today, contractForm.MonthlyFee); if (newId > 0) { await JS.InvokeVoidAsync("alert", "계약이 추가되었습니다."); CloseDialog(); await LoadData(); } } catch (Exception ex) { await JS.InvokeVoidAsync("alert", $"저장 실패: {ex.Message}"); } } private async Task DeleteContract(int id) { if (!await JS.InvokeAsync("confirm", "이 계약을 삭제하시겠습니까?")) return; try { await ContractClient.DeleteAsync(id); await JS.InvokeVoidAsync("alert", "계약이 삭제되었습니다."); await LoadData(); } catch (Exception ex) { await JS.InvokeVoidAsync("alert", $"삭제 실패: {ex.Message}"); } } private void CloseDialog() { isDialogOpen = false; contractForm = new(); } private static string GetClientDisplayName(Client client) => !string.IsNullOrWhiteSpace(client.CompanyName) ? client.CompanyName : !string.IsNullOrWhiteSpace(client.Name) ? client.Name : $"Client #{client.Id}"; private sealed class ContractForm { public int ClientId { get; set; } public string ContractNumber { get; set; } = ""; public string ServiceType { get; set; } = ""; public DateTime? StartDate { get; set; } public decimal? MonthlyFee { get; set; } } }