admin: add common-code crud and business-day rules

This commit is contained in:
2026-07-02 10:27:57 +09:00
parent df4c555dd1
commit 1839c2c3d1
17 changed files with 628 additions and 170 deletions
@@ -0,0 +1,88 @@
namespace TaxBaik.Web.Components.Admin.Shared;
public static class BusinessDayCalculator
{
private sealed record HolidayWindow(DateOnly Start, DateOnly End)
{
public IEnumerable<DateOnly> 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<DateOnly> 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<DateOnly> BuildHolidayDates()
{
var holidays = new HashSet<DateOnly>();
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;
}
}
@@ -0,0 +1,56 @@
@using TaxBaik.Domain.Entities
@using TaxBaik.Web.Services.AdminClients
@inject ICommonCodeBrowserClient CommonCodeClient
<MudSelect T="string"
Value="Value"
ValueChanged="ValueChanged"
Label="@Label"
Variant="@Variant"
FullWidth="@FullWidth"
Class="@Class"
Required="@Required"
Clearable="@Clearable"
Disabled="@Disabled">
@if (!string.IsNullOrWhiteSpace(Placeholder))
{
<MudSelectItem Value="@string.Empty">@Placeholder</MudSelectItem>
}
@foreach (var item in items)
{
<MudSelectItem Value="@item.CodeValue">@item.CodeName</MudSelectItem>
}
</MudSelect>
@code {
[Parameter] public string? Value { get; set; }
[Parameter] public EventCallback<string?> 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<CommonCode> 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;
}