332 lines
13 KiB
Plaintext
332 lines
13 KiB
Plaintext
@page "/admin/tax-profiles"
|
|
@using TaxBaik.Web.Services.AdminClients
|
|
@inject ITaxProfileBrowserClient TaxProfileClient
|
|
@inject IClientBrowserClient ClientClient
|
|
@inject ICommonCodeBrowserClient CommonCodeClient
|
|
@inject ISnackbar Snackbar
|
|
@inject IDialogService DialogService
|
|
@attribute [Authorize]
|
|
|
|
<PageTitle>세무 프로필</PageTitle>
|
|
|
|
<section class="admin-page-hero">
|
|
<div>
|
|
<MudText Typo="Typo.caption" Class="admin-eyebrow">CRM & 세무관리</MudText>
|
|
<MudText Typo="Typo.h4" Class="admin-page-title">세무 프로필</MudText>
|
|
<MudText Typo="Typo.body2" Class="admin-page-subtitle">고객별 세무 프로필, 신고 일정, 위험도 추적</MudText>
|
|
</div>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="PrepareCreate" StartIcon="@Icons.Material.Filled.Add" id="btn-add-profile">
|
|
새 프로필 추가
|
|
</MudButton>
|
|
</section>
|
|
|
|
@if (profiles == null)
|
|
{
|
|
<MudProgressCircular Indeterminate="true" Class="mt-4" />
|
|
}
|
|
else
|
|
{
|
|
<MudGrid Spacing="2" Class="mt-2">
|
|
<!-- Left: Dense Grid List -->
|
|
<MudItem XS="12" MD="8">
|
|
@if (profiles.Count == 0)
|
|
{
|
|
<MudAlert Severity="Severity.Info">세무 프로필이 없습니다.</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
<MudDataGrid T="TaxProfile"
|
|
Items="@profiles"
|
|
Dense="true"
|
|
Hover="true"
|
|
Striped="true"
|
|
Virtualize="true"
|
|
RowsPerPage="30"
|
|
SelectedItem="@selectedProfile"
|
|
SelectedItemChanged="OnRowSelected"
|
|
Class="admin-grid">
|
|
<Columns>
|
|
<PropertyColumn Property="x => x.Id" Title="ID" Sortable="true" />
|
|
<TemplateColumn Title="고객">
|
|
<CellTemplate>
|
|
@if (clientMap.TryGetValue(context.Item.ClientId, out var clientName))
|
|
{
|
|
@clientName
|
|
}
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
<PropertyColumn Property="x => x.BusinessType" Title="사업 유형" />
|
|
<TemplateColumn Title="위험도">
|
|
<CellTemplate>
|
|
<MudChip Size="Size.Small" Color="@GetRiskColor(context.Item.TaxRiskLevel)" Variant="Variant.Filled">
|
|
@context.Item.TaxRiskLevel
|
|
</MudChip>
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
<TemplateColumn Title="다음 신고">
|
|
<CellTemplate>
|
|
@if (context.Item.NextFilingDueDate.HasValue)
|
|
{
|
|
@context.Item.NextFilingDueDate.Value.ToString("yyyy-MM-dd")
|
|
}
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
<TemplateColumn Title="작업" Sortable="false">
|
|
<CellTemplate>
|
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="@(async () => await DeleteProfile(context.Item.Id))" />
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
</Columns>
|
|
</MudDataGrid>
|
|
}
|
|
</MudItem>
|
|
|
|
<!-- Right: Detail Form Panel (Inline Editor) -->
|
|
<MudItem XS="12" MD="4">
|
|
<MudPaper Class="pa-4 admin-surface admin-editor-panel" Elevation="0" Style="border: 1px solid var(--border-color); min-height: 400px;">
|
|
<div class="d-flex align-center justify-space-between mb-4">
|
|
<MudText Typo="Typo.h6" Class="font-weight-bold">@(isEditMode ? "세무 프로필 수정" : "새 세무 프로필 추가")</MudText>
|
|
@if (isEditMode)
|
|
{
|
|
<MudButton Size="Size.Small" Variant="Variant.Outlined" Color="Color.Secondary" OnClick="PrepareCreate">
|
|
새로 작성
|
|
</MudButton>
|
|
}
|
|
</div>
|
|
<MudForm @ref="form">
|
|
<MudSelect T="int?" @bind-Value="profileForm.ClientId" Label="고객" Required="true" Variant="Variant.Outlined" FullWidth="@true" Class="mb-3" RequiredError="고객을 선택하세요." Disabled="@isEditMode">
|
|
@foreach (var client in clients)
|
|
{
|
|
<MudSelectItem Value="@((int?)client.Id)">@GetClientDisplayName(client)</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudSelect T="string" @bind-Value="profileForm.BusinessType" Label="사업 유형" Variant="Variant.Outlined" FullWidth="@true" Class="mb-3" Required="true">
|
|
@foreach (var type in businessTypes)
|
|
{
|
|
<MudSelectItem Value="@type.CodeValue">@type.CodeName</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudSelect T="string" @bind-Value="profileForm.TaxRiskLevel" Label="위험도" Variant="Variant.Outlined" FullWidth="@true" Class="mb-3">
|
|
@foreach (var level in riskLevels)
|
|
{
|
|
<MudSelectItem Value="@level.CodeValue">@level.CodeName</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
<MudDatePicker @bind-Date="profileForm.NextFilingDueDate" Label="다음 신고 예정일" Variant="Variant.Outlined" FullWidth="@true" Class="mb-3" />
|
|
<MudTextField T="string" @bind-Value="profileForm.SpecialNotes" Label="특수 사항" Variant="Variant.Outlined" FullWidth="@true" Lines="3" Class="mb-4" />
|
|
|
|
<div class="d-flex justify-end gap-2">
|
|
@if (isEditMode)
|
|
{
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="@(async () => await DeleteProfile(selectedProfile?.Id ?? 0))">삭제</MudButton>
|
|
}
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveProfile" id="btn-save-profile">저장</MudButton>
|
|
</div>
|
|
</MudForm>
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
}
|
|
|
|
@code {
|
|
[CascadingParameter]
|
|
private Task<AuthenticationState>? AuthStateTask { get; set; }
|
|
|
|
private List<TaxProfile>? profiles;
|
|
private List<Client> clients = [];
|
|
private Dictionary<int, string> clientMap = new();
|
|
private List<CommonCode> businessTypes = [];
|
|
private List<CommonCode> riskLevels = [];
|
|
private MudForm? form;
|
|
private bool isEditMode;
|
|
private TaxProfile? selectedProfile;
|
|
private TaxProfileForm profileForm = new();
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (firstRender)
|
|
{
|
|
if (AuthStateTask != null)
|
|
{
|
|
var authState = await AuthStateTask;
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
await LoadData();
|
|
PrepareCreate();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task LoadData()
|
|
{
|
|
try
|
|
{
|
|
profiles = await TaxProfileClient.GetAllAsync();
|
|
var (clientItems, _) = await ClientClient.GetPagedAsync(pageSize: 1000);
|
|
clients = clientItems.ToList();
|
|
clientMap = clients.ToDictionary(c => c.Id, GetClientDisplayName);
|
|
|
|
businessTypes = await CommonCodeClient.GetByGroupAsync("BUSINESS_TYPE");
|
|
if (businessTypes.Count == 0)
|
|
{
|
|
businessTypes = [
|
|
new() { CodeValue = "일반제조업", CodeName = "일반제조업" },
|
|
new() { CodeValue = "도소매업", CodeName = "도소매업" },
|
|
new() { CodeValue = "서비스업", CodeName = "서비스업" },
|
|
new() { CodeValue = "정보통신업", CodeName = "정보통신업" },
|
|
new() { CodeValue = "부동산업", CodeName = "부동산업" },
|
|
new() { CodeValue = "건설업", CodeName = "건설업" },
|
|
new() { CodeValue = "음식점업", CodeName = "음식점업" },
|
|
new() { CodeValue = "프리랜서", CodeName = "프리랜서" },
|
|
new() { CodeValue = "기타", CodeName = "기타" }
|
|
];
|
|
}
|
|
|
|
riskLevels = await CommonCodeClient.GetByGroupAsync("TAX_RISK_LEVEL");
|
|
if (riskLevels.Count == 0)
|
|
{
|
|
riskLevels = [
|
|
new() { CodeValue = "low", CodeName = "낮음" },
|
|
new() { CodeValue = "normal", CodeName = "보통" },
|
|
new() { CodeValue = "high", CodeName = "높음" }
|
|
];
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private void PrepareCreate()
|
|
{
|
|
selectedProfile = null;
|
|
isEditMode = false;
|
|
profileForm = new TaxProfileForm
|
|
{
|
|
ClientId = clients.FirstOrDefault()?.Id,
|
|
TaxRiskLevel = "normal",
|
|
NextFilingDueDate = DateTime.Today.AddMonths(1)
|
|
};
|
|
}
|
|
|
|
private void OnRowSelected(TaxProfile profile)
|
|
{
|
|
if (profile == null) return;
|
|
selectedProfile = profile;
|
|
isEditMode = true;
|
|
profileForm = new TaxProfileForm
|
|
{
|
|
ClientId = profile.ClientId,
|
|
BusinessType = profile.BusinessType ?? "",
|
|
TaxRiskLevel = profile.TaxRiskLevel,
|
|
NextFilingDueDate = profile.NextFilingDueDate,
|
|
SpecialNotes = profile.SpecialNotes
|
|
};
|
|
}
|
|
|
|
private async Task SaveProfile()
|
|
{
|
|
if (form != null)
|
|
{
|
|
await form.Validate();
|
|
if (!form.IsValid)
|
|
{
|
|
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (isEditMode && selectedProfile != null)
|
|
{
|
|
await TaxProfileClient.UpdateAsync(selectedProfile.Id, profileForm.BusinessType,
|
|
null, profileForm.NextFilingDueDate, profileForm.TaxRiskLevel);
|
|
Snackbar.Add("세무 프로필이 수정되었습니다.", Severity.Success);
|
|
}
|
|
else
|
|
{
|
|
if (!profileForm.ClientId.HasValue)
|
|
{
|
|
Snackbar.Add("고객을 선택하세요.", Severity.Warning);
|
|
return;
|
|
}
|
|
var newId = await TaxProfileClient.CreateAsync(
|
|
profileForm.ClientId.Value,
|
|
profileForm.BusinessType);
|
|
if (newId > 0)
|
|
{
|
|
await TaxProfileClient.UpdateAsync(
|
|
newId,
|
|
profileForm.BusinessType,
|
|
null,
|
|
profileForm.NextFilingDueDate,
|
|
profileForm.TaxRiskLevel);
|
|
Snackbar.Add("세무 프로필이 추가되었습니다.", Severity.Success);
|
|
}
|
|
}
|
|
PrepareCreate();
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"저장 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task DeleteProfile(int id)
|
|
{
|
|
var parameters = new DialogParameters();
|
|
parameters.Add("Title", "삭제 확인");
|
|
parameters.Add("Message", "이 세무 프로필을 삭제하시겠습니까?");
|
|
|
|
var dialog = await DialogService.ShowAsync<ConfirmDialog>("", parameters);
|
|
var result = await dialog.Result;
|
|
|
|
if (result?.Canceled ?? true)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await TaxProfileClient.DeleteAsync(id);
|
|
Snackbar.Add("세무 프로필이 삭제되었습니다.", Severity.Success);
|
|
if (selectedProfile?.Id == id)
|
|
{
|
|
PrepareCreate();
|
|
}
|
|
await LoadData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"삭제 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private Color GetRiskColor(string riskLevel) => riskLevel switch
|
|
{
|
|
"high" => Color.Error,
|
|
"normal" => Color.Warning,
|
|
"low" => Color.Success,
|
|
_ => Color.Default
|
|
};
|
|
|
|
private static string GetClientDisplayName(Client client)
|
|
=> !string.IsNullOrWhiteSpace(client.CompanyName)
|
|
? client.CompanyName
|
|
: !string.IsNullOrWhiteSpace(client.Name)
|
|
? client.Name
|
|
: $"Client #{client.Id}";
|
|
|
|
private class TaxProfileForm
|
|
{
|
|
public int? ClientId { get; set; }
|
|
public string BusinessType { get; set; } = "";
|
|
public string TaxRiskLevel { get; set; } = "normal";
|
|
public DateTime? NextFilingDueDate { get; set; }
|
|
public string? SpecialNotes { get; set; }
|
|
}
|
|
}
|