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
@@ -1,21 +1,30 @@
package repository
import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"context"
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gorm.io/gorm"
)
type FlockRepository interface {
repository.BaseRepository[entity.Flock]
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
}
type FlockRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.Flock]
db *gorm.DB
}
func NewFlockRepository(db *gorm.DB) FlockRepository {
return &FlockRepositoryImpl{
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 (
"errors"
"fmt"
"strings"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
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
}
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{
Name: req.Name,
Name: name,
CreatedBy: 1,
}
@@ -100,7 +116,20 @@ func (s flockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (
updateBody := make(map[string]any)
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 {
@@ -4,75 +4,66 @@ import (
"time"
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"
)
type ProjectFlockBaseDTO struct {
Id uint `json:"id"`
// FlockId uint `json:"flock_id"`
// AreaId uint `json:"area_id"`
// ProductCategoryId uint `json:"product_category_id"`
// FcrId uint `json:"fcr_id"`
// LocationId uint `json:"location_id"`
Period int `json:"period"`
Id uint `json:"id"`
Period int `json:"period"`
Category string `json:"category"`
Flock *flockDTO.FlockBaseDTO `json:"flock"`
Area *areaDTO.AreaBaseDTO `json:"area"`
Fcr *fcrDTO.FcrBaseDTO `json:"fcr"`
Location *locationDTO.LocationBaseDTO `json:"location"`
}
func ToProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
return ProjectFlockBaseDTO{
Id: e.Id,
// FlockId: e.FlockId,
// AreaId: e.AreaId,
// ProductCategoryId: e.ProductCategoryId,
// FcrId: e.FcrId,
// LocationId: e.LocationId,
Period: e.Period,
var flock *flockDTO.FlockBaseDTO
if e.Flock.Id != 0 {
mapped := flockDTO.ToFlockBaseDTO(e.Flock)
flock = &mapped
}
}
type FlockSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
var area *areaDTO.AreaBaseDTO
if e.Area.Id != 0 {
mapped := areaDTO.ToAreaBaseDTO(e.Area)
area = &mapped
}
type AreaSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
var fcr *fcrDTO.FcrBaseDTO
if e.Fcr.Id != 0 {
mapped := fcrDTO.ToFcrBaseDTO(e.Fcr)
fcr = &mapped
}
type ProductCategorySummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
}
var location *locationDTO.LocationBaseDTO
if e.Location.Id != 0 {
mapped := locationDTO.ToLocationBaseDTO(e.Location)
location = &mapped
}
type FcrSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
type LocationSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
}
type KandangSummaryDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
return ProjectFlockBaseDTO{
Id: e.Id,
Period: e.Period,
Category: e.Category,
Flock: flock,
Area: area,
Fcr: fcr,
Location: location,
}
}
type ProjectFlockListDTO struct {
ProjectFlockBaseDTO
Flock *FlockSummaryDTO `json:"flock,omitempty"`
Area *AreaSummaryDTO `json:"area,omitempty"`
ProductCategory *ProductCategorySummaryDTO `json:"product_category,omitempty"`
Fcr *FcrSummaryDTO `json:"fcr,omitempty"`
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"`
Kandangs []kandangDTO.KandangBaseDTO `json:"kandangs,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ProjectFlockDetailDTO struct {
@@ -80,8 +71,8 @@ type ProjectFlockDetailDTO struct {
}
type FlockPeriodSummaryDTO struct {
Flock FlockSummaryDTO `json:"flock"`
NextPeriod int `json:"next_period"`
Flock flockDTO.FlockBaseDTO `json:"flock"`
NextPeriod int `json:"next_period"`
}
func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
@@ -91,62 +82,16 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
createdUser = &mapped
}
var flockSummary *FlockSummaryDTO
if e.Flock.Id != 0 {
summary := ToFlockSummaryDTO(e.Flock)
flockSummary = &summary
}
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,
var kandangSummaries []kandangDTO.KandangBaseDTO
if len(e.Kandangs) > 0 {
kandangSummaries = make([]kandangDTO.KandangBaseDTO, len(e.Kandangs))
for i, kandang := range e.Kandangs {
kandangSummaries[i] = kandangDTO.ToKandangBaseDTO(kandang)
}
}
return ProjectFlockListDTO{
ProjectFlockBaseDTO: ToProjectFlockBaseDTO(e),
Flock: flockSummary,
Area: areaSummary,
ProductCategory: categorySummary,
Fcr: fcrSummary,
Location: locationSummary,
Kandangs: kandangSummaries,
CreatedAt: e.CreatedAt,
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 {
return FlockPeriodSummaryDTO{
Flock: ToFlockSummaryDTO(flock),
Flock: flockDTO.ToFlockBaseDTO(flock),
NextPeriod: next,
}
}
@@ -67,7 +67,6 @@ func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
Preload("CreatedUser").
Preload("Flock").
Preload("Area").
Preload("ProductCategory").
Preload("Fcr").
Preload("Location").
Preload("Kandangs")
@@ -115,15 +114,13 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
db = db.
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 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 locations ON locations.id = project_flocks.location_id").
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
Where(`
LOWER(flocks.name) LIKE ?
OR LOWER(areas.name) LIKE ?
OR LOWER(product_categories.name) LIKE ?
OR LOWER(product_categories.code) LIKE ?
OR LOWER(project_flocks.category) LIKE ?
OR LOWER(fcrs.name) LIKE ?
OR LOWER(locations.name) 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,
)
}
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
}
category, ok := utils.NormalizeProjectFlockCategory(req.Category)
if !ok {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
}
if len(req.KandangIds) == 0 {
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(),
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 {
@@ -224,13 +224,13 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
}
createBody := &entity.ProjectFlock{
FlockId: req.FlockId,
AreaId: req.AreaId,
ProductCategoryId: req.ProductCategoryId,
FcrId: req.FcrId,
LocationId: req.LocationId,
Period: nextPeriod,
CreatedBy: 1,
FlockId: req.FlockId,
AreaId: req.AreaId,
Category: string(category),
FcrId: req.FcrId,
LocationId: req.LocationId,
Period: nextPeriod,
CreatedBy: 1,
}
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()),
})
}
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.Category != nil {
if normalized, ok := utils.NormalizeProjectFlockCategory(*req.Category); ok {
updateBody["category"] = string(normalized)
} else {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
}
}
if req.FcrId != nil {
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{}).
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")
}
@@ -1,32 +1,32 @@
package validation
type Create struct {
FlockId uint `json:"flock_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"`
FcrId uint `json:"fcr_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"`
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
AreaId uint `json:"area_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"`
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
}
type Update struct {
FlockId *uint `json:"flock_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"`
FcrId *uint `json:"fcr_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"`
KandangIds []uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,gt=0"`
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
AreaId *uint `json:"area_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"`
LocationId *uint `json:"location_id,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"`
}
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Search string `query:"search" validate:"omitempty,max=50"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=area location kandangs period"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
AreaId uint `query:"area_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"`
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Search string `query:"search" validate:"omitempty,max=50"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=area location kandangs period"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
AreaId uint `query:"area_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"`
KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"`
}