feat: 시즌별 마케팅 + 공지사항 관리 기능 추가
- 연간 세무 캘린더(7개 시즌) 기반 자동 Hero 섹션 전환 - 시즌 감지 시 D-Day 카운트다운, 긴박감 배지, 시즌 CTA 표시 - 서비스 카드 순서 시즌 관련 항목 우선 정렬 - 어드민 공지사항 CRUD (등록·수정·삭제, 기간·유형 설정) - 홈페이지 상단 공지 배너 자동 노출 (일반/배너/긴급) - CLAUDE.md에 세무 캘린더 및 마케팅 방향 하네스 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
namespace TaxBaik.Application.Services;
|
||||
|
||||
using TaxBaik.Application.DTOs;
|
||||
using TaxBaik.Domain.Entities;
|
||||
using TaxBaik.Domain.Interfaces;
|
||||
|
||||
public class AnnouncementService(IAnnouncementRepository repository)
|
||||
{
|
||||
public Task<IEnumerable<Announcement>> GetActiveAsync(CancellationToken ct = default)
|
||||
=> repository.GetActiveAsync(ct);
|
||||
|
||||
public Task<IEnumerable<Announcement>> GetAllAsync(CancellationToken ct = default)
|
||||
=> repository.GetAllAsync(ct);
|
||||
|
||||
public Task<Announcement?> GetByIdAsync(int id, CancellationToken ct = default)
|
||||
=> repository.GetByIdAsync(id, ct);
|
||||
|
||||
public Task<int> CreateAsync(AnnouncementDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var entity = MapToEntity(dto);
|
||||
return repository.CreateAsync(entity, ct);
|
||||
}
|
||||
|
||||
public Task UpdateAsync(AnnouncementDto dto, CancellationToken ct = default)
|
||||
{
|
||||
var entity = MapToEntity(dto);
|
||||
return repository.UpdateAsync(entity, ct);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(int id, CancellationToken ct = default)
|
||||
=> repository.DeleteAsync(id, ct);
|
||||
|
||||
private static Announcement MapToEntity(AnnouncementDto dto) => new()
|
||||
{
|
||||
Id = dto.Id,
|
||||
Title = dto.Title.Trim(),
|
||||
Content = string.IsNullOrWhiteSpace(dto.Content) ? null : dto.Content.Trim(),
|
||||
DisplayType = dto.DisplayType,
|
||||
IsActive = dto.IsActive,
|
||||
StartsAt = dto.StartsAt,
|
||||
EndsAt = dto.EndsAt,
|
||||
SortOrder = dto.SortOrder
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace TaxBaik.Application.Services;
|
||||
|
||||
using TaxBaik.Application.Seasonal;
|
||||
|
||||
public class SeasonalMarketingService
|
||||
{
|
||||
public CurrentSeasonDto? GetCurrentSeason()
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
|
||||
foreach (var season in TaxSeasonCalendar.Seasons)
|
||||
{
|
||||
var start = new DateTime(today.Year, season.StartMonth, season.StartDay);
|
||||
var end = new DateTime(today.Year, season.EndMonth, season.EndDay);
|
||||
|
||||
if (today >= start && today <= end)
|
||||
{
|
||||
var days = (end - today).Days;
|
||||
return new CurrentSeasonDto
|
||||
{
|
||||
Key = season.Key,
|
||||
Name = season.Name,
|
||||
HeroHeadline = season.HeroHeadline,
|
||||
HeroSubtext = season.HeroSubtext,
|
||||
UrgencyBadge = season.UrgencyBadge.Replace("{n}", days.ToString()),
|
||||
FocusService = season.FocusService,
|
||||
CtaText = season.CtaText,
|
||||
DaysUntilDeadline = days,
|
||||
Deadline = end
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TaxSeason> GetFullCalendar() => TaxSeasonCalendar.Seasons;
|
||||
}
|
||||
Reference in New Issue
Block a user