using SharedKernel.Extensions; using Application.Abstractions.Messaging; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace Admin.Pages.Store.Shipment; public class DetailModel(IMediator mediator) : PageModel { public GetShipment.Response? Data { get; private set; } [BindProperty(SupportsGet = true)] public bool WithAddress { get; set; } [BindProperty] public TrackingInput Tracking { get; set; } = new(); public sealed class TrackingInput { [DisplayName("택배사")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] [StringLength(50)] public string Carrier { get; set; } = null!; [DisplayName("송장번호")] [Required(ErrorMessage = "{0}은(는) 필수입니다.")] [StringLength(50)] public string TrackingNumber { get; set; } = null!; } [BindProperty] public BillingInput Billing { get; set; } = new(); public sealed class BillingInput { [DisplayName("배송비")] [Range(0, int.MaxValue, ErrorMessage = "{0}은(는) 0 이상이어야 합니다.")] public int ShippingFee { get; set; } [DisplayName("관리자 메모")] [StringLength(500, ErrorMessage = "{0}은(는) 최대 {1}자까지 입력 가능합니다.")] public string? AdminMemo { get; set; } } public async Task OnGetAsync(int id, CancellationToken ct) { await LoadAsync(id, ct); } public async Task OnPostRegisterTrackingAsync(int id, CancellationToken ct) { if (!ModelState.IsValid) { TempData["ErrorMessages"] = ModelState.GetErrorMessages(); return RedirectToPage("/Store/Shipment/Detail", new { id }); } var result = await mediator.Send(new UpdateShipmentStatus.Command( id, "RegisterTracking", Tracking.Carrier, Tracking.TrackingNumber ), ct); if (result.IsFailure) { TempData["ErrorMessages"] = result.Error.Description; } else { TempData["SuccessMessage"] = "송장이 등록되었습니다."; } return RedirectToPage("/Store/Shipment/Detail", new { id }); } public async Task OnPostTransitAsync(int id, CancellationToken ct) { var result = await mediator.Send(new UpdateShipmentStatus.Command(id, "InTransit"), ct); TempData[result.IsFailure ? "ErrorMessages" : "SuccessMessage"] = result.IsFailure ? result.Error.Description : "배송중으로 전환되었습니다."; return RedirectToPage("/Store/Shipment/Detail", new { id }); } public async Task OnPostDeliveredAsync(int id, CancellationToken ct) { var result = await mediator.Send(new UpdateShipmentStatus.Command(id, "Delivered"), ct); TempData[result.IsFailure ? "ErrorMessages" : "SuccessMessage"] = result.IsFailure ? result.Error.Description : "배송완료로 전환되었습니다."; return RedirectToPage("/Store/Shipment/Detail", new { id }); } public async Task OnPostFailedAsync(int id, CancellationToken ct) { var result = await mediator.Send(new UpdateShipmentStatus.Command(id, "Failed"), ct); TempData[result.IsFailure ? "ErrorMessages" : "SuccessMessage"] = result.IsFailure ? result.Error.Description : "배송실패로 표시되었습니다."; return RedirectToPage("/Store/Shipment/Detail", new { id }); } public async Task OnPostUpdateBillingAsync(int id, CancellationToken ct) { if (!ModelState.IsValid) { TempData["ErrorMessages"] = ModelState.GetErrorMessages(); return RedirectToPage("/Store/Shipment/Detail", new { id }); } var result = await mediator.Send(new UpdateShipmentStatus.Command( id, "UpdateBilling", ShippingFee: Billing.ShippingFee, AdminMemo: Billing.AdminMemo ), ct); TempData[result.IsFailure ? "ErrorMessages" : "SuccessMessage"] = result.IsFailure ? result.Error.Description : "배송비/메모가 저장되었습니다."; return RedirectToPage("/Store/Shipment/Detail", new { id }); } private async Task LoadAsync(int id, CancellationToken ct) { // 1차에는 모든 관리자가 주소 조회 가능 (S5에서는 권한 분리 미적용) // 실제 운영에서는 Member.IsAdmin 또는 별도 "Store.ViewAddress" 권한 검증 필요 var result = await mediator.Send(new GetShipment.Query(id, WithAddress: WithAddress), ct); if (result.IsFailure) { TempData["ErrorMessages"] = result.Error.Description; return; } Data = result.Value; // Billing 폼 기본값을 현재 값으로 채우기 Billing.ShippingFee = Data.ShippingFee; Billing.AdminMemo = Data.AdminMemo; } }