| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- using Domain.Entities.Store.ValueObject;
- using SharedKernel.Extensions;
- using MediatR;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Mvc.RazorPages;
- using System.ComponentModel;
- using System.ComponentModel.DataAnnotations;
- namespace Admin.Pages.Store.Product;
- /// <summary>
- /// 상품 판매 정책 설정 (fdmbox tb_box_limit_config 패턴).
- /// /Store/Product/LimitConfig/{id} — Product 와 1:1, row 없으면 기본값으로 폼 초기화.
- /// Custom 기간은 절대 datetime 범위 (PeriodStartAt ~ PeriodEndAt).
- /// </summary>
- public class LimitConfigModel(IMediator mediator) : PageModel
- {
- public int ProductID { get; private set; }
- public string? ProductName { get; private set; }
- public string? ProductThumbnail { get; private set; }
- public string? QueryString { get; set; }
- [BindProperty]
- public InputModel Input { get; set; } = new();
- public sealed class InputModel
- {
- [DisplayName("PK")]
- [Required]
- public int ProductID { get; set; }
- [DisplayName("수량 제한")]
- public bool UseQuantityLimit { get; set; }
- [DisplayName("최대 구매 수량")]
- [Range(0, 9999, ErrorMessage = "{0}은(는) {1}~{2} 사이여야 합니다.")]
- public int MaxQuantity { get; set; }
- [DisplayName("기간 제한")]
- public bool UsePeriodLimit { get; set; }
- [DisplayName("기간 유형")]
- public LimitPeriodType PeriodType { get; set; } = LimitPeriodType.None;
- [DisplayName("시작 시각")]
- [DataType(DataType.DateTime)]
- public DateTime? PeriodStartAt { get; set; }
- [DisplayName("종료 시각")]
- [DataType(DataType.DateTime)]
- public DateTime? PeriodEndAt { get; set; }
- }
- public async Task<IActionResult> OnGetAsync(int id, CancellationToken ct)
- {
- var result = await mediator.Send(new GetProductLimitConfig.Query(id), ct);
- if (result.IsFailure)
- {
- TempData["ErrorMessages"] = result.Error.Description;
- return RedirectToPage("/Store/Product/Index");
- }
- var data = result.Value;
- ProductID = data.ProductID;
- ProductName = data.ProductName;
- ProductThumbnail = data.ProductThumbnail;
- QueryString = Request.QueryString.ToString();
- // DB는 UTC 저장 규약. datetime-local input은 사용자 로컬 시각을 기대하므로 표시 시점에 변환.
- Input = new InputModel
- {
- ProductID = data.ProductID,
- UseQuantityLimit = data.UseQuantityLimit,
- MaxQuantity = data.MaxQuantity,
- UsePeriodLimit = data.UsePeriodLimit,
- PeriodType = data.PeriodType,
- PeriodStartAt = data.PeriodStartAt.HasValue
- ? DateTime.SpecifyKind(data.PeriodStartAt.Value, DateTimeKind.Utc).ToLocalTime()
- : null,
- PeriodEndAt = data.PeriodEndAt.HasValue
- ? DateTime.SpecifyKind(data.PeriodEndAt.Value, DateTimeKind.Utc).ToLocalTime()
- : null
- };
- return Page();
- }
- public async Task<IActionResult> OnPostAsync(CancellationToken ct)
- {
- if (!ModelState.IsValid)
- {
- TempData["ErrorMessages"] = ModelState.GetErrorMessages();
- return Redirect($"/Store/Product/LimitConfig/{Input.ProductID}");
- }
- // UI 안전망: PeriodType 이 None 이외이면 UsePeriodLimit 강제 ON
- // (사용자가 라디오만 바꾸고 체크박스 토글을 잊은 경우 처리).
- // 반대로 UsePeriodLimit=false 면 PeriodType 도 None 으로 정규화.
- if (Input.PeriodType != LimitPeriodType.None)
- {
- Input.UsePeriodLimit = true;
- }
- else if (!Input.UsePeriodLimit)
- {
- Input.PeriodType = LimitPeriodType.None;
- }
- // datetime-local input은 사용자 로컬 시각(Kind=Unspecified). DB는 UTC 저장 규약 → 명시 변환.
- var periodStartUtc = Input.PeriodStartAt.HasValue
- ? DateTime.SpecifyKind(Input.PeriodStartAt.Value, DateTimeKind.Local).ToUniversalTime()
- : (DateTime?)null;
- var periodEndUtc = Input.PeriodEndAt.HasValue
- ? DateTime.SpecifyKind(Input.PeriodEndAt.Value, DateTimeKind.Local).ToUniversalTime()
- : (DateTime?)null;
- var result = await mediator.Send(new UpdateProductLimitConfig.Command(
- Input.ProductID,
- Input.UseQuantityLimit,
- Input.MaxQuantity,
- Input.UsePeriodLimit,
- Input.PeriodType,
- periodStartUtc,
- periodEndUtc
- ), ct);
- if (result.IsFailure)
- {
- TempData["ErrorMessages"] = result.Error.Description;
- }
- else
- {
- TempData["SuccessMessage"] = "판매 정책이 저장되었습니다.";
- }
- return Redirect($"/Store/Product/LimitConfig/{Input.ProductID}");
- }
- }
|