apskel-pos-backend/internal/handler/inventory_handler.go

438 lines
19 KiB
Go
Raw Normal View History

2025-07-18 20:10:29 +07:00
package handler
import (
"strconv"
2025-08-14 00:38:26 +07:00
"time"
2025-07-18 20:10:29 +07:00
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/logger"
2025-08-13 23:36:31 +07:00
"apskel-pos-be/internal/models"
2025-07-18 20:10:29 +07:00
"apskel-pos-be/internal/service"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type InventoryHandler struct {
inventoryService service.InventoryService
inventoryValidator validator.InventoryValidator
}
func NewInventoryHandler(
inventoryService service.InventoryService,
inventoryValidator validator.InventoryValidator,
) *InventoryHandler {
return &InventoryHandler{
inventoryService: inventoryService,
inventoryValidator: inventoryValidator,
}
}
func (h *InventoryHandler) CreateInventory(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
var req contract.CreateInventoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("InventoryHandler::CreateInventory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::CreateInventory")
return
}
validationError, validationErrorCode := h.inventoryValidator.ValidateCreateInventoryRequest(&req)
if validationError != nil {
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::CreateInventory")
return
}
inventoryResponse := h.inventoryService.CreateInventory(ctx, contextInfo, &req)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::CreateInventory -> Failed to create inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::CreateInventory")
}
func (h *InventoryHandler) UpdateInventory(c *gin.Context) {
ctx := c.Request.Context()
inventoryIDStr := c.Param("id")
inventoryID, err := uuid.Parse(inventoryIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::UpdateInventory -> Invalid inventory ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid inventory ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::UpdateInventory")
return
}
var req contract.UpdateInventoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::UpdateInventory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::UpdateInventory")
return
}
validationError, validationErrorCode := h.inventoryValidator.ValidateUpdateInventoryRequest(&req)
if validationError != nil {
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::UpdateInventory")
return
}
inventoryResponse := h.inventoryService.UpdateInventory(ctx, inventoryID, &req)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::UpdateInventory -> Failed to update inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::UpdateInventory")
}
func (h *InventoryHandler) DeleteInventory(c *gin.Context) {
ctx := c.Request.Context()
inventoryIDStr := c.Param("id")
inventoryID, err := uuid.Parse(inventoryIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::DeleteInventory -> Invalid inventory ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid inventory ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::DeleteInventory")
return
}
inventoryResponse := h.inventoryService.DeleteInventory(ctx, inventoryID)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::DeleteInventory -> Failed to delete inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::DeleteInventory")
}
func (h *InventoryHandler) GetInventory(c *gin.Context) {
ctx := c.Request.Context()
inventoryIDStr := c.Param("id")
inventoryID, err := uuid.Parse(inventoryIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventory -> Invalid inventory ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid inventory ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventory")
return
}
inventoryResponse := h.inventoryService.GetInventoryByID(ctx, inventoryID)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::GetInventory -> Failed to get inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::GetInventory")
}
func (h *InventoryHandler) ListInventory(c *gin.Context) {
ctx := c.Request.Context()
2025-08-13 20:57:57 +07:00
contextInfo := appcontext.FromGinContext(ctx)
2025-07-18 20:10:29 +07:00
req := &contract.ListInventoryRequest{
2025-08-13 20:57:57 +07:00
Page: 1,
Limit: 10,
OutletID: &contextInfo.OutletID,
2025-07-18 20:10:29 +07:00
}
if pageStr := c.Query("page"); pageStr != "" {
if page, err := strconv.Atoi(pageStr); err == nil {
req.Page = page
}
}
if limitStr := c.Query("limit"); limitStr != "" {
if limit, err := strconv.Atoi(limitStr); err == nil {
req.Limit = limit
}
}
if search := c.Query("search"); search != "" {
req.Search = search
}
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
if outletID, err := uuid.Parse(outletIDStr); err == nil {
req.OutletID = &outletID
}
}
if productIDStr := c.Query("product_id"); productIDStr != "" {
if productID, err := uuid.Parse(productIDStr); err == nil {
req.ProductID = &productID
}
}
if categoryIDStr := c.Query("category_id"); categoryIDStr != "" {
if categoryID, err := uuid.Parse(categoryIDStr); err == nil {
req.CategoryID = &categoryID
}
}
if lowStockStr := c.Query("low_stock_only"); lowStockStr != "" {
if lowStock, err := strconv.ParseBool(lowStockStr); err == nil {
req.LowStockOnly = &lowStock
}
}
if zeroStockStr := c.Query("zero_stock_only"); zeroStockStr != "" {
if zeroStock, err := strconv.ParseBool(zeroStockStr); err == nil {
req.ZeroStockOnly = &zeroStock
}
}
validationError, validationErrorCode := h.inventoryValidator.ValidateListInventoryRequest(req)
if validationError != nil {
logger.FromContext(ctx).WithError(validationError).Error("InventoryHandler::ListInventory -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::ListInventory")
return
}
inventoryResponse := h.inventoryService.ListInventory(ctx, req)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::ListInventory -> Failed to list inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::ListInventory")
}
func (h *InventoryHandler) AdjustInventory(c *gin.Context) {
ctx := c.Request.Context()
var req contract.AdjustInventoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::AdjustInventory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::AdjustInventory")
return
}
validationError, validationErrorCode := h.inventoryValidator.ValidateAdjustInventoryRequest(&req)
if validationError != nil {
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::AdjustInventory")
return
}
inventoryResponse := h.inventoryService.AdjustInventory(ctx, &req)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::AdjustInventory -> Failed to adjust inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::AdjustInventory")
}
2025-08-14 01:35:19 +07:00
func (h *InventoryHandler) RestockInventory(c *gin.Context) {
ctx := c.Request.Context()
var req contract.RestockInventoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("InventoryHandler::RestockInventory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::RestockInventory")
return
}
// TODO: Add validation for restock request
// validationError, validationErrorCode := h.inventoryValidator.ValidateRestockInventoryRequest(&req)
// if validationError != nil {
// validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
// util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::RestockInventory")
// return
// }
inventoryResponse := h.inventoryService.RestockInventory(ctx, &req)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::RestockInventory -> Failed to restock inventory from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::RestockInventory")
}
2025-07-18 20:10:29 +07:00
func (h *InventoryHandler) GetLowStockItems(c *gin.Context) {
ctx := c.Request.Context()
outletIDStr := c.Param("outlet_id")
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetLowStockItems -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetLowStockItems")
return
}
inventoryResponse := h.inventoryService.GetLowStockItems(ctx, outletID)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::GetLowStockItems -> Failed to get low stock items from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::GetLowStockItems")
}
func (h *InventoryHandler) GetZeroStockItems(c *gin.Context) {
ctx := c.Request.Context()
outletIDStr := c.Param("outlet_id")
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetZeroStockItems -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetZeroStockItems")
return
}
inventoryResponse := h.inventoryService.GetZeroStockItems(ctx, outletID)
if inventoryResponse.HasErrors() {
errorResp := inventoryResponse.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("InventoryHandler::GetZeroStockItems -> Failed to get zero stock items from service")
}
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::GetZeroStockItems")
}
2025-08-13 23:36:31 +07:00
func (h *InventoryHandler) GetInventoryReportSummary(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
outletIDStr := c.Param("outlet_id")
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportSummary -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportSummary")
return
}
2025-08-14 00:38:26 +07:00
// Parse date range parameters for summary
var dateFrom, dateTo *time.Time
if dateFromStr := c.Query("date_from"); dateFromStr != "" {
if parsedDateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil {
dateFrom = &parsedDateFrom
}
}
if dateToStr := c.Query("date_to"); dateToStr != "" {
if parsedDateTo, err := time.Parse("2006-01-02", dateToStr); err == nil {
dateTo = &parsedDateTo
}
}
summary, err := h.inventoryService.GetInventoryReportSummary(ctx, outletID, contextInfo.OrganizationID, dateFrom, dateTo)
2025-08-13 23:36:31 +07:00
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportSummary -> Failed to get inventory report summary from service")
responseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{responseError}), "InventoryHandler::GetInventoryReportSummary")
return
}
response := contract.BuildSuccessResponse(summary)
util.HandleResponse(c.Writer, c.Request, response, "InventoryHandler::GetInventoryReportSummary")
}
func (h *InventoryHandler) GetInventoryReportDetails(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
filter := &models.InventoryReportFilter{}
2025-08-14 00:38:26 +07:00
if outletIDStr := c.Param("outlet_id"); outletIDStr != "" {
2025-08-13 23:36:31 +07:00
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
return
}
filter.OutletID = &outletID
} else {
logger.FromContext(ctx).Error("InventoryHandler::GetInventoryReportDetails -> Missing outlet_id parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "outlet_id is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
return
}
if categoryIDStr := c.Query("category_id"); categoryIDStr != "" {
categoryID, err := uuid.Parse(categoryIDStr)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Invalid category ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid category ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
return
}
filter.CategoryID = &categoryID
}
// Parse show_low_stock (optional)
if showLowStockStr := c.Query("show_low_stock"); showLowStockStr != "" {
if showLowStock, err := strconv.ParseBool(showLowStockStr); err == nil {
filter.ShowLowStock = &showLowStock
}
}
// Parse show_zero_stock (optional)
if showZeroStockStr := c.Query("show_zero_stock"); showZeroStockStr != "" {
if showZeroStock, err := strconv.ParseBool(showZeroStockStr); err == nil {
filter.ShowZeroStock = &showZeroStock
}
}
// Parse search (optional)
if search := c.Query("search"); search != "" {
filter.Search = &search
}
// Parse limit (optional)
if limitStr := c.Query("limit"); limitStr != "" {
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
filter.Limit = &limit
}
}
// Parse offset (optional)
if offsetStr := c.Query("offset"); offsetStr != "" {
if offset, err := strconv.Atoi(offsetStr); err == nil && offset >= 0 {
filter.Offset = &offset
}
}
2025-08-14 00:38:26 +07:00
dateFromStr := c.Query("date_from")
dateToStr := c.Query("date_to")
if fromTime, toTime, err := util.ParseDateRangeToJakartaTime(dateFromStr, dateToStr); err == nil {
if fromTime != nil {
filter.DateFrom = fromTime
}
if toTime != nil {
filter.DateTo = toTime
}
}
2025-08-13 23:36:31 +07:00
report, err := h.inventoryService.GetInventoryReportDetails(ctx, filter, contextInfo.OrganizationID)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Failed to get inventory report details from service")
responseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{responseError}), "InventoryHandler::GetInventoryReportDetails")
return
}
response := contract.BuildSuccessResponse(report)
util.HandleResponse(c.Writer, c.Request, response, "InventoryHandler::GetInventoryReportDetails")
}