feat(zona): Implement Zona module with CRUD operations and routing

This commit is contained in:
Habib Fatkhul Rohman 2025-11-10 14:41:11 +07:00
parent e0275ee82c
commit a432c007bf
9 changed files with 513 additions and 2 deletions

View File

@ -21,6 +21,7 @@ import (
"github.com/Caknoooo/go-gin-clean-starter/modules/role" "github.com/Caknoooo/go-gin-clean-starter/modules/role"
"github.com/Caknoooo/go-gin-clean-starter/modules/uom" "github.com/Caknoooo/go-gin-clean-starter/modules/uom"
"github.com/Caknoooo/go-gin-clean-starter/modules/warehouse" "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
@ -157,6 +158,7 @@ func main() {
uom.RegisterRoutes(server, injector) uom.RegisterRoutes(server, injector)
mvendor.RegisterRoutes(server, injector) mvendor.RegisterRoutes(server, injector)
warehouse.RegisterRoutes(server, injector) warehouse.RegisterRoutes(server, injector)
zona.RegisterRoutes(server, injector)
// register swagger route // register swagger route
server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View File

@ -52,10 +52,10 @@ func MigrateFresh(db *gorm.DB) error {
// &entities.M_MaintenanceGroupRole{}, // &entities.M_MaintenanceGroupRole{},
// &entities.M_MaintenanceGroupRoleUser{}, // &entities.M_MaintenanceGroupRoleUser{},
// &entities.MCategoryEntity{}, // &entities.MCategoryEntity{},
&entities.MProductEntity{}, // &entities.MProductEntity{},
// &entities.MUomEntity{}, // &entities.MUomEntity{},
// &entities.MVendorEntity{}, // &entities.MVendorEntity{},
&entities.MCrossReferenceEntity{}, // &entities.MCrossReferenceEntity{},
&entities.MWarehouseEntity{}, &entities.MWarehouseEntity{},
&entities.MZonaEntity{}, &entities.MZonaEntity{},
&entities.MAisleEntity{}, &entities.MAisleEntity{},

View File

@ -0,0 +1,131 @@
package controller
import (
"net/http"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/service"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"gorm.io/gorm"
)
type ZonaController interface {
Create(ctx *gin.Context)
Update(ctx *gin.Context)
Delete(ctx *gin.Context)
GetById(ctx *gin.Context)
GetAll(ctx *gin.Context)
}
type zonaController struct {
zonaService service.ZonaService
db *gorm.DB
}
func (z *zonaController) Create(ctx *gin.Context) {
var req dto.ZonaCreateRequest
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 := z.zonaService.Create(ctx, req)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_ZONA, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_ZONA, created)
ctx.JSON(http.StatusOK, res)
}
func (z *zonaController) Update(ctx *gin.Context) {
id := ctx.Param("id")
var req dto.ZonaUpdateRequest
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 := z.zonaService.Update(ctx, req, id)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_ZONA, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_ZONA, updated)
ctx.JSON(http.StatusOK, res)
}
func (z *zonaController) Delete(ctx *gin.Context) {
id := ctx.Param("id")
if err := z.zonaService.Delete(ctx, id); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_ZONA, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_ZONA, nil)
ctx.JSON(http.StatusOK, res)
}
func (z *zonaController) GetById(ctx *gin.Context) {
id := ctx.Param("id")
zona, err := z.zonaService.GetById(ctx, id)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_ZONA, err.Error(), nil)
ctx.JSON(http.StatusNotFound, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_ZONA, zona)
ctx.JSON(http.StatusOK, res)
}
func (z *zonaController) GetAll(ctx *gin.Context) {
clientId := ctx.MustGet("client_id").(string)
var filter query.ZonaFilter
filter.ClientID = clientId
if err := ctx.ShouldBindQuery(&filter); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_ZONA, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
getAll := ctx.Query("get_all")
if getAll != "" {
zonas, _, err := z.zonaService.GetAll(ctx, filter)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_ZONA, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_ZONA, zonas)
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
zonas, total, err := z.zonaService.GetAll(ctx, filter)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_ZONA, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
paginationResponse := utils.BuildPaginationResponse(perPage, page, total)
res := utils.BuildResponseSuccessWithPagination(http.StatusOK, dto.MESSAGE_SUCCESS_GET_ZONA, zonas, paginationResponse)
ctx.JSON(http.StatusOK, res)
}
func NewZonaController(i *do.Injector, zonaService service.ZonaService) ZonaController {
db := do.MustInvokeNamed[*gorm.DB](i, constants.DB)
return &zonaController{
zonaService: zonaService,
db: db,
}
}

View File

@ -0,0 +1,59 @@
package dto
import "errors"
const (
MESSAGE_FAILED_CREATE_ZONA = "failed create zona"
MESSAGE_SUCCESS_CREATE_ZONA = "success create zona"
MESSAGE_FAILED_GET_ZONA = "failed get zona"
MESSAGE_SUCCESS_GET_ZONA = "success get zona"
MESSAGE_FAILED_UPDATE_ZONA = "failed update zona"
MESSAGE_SUCCESS_UPDATE_ZONA = "success update zona"
MESSAGE_FAILED_DELETE_ZONA = "failed delete zona"
MESSAGE_SUCCESS_DELETE_ZONA = "success delete zona"
MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body"
)
var (
ErrCreateZona = errors.New("failed to create zona")
ErrGetZonaById = errors.New("failed to get zona by id")
ErrUpdateZona = errors.New("failed to update zona")
ErrDeleteZona = errors.New("failed to delete zona")
)
type ZonaCreateRequest struct {
Code string `json:"code" binding:"required"`
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required"`
Temperature string `json:"temperature"`
Hazardous bool `json:"hazardous"`
// QRCodeZone string `json:"qr_code_zone"`
// IsActive bool `json:"is_active"`
WarehouseID string `json:"warehouse_id" binding:"required"`
ClientID string `json:"client_id" binding:"required"`
}
type ZonaUpdateRequest struct {
Code string `json:"code"`
Name string `json:"name"`
Type string `json:"type"`
Temperature string `json:"temperature"`
Hazardous bool `json:"hazardous"`
// QRCodeZone string `json:"qr_code_zone"`
IsActive bool `json:"is_active"`
// WarehouseID string `json:"warehouse_id"`
// ClientID string `json:"client_id"`
}
type ZonaResponse struct {
ID string `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Type string `json:"type"`
Temperature string `json:"temperature"`
Hazardous bool `json:"hazardous"`
QRCodeZone string `json:"qr_code_zone"`
IsActive bool `json:"is_active"`
WarehouseID string `json:"warehouse_id"`
ClientID string `json:"client_id"`
}

View File

@ -0,0 +1,32 @@
package query
import "gorm.io/gorm"
type ZonaFilter struct {
Name string `form:"name"`
Code string `form:"code"`
Type string `form:"type"`
WarehouseID string `form:"warehouse_id"`
ClientID string `form:"client_id"`
PerPage int `form:"per_page"`
Page int `form:"page"`
}
func ApplyZonaFilters(db *gorm.DB, filter ZonaFilter) *gorm.DB {
if filter.Name != "" {
db = db.Where("name ILIKE ?", "%"+filter.Name+"%")
}
if filter.Code != "" {
db = db.Where("code ILIKE ?", "%"+filter.Code+"%")
}
if filter.Type != "" {
db = db.Where("type = ?", filter.Type)
}
if filter.WarehouseID != "" {
db = db.Where("warehouse_id = ?", filter.WarehouseID)
}
if filter.ClientID != "" {
db = db.Where("client_id = ?", filter.ClientID)
}
return db
}

View File

@ -0,0 +1,80 @@
package repository
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/query"
"gorm.io/gorm"
)
type ZonaRepository interface {
Create(ctx context.Context, tx *gorm.DB, zona entities.MZonaEntity) (entities.MZonaEntity, error)
GetById(ctx context.Context, tx *gorm.DB, zonaId string) (entities.MZonaEntity, error)
GetAll(ctx context.Context, filter query.ZonaFilter) ([]entities.MZonaEntity, int64, error)
Update(ctx context.Context, tx *gorm.DB, zona entities.MZonaEntity) (entities.MZonaEntity, error)
Delete(ctx context.Context, tx *gorm.DB, zonaId string) error
}
type zonaRepository struct {
db *gorm.DB
}
func NewZonaRepository(db *gorm.DB) ZonaRepository {
return &zonaRepository{db: db}
}
func (r *zonaRepository) Create(ctx context.Context, tx *gorm.DB, zona entities.MZonaEntity) (entities.MZonaEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Create(&zona).Error; err != nil {
return zona, err
}
return zona, nil
}
func (r *zonaRepository) GetById(ctx context.Context, tx *gorm.DB, zonaId string) (entities.MZonaEntity, error) {
if tx == nil {
tx = r.db
}
var zona entities.MZonaEntity
if err := tx.WithContext(ctx).Preload("Client").Preload("Warehouse").First(&zona, "id = ?", zonaId).Error; err != nil {
return zona, err
}
return zona, nil
}
func (r *zonaRepository) GetAll(ctx context.Context, filter query.ZonaFilter) ([]entities.MZonaEntity, int64, error) {
var zonas []entities.MZonaEntity
var total int64
db := query.ApplyZonaFilters(r.db, filter)
db.Model(&entities.MZonaEntity{}).Count(&total)
if filter.PerPage > 0 && filter.Page > 0 {
db = db.Offset((filter.Page - 1) * filter.PerPage).Limit(filter.PerPage)
}
if err := db.Find(&zonas).Error; err != nil {
return zonas, total, err
}
return zonas, total, nil
}
func (r *zonaRepository) Update(ctx context.Context, tx *gorm.DB, zona entities.MZonaEntity) (entities.MZonaEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Model(&entities.MZonaEntity{}).Where("id = ?", zona.ID).Updates(&zona).Error; err != nil {
return zona, err
}
return zona, nil
}
func (r *zonaRepository) Delete(ctx context.Context, tx *gorm.DB, zonaId string) error {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Delete(&entities.MZonaEntity{}, "id = ?", zonaId).Error; err != nil {
return err
}
return nil
}

24
modules/zona/routes.go Normal file
View File

@ -0,0 +1,24 @@
package zona
import (
"github.com/Caknoooo/go-gin-clean-starter/middlewares"
"github.com/Caknoooo/go-gin-clean-starter/modules/auth/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/controller"
"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) {
zonaController := do.MustInvoke[controller.ZonaController](injector)
jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService)
zonaRoutes := server.Group("/api/v1/zonas")
{
zonaRoutes.POST("", middlewares.Authenticate(jwtService), zonaController.Create)
zonaRoutes.GET("/:id", middlewares.Authenticate(jwtService), zonaController.GetById)
zonaRoutes.PUT("/:id", middlewares.Authenticate(jwtService), zonaController.Update)
zonaRoutes.DELETE("/:id", middlewares.Authenticate(jwtService), zonaController.Delete)
zonaRoutes.GET("", middlewares.Authenticate(jwtService), zonaController.GetAll)
}
}

View File

@ -0,0 +1,172 @@
package service
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/zona/repository"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ZonaService interface {
Create(ctx context.Context, req dto.ZonaCreateRequest) (dto.ZonaResponse, error)
GetById(ctx context.Context, zonaId string) (dto.ZonaResponse, error)
GetAll(ctx context.Context, filter query.ZonaFilter) ([]dto.ZonaResponse, int64, error)
Update(ctx context.Context, req dto.ZonaUpdateRequest, zonaId string) (dto.ZonaResponse, error)
Delete(ctx context.Context, zonaId string) error
}
type zonaService struct {
db *gorm.DB
zonaRepo repository.ZonaRepository
}
func toZonaResponse(e entities.MZonaEntity) dto.ZonaResponse {
return dto.ZonaResponse{
ID: e.ID.String(),
Code: e.Code,
Name: e.Name,
Type: e.Type,
Temperature: e.Temperature,
Hazardous: e.Hazardous,
QRCodeZone: e.QRCodeZone,
IsActive: e.IsActive,
WarehouseID: e.WarehouseID.String(),
ClientID: e.ClientID.String(),
}
}
func (s *zonaService) Create(ctx context.Context, req dto.ZonaCreateRequest) (dto.ZonaResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
warehouseUUID, err := uuid.Parse(req.WarehouseID)
if err != nil {
tx.Rollback()
return dto.ZonaResponse{}, err
}
clientUUID, err := uuid.Parse(req.ClientID)
if err != nil {
tx.Rollback()
return dto.ZonaResponse{}, err
}
zona := entities.MZonaEntity{
Code: req.Code,
Name: req.Name,
Type: req.Type,
Temperature: req.Temperature,
Hazardous: req.Hazardous,
// QRCodeZone: req.QRCodeZone,
// IsActive: req.IsActive,
WarehouseID: warehouseUUID,
ClientID: clientUUID,
}
created, err := s.zonaRepo.Create(ctx, tx, zona)
if err != nil {
tx.Rollback()
return dto.ZonaResponse{}, err
}
tx.Commit()
result, err := s.zonaRepo.GetById(ctx, nil, created.ID.String())
if err != nil {
return dto.ZonaResponse{}, err
}
return toZonaResponse(result), nil
}
func (s *zonaService) GetById(ctx context.Context, zonaId string) (dto.ZonaResponse, error) {
zona, err := s.zonaRepo.GetById(ctx, nil, zonaId)
if err != nil {
return dto.ZonaResponse{}, err
}
return toZonaResponse(zona), nil
}
func (s *zonaService) GetAll(ctx context.Context, filter query.ZonaFilter) ([]dto.ZonaResponse, int64, error) {
zonas, total, err := s.zonaRepo.GetAll(ctx, filter)
if err != nil {
return nil, 0, err
}
var responses []dto.ZonaResponse
for _, e := range zonas {
responses = append(responses, toZonaResponse(e))
}
if responses == nil {
responses = make([]dto.ZonaResponse, 0)
}
return responses, total, nil
}
func (s *zonaService) Update(ctx context.Context, req dto.ZonaUpdateRequest, zonaId string) (dto.ZonaResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
zona, err := s.zonaRepo.GetById(ctx, tx, zonaId)
if err != nil {
tx.Rollback()
return dto.ZonaResponse{}, err
}
if req.Code != "" {
zona.Code = req.Code
}
if req.Name != "" {
zona.Name = req.Name
}
if req.Type != "" {
zona.Type = req.Type
}
zona.Temperature = req.Temperature
zona.Hazardous = req.Hazardous
// zona.QRCodeZone = req.QRCodeZone
zona.IsActive = req.IsActive
// if req.WarehouseID != "" {
// warehouseUUID, err := uuid.Parse(req.WarehouseID)
// if err == nil {
// zona.WarehouseID = warehouseUUID
// }
// }
// if req.ClientID != "" {
// clientUUID, err := uuid.Parse(req.ClientID)
// if err == nil {
// zona.ClientID = clientUUID
// }
// }
updated, err := s.zonaRepo.Update(ctx, tx, zona)
if err != nil {
tx.Rollback()
return dto.ZonaResponse{}, err
}
tx.Commit()
return toZonaResponse(updated), nil
}
func (s *zonaService) Delete(ctx context.Context, zonaId string) error {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := s.zonaRepo.Delete(ctx, tx, zonaId); err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
func NewZonaService(zonaRepo repository.ZonaRepository, db *gorm.DB) ZonaService {
return &zonaService{
zonaRepo: zonaRepo,
db: db,
}
}

View File

@ -50,6 +50,10 @@ import (
warehouseRepo "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse/repository" warehouseRepo "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse/repository"
warehouseService "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse/service" warehouseService "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse/service"
zonaController "github.com/Caknoooo/go-gin-clean-starter/modules/zona/controller"
zonaRepo "github.com/Caknoooo/go-gin-clean-starter/modules/zona/repository"
zonaService "github.com/Caknoooo/go-gin-clean-starter/modules/zona/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/controller" "github.com/Caknoooo/go-gin-clean-starter/modules/user/controller"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/repository" "github.com/Caknoooo/go-gin-clean-starter/modules/user/repository"
userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service" userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service"
@ -97,6 +101,7 @@ func RegisterDependencies(injector *do.Injector) {
uomRepository := uomRepo.NewUomRepository(db) uomRepository := uomRepo.NewUomRepository(db)
mvendorRepository := mvendorRepo.NewVendorRepository(db) mvendorRepository := mvendorRepo.NewVendorRepository(db)
warehouseRepository := warehouseRepo.NewWarehouseRepository(db) warehouseRepository := warehouseRepo.NewWarehouseRepository(db)
zonaRepository := zonaRepo.NewZonaRepository(db)
// Service // Service
userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db) userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db)
@ -110,6 +115,7 @@ func RegisterDependencies(injector *do.Injector) {
uomServ := uomService.NewUomService(uomRepository, db) uomServ := uomService.NewUomService(uomRepository, db)
mvendorServ := mvendorService.NewVendorService(mvendorRepository, db) mvendorServ := mvendorService.NewVendorService(mvendorRepository, db)
warehouseServ := warehouseService.NewWarehouseService(warehouseRepository, db) warehouseServ := warehouseService.NewWarehouseService(warehouseRepository, db)
zonaServ := zonaService.NewZonaService(zonaRepository, db)
// Controller // Controller
do.Provide( do.Provide(
@ -177,4 +183,9 @@ func RegisterDependencies(injector *do.Injector) {
return warehouseController.NewWarehouseController(i, warehouseServ), nil return warehouseController.NewWarehouseController(i, warehouseServ), nil
}, },
) )
do.Provide(
injector, func(i *do.Injector) (zonaController.ZonaController, error) {
return zonaController.NewZonaController(i, zonaServ), nil
},
)
} }