FIX[BE]: name duplicate flock,projectflock category change,menerapkan dto seperti warehouse di projectflock

This commit is contained in:
ragilap
2025-10-20 16:39:16 +07:00
parent f15e0d62e3
commit ee033b8fe6
10 changed files with 290 additions and 285 deletions
+38 -42
View File
@@ -50,7 +50,7 @@ func Run(db *gorm.DB) error {
return err return err
} }
projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, productCategories, fcrs, locations) projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, fcrs, locations)
if err != nil { if err != nil {
return err return err
} }
@@ -239,33 +239,33 @@ func seedFlocks(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
return result, nil return result, nil
} }
func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, productCategories, fcrs, locations map[string]uint) (map[string]uint, error) { func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, fcrs, locations map[string]uint) (map[string]uint, error) {
seeds := []struct { seeds := []struct {
Key string Key string
Flock string Flock string
Area string Area string
ProductCategory string Category utils.ProjectFlockCategory
Fcr string Fcr string
Location string Location string
Period int Period int
}{ }{
{ {
Key: "Singaparna Period 1", Key: "Singaparna Period 1",
Flock: "Flock Priangan", Flock: "Flock Priangan",
Area: "Priangan", Area: "Priangan",
ProductCategory: "Day Old Chick", Category: utils.ProjectFlockCategoryGrowing,
Fcr: "FCR Layer", Fcr: "FCR Layer",
Location: "Singaparna", Location: "Singaparna",
Period: 1, Period: 1,
}, },
{ {
Key: "Cikaum Period 1", Key: "Cikaum Period 1",
Flock: "Flock Banten", Flock: "Flock Banten",
Area: "Banten", Area: "Banten",
ProductCategory: "Day Old Chick", Category: utils.ProjectFlockCategoryGrowing,
Fcr: "FCR Layer", Fcr: "FCR Layer",
Location: "Cikaum", Location: "Cikaum",
Period: 1, Period: 1,
}, },
} }
@@ -280,10 +280,6 @@ func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, productCatego
if !ok { if !ok {
return nil, fmt.Errorf("area %s not seeded", seed.Area) return nil, fmt.Errorf("area %s not seeded", seed.Area)
} }
categoryID, ok := productCategories[seed.ProductCategory]
if !ok {
return nil, fmt.Errorf("product category %s not seeded", seed.ProductCategory)
}
fcrID, ok := fcrs[seed.Fcr] fcrID, ok := fcrs[seed.Fcr]
if !ok { if !ok {
return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr) return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr)
@@ -294,17 +290,17 @@ func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, productCatego
} }
var projectFlock entity.ProjectFlock var projectFlock entity.ProjectFlock
err := tx.Where("flock_id = ? AND area_id = ? AND product_category_id = ? AND fcr_id = ? AND location_id = ? AND period = ?", err := tx.Where("flock_id = ? AND area_id = ? AND category = ? AND fcr_id = ? AND location_id = ? AND period = ?",
flockID, areaID, categoryID, fcrID, locationID, seed.Period).First(&projectFlock).Error flockID, areaID, seed.Category, fcrID, locationID, seed.Period).First(&projectFlock).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
projectFlock = entity.ProjectFlock{ projectFlock = entity.ProjectFlock{
FlockId: flockID, FlockId: flockID,
AreaId: areaID, AreaId: areaID,
ProductCategoryId: categoryID, Category: string(seed.Category),
FcrId: fcrID, FcrId: fcrID,
LocationId: locationID, LocationId: locationID,
Period: seed.Period, Period: seed.Period,
CreatedBy: createdBy, CreatedBy: createdBy,
} }
if err := tx.Create(&projectFlock).Error; err != nil { if err := tx.Create(&projectFlock).Error; err != nil {
return nil, err return nil, err
@@ -313,12 +309,12 @@ func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, productCatego
return nil, err return nil, err
} else { } else {
if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{ if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{
"flock_id": flockID, "flock_id": flockID,
"area_id": areaID, "area_id": areaID,
"product_category_id": categoryID, "category": string(seed.Category),
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationID, "location_id": locationID,
"period": seed.Period, "period": seed.Period,
}).Error; err != nil { }).Error; err != nil {
return nil, err return nil, err
} }
+18 -19
View File
@@ -7,23 +7,22 @@ import (
) )
type ProjectFlock struct { type ProjectFlock struct {
Id uint `gorm:"primaryKey"` Id uint `gorm:"primaryKey"`
FlockId uint `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:1"` FlockId uint `gorm:"not null;uniqueIndex:project_flocks_flock_period_unique"`
AreaId uint `gorm:"not null"` AreaId uint `gorm:"not null"`
ProductCategoryId uint `gorm:"not null"` Category string `gorm:"type:varchar(20);not null"`
FcrId uint `gorm:"not null"` FcrId uint `gorm:"not null"`
LocationId uint `gorm:"not null"` LocationId uint `gorm:"not null"`
Period int `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:2"` Period int `gorm:"not null;uniqueIndex:project_flocks_flock_period_unique"`
CreatedBy uint `gorm:"not null"` CreatedBy uint `gorm:"not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
Flock Flock `gorm:"foreignKey:FlockId;references:Id"` Flock Flock `gorm:"foreignKey:FlockId;references:Id"`
Area Area `gorm:"foreignKey:AreaId;references:Id"` Area Area `gorm:"foreignKey:AreaId;references:Id"`
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"` Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"`
Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"` Location Location `gorm:"foreignKey:LocationId;references:Id"`
Location Location `gorm:"foreignKey:LocationId;references:Id"` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` Kandangs []Kandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
Kandangs []Kandang `gorm:"foreignKey:ProjectFlockId;references:Id"` KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
} }
@@ -1,21 +1,30 @@
package repository package repository
import ( import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "context"
"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"
"gorm.io/gorm" "gorm.io/gorm"
) )
type FlockRepository interface { type FlockRepository interface {
repository.BaseRepository[entity.Flock] repository.BaseRepository[entity.Flock]
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
} }
type FlockRepositoryImpl struct { type FlockRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.Flock] *repository.BaseRepositoryImpl[entity.Flock]
db *gorm.DB
} }
func NewFlockRepository(db *gorm.DB) FlockRepository { func NewFlockRepository(db *gorm.DB) FlockRepository {
return &FlockRepositoryImpl{ return &FlockRepositoryImpl{
BaseRepositoryImpl: repository.NewBaseRepository[entity.Flock](db), BaseRepositoryImpl: repository.NewBaseRepository[entity.Flock](db),
db: db,
} }
} }
func (r *FlockRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
return repository.ExistsByName[entity.Flock](ctx, r.db, name, excludeID)
}
@@ -2,6 +2,8 @@ package service
import ( import (
"errors" "errors"
"fmt"
"strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
@@ -79,8 +81,22 @@ func (s *flockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.
return nil, err return nil, err
} }
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, fiber.NewError(fiber.StatusBadRequest, "Name is required")
}
exists, err := s.Repository.NameExists(c.Context(), name, nil)
if err != nil {
s.Log.Errorf("Failed to check flock name: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check flock name")
}
if exists {
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Flock with name %s already exists", name))
}
createBody := &entity.Flock{ createBody := &entity.Flock{
Name: req.Name, Name: name,
CreatedBy: 1, CreatedBy: 1,
} }
@@ -100,7 +116,20 @@ func (s flockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (
updateBody := make(map[string]any) updateBody := make(map[string]any)
if req.Name != nil { if req.Name != nil {
updateBody["name"] = *req.Name name := strings.TrimSpace(*req.Name)
if name == "" {
return nil, fiber.NewError(fiber.StatusBadRequest, "Name cannot be empty")
}
exists, err := s.Repository.NameExists(c.Context(), name, &id)
if err != nil {
s.Log.Errorf("Failed to check flock name: %+v", err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check flock name")
}
if exists {
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Flock with name %s already exists", name))
}
updateBody["name"] = name
} }
if len(updateBody) == 0 { if len(updateBody) == 0 {
@@ -4,75 +4,66 @@ import (
"time" "time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
) )
type ProjectFlockBaseDTO struct { type ProjectFlockBaseDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
// FlockId uint `json:"flock_id"` Period int `json:"period"`
// AreaId uint `json:"area_id"` Category string `json:"category"`
// ProductCategoryId uint `json:"product_category_id"` Flock *flockDTO.FlockBaseDTO `json:"flock"`
// FcrId uint `json:"fcr_id"` Area *areaDTO.AreaBaseDTO `json:"area"`
// LocationId uint `json:"location_id"` Fcr *fcrDTO.FcrBaseDTO `json:"fcr"`
Period int `json:"period"` Location *locationDTO.LocationBaseDTO `json:"location"`
} }
func ToProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO { func ToProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
return ProjectFlockBaseDTO{ var flock *flockDTO.FlockBaseDTO
Id: e.Id, if e.Flock.Id != 0 {
// FlockId: e.FlockId, mapped := flockDTO.ToFlockBaseDTO(e.Flock)
// AreaId: e.AreaId, flock = &mapped
// ProductCategoryId: e.ProductCategoryId,
// FcrId: e.FcrId,
// LocationId: e.LocationId,
Period: e.Period,
} }
}
type FlockSummaryDTO struct { var area *areaDTO.AreaBaseDTO
Id uint `json:"id"` if e.Area.Id != 0 {
Name string `json:"name"` mapped := areaDTO.ToAreaBaseDTO(e.Area)
} area = &mapped
}
type AreaSummaryDTO struct { var fcr *fcrDTO.FcrBaseDTO
Id uint `json:"id"` if e.Fcr.Id != 0 {
Name string `json:"name"` mapped := fcrDTO.ToFcrBaseDTO(e.Fcr)
} fcr = &mapped
}
type ProductCategorySummaryDTO struct { var location *locationDTO.LocationBaseDTO
Id uint `json:"id"` if e.Location.Id != 0 {
Name string `json:"name"` mapped := locationDTO.ToLocationBaseDTO(e.Location)
Code string `json:"code"` location = &mapped
} }
type FcrSummaryDTO struct { return ProjectFlockBaseDTO{
Id uint `json:"id"` Id: e.Id,
Name string `json:"name"` Period: e.Period,
} Category: e.Category,
Flock: flock,
type LocationSummaryDTO struct { Area: area,
Id uint `json:"id"` Fcr: fcr,
Name string `json:"name"` Location: location,
Address string `json:"address"` }
}
type KandangSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
} }
type ProjectFlockListDTO struct { type ProjectFlockListDTO struct {
ProjectFlockBaseDTO ProjectFlockBaseDTO
Flock *FlockSummaryDTO `json:"flock,omitempty"` Kandangs []kandangDTO.KandangBaseDTO `json:"kandangs,omitempty"`
Area *AreaSummaryDTO `json:"area,omitempty"` CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
ProductCategory *ProductCategorySummaryDTO `json:"product_category,omitempty"` CreatedAt time.Time `json:"created_at"`
Fcr *FcrSummaryDTO `json:"fcr,omitempty"` UpdatedAt time.Time `json:"updated_at"`
Location *LocationSummaryDTO `json:"location,omitempty"`
Kandangs []KandangSummaryDTO `json:"kandangs,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
} }
type ProjectFlockDetailDTO struct { type ProjectFlockDetailDTO struct {
@@ -80,8 +71,8 @@ type ProjectFlockDetailDTO struct {
} }
type FlockPeriodSummaryDTO struct { type FlockPeriodSummaryDTO struct {
Flock FlockSummaryDTO `json:"flock"` Flock flockDTO.FlockBaseDTO `json:"flock"`
NextPeriod int `json:"next_period"` NextPeriod int `json:"next_period"`
} }
func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO { func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
@@ -91,62 +82,16 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
createdUser = &mapped createdUser = &mapped
} }
var flockSummary *FlockSummaryDTO var kandangSummaries []kandangDTO.KandangBaseDTO
if e.Flock.Id != 0 { if len(e.Kandangs) > 0 {
summary := ToFlockSummaryDTO(e.Flock) kandangSummaries = make([]kandangDTO.KandangBaseDTO, len(e.Kandangs))
flockSummary = &summary for i, kandang := range e.Kandangs {
} kandangSummaries[i] = kandangDTO.ToKandangBaseDTO(kandang)
var areaSummary *AreaSummaryDTO
if e.Area.Id != 0 {
areaSummary = &AreaSummaryDTO{
Id: e.Area.Id,
Name: e.Area.Name,
}
}
var categorySummary *ProductCategorySummaryDTO
if e.ProductCategory.Id != 0 {
categorySummary = &ProductCategorySummaryDTO{
Id: e.ProductCategory.Id,
Name: e.ProductCategory.Name,
Code: e.ProductCategory.Code,
}
}
var fcrSummary *FcrSummaryDTO
if e.Fcr.Id != 0 {
fcrSummary = &FcrSummaryDTO{
Id: e.Fcr.Id,
Name: e.Fcr.Name,
}
}
var locationSummary *LocationSummaryDTO
if e.Location.Id != 0 {
locationSummary = &LocationSummaryDTO{
Id: e.Location.Id,
Name: e.Location.Name,
Address: e.Location.Address,
}
}
kandangSummaries := make([]KandangSummaryDTO, len(e.Kandangs))
for i, kandang := range e.Kandangs {
kandangSummaries[i] = KandangSummaryDTO{
Id: kandang.Id,
Name: kandang.Name,
Status: kandang.Status,
} }
} }
return ProjectFlockListDTO{ return ProjectFlockListDTO{
ProjectFlockBaseDTO: ToProjectFlockBaseDTO(e), ProjectFlockBaseDTO: ToProjectFlockBaseDTO(e),
Flock: flockSummary,
Area: areaSummary,
ProductCategory: categorySummary,
Fcr: fcrSummary,
Location: locationSummary,
Kandangs: kandangSummaries, Kandangs: kandangSummaries,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
@@ -168,16 +113,9 @@ func ToProjectFlockDetailDTO(e entity.ProjectFlock) ProjectFlockDetailDTO {
} }
} }
func ToFlockSummaryDTO(e entity.Flock) FlockSummaryDTO {
return FlockSummaryDTO{
Id: e.Id,
Name: e.Name,
}
}
func ToFlockPeriodSummaryDTO(flock entity.Flock, next int) FlockPeriodSummaryDTO { func ToFlockPeriodSummaryDTO(flock entity.Flock, next int) FlockPeriodSummaryDTO {
return FlockPeriodSummaryDTO{ return FlockPeriodSummaryDTO{
Flock: ToFlockSummaryDTO(flock), Flock: flockDTO.ToFlockBaseDTO(flock),
NextPeriod: next, NextPeriod: next,
} }
} }
@@ -67,7 +67,6 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
Preload("CreatedUser"). Preload("CreatedUser").
Preload("Flock"). Preload("Flock").
Preload("Area"). Preload("Area").
Preload("ProductCategory").
Preload("Fcr"). Preload("Fcr").
Preload("Location"). Preload("Location").
Preload("Kandangs") Preload("Kandangs")
@@ -115,15 +114,13 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
db = db. db = db.
Joins("LEFT JOIN flocks ON flocks.id = project_flocks.flock_id"). Joins("LEFT JOIN flocks ON flocks.id = project_flocks.flock_id").
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id"). Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
Joins("LEFT JOIN product_categories ON product_categories.id = project_flocks.product_category_id").
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id"). Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id"). Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by"). Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
Where(` Where(`
LOWER(flocks.name) LIKE ? LOWER(flocks.name) LIKE ?
OR LOWER(areas.name) LIKE ? OR LOWER(areas.name) LIKE ?
OR LOWER(product_categories.name) LIKE ? OR LOWER(project_flocks.category) LIKE ?
OR LOWER(product_categories.code) LIKE ?
OR LOWER(fcrs.name) LIKE ? OR LOWER(fcrs.name) LIKE ?
OR LOWER(locations.name) LIKE ? OR LOWER(locations.name) LIKE ?
OR LOWER(locations.address) LIKE ? OR LOWER(locations.address) LIKE ?
@@ -146,7 +143,6 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
likeQuery, likeQuery,
likeQuery, likeQuery,
likeQuery, likeQuery,
likeQuery,
) )
} }
for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) { for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) {
@@ -179,6 +175,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
return nil, err return nil, err
} }
category, ok := utils.NormalizeProjectFlockCategory(req.Category)
if !ok {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
}
if len(req.KandangIds) == 0 { if len(req.KandangIds) == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required") return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
} }
@@ -186,7 +187,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
if err := common.EnsureRelations(c.Context(), if err := common.EnsureRelations(c.Context(),
common.RelationCheck{Name: "Flock", ID: &req.FlockId, Exists: relationExistsChecker[entity.Flock](s.Repository.DB())}, 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: "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: "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())}, common.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: relationExistsChecker[entity.Location](s.Repository.DB())},
); err != nil { ); err != nil {
@@ -224,13 +224,13 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
} }
createBody := &entity.ProjectFlock{ createBody := &entity.ProjectFlock{
FlockId: req.FlockId, FlockId: req.FlockId,
AreaId: req.AreaId, AreaId: req.AreaId,
ProductCategoryId: req.ProductCategoryId, Category: string(category),
FcrId: req.FcrId, FcrId: req.FcrId,
LocationId: req.LocationId, LocationId: req.LocationId,
Period: nextPeriod, Period: nextPeriod,
CreatedBy: 1, CreatedBy: 1,
} }
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil { if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
@@ -289,13 +289,12 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
Exists: relationExistsChecker[entity.Area](s.Repository.DB()), Exists: relationExistsChecker[entity.Area](s.Repository.DB()),
}) })
} }
if req.ProductCategoryId != nil { if req.Category != nil {
updateBody["product_category_id"] = *req.ProductCategoryId if normalized, ok := utils.NormalizeProjectFlockCategory(*req.Category); ok {
relationChecks = append(relationChecks, common.RelationCheck{ updateBody["category"] = string(normalized)
Name: "Product category", } else {
ID: req.ProductCategoryId, return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
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
@@ -541,7 +540,10 @@ func (s projectflockService) attachKandangs(ctx context.Context, tx *gorm.DB, pr
if err := tx.Model(&entity.Kandang{}). if err := tx.Model(&entity.Kandang{}).
Where("id IN ?", kandangIDs). Where("id IN ?", kandangIDs).
Updates(map[string]any{"project_flock_id": projectFlockID}).Error; err != nil { Updates(map[string]any{
"project_flock_id": projectFlockID,
"status": string(utils.KandangStatusPengajuan),
}).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs") return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
} }
@@ -1,32 +1,32 @@
package validation package validation
type Create struct { type Create struct {
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"` FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
ProductCategoryId uint `json:"product_category_id" validate:"required_strict,number,gt=0"` Category string `json:"category" validate:"required_strict,oneof=growing laying GROWING LAYING"`
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"` FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"` KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
} }
type Update struct { type Update struct {
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"` FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"` AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
ProductCategoryId *uint `json:"product_category_id,omitempty" validate:"omitempty,number,gt=0"` Category *string `json:"category,omitempty" validate:"omitempty,oneof=growing laying GROWING LAYING"`
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"` FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
Period *int `json:"period,omitempty" validate:"omitempty,number,gt=0"` Period *int `json:"period,omitempty" validate:"omitempty,number,gt=0"`
KandangIds []uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,gt=0"` KandangIds []uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,gt=0"`
} }
type Query struct { type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"` Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Search string `query:"search" validate:"omitempty,max=50"` Search string `query:"search" validate:"omitempty,max=50"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=area location kandangs period"` SortBy string `query:"sort_by" validate:"omitempty,oneof=area location kandangs period"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
AreaId uint `query:"area_id" validate:"omitempty,number,gt=0"` AreaId uint `query:"area_id" validate:"omitempty,number,gt=0"`
LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"` LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"`
Period int `query:"period" validate:"omitempty,number,gt=0"` Period int `query:"period" validate:"omitempty,number,gt=0"`
KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"` KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"`
} }
+31 -9
View File
@@ -59,7 +59,6 @@ var allFlagTypes = func() map[FlagType]struct{} {
return m return m
}() }()
func AllFlagTypes() map[FlagType]struct{} { func AllFlagTypes() map[FlagType]struct{} {
return allFlagTypes return allFlagTypes
} }
@@ -76,8 +75,6 @@ const (
WarehouseTypeKandang WarehouseType = "KANDANG" WarehouseTypeKandang WarehouseType = "KANDANG"
) )
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// WarehouseType // WarehouseType
// ------------------------------------------------------------------- // -------------------------------------------------------------------
@@ -100,19 +97,29 @@ const (
SupplierCategorySapronak SupplierCategory = "SAPRONAK" SupplierCategorySapronak SupplierCategory = "SAPRONAK"
) )
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Kandang Status // Kandang Status
// ------------------------------------------------------------------- // -------------------------------------------------------------------
type KandangStatus string type KandangStatus string
const ( const (
KandangStatusNonActive KandangStatus = "NON_ACTIVE" KandangStatusNonActive KandangStatus = "NON_ACTIVE"
KandangStatusPengajuan KandangStatus = "PENGAJUAN" KandangStatusPengajuan KandangStatus = "PENGAJUAN"
KandangStatusActive KandangStatus = "ACTIVE" KandangStatusActive KandangStatus = "ACTIVE"
) )
// -------------------------------------------------------------------
// ProjectFlockCategory
// -------------------------------------------------------------------
type ProjectFlockCategory string
const (
ProjectFlockCategoryGrowing ProjectFlockCategory = "GROWING"
ProjectFlockCategoryLaying ProjectFlockCategory = "LAYING"
)
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Validators // Validators
// ------------------------------------------------------------------- // -------------------------------------------------------------------
@@ -223,6 +230,21 @@ func IsValidCustomerSupplierType(v string) bool {
return false return false
} }
func NormalizeProjectFlockCategory(v string) (ProjectFlockCategory, bool) {
normalized := ProjectFlockCategory(strings.ToUpper(strings.TrimSpace(v)))
switch normalized {
case ProjectFlockCategoryGrowing, ProjectFlockCategoryLaying:
return normalized, true
default:
return "", false
}
}
func IsValidProjectFlockCategory(v string) bool {
_, ok := NormalizeProjectFlockCategory(v)
return ok
}
func IsValidSupplierCategory(v string) bool { func IsValidSupplierCategory(v string) bool {
switch SupplierCategory(v) { switch SupplierCategory(v) {
case SupplierCategoryBOP, SupplierCategorySapronak: case SupplierCategoryBOP, SupplierCategorySapronak:
+8 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"gitlab.com/mbugroup/lti-api.git/internal/entities" "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
) )
func TestKandangIntegration(t *testing.T) { func TestKandangIntegration(t *testing.T) {
@@ -51,20 +52,19 @@ func TestKandangIntegration(t *testing.T) {
}) })
t.Run("cannot assign project floc with existing active kandang", func(t *testing.T) { t.Run("cannot assign project floc with existing active kandang", func(t *testing.T) {
categoryID := createProductCategory(t, app, "DOC Category", "DOC1")
fcrID := createFcr(t, app, "FCR For Floc", []map[string]any{ fcrID := createFcr(t, app, "FCR For Floc", []map[string]any{
{"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0}, {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
}) })
flocID := createFlock(t, app, "Floc Test") flocID := createFlock(t, app, "Floc Test")
projectFloc := entities.ProjectFlock{ projectFloc := entities.ProjectFlock{
FlockId: flocID, FlockId: flocID,
AreaId: areaID, AreaId: areaID,
ProductCategoryId: categoryID, Category: string(utils.ProjectFlockCategoryGrowing),
FcrId: fcrID, FcrId: fcrID,
LocationId: locationID, LocationId: locationID,
Period: 1, Period: 1,
CreatedBy: 1, CreatedBy: 1,
} }
if err := db.Create(&projectFloc).Error; err != nil { if err := db.Create(&projectFloc).Error; err != nil {
t.Fatalf("failed to seed project floc: %v", err) t.Fatalf("failed to seed project floc: %v", err)
@@ -19,19 +19,18 @@ func TestProjectFlockSummary(t *testing.T) {
areaID := createArea(t, app, "Area Project") areaID := createArea(t, app, "Area Project")
locationID := createLocation(t, app, "Location Project", "Address", areaID) locationID := createLocation(t, app, "Location Project", "Address", areaID)
flockID := createFlock(t, app, "Flock Summary") flockID := createFlock(t, app, "Flock Summary")
categoryID := createProductCategory(t, app, "DOC Summary", "DOCS")
fcrID := createFcr(t, app, "FCR Summary", []map[string]any{ fcrID := createFcr(t, app, "FCR Summary", []map[string]any{
{"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0}, {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
}) })
kandangID := createKandang(t, app, "Kandang Summary", locationID, 1) kandangID := createKandang(t, app, "Kandang Summary", locationID, 1)
createPayload := map[string]any{ createPayload := map[string]any{
"flock_id": flockID, "flock_id": flockID,
"area_id": areaID, "area_id": areaID,
"product_category_id": categoryID, "category": "growing",
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationID, "location_id": locationID,
"kandang_ids": []uint{kandangID}, "kandang_ids": []uint{kandangID},
} }
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload) resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
if resp.StatusCode != fiber.StatusCreated { if resp.StatusCode != fiber.StatusCreated {
@@ -40,9 +39,10 @@ func TestProjectFlockSummary(t *testing.T) {
var createResp struct { var createResp struct {
Data struct { Data struct {
Id uint `json:"id"` Id uint `json:"id"`
Period int `json:"period"` Period int `json:"period"`
Flock struct { Category string `json:"category"`
Flock struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} `json:"flock"` } `json:"flock"`
@@ -50,11 +50,6 @@ func TestProjectFlockSummary(t *testing.T) {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} `json:"area"` } `json:"area"`
ProductCategory struct {
Id uint `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
} `json:"product_category"`
Fcr struct { Fcr struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -86,19 +81,27 @@ func TestProjectFlockSummary(t *testing.T) {
if createResp.Data.Area.Id != areaID || createResp.Data.Area.Name == "" { if createResp.Data.Area.Id != areaID || createResp.Data.Area.Name == "" {
t.Fatalf("expected area detail to be present, got %+v", createResp.Data.Area) t.Fatalf("expected area detail to be present, got %+v", createResp.Data.Area)
} }
if createResp.Data.Category != string(utils.ProjectFlockCategoryGrowing) {
t.Fatalf("expected category to be %s, got %s", utils.ProjectFlockCategoryGrowing, createResp.Data.Category)
}
if createResp.Data.Location.Id != locationID || createResp.Data.Location.Name == "" { if createResp.Data.Location.Id != locationID || createResp.Data.Location.Name == "" {
t.Fatalf("expected location detail to be present, got %+v", createResp.Data.Location) t.Fatalf("expected location detail to be present, got %+v", createResp.Data.Location)
} }
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 == "" { if createResp.Data.Kandangs[0].Status != string(utils.KandangStatusPengajuan) {
t.Fatalf("expected kandang status to be present, got %+v", createResp.Data.Kandangs[0]) t.Fatalf("expected kandang status to be PENGAJUAN, got %s", createResp.Data.Kandangs[0].Status)
} }
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)
} }
createdKandang := fetchKandang(t, db, kandangID)
if createdKandang.Status != string(utils.KandangStatusPengajuan) {
t.Fatalf("expected kandang status in DB to be PENGAJUAN, got %s", createdKandang.Status)
}
var pivotRecords []entities.ProjectFlockKandang var pivotRecords []entities.ProjectFlockKandang
if err := db.Where("project_flock_id = ?", createResp.Data.Id).Find(&pivotRecords).Error; err != nil { if err := db.Where("project_flock_id = ?", createResp.Data.Id).Find(&pivotRecords).Error; err != nil {
t.Fatalf("failed to fetch pivot records: %v", err) t.Fatalf("failed to fetch pivot records: %v", err)
@@ -116,12 +119,12 @@ func TestProjectFlockSummary(t *testing.T) {
secondKandangID := createKandang(t, app, "Kandang Summary 2", locationID, 1) secondKandangID := createKandang(t, app, "Kandang Summary 2", locationID, 1)
secondPayload := map[string]any{ secondPayload := map[string]any{
"flock_id": flockID, "flock_id": flockID,
"area_id": areaID, "area_id": areaID,
"product_category_id": categoryID, "category": "laying",
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationID, "location_id": locationID,
"kandang_ids": []uint{secondKandangID}, "kandang_ids": []uint{secondKandangID},
} }
resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", secondPayload) resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", secondPayload)
if resp.StatusCode != fiber.StatusCreated { if resp.StatusCode != fiber.StatusCreated {
@@ -129,8 +132,9 @@ func TestProjectFlockSummary(t *testing.T) {
} }
var createRespSecond struct { var createRespSecond struct {
Data struct { Data struct {
Id uint `json:"id"` Id uint `json:"id"`
Period int `json:"period"` Period int `json:"period"`
Category string `json:"category"`
} `json:"data"` } `json:"data"`
} }
if err := json.Unmarshal(body, &createRespSecond); err != nil { if err := json.Unmarshal(body, &createRespSecond); err != nil {
@@ -139,6 +143,9 @@ func TestProjectFlockSummary(t *testing.T) {
if createRespSecond.Data.Period != 2 { if createRespSecond.Data.Period != 2 {
t.Fatalf("expected second period to be 2, got %d", createRespSecond.Data.Period) t.Fatalf("expected second period to be 2, got %d", createRespSecond.Data.Period)
} }
if createRespSecond.Data.Category != string(utils.ProjectFlockCategoryLaying) {
t.Fatalf("expected category to be %s, got %s", utils.ProjectFlockCategoryLaying, createRespSecond.Data.Category)
}
pivotRecords = nil pivotRecords = nil
if err := db.Where("project_flock_id = ?", createRespSecond.Data.Id).Find(&pivotRecords).Error; err != nil { if err := db.Where("project_flock_id = ?", createRespSecond.Data.Id).Find(&pivotRecords).Error; err != nil {
@@ -155,6 +162,11 @@ func TestProjectFlockSummary(t *testing.T) {
t.Fatalf("expected second pivot DetachedAt to be nil, got %v", secondPivotRecord.DetachedAt) t.Fatalf("expected second pivot DetachedAt to be nil, got %v", secondPivotRecord.DetachedAt)
} }
secondKandang := fetchKandang(t, db, secondKandangID)
if secondKandang.Status != string(utils.KandangStatusPengajuan) {
t.Fatalf("expected second kandang status in DB to be PENGAJUAN, got %s", secondKandang.Status)
}
resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil) resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
if resp.StatusCode != fiber.StatusOK { if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching summary, got %d: %s", resp.StatusCode, string(body)) t.Fatalf("expected 200 when fetching summary, got %d: %s", resp.StatusCode, string(body))
@@ -202,7 +214,7 @@ func TestProjectFlockSummary(t *testing.T) {
t.Fatalf("expected 200 when deleting second project flock, got %d: %s", resp.StatusCode, string(body)) t.Fatalf("expected 200 when deleting second project flock, got %d: %s", resp.StatusCode, string(body))
} }
secondKandang := fetchKandang(t, db, secondKandangID) secondKandang = fetchKandang(t, db, secondKandangID)
if secondKandang.ProjectFlockId != nil { if secondKandang.ProjectFlockId != nil {
t.Fatalf("expected second project_flock_id to be nil after delete, got %v", *secondKandang.ProjectFlockId) t.Fatalf("expected second project_flock_id to be nil after delete, got %v", *secondKandang.ProjectFlockId)
} }
@@ -245,19 +257,18 @@ func TestProjectFlockSearchByRelatedFields(t *testing.T) {
areaID := createArea(t, app, "Area Search Target") areaID := createArea(t, app, "Area Search Target")
locationID := createLocation(t, app, "Location Search Target", "Location Address Target", areaID) locationID := createLocation(t, app, "Location Search Target", "Location Address Target", areaID)
flockID := createFlock(t, app, "Flock Search Target") flockID := createFlock(t, app, "Flock Search Target")
categoryID := createProductCategory(t, app, "Category Search Target", "CATGT")
fcrID := createFcr(t, app, "FCR Search Target", []map[string]any{ fcrID := createFcr(t, app, "FCR Search Target", []map[string]any{
{"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0}, {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
}) })
kandangID := createKandang(t, app, "Kandang Search Target", locationID, 1) kandangID := createKandang(t, app, "Kandang Search Target", locationID, 1)
createPayload := map[string]any{ createPayload := map[string]any{
"flock_id": flockID, "flock_id": flockID,
"area_id": areaID, "area_id": areaID,
"product_category_id": categoryID, "category": "growing",
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationID, "location_id": locationID,
"kandang_ids": []uint{kandangID}, "kandang_ids": []uint{kandangID},
} }
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload) resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
@@ -277,8 +288,8 @@ func TestProjectFlockSearchByRelatedFields(t *testing.T) {
searchTerms := []string{ searchTerms := []string{
"Flock Search Target", "Flock Search Target",
"Area Search Target", "Area Search Target",
"Category Search Target", string(utils.ProjectFlockCategoryGrowing),
"CATGT", "growing",
"FCR Search Target", "FCR Search Target",
"Kandang Search Target", "Kandang Search Target",
"Location Search Target", "Location Search Target",
@@ -329,7 +340,6 @@ func TestProjectFlockSorting(t *testing.T) {
flockOne := createFlock(t, app, "Flock Sort One") flockOne := createFlock(t, app, "Flock Sort One")
flockTwo := createFlock(t, app, "Flock Sort Two") flockTwo := createFlock(t, app, "Flock Sort Two")
categoryID := createProductCategory(t, app, "Category Sort", "CSORT")
fcrID := createFcr(t, app, "FCR Sort", []map[string]any{ fcrID := createFcr(t, app, "FCR Sort", []map[string]any{
{"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0}, {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
}) })
@@ -339,12 +349,12 @@ func TestProjectFlockSorting(t *testing.T) {
kandangThree := createKandang(t, app, "Kandang Sort Three", locationB, 1) kandangThree := createKandang(t, app, "Kandang Sort Three", locationB, 1)
projectOnePayload := map[string]any{ projectOnePayload := map[string]any{
"flock_id": flockOne, "flock_id": flockOne,
"area_id": areaA, "area_id": areaA,
"product_category_id": categoryID, "category": "growing",
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationA, "location_id": locationA,
"kandang_ids": []uint{kandangOne}, "kandang_ids": []uint{kandangOne},
} }
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectOnePayload) resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectOnePayload)
if resp.StatusCode != fiber.StatusCreated { if resp.StatusCode != fiber.StatusCreated {
@@ -353,12 +363,12 @@ func TestProjectFlockSorting(t *testing.T) {
projectOneID := parseProjectFlockID(t, body) projectOneID := parseProjectFlockID(t, body)
projectTwoPayload := map[string]any{ projectTwoPayload := map[string]any{
"flock_id": flockTwo, "flock_id": flockTwo,
"area_id": areaB, "area_id": areaB,
"product_category_id": categoryID, "category": "laying",
"fcr_id": fcrID, "fcr_id": fcrID,
"location_id": locationB, "location_id": locationB,
"kandang_ids": []uint{kandangTwo, kandangThree}, "kandang_ids": []uint{kandangTwo, kandangThree},
} }
resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectTwoPayload) resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectTwoPayload)
if resp.StatusCode != fiber.StatusCreated { if resp.StatusCode != fiber.StatusCreated {