mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'dev/ragil-before-sso' into 'feat/BE/US-74/pengajuan-flock'
FIX[BE]: period and adjustment helper to function See merge request mbugroup/lti-api!17
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
type KandangBaseDTO struct {
|
type KandangBaseDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
Location *locationDTO.LocationBaseDTO `json:"location"`
|
Location *locationDTO.LocationBaseDTO `json:"location"`
|
||||||
Pic *userDTO.UserBaseDTO `json:"pic"`
|
Pic *userDTO.UserBaseDTO `json:"pic"`
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,7 @@ func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
|
|||||||
return KandangBaseDTO{
|
return KandangBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
|
Status: e.Status,
|
||||||
Location: location,
|
Location: location,
|
||||||
Pic: pic,
|
Pic: pic,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
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) {
|
if !utils.IsValidKandangStatus(status) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package validation
|
|||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3"`
|
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"`
|
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||||
PicId uint `json:"pic_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"`
|
ProjectFlockId *uint `json:"project_flock_id" validate:"omitempty,number,gt=0"`
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectflockRepository interface {
|
type ProjectflockRepository interface {
|
||||||
@@ -14,6 +15,7 @@ type ProjectflockRepository interface {
|
|||||||
GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error)
|
GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error)
|
||||||
GetActiveByFlock(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)
|
GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error)
|
||||||
|
GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectflockRepositoryImpl struct {
|
type ProjectflockRepositoryImpl struct {
|
||||||
@@ -64,3 +66,23 @@ func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, fl
|
|||||||
}
|
}
|
||||||
return max, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/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/gofiber/fiber/v2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectflockService interface {
|
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")
|
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)
|
kandangIDs := uniqueUintSlice(req.KandangIds)
|
||||||
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
||||||
if err != 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")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextPeriod int
|
projectRepo := repository.NewProjectflockRepository(tx)
|
||||||
periodQuery := tx.Model(&entity.ProjectFlock{}).
|
nextPeriod, err := projectRepo.GetNextPeriodForFlock(c.Context(), req.FlockId)
|
||||||
Where("flock_id = ?", req.FlockId).
|
if err != nil {
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"})
|
|
||||||
if err := periodQuery.Select("COALESCE(MAX(period), 0)").Scan(&nextPeriod).Error; err != nil {
|
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
s.Log.Errorf("Failed to determine next period for flock %d: %+v", req.FlockId, err)
|
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")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine next period")
|
||||||
}
|
}
|
||||||
nextPeriod++
|
|
||||||
|
|
||||||
projectRepo := s.Repository.WithTx(tx)
|
|
||||||
createBody := &entity.ProjectFlock{
|
createBody := &entity.ProjectFlock{
|
||||||
FlockId: req.FlockId,
|
FlockId: req.FlockId,
|
||||||
AreaId: req.AreaId,
|
AreaId: req.AreaId,
|
||||||
@@ -190,26 +198,58 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateBody := make(map[string]any)
|
updateBody := make(map[string]any)
|
||||||
|
var relationChecks []common.RelationCheck
|
||||||
|
|
||||||
if req.FlockId != nil {
|
if req.FlockId != nil {
|
||||||
updateBody["flock_id"] = *req.FlockId
|
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 {
|
if req.AreaId != nil {
|
||||||
updateBody["area_id"] = *req.AreaId
|
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 {
|
if req.ProductCategoryId != nil {
|
||||||
updateBody["product_category_id"] = *req.ProductCategoryId
|
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 {
|
if req.FcrId != nil {
|
||||||
updateBody["fcr_id"] = *req.FcrId
|
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 {
|
if req.LocationId != nil {
|
||||||
updateBody["location_id"] = *req.LocationId
|
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 {
|
if req.Period != nil {
|
||||||
updateBody["period"] = *req.Period
|
updateBody["period"] = *req.Period
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(relationChecks) > 0 {
|
||||||
|
if err := common.EnsureRelations(c.Context(), relationChecks...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var newKandangIDs []uint
|
var newKandangIDs []uint
|
||||||
if req.KandangIds != nil {
|
if req.KandangIds != nil {
|
||||||
if len(req.KandangIds) == 0 {
|
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")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
projectRepo := s.Repository.WithTx(tx)
|
projectRepo := repository.NewProjectflockRepository(tx)
|
||||||
if len(updateBody) > 0 {
|
if len(updateBody) > 0 {
|
||||||
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
tx.Rollback()
|
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()
|
tx.Rollback()
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
@@ -385,3 +425,9 @@ func uniqueUintSlice(values []uint) []uint {
|
|||||||
}
|
}
|
||||||
return result
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -17,13 +18,24 @@ func TestKandangIntegration(t *testing.T) {
|
|||||||
t.Run("create kandang success", func(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{
|
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||||
"name": "Kandang OK",
|
"name": "Kandang OK",
|
||||||
"status": "ACTIVE",
|
|
||||||
"location_id": locationID,
|
"location_id": locationID,
|
||||||
"pic_id": 1,
|
"pic_id": 1,
|
||||||
})
|
})
|
||||||
if resp.StatusCode != fiber.StatusCreated {
|
if resp.StatusCode != fiber.StatusCreated {
|
||||||
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
|
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) {
|
t.Run("create kandang with unknown location fails", func(t *testing.T) {
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ func TestProjectFlockSummary(t *testing.T) {
|
|||||||
if len(createResp.Data.Kandangs) != 1 || createResp.Data.Kandangs[0].Id != kandangID {
|
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)
|
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 {
|
if createResp.Data.Period != 1 {
|
||||||
t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period)
|
t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user