Merge branch 'Fix/BE/recording_avaible_qty' into 'development'

[FEAT/BE]Fix add new response depletions_rate

See merge request mbugroup/lti-api!301
This commit is contained in:
Hafizh A. Y.
2026-02-03 05:09:53 +00:00
4 changed files with 134 additions and 8 deletions
+2
View File
@@ -12,7 +12,9 @@ type Recording struct {
RecordDatetime time.Time `gorm:"column:record_datetime;not null"`
Day *int `gorm:"column:day"`
TotalDepletionQty *float64 `gorm:"column:total_depletion_qty"`
TotalDepletionCumQty *float64 `gorm:"-"`
CumDepletionRate *float64 `gorm:"column:cum_depletion_rate"`
DepletionRate *float64 `gorm:"-"`
CumIntake *int `gorm:"column:cum_intake"`
FcrValue *float64 `gorm:"column:fcr_value"`
TotalChickQty *float64 `gorm:"column:total_chick_qty"`
@@ -1,6 +1,7 @@
package dto
import (
"math"
"strings"
"time"
@@ -73,7 +74,9 @@ type RecordingRelationDTO struct {
RecordDatetime time.Time `json:"record_datetime"`
Day int `json:"day"`
TotalDepletionQty float64 `json:"total_depletion_qty"`
TotalDepletionCumQty float64 `json:"total_depletion_cum_qty"`
CumDepletionRate float64 `json:"cum_depletion_rate"`
DepletionRate float64 `json:"depletion_rate"`
CumIntake int `json:"cum_intake"`
FcrValue float64 `json:"fcr_value"`
HenDay float64 `json:"hen_day"`
@@ -230,7 +233,9 @@ func toRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
RecordDatetime: e.RecordDatetime,
Day: intValue(e.Day),
TotalDepletionQty: floatValue(e.TotalDepletionQty),
CumDepletionRate: floatValue(e.CumDepletionRate),
TotalDepletionCumQty: floatValue(e.TotalDepletionCumQty),
CumDepletionRate: roundFloatValue(e.CumDepletionRate, 2),
DepletionRate: roundFloatValue(e.DepletionRate, 2),
CumIntake: intValue(e.CumIntake),
FcrValue: floatValue(e.FcrValue),
HenDay: floatValue(e.HenDay),
@@ -426,6 +431,17 @@ func floatValue(value *float64) float64 {
return *value
}
func roundFloatValue(value *float64, places int) float64 {
if value == nil {
return 0
}
if places <= 0 {
return math.Round(*value)
}
factor := math.Pow(10, float64(places))
return math.Round(*value*factor) / factor
}
func intValue(value *int) int {
if value == nil {
return 0
@@ -39,6 +39,7 @@ type RecordingRepository interface {
ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error)
SumRecordingDepletions(tx *gorm.DB, recordingID uint) (float64, error)
GetCumulativeDepletionByProjectFlockKandangUntil(tx *gorm.DB, projectFlockKandangId uint, recordTime time.Time) (float64, error)
FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error)
GetTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error)
GetTotalChickinByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint) (float64, error)
@@ -314,6 +315,23 @@ func (r *RecordingRepositoryImpl) SumRecordingDepletions(tx *gorm.DB, recordingI
return result, nil
}
func (r *RecordingRepositoryImpl) GetCumulativeDepletionByProjectFlockKandangUntil(tx *gorm.DB, projectFlockKandangId uint, recordTime time.Time) (float64, error) {
if projectFlockKandangId == 0 || recordTime.IsZero() {
return 0, nil
}
var total float64
err := tx.
Table("recording_depletions rd").
Select("COALESCE(SUM(rd.qty),0)").
Joins("JOIN recordings r ON r.id = rd.recording_id").
Where("r.project_flock_kandangs_id = ?", projectFlockKandangId).
Where("r.record_datetime <= ?", recordTime).
Where("r.deleted_at IS NULL").
Scan(&total).Error
return total, err
}
func (r *RecordingRepositoryImpl) FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error) {
if currentDay <= 1 {
return nil, nil
@@ -128,6 +128,12 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
if err := s.attachProductionStandards(c.Context(), recordings); err != nil {
return nil, 0, err
}
if err := s.attachCumulativeDepletions(c.Context(), recordings); err != nil {
return nil, 0, err
}
if err := s.attachDepletionRates(c.Context(), recordings); err != nil {
return nil, 0, err
}
return recordings, total, nil
}
@@ -152,6 +158,12 @@ func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, erro
if err := s.attachProductionStandard(c.Context(), recording); err != nil {
return nil, err
}
if err := s.attachCumulativeDepletion(c.Context(), recording); err != nil {
return nil, err
}
if err := s.attachDepletionRate(c.Context(), recording); err != nil {
return nil, err
}
return recording, nil
}
@@ -1026,12 +1038,8 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
return fmt.Errorf("getPreviousRecording: %w", err)
}
var prevCumDepletionQty float64
var prevCumIntake float64
if prevRecording != nil {
if prevRecording.TotalDepletionQty != nil {
prevCumDepletionQty = *prevRecording.TotalDepletionQty
}
if prevRecording.CumIntake != nil {
prevCumIntake = float64(*prevRecording.CumIntake)
}
@@ -1063,12 +1071,16 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
}
currentDepletion := float64(totalDepletionQty)
cumDepletionQty := prevCumDepletionQty + currentDepletion
cumDepletionQty, err := s.Repository.GetCumulativeDepletionByProjectFlockKandangUntil(tx, recording.ProjectFlockKandangId, recording.RecordDatetime)
if err != nil {
return fmt.Errorf("getCumulativeDepletionByProjectFlockKandangUntil: %w", err)
}
updates := map[string]any{
"total_depletion_qty": cumDepletionQty,
"total_depletion_qty": currentDepletion,
}
recording.TotalDepletionQty = &cumDepletionQty
recording.TotalDepletionQty = &currentDepletion
recording.TotalDepletionCumQty = &cumDepletionQty
var remainingChick float64
if totalChick > 0 {
@@ -1111,6 +1123,9 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
recording.CumDepletionRate = nil
}
depletionRate := computeDepletionRate(prevRecording, currentDepletion, totalChick)
recording.DepletionRate = &depletionRate
var feedIntake float64
if remainingChick > 0 && usageInGrams > 0 {
feedIntake = (usageInGrams / remainingChick) * 1000
@@ -1201,6 +1216,81 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
return nil
}
func computeDepletionRate(prevRecording *entity.Recording, currentDepletion float64, totalChick int64) float64 {
base := 0.0
if prevRecording != nil && prevRecording.TotalChickQty != nil && *prevRecording.TotalChickQty > 0 {
base = *prevRecording.TotalChickQty
} else if totalChick > 0 {
// totalChick is already remaining after today's depletion; add back current to approximate previous population.
base = float64(totalChick) + currentDepletion
}
if base <= 0 {
return 0
}
return (currentDepletion / base) * 100
}
func (s *recordingService) attachCumulativeDepletion(ctx context.Context, recording *entity.Recording) error {
if recording == nil || recording.ProjectFlockKandangId == 0 || recording.RecordDatetime.IsZero() {
return nil
}
total, err := s.Repository.GetCumulativeDepletionByProjectFlockKandangUntil(s.Repository.DB().WithContext(ctx), recording.ProjectFlockKandangId, recording.RecordDatetime)
if err != nil {
return err
}
recording.TotalDepletionCumQty = &total
return nil
}
func (s *recordingService) attachCumulativeDepletions(ctx context.Context, recordings []entity.Recording) error {
if len(recordings) == 0 {
return nil
}
for i := range recordings {
if err := s.attachCumulativeDepletion(ctx, &recordings[i]); err != nil {
return err
}
}
return nil
}
func (s *recordingService) attachDepletionRate(ctx context.Context, recording *entity.Recording) error {
if recording == nil {
return nil
}
current := 0.0
if recording.TotalDepletionQty != nil {
current = *recording.TotalDepletionQty
}
day := 0
if recording.Day != nil {
day = *recording.Day
}
prev, err := s.Repository.FindPreviousRecording(s.Repository.DB().WithContext(ctx), recording.ProjectFlockKandangId, day)
if err != nil {
return err
}
totalChick, err := s.Repository.GetTotalChick(s.Repository.DB().WithContext(ctx), recording.ProjectFlockKandangId)
if err != nil {
return err
}
rate := computeDepletionRate(prev, current, totalChick)
recording.DepletionRate = &rate
return nil
}
func (s *recordingService) attachDepletionRates(ctx context.Context, recordings []entity.Recording) error {
if len(recordings) == 0 {
return nil
}
for i := range recordings {
if err := s.attachDepletionRate(ctx, &recordings[i]); err != nil {
return err
}
}
return nil
}
func (s *recordingService) createRecordingApproval(
ctx context.Context,
db *gorm.DB,