mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/Sprint-5' into 'feat/BE/US-159,160/marketing'
# Conflicts: # internal/modules/production/project_flocks/controllers/projectflock.controller.go # internal/modules/production/project_flocks/dto/projectflock.dto.go # internal/modules/production/project_flocks/route.go # internal/modules/production/transfer_layings/dto/transfer_laying.dto.go
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
DROP COLUMN IF EXISTS period;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
ADD COLUMN IF NOT EXISTS period INT NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS project_flocks_base_period_unique
|
||||||
|
ON project_flocks (
|
||||||
|
LOWER(TRIM(regexp_replace(flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))),
|
||||||
|
period
|
||||||
|
)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
ADD COLUMN IF NOT EXISTS period INT;
|
||||||
|
|
||||||
|
UPDATE project_flock_kandangs pfk
|
||||||
|
SET period = pf.period
|
||||||
|
FROM project_flocks pf
|
||||||
|
WHERE pfk.project_flock_id = pf.id
|
||||||
|
AND (pfk.period IS NULL OR pfk.period = 0)
|
||||||
|
AND pf.period IS NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
ALTER COLUMN period SET DEFAULT 0;
|
||||||
|
|
||||||
|
UPDATE project_flock_kandangs
|
||||||
|
SET period = 0
|
||||||
|
WHERE period IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE project_flock_kandangs
|
||||||
|
ALTER COLUMN period SET NOT NULL;
|
||||||
|
|
||||||
|
-- Drop period from project_flocks as the source of truth
|
||||||
|
DROP INDEX IF EXISTS project_flocks_base_period_unique;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
DROP COLUMN IF EXISTS period;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -16,6 +16,7 @@ type Location struct {
|
|||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||||
|
Kandangs []Kandang `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ type ProjectFlock struct {
|
|||||||
Category string `gorm:"type:varchar(20);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"`
|
|
||||||
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"`
|
||||||
@@ -28,4 +27,3 @@ type ProjectFlock struct {
|
|||||||
|
|
||||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type ProjectFlockKandang struct {
|
|||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
|
Period int `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
|
|||||||
@@ -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"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -120,9 +121,41 @@ func (r *KandangRepositoryImpl) UpsertProjectFlockKandang(ctx context.Context, p
|
|||||||
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
||||||
First(&link).Error
|
First(&link).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
var project entity.ProjectFlock
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Select("id, location_id").
|
||||||
|
First(&project, projectFlockID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kandang entity.Kandang
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Select("id, location_id").
|
||||||
|
First(&kandang, kandangID).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if kandang.LocationId != project.LocationId {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Kandang tidak berada pada lokasi yang sama dengan project flock")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine project's period from existing pivot rows so the new kandang
|
||||||
|
// shares the same period.
|
||||||
|
var period int
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("project_flock_id = ?", projectFlockID).
|
||||||
|
Select("COALESCE(MAX(period), 0)").
|
||||||
|
Scan(&period).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if period <= 0 {
|
||||||
|
period = 1
|
||||||
|
}
|
||||||
|
|
||||||
link = entity.ProjectFlockKandang{
|
link = entity.ProjectFlockKandang{
|
||||||
ProjectFlockId: projectFlockID,
|
ProjectFlockId: projectFlockID,
|
||||||
KandangId: kandangID,
|
KandangId: kandangID,
|
||||||
|
Period: period,
|
||||||
}
|
}
|
||||||
return r.db.WithContext(ctx).Create(&link).Error
|
return r.db.WithContext(ctx).Create(&link).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ func ToUserBaseDTO(e entity.User) userBaseDTO.UserBaseDTO {
|
|||||||
return userBaseDTO.ToUserBaseDTO(e)
|
return userBaseDTO.ToUserBaseDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
||||||
|
e := pfk.ProjectFlock
|
||||||
var flock *flockBaseDTO.FlockBaseDTO
|
var flock *flockBaseDTO.FlockBaseDTO
|
||||||
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
||||||
summary := flockBaseDTO.FlockBaseDTO{Id: 0, Name: base}
|
summary := flockBaseDTO.FlockBaseDTO{Id: 0, Name: base}
|
||||||
@@ -132,7 +133,7 @@ func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
|||||||
}
|
}
|
||||||
return ProjectFlockDTO{
|
return ProjectFlockDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Period: e.Period,
|
Period: pfk.Period,
|
||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
Flock: flock,
|
Flock: flock,
|
||||||
Area: area,
|
Area: area,
|
||||||
@@ -144,7 +145,7 @@ func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
|||||||
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
||||||
var pf *ProjectFlockDTO
|
var pf *ProjectFlockDTO
|
||||||
if e.ProjectFlock.Id != 0 {
|
if e.ProjectFlock.Id != 0 {
|
||||||
mapped := ToProjectFlockDTO(e.ProjectFlock)
|
mapped := ToProjectFlockDTO(e)
|
||||||
pf = &mapped
|
pf = &mapped
|
||||||
}
|
}
|
||||||
var kandang *kandangBaseDTO.KandangBaseDTO
|
var kandang *kandangBaseDTO.KandangBaseDTO
|
||||||
|
|||||||
@@ -90,13 +90,15 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make([]dto.ProjectFlockListDTO, 0)
|
var periodMap map[uint]int
|
||||||
for _, projectFlock := range result {
|
if len(result) > 0 {
|
||||||
var flock *flockDTO.FlockBaseDTO
|
ids := make([]uint, len(result))
|
||||||
if flockMap != nil {
|
for i, item := range result {
|
||||||
flock = flockMap[projectFlock.Id]
|
ids[i] = item.Id
|
||||||
|
}
|
||||||
|
if periods, err := u.ProjectflockService.GetProjectPeriods(c, ids); err == nil {
|
||||||
|
periodMap = periods
|
||||||
}
|
}
|
||||||
data = append(data, dto.ToProjectFlockListDTO(projectFlock, flock))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
@@ -110,7 +112,7 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
|||||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
TotalResults: totalResults,
|
TotalResults: totalResults,
|
||||||
},
|
},
|
||||||
Data: data,
|
Data: dto.ToProjectFlockListDTOsWithPeriods(result, periodMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,12 +129,19 @@ func (u *ProjectflockController) GetOne(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var period int
|
||||||
|
if periods, err := u.ProjectflockService.GetProjectPeriods(c, []uint{uint(id)}); err == nil {
|
||||||
|
if p, ok := periods[uint(id)]; ok {
|
||||||
|
period = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.Success{
|
JSON(response.Success{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get projectflock successfully",
|
Message: "Get projectflock successfully",
|
||||||
Data: dto.ToProjectFlockListDTO(*result, flock),
|
Data: dto.ToProjectFlockListDTOWithPeriod(*result, period),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,11 +228,29 @@ func (u *ProjectflockController) Approval(c *fiber.Ctx) error {
|
|||||||
data interface{}
|
data interface{}
|
||||||
message = "Submit projectflock approval successfully"
|
message = "Submit projectflock approval successfully"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var periodMap map[uint]int
|
||||||
|
if len(results) > 0 {
|
||||||
|
ids := make([]uint, len(results))
|
||||||
|
for i, item := range results {
|
||||||
|
ids[i] = item.Id
|
||||||
|
}
|
||||||
|
if periods, err := u.ProjectflockService.GetProjectPeriods(c, ids); err == nil {
|
||||||
|
periodMap = periods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(results) == 1 {
|
if len(results) == 1 {
|
||||||
data = dto.ToProjectFlockListDTO(results[0], nil)
|
period := 0
|
||||||
|
if periodMap != nil {
|
||||||
|
if p, ok := periodMap[results[0].Id]; ok {
|
||||||
|
period = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = dto.ToProjectFlockListDTOWithPeriod(results[0], period)
|
||||||
} else {
|
} else {
|
||||||
message = "Submit projectflock approvals successfully"
|
message = "Submit projectflock approvals successfully"
|
||||||
data = dto.ToProjectFlockListDTOs(results)
|
data = dto.ToProjectFlockListDTOsWithPeriods(results, periodMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
@@ -236,25 +263,32 @@ func (u *ProjectflockController) Approval(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
||||||
param := c.Params("project_flock_kandang_id")
|
param := c.Params("location_id")
|
||||||
|
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.Atoi(param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_kandang_id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id))
|
summaries, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
responseBody := dto.ToFlockPeriodSummaryDTO(summary.Flock, summary.NextPeriod)
|
responseBody := make([]dto.KandangPeriodSummaryDTO, 0, len(summaries))
|
||||||
|
for _, item := range summaries {
|
||||||
|
responseBody = append(responseBody, dto.KandangPeriodSummaryDTO{
|
||||||
|
Id: item.Id,
|
||||||
|
Name: item.Name,
|
||||||
|
Period: item.Period,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.Success{
|
JSON(response.Success{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get flock period summary successfully",
|
Message: "Get kandang period summary successfully",
|
||||||
Data: responseBody,
|
Data: responseBody,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ type FlockPeriodDTO struct {
|
|||||||
NextPeriod int `json:"next_period"`
|
NextPeriod int `json:"next_period"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProjectFlockListDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockListDTO {
|
type KandangPeriodSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectFlockListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserBaseDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||||
@@ -107,17 +113,17 @@ func ToProjectFlockListDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ProjectFlockListDTO{
|
return ProjectFlockListDTO{
|
||||||
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
|
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e, period),
|
||||||
Flock: flockSummary,
|
// Flock: flockSummary,
|
||||||
Area: areaSummary,
|
Area: areaSummary,
|
||||||
Kandangs: kandangSummaries,
|
Kandangs: kandangSummaries,
|
||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
Fcr: fcrSummary,
|
Fcr: fcrSummary,
|
||||||
Location: locationSummary,
|
Location: locationSummary,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
Approval: latestApproval,
|
Approval: latestApproval,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +134,25 @@ func ToProjectFlockListDTOWithFlock(e entity.ProjectFlock, flock *flockDTO.Flock
|
|||||||
func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO {
|
func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO {
|
||||||
result := make([]ProjectFlockListDTO, len(items))
|
result := make([]ProjectFlockListDTO, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
result[i] = ToProjectFlockListDTO(item, nil)
|
result[i] = ToProjectFlockListDTOWithPeriod(item, 0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
|
||||||
|
return ToProjectFlockListDTOWithPeriod(e, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockListDTOsWithPeriods(items []entity.ProjectFlock, periods map[uint]int) []ProjectFlockListDTO {
|
||||||
|
result := make([]ProjectFlockListDTO, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
p := 0
|
||||||
|
if periods != nil {
|
||||||
|
if v, ok := periods[item.Id]; ok {
|
||||||
|
p = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[i] = ToProjectFlockListDTOWithPeriod(item, p)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -152,7 +176,7 @@ func ToProjectFlockListDTOsWithFlocks(items []entity.ProjectFlock, flocks map[ui
|
|||||||
|
|
||||||
func ToProjectFlockDetailDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockDetailDTO {
|
func ToProjectFlockDetailDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockDetailDTO {
|
||||||
return ProjectFlockDetailDTO{
|
return ProjectFlockDetailDTO{
|
||||||
ProjectFlockListDTO: ToProjectFlockListDTO(e, flock),
|
ProjectFlockListDTO: ToProjectFlockListDTOWithPeriod(e, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,10 +205,10 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func createProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
|
func createProjectFlockBaseDTO(e entity.ProjectFlock, period int) ProjectFlockBaseDTO {
|
||||||
return ProjectFlockBaseDTO{
|
return ProjectFlockBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Period: e.Period,
|
Period: period,
|
||||||
FlockName: e.FlockName,
|
FlockName: e.FlockName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
pfLocal := ProjectFlockWithPivotDTO{
|
pfLocal := ProjectFlockWithPivotDTO{
|
||||||
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
|
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
|
||||||
Id: e.ProjectFlock.Id,
|
Id: e.ProjectFlock.Id,
|
||||||
Period: e.ProjectFlock.Period,
|
Period: e.Period,
|
||||||
FlockName: e.ProjectFlock.FlockName,
|
FlockName: e.ProjectFlock.FlockName,
|
||||||
},
|
},
|
||||||
Category: e.ProjectFlock.Category,
|
Category: e.ProjectFlock.Category,
|
||||||
|
|||||||
+15
-69
@@ -2,7 +2,6 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -10,17 +9,12 @@ import (
|
|||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const baseNameExpression = "LOWER(TRIM(regexp_replace(flock_name, '\\\\s+\\\\d+(\\\\s+\\\\d+)*$', '', 'g')))"
|
const baseNameExpression = "LOWER(TRIM(regexp_replace(flock_name, '\\\\s+\\\\d+(\\\\s+\\\\d+)*$', '', 'g')))"
|
||||||
|
|
||||||
type ProjectflockRepository interface {
|
type ProjectflockRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectFlock]
|
repository.BaseRepository[entity.ProjectFlock]
|
||||||
GetAllByBaseName(ctx context.Context, baseName string) ([]entity.ProjectFlock, error)
|
|
||||||
GetActiveByBaseName(ctx context.Context, baseName string) (*entity.ProjectFlock, error)
|
|
||||||
GetMaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
|
||||||
GetNextSequenceForBase(ctx context.Context, baseName string) (int, error)
|
|
||||||
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
||||||
WithDefaultRelations() func(*gorm.DB) *gorm.DB
|
WithDefaultRelations() func(*gorm.DB) *gorm.DB
|
||||||
ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error)
|
ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error)
|
||||||
@@ -39,65 +33,6 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetAllByBaseName(ctx context.Context, baseName string) ([]entity.ProjectFlock, error) {
|
|
||||||
var records []entity.ProjectFlock
|
|
||||||
if err := r.DB().WithContext(ctx).
|
|
||||||
Unscoped().
|
|
||||||
Where(baseNameExpression+" = LOWER(?)", baseName).
|
|
||||||
Order("period ASC").
|
|
||||||
Find(&records).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return records, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetActiveByBaseName(ctx context.Context, baseName string) (*entity.ProjectFlock, error) {
|
|
||||||
var record entity.ProjectFlock
|
|
||||||
err := r.DB().WithContext(ctx).
|
|
||||||
Where(baseNameExpression+" = LOWER(?)", baseName).
|
|
||||||
Order("period DESC").
|
|
||||||
First(&record).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetMaxPeriodByBaseName(ctx context.Context, baseName string) (int, error) {
|
|
||||||
var max int
|
|
||||||
if err := r.DB().WithContext(ctx).
|
|
||||||
Model(&entity.ProjectFlock{}).
|
|
||||||
Where(baseNameExpression+" = LOWER(?)", baseName).
|
|
||||||
Select("COALESCE(MAX(period), 0)").
|
|
||||||
Scan(&max).Error; err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return max, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetNextSequenceForBase(ctx context.Context, baseName string) (int, error) {
|
|
||||||
var payload struct {
|
|
||||||
Period int
|
|
||||||
}
|
|
||||||
if err := r.DB().WithContext(ctx).
|
|
||||||
Model(&entity.ProjectFlock{}).
|
|
||||||
Where(baseNameExpression+" = LOWER(?)", baseName).
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||||
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
|
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = r.withDefaultRelations(db)
|
db = r.withDefaultRelations(db)
|
||||||
@@ -137,7 +72,13 @@ func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *vali
|
|||||||
db = db.Where("project_flocks.location_id = ?", params.LocationId)
|
db = db.Where("project_flocks.location_id = ?", params.LocationId)
|
||||||
}
|
}
|
||||||
if params.Period > 0 {
|
if params.Period > 0 {
|
||||||
db = db.Where("project_flocks.period = ?", params.Period)
|
db = db.Where(`
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM project_flock_kandangs pfk
|
||||||
|
WHERE pfk.project_flock_id = project_flocks.id
|
||||||
|
AND pfk.period = ?
|
||||||
|
)`, params.Period)
|
||||||
}
|
}
|
||||||
if len(params.KandangIds) > 0 {
|
if len(params.KandangIds) > 0 {
|
||||||
db = db.Where(`
|
db = db.Where(`
|
||||||
@@ -184,10 +125,15 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
|||||||
OR LOWER(created_users.email) LIKE ?
|
OR LOWER(created_users.email) LIKE ?
|
||||||
OR LOWER(project_flocks.flock_name) LIKE ?
|
OR LOWER(project_flocks.flock_name) LIKE ?
|
||||||
OR LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))) LIKE ?
|
OR LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))) LIKE ?
|
||||||
OR LOWER(CAST(project_flocks.period AS TEXT)) LIKE ?
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM project_flock_kandangs
|
||||||
|
WHERE project_flock_kandangs.project_flock_id = project_flocks.id
|
||||||
|
AND LOWER(CAST(project_flock_kandangs.period AS TEXT)) LIKE ?
|
||||||
|
)
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1 FROM kandangs
|
SELECT 1 FROM kandangs
|
||||||
WHERE kandangs.project_flock_id = project_flocks.id
|
JOIN project_flock_kandangs pfk ON pfk.kandang_id = kandangs.id
|
||||||
|
WHERE pfk.project_flock_id = project_flocks.id
|
||||||
AND LOWER(kandangs.name) LIKE ?
|
AND LOWER(kandangs.name) LIKE ?
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
@@ -241,7 +187,7 @@ func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder str
|
|||||||
}
|
}
|
||||||
case "period":
|
case "period":
|
||||||
return []string{
|
return []string{
|
||||||
fmt.Sprintf("project_flocks.period %s", direction),
|
fmt.Sprintf("(SELECT COALESCE(MAX(period), 0) FROM project_flock_kandangs pfk WHERE pfk.project_flock_id = project_flocks.id) %s", direction),
|
||||||
fmt.Sprintf("project_flocks.id %s", direction),
|
fmt.Sprintf("project_flocks.id %s", direction),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
+28
-1
@@ -21,6 +21,7 @@ type ProjectFlockKandangRepository interface {
|
|||||||
HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error)
|
HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error)
|
||||||
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
||||||
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||||
|
ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error)
|
||||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
DB() *gorm.DB
|
DB() *gorm.DB
|
||||||
@@ -274,7 +275,33 @@ func (r *projectFlockKandangRepositoryImpl) MaxPeriodByBaseName(ctx context.Cont
|
|||||||
Table("project_flock_kandangs pfk").
|
Table("project_flock_kandangs pfk").
|
||||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
Where(flockBaseNameExpression+" = LOWER(?)", baseName).
|
Where(flockBaseNameExpression+" = LOWER(?)", baseName).
|
||||||
Select("COALESCE(MAX(pf.period), 0)").
|
Select("COALESCE(MAX(pfk.period), 0)").
|
||||||
Scan(&max).Error
|
Scan(&max).Error
|
||||||
return max, err
|
return max, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error) {
|
||||||
|
result := make(map[uint]int)
|
||||||
|
if len(projectIDs) == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type row struct {
|
||||||
|
ProjectFlockID uint
|
||||||
|
Period int
|
||||||
|
}
|
||||||
|
var rows []row
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("project_flock_id IN ?", projectIDs).
|
||||||
|
Select("project_flock_id, COALESCE(MAX(period), 0) AS period").
|
||||||
|
Group("project_flock_id").
|
||||||
|
Scan(&rows).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range rows {
|
||||||
|
result[item.ProjectFlockID] = item.Period
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package project_flocks
|
package project_flocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers"
|
||||||
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,13 +13,7 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
|
|||||||
ctrl := controller.NewProjectflockController(s)
|
ctrl := controller.NewProjectflockController(s)
|
||||||
|
|
||||||
route := v1.Group("/project-flocks")
|
route := v1.Group("/project-flocks")
|
||||||
|
// route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
route.Use(m.Auth(u))
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
@@ -29,6 +23,6 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
|
|||||||
route.Get("/kandangs/:project_flock_kandang_id/periods", ctrl.GetFlockPeriodSummary)
|
route.Get("/kandangs/:project_flock_kandang_id/periods", ctrl.GetFlockPeriodSummary)
|
||||||
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
|
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
|
||||||
route.Post("/approvals", ctrl.Approval)
|
route.Post("/approvals", ctrl.Approval)
|
||||||
route.Get("/kandangs/:project_flock_kandang_id/periods", ctrl.GetFlockPeriodSummary)
|
route.Get("/kandangs/:location_id/periods", ctrl.GetFlockPeriodSummary)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ type ProjectflockService interface {
|
|||||||
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
|
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
|
||||||
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error)
|
GetFlockPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error)
|
||||||
|
GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, error)
|
||||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +54,10 @@ type projectflockService struct {
|
|||||||
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockPeriodSummary struct {
|
type KandangPeriodSummary struct {
|
||||||
Flock entity.Flock
|
Id uint
|
||||||
NextPeriod int
|
Name string
|
||||||
|
Period int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProjectflockService(
|
func NewProjectflockService(
|
||||||
@@ -283,6 +285,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
if len(kandangs) != len(kandangIDs) {
|
if len(kandangs) != len(kandangIDs) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
}
|
}
|
||||||
|
for _, kandang := range kandangs {
|
||||||
|
if kandang.LocationId != req.LocationId {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d tidak berada pada lokasi yang sama dengan project flock", kandang.Id))
|
||||||
|
}
|
||||||
|
}
|
||||||
// larang kalau ada yg sudah terikat ke project lain
|
// larang kalau ada yg sudah terikat ke project lain
|
||||||
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), kandangIDs, nil); err != nil {
|
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), kandangIDs, nil); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||||
@@ -301,22 +308,24 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
||||||
|
|
||||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), canonicalBase)
|
// Generate unique flock name (sequential per base name, starting from 1)
|
||||||
if err != nil {
|
generatedName, _, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, 1, nil)
|
||||||
return err
|
|
||||||
}
|
|
||||||
generatedName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, nextSeq, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
createBody.FlockName = generatedName
|
createBody.FlockName = generatedName
|
||||||
createBody.Period = seq
|
|
||||||
|
|
||||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.attachKandangs(c.Context(), dbTransaction, createBody.Id, kandangIDs); err != nil {
|
// Compute period based on location history (max period in that location + 1),
|
||||||
|
// and store it on project_flock_kandangs only.
|
||||||
|
nextPeriod, err := s.nextLocationPeriod(c.Context(), dbTransaction, req.LocationId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.attachKandangs(c.Context(), dbTransaction, createBody.Id, kandangIDs, nextPeriod); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,6 +461,15 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
if len(kandangs) != len(newKandangIDs) {
|
if len(kandangs) != len(newKandangIDs) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
}
|
}
|
||||||
|
targetLocationID := existing.LocationId
|
||||||
|
if req.LocationId != nil && *req.LocationId > 0 {
|
||||||
|
targetLocationID = *req.LocationId
|
||||||
|
}
|
||||||
|
for _, kandang := range kandangs {
|
||||||
|
if kandang.LocationId != targetLocationID {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Kandang %d tidak berada pada lokasi yang sama dengan project flock", kandang.Id))
|
||||||
|
}
|
||||||
|
}
|
||||||
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), newKandangIDs, &id); err != nil {
|
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), newKandangIDs, &id); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||||
} else if linked {
|
} else if linked {
|
||||||
@@ -477,18 +495,11 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
if needFlockNameRegenerate {
|
if needFlockNameRegenerate {
|
||||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), baseForGeneration)
|
newName, _, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, 1, &id)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, nextSeq, &id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
updateBody["flock_name"] = newName
|
updateBody["flock_name"] = newName
|
||||||
if seq != existing.Period {
|
|
||||||
updateBody["period"] = seq
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updateBody) > 0 {
|
if len(updateBody) > 0 {
|
||||||
@@ -532,7 +543,19 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(toAttach) > 0 {
|
if len(toAttach) > 0 {
|
||||||
if err := s.attachKandangs(c.Context(), dbTransaction, id, toAttach); err != nil {
|
var currentPeriod int
|
||||||
|
if err := dbTransaction.WithContext(c.Context()).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("project_flock_id = ?", id).
|
||||||
|
Select("COALESCE(MAX(period), 0)").
|
||||||
|
Scan(¤tPeriod).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if currentPeriod <= 0 {
|
||||||
|
currentPeriod = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.attachKandangs(c.Context(), dbTransaction, id, toAttach, currentPeriod); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,57 +831,90 @@ func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID u
|
|||||||
return total, nil
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, projectFlockKandangID uint) (*FlockPeriodSummary, error) {
|
// nextLocationPeriod computes the next period number for a given location
|
||||||
if projectFlockKandangID == 0 {
|
// based on the maximum period that has ever been used by any kandang in that location.
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
func (s projectflockService) nextLocationPeriod(ctx context.Context, tx *gorm.DB, locationID uint) (int, error) {
|
||||||
|
if locationID == 0 {
|
||||||
|
return 0, fiber.NewError(fiber.StatusBadRequest, "location_id is required to compute period")
|
||||||
}
|
}
|
||||||
|
|
||||||
pivot, err := s.pivotRepo().GetByID(c.Context(), projectFlockKandangID)
|
db := s.Repository.DB()
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if tx != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
|
db = tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var maxPeriod int
|
||||||
|
if err := db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs pfk").
|
||||||
|
Joins("JOIN kandangs k ON k.id = pfk.kandang_id").
|
||||||
|
Where("k.location_id = ?", locationID).
|
||||||
|
Select("COALESCE(MAX(pfk.period), 0)").
|
||||||
|
Scan(&maxPeriod).Error; err != nil {
|
||||||
|
s.Log.Errorf("Failed to compute max period for location %d: %+v", locationID, err)
|
||||||
|
return 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to compute period for location")
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxPeriod + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
|
||||||
|
if len(projectIDs) == 0 {
|
||||||
|
return map[uint]int{}, nil
|
||||||
|
}
|
||||||
|
return s.pivotRepo().ProjectPeriodsByProjectIDs(c.Context(), projectIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error) {
|
||||||
|
if locationID == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "location_id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.Repository.LocationExists(c.Context(), locationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", projectFlockKandangID, err)
|
s.Log.Errorf("Failed to validate location %d: %+v", locationID, err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate location")
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Location not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseName string
|
type kandangPeriodRow struct {
|
||||||
var referenceFlock *entity.Flock
|
Id uint
|
||||||
if pivot.ProjectFlock.Id != 0 {
|
Name string
|
||||||
baseName = pfutils.DeriveBaseName(pivot.ProjectFlock.FlockName)
|
LatestPeriod int
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(baseName) != "" {
|
var rows []kandangPeriodRow
|
||||||
referenceFlock, err = s.FlockRepo.GetByName(c.Context(), baseName)
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
db := s.Repository.DB().WithContext(c.Context())
|
||||||
s.Log.Errorf("Failed to fetch flock %q: %+v", baseName, err)
|
if err := db.
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
Table("kandangs AS k").
|
||||||
}
|
Select("k.id, k.name, COALESCE(MAX(pfk.period), 0) AS latest_period").
|
||||||
|
Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.kandang_id = k.id").
|
||||||
|
Where("k.location_id = ?", locationID).
|
||||||
|
Where("k.deleted_at IS NULL").
|
||||||
|
Group("k.id, k.name").
|
||||||
|
Order("k.id ASC").
|
||||||
|
Scan(&rows).Error; err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch kandang period summary for location %d: %+v", locationID, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandang period summary")
|
||||||
}
|
}
|
||||||
|
|
||||||
if referenceFlock == nil {
|
summaries := make([]KandangPeriodSummary, 0, len(rows))
|
||||||
referenceFlock = &entity.Flock{Name: pivot.ProjectFlock.FlockName}
|
for _, row := range rows {
|
||||||
}
|
nextPeriod := 0
|
||||||
|
if row.LatestPeriod > 0 {
|
||||||
maxPeriod := pivot.ProjectFlock.Period
|
nextPeriod = row.LatestPeriod + 1
|
||||||
if strings.TrimSpace(baseName) != "" {
|
|
||||||
if headerMax, err := s.Repository.GetMaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
|
||||||
s.Log.Warnf("Unable to compute header period for base %q: %+v", baseName, err)
|
|
||||||
} else if headerMax > maxPeriod {
|
|
||||||
maxPeriod = headerMax
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pivotMax, err := s.pivotRepo().MaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
summaries = append(summaries, KandangPeriodSummary{
|
||||||
s.Log.Warnf("Unable to compute pivot period for base %q: %+v", baseName, err)
|
Id: row.Id,
|
||||||
} else if pivotMax > maxPeriod {
|
Name: row.Name,
|
||||||
maxPeriod = pivotMax
|
Period: nextPeriod,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FlockPeriodSummary{
|
return summaries, nil
|
||||||
Flock: *referenceFlock,
|
|
||||||
NextPeriod: maxPeriod + 1,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func uniqueUintSlice(values []uint) []uint {
|
func uniqueUintSlice(values []uint) []uint {
|
||||||
@@ -934,7 +990,7 @@ func (s projectflockService) ensureFlockByName(ctx context.Context, actorID uint
|
|||||||
return newFlock, nil
|
return newFlock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint) error {
|
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint, period int) error {
|
||||||
if len(kandangIDs) == 0 {
|
if len(kandangIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -972,6 +1028,7 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
|
|||||||
records = append(records, &entity.ProjectFlockKandang{
|
records = append(records, &entity.ProjectFlockKandang{
|
||||||
ProjectFlockId: projectFlockID,
|
ProjectFlockId: projectFlockID,
|
||||||
KandangId: id,
|
KandangId: id,
|
||||||
|
Period: period,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err := s.pivotRepoWithTx(dbTransaction).CreateMany(ctx, records); err != nil {
|
if err := s.pivotRepoWithTx(dbTransaction).CreateMany(ctx, records); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user