Adds GET and PUT /marketing/loyalty-settings and GET /marketing/loyalty-settings/history (docs/prd-point-coin.md F2, PC-302). The settings are the point value, the exchange rate, transfer limits and the stored expiry settings. PUT merges the body like the outlet settings and is limited to loyalty managers. Every response carries the impact of the change on the balances in circulation: outstanding EnakPoint and EnakCoin, their rupiah value, and the coins exchanged into points, before and after. With ?dry_run=true nothing is saved and the response lists the keys that would change, for the warning shown before saving. Saving records each change in loyalty_setting_changes with who made it; history can be filtered to one outlet. Changing the value leaves what was already written alone. The diff behind saving and previewing is shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
5.1 KiB
Go
118 lines
5.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"io"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"apskel-pos-be/internal/appcontext"
|
|
"apskel-pos-be/internal/constants"
|
|
"apskel-pos-be/internal/contract"
|
|
"apskel-pos-be/internal/logger"
|
|
"apskel-pos-be/internal/service"
|
|
"apskel-pos-be/internal/util"
|
|
)
|
|
|
|
// loyaltySettingsBodyLimit caps a settings body; a real one is well under 2 KB.
|
|
const loyaltySettingsBodyLimit = 64 << 10
|
|
|
|
// LoyaltySettingsHandler serves the loyalty settings (docs/prd-point-coin.md F1, F2).
|
|
type LoyaltySettingsHandler struct {
|
|
loyaltySettingsService service.LoyaltySettingsService
|
|
}
|
|
|
|
func NewLoyaltySettingsHandler(loyaltySettingsService service.LoyaltySettingsService) *LoyaltySettingsHandler {
|
|
return &LoyaltySettingsHandler{loyaltySettingsService: loyaltySettingsService}
|
|
}
|
|
|
|
func (h *LoyaltySettingsHandler) GetOutletSettings(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
outletID, ok := parseUUIDParam(c, "outlet_id", "LoyaltySettingsHandler::GetOutletSettings")
|
|
if !ok {
|
|
return
|
|
}
|
|
response := h.loyaltySettingsService.GetOutletSettings(ctx, appcontext.FromGinContext(ctx), outletID)
|
|
if response.HasErrors() {
|
|
logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::GetOutletSettings -> service call failed")
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::GetOutletSettings")
|
|
}
|
|
|
|
func (h *LoyaltySettingsHandler) UpdateOutletSettings(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
outletID, ok := parseUUIDParam(c, "outlet_id", "LoyaltySettingsHandler::UpdateOutletSettings")
|
|
if !ok {
|
|
return
|
|
}
|
|
body, err := io.ReadAll(io.LimitReader(c.Request.Body, loyaltySettingsBodyLimit))
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "unable to read request body"),
|
|
}), "LoyaltySettingsHandler::UpdateOutletSettings")
|
|
return
|
|
}
|
|
response := h.loyaltySettingsService.UpdateOutletSettings(ctx, appcontext.FromGinContext(ctx), outletID, body)
|
|
if response.HasErrors() {
|
|
logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::UpdateOutletSettings -> service call failed")
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::UpdateOutletSettings")
|
|
}
|
|
|
|
// parseUUIDParam reads a UUID path parameter, answering 400 itself when it is not one.
|
|
func parseUUIDParam(c *gin.Context, name, method string) (uuid.UUID, bool) {
|
|
id, err := uuid.Parse(c.Param(name))
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid "+name),
|
|
}), method)
|
|
return uuid.Nil, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func (h *LoyaltySettingsHandler) GetOrganizationSettings(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
util.HandleResponse(c.Writer, c.Request, h.loyaltySettingsService.GetOrganizationSettings(ctx, appcontext.FromGinContext(ctx)), "LoyaltySettingsHandler::GetOrganizationSettings")
|
|
}
|
|
|
|
// UpdateOrganizationSettings is PUT /marketing/loyalty-settings. With ?dry_run=true it
|
|
// saves nothing and returns what would change and what the balances in circulation
|
|
// would be worth, for the warning the dashboard shows before saving (F2).
|
|
func (h *LoyaltySettingsHandler) UpdateOrganizationSettings(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
body, err := io.ReadAll(io.LimitReader(c.Request.Body, loyaltySettingsBodyLimit))
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "unable to read request body"),
|
|
}), "LoyaltySettingsHandler::UpdateOrganizationSettings")
|
|
return
|
|
}
|
|
response := h.loyaltySettingsService.UpdateOrganizationSettings(ctx, appcontext.FromGinContext(ctx), body, c.Query("dry_run") == "true")
|
|
if response.HasErrors() {
|
|
logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::UpdateOrganizationSettings -> service call failed")
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::UpdateOrganizationSettings")
|
|
}
|
|
|
|
// ListHistory is GET /marketing/loyalty-settings/history, optionally for one outlet
|
|
// (?outlet_id=).
|
|
func (h *LoyaltySettingsHandler) ListHistory(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
var outletID *uuid.UUID
|
|
if raw := c.Query("outlet_id"); raw != "" {
|
|
id, err := uuid.Parse(raw)
|
|
if err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet_id"),
|
|
}), "LoyaltySettingsHandler::ListHistory")
|
|
return
|
|
}
|
|
outletID = &id
|
|
}
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
util.HandleResponse(c.Writer, c.Request, h.loyaltySettingsService.ListHistory(ctx, appcontext.FromGinContext(ctx), outletID, page, limit), "LoyaltySettingsHandler::ListHistory")
|
|
}
|