diff --git a/src/TaxBaik.Web/Controllers/AnnouncementController.cs b/src/TaxBaik.Web/Controllers/AnnouncementController.cs.bak similarity index 100% rename from src/TaxBaik.Web/Controllers/AnnouncementController.cs rename to src/TaxBaik.Web/Controllers/AnnouncementController.cs.bak diff --git a/src/TaxBaik.Web/Controllers/CategoryController.cs b/src/TaxBaik.Web/Controllers/CategoryController.cs.bak similarity index 100% rename from src/TaxBaik.Web/Controllers/CategoryController.cs rename to src/TaxBaik.Web/Controllers/CategoryController.cs.bak diff --git a/src/TaxBaik.Web/Controllers/FaqController.cs b/src/TaxBaik.Web/Controllers/FaqController.cs.bak similarity index 100% rename from src/TaxBaik.Web/Controllers/FaqController.cs rename to src/TaxBaik.Web/Controllers/FaqController.cs.bak diff --git a/src/TaxBaik.Web/Controllers/RevenueTrackingController.cs b/src/TaxBaik.Web/Controllers/RevenueTrackingController.cs.bak similarity index 100% rename from src/TaxBaik.Web/Controllers/RevenueTrackingController.cs rename to src/TaxBaik.Web/Controllers/RevenueTrackingController.cs.bak diff --git a/src/TaxBaik.Web/Endpoints/Announcement/AllEndpoints.cs b/src/TaxBaik.Web/Endpoints/Announcement/AllEndpoints.cs new file mode 100644 index 0000000..559021e --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/Announcement/AllEndpoints.cs @@ -0,0 +1,182 @@ +using FastEndpoints; +using TaxBaik.Application.DTOs; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.Announcement; + +// DTOs +public class GetAnnouncementResponse +{ + public List Data { get; set; } = []; +} + +public class CreateAnnouncementResponse +{ + public int Id { get; set; } +} + +public class UpdateAnnouncementResponse +{ + public string Message { get; set; } = string.Empty; +} + +// Endpoints +public class GetActiveEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public GetActiveEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Get("/api/announcement/active"); + AllowAnonymous(); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + try + { + var announcements = await _service.GetActiveAsync(ct); + await SendAsync(new GetAnnouncementResponse { Data = announcements.Cast().ToList() }, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class GetAllEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public GetAllEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Get("/api/announcement"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + try + { + var announcements = await _service.GetAllAsync(ct); + await SendAsync(new GetAnnouncementResponse { Data = announcements.Cast().ToList() }, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class GetByIdEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public GetByIdEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Get("/api/announcement/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("id"); + try + { + var announcement = await _service.GetByIdAsync(id, ct); + if (announcement == null) + ThrowError("공지사항을 찾을 수 없습니다.", statusCode: 404); + await SendAsync(announcement, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class CreateEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public CreateEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Post("/api/announcement"); + Policies("Bearer"); + } + + public override async Task HandleAsync(AnnouncementDto request, CancellationToken ct) + { + try + { + var announcementId = await _service.CreateAsync(request, ct); + var result = await _service.GetByIdAsync(announcementId, ct); + await SendAsync(new CreateAnnouncementResponse { Id = announcementId }, 201, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 400); + } + } +} + +public class UpdateEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public UpdateEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Put("/api/announcement/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(AnnouncementDto request, CancellationToken ct) + { + var id = Route("id"); + request.Id = id; + try + { + await _service.UpdateAsync(request, ct); + var result = await _service.GetByIdAsync(id, ct); + if (result == null) + ThrowError("공지사항을 찾을 수 없습니다.", statusCode: 404); + await SendAsync(new UpdateAnnouncementResponse { Message = "공지사항이 수정되었습니다." }, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 400); + } + } +} + +public class DeleteEndpoint : Endpoint +{ + private readonly AnnouncementService _service; + public DeleteEndpoint(AnnouncementService service) => _service = service; + + public override void Configure() + { + Delete("/api/announcement/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("id"); + try + { + await _service.DeleteAsync(id, ct); + await SendAsync(new EmptyResponse(), 204, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/Category/AllEndpoints.cs b/src/TaxBaik.Web/Endpoints/Category/AllEndpoints.cs new file mode 100644 index 0000000..c20b71c --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/Category/AllEndpoints.cs @@ -0,0 +1,171 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.Category; + +// DTOs +public class CreateCategoryRequest +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } +} + +public class CategoryResponse +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public int SortOrder { get; set; } +} + +public class CategoryListResponse +{ + public List Data { get; set; } = []; +} + +public class CategoryCreateResponse +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; +} + +// Endpoints +public class GetAllEndpoint : Endpoint +{ + private readonly CategoryService _service; + public GetAllEndpoint(CategoryService service) => _service = service; + + public override void Configure() + { + Get("/api/category"); + AllowAnonymous(); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + try + { + var categories = await _service.GetAllAsync(ct); + var response = new CategoryListResponse + { + Data = categories.Select(c => new CategoryResponse + { + Id = c.Id, + Name = c.Name, + Slug = c.Slug, + SortOrder = c.SortOrder + }).ToList() + }; + await SendAsync(response, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class CreateEndpoint : Endpoint +{ + private readonly CategoryService _service; + public CreateEndpoint(CategoryService service) => _service = service; + + public override void Configure() + { + Post("/api/category"); + Policies("Bearer"); + } + + public override async Task HandleAsync(CreateCategoryRequest request, CancellationToken ct) + { + try + { + if (string.IsNullOrWhiteSpace(request.Name)) + ThrowError("카테고리 이름은 필수입니다."); + + var category = await _service.CreateAsync(request.Name, request.Description, ct); + var response = new CategoryCreateResponse + { + Id = category.Id, + Name = category.Name, + Slug = category.Slug + }; + await SendAsync(response, 201, cancellation: ct); + } + catch (ValidationException ex) + { + ThrowError(ex.Message); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class UpdateEndpoint : Endpoint +{ + private readonly CategoryService _service; + public UpdateEndpoint(CategoryService service) => _service = service; + + public override void Configure() + { + Put("/api/category/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(CreateCategoryRequest request, CancellationToken ct) + { + var id = Route("id"); + try + { + var category = await _service.UpdateAsync(id, request.Name, request.Description, ct); + if (category == null) + ThrowError("카테고리를 찾을 수 없습니다.", statusCode: 404); + + var response = new CategoryResponse + { + Id = category.Id, + Name = category.Name, + Slug = category.Slug, + SortOrder = category.SortOrder + }; + await SendAsync(response, 200, cancellation: ct); + } + catch (ValidationException ex) + { + ThrowError(ex.Message); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class DeleteEndpoint : Endpoint +{ + private readonly CategoryService _service; + public DeleteEndpoint(CategoryService service) => _service = service; + + public override void Configure() + { + Delete("/api/category/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("id"); + try + { + await _service.DeleteAsync(id, ct); + await SendAsync(new EmptyResponse(), 204, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/Faq/AllEndpoints.cs b/src/TaxBaik.Web/Endpoints/Faq/AllEndpoints.cs new file mode 100644 index 0000000..00e828f --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/Faq/AllEndpoints.cs @@ -0,0 +1,223 @@ +using FastEndpoints; +using TaxBaik.Application.Services; +using TaxBaik.Domain.Entities; +using FaqEntity = TaxBaik.Domain.Entities.Faq; + +namespace TaxBaik.Web.Endpoints.Faq; + +// DTOs +public class CreateFaqRequest +{ + public string Question { get; set; } = string.Empty; + public string Answer { get; set; } = string.Empty; + public string? Category { get; set; } + public int SortOrder { get; set; } +} + +public class UpdateFaqRequest +{ + public string Question { get; set; } = string.Empty; + public string Answer { get; set; } = string.Empty; + public string? Category { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; +} + +public class FaqListResponse +{ + public List Data { get; set; } = []; +} + +public class FaqCreateResponse +{ + public int Id { get; set; } +} + +public class FaqUpdateResponse +{ + public string Message { get; set; } = string.Empty; +} + +// Endpoints +public class GetActiveEndpoint : Endpoint +{ + private readonly FaqService _service; + public GetActiveEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Get("/api/faq/active"); + AllowAnonymous(); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + try + { + var faqs = await _service.GetActiveAsync(ct); + await SendAsync(new FaqListResponse { Data = faqs.ToList() }, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class GetAllEndpoint : Endpoint +{ + private readonly FaqService _service; + public GetAllEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Get("/api/faq"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + try + { + var faqs = await _service.GetAllAsync(ct); + await SendAsync(new FaqListResponse { Data = faqs.ToList() }, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class GetByIdEndpoint : Endpoint +{ + private readonly FaqService _service; + public GetByIdEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Get("/api/faq/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("id"); + try + { + var faq = await _service.GetByIdAsync(id, ct); + if (faq == null) + ThrowError("FAQ를 찾을 수 없습니다.", statusCode: 404); + await SendAsync(faq, 200, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class CreateEndpoint : Endpoint +{ + private readonly FaqService _service; + public CreateEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Post("/api/faq"); + Policies("Bearer"); + } + + public override async Task HandleAsync(CreateFaqRequest request, CancellationToken ct) + { + try + { + var faq = new FaqEntity + { + Question = request.Question, + Answer = request.Answer, + Category = request.Category, + SortOrder = request.SortOrder, + IsActive = true, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + var id = await _service.CreateAsync(faq, ct); + await SendAsync(new FaqCreateResponse { Id = id }, 201, cancellation: ct); + } + catch (ValidationException ex) + { + ThrowError(ex.Message); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class UpdateEndpoint : Endpoint +{ + private readonly FaqService _service; + public UpdateEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Put("/api/faq/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(UpdateFaqRequest request, CancellationToken ct) + { + var id = Route("id"); + try + { + var faq = new FaqEntity + { + Id = id, + Question = request.Question, + Answer = request.Answer, + Category = request.Category, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + UpdatedAt = DateTime.UtcNow + }; + await _service.UpdateAsync(faq, ct); + await SendAsync(new FaqUpdateResponse { Message = "FAQ가 수정되었습니다." }, 200, cancellation: ct); + } + catch (ValidationException ex) + { + ThrowError(ex.Message); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} + +public class DeleteEndpoint : Endpoint +{ + private readonly FaqService _service; + public DeleteEndpoint(FaqService service) => _service = service; + + public override void Configure() + { + Delete("/api/faq/{id}"); + Policies("Bearer"); + } + + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("id"); + try + { + await _service.DeleteAsync(id, ct); + await SendAsync(new EmptyResponse(), 204, cancellation: ct); + } + catch (Exception ex) + { + ThrowError(ex.Message, statusCode: 500); + } + } +} diff --git a/src/TaxBaik.Web/Endpoints/RevenueTracking/AllEndpoints.cs b/src/TaxBaik.Web/Endpoints/RevenueTracking/AllEndpoints.cs new file mode 100644 index 0000000..8576388 --- /dev/null +++ b/src/TaxBaik.Web/Endpoints/RevenueTracking/AllEndpoints.cs @@ -0,0 +1,108 @@ +using FastEndpoints; +using TaxBaik.Application.Services; + +namespace TaxBaik.Web.Endpoints.RevenueTracking; + +public class CreateRequest +{ + public int ClientId { get; set; } + public string InvoiceNumber { get; set; } = ""; + public DateTime InvoiceDate { get; set; } + public decimal Amount { get; set; } + public string? ServiceType { get; set; } + public DateTime? DueDate { get; set; } +} + +public class MarkPaidRequest { public DateTime PaymentDate { get; set; } } +public class ListResp { public List Data { get; set; } = []; } +public class IdResp { public int Id { get; set; } } +public class TotalResp { public decimal Total { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } +public class MonthlyQry { public int Year { get; set; } public int Month { get; set; } } +public class DateRangeQry { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } } + +public class CreateEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public CreateEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Post("/api/revenue-tracking"); Policies("Bearer"); } + public override async Task HandleAsync(CreateRequest r, CancellationToken ct) + { + var id = await _svc.CreateAsync(r.ClientId, r.InvoiceNumber, r.InvoiceDate, r.Amount, r.ServiceType, r.DueDate, ct); + await SendAsync(new IdResp { Id = id }, 201, cancellation: ct); + } +} + +public class GetAllEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public GetAllEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Get("/api/revenue-tracking"); Policies("Bearer"); } + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var revs = await _svc.GetAllAsync(ct); + await SendAsync(new ListResp { Data = revs.Cast().ToList() }, 200, cancellation: ct); + } +} + +public class GetByClientEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public GetByClientEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Get("/api/revenue-tracking/client/{clientId}"); Policies("Bearer"); } + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var id = Route("clientId"); + var revs = await _svc.GetByClientIdAsync(id, ct); + await SendAsync(new ListResp { Data = revs.Cast().ToList() }, 200, cancellation: ct); + } +} + +public class GetPendingEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public GetPendingEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Get("/api/revenue-tracking/pending"); Policies("Bearer"); } + public override async Task HandleAsync(EmptyRequest _, CancellationToken ct) + { + var revs = await _svc.GetPendingPaymentsAsync(ct); + await SendAsync(new ListResp { Data = revs.Cast().ToList() }, 200, cancellation: ct); + } +} + +public class GetMonthlyEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public GetMonthlyEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Get("/api/revenue-tracking/monthly"); Policies("Bearer"); } + public override async Task HandleAsync(MonthlyQry r, CancellationToken ct) + { + var monthDate = new DateTime(r.Year, r.Month, 1); + var revs = await _svc.GetMonthlyRevenueAsync(monthDate, ct); + await SendAsync(new ListResp { Data = revs.Cast().ToList() }, 200, cancellation: ct); + } +} + +public class GetTotalEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public GetTotalEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Get("/api/revenue-tracking/total"); Policies("Bearer"); } + public override async Task HandleAsync(DateRangeQry r, CancellationToken ct) + { + var total = await _svc.GetTotalRevenueAsync(r.StartDate, r.EndDate, ct); + await SendAsync(new TotalResp { Total = total, StartDate = r.StartDate, EndDate = r.EndDate }, 200, cancellation: ct); + } +} + +public class MarkPaidEp : Endpoint +{ + readonly RevenueTrackingService _svc; + public MarkPaidEp(RevenueTrackingService svc) => _svc = svc; + public override void Configure() { Put("/api/revenue-tracking/{id}/paid"); Policies("Bearer"); } + public override async Task HandleAsync(MarkPaidRequest r, CancellationToken ct) + { + var id = Route("id"); + await _svc.MarkPaidAsync(id, r.PaymentDate, ct); + await SendAsync(new { message = "결제가 완료됨으로 표시되었습니다." }, 200, cancellation: ct); + } +}