|
@@ -103,7 +105,7 @@
|
@f.FilingType |
- @f.DueDate.ToString("yyyy-MM-dd") |
+ @effectiveDueDate.ToDateTime(TimeOnly.MinValue).ToString("yyyy-MM-dd") |
@if (dday < 0)
{
@@ -175,35 +177,30 @@
private string? errorMessage;
private bool isLoading = true;
- protected override async Task OnAfterRenderAsync(bool firstRender)
+ protected override async Task OnInitializedAsync()
{
- if (firstRender)
+ if (AuthStateTask != null)
{
- if (AuthStateTask != null)
+ var authState = await AuthStateTask;
+ if (authState.User.Identity?.IsAuthenticated == true)
{
- var authState = await AuthStateTask;
- if (authState.User.Identity?.IsAuthenticated == true)
+ try
{
- try
- {
- // API 클라이언트 사용 (서비스 직접 호출 X)
- var summaryTask = DashboardClient.GetSummaryAsync();
- var filingsTask = DashboardClient.GetUpcomingFilingsAsync(30);
+ var summaryTask = DashboardClient.GetSummaryAsync();
+ var filingsTask = DashboardClient.GetUpcomingFilingsAsync(30);
- await Task.WhenAll(summaryTask, filingsTask);
- summary = await summaryTask;
- upcomingFilings = (await filingsTask).ToList();
- }
- catch (Exception ex)
- {
- errorMessage = "대시보드 데이터를 불러올 수 없습니다.";
- Console.Error.WriteLine($"Dashboard error: {ex.Message}");
- }
- finally
- {
- isLoading = false;
- StateHasChanged();
- }
+ await Task.WhenAll(summaryTask, filingsTask);
+ summary = await summaryTask;
+ upcomingFilings = (await filingsTask).ToList();
+ }
+ catch (Exception ex)
+ {
+ errorMessage = "대시보드 데이터를 불러올 수 없습니다.";
+ Console.Error.WriteLine($"Dashboard error: {ex.Message}");
+ }
+ finally
+ {
+ isLoading = false;
}
}
}
diff --git a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor
index 7a40ff8..beb3c3a 100644
--- a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor
+++ b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor
@@ -5,15 +5,12 @@
문의 관리
-
-
- Customer Requests
- 문의 관리
- 상담 요청을 상태별로 확인하고 후속 조치를 기록합니다.
-
- 새 문의 등록
-
+
+
+ 새 문의 등록
+
+
@if (isLoading)
@@ -52,18 +49,14 @@ else
private bool isLoading = true;
private IReadOnlyList allInquiries = [];
- protected override async Task OnAfterRenderAsync(bool firstRender)
+ protected override async Task OnInitializedAsync()
{
- if (firstRender)
+ if (AuthStateTask != null)
{
- if (AuthStateTask != null)
+ var authState = await AuthStateTask;
+ if (authState.User.Identity?.IsAuthenticated == true)
{
- var authState = await AuthStateTask;
- if (authState.User.Identity?.IsAuthenticated == true)
- {
- await LoadData();
- StateHasChanged();
- }
+ await LoadData();
}
}
}
diff --git a/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor b/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor
index 48404b0..c1358c5 100644
--- a/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor
+++ b/TaxBaik.Web/Components/Admin/Pages/TaxFilingSchedules.razor
@@ -1,5 +1,7 @@
@page "/admin/tax-filing-schedules"
@using TaxBaik.Web.Services.AdminClients
+@using TaxBaik.Domain.Entities
+@using TaxBaik.Web.Components.Admin.Shared
@inject ITaxFilingScheduleBrowserClient TaxFilingClient
@inject IClientBrowserClient ClientClient
@inject ISnackbar Snackbar
@@ -61,11 +63,12 @@ else
@{
- var daysLeft = (context.Item.DueDate.Date - DateTime.Today).Days;
+ var effectiveDueDate = BusinessDayCalculator.GetEffectiveDueDate(DateOnly.FromDateTime(context.Item.DueDate));
+ var daysLeft = BusinessDayCalculator.GetDday(DateOnly.FromDateTime(context.Item.DueDate));
var statusColor = daysLeft < 0 ? Color.Error : daysLeft <= 7 ? Color.Warning : Color.Success;
}
- @context.Item.DueDate.ToString("yyyy-MM-dd")
+ @effectiveDueDate.ToDateTime(TimeOnly.MinValue).ToString("yyyy-MM-dd")
@if (daysLeft >= 0)
{
(D-@daysLeft)
@@ -139,16 +142,7 @@ else
@GetClientDisplayName(client)
}
-
- 종합소득세
- 부가가치세
- 법인세
- 원천세
- 종합부동산세
- 양도소득세
- 상속·증여세
- 세무조정
-
+
@@ -224,7 +218,8 @@ else
{
FilingYear = DateTime.Now.Year,
DueDate = DateTime.Today,
- ClientId = clients.FirstOrDefault()?.Id
+ ClientId = clients.FirstOrDefault()?.Id,
+ FilingType = string.Empty
};
}
diff --git a/TaxBaik.Web/Components/Admin/Pages/TaxProfiles.razor b/TaxBaik.Web/Components/Admin/Pages/TaxProfiles.razor
index fa13abd..9baf7f0 100644
--- a/TaxBaik.Web/Components/Admin/Pages/TaxProfiles.razor
+++ b/TaxBaik.Web/Components/Admin/Pages/TaxProfiles.razor
@@ -1,8 +1,8 @@
@page "/admin/tax-profiles"
@using TaxBaik.Web.Services.AdminClients
+@using TaxBaik.Web.Components.Admin.Shared
@inject ITaxProfileBrowserClient TaxProfileClient
@inject IClientBrowserClient ClientClient
-@inject ICommonCodeBrowserClient CommonCodeClient
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@attribute [Authorize]
@@ -100,18 +100,8 @@ else
@GetClientDisplayName(client)
}
-
- @foreach (var type in businessTypes)
- {
- @type.CodeName
- }
-
-
- @foreach (var level in riskLevels)
- {
- @level.CodeName
- }
-
+
+
@@ -135,26 +125,21 @@ else
private List? profiles;
private List clients = [];
private Dictionary clientMap = new();
- private List businessTypes = [];
private List riskLevels = [];
private MudForm? form;
private bool isEditMode;
private TaxProfile? selectedProfile;
private TaxProfileForm profileForm = new();
- protected override async Task OnAfterRenderAsync(bool firstRender)
+ protected override async Task OnInitializedAsync()
{
- if (firstRender)
+ if (AuthStateTask != null)
{
- if (AuthStateTask != null)
+ var authState = await AuthStateTask;
+ if (authState.User.Identity?.IsAuthenticated == true)
{
- var authState = await AuthStateTask;
- if (authState.User.Identity?.IsAuthenticated == true)
- {
- await LoadData();
- PrepareCreate();
- StateHasChanged();
- }
+ await LoadData();
+ PrepareCreate();
}
}
}
@@ -168,31 +153,6 @@ else
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)
{
diff --git a/TaxBaik.Web/Components/Admin/Shared/BusinessDayCalculator.cs b/TaxBaik.Web/Components/Admin/Shared/BusinessDayCalculator.cs
new file mode 100644
index 0000000..087c9c7
--- /dev/null
+++ b/TaxBaik.Web/Components/Admin/Shared/BusinessDayCalculator.cs
@@ -0,0 +1,88 @@
+namespace TaxBaik.Web.Components.Admin.Shared;
+
+public static class BusinessDayCalculator
+{
+ private sealed record HolidayWindow(DateOnly Start, DateOnly End)
+ {
+ public IEnumerable Dates()
+ {
+ for (var date = Start; date <= End; date = date.AddDays(1))
+ {
+ yield return date;
+ }
+ }
+ }
+
+ private static readonly HolidayWindow[] HolidayWindows =
+ {
+ new(new DateOnly(2026, 1, 1), new DateOnly(2026, 1, 1)),
+ new(new DateOnly(2026, 2, 16), new DateOnly(2026, 2, 18)),
+ new(new DateOnly(2026, 3, 1), new DateOnly(2026, 3, 2)),
+ new(new DateOnly(2026, 5, 5), new DateOnly(2026, 5, 5)),
+ new(new DateOnly(2026, 6, 6), new DateOnly(2026, 6, 6)),
+ new(new DateOnly(2026, 8, 15), new DateOnly(2026, 8, 17)),
+ new(new DateOnly(2026, 9, 24), new DateOnly(2026, 9, 26)),
+ new(new DateOnly(2026, 10, 3), new DateOnly(2026, 10, 5)),
+ new(new DateOnly(2026, 10, 9), new DateOnly(2026, 10, 9)),
+ new(new DateOnly(2026, 12, 25), new DateOnly(2026, 12, 25))
+ };
+
+ private static readonly HashSet HolidayDates = BuildHolidayDates();
+
+ public static DateOnly GetEffectiveDueDate(DateOnly dueDate)
+ {
+ var effectiveDate = dueDate;
+ while (!IsBusinessDay(effectiveDate))
+ {
+ effectiveDate = effectiveDate.AddDays(1);
+ }
+
+ return effectiveDate;
+ }
+
+ public static int GetDday(DateOnly dueDate, DateOnly? referenceDate = null)
+ {
+ var today = referenceDate ?? DateOnly.FromDateTime(DateTime.Today);
+ var effectiveDueDate = GetEffectiveDueDate(dueDate);
+ return effectiveDueDate.DayNumber - today.DayNumber;
+ }
+
+ public static bool IsBusinessDay(DateOnly date)
+ => date.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday
+ && !HolidayDates.Contains(date);
+
+ private static HashSet BuildHolidayDates()
+ {
+ var holidays = new HashSet();
+
+ foreach (var window in HolidayWindows)
+ {
+ foreach (var date in window.Dates())
+ {
+ holidays.Add(date);
+ }
+ }
+
+ // 주말과 연속 공휴일 뒤에 붙는 대체휴일을 다음 영업일로 자동 확장한다.
+ foreach (var window in HolidayWindows)
+ {
+ foreach (var date in window.Dates())
+ {
+ if (date.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday)
+ {
+ continue;
+ }
+
+ var substitute = date.AddDays(1);
+ while (substitute.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday || holidays.Contains(substitute))
+ {
+ substitute = substitute.AddDays(1);
+ }
+
+ holidays.Add(substitute);
+ }
+ }
+
+ return holidays;
+ }
+}
diff --git a/TaxBaik.Web/Components/Admin/Shared/CommonCodeSelect.razor b/TaxBaik.Web/Components/Admin/Shared/CommonCodeSelect.razor
new file mode 100644
index 0000000..f0e4fdf
--- /dev/null
+++ b/TaxBaik.Web/Components/Admin/Shared/CommonCodeSelect.razor
@@ -0,0 +1,56 @@
+@using TaxBaik.Domain.Entities
+@using TaxBaik.Web.Services.AdminClients
+@inject ICommonCodeBrowserClient CommonCodeClient
+
+
+ @if (!string.IsNullOrWhiteSpace(Placeholder))
+ {
+ @Placeholder
+ }
+ @foreach (var item in items)
+ {
+ @item.CodeName
+ }
+
+
+@code {
+ [Parameter] public string? Value { get; set; }
+ [Parameter] public EventCallback ValueChanged { get; set; }
+ [Parameter] public string Group { get; set; } = string.Empty;
+ [Parameter] public string Label { get; set; } = string.Empty;
+ [Parameter] public Variant Variant { get; set; } = Variant.Outlined;
+ [Parameter] public bool FullWidth { get; set; } = true;
+ [Parameter] public string? Class { get; set; }
+ [Parameter] public bool Required { get; set; }
+ [Parameter] public bool Clearable { get; set; }
+ [Parameter] public bool Disabled { get; set; }
+ [Parameter] public string? Placeholder { get; set; }
+
+ private List items = [];
+
+ protected override async Task OnParametersSetAsync()
+ {
+ var normalizedGroup = Group?.Trim() ?? string.Empty;
+ if (!string.Equals(normalizedGroup, _loadedGroup, StringComparison.OrdinalIgnoreCase))
+ {
+ _loadedGroup = normalizedGroup;
+ items = string.IsNullOrWhiteSpace(normalizedGroup)
+ ? []
+ : (await CommonCodeClient.GetByGroupAsync(normalizedGroup))
+ .OrderBy(x => x.SortOrder)
+ .ThenBy(x => x.CodeName)
+ .ToList();
+ }
+ }
+
+ private string? _loadedGroup;
+}
diff --git a/TaxBaik.Web/Controllers/CommonCodeController.cs b/TaxBaik.Web/Controllers/CommonCodeController.cs
index 4cdc09b..8d70f49 100644
--- a/TaxBaik.Web/Controllers/CommonCodeController.cs
+++ b/TaxBaik.Web/Controllers/CommonCodeController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TaxBaik.Application.Services;
+using TaxBaik.Domain.Entities;
namespace TaxBaik.Web.Controllers;
@@ -36,4 +37,44 @@ public class CommonCodeController(CommonCodeService commonCodeService) : Control
return StatusCode(500, new { error = "그룹별 공통코드 조회 실패", message = ex.Message });
}
}
+
+ [HttpGet("groups")]
+ public async Task GetGroups()
+ {
+ try
+ {
+ var groups = await commonCodeService.GetAllGroupsAsync();
+ return Ok(groups);
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new { error = "공통코드 그룹 조회 실패", message = ex.Message });
+ }
+ }
+
+ [HttpGet("{group}/{value}")]
+ public async Task Get(string group, string value)
+ {
+ var code = await commonCodeService.GetAsync(group, value);
+ return code is null ? NotFound() : Ok(code);
+ }
+
+ [HttpPost]
+ public async Task Upsert([FromBody] CommonCode code)
+ {
+ if (string.IsNullOrWhiteSpace(code.CodeGroup) || string.IsNullOrWhiteSpace(code.CodeValue) || string.IsNullOrWhiteSpace(code.CodeName))
+ return BadRequest(new { error = "코드 그룹, 값, 이름은 필수입니다." });
+ if (code.CodeValue.Contains(' '))
+ return BadRequest(new { error = "code_value에는 공백을 사용할 수 없습니다." });
+
+ await commonCodeService.UpsertAsync(code);
+ return Ok(code);
+ }
+
+ [HttpDelete("{group}/{value}")]
+ public async Task Delete(string group, string value)
+ {
+ await commonCodeService.DeleteAsync(group, value);
+ return NoContent();
+ }
}
|