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"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||
CreatedUser User `gorm:"foreignKey:CreatedBy;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"`
|
||||
FcrId uint `gorm:"not null"`
|
||||
LocationId uint `gorm:"not null"`
|
||||
Period int `gorm:"not null"`
|
||||
CreatedBy uint `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
@@ -28,4 +27,3 @@ type ProjectFlock struct {
|
||||
|
||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ type ProjectFlockKandang struct {
|
||||
Id uint `gorm:"primaryKey"`
|
||||
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"`
|
||||
Period int `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
|
||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -120,9 +121,41 @@ func (r *KandangRepositoryImpl) UpsertProjectFlockKandang(ctx context.Context, p
|
||||
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
||||
First(&link).Error
|
||||
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{
|
||||
ProjectFlockId: projectFlockID,
|
||||
KandangId: kandangID,
|
||||
Period: period,
|
||||
}
|
||||
return r.db.WithContext(ctx).Create(&link).Error
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ func ToUserBaseDTO(e entity.User) userBaseDTO.UserBaseDTO {
|
||||
return userBaseDTO.ToUserBaseDTO(e)
|
||||
}
|
||||
|
||||
func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
||||
func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
||||
e := pfk.ProjectFlock
|
||||
var flock *flockBaseDTO.FlockBaseDTO
|
||||
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
||||
summary := flockBaseDTO.FlockBaseDTO{Id: 0, Name: base}
|
||||
@@ -132,7 +133,7 @@ func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
||||
}
|
||||
return ProjectFlockDTO{
|
||||
Id: e.Id,
|
||||
Period: e.Period,
|
||||
Period: pfk.Period,
|
||||
Category: e.Category,
|
||||
Flock: flock,
|
||||
Area: area,
|
||||
@@ -144,7 +145,7 @@ func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
||||
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
||||
var pf *ProjectFlockDTO
|
||||
if e.ProjectFlock.Id != 0 {
|
||||
mapped := ToProjectFlockDTO(e.ProjectFlock)
|
||||
mapped := ToProjectFlockDTO(e)
|
||||
pf = &mapped
|
||||
}
|
||||
var kandang *kandangBaseDTO.KandangBaseDTO
|
||||
|
||||
@@ -90,13 +90,15 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data := make([]dto.ProjectFlockListDTO, 0)
|
||||
for _, projectFlock := range result {
|
||||
var flock *flockDTO.FlockBaseDTO
|
||||
if flockMap != nil {
|
||||
flock = flockMap[projectFlock.Id]
|
||||
var periodMap map[uint]int
|
||||
if len(result) > 0 {
|
||||
ids := make([]uint, len(result))
|
||||
for i, item := range result {
|
||||
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).
|
||||
@@ -110,7 +112,7 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||
TotalResults: totalResults,
|
||||
},
|
||||
Data: data,
|
||||
Data: dto.ToProjectFlockListDTOsWithPeriods(result, periodMap),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,12 +129,19 @@ func (u *ProjectflockController) GetOne(c *fiber.Ctx) error {
|
||||
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).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
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{}
|
||||
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 {
|
||||
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 {
|
||||
message = "Submit projectflock approvals successfully"
|
||||
data = dto.ToProjectFlockListDTOs(results)
|
||||
data = dto.ToProjectFlockListDTOsWithPeriods(results, periodMap)
|
||||
}
|
||||
|
||||
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 {
|
||||
param := c.Params("project_flock_kandang_id")
|
||||
param := c.Params("location_id")
|
||||
|
||||
id, err := strconv.Atoi(param)
|
||||
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 {
|
||||
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).
|
||||
JSON(response.Success{
|
||||
Code: fiber.StatusOK,
|
||||
Status: "success",
|
||||
Message: "Get flock period summary successfully",
|
||||
Message: "Get kandang period summary successfully",
|
||||
Data: responseBody,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,7 +51,13 @@ type FlockPeriodDTO struct {
|
||||
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
|
||||
if e.CreatedUser.Id != 0 {
|
||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||
@@ -107,17 +113,17 @@ func ToProjectFlockListDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO)
|
||||
}
|
||||
|
||||
return ProjectFlockListDTO{
|
||||
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
|
||||
Flock: flockSummary,
|
||||
Area: areaSummary,
|
||||
Kandangs: kandangSummaries,
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
Approval: latestApproval,
|
||||
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e, period),
|
||||
// Flock: flockSummary,
|
||||
Area: areaSummary,
|
||||
Kandangs: kandangSummaries,
|
||||
Category: e.Category,
|
||||
Fcr: fcrSummary,
|
||||
Location: locationSummary,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedUser: createdUser,
|
||||
Approval: latestApproval,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +134,25 @@ func ToProjectFlockListDTOWithFlock(e entity.ProjectFlock, flock *flockDTO.Flock
|
||||
func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO {
|
||||
result := make([]ProjectFlockListDTO, len(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
|
||||
}
|
||||
@@ -152,7 +176,7 @@ func ToProjectFlockListDTOsWithFlocks(items []entity.ProjectFlock, flocks map[ui
|
||||
|
||||
func ToProjectFlockDetailDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockDetailDTO {
|
||||
return ProjectFlockDetailDTO{
|
||||
ProjectFlockListDTO: ToProjectFlockListDTO(e, flock),
|
||||
ProjectFlockListDTO: ToProjectFlockListDTOWithPeriod(e, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +205,10 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
|
||||
return result
|
||||
}
|
||||
|
||||
func createProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
|
||||
func createProjectFlockBaseDTO(e entity.ProjectFlock, period int) ProjectFlockBaseDTO {
|
||||
return ProjectFlockBaseDTO{
|
||||
Id: e.Id,
|
||||
Period: e.Period,
|
||||
Period: period,
|
||||
FlockName: e.FlockName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
||||
pfLocal := ProjectFlockWithPivotDTO{
|
||||
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
|
||||
Id: e.ProjectFlock.Id,
|
||||
Period: e.ProjectFlock.Period,
|
||||
Period: e.Period,
|
||||
FlockName: e.ProjectFlock.FlockName,
|
||||
},
|
||||
Category: e.ProjectFlock.Category,
|
||||
|
||||
+15
-69
@@ -2,7 +2,6 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -10,17 +9,12 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const baseNameExpression = "LOWER(TRIM(regexp_replace(flock_name, '\\\\s+\\\\d+(\\\\s+\\\\d+)*$', '', 'g')))"
|
||||
|
||||
type ProjectflockRepository interface {
|
||||
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)
|
||||
WithDefaultRelations() func(*gorm.DB) *gorm.DB
|
||||
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) {
|
||||
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.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)
|
||||
}
|
||||
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 {
|
||||
db = db.Where(`
|
||||
@@ -184,10 +125,15 @@ func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch s
|
||||
OR LOWER(created_users.email) 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(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 (
|
||||
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 ?
|
||||
)
|
||||
`,
|
||||
@@ -241,7 +187,7 @@ func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder str
|
||||
}
|
||||
case "period":
|
||||
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),
|
||||
}
|
||||
default:
|
||||
|
||||
+28
-1
@@ -21,6 +21,7 @@ type ProjectFlockKandangRepository interface {
|
||||
HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error)
|
||||
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
||||
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||
ProjectPeriodsByProjectIDs(ctx context.Context, projectIDs []uint) (map[uint]int, error)
|
||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||
IdExists(ctx context.Context, id uint) (bool, error)
|
||||
DB() *gorm.DB
|
||||
@@ -274,7 +275,33 @@ func (r *projectFlockKandangRepositoryImpl) MaxPeriodByBaseName(ctx context.Cont
|
||||
Table("project_flock_kandangs pfk").
|
||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||
Where(flockBaseNameExpression+" = LOWER(?)", baseName).
|
||||
Select("COALESCE(MAX(pf.period), 0)").
|
||||
Select("COALESCE(MAX(pfk.period), 0)").
|
||||
Scan(&max).Error
|
||||
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
|
||||
|
||||
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"
|
||||
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/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)
|
||||
|
||||
route := v1.Group("/project-flocks")
|
||||
|
||||
// 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.Use(m.Auth(u))
|
||||
|
||||
route.Get("/", ctrl.GetAll)
|
||||
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/lookup", ctrl.LookupProjectFlockKandang)
|
||||
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)
|
||||
DeleteOne(ctx *fiber.Ctx, id uint) 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)
|
||||
}
|
||||
|
||||
@@ -53,9 +54,10 @@ type projectflockService struct {
|
||||
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
||||
}
|
||||
|
||||
type FlockPeriodSummary struct {
|
||||
Flock entity.Flock
|
||||
NextPeriod int
|
||||
type KandangPeriodSummary struct {
|
||||
Id uint
|
||||
Name string
|
||||
Period int
|
||||
}
|
||||
|
||||
func NewProjectflockService(
|
||||
@@ -283,6 +285,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
||||
if len(kandangs) != len(kandangIDs) {
|
||||
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
|
||||
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), kandangIDs, nil); err != nil {
|
||||
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 {
|
||||
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
||||
|
||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), canonicalBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
generatedName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, nextSeq, nil)
|
||||
// Generate unique flock name (sequential per base name, starting from 1)
|
||||
generatedName, _, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, 1, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createBody.FlockName = generatedName
|
||||
createBody.Period = seq
|
||||
|
||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -452,6 +461,15 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
if len(kandangs) != len(newKandangIDs) {
|
||||
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 {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||
} else if linked {
|
||||
@@ -477,18 +495,11 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
|
||||
if needFlockNameRegenerate {
|
||||
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), baseForGeneration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, nextSeq, &id)
|
||||
newName, _, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, 1, &id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateBody["flock_name"] = newName
|
||||
if seq != existing.Period {
|
||||
updateBody["period"] = seq
|
||||
}
|
||||
}
|
||||
|
||||
if len(updateBody) > 0 {
|
||||
@@ -532,7 +543,19 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -808,57 +831,90 @@ func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID u
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, projectFlockKandangID uint) (*FlockPeriodSummary, error) {
|
||||
if projectFlockKandangID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||
// nextLocationPeriod computes the next period number for a given location
|
||||
// based on the maximum period that has ever been used by any kandang in that location.
|
||||
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)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
|
||||
db := s.Repository.DB()
|
||||
if tx != nil {
|
||||
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 {
|
||||
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", projectFlockKandangID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||
s.Log.Errorf("Failed to validate location %d: %+v", locationID, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate location")
|
||||
}
|
||||
if !exists {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Location not found")
|
||||
}
|
||||
|
||||
var baseName string
|
||||
var referenceFlock *entity.Flock
|
||||
if pivot.ProjectFlock.Id != 0 {
|
||||
baseName = pfutils.DeriveBaseName(pivot.ProjectFlock.FlockName)
|
||||
type kandangPeriodRow struct {
|
||||
Id uint
|
||||
Name string
|
||||
LatestPeriod int
|
||||
}
|
||||
|
||||
if strings.TrimSpace(baseName) != "" {
|
||||
referenceFlock, err = s.FlockRepo.GetByName(c.Context(), baseName)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.Log.Errorf("Failed to fetch flock %q: %+v", baseName, err)
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
||||
}
|
||||
var rows []kandangPeriodRow
|
||||
|
||||
db := s.Repository.DB().WithContext(c.Context())
|
||||
if err := db.
|
||||
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 {
|
||||
referenceFlock = &entity.Flock{Name: pivot.ProjectFlock.FlockName}
|
||||
}
|
||||
|
||||
maxPeriod := pivot.ProjectFlock.Period
|
||||
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
|
||||
summaries := make([]KandangPeriodSummary, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
nextPeriod := 0
|
||||
if row.LatestPeriod > 0 {
|
||||
nextPeriod = row.LatestPeriod + 1
|
||||
}
|
||||
|
||||
if pivotMax, err := s.pivotRepo().MaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
||||
s.Log.Warnf("Unable to compute pivot period for base %q: %+v", baseName, err)
|
||||
} else if pivotMax > maxPeriod {
|
||||
maxPeriod = pivotMax
|
||||
}
|
||||
summaries = append(summaries, KandangPeriodSummary{
|
||||
Id: row.Id,
|
||||
Name: row.Name,
|
||||
Period: nextPeriod,
|
||||
})
|
||||
}
|
||||
|
||||
return &FlockPeriodSummary{
|
||||
Flock: *referenceFlock,
|
||||
NextPeriod: maxPeriod + 1,
|
||||
}, nil
|
||||
return summaries, nil
|
||||
}
|
||||
|
||||
func uniqueUintSlice(values []uint) []uint {
|
||||
@@ -934,7 +990,7 @@ func (s projectflockService) ensureFlockByName(ctx context.Context, actorID uint
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@@ -972,6 +1028,7 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
|
||||
records = append(records, &entity.ProjectFlockKandang{
|
||||
ProjectFlockId: projectFlockID,
|
||||
KandangId: id,
|
||||
Period: period,
|
||||
})
|
||||
}
|
||||
if err := s.pivotRepoWithTx(dbTransaction).CreateMany(ctx, records); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user