From 566567e3281e06cb3a6636d89d97c5426f9a1491 Mon Sep 17 00:00:00 2001 From: giovanni Date: Sun, 8 Mar 2026 23:22:47 +0700 Subject: [PATCH] first commit add master data kandang group --- ...01515_create_table_kandang_groups.down.sql | 28 +++ ...8101515_create_table_kandang_groups.up.sql | 91 +++++++ internal/entities/kandang.go | 2 + internal/entities/kandang_group.go | 24 ++ internal/middleware/permissions.go | 6 + .../controllers/kandang_group.controller.go | 146 +++++++++++ .../kandang-groups/dto/kandang_group.dto.go | 114 +++++++++ .../modules/master/kandang-groups/module.go | 25 ++ .../repositories/kandang_group.repository.go | 41 +++ .../modules/master/kandang-groups/route.go | 24 ++ .../services/kandang_group.service.go | 238 ++++++++++++++++++ .../validations/kandang_group.validation.go | 23 ++ internal/modules/master/route.go | 4 +- 13 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 internal/database/migrations/20260308101515_create_table_kandang_groups.down.sql create mode 100644 internal/database/migrations/20260308101515_create_table_kandang_groups.up.sql create mode 100644 internal/entities/kandang_group.go create mode 100644 internal/modules/master/kandang-groups/controllers/kandang_group.controller.go create mode 100644 internal/modules/master/kandang-groups/dto/kandang_group.dto.go create mode 100644 internal/modules/master/kandang-groups/module.go create mode 100644 internal/modules/master/kandang-groups/repositories/kandang_group.repository.go create mode 100644 internal/modules/master/kandang-groups/route.go create mode 100644 internal/modules/master/kandang-groups/services/kandang_group.service.go create mode 100644 internal/modules/master/kandang-groups/validations/kandang_group.validation.go diff --git a/internal/database/migrations/20260308101515_create_table_kandang_groups.down.sql b/internal/database/migrations/20260308101515_create_table_kandang_groups.down.sql new file mode 100644 index 00000000..c671d28c --- /dev/null +++ b/internal/database/migrations/20260308101515_create_table_kandang_groups.down.sql @@ -0,0 +1,28 @@ +BEGIN; + +ALTER TABLE daily_checklists + DROP CONSTRAINT IF EXISTS fk_daily_checklists_kandang; + +UPDATE daily_checklists dc +SET kandang_id = k.id +FROM kandangs k +WHERE + dc.kandang_id = k.kandang_group_id; + +ALTER TABLE daily_checklists + ADD CONSTRAINT fk_daily_checklists_kandang + FOREIGN KEY (kandang_id) REFERENCES kandangs (id) ON DELETE CASCADE; + +DROP INDEX IF EXISTS idx_kandangs_kandang_group_id; + +ALTER TABLE kandangs + DROP CONSTRAINT IF EXISTS fk_kandangs_kandang_group; + +ALTER TABLE kandangs + DROP COLUMN IF EXISTS kandang_group_id; + +DROP INDEX IF EXISTS kandang_groups_name_unique; + +DROP TABLE IF EXISTS kandang_groups; + +COMMIT; diff --git a/internal/database/migrations/20260308101515_create_table_kandang_groups.up.sql b/internal/database/migrations/20260308101515_create_table_kandang_groups.up.sql new file mode 100644 index 00000000..13ea9541 --- /dev/null +++ b/internal/database/migrations/20260308101515_create_table_kandang_groups.up.sql @@ -0,0 +1,91 @@ +BEGIN; + +CREATE TABLE kandang_groups ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL, + status VARCHAR(20) NOT NULL, + location_id BIGINT NOT NULL REFERENCES locations (id) ON DELETE RESTRICT ON UPDATE CASCADE, + pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE, + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), + deleted_at TIMESTAMPTZ, + created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE +); + +CREATE UNIQUE INDEX kandang_groups_name_unique ON kandang_groups (name) +WHERE + deleted_at IS NULL; + +ALTER TABLE kandangs + ADD COLUMN kandang_group_id BIGINT; + +CREATE TEMP TABLE tmp_kandang_group_map ( + kandang_id BIGINT PRIMARY KEY, + kandang_group_id BIGINT NOT NULL +) ON COMMIT DROP; + +INSERT INTO tmp_kandang_group_map (kandang_id, kandang_group_id) +SELECT + k.id, + nextval(pg_get_serial_sequence('kandang_groups', 'id')) +FROM kandangs k +ORDER BY + k.id; + +INSERT INTO kandang_groups ( + id, + name, + status, + location_id, + pic_id, + created_at, + updated_at, + deleted_at, + created_by +) +SELECT + m.kandang_group_id, + k.name, + k.status, + k.location_id, + CASE WHEN pic.id IS NOT NULL THEN k.pic_id ELSE NULL END, + k.created_at, + k.updated_at, + k.deleted_at, + CASE WHEN creator.id IS NOT NULL THEN k.created_by ELSE NULL END +FROM kandangs k + JOIN tmp_kandang_group_map m ON m.kandang_id = k.id + LEFT JOIN users pic ON pic.id = k.pic_id + LEFT JOIN users creator ON creator.id = k.created_by +ORDER BY + k.id; + +UPDATE kandangs k +SET kandang_group_id = m.kandang_group_id +FROM tmp_kandang_group_map m +WHERE + m.kandang_id = k.id; + +ALTER TABLE daily_checklists + DROP CONSTRAINT IF EXISTS fk_daily_checklists_kandang; + +UPDATE daily_checklists dc +SET kandang_id = m.kandang_group_id +FROM tmp_kandang_group_map m +WHERE + dc.kandang_id = m.kandang_id; + +ALTER TABLE daily_checklists + ADD CONSTRAINT fk_daily_checklists_kandang + FOREIGN KEY (kandang_id) REFERENCES kandang_groups (id) ON DELETE CASCADE; + +ALTER TABLE kandangs + ALTER COLUMN kandang_group_id SET NOT NULL; + +ALTER TABLE kandangs + ADD CONSTRAINT fk_kandangs_kandang_group + FOREIGN KEY (kandang_group_id) REFERENCES kandang_groups (id) ON DELETE RESTRICT ON UPDATE CASCADE; + +CREATE INDEX idx_kandangs_kandang_group_id ON kandangs (kandang_group_id); + +COMMIT; diff --git a/internal/entities/kandang.go b/internal/entities/kandang.go index e4db5655..47daf0bf 100644 --- a/internal/entities/kandang.go +++ b/internal/entities/kandang.go @@ -11,6 +11,7 @@ type Kandang struct { Name string `gorm:"type:varchar(50);not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"` Status string `gorm:"type:varchar(50);not null"` LocationId uint `gorm:"not null"` + KandangGroupId uint `gorm:"not null"` Capacity float64 `gorm:"not null"` PicId uint `gorm:"not null"` CreatedBy uint `gorm:"not null"` @@ -19,6 +20,7 @@ type Kandang struct { DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` Location Location `gorm:"foreignKey:LocationId;references:Id"` + KandangGroup KandangGroup `gorm:"foreignKey:KandangGroupId;references:Id"` Pic User `gorm:"foreignKey:PicId;references:Id"` Warehouses []Warehouse `gorm:"foreignKey:KandangId;references:Id"` ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"` diff --git a/internal/entities/kandang_group.go b/internal/entities/kandang_group.go new file mode 100644 index 00000000..8d88d628 --- /dev/null +++ b/internal/entities/kandang_group.go @@ -0,0 +1,24 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type KandangGroup struct { + Id uint `gorm:"primaryKey"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:kandang_groups_name_unique,where:deleted_at IS NULL"` + Status string `gorm:"type:varchar(50);not null"` + LocationId uint `gorm:"not null"` + PicId uint `gorm:"not null"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` + Location Location `gorm:"foreignKey:LocationId;references:Id"` + Pic User `gorm:"foreignKey:PicId;references:Id"` + Kandangs []Kandang `gorm:"foreignKey:KandangGroupId;references:Id"` +} diff --git a/internal/middleware/permissions.go b/internal/middleware/permissions.go index 05c80d54..646f6d5b 100644 --- a/internal/middleware/permissions.go +++ b/internal/middleware/permissions.go @@ -130,6 +130,12 @@ const ( P_KandangsUpdateOne = "lti.master.kandangs.update" P_KandangsDeleteOne = "lti.master.kandangs.delete" + P_KandangGroupsGetAll = "lti.master.kandang_groups.list" + P_KandangGroupsGetOne = "lti.master.kandang_groups.detail" + P_KandangGroupsCreateOne = "lti.master.kandang_groups.create" + P_KandangGroupsUpdateOne = "lti.master.kandang_groups.update" + P_KandangGroupsDeleteOne = "lti.master.kandang_groups.delete" + P_LocationsGetAll = "lti.master.locations.list" P_LocationsGetOne = "lti.master.locations.detail" P_LocationsCreateOne = "lti.master.locations.create" diff --git a/internal/modules/master/kandang-groups/controllers/kandang_group.controller.go b/internal/modules/master/kandang-groups/controllers/kandang_group.controller.go new file mode 100644 index 00000000..a7039f67 --- /dev/null +++ b/internal/modules/master/kandang-groups/controllers/kandang_group.controller.go @@ -0,0 +1,146 @@ +package controller + +import ( + "math" + "strconv" + + "github.com/gofiber/fiber/v2" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" +) + +type KandangGroupController struct { + KandangGroupService service.KandangGroupService +} + +func NewKandangGroupController(kandangGroupService service.KandangGroupService) *KandangGroupController { + return &KandangGroupController{ + KandangGroupService: kandangGroupService, + } +} + +func (u *KandangGroupController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + LocationId: c.QueryInt("location_id", 0), + PicId: c.QueryInt("pic_id", 0), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + result, totalResults, err := u.KandangGroupService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.KandangGroupListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all kandang groups successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToKandangGroupListDTOs(result), + }) +} + +func (u *KandangGroupController) GetOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + result, err := u.KandangGroupService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get kandang group successfully", + Data: dto.ToKandangGroupListDTO(*result), + }) +} + +func (u *KandangGroupController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.KandangGroupService.CreateOne(c, req) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create kandang group successfully", + Data: dto.ToKandangGroupListDTO(*result), + }) +} + +func (u *KandangGroupController) UpdateOne(c *fiber.Ctx) error { + req := new(validation.Update) + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.KandangGroupService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update kandang group successfully", + Data: dto.ToKandangGroupListDTO(*result), + }) +} + +func (u *KandangGroupController) DeleteOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := u.KandangGroupService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete kandang group successfully", + }) +} diff --git a/internal/modules/master/kandang-groups/dto/kandang_group.dto.go b/internal/modules/master/kandang-groups/dto/kandang_group.dto.go new file mode 100644 index 00000000..2217744c --- /dev/null +++ b/internal/modules/master/kandang-groups/dto/kandang_group.dto.go @@ -0,0 +1,114 @@ +package dto + +import ( + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" +) + +type KandangGroupRelationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` + Pic *userDTO.UserRelationDTO `json:"pic,omitempty"` +} + +type RecordingKandangDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type KandangGroupListDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Location locationDTO.LocationRelationDTO `json:"location"` + Pic userDTO.UserRelationDTO `json:"pic"` + CreatedUser userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RecordingKandangs []RecordingKandangDTO `json:"recording_kandangs"` +} + +type KandangGroupDetailDTO struct { + KandangGroupListDTO +} + +func ToKandangGroupRelationDTO(e entity.KandangGroup) KandangGroupRelationDTO { + var location *locationDTO.LocationRelationDTO + if e.Location.Id != 0 { + mapped := locationDTO.ToLocationRelationDTO(e.Location) + location = &mapped + } + + var pic *userDTO.UserRelationDTO + if e.Pic.Id != 0 { + mapped := userDTO.ToUserRelationDTO(e.Pic) + pic = &mapped + } + + return KandangGroupRelationDTO{ + Id: e.Id, + Name: e.Name, + Status: e.Status, + Location: location, + Pic: pic, + } +} + +func ToKandangGroupListDTO(e entity.KandangGroup) KandangGroupListDTO { + var location locationDTO.LocationRelationDTO + if e.Location.Id != 0 { + mapped := locationDTO.ToLocationRelationDTO(e.Location) + location = mapped + } + + var pic userDTO.UserRelationDTO + if e.Pic.Id != 0 { + mapped := userDTO.ToUserRelationDTO(e.Pic) + pic = mapped + } + + var createdUser userDTO.UserRelationDTO + if e.CreatedUser.Id != 0 { + mapped := userDTO.ToUserRelationDTO(e.CreatedUser) + createdUser = mapped + } + + recordingKandangs := make([]RecordingKandangDTO, 0, len(e.Kandangs)) + for _, kandang := range e.Kandangs { + recordingKandangs = append(recordingKandangs, RecordingKandangDTO{ + Id: kandang.Id, + Name: kandang.Name, + }) + } + + return KandangGroupListDTO{ + Id: e.Id, + Name: e.Name, + Status: e.Status, + Location: location, + Pic: pic, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + RecordingKandangs: recordingKandangs, + } +} + +func ToKandangGroupListDTOs(e []entity.KandangGroup) []KandangGroupListDTO { + result := make([]KandangGroupListDTO, len(e)) + for i, r := range e { + result[i] = ToKandangGroupListDTO(r) + } + return result +} + +func ToKandangGroupDetailDTO(e entity.KandangGroup) KandangGroupDetailDTO { + return KandangGroupDetailDTO{ + KandangGroupListDTO: ToKandangGroupListDTO(e), + } +} diff --git a/internal/modules/master/kandang-groups/module.go b/internal/modules/master/kandang-groups/module.go new file mode 100644 index 00000000..b14f5761 --- /dev/null +++ b/internal/modules/master/kandang-groups/module.go @@ -0,0 +1,25 @@ +package kandanggroups + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rKandangGroup "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/repositories" + sKandangGroup "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/services" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type KandangGroupModule struct{} + +func (KandangGroupModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + kandangGroupRepo := rKandangGroup.NewKandangGroupRepository(db) + userRepo := rUser.NewUserRepository(db) + + kandangGroupService := sKandangGroup.NewKandangGroupService(kandangGroupRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + KandangGroupRoutes(router, userService, kandangGroupService) +} diff --git a/internal/modules/master/kandang-groups/repositories/kandang_group.repository.go b/internal/modules/master/kandang-groups/repositories/kandang_group.repository.go new file mode 100644 index 00000000..a7c579b7 --- /dev/null +++ b/internal/modules/master/kandang-groups/repositories/kandang_group.repository.go @@ -0,0 +1,41 @@ +package repository + +import ( + "context" + + "gorm.io/gorm" + + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" +) + +type KandangGroupRepository interface { + repository.BaseRepository[entity.KandangGroup] + LocationExists(ctx context.Context, locationId uint) (bool, error) + PicExists(ctx context.Context, picId uint) (bool, error) + NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) +} + +type KandangGroupRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.KandangGroup] + db *gorm.DB +} + +func NewKandangGroupRepository(db *gorm.DB) KandangGroupRepository { + return &KandangGroupRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.KandangGroup](db), + db: db, + } +} + +func (r *KandangGroupRepositoryImpl) LocationExists(ctx context.Context, locationId uint) (bool, error) { + return repository.Exists[entity.Location](ctx, r.db, locationId) +} + +func (r *KandangGroupRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool, error) { + return repository.Exists[entity.User](ctx, r.db, picId) +} + +func (r *KandangGroupRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) { + return repository.ExistsByName[entity.KandangGroup](ctx, r.db, name, excludeID) +} diff --git a/internal/modules/master/kandang-groups/route.go b/internal/modules/master/kandang-groups/route.go new file mode 100644 index 00000000..3dd0bb4a --- /dev/null +++ b/internal/modules/master/kandang-groups/route.go @@ -0,0 +1,24 @@ +package kandanggroups + +import ( + "github.com/gofiber/fiber/v2" + + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/controllers" + kandanggroup "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +func KandangGroupRoutes(v1 fiber.Router, u user.UserService, s kandanggroup.KandangGroupService) { + ctrl := controller.NewKandangGroupController(s) + + route := v1.Group("/kandang-groups") + route.Use(m.Auth(u)) + + route.Get("/", m.RequirePermissions(m.P_KandangGroupsGetAll), ctrl.GetAll) + route.Post("/", m.RequirePermissions(m.P_KandangGroupsCreateOne), ctrl.CreateOne) + route.Get("/:id", m.RequirePermissions(m.P_KandangGroupsGetOne), ctrl.GetOne) + route.Get("/:id", ctrl.GetOne) + route.Patch("/:id", m.RequirePermissions(m.P_KandangGroupsUpdateOne), ctrl.UpdateOne) + route.Delete("/:id", m.RequirePermissions(m.P_KandangGroupsDeleteOne), ctrl.DeleteOne) +} diff --git a/internal/modules/master/kandang-groups/services/kandang_group.service.go b/internal/modules/master/kandang-groups/services/kandang_group.service.go new file mode 100644 index 00000000..4f643438 --- /dev/null +++ b/internal/modules/master/kandang-groups/services/kandang_group.service.go @@ -0,0 +1,238 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" + "gorm.io/gorm" + + common "gitlab.com/mbugroup/lti-api.git/internal/common/service" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" +) + +type KandangGroupService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.KandangGroup, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.KandangGroup, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.KandangGroup, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.KandangGroup, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type kandangGroupService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.KandangGroupRepository +} + +func NewKandangGroupService(repo repository.KandangGroupRepository, validate *validator.Validate) KandangGroupService { + return &kandangGroupService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + } +} + +func (s kandangGroupService) withRelations(db *gorm.DB) *gorm.DB { + return db. + Preload("CreatedUser"). + Preload("Location"). + Preload("Pic"). + Preload("Kandangs", func(tx *gorm.DB) *gorm.DB { + return tx.Select("id", "name", "kandang_group_id").Order("name ASC") + }) +} + +func (s kandangGroupService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.KandangGroup, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + var scopeErr error + offset := (params.Page - 1) * params.Limit + + kandangGroups, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { + db = s.withRelations(db) + db, scopeErr = m.ApplyLocationScope(c, db, "kandang_groups.location_id") + if params.Search != "" { + db = db.Where("kandang_groups.name ILIKE ?", "%"+params.Search+"%") + } + if params.LocationId != 0 { + db = db.Where("kandang_groups.location_id = ?", params.LocationId) + } + if params.PicId != 0 { + db = db.Where("kandang_groups.pic_id = ?", params.PicId) + } + return db.Order("kandang_groups.created_at DESC").Order("kandang_groups.updated_at DESC") + }) + + if scopeErr != nil { + return nil, 0, scopeErr + } + if err != nil { + s.Log.Errorf("Failed to get kandang groups: %+v", err) + return nil, 0, err + } + + return kandangGroups, total, nil +} + +func (s kandangGroupService) GetOne(c *fiber.Ctx, id uint) (*entity.KandangGroup, error) { + var scopeErr error + + kandangGroup, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { + db = s.withRelations(db) + db, scopeErr = m.ApplyLocationScope(c, db, "kandang_groups.location_id") + return db + }) + if scopeErr != nil { + return nil, scopeErr + } + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Kandang group not found") + } + if err != nil { + s.Log.Errorf("Failed to get kandang group by id: %+v", err) + return nil, err + } + + return kandangGroup, nil +} + +func (s *kandangGroupService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.KandangGroup, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + if err := m.EnsureLocationAccess(c, s.Repository.DB(), req.LocationId); err != nil { + return nil, err + } + + if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil { + s.Log.Errorf("Failed to check kandang group name: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check kandang group name") + } else if exists { + return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang group with name %s already exists", req.Name)) + } + + if err := common.EnsureRelations(c.Context(), + common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists}, + common.RelationCheck{Name: "Pic", ID: &req.PicId, Exists: s.Repository.PicExists}, + ); err != nil { + return nil, err + } + + status := strings.ToUpper(strings.TrimSpace(req.Status)) + if status == "" { + status = string(utils.KandangStatusNonActive) + } + if !utils.IsValidKandangStatus(status) { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang group status") + } + + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + + createBody := &entity.KandangGroup{ + Name: req.Name, + Status: status, + LocationId: req.LocationId, + PicId: req.PicId, + CreatedBy: actorID, + } + + if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + s.Log.Errorf("Failed to create kandang group: %+v", err) + return nil, err + } + + return s.GetOne(c, createBody.Id) +} + +func (s kandangGroupService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.KandangGroup, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + existing, err := s.GetOne(c, id) + if err != nil { + return nil, err + } + + if req.LocationId != nil { + if err := m.EnsureLocationAccess(c, s.Repository.DB(), *req.LocationId); err != nil { + return nil, err + } + } + + updateBody := make(map[string]any) + + if req.Name != nil { + if exists, err := s.Repository.NameExists(c.Context(), *req.Name, &id); err != nil { + s.Log.Errorf("Failed to check kandang group name: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check kandang group name") + } else if exists { + return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang group with name %s already exists", *req.Name)) + } + updateBody["name"] = *req.Name + } + + if err := common.EnsureRelations(c.Context(), + common.RelationCheck{Name: "Location", ID: req.LocationId, Exists: s.Repository.LocationExists}, + common.RelationCheck{Name: "Pic", ID: req.PicId, Exists: s.Repository.PicExists}, + ); err != nil { + return nil, err + } + + if req.LocationId != nil { + updateBody["location_id"] = *req.LocationId + } + if req.PicId != nil { + updateBody["pic_id"] = *req.PicId + } + if req.Status != nil { + status := strings.ToUpper(strings.TrimSpace(*req.Status)) + if !utils.IsValidKandangStatus(status) { + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang group status") + } + updateBody["status"] = status + } + + if len(updateBody) == 0 { + return existing, nil + } + + if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Kandang group not found") + } + s.Log.Errorf("Failed to update kandang group: %+v", err) + return nil, err + } + + return s.GetOne(c, id) +} + +func (s kandangGroupService) DeleteOne(c *fiber.Ctx, id uint) error { + if _, err := s.GetOne(c, id); err != nil { + return err + } + + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Kandang group not found") + } + s.Log.Errorf("Failed to delete kandang group: %+v", err) + return err + } + + return nil +} diff --git a/internal/modules/master/kandang-groups/validations/kandang_group.validation.go b/internal/modules/master/kandang-groups/validations/kandang_group.validation.go new file mode 100644 index 00000000..9637ebe7 --- /dev/null +++ b/internal/modules/master/kandang-groups/validations/kandang_group.validation.go @@ -0,0 +1,23 @@ +package validation + +type Create struct { + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Status string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` + LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` + PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"` +} + +type Update struct { + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` + Status *string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` + LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` + PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"` +} + +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=500"` + Search string `query:"search" validate:"omitempty,max=50"` + LocationId int `query:"location_id" validate:"omitempty,number,gt=0"` + PicId int `query:"pic_id" validate:"omitempty,number,gt=0"` +} diff --git a/internal/modules/master/route.go b/internal/modules/master/route.go index 06ba1ae3..d4930cce 100644 --- a/internal/modules/master/route.go +++ b/internal/modules/master/route.go @@ -9,10 +9,12 @@ import ( areas "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas" banks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks" + configChecklists "gitlab.com/mbugroup/lti-api.git/internal/modules/master/config-checklists" customers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers" employeess "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees" fcrs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs" flocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks" + kandanggroups "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandang-groups" kandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs" locations "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations" nonstocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks" @@ -24,7 +26,6 @@ import ( suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers" uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms" warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses" - configChecklists "gitlab.com/mbugroup/lti-api.git/internal/modules/master/config-checklists" // MODULE IMPORTS ) @@ -35,6 +36,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida uoms.UomModule{}, areas.AreaModule{}, locations.LocationModule{}, + kandanggroups.KandangGroupModule{}, kandangs.KandangModule{}, warehouses.WarehouseModule{}, customers.CustomerModule{},