mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/BE/US-281-adjustment_recording' into 'development'
feat(BE-281): adjustment recording table with handhouse and deleting weight... See merge request mbugroup/lti-api!122
This commit is contained in:
@@ -22,11 +22,21 @@ type RecordingRelationDTO struct {
|
||||
ProjectFlockCategory string `json:"project_flock_category"`
|
||||
TotalDepletionQty float64 `json:"total_depletion_qty"`
|
||||
CumDepletionRate float64 `json:"cum_depletion_rate"`
|
||||
DailyGain float64 `json:"daily_gain"`
|
||||
AvgDailyGain float64 `json:"avg_daily_gain"`
|
||||
CumIntake int `json:"cum_intake"`
|
||||
FcrValue float64 `json:"fcr_value"`
|
||||
TotalChickQty float64 `json:"total_chick_qty"`
|
||||
HandDay float64 `json:"hand_day"`
|
||||
HandHouse float64 `json:"hand_house"`
|
||||
FeedIntake float64 `json:"feed_intake"`
|
||||
EggMesh float64 `json:"egg_mesh"`
|
||||
EggWeight float64 `json:"egg_weight"`
|
||||
StandardHandDay *float64 `json:"hand_day_std,omitempty"`
|
||||
StandardHandHouse *float64 `json:"hand_house_std,omitempty"`
|
||||
StandardFeedIntake *float64 `json:"feed_intake_std,omitempty"`
|
||||
StandardMaxDepletion *float64 `json:"max_depletion_std,omitempty"`
|
||||
StandardEggMesh *float64 `json:"egg_mesh_std,omitempty"`
|
||||
StandardEggWeight *float64 `json:"egg_weight_std,omitempty"`
|
||||
StandardFcr *float64 `json:"fcr_std,omitempty"`
|
||||
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
|
||||
}
|
||||
|
||||
@@ -39,16 +49,9 @@ type RecordingListDTO struct {
|
||||
|
||||
type RecordingDetailDTO struct {
|
||||
RecordingListDTO
|
||||
BodyWeights []RecordingBodyWeightDTO `json:"body_weights"`
|
||||
Depletions []RecordingDepletionDTO `json:"depletions"`
|
||||
Stocks []RecordingStockDTO `json:"stocks"`
|
||||
Eggs []RecordingEggDTO `json:"eggs"`
|
||||
}
|
||||
|
||||
type RecordingBodyWeightDTO struct {
|
||||
AvgWeight float64 `json:"avg_weight"`
|
||||
Qty float64 `json:"qty"`
|
||||
TotalWeight float64 `json:"total_weight"`
|
||||
Depletions []RecordingDepletionDTO `json:"depletions"`
|
||||
Stocks []RecordingStockDTO `json:"stocks"`
|
||||
Eggs []RecordingEggDTO `json:"eggs"`
|
||||
}
|
||||
|
||||
type RecordingDepletionDTO struct {
|
||||
@@ -88,11 +91,14 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
||||
day int
|
||||
totalDepletionQty float64
|
||||
cumDepletionRate float64
|
||||
dailyGain float64
|
||||
avgDailyGain float64
|
||||
cumIntake int
|
||||
fcrValue float64
|
||||
totalChickQty float64
|
||||
handDay float64
|
||||
handHouse float64
|
||||
feedIntake float64
|
||||
eggMesh float64
|
||||
eggWeight float64
|
||||
)
|
||||
|
||||
if e.Day != nil {
|
||||
@@ -104,12 +110,6 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
||||
if e.CumDepletionRate != nil {
|
||||
cumDepletionRate = *e.CumDepletionRate
|
||||
}
|
||||
if e.DailyGain != nil {
|
||||
dailyGain = *e.DailyGain
|
||||
}
|
||||
if e.AvgDailyGain != nil {
|
||||
avgDailyGain = *e.AvgDailyGain
|
||||
}
|
||||
if e.CumIntake != nil {
|
||||
cumIntake = *e.CumIntake
|
||||
}
|
||||
@@ -119,6 +119,21 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
||||
if e.TotalChickQty != nil {
|
||||
totalChickQty = *e.TotalChickQty
|
||||
}
|
||||
if e.HandDay != nil {
|
||||
handDay = *e.HandDay
|
||||
}
|
||||
if e.HandHouse != nil {
|
||||
handHouse = *e.HandHouse
|
||||
}
|
||||
if e.FeedIntake != nil {
|
||||
feedIntake = *e.FeedIntake
|
||||
}
|
||||
if e.EggMesh != nil {
|
||||
eggMesh = *e.EggMesh
|
||||
}
|
||||
if e.EggWeight != nil {
|
||||
eggWeight = *e.EggWeight
|
||||
}
|
||||
|
||||
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.ProjectFlock.Id != 0 {
|
||||
category := e.ProjectFlockKandang.ProjectFlock.Category
|
||||
@@ -139,11 +154,21 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
||||
ProjectFlockCategory: projectFlockCategory,
|
||||
TotalDepletionQty: totalDepletionQty,
|
||||
CumDepletionRate: cumDepletionRate,
|
||||
DailyGain: dailyGain,
|
||||
AvgDailyGain: avgDailyGain,
|
||||
CumIntake: cumIntake,
|
||||
FcrValue: fcrValue,
|
||||
TotalChickQty: totalChickQty,
|
||||
HandDay: handDay,
|
||||
HandHouse: handHouse,
|
||||
FeedIntake: feedIntake,
|
||||
EggMesh: eggMesh,
|
||||
EggWeight: eggWeight,
|
||||
StandardHandDay: e.StandardHandDay,
|
||||
StandardHandHouse: e.StandardHandHouse,
|
||||
StandardFeedIntake: e.StandardFeedIntake,
|
||||
StandardMaxDepletion: e.StandardMaxDepletion,
|
||||
StandardEggMesh: e.StandardEggMesh,
|
||||
StandardEggWeight: e.StandardEggWeight,
|
||||
StandardFcr: e.StandardFcr,
|
||||
Approval: latestApproval,
|
||||
}
|
||||
}
|
||||
@@ -183,25 +208,12 @@ func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
|
||||
|
||||
return RecordingDetailDTO{
|
||||
RecordingListDTO: listDTO,
|
||||
BodyWeights: ToRecordingBodyWeightDTOs(e.BodyWeights),
|
||||
Depletions: ToRecordingDepletionDTOs(e.Depletions),
|
||||
Stocks: ToRecordingStockDTOs(e.Stocks),
|
||||
Eggs: eggs,
|
||||
}
|
||||
}
|
||||
|
||||
func ToRecordingBodyWeightDTOs(bodyWeights []entity.RecordingBW) []RecordingBodyWeightDTO {
|
||||
result := make([]RecordingBodyWeightDTO, len(bodyWeights))
|
||||
for i, bw := range bodyWeights {
|
||||
result[i] = RecordingBodyWeightDTO{
|
||||
AvgWeight: bw.AvgWeight,
|
||||
Qty: bw.Qty,
|
||||
TotalWeight: bw.TotalWeight,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ToRecordingDepletionDTOs(depletions []entity.RecordingDepletion) []RecordingDepletionDTO {
|
||||
result := make([]RecordingDepletionDTO, len(depletions))
|
||||
for i, d := range depletions {
|
||||
|
||||
@@ -20,9 +20,6 @@ type RecordingRepository interface {
|
||||
GetLatestByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (*entity.Recording, error)
|
||||
GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error)
|
||||
|
||||
CreateBodyWeights(tx *gorm.DB, bodyWeights []entity.RecordingBW) error
|
||||
DeleteBodyWeights(tx *gorm.DB, recordingID uint) error
|
||||
|
||||
CreateStocks(tx *gorm.DB, stocks []entity.RecordingStock) error
|
||||
DeleteStocks(tx *gorm.DB, recordingID uint) error
|
||||
ListStocks(tx *gorm.DB, recordingID uint) ([]entity.RecordingStock, error)
|
||||
@@ -42,10 +39,11 @@ type RecordingRepository interface {
|
||||
SumRecordingDepletions(tx *gorm.DB, recordingID uint) (float64, error)
|
||||
FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error)
|
||||
GetTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error)
|
||||
GetAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error)
|
||||
GetTotalChickinByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint) (float64, error)
|
||||
GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error)
|
||||
GetFcrID(tx *gorm.DB, projectFlockKandangId uint) (uint, error)
|
||||
GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error)
|
||||
GetEggSummaryByRecording(tx *gorm.DB, recordingID uint) (totalQty float64, totalWeightGrams float64, err error)
|
||||
GetCumulativeEggQtyByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint, recordTime time.Time) (float64, error)
|
||||
GetFcrStandardNumber(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error)
|
||||
GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error)
|
||||
GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalDepletion float64, err error)
|
||||
GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error)
|
||||
@@ -67,7 +65,6 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("CreatedUser").
|
||||
Preload("ProjectFlockKandang").
|
||||
Preload("ProjectFlockKandang.ProjectFlock").
|
||||
Preload("BodyWeights").
|
||||
Preload("Depletions").
|
||||
Preload("Depletions.ProductWarehouse").
|
||||
Preload("Depletions.ProductWarehouse.Product").
|
||||
@@ -114,17 +111,6 @@ func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKanda
|
||||
return nextRecordingDay(days), nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) CreateBodyWeights(tx *gorm.DB, bodyWeights []entity.RecordingBW) error {
|
||||
if len(bodyWeights) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&bodyWeights).Error
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) DeleteBodyWeights(tx *gorm.DB, recordingID uint) error {
|
||||
return tx.Where("recording_id = ?", recordingID).Delete(&entity.RecordingBW{}).Error
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) CreateStocks(tx *gorm.DB, stocks []entity.RecordingStock) error {
|
||||
if len(stocks) == 0 {
|
||||
return nil
|
||||
@@ -293,21 +279,18 @@ func (r *RecordingRepositoryImpl) GetTotalChick(tx *gorm.DB, projectFlockKandang
|
||||
return int64(math.Round(total)), nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error) {
|
||||
var result struct {
|
||||
TotalWeight float64
|
||||
TotalQty float64
|
||||
}
|
||||
if err := tx.Model(&entity.RecordingBW{}).
|
||||
Select("COALESCE(SUM(total_weight), 0) AS total_weight, COALESCE(SUM(qty), 0) AS total_qty").
|
||||
Where("recording_id = ?", recordingID).
|
||||
Scan(&result).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result.TotalQty == 0 {
|
||||
func (r *RecordingRepositoryImpl) GetTotalChickinByProjectFlockKandang(tx *gorm.DB, projectFlockKandangId uint) (float64, error) {
|
||||
if projectFlockKandangId == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return result.TotalWeight / result.TotalQty, nil
|
||||
|
||||
var result float64
|
||||
err := tx.
|
||||
Table("project_chickins").
|
||||
Select("COALESCE(SUM(project_chickins.usage_qty), 0)").
|
||||
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangId).
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error) {
|
||||
@@ -344,22 +327,48 @@ func (r *RecordingRepositoryImpl) GetFeedUsageInGrams(tx *gorm.DB, recordingID u
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetFcrID(tx *gorm.DB, projectFlockKandangId uint) (uint, error) {
|
||||
func (r *RecordingRepositoryImpl) GetEggSummaryByRecording(tx *gorm.DB, recordingID uint) (totalQty float64, totalWeightGrams float64, err error) {
|
||||
if recordingID == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
FcrID uint
|
||||
TotalQty float64
|
||||
TotalWeightGrams float64
|
||||
}
|
||||
if err := tx.Table("project_flock_kandangs").
|
||||
Select("project_flocks.fcr_id AS fcr_id").
|
||||
Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id").
|
||||
Where("project_flock_kandangs.id = ?", projectFlockKandangId).
|
||||
Scan(&result).Error; err != nil {
|
||||
return 0, err
|
||||
err = tx.
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0) AS total_qty, COALESCE(SUM(recording_eggs.qty * COALESCE(recording_eggs.weight, 0)), 0) AS total_weight_grams").
|
||||
Where("recording_eggs.recording_id = ?", recordingID).
|
||||
Scan(&result).Error
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return result.FcrID, nil
|
||||
return result.TotalQty, result.TotalWeightGrams, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error) {
|
||||
if fcrId == 0 {
|
||||
func (r *RecordingRepositoryImpl) GetCumulativeEggQtyByProjectFlockKandang(
|
||||
tx *gorm.DB,
|
||||
projectFlockKandangId uint,
|
||||
recordTime time.Time,
|
||||
) (float64, error) {
|
||||
if projectFlockKandangId == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var result float64
|
||||
err := tx.
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0)").
|
||||
Joins("JOIN recordings ON recordings.id = recording_eggs.recording_id").
|
||||
Where("recordings.project_flock_kandangs_id = ?", projectFlockKandangId).
|
||||
Where("recordings.record_datetime <= ?", recordTime).
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetFcrStandardNumber(tx *gorm.DB, fcrId uint, currentWeightKg float64) (float64, bool, error) {
|
||||
if fcrId == 0 || currentWeightKg <= 0 {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
@@ -382,49 +391,12 @@ func (r *RecordingRepositoryImpl) GetFcrStandardWeightKg(tx *gorm.DB, fcrId uint
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
weight := standard.Weight
|
||||
if weight > 10 {
|
||||
return weight / 1000, true, nil
|
||||
}
|
||||
return weight, true, nil
|
||||
return standard.FcrNumber, true, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetProductionWeightAndQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeight float64, totalQty float64, err error) {
|
||||
if projectFlockID == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
totalChickinQty, err := r.getTotalChickinQtyByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
totalDepletion, err := r.GetTotalDepletionByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
actualQty := totalChickinQty - totalDepletion
|
||||
|
||||
avgWeight, err := r.GetLatestAvgWeightByProjectFlockID(ctx, projectFlockID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
totalWeight = actualQty * avgWeight
|
||||
|
||||
return totalWeight, actualQty, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) getTotalChickinQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||
var result float64
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("project_chickins").
|
||||
Select("COALESCE(SUM(project_chickins.usage_qty), 0)").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = project_chickins.project_flock_kandang_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
// Body-weight tracking is removed; keep stub for report compatibility.
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||
@@ -440,16 +412,8 @@ func (r *RecordingRepositoryImpl) GetTotalDepletionByProjectFlockID(ctx context.
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||
var result float64
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("recording_bws").
|
||||
Select("COALESCE(AVG(recording_bws.avg_weight), 0)").
|
||||
Joins("JOIN recordings ON recordings.id = recording_bws.recording_id").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = recordings.project_flock_kandangs_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
Where("recordings.record_datetime = (SELECT MAX(record_datetime) FROM recordings r2 WHERE r2.project_flock_kandangs_id IN (SELECT id FROM project_flock_kandangs WHERE project_flock_id = ?))", projectFlockID).
|
||||
Scan(&result).Error
|
||||
return result, err
|
||||
// Body-weight tracking is removed; keep stub for report compatibility.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r *RecordingRepositoryImpl) GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||
@@ -121,6 +122,9 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
||||
if err := s.attachLatestApprovals(c.Context(), recordings); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := s.attachProductionStandards(c.Context(), recordings); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return recordings, total, nil
|
||||
}
|
||||
|
||||
@@ -138,6 +142,9 @@ func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, erro
|
||||
if err := s.attachLatestApproval(c.Context(), recording); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.attachProductionStandard(c.Context(), recording); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return recording, nil
|
||||
}
|
||||
|
||||
@@ -233,12 +240,6 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return err
|
||||
}
|
||||
|
||||
mappedBodyWeights := recordingutil.MapBodyWeights(createdRecording.Id, req.BodyWeights)
|
||||
if err := s.Repository.CreateBodyWeights(tx, mappedBodyWeights); err != nil {
|
||||
s.Log.Errorf("Failed to persist body weights: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
mappedStocks := recordingutil.MapStocks(createdRecording.Id, req.Stocks)
|
||||
if err := s.Repository.CreateStocks(tx, mappedStocks); err != nil {
|
||||
s.Log.Errorf("Failed to persist stocks: %+v", err)
|
||||
@@ -261,7 +262,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, nil, nil, mappedEggs)); err != nil {
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, mappedEggs)); err != nil {
|
||||
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
|
||||
return err
|
||||
}
|
||||
@@ -291,7 +292,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.BodyWeights == nil && req.Stocks == nil && req.Depletions == nil && req.Eggs == nil {
|
||||
if req.Stocks == nil && req.Depletions == nil && req.Eggs == nil {
|
||||
return s.GetOne(c, id)
|
||||
}
|
||||
|
||||
@@ -311,12 +312,11 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
recordingEntity = recording
|
||||
|
||||
hasBodyChanges := req.BodyWeights != nil
|
||||
hasStockChanges := req.Stocks != nil
|
||||
hasDepletionChanges := req.Depletions != nil
|
||||
hasEggChanges := req.Eggs != nil
|
||||
|
||||
if !hasBodyChanges && !hasStockChanges && !hasDepletionChanges && !hasEggChanges {
|
||||
if !hasStockChanges && !hasDepletionChanges && !hasEggChanges {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -346,17 +346,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
}
|
||||
}
|
||||
|
||||
if hasBodyChanges {
|
||||
if err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil {
|
||||
s.Log.Errorf("Failed to clear body weights: %+v", err)
|
||||
return err
|
||||
}
|
||||
if err := s.Repository.CreateBodyWeights(tx, recordingutil.MapBodyWeights(recordingEntity.Id, req.BodyWeights)); err != nil {
|
||||
s.Log.Errorf("Failed to update body weights: %+v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hasStockChanges {
|
||||
existingStocks, err := s.Repository.ListStocks(tx, recordingEntity.Id)
|
||||
if err != nil {
|
||||
@@ -402,7 +391,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(existingDepletions, mappedDepletions, nil, nil, nil, nil)); err != nil {
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(existingDepletions, mappedDepletions, nil, nil)); err != nil {
|
||||
s.Log.Errorf("Failed to adjust product warehouses for depletions: %+v", err)
|
||||
return err
|
||||
}
|
||||
@@ -426,13 +415,13 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, nil, nil, existingEggs, mappedEggs)); err != nil {
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingEggs, mappedEggs)); err != nil {
|
||||
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hasBodyChanges || hasStockChanges || hasDepletionChanges {
|
||||
if hasStockChanges || hasDepletionChanges {
|
||||
if err := s.computeAndUpdateMetrics(ctx, tx, recordingEntity); err != nil {
|
||||
s.Log.Errorf("Failed to recompute recording metrics: %+v", err)
|
||||
return err
|
||||
@@ -596,7 +585,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, nil, nil, oldEggs, nil)); err != nil {
|
||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, oldEggs, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -724,7 +713,6 @@ func (s *recordingService) ReleaseRecordingStocks(ctx context.Context, tx *gorm.
|
||||
|
||||
func buildWarehouseDeltas(
|
||||
oldDepletions, newDepletions []entity.RecordingDepletion,
|
||||
oldStocks, newStocks []entity.RecordingStock,
|
||||
oldEggs, newEggs []entity.RecordingEgg,
|
||||
) map[uint]float64 {
|
||||
deltas := make(map[uint]float64)
|
||||
@@ -775,7 +763,6 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
|
||||
var prevCumDepletionQty float64
|
||||
var prevCumIntake float64
|
||||
var prevAvgWeight float64
|
||||
if prevRecording != nil {
|
||||
if prevRecording.TotalDepletionQty != nil {
|
||||
prevCumDepletionQty = *prevRecording.TotalDepletionQty
|
||||
@@ -783,10 +770,6 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
if prevRecording.CumIntake != nil {
|
||||
prevCumIntake = float64(*prevRecording.CumIntake)
|
||||
}
|
||||
prevAvgWeight, err = s.Repository.GetAverageBodyWeight(tx, prevRecording.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAverageBodyWeight(prev): %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
totalChick, err := s.Repository.GetTotalChick(tx, recording.ProjectFlockKandangId)
|
||||
@@ -794,20 +777,25 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
return fmt.Errorf("getTotalChick: %w", err)
|
||||
}
|
||||
|
||||
currentAvgWeight, err := s.Repository.GetAverageBodyWeight(tx, recording.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAverageBodyWeight(current): %w", err)
|
||||
}
|
||||
|
||||
usageInGrams, err := s.Repository.GetFeedUsageInGrams(tx, recording.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getFeedUsageInGrams: %w", err)
|
||||
}
|
||||
|
||||
currentAvgGrams := recordingutil.ToGrams(currentAvgWeight)
|
||||
currentAvgKg := recordingutil.GramsToKg(currentAvgGrams)
|
||||
prevAvgGrams := recordingutil.ToGrams(prevAvgWeight)
|
||||
prevAvgKg := recordingutil.GramsToKg(prevAvgGrams)
|
||||
totalEggQty, totalEggWeightGrams, err := s.Repository.GetEggSummaryByRecording(tx, recording.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getEggSummaryByRecording: %w", err)
|
||||
}
|
||||
|
||||
cumulativeEggQty, err := s.Repository.GetCumulativeEggQtyByProjectFlockKandang(tx, recording.ProjectFlockKandangId, recording.RecordDatetime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getCumulativeEggQtyByProjectFlockKandang: %w", err)
|
||||
}
|
||||
|
||||
initialChickin, err := s.Repository.GetTotalChickinByProjectFlockKandang(tx, recording.ProjectFlockKandangId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getTotalChickinByProjectFlockKandang: %w", err)
|
||||
}
|
||||
|
||||
currentDepletion := float64(totalDepletionQty)
|
||||
cumDepletionQty := prevCumDepletionQty + currentDepletion
|
||||
@@ -840,24 +828,64 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
recording.CumDepletionRate = nil
|
||||
}
|
||||
|
||||
if currentAvgGrams > 0 && prevAvgGrams > 0 {
|
||||
dailyGainKg := (currentAvgGrams - prevAvgGrams) / 1000
|
||||
updates["daily_gain"] = dailyGainKg
|
||||
recording.DailyGain = &dailyGainKg
|
||||
var feedIntake float64
|
||||
if remainingChick > 0 && usageInGrams > 0 {
|
||||
feedIntake = (usageInGrams / remainingChick) * 1000
|
||||
updates["feed_intake"] = feedIntake
|
||||
recording.FeedIntake = &feedIntake
|
||||
} else {
|
||||
dailyGainKg := 0.0
|
||||
updates["daily_gain"] = dailyGainKg
|
||||
recording.DailyGain = &dailyGainKg
|
||||
updates["feed_intake"] = gorm.Expr("NULL")
|
||||
recording.FeedIntake = nil
|
||||
}
|
||||
|
||||
if currentAvgKg > 0 && remainingChick > 0 {
|
||||
avgDailyGain := (currentAvgKg - prevAvgKg) / remainingChick
|
||||
updates["avg_daily_gain"] = avgDailyGain
|
||||
recording.AvgDailyGain = &avgDailyGain
|
||||
var handDay float64
|
||||
if remainingChick > 0 && totalEggQty >= 0 {
|
||||
handDay = (totalEggQty / remainingChick) * 100
|
||||
updates["hand_day"] = handDay
|
||||
recording.HandDay = &handDay
|
||||
} else {
|
||||
avgDailyGain := 0.0
|
||||
updates["avg_daily_gain"] = avgDailyGain
|
||||
recording.AvgDailyGain = &avgDailyGain
|
||||
updates["hand_day"] = gorm.Expr("NULL")
|
||||
recording.HandDay = nil
|
||||
}
|
||||
|
||||
var handHouse float64
|
||||
if initialChickin > 0 && cumulativeEggQty >= 0 {
|
||||
handHouse = cumulativeEggQty / initialChickin
|
||||
updates["hand_house"] = handHouse
|
||||
recording.HandHouse = &handHouse
|
||||
} else {
|
||||
updates["hand_house"] = gorm.Expr("NULL")
|
||||
recording.HandHouse = nil
|
||||
}
|
||||
|
||||
var eggMesh float64
|
||||
if remainingChick > 0 && totalEggWeightGrams > 0 {
|
||||
eggMesh = (totalEggWeightGrams / remainingChick) * 1000
|
||||
updates["egg_mesh"] = eggMesh
|
||||
recording.EggMesh = &eggMesh
|
||||
} else {
|
||||
updates["egg_mesh"] = gorm.Expr("NULL")
|
||||
recording.EggMesh = nil
|
||||
}
|
||||
|
||||
var eggWeight float64
|
||||
if totalEggQty > 0 && totalEggWeightGrams > 0 {
|
||||
eggWeight = (totalEggWeightGrams / totalEggQty) * 1000
|
||||
updates["egg_weight"] = eggWeight
|
||||
recording.EggWeight = &eggWeight
|
||||
} else {
|
||||
updates["egg_weight"] = gorm.Expr("NULL")
|
||||
recording.EggWeight = nil
|
||||
}
|
||||
|
||||
var fcrValue float64
|
||||
if usageInGrams > 0 && totalEggWeightGrams > 0 {
|
||||
fcrValue = totalEggWeightGrams / usageInGrams
|
||||
updates["fcr_value"] = fcrValue
|
||||
recording.FcrValue = &fcrValue
|
||||
} else {
|
||||
updates["fcr_value"] = gorm.Expr("NULL")
|
||||
recording.FcrValue = nil
|
||||
}
|
||||
|
||||
if usageInGrams > 0 && totalChick > 0 {
|
||||
@@ -882,16 +910,6 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
recording.CumIntake = nil
|
||||
}
|
||||
|
||||
if usageInGrams > 0 && currentAvgKg > 0 {
|
||||
feedUsageKg := usageInGrams / 1000
|
||||
fcrValue := feedUsageKg / currentAvgKg
|
||||
updates["fcr_value"] = fcrValue
|
||||
recording.FcrValue = &fcrValue
|
||||
} else {
|
||||
updates["fcr_value"] = gorm.Expr("NULL")
|
||||
recording.FcrValue = nil
|
||||
}
|
||||
|
||||
if err := s.Repository.WithTx(tx).PatchOne(ctx, recording.Id, updates, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -997,6 +1015,104 @@ func (s *recordingService) attachLatestApproval(ctx context.Context, item *entit
|
||||
return nil
|
||||
}
|
||||
|
||||
type productionStandardValues struct {
|
||||
HandDay *float64
|
||||
HandHouse *float64
|
||||
FeedIntake *float64
|
||||
MaxDepletion *float64
|
||||
EggMesh *float64
|
||||
EggWeight *float64
|
||||
}
|
||||
|
||||
func (s *recordingService) attachProductionStandards(ctx context.Context, items []entity.Recording) error {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
if err := s.attachProductionStandard(ctx, &items[i]); err != nil {
|
||||
s.Log.Warnf("Unable to load production standard for recording %d: %+v", items[i].Id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *recordingService) attachProductionStandard(ctx context.Context, item *entity.Recording) error {
|
||||
if item == nil || item.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
if item.Day == nil || *item.Day <= 0 {
|
||||
return nil
|
||||
}
|
||||
if item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
standardID := item.ProjectFlockKandang.ProjectFlock.ProductionStandardId
|
||||
if standardID == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
week := ((int(*item.Day) - 1) / 7) + 1
|
||||
if week <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
category := strings.ToUpper(item.ProjectFlockKandang.ProjectFlock.Category)
|
||||
db := s.Repository.DB()
|
||||
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
||||
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
||||
|
||||
var standard productionStandardValues
|
||||
var standardFcr *float64
|
||||
if category == string(utils.ProjectFlockCategoryLaying) {
|
||||
detail, err := standardDetailRepo.GetByStandardIDAndWeek(ctx, standardID, week)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if detail != nil {
|
||||
standard.HandDay = detail.TargetHenDayProduction
|
||||
standard.HandHouse = detail.TargetHenHouseProduction
|
||||
standard.EggWeight = detail.TargetEggWeight
|
||||
standard.EggMesh = detail.TargetEggMass
|
||||
}
|
||||
}
|
||||
|
||||
growthDetail, err := growthDetailRepo.GetByStandardIDAndWeek(ctx, standardID, week)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if growthDetail != nil {
|
||||
standard.FeedIntake = growthDetail.FeedIntake
|
||||
standard.MaxDepletion = growthDetail.MaxDepletion
|
||||
if category == string(utils.ProjectFlockCategoryLaying) && growthDetail.TargetMeanBw != nil && item.ProjectFlockKandang.ProjectFlock.FcrId > 0 {
|
||||
targetWeight := *growthDetail.TargetMeanBw
|
||||
if targetWeight > 10 {
|
||||
targetWeight = targetWeight / 1000
|
||||
}
|
||||
if targetWeight > 0 {
|
||||
fcrStd, ok, err := s.Repository.GetFcrStandardNumber(db, item.ProjectFlockKandang.ProjectFlock.FcrId, targetWeight)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
standardFcr = &fcrStd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.StandardHandDay = standard.HandDay
|
||||
item.StandardHandHouse = standard.HandHouse
|
||||
item.StandardFeedIntake = standard.FeedIntake
|
||||
item.StandardMaxDepletion = standard.MaxDepletion
|
||||
item.StandardEggMesh = standard.EggMesh
|
||||
item.StandardEggWeight = standard.EggWeight
|
||||
item.StandardFcr = standardFcr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uniqueUintSlice(values []uint) []uint {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package validation
|
||||
|
||||
type (
|
||||
BodyWeight struct {
|
||||
AvgWeight float64 `json:"avg_weight" validate:"required"`
|
||||
Qty float64 `json:"qty" validate:"required,gt=0"`
|
||||
TotalWeight *float64 `json:"total_weight,omitempty" validate:"omitempty,gte=0"`
|
||||
}
|
||||
|
||||
Stock struct {
|
||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
|
||||
Qty float64 `json:"qty" validate:"required,gte=0"`
|
||||
@@ -27,14 +21,12 @@ type (
|
||||
|
||||
type Create struct {
|
||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
||||
BodyWeights []BodyWeight `json:"body_weights" validate:"dive"`
|
||||
Stocks []Stock `json:"stocks" validate:"dive"`
|
||||
Depletions []Depletion `json:"depletions" validate:"dive"`
|
||||
Eggs []Egg `json:"eggs" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
BodyWeights []BodyWeight `json:"body_weights,omitempty" validate:"omitempty,dive"`
|
||||
Stocks []Stock `json:"stocks,omitempty" validate:"omitempty,dive"`
|
||||
Depletions []Depletion `json:"depletions,omitempty" validate:"omitempty,dive"`
|
||||
Eggs []Egg `json:"eggs,omitempty" validate:"omitempty,dive"`
|
||||
|
||||
Reference in New Issue
Block a user