diff --git a/cmd/main.go b/cmd/main.go index 4e10bdf..62821ad 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,7 @@ import ( "github.com/Caknoooo/go-gin-clean-starter/config" "github.com/Caknoooo/go-gin-clean-starter/docs" "github.com/Caknoooo/go-gin-clean-starter/middlewares" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle" "github.com/Caknoooo/go-gin-clean-starter/modules/auth" "github.com/Caknoooo/go-gin-clean-starter/modules/category" "github.com/Caknoooo/go-gin-clean-starter/modules/client" @@ -159,6 +160,7 @@ func main() { mvendor.RegisterRoutes(server, injector) warehouse.RegisterRoutes(server, injector) zona.RegisterRoutes(server, injector) + aisle.RegisterRoutes(server, injector) // register swagger route server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/modules/aisle/controller/aisle_controller.go b/modules/aisle/controller/aisle_controller.go new file mode 100644 index 0000000..82ea238 --- /dev/null +++ b/modules/aisle/controller/aisle_controller.go @@ -0,0 +1,126 @@ +package controller + +import ( + "net/http" + + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/gin-gonic/gin" +) + +type AisleController interface { + Create(ctx *gin.Context) + Update(ctx *gin.Context) + Delete(ctx *gin.Context) + GetById(ctx *gin.Context) + GetAll(ctx *gin.Context) +} + +type aisleController struct { + aisleService service.AisleService +} + +func (a *aisleController) Create(ctx *gin.Context) { + var req dto.AisleCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + created, err := a.aisleService.Create(ctx, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_AISLE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_AISLE, created) + ctx.JSON(http.StatusOK, res) +} + +func (a *aisleController) Update(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.AisleUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + updated, err := a.aisleService.Update(ctx, req, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_AISLE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_AISLE, updated) + ctx.JSON(http.StatusOK, res) +} + +func (a *aisleController) Delete(ctx *gin.Context) { + id := ctx.Param("id") + err := a.aisleService.Delete(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_AISLE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_AISLE, nil) + ctx.JSON(http.StatusOK, res) +} + +func (a *aisleController) GetById(ctx *gin.Context) { + id := ctx.Param("id") + aisle, err := a.aisleService.GetById(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_AISLE, err.Error(), nil) + ctx.JSON(http.StatusNotFound, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_AISLE, aisle) + ctx.JSON(http.StatusOK, res) +} + +func (a *aisleController) GetAll(ctx *gin.Context) { + clientId := ctx.MustGet("client_id").(string) + var filter query.AisleFilter + filter.ClientID = clientId + if err := ctx.ShouldBindQuery(&filter); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_AISLE, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + + getAll := ctx.Query("get_all") + if getAll != "" { + aisles, _, err := a.aisleService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_AISLE, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_AISLE, aisles) + ctx.JSON(http.StatusOK, response) + return + } + + perPage := utils.ParseInt(ctx.DefaultQuery("per_page", "10")) + page := utils.ParseInt(ctx.DefaultQuery("page", "1")) + filter.PerPage = perPage + filter.Page = (page - 1) * perPage + aisles, total, err := a.aisleService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_AISLE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + paginationResponse := utils.BuildPaginationResponse(perPage, page, total) + res := utils.BuildResponseSuccessWithPagination(http.StatusOK, dto.MESSAGE_SUCCESS_GET_AISLE, aisles, paginationResponse) + ctx.JSON(http.StatusOK, res) +} + +func NewAisleController(aisleService service.AisleService) AisleController { + return &aisleController{ + aisleService: aisleService, + } +} diff --git a/modules/aisle/dto/aisle_dto.go b/modules/aisle/dto/aisle_dto.go new file mode 100644 index 0000000..9def579 --- /dev/null +++ b/modules/aisle/dto/aisle_dto.go @@ -0,0 +1,67 @@ +package dto + +import "errors" + +const ( + MESSAGE_FAILED_CREATE_AISLE = "failed create aisle" + MESSAGE_SUCCESS_CREATE_AISLE = "success create aisle" + MESSAGE_FAILED_GET_AISLE = "failed get aisle" + MESSAGE_SUCCESS_GET_AISLE = "success get aisle" + MESSAGE_FAILED_UPDATE_AISLE = "failed update aisle" + MESSAGE_SUCCESS_UPDATE_AISLE = "success update aisle" + MESSAGE_FAILED_DELETE_AISLE = "failed delete aisle" + MESSAGE_SUCCESS_DELETE_AISLE = "success delete aisle" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" +) + +var ( + ErrCreateAisle = errors.New("failed to create aisle") + ErrGetAisleById = errors.New("failed to get aisle by id") + ErrUpdateAisle = errors.New("failed to update aisle") + ErrDeleteAisle = errors.New("failed to delete aisle") +) + +type AisleCreateRequest struct { + Code string `json:"code" binding:"required"` + Name string `json:"name" binding:"required"` + IsleX string `json:"isle_x"` + BinY string `json:"bin_y"` + LevelZ string `json:"level_z"` + Dimension string `json:"dimension"` + Weight string `json:"weight"` + QrCodeAisle string `json:"qr_code_aisle"` + IsActive bool `json:"is_active"` + ZoneID string `json:"zone_id" binding:"required"` + DimUomID string `json:"dim_uom_id" binding:"required"` + WeightUomID string `json:"weight_uom_id" binding:"required"` + ClientID string `json:"client_id" binding:"required"` +} + +type AisleUpdateRequest struct { + Code string `json:"code"` + Name string `json:"name"` + IsleX string `json:"isle_x"` + BinY string `json:"bin_y"` + LevelZ string `json:"level_z"` + Dimension string `json:"dimension"` + Weight string `json:"weight"` + QrCodeAisle string `json:"qr_code_aisle"` + IsActive bool `json:"is_active"` +} + +type AisleResponse struct { + ID string `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + IsleX string `json:"isle_x"` + BinY string `json:"bin_y"` + LevelZ string `json:"level_z"` + Dimension string `json:"dimension"` + Weight string `json:"weight"` + QrCodeAisle string `json:"qr_code_aisle"` + IsActive bool `json:"is_active"` + ZoneID string `json:"zone_id"` + DimUomID string `json:"dim_uom_id"` + WeightUomID string `json:"weight_uom_id"` + ClientID string `json:"client_id"` +} diff --git a/modules/aisle/query/aisle_query.go b/modules/aisle/query/aisle_query.go new file mode 100644 index 0000000..b9b30a7 --- /dev/null +++ b/modules/aisle/query/aisle_query.go @@ -0,0 +1,28 @@ +package query + +import "gorm.io/gorm" + +type AisleFilter struct { + Name string `form:"name"` + Code string `form:"code"` + ZoneID string `form:"zone_id"` + ClientID string `form:"client_id"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyAisleFilters(db *gorm.DB, filter AisleFilter) *gorm.DB { + if filter.Name != "" { + db = db.Where("name ILIKE ?", "%"+filter.Name+"%") + } + if filter.Code != "" { + db = db.Where("code ILIKE ?", "%"+filter.Code+"%") + } + if filter.ZoneID != "" { + db = db.Where("zone_id = ?", filter.ZoneID) + } + if filter.ClientID != "" { + db = db.Where("client_id = ?", filter.ClientID) + } + return db +} diff --git a/modules/aisle/repository/aisle_repository.go b/modules/aisle/repository/aisle_repository.go new file mode 100644 index 0000000..14a437c --- /dev/null +++ b/modules/aisle/repository/aisle_repository.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/query" + "gorm.io/gorm" +) + +type AisleRepository interface { + Create(ctx context.Context, tx *gorm.DB, aisle entities.MAisleEntity) (entities.MAisleEntity, error) + GetById(ctx context.Context, tx *gorm.DB, aisleId string) (entities.MAisleEntity, error) + GetAll(ctx context.Context, filter query.AisleFilter) ([]entities.MAisleEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, aisle entities.MAisleEntity) (entities.MAisleEntity, error) + Delete(ctx context.Context, tx *gorm.DB, aisleId string) error +} + +type aisleRepository struct { + db *gorm.DB +} + +func NewAisleRepository(db *gorm.DB) AisleRepository { + return &aisleRepository{db: db} +} + +func (r *aisleRepository) Create(ctx context.Context, tx *gorm.DB, aisle entities.MAisleEntity) (entities.MAisleEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&aisle).Error; err != nil { + return aisle, err + } + return aisle, nil +} + +func (r *aisleRepository) GetById(ctx context.Context, tx *gorm.DB, aisleId string) (entities.MAisleEntity, error) { + if tx == nil { + tx = r.db + } + var aisle entities.MAisleEntity + if err := tx.WithContext(ctx).Preload("Zona").Preload("DimUom").Preload("WeightUom").Preload("Client").First(&aisle, "id = ?", aisleId).Error; err != nil { + return aisle, err + } + return aisle, nil +} + +func (r *aisleRepository) GetAll(ctx context.Context, filter query.AisleFilter) ([]entities.MAisleEntity, int64, error) { + var aisles []entities.MAisleEntity + var total int64 + db := query.ApplyAisleFilters(r.db, filter) + db.Find(&aisles).Count(&total) + return aisles, total, nil +} + +func (r *aisleRepository) Update(ctx context.Context, tx *gorm.DB, aisle entities.MAisleEntity) (entities.MAisleEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Save(&aisle).Error; err != nil { + return aisle, err + } + return aisle, nil +} + +func (r *aisleRepository) Delete(ctx context.Context, tx *gorm.DB, aisleId string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.MAisleEntity{}, "id = ?", aisleId).Error; err != nil { + return err + } + return nil +} diff --git a/modules/aisle/routes.go b/modules/aisle/routes.go new file mode 100644 index 0000000..f90bdcb --- /dev/null +++ b/modules/aisle/routes.go @@ -0,0 +1,24 @@ +package aisle + +import ( + "github.com/Caknoooo/go-gin-clean-starter/middlewares" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/controller" + "github.com/Caknoooo/go-gin-clean-starter/modules/auth/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/gin-gonic/gin" + "github.com/samber/do" +) + +func RegisterRoutes(server *gin.Engine, injector *do.Injector) { + aisleController := do.MustInvoke[controller.AisleController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + aisleRoutes := server.Group("/api/v1/aisles") + { + aisleRoutes.POST("", middlewares.Authenticate(jwtService), aisleController.Create) + aisleRoutes.GET("/:id", middlewares.Authenticate(jwtService), aisleController.GetById) + aisleRoutes.PUT("/:id", middlewares.Authenticate(jwtService), aisleController.Update) + aisleRoutes.DELETE("/:id", middlewares.Authenticate(jwtService), aisleController.Delete) + aisleRoutes.GET("", middlewares.Authenticate(jwtService), aisleController.GetAll) + } +} diff --git a/modules/aisle/service/aisle_service.go b/modules/aisle/service/aisle_service.go new file mode 100644 index 0000000..c273227 --- /dev/null +++ b/modules/aisle/service/aisle_service.go @@ -0,0 +1,190 @@ +package service + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/repository" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type AisleService interface { + Create(ctx context.Context, req dto.AisleCreateRequest) (dto.AisleResponse, error) + GetById(ctx context.Context, aisleId string) (dto.AisleResponse, error) + GetAll(ctx context.Context, filter query.AisleFilter) ([]dto.AisleResponse, int64, error) + Update(ctx context.Context, req dto.AisleUpdateRequest, aisleId string) (dto.AisleResponse, error) + Delete(ctx context.Context, aisleId string) error +} + +type aisleService struct { + db *gorm.DB + aisleRepo repository.AisleRepository +} + +func toAisleResponse(e entities.MAisleEntity) dto.AisleResponse { + return dto.AisleResponse{ + ID: e.ID.String(), + Code: e.Code, + Name: e.Name, + IsleX: e.IsleX, + BinY: e.BinY, + LevelZ: e.LevelZ, + Dimension: e.Dimension, + Weight: e.Weight, + QrCodeAisle: e.QrCodeAisle, + IsActive: e.IsActive, + ZoneID: e.ZoneID.String(), + DimUomID: e.DimUomID.String(), + WeightUomID: e.WeightUomID.String(), + ClientID: e.ClientID.String(), + } +} + +func (s *aisleService) Create(ctx context.Context, req dto.AisleCreateRequest) (dto.AisleResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + zoneUUID, err := uuid.Parse(req.ZoneID) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + dimUomUUID, err := uuid.Parse(req.DimUomID) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + weightUomUUID, err := uuid.Parse(req.WeightUomID) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + clientUUID, err := uuid.Parse(req.ClientID) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + aisle := entities.MAisleEntity{ + Code: req.Code, + Name: req.Name, + IsleX: req.IsleX, + BinY: req.BinY, + LevelZ: req.LevelZ, + Dimension: req.Dimension, + Weight: req.Weight, + QrCodeAisle: req.QrCodeAisle, + IsActive: req.IsActive, + ZoneID: zoneUUID, + DimUomID: dimUomUUID, + WeightUomID: weightUomUUID, + ClientID: clientUUID, + } + created, err := s.aisleRepo.Create(ctx, tx, aisle) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + tx.Commit() + result, err := s.aisleRepo.GetById(ctx, nil, created.ID.String()) + if err != nil { + return dto.AisleResponse{}, err + } + return toAisleResponse(result), nil +} + +func (s *aisleService) GetById(ctx context.Context, aisleId string) (dto.AisleResponse, error) { + aisle, err := s.aisleRepo.GetById(ctx, nil, aisleId) + if err != nil { + return dto.AisleResponse{}, err + } + return toAisleResponse(aisle), nil +} + +func (s *aisleService) GetAll(ctx context.Context, filter query.AisleFilter) ([]dto.AisleResponse, int64, error) { + aisles, total, err := s.aisleRepo.GetAll(ctx, filter) + if err != nil { + return nil, 0, err + } + var responses []dto.AisleResponse + for _, e := range aisles { + responses = append(responses, toAisleResponse(e)) + } + if responses == nil { + responses = make([]dto.AisleResponse, 0) + } + return responses, total, nil +} + +func (s *aisleService) Update(ctx context.Context, req dto.AisleUpdateRequest, aisleId string) (dto.AisleResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + aisle, err := s.aisleRepo.GetById(ctx, tx, aisleId) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + if req.Code != "" { + aisle.Code = req.Code + } + if req.Name != "" { + aisle.Name = req.Name + } + if req.IsleX != "" { + aisle.IsleX = req.IsleX + } + if req.BinY != "" { + aisle.BinY = req.BinY + } + if req.LevelZ != "" { + aisle.LevelZ = req.LevelZ + } + if req.Dimension != "" { + aisle.Dimension = req.Dimension + } + if req.Weight != "" { + aisle.Weight = req.Weight + } + if req.QrCodeAisle != "" { + aisle.QrCodeAisle = req.QrCodeAisle + } + aisle.IsActive = req.IsActive + updated, err := s.aisleRepo.Update(ctx, tx, aisle) + if err != nil { + tx.Rollback() + return dto.AisleResponse{}, err + } + tx.Commit() + return toAisleResponse(updated), nil +} + +func (s *aisleService) Delete(ctx context.Context, aisleId string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := s.aisleRepo.Delete(ctx, tx, aisleId); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func NewAisleService(aisleRepo repository.AisleRepository, db *gorm.DB) AisleService { + return &aisleService{ + aisleRepo: aisleRepo, + db: db, + } +} diff --git a/providers/core.go b/providers/core.go index 8dd54ce..90356f7 100644 --- a/providers/core.go +++ b/providers/core.go @@ -54,6 +54,10 @@ import ( zonaRepo "github.com/Caknoooo/go-gin-clean-starter/modules/zona/repository" zonaService "github.com/Caknoooo/go-gin-clean-starter/modules/zona/service" + aisleController "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/controller" + aisleRepo "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/repository" + aisleService "github.com/Caknoooo/go-gin-clean-starter/modules/aisle/service" + "github.com/Caknoooo/go-gin-clean-starter/modules/user/controller" "github.com/Caknoooo/go-gin-clean-starter/modules/user/repository" userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service" @@ -102,6 +106,7 @@ func RegisterDependencies(injector *do.Injector) { mvendorRepository := mvendorRepo.NewVendorRepository(db) warehouseRepository := warehouseRepo.NewWarehouseRepository(db) zonaRepository := zonaRepo.NewZonaRepository(db) + aisleRepository := aisleRepo.NewAisleRepository(db) // Service userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db) @@ -116,6 +121,7 @@ func RegisterDependencies(injector *do.Injector) { mvendorServ := mvendorService.NewVendorService(mvendorRepository, db) warehouseServ := warehouseService.NewWarehouseService(warehouseRepository, db) zonaServ := zonaService.NewZonaService(zonaRepository, db) + aisleServ := aisleService.NewAisleService(aisleRepository, db) // Controller do.Provide( @@ -188,4 +194,9 @@ func RegisterDependencies(injector *do.Injector) { return zonaController.NewZonaController(i, zonaServ), nil }, ) + do.Provide( + injector, func(i *do.Injector) (aisleController.AisleController, error) { + return aisleController.NewAisleController(aisleServ), nil + }, + ) }