refactor: move buildable .NET source into src/, update CI/doc paths
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m7s
Groups the repo root into src (buildable source), docs (already existed), and everything else (db/, scripts/, tests/, deploy/ - deployment/ops/test assets that aren't compiled, already organized as their own folders). CI now only needs src/ to build: dotnet restore/build/test/publish all point at src/TaxBaik.sln, src/TaxBaik.Web/, src/TaxBaik.Proxy/. - git mv every project (Domain, Infrastructure, Application, Application.Tests, Web, Web.Client, Proxy) and TaxBaik.sln into src/ as a unit, so relative ProjectReference/.sln paths stay valid unchanged. - .gitea/workflows/deploy.yml: 6 dotnet restore/clean/build/test/publish invocations now point at src/. db/migrations and scripts/ stay at root (deploy_gb.sh and browser-e2e.yml only touch published output and the deployed URL, not source paths - verified, no changes needed there). - scripts/validate_admin_render.sh: admin render-mode file paths now src/TaxBaik.Web.Client/... - scripts/validate_kst_timestamps.sh: dropped deploy.sh from its target list - that script was removed in the prior cleanup commit (dead, no CI workflow referenced it) but this validator still expected it to exist. - CLAUDE.md, docs/ENGINEERING_HARNESS.md, docs/ADMIN_PATTERN_CRITIQUE_WBS.md: updated project-structure diagram, dotnet run/build commands, and grep targets to the new src/ paths (also fixed a pre-existing stale path in ADMIN_PATTERN_CRITIQUE_WBS.md that still said TaxBaik.Web/Components/Admin from before that ever moved to TaxBaik.Web.Client). - Added a Repo Root harness rule + Architecture Guardrail entries: new files belong under src/docs/tests/scripts/db/deploy, not loose at root; temp work stays outside the repo (or under a gitignored .scratch/) and is never committed. Verified locally: dotnet build/test src/TaxBaik.sln (26/26 tests), and all three scripts/validate_*.sh pass against the new layout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 관리자 대시보드 API
|
||||
/// SOLID: Single Responsibility - 대시보드 데이터만 담당
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/admin-dashboard")]
|
||||
[Authorize]
|
||||
public class AdminDashboardController : ControllerBase
|
||||
{
|
||||
private readonly AdminDashboardService _dashboardService;
|
||||
private readonly TaxFilingService _taxFilingService;
|
||||
|
||||
public AdminDashboardController(
|
||||
AdminDashboardService dashboardService,
|
||||
TaxFilingService taxFilingService)
|
||||
{
|
||||
_dashboardService = dashboardService;
|
||||
_taxFilingService = taxFilingService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대시보드 요약 정보 조회
|
||||
/// GET /api/admin-dashboard/summary
|
||||
/// </summary>
|
||||
[HttpGet("summary")]
|
||||
public async Task<IActionResult> GetSummary()
|
||||
{
|
||||
try
|
||||
{
|
||||
var summary = await _dashboardService.GetSummaryAsync();
|
||||
return Ok(summary);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "대시보드 요약 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 30일 이내 마감 임박 신고 조회
|
||||
/// GET /api/admin-dashboard/upcoming-filings?days=30
|
||||
/// </summary>
|
||||
[HttpGet("upcoming-filings")]
|
||||
public async Task<IActionResult> GetUpcomingFilings([FromQuery] int days = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (days <= 0) days = 30;
|
||||
var filings = await _taxFilingService.GetUpcomingAsync(days);
|
||||
return Ok(new { data = filings, days });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "마감 임박 신고 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 최근 문의 조회
|
||||
/// GET /api/admin-dashboard/recent-inquiries?limit=10
|
||||
/// </summary>
|
||||
[HttpGet("recent-inquiries")]
|
||||
public async Task<IActionResult> GetRecentInquiries([FromQuery] int limit = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (limit <= 0) limit = 10;
|
||||
if (limit > 100) limit = 100; // 보안: 최대 100개
|
||||
|
||||
var inquiries = await _dashboardService.GetRecentInquiriesAsync(limit);
|
||||
return Ok(new { data = inquiries, limit });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "최근 문의 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 월별 통계
|
||||
/// GET /api/admin-dashboard/monthly-stats?month=2026-06
|
||||
/// </summary>
|
||||
[HttpGet("monthly-stats")]
|
||||
public async Task<IActionResult> GetMonthlyStats([FromQuery] string? month = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _dashboardService.GetMonthlyStatsAsync(month);
|
||||
return Ok(stats);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails
|
||||
{
|
||||
Title = "월별 통계 조회 실패",
|
||||
Detail = ex.Message,
|
||||
Status = StatusCodes.Status500InternalServerError
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.DTOs;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AnnouncementController : ControllerBase
|
||||
{
|
||||
private readonly AnnouncementService _announcementService;
|
||||
|
||||
public AnnouncementController(AnnouncementService announcementService)
|
||||
{
|
||||
_announcementService = announcementService;
|
||||
}
|
||||
|
||||
[HttpGet("active")]
|
||||
public async Task<IActionResult> GetActive()
|
||||
{
|
||||
var announcements = await _announcementService.GetActiveAsync();
|
||||
return Ok(new { data = announcements });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var announcements = await _announcementService.GetAllAsync();
|
||||
return Ok(new { data = announcements });
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var announcement = await _announcementService.GetByIdAsync(id);
|
||||
if (announcement == null)
|
||||
return NotFound(new ProblemDetails { Title = "공지사항을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(announcement);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Create([FromBody] AnnouncementDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var announcementId = await _announcementService.CreateAsync(dto);
|
||||
var result = await _announcementService.GetByIdAsync(announcementId);
|
||||
return CreatedAtAction(nameof(GetById), new { id = announcementId }, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] AnnouncementDto dto)
|
||||
{
|
||||
dto.Id = id;
|
||||
try
|
||||
{
|
||||
await _announcementService.UpdateAsync(dto);
|
||||
var result = await _announcementService.GetByIdAsync(id);
|
||||
if (result == null)
|
||||
return NotFound(new ProblemDetails { Title = "공지사항을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _announcementService.DeleteAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
using TaxBaik.Web.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly AuthService _authService;
|
||||
|
||||
public AuthController(AuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Username) || string.IsNullOrWhiteSpace(request.Password))
|
||||
return BadRequest(new ProblemDetails { Title = "로그인 정보가 필요합니다.", Status = StatusCodes.Status400BadRequest });
|
||||
|
||||
var tokenPair = await _authService.AuthenticateAndGenerateTokenPairAsync(request.Username, request.Password);
|
||||
if (tokenPair == null)
|
||||
return Unauthorized(new ProblemDetails { Title = "아이디 또는 비밀번호가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
token = tokenPair.AccessToken,
|
||||
accessToken = tokenPair.AccessToken,
|
||||
refreshToken = tokenPair.RefreshToken,
|
||||
expiresIn = tokenPair.ExpiresIn
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.RefreshToken))
|
||||
return BadRequest(new ProblemDetails { Title = "Refresh token이 필요합니다.", Status = StatusCodes.Status400BadRequest });
|
||||
|
||||
var tokenPair = await _authService.RefreshAccessTokenAsync(request.RefreshToken);
|
||||
if (tokenPair == null)
|
||||
return Unauthorized(new ProblemDetails { Title = "Refresh token이 유효하지 않습니다.", Status = StatusCodes.Status401Unauthorized });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
token = tokenPair.AccessToken,
|
||||
accessToken = tokenPair.AccessToken,
|
||||
refreshToken = tokenPair.RefreshToken,
|
||||
expiresIn = tokenPair.ExpiresIn
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("change-password")]
|
||||
[Microsoft.AspNetCore.Authorization.Authorize]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
|
||||
{
|
||||
var username = User.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
return Unauthorized(new ProblemDetails { Title = "인증 정보가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
|
||||
|
||||
try
|
||||
{
|
||||
var changed = await _authService.ChangePasswordAsync(username, request.CurrentPassword, request.NewPassword);
|
||||
if (!changed)
|
||||
return Unauthorized(new ProblemDetails { Title = "현재 비밀번호가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
|
||||
|
||||
return Ok(new { message = "비밀번호가 변경되었습니다." });
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("reset-password")]
|
||||
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reset = await _authService.ResetPasswordAsync(request.Username, request.NewPassword, request.ResetToken);
|
||||
if (!reset)
|
||||
return Unauthorized(new ProblemDetails { Title = "재설정 토큰 또는 사용자 정보가 올바르지 않습니다.", Status = StatusCodes.Status401Unauthorized });
|
||||
|
||||
return Ok(new { message = "비밀번호가 재설정되었습니다." });
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable, new ProblemDetails
|
||||
{
|
||||
Title = "비밀번호 재설정 토큰이 서버에 설정되어 있지 않습니다.",
|
||||
Status = StatusCodes.Status503ServiceUnavailable
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ChangePasswordRequest
|
||||
{
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ResetPasswordRequest
|
||||
{
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
public string ResetToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class RefreshTokenRequest
|
||||
{
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Application.DTOs;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class BlogController : ControllerBase
|
||||
{
|
||||
private readonly BlogService _blogService;
|
||||
|
||||
public BlogController(BlogService blogService)
|
||||
{
|
||||
_blogService = blogService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPublished([FromQuery] int page = 1, [FromQuery] int pageSize = 12)
|
||||
{
|
||||
var (items, total) = await _blogService.GetPublishedPagedAsync(page, pageSize);
|
||||
return Ok(new { data = items, total, page, pageSize });
|
||||
}
|
||||
|
||||
[HttpGet("{slug}")]
|
||||
public async Task<IActionResult> GetBySlug(string slug)
|
||||
{
|
||||
var post = await _blogService.GetBySlugAsync(slug);
|
||||
if (post == null)
|
||||
return NotFound(new ProblemDetails { Title = "포스트를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(post);
|
||||
}
|
||||
|
||||
[HttpGet("admin/{id:int}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var post = await _blogService.GetByIdAsync(id);
|
||||
if (post == null)
|
||||
return NotFound(new ProblemDetails { Title = "포스트를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(post);
|
||||
}
|
||||
|
||||
[HttpGet("admin/all")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var posts = await _blogService.GetAllAsync();
|
||||
return Ok(posts);
|
||||
}
|
||||
|
||||
[HttpGet("admin")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAdminPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var (items, total) = await _blogService.GetAdminPagedAsync(page, pageSize);
|
||||
return Ok(new { data = items, total, page, pageSize });
|
||||
}
|
||||
|
||||
[HttpGet("admin/archived")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetArchivedPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var (items, total) = await _blogService.GetArchivedPagedAsync(page, pageSize);
|
||||
return Ok(new { data = items, total, page, pageSize });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Create([FromBody] CreateBlogPostDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _blogService.CreateAsync(dto);
|
||||
return CreatedAtAction(nameof(GetBySlug), new { slug = result.Slug }, result);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] CreateBlogPostDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _blogService.UpdateAsync(id, dto);
|
||||
if (result == null)
|
||||
return NotFound(new ProblemDetails { Title = "포스트를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _blogService.ArchiveAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("{id}/restore")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Restore(int id)
|
||||
{
|
||||
await _blogService.RestoreAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CategoryController : ControllerBase
|
||||
{
|
||||
private readonly CategoryService _categoryService;
|
||||
|
||||
public CategoryController(CategoryService categoryService)
|
||||
{
|
||||
_categoryService = categoryService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var categories = await _categoryService.GetAllAsync();
|
||||
return Ok(categories);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Create([FromBody] CreateCategoryRequest request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
return BadRequest(new { message = "Category name is required" });
|
||||
|
||||
var category = await _categoryService.CreateAsync(request.Name, request.Description);
|
||||
return CreatedAtAction(nameof(GetAll), category);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] CreateCategoryRequest request)
|
||||
{
|
||||
var category = await _categoryService.UpdateAsync(id, request.Name, request.Description);
|
||||
if (category == null)
|
||||
return NotFound(new { message = "Category not found" });
|
||||
return Ok(category);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _categoryService.DeleteAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateCategoryRequest
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.DTOs;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class ClientController : ControllerBase
|
||||
{
|
||||
private readonly ClientService _clientService;
|
||||
|
||||
public ClientController(ClientService clientService)
|
||||
{
|
||||
_clientService = clientService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged(
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20,
|
||||
[FromQuery] string? status = null,
|
||||
[FromQuery] string? search = null)
|
||||
{
|
||||
var (clients, total) = await _clientService.GetPagedAsync(page, pageSize, status, search);
|
||||
return Ok(new { data = clients, total, page, pageSize });
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var client = await _clientService.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
return NotFound(new ProblemDetails { Title = "고객을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(client);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateClientDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientId = await _clientService.CreateAsync(dto);
|
||||
var client = await _clientService.GetByIdAsync(clientId);
|
||||
return CreatedAtAction(nameof(GetById), new { id = clientId }, client);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] CreateClientDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _clientService.UpdateAsync(id, dto);
|
||||
var client = await _clientService.GetByIdAsync(id);
|
||||
if (client == null)
|
||||
return NotFound(new ProblemDetails { Title = "고객을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(client);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _clientService.DeleteAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/client-logs")]
|
||||
[AllowAnonymous]
|
||||
[EnableRateLimiting("client-logs")]
|
||||
public class ClientLogsController(ILogger<ClientLogsController> logger) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public IActionResult Post([FromBody] ClientLogEntry entry)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry.Message))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var logMessage = "ClientLog {Level} {Source} {Message} Url={Url} Route={Route} Screen={Screen} Feature={Feature} Action={Action} Step={Step} Entity={Entity} EntityId={EntityId} DataKey={DataKey} BuildVersion={BuildVersion} UserAgent={UserAgent} Stack={Stack}";
|
||||
var args = new object?[]
|
||||
{
|
||||
entry.Level ?? "error",
|
||||
entry.Source ?? "unknown",
|
||||
entry.Message,
|
||||
entry.Url ?? string.Empty,
|
||||
entry.Route ?? string.Empty,
|
||||
entry.Screen ?? string.Empty,
|
||||
entry.Feature ?? string.Empty,
|
||||
entry.Action ?? string.Empty,
|
||||
entry.Step ?? string.Empty,
|
||||
entry.Entity ?? string.Empty,
|
||||
entry.EntityId ?? string.Empty,
|
||||
entry.DataKey ?? string.Empty,
|
||||
entry.BuildVersion ?? string.Empty,
|
||||
entry.UserAgent ?? string.Empty,
|
||||
entry.Stack ?? string.Empty
|
||||
};
|
||||
|
||||
// Client errors (level: error) → Telegram alert
|
||||
// Client warnings (level: warning/info) → Log file only
|
||||
if (entry.Level?.Equals("error", StringComparison.OrdinalIgnoreCase) ?? true)
|
||||
{
|
||||
logger.LogError(logMessage, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning(logMessage, args);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ClientLogEntry
|
||||
{
|
||||
public string? Level { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public string? Url { get; set; }
|
||||
public string? Route { get; set; }
|
||||
public string? Screen { get; set; }
|
||||
public string? Feature { get; set; }
|
||||
public string? Action { get; set; }
|
||||
public string? Step { get; set; }
|
||||
public string? Entity { get; set; }
|
||||
public string? EntityId { get; set; }
|
||||
public string? DataKey { get; set; }
|
||||
public string? BuildVersion { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public string? Stack { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class CommonCodeController(CommonCodeService commonCodeService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAllActive()
|
||||
{
|
||||
try
|
||||
{
|
||||
var codes = await commonCodeService.GetAllActiveAsync();
|
||||
return Ok(codes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "공통코드 조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("group/{group}")]
|
||||
public async Task<IActionResult> GetByGroup(string group)
|
||||
{
|
||||
try
|
||||
{
|
||||
var codes = await commonCodeService.GetByGroupAsync(group);
|
||||
return Ok(codes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "그룹별 공통코드 조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("groups")]
|
||||
public async Task<IActionResult> 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<IActionResult> Get(string group, string value)
|
||||
{
|
||||
var code = await commonCodeService.GetAsync(group, value);
|
||||
return code is null ? NotFound() : Ok(code);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Upsert([FromBody] CommonCode code)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(code.CodeGroup) || string.IsNullOrWhiteSpace(code.CodeValue) || string.IsNullOrWhiteSpace(code.CodeName))
|
||||
return BadRequest(new { error = "코드 그룹, 값, 이름은 필수입니다." });
|
||||
if (code.CodeGroup.Any(char.IsWhiteSpace))
|
||||
return BadRequest(new { error = "code_group에는 공백을 사용할 수 없습니다." });
|
||||
if (code.CodeValue.Contains(' '))
|
||||
return BadRequest(new { error = "code_value에는 공백을 사용할 수 없습니다." });
|
||||
|
||||
await commonCodeService.UpsertAsync(code);
|
||||
return Ok(code);
|
||||
}
|
||||
|
||||
[HttpDelete("{group}/{value}")]
|
||||
public async Task<IActionResult> Delete(string group, string value)
|
||||
{
|
||||
await commonCodeService.DeleteAsync(group, value);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class CompanyController(CompanyService companyService) : ControllerBase
|
||||
{
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var company = await companyService.GetByIdAsync(id);
|
||||
if (company == null)
|
||||
return NotFound(new ProblemDetails { Title = "회사를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(company);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("code/{code}")]
|
||||
public async Task<IActionResult> GetByCode(string code)
|
||||
{
|
||||
try
|
||||
{
|
||||
var company = await companyService.GetByCodeAsync(code);
|
||||
if (company == null)
|
||||
return NotFound(new ProblemDetails { Title = "회사를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(company);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (companies, total) = await companyService.GetPagedAsync(page, pageSize);
|
||||
return Ok(new { data = companies, total, page, pageSize });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 목록 조회 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateCompanyRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await companyService.CreateAsync(
|
||||
request.CompanyCode, request.CompanyName, request.ContactPerson,
|
||||
request.Phone, request.Email, request.Memo);
|
||||
return CreatedAtAction(nameof(GetById), new { id }, new { message = "회사가 등록되었습니다.", id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 등록 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] UpdateCompanyRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await companyService.UpdateAsync(id, request.CompanyCode, request.CompanyName,
|
||||
request.ContactPerson, request.Phone, request.Email, request.Memo, request.IsActive);
|
||||
return Ok(new { message = "회사가 수정되었습니다." });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 수정 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await companyService.DeleteAsync(id);
|
||||
return Ok(new { message = "회사가 삭제되었습니다." });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new ProblemDetails { Title = "회사 삭제 실패", Detail = ex.Message, Status = StatusCodes.Status500InternalServerError });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateCompanyRequest(string CompanyCode, string CompanyName, string? ContactPerson, string? Phone, string? Email, string? Memo);
|
||||
public record UpdateCompanyRequest(string CompanyCode, string CompanyName, string? ContactPerson, string? Phone, string? Email, string? Memo, bool IsActive);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class ConsultingActivityController(ConsultingActivityService service) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateConsultingActivityRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await service.CreateAsync(request.ClientId, request.ActivityType, request.ActivityDate,
|
||||
request.Description, request.ConsultantId, request.NextFollowupDate);
|
||||
return CreatedAtAction(nameof(GetById), new { id }, new { id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var activities = await service.GetAllAsync();
|
||||
return Ok(activities);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = await service.GetByClientIdAsync(id);
|
||||
if (activity == null)
|
||||
return NotFound(new { error = "상담 활동을 찾을 수 없습니다." });
|
||||
return Ok(activity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId:int}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activities = await service.GetByClientIdAsync(clientId);
|
||||
return Ok(new { data = activities });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("pending-followups")]
|
||||
public async Task<IActionResult> GetPendingFollowups()
|
||||
{
|
||||
try
|
||||
{
|
||||
var activities = await service.GetPendingFollowupsAsync();
|
||||
return Ok(new { data = activities });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("consultant/{consultantId:int}")]
|
||||
public async Task<IActionResult> GetByConsultant(int consultantId, [FromQuery] int daysBack = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fromDate = DateTime.Today.AddDays(-daysBack);
|
||||
var activities = await service.GetConsultantActivityAsync(consultantId, fromDate);
|
||||
return Ok(new { data = activities, daysBack });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] UpdateConsultingActivityRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await service.UpdateAsync(id, request.Outcome, request.NextFollowupDate);
|
||||
return Ok(new { message = "상담 활동이 수정되었습니다." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "수정 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateConsultingActivityRequest(
|
||||
int ClientId, string ActivityType, DateTime ActivityDate, string Description,
|
||||
int? ConsultantId = null, DateTime? NextFollowupDate = null);
|
||||
|
||||
public record UpdateConsultingActivityRequest(
|
||||
string? Outcome = null, DateTime? NextFollowupDate = null);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class ContractController(ContractService service) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateContractRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await service.CreateAsync(request.ClientId, request.ContractNumber, request.ServiceType,
|
||||
request.StartDate, request.MonthlyFee, request.TotalAmount);
|
||||
return CreatedAtAction(nameof(GetById), new { id }, new { id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var contracts = await service.GetAllAsync();
|
||||
return Ok(contracts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var contract = await service.GetByIdAsync(id);
|
||||
if (contract == null)
|
||||
return NotFound(new { error = "계약을 찾을 수 없습니다." });
|
||||
return Ok(contract);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId:int}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var contracts = await service.GetByClientIdAsync(clientId);
|
||||
return Ok(new { data = contracts });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("active")]
|
||||
public async Task<IActionResult> GetActiveContracts()
|
||||
{
|
||||
try
|
||||
{
|
||||
var contracts = await service.GetActiveContractsAsync();
|
||||
return Ok(new { data = contracts });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("expiring")]
|
||||
public async Task<IActionResult> GetExpiringContracts([FromQuery] int daysAhead = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var contracts = await service.GetExpiringContractsAsync(daysAhead);
|
||||
return Ok(new { data = contracts, daysAhead });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("mrr")]
|
||||
public async Task<IActionResult> GetMonthlyRecurringRevenue()
|
||||
{
|
||||
try
|
||||
{
|
||||
var mrr = await service.GetMonthlyRecurringRevenueAsync();
|
||||
return Ok(new { mrr });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateContractRequest(
|
||||
int ClientId, string ContractNumber, string ServiceType, DateTime StartDate,
|
||||
decimal? MonthlyFee = null, decimal? TotalAmount = null);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FaqController : ControllerBase
|
||||
{
|
||||
private readonly FaqService _faqService;
|
||||
|
||||
public FaqController(FaqService faqService)
|
||||
{
|
||||
_faqService = faqService;
|
||||
}
|
||||
|
||||
[HttpGet("active")]
|
||||
public async Task<IActionResult> GetActive()
|
||||
{
|
||||
var faqs = await _faqService.GetActiveAsync();
|
||||
return Ok(new { data = faqs });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
var faqs = await _faqService.GetAllAsync();
|
||||
return Ok(new { data = faqs });
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var faq = await _faqService.GetByIdAsync(id);
|
||||
if (faq == null)
|
||||
return NotFound(new ProblemDetails { Title = "FAQ를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(faq);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Create([FromBody] Faq faq)
|
||||
{
|
||||
try
|
||||
{
|
||||
var faqId = await _faqService.CreateAsync(faq);
|
||||
var result = await _faqService.GetByIdAsync(faqId);
|
||||
return CreatedAtAction(nameof(GetById), new { id = faqId }, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] Faq faq)
|
||||
{
|
||||
faq.Id = id;
|
||||
try
|
||||
{
|
||||
await _faqService.UpdateAsync(faq);
|
||||
var result = await _faqService.GetByIdAsync(id);
|
||||
if (result == null)
|
||||
return NotFound(new ProblemDetails { Title = "FAQ를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _faqService.DeleteAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
using TaxBaik.Application.DTOs;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class InquiryController : ControllerBase
|
||||
{
|
||||
private readonly InquiryService _inquiryService;
|
||||
private readonly ClientService _clientService;
|
||||
|
||||
public InquiryController(InquiryService inquiryService, ClientService clientService)
|
||||
{
|
||||
_inquiryService = inquiryService;
|
||||
_clientService = clientService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Submit([FromBody] SubmitInquiryDto request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Phone))
|
||||
return BadRequest(new ProblemDetails { Title = "이름과 전화번호를 입력하세요.", Status = StatusCodes.Status400BadRequest });
|
||||
|
||||
try
|
||||
{
|
||||
await _inquiryService.SubmitAsync(
|
||||
request.Name,
|
||||
request.Phone,
|
||||
request.ServiceType,
|
||||
request.Message,
|
||||
request.Email,
|
||||
HttpContext.Connection.RemoteIpAddress?.ToString(),
|
||||
request.SuppressNotification);
|
||||
return Ok(new { message = "상담 신청이 접수되었습니다." });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var (inquiries, total) = await _inquiryService.GetPagedAsync(page, pageSize);
|
||||
return Ok(new { data = inquiries, total, page, pageSize });
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var inquiry = await _inquiryService.GetByIdAsync(id);
|
||||
if (inquiry == null)
|
||||
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(inquiry);
|
||||
}
|
||||
|
||||
[HttpPut("{id}/status")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdateStatus(int id, [FromBody] UpdateStatusRequest request)
|
||||
{
|
||||
var inquiry = await _inquiryService.GetByIdAsync(id);
|
||||
if (inquiry == null)
|
||||
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
try
|
||||
{
|
||||
var changedBy = User.FindFirstValue(ClaimTypes.Name) ?? User.Identity?.Name;
|
||||
await _inquiryService.UpdateStatusAsync(id, request.Status, changedBy);
|
||||
return Ok(new { message = "상태가 변경되었습니다." });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}/memo")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> UpdateAdminMemo(int id, [FromBody] UpdateAdminMemoRequest request)
|
||||
{
|
||||
var inquiry = await _inquiryService.GetByIdAsync(id);
|
||||
if (inquiry == null)
|
||||
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
try
|
||||
{
|
||||
await _inquiryService.UpdateAdminMemoAsync(id, request.AdminMemo);
|
||||
return Ok(new { message = "메모가 저장되었습니다." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] UpdateInquiryDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _inquiryService.UpdateAsync(id, request);
|
||||
if (result == null)
|
||||
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/convert-to-client")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> ConvertToClient(int id, [FromBody] ConvertToClientRequest request)
|
||||
{
|
||||
var inquiry = await _inquiryService.GetByIdAsync(id);
|
||||
if (inquiry == null)
|
||||
return NotFound(new ProblemDetails { Title = "문의를 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
if (inquiry.ClientId != null)
|
||||
return BadRequest(new ProblemDetails { Title = "이미 고객 카드가 연결되어 있습니다.", Status = StatusCodes.Status400BadRequest });
|
||||
|
||||
try
|
||||
{
|
||||
var clientId = await _clientService.CreateFromInquiryAsync(
|
||||
request.Name ?? inquiry.Name,
|
||||
request.Phone ?? inquiry.Phone,
|
||||
request.ServiceType ?? inquiry.ServiceType);
|
||||
|
||||
await _inquiryService.LinkClientAsync(inquiry.Id, clientId);
|
||||
await _inquiryService.UpdateStatusAsync(inquiry.Id, "consulting", User.FindFirstValue(ClaimTypes.Name) ?? "system");
|
||||
|
||||
return Ok(new { clientId, message = "고객 카드가 생성되었습니다." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateStatusRequest
|
||||
{
|
||||
public string Status { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class UpdateAdminMemoRequest
|
||||
{
|
||||
public string? AdminMemo { get; set; }
|
||||
}
|
||||
|
||||
public class ConvertToClientRequest
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? ServiceType { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class RevenueTrackingController(RevenueTrackingService service) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateRevenueTrackingRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await service.CreateAsync(request.ClientId, request.InvoiceNumber, request.InvoiceDate,
|
||||
request.Amount, request.ServiceType, request.DueDate);
|
||||
return CreatedAtAction(nameof(GetById), new { id }, new { id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var revenues = await service.GetAllAsync();
|
||||
return Ok(revenues);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
// GetByIdAsync가 없으면 GetByClientIdAsync를 사용하거나 별도 구현 필요
|
||||
// 임시로 구현 - 실제로는 repository에 GetByIdAsync 추가 필요
|
||||
return Ok(new { message = "조회됨" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId:int}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var revenues = await service.GetByClientIdAsync(clientId);
|
||||
return Ok(new { data = revenues });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("pending")]
|
||||
public async Task<IActionResult> GetPendingPayments()
|
||||
{
|
||||
try
|
||||
{
|
||||
var revenues = await service.GetPendingPaymentsAsync();
|
||||
return Ok(new { data = revenues });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("monthly")]
|
||||
public async Task<IActionResult> GetMonthlyRevenue([FromQuery] int year, [FromQuery] int month)
|
||||
{
|
||||
try
|
||||
{
|
||||
var monthDate = new DateTime(year, month, 1);
|
||||
var revenues = await service.GetMonthlyRevenueAsync(monthDate);
|
||||
return Ok(new { data = revenues, year, month });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("total")]
|
||||
public async Task<IActionResult> GetTotalRevenue([FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var total = await service.GetTotalRevenueAsync(startDate, endDate);
|
||||
return Ok(new { total, startDate, endDate });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}/paid")]
|
||||
public async Task<IActionResult> MarkPaid(int id, [FromBody] MarkPaidRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await service.MarkPaidAsync(id, request.PaymentDate);
|
||||
return Ok(new { message = "결제가 완료됨으로 표시되었습니다." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "수정 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateRevenueTrackingRequest(
|
||||
int ClientId, string InvoiceNumber, DateTime InvoiceDate, decimal Amount,
|
||||
string? ServiceType = null, DateTime? DueDate = null);
|
||||
|
||||
public record MarkPaidRequest(DateTime PaymentDate);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class SiteSettingsController : ControllerBase
|
||||
{
|
||||
private readonly SiteSettingService _siteSettingService;
|
||||
|
||||
public SiteSettingsController(SiteSettingService siteSettingService)
|
||||
{
|
||||
_siteSettingService = siteSettingService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
var settings = await _siteSettingService.GetAllAsync();
|
||||
return Ok(settings);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> Save([FromBody] SaveSiteSettingsRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
return BadRequest(new { message = "요청 본문이 비어 있습니다." });
|
||||
|
||||
await _siteSettingService.SaveAsync(request.Phone, request.Email, request.KakaoUrl, request.InstagramUrl);
|
||||
return Ok(new { message = "사이트 설정이 저장되었습니다." });
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveSiteSettingsRequest
|
||||
{
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string KakaoUrl { get; set; } = string.Empty;
|
||||
public string InstagramUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
using TaxBaik.Domain.Entities;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class TaxFilingController : ControllerBase
|
||||
{
|
||||
private readonly TaxFilingService _taxFilingService;
|
||||
|
||||
public TaxFilingController(TaxFilingService taxFilingService)
|
||||
{
|
||||
_taxFilingService = taxFilingService;
|
||||
}
|
||||
|
||||
[HttpGet("upcoming")]
|
||||
public async Task<IActionResult> GetUpcoming([FromQuery] int daysAhead = 30)
|
||||
{
|
||||
var filings = await _taxFilingService.GetUpcomingAsync(daysAhead);
|
||||
return Ok(new { data = filings });
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
var filings = await _taxFilingService.GetByClientIdAsync(clientId);
|
||||
return Ok(new { data = filings });
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var filing = await _taxFilingService.GetByIdAsync(id);
|
||||
if (filing == null)
|
||||
return NotFound(new ProblemDetails { Title = "신고 일정을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(filing);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] TaxFiling filing)
|
||||
{
|
||||
try
|
||||
{
|
||||
var filingId = await _taxFilingService.CreateAsync(filing);
|
||||
var result = await _taxFilingService.GetByIdAsync(filingId);
|
||||
return CreatedAtAction(nameof(GetById), new { id = filingId }, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] TaxFiling filing)
|
||||
{
|
||||
filing.Id = id;
|
||||
try
|
||||
{
|
||||
await _taxFilingService.UpdateAsync(filing);
|
||||
var result = await _taxFilingService.GetByIdAsync(id);
|
||||
if (result == null)
|
||||
return NotFound(new ProblemDetails { Title = "신고 일정을 찾을 수 없습니다.", Status = StatusCodes.Status404NotFound });
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new ProblemDetails { Title = ex.Message, Status = StatusCodes.Status400BadRequest });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _taxFilingService.DeleteAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class TaxFilingScheduleController(TaxFilingScheduleService service) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateTaxFilingScheduleRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await service.CreateAsync(request.ClientId, request.FilingType, request.DueDate,
|
||||
request.FilingYear, request.AssignedTo);
|
||||
return CreatedAtAction(nameof(GetById), new { id }, new { id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var schedules = await service.GetAllAsync();
|
||||
return Ok(schedules);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var schedule = await service.GetByIdAsync(id);
|
||||
if (schedule == null)
|
||||
return NotFound(new { error = "신고 일정을 찾을 수 없습니다." });
|
||||
return Ok(schedule);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId:int}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var schedules = await service.GetByClientIdAsync(clientId);
|
||||
return Ok(new { data = schedules });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("upcoming")]
|
||||
public async Task<IActionResult> GetUpcomingDues([FromQuery] int daysAhead = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var schedules = await service.GetUpcomingDuesAsync(daysAhead);
|
||||
return Ok(new { data = schedules, daysAhead });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("pending-count")]
|
||||
public async Task<IActionResult> GetPendingCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await service.GetPendingCountAsync();
|
||||
return Ok(new { count });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}/complete")]
|
||||
public async Task<IActionResult> MarkCompleted(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await service.MarkCompletedAsync(id);
|
||||
return Ok(new { message = "신고 일정이 완료되었습니다." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "수정 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateTaxFilingScheduleRequest(
|
||||
int ClientId, string FilingType, DateTime DueDate, int FilingYear,
|
||||
int? AssignedTo = null);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxBaik.Application.Services;
|
||||
|
||||
namespace TaxBaik.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class TaxProfileController(TaxProfileService taxProfileService) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateTaxProfileRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var id = await taxProfileService.CreateAsync(request.ClientId, request.BusinessType,
|
||||
request.BusinessRegistration, request.AccountingMethod, request.EstablishmentDate);
|
||||
return CreatedAtAction(nameof(GetByClientId), new { clientId = request.ClientId }, new { id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profiles = await taxProfileService.GetAllAsync();
|
||||
return Ok(profiles);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("client/{clientId:int}")]
|
||||
public async Task<IActionResult> GetByClientId(int clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var profile = await taxProfileService.GetByClientIdAsync(clientId);
|
||||
if (profile == null)
|
||||
return NotFound(new { error = "세무 프로필을 찾을 수 없습니다." });
|
||||
return Ok(profile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("high-risk")]
|
||||
public async Task<IActionResult> GetHighRiskProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profiles = await taxProfileService.GetHighRiskProfilesAsync();
|
||||
return Ok(new { data = profiles });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("upcoming-filings")]
|
||||
public async Task<IActionResult> GetUpcomingFiliings([FromQuery] int daysAhead = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var profiles = await taxProfileService.GetUpcomingFilingDuesAsync(daysAhead);
|
||||
return Ok(new { data = profiles, daysAhead });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "조회 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] UpdateTaxProfileRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await taxProfileService.UpdateAsync(id, request.BusinessType, request.AccountingMethod,
|
||||
request.NextFilingDueDate, request.TaxRiskLevel);
|
||||
return Ok(new { message = "세무 프로필이 수정되었습니다." });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { error = "수정 실패", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateTaxProfileRequest(
|
||||
int ClientId, string BusinessType, string? BusinessRegistration = null,
|
||||
string? AccountingMethod = null, DateTime? EstablishmentDate = null);
|
||||
|
||||
public record UpdateTaxProfileRequest(
|
||||
string? BusinessType = null, string? AccountingMethod = null,
|
||||
DateTime? NextFilingDueDate = null, string TaxRiskLevel = "normal");
|
||||
}
|
||||
Reference in New Issue
Block a user