From 62a1011a4bdb371eeb6097c19759706be68ade64 Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 16 Oct 2025 16:35:01 +0700 Subject: [PATCH 1/2] FIX[BE]: period and adjustment helper to function --- .../kandangs/services/kandang.service.go | 6 +- .../validations/kandang.validation.go | 2 +- .../repositories/projectflock.repository.go | 22 +++++++ .../services/projectflock.service.go | 66 ++++++++++++++++--- 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/internal/modules/master/kandangs/services/kandang.service.go b/internal/modules/master/kandangs/services/kandang.service.go index 9ece8898..6e836170 100644 --- a/internal/modules/master/kandangs/services/kandang.service.go +++ b/internal/modules/master/kandangs/services/kandang.service.go @@ -101,7 +101,11 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit ); err != nil { return nil, err } - status := strings.ToUpper(req.Status) + + 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 status") } diff --git a/internal/modules/master/kandangs/validations/kandang.validation.go b/internal/modules/master/kandangs/validations/kandang.validation.go index 0dc89212..f6886991 100644 --- a/internal/modules/master/kandangs/validations/kandang.validation.go +++ b/internal/modules/master/kandangs/validations/kandang.validation.go @@ -2,7 +2,7 @@ package validation type Create struct { Name string `json:"name" validate:"required_strict,min=3"` - Status string `json:"status" validate:"required_strict,min=3"` + Status string `json:"status,omitempty" validate:"omitempty,min=3"` LocationId uint `json:"location_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"` diff --git a/internal/modules/production/project_flocks/repositories/projectflock.repository.go b/internal/modules/production/project_flocks/repositories/projectflock.repository.go index dde9ed35..476b061b 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock.repository.go @@ -7,6 +7,7 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gorm.io/gorm" + "gorm.io/gorm/clause" ) type ProjectflockRepository interface { @@ -14,6 +15,7 @@ type ProjectflockRepository interface { GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error) GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error) GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error) + GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error) } type ProjectflockRepositoryImpl struct { @@ -64,3 +66,23 @@ func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, fl } return max, nil } + +func (r *ProjectflockRepositoryImpl) GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error) { + var payload struct { + Period int + } + if err := r.DB().WithContext(ctx). + Model(&entity.ProjectFlock{}). + Where("flock_id = ?", flockID). + Clauses(clause.Locking{Strength: "UPDATE"}). + Order("period DESC"). + Limit(1). + Select("period"). + Scan(&payload).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 1, nil + } + return 0, err + } + return payload.Period + 1, nil +} diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 4ad9d21d..e9ad3ddb 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -1,9 +1,12 @@ package service import ( + "context" "errors" "fmt" + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + common "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" @@ -15,7 +18,6 @@ import ( "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" "gorm.io/gorm" - "gorm.io/gorm/clause" ) type ProjectflockService interface { @@ -106,6 +108,16 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required") } + if err := common.EnsureRelations(c.Context(), + common.RelationCheck{Name: "Flock", ID: &req.FlockId, Exists: relationExistsChecker[entity.Flock](s.Repository.DB())}, + common.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: relationExistsChecker[entity.Area](s.Repository.DB())}, + common.RelationCheck{Name: "Product category", ID: &req.ProductCategoryId, Exists: relationExistsChecker[entity.ProductCategory](s.Repository.DB())}, + common.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: relationExistsChecker[entity.Fcr](s.Repository.DB())}, + common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: relationExistsChecker[entity.Location](s.Repository.DB())}, + ); err != nil { + return nil, err + } + kandangIDs := uniqueUintSlice(req.KandangIds) kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil) if err != nil { @@ -128,18 +140,14 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction") } - var nextPeriod int - periodQuery := tx.Model(&entity.ProjectFlock{}). - Where("flock_id = ?", req.FlockId). - Clauses(clause.Locking{Strength: "UPDATE"}) - if err := periodQuery.Select("COALESCE(MAX(period), 0)").Scan(&nextPeriod).Error; err != nil { + projectRepo := repository.NewProjectflockRepository(tx) + nextPeriod, err := projectRepo.GetNextPeriodForFlock(c.Context(), req.FlockId) + if err != nil { tx.Rollback() s.Log.Errorf("Failed to determine next period for flock %d: %+v", req.FlockId, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine next period") } - nextPeriod++ - projectRepo := s.Repository.WithTx(tx) createBody := &entity.ProjectFlock{ FlockId: req.FlockId, AreaId: req.AreaId, @@ -190,26 +198,58 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id } updateBody := make(map[string]any) + var relationChecks []common.RelationCheck if req.FlockId != nil { updateBody["flock_id"] = *req.FlockId + relationChecks = append(relationChecks, common.RelationCheck{ + Name: "Flock", + ID: req.FlockId, + Exists: relationExistsChecker[entity.Flock](s.Repository.DB()), + }) } if req.AreaId != nil { updateBody["area_id"] = *req.AreaId + relationChecks = append(relationChecks, common.RelationCheck{ + Name: "Area", + ID: req.AreaId, + Exists: relationExistsChecker[entity.Area](s.Repository.DB()), + }) } if req.ProductCategoryId != nil { updateBody["product_category_id"] = *req.ProductCategoryId + relationChecks = append(relationChecks, common.RelationCheck{ + Name: "Product category", + ID: req.ProductCategoryId, + Exists: relationExistsChecker[entity.ProductCategory](s.Repository.DB()), + }) } if req.FcrId != nil { updateBody["fcr_id"] = *req.FcrId + relationChecks = append(relationChecks, common.RelationCheck{ + Name: "FCR", + ID: req.FcrId, + Exists: relationExistsChecker[entity.Fcr](s.Repository.DB()), + }) } if req.LocationId != nil { updateBody["location_id"] = *req.LocationId + relationChecks = append(relationChecks, common.RelationCheck{ + Name: "Location", + ID: req.LocationId, + Exists: relationExistsChecker[entity.Location](s.Repository.DB()), + }) } if req.Period != nil { updateBody["period"] = *req.Period } + if len(relationChecks) > 0 { + if err := common.EnsureRelations(c.Context(), relationChecks...); err != nil { + return nil, err + } + } + var newKandangIDs []uint if req.KandangIds != nil { if len(req.KandangIds) == 0 { @@ -238,7 +278,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction") } - projectRepo := s.Repository.WithTx(tx) + projectRepo := repository.NewProjectflockRepository(tx) if len(updateBody) > 0 { if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil { tx.Rollback() @@ -332,7 +372,7 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error { } } - if err := s.Repository.WithTx(tx).DeleteOne(c.Context(), id); err != nil { + if err := repository.NewProjectflockRepository(tx).DeleteOne(c.Context(), id); err != nil { tx.Rollback() if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Projectflock not found") @@ -385,3 +425,9 @@ func uniqueUintSlice(values []uint) []uint { } return result } + +func relationExistsChecker[T any](db *gorm.DB) func(context.Context, uint) (bool, error) { + return func(ctx context.Context, id uint) (bool, error) { + return commonRepo.Exists[T](ctx, db, id) + } +} From 8c0790627a5f38aeda42a826b29f11bc421dc44e Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 16 Oct 2025 16:44:26 +0700 Subject: [PATCH 2/2] FIX[BE]: period and adjustment helper to function --- .../modules/master/kandangs/dto/kandang.dto.go | 2 ++ test/integration/master_data/kandang_test.go | 14 +++++++++++++- test/integration/master_data/project_flock_test.go | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/internal/modules/master/kandangs/dto/kandang.dto.go b/internal/modules/master/kandangs/dto/kandang.dto.go index d40498af..deed483c 100644 --- a/internal/modules/master/kandangs/dto/kandang.dto.go +++ b/internal/modules/master/kandangs/dto/kandang.dto.go @@ -13,6 +13,7 @@ import ( type KandangBaseDTO struct { Id uint `json:"id"` Name string `json:"name"` + Status string `json:"status"` Location *locationDTO.LocationBaseDTO `json:"location"` Pic *userDTO.UserBaseDTO `json:"pic"` } @@ -46,6 +47,7 @@ func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO { return KandangBaseDTO{ Id: e.Id, Name: e.Name, + Status: e.Status, Location: location, Pic: pic, } diff --git a/test/integration/master_data/kandang_test.go b/test/integration/master_data/kandang_test.go index 2ca436d7..580196d4 100644 --- a/test/integration/master_data/kandang_test.go +++ b/test/integration/master_data/kandang_test.go @@ -1,6 +1,7 @@ package test import ( + "encoding/json" "net/http" "testing" @@ -17,13 +18,24 @@ func TestKandangIntegration(t *testing.T) { t.Run("create kandang success", func(t *testing.T) { resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{ "name": "Kandang OK", - "status": "ACTIVE", "location_id": locationID, "pic_id": 1, }) if resp.StatusCode != fiber.StatusCreated { t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body)) } + + var createResp struct { + Data struct { + Status string `json:"status"` + } `json:"data"` + } + if err := json.Unmarshal(body, &createResp); err != nil { + t.Fatalf("failed to parse create response: %v", err) + } + if createResp.Data.Status == "" { + t.Fatalf("expected default status to be returned, got empty") + } }) t.Run("create kandang with unknown location fails", func(t *testing.T) { diff --git a/test/integration/master_data/project_flock_test.go b/test/integration/master_data/project_flock_test.go index 22c73a5d..59698ae9 100644 --- a/test/integration/master_data/project_flock_test.go +++ b/test/integration/master_data/project_flock_test.go @@ -88,6 +88,9 @@ func TestProjectFlockSummary(t *testing.T) { if len(createResp.Data.Kandangs) != 1 || createResp.Data.Kandangs[0].Id != kandangID { t.Fatalf("expected kandang detail to be present, got %+v", createResp.Data.Kandangs) } + if createResp.Data.Kandangs[0].Status == "" { + t.Fatalf("expected kandang status to be present, got %+v", createResp.Data.Kandangs[0]) + } if createResp.Data.Period != 1 { t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period) }