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/daily-checklist.go b/internal/entities/daily-checklist.go index 8b62b1a3..71513259 100644 --- a/internal/entities/daily-checklist.go +++ b/internal/entities/daily-checklist.go @@ -17,7 +17,7 @@ type DailyChecklist struct { CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` - Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"` + Kandang KandangGroup `gorm:"foreignKey:KandangId;references:Id"` Checklist *Checklist `gorm:"foreignKey:ChecklistId;references:Id"` Creator *User `gorm:"foreignKey:CreatedBy;references:Id"` Tasks []DailyChecklistTask `gorm:"foreignKey:DailyChecklistId;references:Id"` 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..131c1ad5 100644 --- a/internal/middleware/permissions.go +++ b/internal/middleware/permissions.go @@ -130,6 +130,8 @@ const ( P_KandangsUpdateOne = "lti.master.kandangs.update" P_KandangsDeleteOne = "lti.master.kandangs.delete" + P_KandangGroups = "lti.daily_checklist.master_data.kandang" + P_LocationsGetAll = "lti.master.locations.list" P_LocationsGetOne = "lti.master.locations.detail" P_LocationsCreateOne = "lti.master.locations.create" diff --git a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go index 95848839..4c41a6b1 100644 --- a/internal/modules/daily-checklists/controllers/daily-checklist.controller.go +++ b/internal/modules/daily-checklists/controllers/daily-checklist.controller.go @@ -64,9 +64,11 @@ func (u *DailyChecklistController) GetAll(c *fiber.Ctx) error { status = *item.Status } - var kandang *kandangDTO.KandangRelationDTO + // var kandang *kandangDTO.KandangRelationDTO + var kandang *kandangDTO.KandangGroupRelationDTO if item.Kandang.Id != 0 { - mapped := kandangDTO.ToKandangRelationDTO(item.Kandang) + // mapped := kandangDTO.ToKandangRelationDTO(item.Kandang)x + mapped := kandangDTO.ToKandangGroupRelationDTO(item.Kandang) kandang = &mapped } diff --git a/internal/modules/daily-checklists/dto/daily-checklist.dto.go b/internal/modules/daily-checklists/dto/daily-checklist.dto.go index 58ca6bb0..6869894f 100644 --- a/internal/modules/daily-checklists/dto/daily-checklist.dto.go +++ b/internal/modules/daily-checklists/dto/daily-checklist.dto.go @@ -19,19 +19,19 @@ type DailyChecklistRelationDTO struct { } type DailyChecklistListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - Category string `json:"category"` - Date time.Time `json:"date"` - Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` - CreatedUser *userDTO.UserRelationDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - TotalPhase int `json:"total_phase"` - TotalActivity int `json:"total_activity"` - Progress int `json:"progress"` - RejectReason *string `json:"reject_reason"` + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Category string `json:"category"` + Date time.Time `json:"date"` + Kandang *kandangDTO.KandangGroupRelationDTO `json:"kandang,omitempty"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + TotalPhase int `json:"total_phase"` + TotalActivity int `json:"total_activity"` + Progress int `json:"progress"` + RejectReason *string `json:"reject_reason"` } type DailyChecklistDetailDTO struct { @@ -156,9 +156,9 @@ func ToDailyChecklistListDTO(e entity.DailyChecklist) DailyChecklistListDTO { status = *e.Status } - var kandang *kandangDTO.KandangRelationDTO + var kandang *kandangDTO.KandangGroupRelationDTO if e.Kandang.Id != 0 { - mapped := kandangDTO.ToKandangRelationDTO(e.Kandang) + mapped := kandangDTO.ToKandangGroupRelationDTO(e.Kandang) kandang = &mapped } diff --git a/internal/modules/daily-checklists/services/daily-checklist.service.go b/internal/modules/daily-checklists/services/daily-checklist.service.go index 6913f587..6330e641 100644 --- a/internal/modules/daily-checklists/services/daily-checklist.service.go +++ b/internal/modules/daily-checklists/services/daily-checklist.service.go @@ -75,7 +75,7 @@ type DailyChecklistListItem struct { RejectReason *string CreatedAt time.Time UpdatedAt time.Time - Kandang entity.Kandang + Kandang entity.KandangGroup TotalPhase int TotalActivity int Progress int @@ -142,7 +142,7 @@ func (s dailyChecklistService) ensureChecklistAccess(c *fiber.Ctx, checklistID u db := s.Repository.DB().WithContext(c.Context()). Table("daily_checklists dc"). - Joins("JOIN kandangs k ON k.id = dc.kandang_id"). + Joins("JOIN kandang_groups k ON k.id = dc.kandang_id"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas a ON a.id = loc.area_id"). Where("dc.id = ?", checklistID) @@ -168,7 +168,7 @@ func (s dailyChecklistService) ensureKandangAccess(c *fiber.Ctx, kandangID uint) } db := s.Repository.DB().WithContext(c.Context()). - Table("kandangs k"). + Table("kandang_groups k"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas a ON a.id = loc.area_id"). Where("k.id = ?", kandangID) @@ -196,7 +196,7 @@ func (s dailyChecklistService) ensureTaskAccess(c *fiber.Ctx, taskID uint) error db := s.Repository.DB().WithContext(c.Context()). Table("daily_checklist_activity_tasks t"). Joins("JOIN daily_checklists dc ON dc.id = t.checklist_id"). - Joins("JOIN kandangs k ON k.id = dc.kandang_id"). + Joins("JOIN kandang_groups k ON k.id = dc.kandang_id"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas a ON a.id = loc.area_id"). Where("t.id = ?", taskID) @@ -225,7 +225,7 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([ db := s.Repository.DB().WithContext(c.Context()). Table("daily_checklists dc"). - Joins("JOIN kandangs k ON k.id = dc.kandang_id"). + Joins("JOIN kandang_groups k ON k.id = dc.kandang_id"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas a ON a.id = loc.area_id") @@ -341,9 +341,9 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([ } } - kandangMap := make(map[uint]entity.Kandang) + kandangMap := make(map[uint]entity.KandangGroup) if len(kandangIDs) > 0 { - var kandangs []entity.Kandang + var kandangs []entity.KandangGroup if err := s.Repository.DB().WithContext(c.Context()). Where("id IN ?", kandangIDs). Preload("Location"). @@ -1019,7 +1019,7 @@ func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.Summa MAX(a.updated_at) AS last_activity`). Joins("JOIN daily_checklist_activity_tasks t ON t.id = a.task_id"). Joins("JOIN daily_checklists d ON d.id = t.checklist_id"). - Joins("JOIN kandangs k ON k.id = d.kandang_id"). + Joins("JOIN kandang_groups k ON k.id = d.kandang_id"). Joins("JOIN employees e ON e.id = a.employee_id"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas ar ON ar.id = loc.area_id"). @@ -1092,7 +1092,7 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report Joins("JOIN daily_checklist_activity_tasks dcat ON dcat.id = dca.task_id"). Joins("JOIN daily_checklists dc ON dc.id = dcat.checklist_id"). Joins("JOIN employees e ON e.id = dca.employee_id"). - Joins("JOIN kandangs k ON k.id = dc.kandang_id"). + Joins("JOIN kandang_groups k ON k.id = dc.kandang_id"). Joins("JOIN locations loc ON loc.id = k.location_id"). Joins("JOIN areas a ON a.id = loc.area_id"). Joins("JOIN phases p ON p.id = dcat.phase_id"). 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..c4718f3e --- /dev/null +++ b/internal/modules/master/kandang-groups/route.go @@ -0,0 +1,23 @@ +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("/", ctrl.GetAll) + route.Post("/", m.RequirePermissions(m.P_KandangGroups), ctrl.CreateOne) + route.Get("/:id", m.RequirePermissions(m.P_KandangGroups), ctrl.GetOne) + route.Patch("/:id", m.RequirePermissions(m.P_KandangGroups), ctrl.UpdateOne) + route.Delete("/:id", m.RequirePermissions(m.P_KandangGroups), 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/kandangs/dto/kandang.dto.go b/internal/modules/master/kandangs/dto/kandang.dto.go index baea9523..a0e54015 100644 --- a/internal/modules/master/kandangs/dto/kandang.dto.go +++ b/internal/modules/master/kandangs/dto/kandang.dto.go @@ -11,24 +11,32 @@ import ( // === DTO Structs === type KandangRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - Capacity float64 `json:"capacity"` - Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` - Pic *userDTO.UserRelationDTO `json:"pic,omitempty"` + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Capacity float64 `json:"capacity"` + KandangGroup *KandangGroupRelationDTO `json:"kandang_group,omitempty"` + Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` + Pic *userDTO.UserRelationDTO `json:"pic,omitempty"` +} + +type KandangGroupRelationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` } type KandangListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - Capacity float64 `json:"capacity"` - 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"` + Id uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Capacity float64 `json:"capacity"` + KandangGroup KandangGroupRelationDTO `json:"kandang_group"` + 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"` } type KandangDetailDTO struct { @@ -38,6 +46,12 @@ type KandangDetailDTO struct { // === Mapper Functions === func ToKandangRelationDTO(e entity.Kandang) KandangRelationDTO { + var kandangGroup *KandangGroupRelationDTO + if e.KandangGroup.Id != 0 { + mapped := ToKandangGroupRelationDTO(e.KandangGroup) + kandangGroup = &mapped + } + var location *locationDTO.LocationRelationDTO if e.Location.Id != 0 { mapped := locationDTO.ToLocationRelationDTO(e.Location) @@ -51,16 +65,31 @@ func ToKandangRelationDTO(e entity.Kandang) KandangRelationDTO { } return KandangRelationDTO{ - Id: e.Id, - Name: e.Name, - Status: e.Status, - Capacity: e.Capacity, - Location: location, - Pic: pic, + Id: e.Id, + Name: e.Name, + Status: e.Status, + Capacity: e.Capacity, + KandangGroup: kandangGroup, + Location: location, + Pic: pic, + } +} + +func ToKandangGroupRelationDTO(e entity.KandangGroup) KandangGroupRelationDTO { + return KandangGroupRelationDTO{ + Id: e.Id, + Name: e.Name, + Status: e.Status, } } func ToKandangListDTO(e entity.Kandang) KandangListDTO { + var kandangGroup KandangGroupRelationDTO + if e.KandangGroup.Id != 0 { + mapped := ToKandangGroupRelationDTO(e.KandangGroup) + kandangGroup = mapped + } + var location locationDTO.LocationRelationDTO if e.Location.Id != 0 { mapped := locationDTO.ToLocationRelationDTO(e.Location) @@ -80,15 +109,16 @@ func ToKandangListDTO(e entity.Kandang) KandangListDTO { } return KandangListDTO{ - Id: e.Id, - Name: e.Name, - Status: e.Status, - Location: location, - Capacity: e.Capacity, - Pic: pic, - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, - CreatedUser: createdUser, + Id: e.Id, + Name: e.Name, + Status: e.Status, + Location: location, + KandangGroup: kandangGroup, + Capacity: e.Capacity, + Pic: pic, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, } } diff --git a/internal/modules/master/kandangs/repositories/kandang.repository.go b/internal/modules/master/kandangs/repositories/kandang.repository.go index 0cdbbca3..832126d9 100644 --- a/internal/modules/master/kandangs/repositories/kandang.repository.go +++ b/internal/modules/master/kandangs/repositories/kandang.repository.go @@ -4,10 +4,10 @@ import ( "context" "errors" + "github.com/gofiber/fiber/v2" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/utils" - "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) @@ -15,6 +15,7 @@ type KandangRepository interface { repository.BaseRepository[entity.Kandang] LocationExists(ctx context.Context, areaId uint) (bool, error) PicExists(ctx context.Context, areaId uint) (bool, error) + KandangGroupExists(ctx context.Context, kandangGroupId uint) (bool, error) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) @@ -45,6 +46,10 @@ func (r *KandangRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool return repository.Exists[entity.User](ctx, r.db, picId) } +func (r *KandangRepositoryImpl) KandangGroupExists(ctx context.Context, kandangGroupId uint) (bool, error) { + return repository.Exists[entity.KandangGroup](ctx, r.db, kandangGroupId) +} + func (r *KandangRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) { return repository.ExistsByName[entity.Kandang](ctx, r.db, name, excludeID) } diff --git a/internal/modules/master/kandangs/services/kandang.service.go b/internal/modules/master/kandangs/services/kandang.service.go index 159bc410..fcb39011 100644 --- a/internal/modules/master/kandangs/services/kandang.service.go +++ b/internal/modules/master/kandangs/services/kandang.service.go @@ -3,13 +3,14 @@ package service import ( "errors" "fmt" + "strings" + 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/kandangs/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" - "strings" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -40,7 +41,7 @@ func NewKandangService(repo repository.KandangRepository, validate *validator.Va } func (s kandangService) withRelations(db *gorm.DB) *gorm.DB { - return db.Preload("CreatedUser").Preload("Location").Preload("Pic").Preload("ProjectFlockKandangs.ProjectFlock") + return db.Preload("CreatedUser").Preload("Location").Preload("KandangGroup").Preload("Pic").Preload("ProjectFlockKandangs.ProjectFlock") } @@ -99,6 +100,28 @@ func (s kandangService) GetOne(c *fiber.Ctx, id uint) (*entity.Kandang, error) { return kandang, nil } +func (s kandangService) ensureKandangGroupAccess(c *fiber.Ctx, groupID uint, expectedLocationID *uint) error { + var kandangGroup entity.KandangGroup + if err := s.Repository.DB().WithContext(c.Context()). + Select("id", "location_id"). + First(&kandangGroup, groupID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Kandang group not found") + } + return err + } + + if err := m.EnsureLocationAccess(c, s.Repository.DB(), kandangGroup.LocationId); err != nil { + return fiber.NewError(fiber.StatusNotFound, "Kandang group not found") + } + + if expectedLocationID != nil && kandangGroup.LocationId != *expectedLocationID { + return fiber.NewError(fiber.StatusBadRequest, "Kandang group location must match kandang location") + } + + return nil +} + func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Kandang, error) { if err := s.Validate.Struct(req); err != nil { return nil, err @@ -116,10 +139,14 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists}, + common.RelationCheck{Name: "KandangGroup", ID: &req.GroupId, Exists: s.Repository.KandangGroupExists}, common.RelationCheck{Name: "Pic", ID: &req.PicId, Exists: s.Repository.PicExists}, ); err != nil { return nil, err } + if err := s.ensureKandangGroupAccess(c, req.GroupId, &req.LocationId); err != nil { + return nil, err + } status := strings.ToUpper(strings.TrimSpace(req.Status)) if status == "" { @@ -154,12 +181,13 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } createBody := &entity.Kandang{ - Name: req.Name, - LocationId: req.LocationId, - Capacity: req.Capacity, - Status: status, - PicId: req.PicId, - CreatedBy: actorID, + Name: req.Name, + LocationId: req.LocationId, + KandangGroupId: req.GroupId, + Capacity: req.Capacity, + Status: status, + PicId: req.PicId, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { @@ -212,6 +240,7 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Location", ID: req.LocationId, Exists: s.Repository.LocationExists}, + common.RelationCheck{Name: "KandangGroup", ID: req.GroupId, Exists: s.Repository.KandangGroupExists}, common.RelationCheck{Name: "Pic", ID: req.PicId, Exists: s.Repository.PicExists}, ); err != nil { return nil, err @@ -220,6 +249,16 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) if req.LocationId != nil { updateBody["location_id"] = *req.LocationId } + if req.GroupId != nil { + targetLocationID := existing.LocationId + if req.LocationId != nil { + targetLocationID = *req.LocationId + } + if err := s.ensureKandangGroupAccess(c, *req.GroupId, &targetLocationID); err != nil { + return nil, err + } + updateBody["kandang_group_id"] = *req.GroupId + } if req.PicId != nil { updateBody["pic_id"] = *req.PicId diff --git a/internal/modules/master/kandangs/validations/kandang.validation.go b/internal/modules/master/kandangs/validations/kandang.validation.go index 63f03d12..9d5fe103 100644 --- a/internal/modules/master/kandangs/validations/kandang.validation.go +++ b/internal/modules/master/kandangs/validations/kandang.validation.go @@ -5,6 +5,7 @@ type Create struct { Status string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` Capacity float64 `json:"capacity" validate:"required_strict,gt=0"` LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` + GroupId uint `json:"group_id" validate:"required_strict,number,gt=0"` PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"` ProjectFlockId *uint `json:"project_flock_id" validate:"omitempty,number,gt=0"` } @@ -14,6 +15,7 @@ type Update struct { Status *string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` Capacity *float64 `json:"capacity" validate:"omitempty,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` + GroupId *uint `json:"group_id" validate:"required_strict,number,gt=0"` PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"` ProjectFlockId *uint `json:"project_flock_id,omitempty" 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{},