using Domain.Entities.Store.ValueObject;
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.Product;
///
/// 상품 판매 정책 설정 (fdmbox tb_box_limit_config 패턴).
/// /Store/Product/LimitConfig/{id} — Product 와 1:1, row 없으면 기본값으로 폼 초기화.
/// Custom 기간은 절대 datetime 범위 (PeriodStartAt ~ PeriodEndAt).
///
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 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 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}");
}
}