Merge branch 'feat/kandang-periode' into 'development'

[FEAT][BE]: adjust api get all project flock kandang with periode

See merge request mbugroup/lti-api!409
This commit is contained in:
Adnan Zahir
2026-04-13 12:58:23 +07:00
8 changed files with 231 additions and 31 deletions
@@ -55,9 +55,9 @@ func (s areaService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Ar
db = s.withRelations(db)
db, scopeErr = m.ApplyAreaScope(c, db, "id")
if params.Search != "" {
return db.Where("name ILIKE ?", "%"+params.Search+"%")
db = db.Where("name ILIKE ?", "%"+params.Search+"%")
}
return db.Order("created_at DESC").Order("updated_at DESC")
return db.Order("name ASC").Order("id ASC")
})
if scopeErr != nil {
@@ -71,7 +71,7 @@ func (s locationService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
)
`, utils.ProjectFlockCategoryLaying)
}
return db.Order("created_at DESC").Order("updated_at DESC")
return db.Order("locations.name ASC").Order("locations.id ASC")
})
if scopeErr != nil {
@@ -24,22 +24,50 @@ func NewProjectFlockKandangController(projectFlockKandangService service.Project
func (u *ProjectFlockKandangController) GetAll(c *fiber.Ctx) error {
query := &validation.Query{
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""),
ProjectFlockId: uint(c.QueryInt("project_flock_id", 0)),
KandangId: uint(c.QueryInt("kandang_id", 0)),
Category: c.Query("category", ""),
AreaId: uint(c.QueryInt("area_id", 0)),
SortBy: c.Query("sort_by", ""),
SortOrder: c.Query("sort_order", ""),
StepName: c.Query("step_name", ""),
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""),
NameWithPeriode: c.QueryBool("name_with_periode", false),
ProjectFlockId: uint(c.QueryInt("project_flock_id", 0)),
KandangId: uint(c.QueryInt("kandang_id", 0)),
Category: c.Query("category", ""),
AreaId: uint(c.QueryInt("area_id", 0)),
LocationId: uint(c.QueryInt("location_id", 0)),
SortBy: c.Query("sort_by", ""),
SortOrder: c.Query("sort_order", ""),
StepName: c.Query("step_name", ""),
}
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
if query.NameWithPeriode {
results, totalResults, err := u.ProjectFlockKandangService.GetAllNameWithPeriode(c, query)
if err != nil {
return err
}
data := make([]dto.ProjectFlockKandangNameWithPeriodDTO, 0, len(results))
for _, result := range results {
data = append(data, dto.ToProjectFlockKandangNameWithPeriodDTOValues(result.Id, result.KandangName, result.Period))
}
return c.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.ProjectFlockKandangNameWithPeriodDTO]{
Code: fiber.StatusOK,
Status: "success",
Message: "Get all projectFlockKandangs successfully",
Meta: response.Meta{
Page: query.Page,
Limit: query.Limit,
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults,
},
Data: data,
})
}
results, totalResults, err := u.ProjectFlockKandangService.GetAll(c, query)
if err != nil {
return err
@@ -60,6 +60,11 @@ type ProjectFlockKandangListDTO struct {
ChickinApproval *approvalDTO.ApprovalRelationDTO `json:"chickin_approval,omitempty"`
}
type ProjectFlockKandangNameWithPeriodDTO struct {
Id uint `json:"id"`
NameWithPeriod string `json:"name_with_period"`
}
type ProjectFlockKandangDetailDTO struct {
ProjectFlockKandangListDTO
Chickins []chickinDTO.ChickinRelationDTO `json:"chickins,omitempty"`
@@ -129,13 +134,17 @@ func toKandangRelation(kandang entity.Kandang) *kandangDTO.KandangRelationDTO {
}
func toNameWithPeriod(kandang entity.Kandang, period int) string {
if kandang.Name == "" {
return toNameWithPeriodValue(kandang.Name, period)
}
func toNameWithPeriodValue(kandangName string, period int) string {
if kandangName == "" {
return ""
}
if period == 0 {
return kandang.Name
return kandangName
}
return kandang.Name + " Period " + strconv.Itoa(period)
return kandangName + " Period " + strconv.Itoa(period)
}
func toApprovalDTOSelector(
@@ -167,6 +176,20 @@ func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKand
}
}
func ToProjectFlockKandangNameWithPeriodDTO(e entity.ProjectFlockKandang) ProjectFlockKandangNameWithPeriodDTO {
return ProjectFlockKandangNameWithPeriodDTO{
Id: e.Id,
NameWithPeriod: toNameWithPeriod(e.Kandang, e.Period),
}
}
func ToProjectFlockKandangNameWithPeriodDTOValues(id uint, kandangName string, period int) ProjectFlockKandangNameWithPeriodDTO {
return ProjectFlockKandangNameWithPeriodDTO{
Id: id,
NameWithPeriod: toNameWithPeriodValue(kandangName, period),
}
}
func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserRelationDTO {
if pf.CreatedUser.Id != 0 {
mapped := userDTO.ToUserRelationDTO(pf.CreatedUser)
@@ -26,6 +26,7 @@ import (
type ProjectFlockKandangService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
GetAllNameWithPeriode(ctx *fiber.Ctx, params *validation.Query) ([]ProjectFlockKandangNameWithPeriode, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error)
CheckClosing(ctx *fiber.Ctx, id uint) (*ClosingCheckResult, error)
Closing(ctx *fiber.Ctx, id uint, req *validation.Closing) (*entity.ProjectFlockKandang, error)
@@ -51,6 +52,12 @@ type ClosingCheckResult struct {
Expenses []ExpenseSummary `json:"expenses"`
}
type ProjectFlockKandangNameWithPeriode struct {
Id uint
KandangName string
Period int
}
type StockRemainingDetail struct {
FlagName string `json:"flag_name"`
ProductWarehouseId uint `json:"product_warehouse_id"`
@@ -133,6 +140,36 @@ func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Quer
return projectFlockKandangs, total, nil
}
func (s projectFlockKandangService) GetAllNameWithPeriode(c *fiber.Ctx, params *validation.Query) ([]ProjectFlockKandangNameWithPeriode, int64, error) {
if err := s.Validate.Struct(params); err != nil {
return nil, 0, err
}
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
if err != nil {
return nil, 0, err
}
offset := (params.Page - 1) * params.Limit
rows, total, err := s.Repository.GetAllNameWithPeriodeScoped(c.Context(), offset, params.Limit, params, scope.IDs, scope.Restrict)
if err != nil {
s.Log.Errorf("Failed to get projectFlockKandangs name_with_periode: %+v", err)
return nil, 0, err
}
results := make([]ProjectFlockKandangNameWithPeriode, 0, len(rows))
for _, row := range rows {
results = append(results, ProjectFlockKandangNameWithPeriode{
Id: row.Id,
KandangName: row.KandangName,
Period: row.Period,
})
}
return results, total, nil
}
func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error) {
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
if err != nil {
@@ -11,19 +11,21 @@ type Update struct {
}
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty,max=50"`
ProjectFlockId uint `query:"project_flock_id" validate:"omitempty"`
KandangId uint `query:"kandang_id" validate:"omitempty"`
Category string `query:"category" validate:"omitempty,oneof=Growing Laying"`
AreaId uint `query:"area_id" validate:"omitempty"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=created_at period"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=ASC DESC"`
StepName string `query:"step_name" validate:"omitempty,max=50"`
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty,max=50"`
NameWithPeriode bool `query:"name_with_periode"`
ProjectFlockId uint `query:"project_flock_id" validate:"omitempty"`
KandangId uint `query:"kandang_id" validate:"omitempty"`
Category string `query:"category" validate:"omitempty,oneof=Growing Laying"`
AreaId uint `query:"area_id" validate:"omitempty"`
LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=created_at period"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=ASC DESC"`
StepName string `query:"step_name" validate:"omitempty,max=50"`
}
type Closing struct {
Action string `json:"action" validate:"required,oneof=close unclose"`
ClosedDate *string `json:"closed_date,omitempty"`
}
}
@@ -322,8 +322,8 @@ func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder str
}
default:
return []string{
"project_flocks.created_at DESC",
"project_flocks.updated_at DESC",
"project_flocks.flock_name ASC",
"project_flocks.id ASC",
}
}
}
@@ -22,6 +22,7 @@ type ProjectFlockKandangRepository interface {
GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error)
GetAllWithFilters(ctx context.Context, offset int, limit int, params interface{}) ([]entity.ProjectFlockKandang, int64, error)
GetAllWithFiltersScoped(ctx context.Context, offset int, limit int, params interface{}, locationIDs []uint, restrict bool) ([]entity.ProjectFlockKandang, int64, error)
GetAllNameWithPeriodeScoped(ctx context.Context, offset int, limit int, params *validation.Query, locationIDs []uint, restrict bool) ([]ProjectFlockKandangNameWithPeriode, int64, error)
GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ProjectFlockKandang, error)
ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error)
HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error)
@@ -40,6 +41,12 @@ type projectFlockKandangRepositoryImpl struct {
db *gorm.DB
}
type ProjectFlockKandangNameWithPeriode struct {
Id uint `gorm:"column:id"`
KandangName string `gorm:"column:kandang_name"`
Period int `gorm:"column:period"`
}
const flockBaseNameExpression = "LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')))"
func NewProjectFlockKandangRepository(db *gorm.DB) ProjectFlockKandangRepository {
@@ -171,13 +178,17 @@ func (r *projectFlockKandangRepositoryImpl) GetAllWithFilters(ctx context.Contex
if query.AreaId > 0 {
q = q.Where("\"project_flocks\".\"area_id\" = ?", query.AreaId)
}
if query.LocationId > 0 {
q = q.Where("\"kandangs\".\"location_id\" = ?", query.LocationId)
}
}
if err := q.Model(&entity.ProjectFlockKandang{}).Count(&total).Error; err != nil {
return nil, 0, err
}
sortBy := "\"project_flock_kandangs\".\"created_at\" DESC"
sortBy := "\"project_flock_kandangs\".\"id\" ASC"
if ok && query != nil && query.SortBy != "" {
sortOrder := "DESC"
if query.SortOrder == "ASC" {
@@ -269,13 +280,17 @@ func (r *projectFlockKandangRepositoryImpl) GetAllWithFiltersScoped(ctx context.
if query.AreaId > 0 {
q = q.Where("\"project_flocks\".\"area_id\" = ?", query.AreaId)
}
if query.LocationId > 0 {
q = q.Where("\"kandangs\".\"location_id\" = ?", query.LocationId)
}
}
if err := q.Model(&entity.ProjectFlockKandang{}).Count(&total).Error; err != nil {
return nil, 0, err
}
sortBy := "\"project_flock_kandangs\".\"created_at\" DESC"
sortBy := "\"project_flock_kandangs\".\"id\" ASC"
if ok && query != nil && query.SortBy != "" {
sortOrder := "DESC"
if query.SortOrder == "ASC" {
@@ -297,6 +312,101 @@ func (r *projectFlockKandangRepositoryImpl) GetAllWithFiltersScoped(ctx context.
return records, total, nil
}
func (r *projectFlockKandangRepositoryImpl) GetAllNameWithPeriodeScoped(ctx context.Context, offset int, limit int, params *validation.Query, locationIDs []uint, restrict bool) ([]ProjectFlockKandangNameWithPeriode, int64, error) {
var records []ProjectFlockKandangNameWithPeriode
var total int64
q := r.db.WithContext(ctx).
Table("project_flock_kandangs").
Joins("JOIN \"kandangs\" ON \"project_flock_kandangs\".\"kandang_id\" = \"kandangs\".\"id\"").
Joins("JOIN \"project_flocks\" ON \"project_flock_kandangs\".\"project_flock_id\" = \"project_flocks\".\"id\"")
if restrict {
if len(locationIDs) == 0 {
return []ProjectFlockKandangNameWithPeriode{}, 0, nil
}
q = q.Where("\"project_flocks\".\"location_id\" IN ?", locationIDs)
}
if params != nil && params.StepName != "" {
q = q.Where(`
EXISTS (
SELECT 1 FROM "approvals"
WHERE "approvals"."approvable_id" = "project_flock_kandangs"."id"
AND "approvals"."approvable_type" = ?
AND LOWER("approvals"."step_name") = LOWER(?)
AND "approvals"."id" IN (
SELECT "approvals"."id" FROM "approvals"
WHERE "approvals"."approvable_id" = "project_flock_kandangs"."id"
AND "approvals"."approvable_type" = ?
ORDER BY "approvals"."id" DESC
LIMIT 1
)
)
`, "PROJECT_FLOCK_KANDANGS", params.StepName, "PROJECT_FLOCK_KANDANGS")
}
if params != nil {
if params.Search != "" {
escapedSearch := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(params.Search)
q = q.Where(
r.db.Where("LOWER(\"kandangs\".\"name\") LIKE LOWER(?) ESCAPE '\\'", "%"+escapedSearch+"%").
Or("LOWER(\"project_flocks\".\"flock_name\") LIKE LOWER(?) ESCAPE '\\'", "%"+escapedSearch+"%"),
)
}
if params.ProjectFlockId > 0 {
q = q.Where("\"project_flock_kandangs\".\"project_flock_id\" = ?", params.ProjectFlockId)
}
if params.KandangId > 0 {
q = q.Where("\"project_flock_kandangs\".\"kandang_id\" = ?", params.KandangId)
}
if params.Category != "" {
q = q.Where("\"project_flocks\".\"category\" = ?", params.Category)
}
if params.AreaId > 0 {
q = q.Where("\"project_flocks\".\"area_id\" = ?", params.AreaId)
}
if params.LocationId > 0 {
q = q.Where("\"kandangs\".\"location_id\" = ?", params.LocationId)
}
}
if err := q.Count(&total).Error; err != nil {
return nil, 0, err
}
sortBy := "\"project_flock_kandangs\".\"created_at\" DESC"
if params != nil && params.SortBy != "" {
sortOrder := "DESC"
if params.SortOrder == "ASC" {
sortOrder = "ASC"
}
switch params.SortBy {
case "created_at":
sortBy = "\"project_flock_kandangs\".\"created_at\" " + sortOrder
case "period":
sortBy = "\"project_flocks\".\"period\" " + sortOrder
}
}
if err := q.
Select("\"project_flock_kandangs\".\"id\", \"project_flock_kandangs\".\"period\", \"kandangs\".\"name\" AS kandang_name").
Order(sortBy).
Offset(offset).
Limit(limit).
Scan(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
func (r *projectFlockKandangRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockKandangRepository {
return &projectFlockKandangRepositoryImpl{db: tx}
}