Fix logic recording transition

This commit is contained in:
ragilap
2026-03-10 17:02:45 +07:00
parent 3a8cc47fa0
commit 333cb9e136
13 changed files with 411 additions and 335 deletions
+193 -192
View File
@@ -6,6 +6,7 @@ import (
"strings"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
"gitlab.com/mbugroup/lti-api.git/internal/config"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -13,31 +14,31 @@ import (
)
type warnLogger interface {
Warnf(format string, args ...any)
Warnf(format string, args ...any)
}
type productWarehouseExistsRepo interface {
ExistsByID(ctx context.Context, id uint) (bool, error)
ExistsByID(ctx context.Context, id uint) (bool, error)
}
type recordingValidationRepo interface {
ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
}
func EnsureProductWarehousesExist(ctx context.Context, repo productWarehouseExistsRepo, ids []uint) error {
if repo == nil || len(ids) == 0 {
return nil
}
for _, id := range ids {
ok, err := repo.ExistsByID(ctx, id)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("product warehouse %d not found", id)
}
}
return nil
if repo == nil || len(ids) == 0 {
return nil
}
for _, id := range ids {
ok, err := repo.ExistsByID(ctx, id)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("product warehouse %d not found", id)
}
}
return nil
}
func EnsureProductWarehousesByFlags(ctx context.Context, repo recordingValidationRepo, ids []uint, flags []string, label string) error {
@@ -82,212 +83,212 @@ func EnsureProductWarehousesByFlagsForItems[T any](
}
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 {
base = float64(totalChick) + currentDepletion
}
if base <= 0 {
return 0
}
return (currentDepletion / base) * 100
base := 0.0
if prevRecording != nil && prevRecording.TotalChickQty != nil && *prevRecording.TotalChickQty > 0 {
base = *prevRecording.TotalChickQty
} else if totalChick > 0 {
base = float64(totalChick) + currentDepletion
}
if base <= 0 {
return 0
}
return (currentDepletion / base) * 100
}
func AttachLatestApprovals(ctx context.Context, items []entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
if len(items) == 0 || approvalSvc == nil {
return nil
}
if len(items) == 0 || approvalSvc == nil {
return nil
}
ids := make([]uint, 0, len(items))
visited := make(map[uint]struct{}, len(items))
for _, item := range items {
if item.Id == 0 {
continue
}
if _, ok := visited[item.Id]; ok {
continue
}
visited[item.Id] = struct{}{}
ids = append(ids, item.Id)
}
ids := make([]uint, 0, len(items))
visited := make(map[uint]struct{}, len(items))
for _, item := range items {
if item.Id == 0 {
continue
}
if _, ok := visited[item.Id]; ok {
continue
}
visited[item.Id] = struct{}{}
ids = append(ids, item.Id)
}
if len(ids) == 0 {
return nil
}
if len(ids) == 0 {
return nil
}
latestMap, err := approvalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowRecording, ids, func(db *gorm.DB) *gorm.DB {
return db.Preload("ActionUser")
})
if err != nil {
if logger != nil {
logger.Warnf("Unable to load latest approvals for recordings: %+v", err)
}
return nil
}
latestMap, err := approvalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowRecording, ids, func(db *gorm.DB) *gorm.DB {
return db.Preload("ActionUser")
})
if err != nil {
if logger != nil {
logger.Warnf("Unable to load latest approvals for recordings: %+v", err)
}
return nil
}
if len(latestMap) == 0 {
return nil
}
if len(latestMap) == 0 {
return nil
}
for i := range items {
if items[i].Id == 0 {
continue
}
if approval, ok := latestMap[items[i].Id]; ok {
items[i].LatestApproval = approval
}
}
for i := range items {
if items[i].Id == 0 {
continue
}
if approval, ok := latestMap[items[i].Id]; ok {
items[i].LatestApproval = approval
}
}
return nil
return nil
}
func AttachLatestApproval(ctx context.Context, item *entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
if item == nil || item.Id == 0 || approvalSvc == nil {
return nil
}
if item == nil || item.Id == 0 || approvalSvc == nil {
return nil
}
latest, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowRecording, item.Id, func(db *gorm.DB) *gorm.DB {
return db.Preload("ActionUser")
})
if err != nil {
if logger != nil {
logger.Warnf("Unable to load approvals for recording %d: %+v", item.Id, err)
}
return nil
}
item.LatestApproval = latest
return nil
latest, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowRecording, item.Id, func(db *gorm.DB) *gorm.DB {
return db.Preload("ActionUser")
})
if err != nil {
if logger != nil {
logger.Warnf("Unable to load approvals for recording %d: %+v", item.Id, err)
}
return nil
}
item.LatestApproval = latest
return nil
}
type productionStandardValues struct {
HenDay *float64
HenHouse *float64
FeedIntake *float64
MaxDepletion *float64
EggMass *float64
EggWeight *float64
HenDay *float64
HenHouse *float64
FeedIntake *float64
MaxDepletion *float64
EggMass *float64
EggWeight *float64
}
func AttachProductionStandards(ctx context.Context, db *gorm.DB, warnOnly bool, logger warnLogger, items ...*entity.Recording) error {
if len(items) == 0 {
return nil
}
if len(items) == 0 {
return nil
}
type standardKey struct {
standardID uint
week int
}
type standardCacheEntry struct {
values productionStandardValues
fcr *float64
}
type standardKey struct {
standardID uint
week int
}
type standardCacheEntry struct {
values productionStandardValues
fcr *float64
}
if db == nil {
return nil
}
if db == nil {
return nil
}
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
cache := make(map[standardKey]standardCacheEntry, len(items))
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
cache := make(map[standardKey]standardCacheEntry, len(items))
standardIDs := make(map[uint]struct{}, len(items))
for _, item := range items {
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
continue
}
if item.ProjectFlockKandang.ProjectFlock.ProductionStandardId > 0 {
standardIDs[item.ProjectFlockKandang.ProjectFlock.ProductionStandardId] = struct{}{}
}
}
standardIDs := make(map[uint]struct{}, len(items))
for _, item := range items {
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
continue
}
if item.ProjectFlockKandang.ProjectFlock.ProductionStandardId > 0 {
standardIDs[item.ProjectFlockKandang.ProjectFlock.ProductionStandardId] = struct{}{}
}
}
standardDetailByStd := make(map[uint]map[int]*entity.ProductionStandardDetail, len(standardIDs))
growthDetailByStd := make(map[uint]map[int]*entity.StandardGrowthDetail, len(standardIDs))
standardDetailByStd := make(map[uint]map[int]*entity.ProductionStandardDetail, len(standardIDs))
growthDetailByStd := make(map[uint]map[int]*entity.StandardGrowthDetail, len(standardIDs))
for standardID := range standardIDs {
details, err := standardDetailRepo.GetByProductionStandardID(ctx, standardID)
if err != nil {
if warnOnly {
if logger != nil {
logger.Warnf("Unable to preload production standard detail for standard %d: %+v", standardID, err)
}
} else {
return err
}
continue
}
detailMap := make(map[int]*entity.ProductionStandardDetail, len(details))
for i := range details {
detail := details[i]
detailMap[detail.Week] = &detail
}
standardDetailByStd[standardID] = detailMap
for standardID := range standardIDs {
details, err := standardDetailRepo.GetByProductionStandardID(ctx, standardID)
if err != nil {
if warnOnly {
if logger != nil {
logger.Warnf("Unable to preload production standard detail for standard %d: %+v", standardID, err)
}
} else {
return err
}
continue
}
detailMap := make(map[int]*entity.ProductionStandardDetail, len(details))
for i := range details {
detail := details[i]
detailMap[detail.Week] = &detail
}
standardDetailByStd[standardID] = detailMap
growths, err := growthDetailRepo.GetByProductionStandardID(ctx, standardID)
if err != nil {
if warnOnly {
if logger != nil {
logger.Warnf("Unable to preload standard growth detail for standard %d: %+v", standardID, err)
}
} else {
return err
}
continue
}
growthMap := make(map[int]*entity.StandardGrowthDetail, len(growths))
for i := range growths {
growth := growths[i]
growthMap[growth.Week] = &growth
}
growthDetailByStd[standardID] = growthMap
}
growths, err := growthDetailRepo.GetByProductionStandardID(ctx, standardID)
if err != nil {
if warnOnly {
if logger != nil {
logger.Warnf("Unable to preload standard growth detail for standard %d: %+v", standardID, err)
}
} else {
return err
}
continue
}
growthMap := make(map[int]*entity.StandardGrowthDetail, len(growths))
for i := range growths {
growth := growths[i]
growthMap[growth.Week] = &growth
}
growthDetailByStd[standardID] = growthMap
}
for _, item := range items {
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
continue
}
standardID := item.ProjectFlockKandang.ProjectFlock.ProductionStandardId
if standardID == 0 {
continue
}
week := RecordingWeekValue(*item)
cacheKey := standardKey{standardID: standardID, week: week}
if cached, ok := cache[cacheKey]; ok {
applyProductionStandardValues(item, cached.values, cached.fcr)
continue
}
values := productionStandardValues{}
var fcr *float64
if detailMap, ok := standardDetailByStd[standardID]; ok {
if detail, ok := detailMap[week]; ok {
values.HenDay = detail.TargetHenDayProduction
values.HenHouse = detail.TargetHenHouseProduction
values.EggMass = detail.TargetEggMass
values.EggWeight = detail.TargetEggWeight
fcr = detail.StandardFCR
}
}
if growthMap, ok := growthDetailByStd[standardID]; ok {
if growth, ok := growthMap[week]; ok {
values.FeedIntake = growth.FeedIntake
values.MaxDepletion = growth.MaxDepletion
}
}
cache[cacheKey] = standardCacheEntry{values: values, fcr: fcr}
applyProductionStandardValues(item, values, fcr)
}
for _, item := range items {
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
continue
}
standardID := item.ProjectFlockKandang.ProjectFlock.ProductionStandardId
if standardID == 0 {
continue
}
week := RecordingWeekValue(*item)
cacheKey := standardKey{standardID: standardID, week: week}
if cached, ok := cache[cacheKey]; ok {
applyProductionStandardValues(item, cached.values, cached.fcr)
continue
}
values := productionStandardValues{}
var fcr *float64
if detailMap, ok := standardDetailByStd[standardID]; ok {
if detail, ok := detailMap[week]; ok {
values.HenDay = detail.TargetHenDayProduction
values.HenHouse = detail.TargetHenHouseProduction
values.EggMass = detail.TargetEggMass
values.EggWeight = detail.TargetEggWeight
fcr = detail.StandardFCR
}
}
if growthMap, ok := growthDetailByStd[standardID]; ok {
if growth, ok := growthMap[week]; ok {
values.FeedIntake = growth.FeedIntake
values.MaxDepletion = growth.MaxDepletion
}
}
cache[cacheKey] = standardCacheEntry{values: values, fcr: fcr}
applyProductionStandardValues(item, values, fcr)
}
return nil
return nil
}
func applyProductionStandardValues(item *entity.Recording, values productionStandardValues, fcr *float64) {
item.StandardHenDay = values.HenDay
item.StandardHenHouse = values.HenHouse
item.StandardFeedIntake = values.FeedIntake
item.StandardMaxDepletion = values.MaxDepletion
item.StandardEggMass = values.EggMass
item.StandardEggWeight = values.EggWeight
item.StandardFcr = fcr
item.StandardHenDay = values.HenDay
item.StandardHenHouse = values.HenHouse
item.StandardFeedIntake = values.FeedIntake
item.StandardMaxDepletion = values.MaxDepletion
item.StandardEggMass = values.EggMass
item.StandardEggWeight = values.EggWeight
item.StandardFcr = fcr
}
func RecordingWeekValue(e entity.Recording) int {
@@ -297,7 +298,7 @@ func RecordingWeekValue(e entity.Recording) int {
}
weekBase := 1
if IsLayingRecording(e) {
weekBase = 18
weekBase = config.LayingWeekStart()
}
return ((day - 1) / 7) + weekBase
}