Detail.cshtml 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. @page "{id:int}"
  2. @model Admin.Pages.Store.Shipment.DetailModel
  3. @using Domain.Entities.Store.ValueObject
  4. @{
  5. ViewData["Title"] = "배송 상세";
  6. }
  7. <div class="container-fluid">
  8. <h3>@ViewData["Title"]</h3>
  9. <hr />
  10. <partial name="_StatusMessage" />
  11. @if (Model.Data is null)
  12. {
  13. <div class="alert alert-danger">배송 정보를 불러올 수 없습니다.</div>
  14. }
  15. else
  16. {
  17. var d = Model.Data;
  18. <div class="card mb-3">
  19. <div class="card-header"><strong>배송 기본 정보</strong></div>
  20. <div class="card-body">
  21. <dl class="row mb-0">
  22. <dt class="col-sm-2">주문번호</dt><dd class="col-sm-4">
  23. <a asp-page="/Store/Order/Detail" asp-route-id="@d.OrderID"><code>@d.OrderNumber</code></a>
  24. </dd>
  25. <dt class="col-sm-2">구매자</dt><dd class="col-sm-4">@d.BuyerEmail</dd>
  26. <dt class="col-sm-2">배송 상태</dt><dd class="col-sm-4">@Admin.Pages.Store.Shipment.IndexModel.StatusToText(d.Status)</dd>
  27. <dt class="col-sm-2">생성일</dt><dd class="col-sm-4">@d.CreatedAt.ToString("yyyy-MM-dd HH:mm")</dd>
  28. <dt class="col-sm-2">택배사</dt><dd class="col-sm-4">@(d.Carrier ?? "-")</dd>
  29. <dt class="col-sm-2">송장번호</dt><dd class="col-sm-4">@(d.TrackingNumber ?? "-")</dd>
  30. <dt class="col-sm-2">출고일</dt><dd class="col-sm-4">@(d.ShippedAt?.ToString("yyyy-MM-dd HH:mm") ?? "-")</dd>
  31. <dt class="col-sm-2">배송완료</dt><dd class="col-sm-4">@(d.DeliveredAt?.ToString("yyyy-MM-dd HH:mm") ?? "-")</dd>
  32. <dt class="col-sm-2">배송비</dt><dd class="col-sm-4">@d.ShippingFee.ToString("N0") P</dd>
  33. <dt class="col-sm-2">관리자 메모</dt><dd class="col-sm-10">@(string.IsNullOrWhiteSpace(d.AdminMemo) ? "-" : d.AdminMemo)</dd>
  34. </dl>
  35. </div>
  36. </div>
  37. <!-- 수령 주소 (권한 필요) -->
  38. <div class="card mb-3">
  39. <div class="card-header d-flex justify-content-between align-items-center">
  40. <strong>수령 주소</strong>
  41. @if (!Model.WithAddress)
  42. {
  43. <a class="btn btn-sm btn-outline-warning" asp-route-WithAddress="true" asp-route-id="@d.ID">
  44. <i class="bi bi-eye"></i> 주소 보기 (복호화)
  45. </a>
  46. }
  47. else
  48. {
  49. <a class="btn btn-sm btn-outline-secondary" asp-route-id="@d.ID">숨기기</a>
  50. }
  51. </div>
  52. <div class="card-body">
  53. @if (Model.WithAddress)
  54. {
  55. <dl class="row mb-0">
  56. <dt class="col-sm-2">수령인</dt><dd class="col-sm-10">@d.RecipientName</dd>
  57. <dt class="col-sm-2">연락처</dt><dd class="col-sm-10">@d.Phone</dd>
  58. <dt class="col-sm-2">우편번호</dt><dd class="col-sm-10">@d.ZipCode</dd>
  59. <dt class="col-sm-2">주소</dt><dd class="col-sm-10">@d.Address1</dd>
  60. <dt class="col-sm-2">상세 주소</dt><dd class="col-sm-10">@d.Address2</dd>
  61. </dl>
  62. <p class="small text-muted mb-0 mt-2">
  63. <i class="bi bi-info-circle"></i> 주소 조회는 감사 로그에 기록됩니다 (S6 적용 예정).
  64. </p>
  65. }
  66. else
  67. {
  68. <p class="text-muted mb-0">개인정보 보호를 위해 가려져 있습니다. "주소 보기" 클릭 시 AES-GCM 복호화하여 표시합니다.</p>
  69. }
  70. </div>
  71. </div>
  72. <!-- 배송비 / 관리자 메모 -->
  73. <div class="card mb-3">
  74. <div class="card-header"><strong>배송비 · 관리자 메모</strong></div>
  75. <div class="card-body">
  76. <form method="post" asp-page-handler="UpdateBilling" asp-route-id="@d.ID" accept-charset="utf-8">
  77. @Html.AntiForgeryToken()
  78. <div class="row mb-2">
  79. <label asp-for="Billing.ShippingFee" class="col-sm-2 col-form-label">배송비</label>
  80. <div class="col-sm-10">
  81. <div class="row">
  82. <div class="col col-md-auto">
  83. <div class="input-group">
  84. <input asp-for="Billing.ShippingFee" class="form-control" type="number" min="0" required />
  85. <span class="input-group-text">원</span>
  86. </div>
  87. </div>
  88. </div>
  89. <span asp-validation-for="Billing.ShippingFee" class="text-danger"></span>
  90. </div>
  91. </div>
  92. <div class="row mb-2">
  93. <label asp-for="Billing.AdminMemo" class="col-sm-2 col-form-label">관리자 메모</label>
  94. <div class="col-sm-10">
  95. <textarea asp-for="Billing.AdminMemo" class="form-control" rows="3" maxlength="500" placeholder="운영용 내부 메모 (최대 500자)"></textarea>
  96. <span asp-validation-for="Billing.AdminMemo" class="text-danger"></span>
  97. </div>
  98. </div>
  99. <div class="text-end">
  100. <button type="submit" class="btn btn-primary">저장</button>
  101. </div>
  102. </form>
  103. </div>
  104. </div>
  105. <!-- 송장 등록 -->
  106. @if (d.Status == ShipmentStatus.Preparing)
  107. {
  108. <div class="card mb-3">
  109. <div class="card-header"><strong>송장 등록</strong></div>
  110. <div class="card-body">
  111. <form method="post" asp-page-handler="RegisterTracking" asp-route-id="@d.ID" accept-charset="utf-8">
  112. @Html.AntiForgeryToken()
  113. <div class="row g-2 align-items-end">
  114. <div class="col-md-3">
  115. <label asp-for="Tracking.Carrier" class="form-label"></label>
  116. <input asp-for="Tracking.Carrier" class="form-control" placeholder="예) CJ대한통운" />
  117. </div>
  118. <div class="col-md-5">
  119. <label asp-for="Tracking.TrackingNumber" class="form-label"></label>
  120. <input asp-for="Tracking.TrackingNumber" class="form-control" />
  121. </div>
  122. <div class="col-md-3">
  123. <button type="submit" class="btn btn-primary w-100">송장 등록 + 출고 준비</button>
  124. </div>
  125. </div>
  126. </form>
  127. </div>
  128. </div>
  129. }
  130. <!-- 상태 전환 버튼 -->
  131. <div class="card mb-3">
  132. <div class="card-header"><strong>상태 변경</strong></div>
  133. <div class="card-body">
  134. <div class="d-flex gap-2">
  135. @if (d.Status is ShipmentStatus.Pickup or ShipmentStatus.Preparing)
  136. {
  137. <form method="post" asp-page-handler="Transit" asp-route-id="@d.ID" class="d-inline">
  138. @Html.AntiForgeryToken()
  139. <button type="submit" class="btn btn-info">배송중 전환</button>
  140. </form>
  141. }
  142. @if (d.Status == ShipmentStatus.InTransit)
  143. {
  144. <form method="post" asp-page-handler="Delivered" asp-route-id="@d.ID" class="d-inline">
  145. @Html.AntiForgeryToken()
  146. <button type="submit" class="btn btn-success">배송완료 처리</button>
  147. </form>
  148. }
  149. @if (d.Status is not ShipmentStatus.Delivered and not ShipmentStatus.Failed)
  150. {
  151. <form method="post" asp-page-handler="Failed" asp-route-id="@d.ID" class="d-inline">
  152. @Html.AntiForgeryToken()
  153. <button type="submit" class="btn btn-outline-danger" onclick="return confirm('배송 실패로 표시하시겠습니까?')">배송 실패</button>
  154. </form>
  155. }
  156. </div>
  157. </div>
  158. </div>
  159. <div class="d-flex justify-content-end gap-2">
  160. <a class="btn btn-secondary" asp-page="/Store/Shipment/Index">목록</a>
  161. </div>
  162. }
  163. </div>