mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Fix logic recording transition
This commit is contained in:
@@ -496,10 +496,6 @@ func (s *fifoStockV2Service) Reflow(ctx context.Context, req ReflowRequest) (*Re
|
|||||||
if len(rollbackRes.Details) > 0 {
|
if len(rollbackRes.Details) > 0 {
|
||||||
result.Rollback.Details = append(result.Rollback.Details, rollbackRes.Details...)
|
result.Rollback.Details = append(result.Rollback.Details, rollbackRes.Details...)
|
||||||
}
|
}
|
||||||
minDesired := rollbackRes.ReleasedQty + usableRow.PendingQuantity
|
|
||||||
if desiredQty < minDesired {
|
|
||||||
desiredQty = minDesired
|
|
||||||
}
|
|
||||||
|
|
||||||
if desiredQty <= 0 {
|
if desiredQty <= 0 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -259,6 +259,10 @@ func defaultString(v, def string) string {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LayingWeekStart() int {
|
||||||
|
return TransferToLayingGrowingMaxWeek
|
||||||
|
}
|
||||||
|
|
||||||
func joinPath(parts ...string) string {
|
func joinPath(parts ...string) string {
|
||||||
out := make([]string, 0, len(parts))
|
out := make([]string, 0, len(parts))
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
|
|||||||
+17
-8
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||||
@@ -343,17 +344,22 @@ func (s productionStandardService) EnsureWeekStart(ctx context.Context, standard
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layingWeekStart := config.LayingWeekStart()
|
||||||
|
|
||||||
switch strings.ToUpper(category) {
|
switch strings.ToUpper(category) {
|
||||||
case string(utils.ProjectFlockCategoryLaying):
|
case string(utils.ProjectFlockCategoryLaying):
|
||||||
details, err := s.ProductionStandardDetailRepo.GetByProductionStandardID(ctx, standardID)
|
details, err := s.ProductionStandardDetailRepo.GetByProductionStandardID(ctx, standardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
startWeek := 0
|
if len(details) == 0 {
|
||||||
if len(details) > 0 {
|
return fiber.NewError(
|
||||||
startWeek = details[0].Week
|
fiber.StatusBadRequest,
|
||||||
|
"Standart production tidak tersedia untuk kategori laying",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if startWeek != 18 {
|
startWeek := details[0].Week
|
||||||
|
if startWeek > layingWeekStart {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock")
|
return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock")
|
||||||
}
|
}
|
||||||
case string(utils.ProjectFlockCategoryGrowing):
|
case string(utils.ProjectFlockCategoryGrowing):
|
||||||
@@ -361,10 +367,13 @@ func (s productionStandardService) EnsureWeekStart(ctx context.Context, standard
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
startWeek := 0
|
if len(details) == 0 {
|
||||||
if len(details) > 0 {
|
return fiber.NewError(
|
||||||
startWeek = details[0].Week
|
fiber.StatusBadRequest,
|
||||||
|
"Standart production tidak tersedia untuk kategori growing",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
startWeek := details[0].Week
|
||||||
if startWeek != 1 {
|
if startWeek != 1 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock")
|
return fiber.NewError(fiber.StatusBadRequest, "Week tidak sesuai dengan standart kategori project flock")
|
||||||
}
|
}
|
||||||
@@ -381,7 +390,7 @@ func (s productionStandardService) EnsureWeekAvailable(ctx context.Context, stan
|
|||||||
upperCategory := strings.ToUpper(category)
|
upperCategory := strings.ToUpper(category)
|
||||||
weekBase := 1
|
weekBase := 1
|
||||||
if upperCategory == string(utils.ProjectFlockCategoryLaying) {
|
if upperCategory == string(utils.ProjectFlockCategoryLaying) {
|
||||||
weekBase = 18
|
weekBase = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
week := ((day - 1) / 7) + weekBase
|
week := ((day - 1) / 7) + weekBase
|
||||||
if week <= 0 {
|
if week <= 0 {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dto
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
areaRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
areaRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||||
flockRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
@@ -35,13 +36,13 @@ type ChickinRelationDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockDTO struct {
|
type ProjectFlockDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Period int `json:"period"`
|
Period int `json:"period"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Flock *flockRelationDTO.FlockRelationDTO `json:"flock"`
|
Flock *flockRelationDTO.FlockRelationDTO `json:"flock"`
|
||||||
Area *areaRelationDTO.AreaRelationDTO `json:"area"`
|
Area *areaRelationDTO.AreaRelationDTO `json:"area"`
|
||||||
StandardFcr *float64 `json:"standard_fcr"`
|
StandardFcr *float64 `json:"standard_fcr"`
|
||||||
Location *locationRelationDTO.LocationRelationDTO `json:"location"`
|
Location *locationRelationDTO.LocationRelationDTO `json:"location"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockKandangDTO struct {
|
type ProjectFlockKandangDTO struct {
|
||||||
@@ -123,13 +124,13 @@ func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
|||||||
location = &mapped
|
location = &mapped
|
||||||
}
|
}
|
||||||
return ProjectFlockDTO{
|
return ProjectFlockDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Period: pfk.Period,
|
Period: pfk.Period,
|
||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
Flock: flock,
|
Flock: flock,
|
||||||
Area: area,
|
Area: area,
|
||||||
StandardFcr: resolveProjectFlockStandardFcr(e),
|
StandardFcr: resolveProjectFlockStandardFcr(e),
|
||||||
Location: location,
|
Location: location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +220,7 @@ func resolveProjectFlockStandardFcr(e entity.ProjectFlock) *float64 {
|
|||||||
}
|
}
|
||||||
week := 1
|
week := 1
|
||||||
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
week = 18
|
week = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
||||||
if detail.Week == week && detail.StandardFCR != nil {
|
if detail.Week == week && detail.StandardFCR != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
|
||||||
@@ -272,10 +273,20 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
|||||||
projectFlockId := c.QueryInt("project_flock_id", 0)
|
projectFlockId := c.QueryInt("project_flock_id", 0)
|
||||||
kandangId := c.QueryInt("kandang_id", 0)
|
kandangId := c.QueryInt("kandang_id", 0)
|
||||||
withPopulation := c.QueryBool("withpopulation", false)
|
withPopulation := c.QueryBool("withpopulation", false)
|
||||||
|
recordDateRaw := strings.TrimSpace(c.Query("record_date", ""))
|
||||||
|
var recordDate *time.Time
|
||||||
|
|
||||||
if projectFlockId == 0 || kandangId == 0 {
|
if projectFlockId == 0 || kandangId == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id or kandang_id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id or kandang_id")
|
||||||
}
|
}
|
||||||
|
if recordDateRaw != "" {
|
||||||
|
parsed, err := time.Parse("2006-01-02", recordDateRaw)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "record_date must be in YYYY-MM-DD format")
|
||||||
|
}
|
||||||
|
utc := parsed.UTC()
|
||||||
|
recordDate = &utc
|
||||||
|
}
|
||||||
|
|
||||||
result, availableStock, err := u.ProjectflockService.GetProjectFlockKandangByProjectAndKandang(c, uint(projectFlockId), uint(kandangId))
|
result, availableStock, err := u.ProjectflockService.GetProjectFlockKandangByProjectAndKandang(c, uint(projectFlockId), uint(kandangId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -300,7 +311,7 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
|||||||
mapped := warehouseDTO.ToWarehouseRelationDTO(*warehouse)
|
mapped := warehouseDTO.ToWarehouseRelationDTO(*warehouse)
|
||||||
dtoResult.Warehouse = &mapped
|
dtoResult.Warehouse = &mapped
|
||||||
}
|
}
|
||||||
if isTransition, isLaying, serr := u.ProjectflockService.GetProjectFlockKandangTransferState(c, result.Id); serr != nil {
|
if isTransition, isLaying, serr := u.ProjectflockService.GetProjectFlockKandangTransferStateAtDate(c, result.Id, recordDate); serr != nil {
|
||||||
return serr
|
return serr
|
||||||
} else {
|
} else {
|
||||||
dtoResult.IsTransition = isTransition
|
dtoResult.IsTransition = isTransition
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dto
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||||
@@ -25,17 +26,17 @@ type ProjectFlockRelationDTO struct {
|
|||||||
|
|
||||||
type ProjectFlockListDTO struct {
|
type ProjectFlockListDTO struct {
|
||||||
ProjectFlockRelationDTO
|
ProjectFlockRelationDTO
|
||||||
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
StandardFcr *float64 `json:"standard_fcr,omitempty"`
|
StandardFcr *float64 `json:"standard_fcr,omitempty"`
|
||||||
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
ProductionStandard *productionStandardDTO.ProductionStandardRelationDTO `json:"production_standard,omitempty"`
|
||||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"`
|
Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"`
|
||||||
ProjectBudgets []ProjectBudgetDTO `json:"project_budgets,omitempty"`
|
ProjectBudgets []ProjectBudgetDTO `json:"project_budgets,omitempty"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
|
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangWithProjectFlockIdDTO struct {
|
type KandangWithProjectFlockIdDTO struct {
|
||||||
@@ -203,7 +204,7 @@ func resolveProjectFlockStandardFcr(e entity.ProjectFlock) *float64 {
|
|||||||
}
|
}
|
||||||
week := 1
|
week := 1
|
||||||
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
if e.Category == string(utils.ProjectFlockCategoryLaying) {
|
||||||
week = 18
|
week = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
for _, detail := range e.ProductionStandard.ProductionStandardDetails {
|
||||||
if detail.Week == week && detail.StandardFCR != nil {
|
if detail.Week == week && detail.StandardFCR != nil {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type ProjectflockService interface {
|
|||||||
GetProjectFlockKandangPopulation(ctx *fiber.Ctx, projectFlockKandangID uint) (float64, error)
|
GetProjectFlockKandangPopulation(ctx *fiber.Ctx, projectFlockKandangID uint) (float64, error)
|
||||||
GetProjectFlockKandangChickinDate(ctx *fiber.Ctx, projectFlockKandangID uint) (*time.Time, error)
|
GetProjectFlockKandangChickinDate(ctx *fiber.Ctx, projectFlockKandangID uint) (*time.Time, error)
|
||||||
GetProjectFlockKandangTransferState(ctx *fiber.Ctx, projectFlockKandangID uint) (bool, bool, error)
|
GetProjectFlockKandangTransferState(ctx *fiber.Ctx, projectFlockKandangID uint) (bool, bool, error)
|
||||||
|
GetProjectFlockKandangTransferStateAtDate(ctx *fiber.Ctx, projectFlockKandangID uint, referenceDate *time.Time) (bool, bool, error)
|
||||||
GetPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error)
|
GetPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error)
|
||||||
GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, 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)
|
||||||
@@ -544,6 +545,10 @@ func (s projectflockService) GetProjectFlockKandangChickinDate(ctx *fiber.Ctx, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetProjectFlockKandangTransferState(ctx *fiber.Ctx, projectFlockKandangID uint) (bool, bool, error) {
|
func (s projectflockService) GetProjectFlockKandangTransferState(ctx *fiber.Ctx, projectFlockKandangID uint) (bool, bool, error) {
|
||||||
|
return s.GetProjectFlockKandangTransferStateAtDate(ctx, projectFlockKandangID, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetProjectFlockKandangTransferStateAtDate(ctx *fiber.Ctx, projectFlockKandangID uint, referenceDate *time.Time) (bool, bool, error) {
|
||||||
if projectFlockKandangID == 0 || s.TransferLayingRepo == nil || s.PivotRepo == nil {
|
if projectFlockKandangID == 0 || s.TransferLayingRepo == nil || s.PivotRepo == nil {
|
||||||
return false, false, nil
|
return false, false, nil
|
||||||
}
|
}
|
||||||
@@ -593,9 +598,12 @@ func (s projectflockService) GetProjectFlockKandangTransferState(ctx *fiber.Ctx,
|
|||||||
economicCutoffDate = physicalMoveDate
|
economicCutoffDate = physicalMoveDate
|
||||||
}
|
}
|
||||||
|
|
||||||
referenceDate := normalizeDateOnlyUTC(time.Now().UTC())
|
reference := normalizeDateOnlyUTC(time.Now().UTC())
|
||||||
isTransition := !referenceDate.Before(physicalMoveDate) && referenceDate.Before(economicCutoffDate)
|
if referenceDate != nil && !referenceDate.IsZero() {
|
||||||
isLaying := !referenceDate.Before(economicCutoffDate)
|
reference = normalizeDateOnlyUTC(referenceDate.UTC())
|
||||||
|
}
|
||||||
|
isTransition := !reference.Before(physicalMoveDate) && reference.Before(economicCutoffDate)
|
||||||
|
isLaying := !reference.Before(economicCutoffDate)
|
||||||
|
|
||||||
return isTransition, isLaying, nil
|
return isTransition, isLaying, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
productWarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/dto"
|
productWarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/dto"
|
||||||
@@ -308,7 +309,7 @@ func recordingWeekValue(e entity.Recording) int {
|
|||||||
}
|
}
|
||||||
weekBase := 1
|
weekBase := 1
|
||||||
if isLayingRecording(e) {
|
if isLayingRecording(e) {
|
||||||
weekBase = 18
|
weekBase = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
return ((day - 1) / 7) + weekBase
|
return ((day - 1) / 7) + weekBase
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ type RecordingRepository interface {
|
|||||||
GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error)
|
GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error)
|
||||||
GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error)
|
GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error)
|
||||||
GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error)
|
GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error)
|
||||||
|
GetProjectFlockKandangIDsByPopulationWarehouseIDs(ctx context.Context, tx *gorm.DB, productWarehouseIDs []uint) ([]uint, error)
|
||||||
ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error
|
ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error
|
||||||
ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
|
ValidateProductWarehousesByFlags(ctx context.Context, ids []uint, flags []string) (uint, error)
|
||||||
}
|
}
|
||||||
@@ -874,6 +875,34 @@ func (r *RecordingRepositoryImpl) GetAverageTargetMetricsByProjectFlockKandangID
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RecordingRepositoryImpl) GetProjectFlockKandangIDsByPopulationWarehouseIDs(
|
||||||
|
ctx context.Context,
|
||||||
|
tx *gorm.DB,
|
||||||
|
productWarehouseIDs []uint,
|
||||||
|
) ([]uint, error) {
|
||||||
|
if len(productWarehouseIDs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db := r.DB().WithContext(ctx)
|
||||||
|
if tx != nil {
|
||||||
|
db = tx.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kandangIDs []uint
|
||||||
|
if err := db.Table("project_flock_populations pfp").
|
||||||
|
Select("DISTINCT pc.project_flock_kandang_id").
|
||||||
|
Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
||||||
|
Where("pfp.product_warehouse_id IN ?", productWarehouseIDs).
|
||||||
|
Where("pfp.deleted_at IS NULL").
|
||||||
|
Where("pc.deleted_at IS NULL").
|
||||||
|
Pluck("pc.project_flock_kandang_id", &kandangIDs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kandangIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error {
|
func (r *RecordingRepositoryImpl) ResyncProjectFlockPopulationUsage(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error {
|
||||||
if projectFlockKandangID == 0 {
|
if projectFlockKandangID == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1039,11 +1039,75 @@ func (s *recordingService) evaluatePopulationMutationState(ctx context.Context,
|
|||||||
populationCanChange := true
|
populationCanChange := true
|
||||||
if category == strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)) {
|
if category == strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)) {
|
||||||
populationCanChange = !(transferExecuted && !recordDate.Before(transferDate))
|
populationCanChange = !(transferExecuted && !recordDate.Before(transferDate))
|
||||||
|
|
||||||
|
if transferExecuted && !recordDate.Before(transferDate) {
|
||||||
|
hasTargetLayingRecording, checkErr := s.hasAnyRecordingOnTransferTargets(ctx, transfer)
|
||||||
|
if checkErr != nil {
|
||||||
|
s.Log.Errorf("Failed to resolve target laying recording state for transfer %d: %+v", transfer.Id, checkErr)
|
||||||
|
return true, false, false, false, nil, time.Time{}, fiber.NewError(fiber.StatusInternalServerError, "Gagal memvalidasi status transisi recording")
|
||||||
|
}
|
||||||
|
if hasTargetLayingRecording {
|
||||||
|
isTransition = false
|
||||||
|
isLaying = true
|
||||||
|
} else {
|
||||||
|
today := normalizeDateOnlyUTC(time.Now().UTC())
|
||||||
|
if !today.Before(economicCutoffDate) {
|
||||||
|
isTransition = true
|
||||||
|
isLaying = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return populationCanChange, transferExecuted, isTransition, isLaying, transfer, transferDate, nil
|
return populationCanChange, transferExecuted, isTransition, isLaying, transfer, transferDate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) hasAnyRecordingOnTransferTargets(ctx context.Context, transfer *entity.LayingTransfer) (bool, error) {
|
||||||
|
if transfer == nil || transfer.Id == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetIDs, err := s.transferTargetProjectFlockKandangIDs(ctx, transfer.Id)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(targetIDs) == 0 {
|
||||||
|
// Keep existing behavior for legacy or incomplete target mapping.
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
err = s.Repository.DB().
|
||||||
|
WithContext(ctx).
|
||||||
|
Table("recordings").
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Where("project_flock_kandangs_id IN ?", targetIDs).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) transferTargetProjectFlockKandangIDs(ctx context.Context, transferID uint) ([]uint, error) {
|
||||||
|
if transferID == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetIDs []uint
|
||||||
|
err := s.Repository.DB().
|
||||||
|
WithContext(ctx).
|
||||||
|
Table("laying_transfer_targets").
|
||||||
|
Where("laying_transfer_id = ?", transferID).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Pluck("target_project_flock_kandang_id", &targetIDs).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return targetIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *recordingService) ensurePopulationMutationAllowed(ctx context.Context, recording *entity.Recording, operation string) error {
|
func (s *recordingService) ensurePopulationMutationAllowed(ctx context.Context, recording *entity.Recording, operation string) error {
|
||||||
populationCanChange, _, _, _, transfer, transferDate, err := s.evaluatePopulationMutationState(ctx, recording)
|
populationCanChange, _, _, _, transfer, transferDate, err := s.evaluatePopulationMutationState(ctx, recording)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1091,19 +1155,16 @@ func (s *recordingService) ensureDepletionMutationAllowed(ctx context.Context, r
|
|||||||
category = strings.ToUpper(strings.TrimSpace(pfk.ProjectFlock.Category))
|
category = strings.ToUpper(strings.TrimSpace(pfk.ProjectFlock.Category))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !shouldGuardDepletionMutation(category) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
transfer *entity.LayingTransfer
|
transfer *entity.LayingTransfer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
switch category {
|
transfer, err = s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx, recording.ProjectFlockKandangId)
|
||||||
case strings.ToUpper(string(utils.ProjectFlockCategoryGrowing)):
|
|
||||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedBySourceKandang(ctx, recording.ProjectFlockKandangId)
|
|
||||||
case strings.ToUpper(string(utils.ProjectFlockCategoryLaying)):
|
|
||||||
transfer, err = s.TransferLayingRepo.GetLatestApprovedByTargetKandang(ctx, recording.ProjectFlockKandangId)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil
|
return nil
|
||||||
@@ -1132,6 +1193,10 @@ func (s *recordingService) ensureDepletionMutationAllowed(ctx context.Context, r
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldGuardDepletionMutation(category string) bool {
|
||||||
|
return strings.EqualFold(strings.TrimSpace(category), string(utils.ProjectFlockCategoryGrowing))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *recordingService) tryAutoExecuteTransferForRecordingCreate(c *fiber.Ctx, pfk *entity.ProjectFlockKandang, recordTime time.Time) error {
|
func (s *recordingService) tryAutoExecuteTransferForRecordingCreate(c *fiber.Ctx, pfk *entity.ProjectFlockKandang, recordTime time.Time) error {
|
||||||
if pfk == nil || pfk.Id == 0 || s.TransferLayingRepo == nil || s.TransferLayingSvc == nil {
|
if pfk == nil || pfk.Id == 0 || s.TransferLayingRepo == nil || s.TransferLayingSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -2026,10 +2091,7 @@ func (s *recordingService) reflowApplyRecordingStocks(
|
|||||||
}
|
}
|
||||||
s.logStockTrace("reflow_apply:done", *refreshed, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desiredTotal, actualUsage, actualPending))
|
s.logStockTrace("reflow_apply:done", *refreshed, fmt.Sprintf("desired=%.3f used=%.3f pending=%.3f", desiredTotal, actualUsage, actualPending))
|
||||||
|
|
||||||
logDecrease := actualUsage
|
logDecrease := recordingStockRollbackQty(*refreshed)
|
||||||
if actualPending > 0 {
|
|
||||||
logDecrease += actualPending
|
|
||||||
}
|
|
||||||
if logDecrease > 0 && shouldWriteLog {
|
if logDecrease > 0 && shouldWriteLog {
|
||||||
log := &entity.StockLog{
|
log := &entity.StockLog{
|
||||||
ProductWarehouseId: refreshed.ProductWarehouseId,
|
ProductWarehouseId: refreshed.ProductWarehouseId,
|
||||||
@@ -2073,11 +2135,8 @@ func (s *recordingService) reflowResetRecordingStocks(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUsage := 0.0
|
rollbackQty := recordingStockRollbackQty(stock)
|
||||||
if stock.UsageQty != nil {
|
s.logStockTrace("reflow_reset:start", stock, fmt.Sprintf("rollback_qty=%.3f", rollbackQty))
|
||||||
currentUsage = *stock.UsageQty
|
|
||||||
}
|
|
||||||
s.logStockTrace("reflow_reset:start", stock, "")
|
|
||||||
|
|
||||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2094,13 +2153,13 @@ func (s *recordingService) reflowResetRecordingStocks(
|
|||||||
s.Log.Errorf("Failed to reflow FIFO v2 rollback for recording stock %d: %+v", stock.Id, err)
|
s.Log.Errorf("Failed to reflow FIFO v2 rollback for recording stock %d: %+v", stock.Id, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.logStockTrace("reflow_reset:done", stock, "")
|
s.logStockTrace("reflow_reset:done", stock, fmt.Sprintf("rollback_qty=%.3f", rollbackQty))
|
||||||
|
|
||||||
if currentUsage > 0 && shouldWriteLog {
|
if rollbackQty > 0 && shouldWriteLog {
|
||||||
log := &entity.StockLog{
|
log := &entity.StockLog{
|
||||||
ProductWarehouseId: stock.ProductWarehouseId,
|
ProductWarehouseId: stock.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
Increase: currentUsage,
|
Increase: rollbackQty,
|
||||||
LoggableType: string(utils.StockLogTypeRecording),
|
LoggableType: string(utils.StockLogTypeRecording),
|
||||||
LoggableId: stock.RecordingId,
|
LoggableId: stock.RecordingId,
|
||||||
Notes: note,
|
Notes: note,
|
||||||
@@ -2114,6 +2173,24 @@ func (s *recordingService) reflowResetRecordingStocks(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recordingStockRollbackQty(stock entity.RecordingStock) float64 {
|
||||||
|
usage := 0.0
|
||||||
|
if stock.UsageQty != nil {
|
||||||
|
usage = *stock.UsageQty
|
||||||
|
}
|
||||||
|
pending := 0.0
|
||||||
|
if stock.PendingQty != nil {
|
||||||
|
pending = *stock.PendingQty
|
||||||
|
}
|
||||||
|
if usage < 0 {
|
||||||
|
usage = 0
|
||||||
|
}
|
||||||
|
if pending < 0 {
|
||||||
|
pending = 0
|
||||||
|
}
|
||||||
|
return usage + pending
|
||||||
|
}
|
||||||
|
|
||||||
type desiredStock struct {
|
type desiredStock struct {
|
||||||
Usage float64
|
Usage float64
|
||||||
Pending float64
|
Pending float64
|
||||||
@@ -2627,19 +2704,8 @@ func (s *recordingService) resyncPopulationUsageForDepletions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(sourceWarehouseIDs) > 0 {
|
if len(sourceWarehouseIDs) > 0 {
|
||||||
db := s.Repository.DB().WithContext(ctx)
|
sourceKandangIDs, err := s.Repository.GetProjectFlockKandangIDsByPopulationWarehouseIDs(ctx, tx, sourceWarehouseIDs)
|
||||||
if tx != nil {
|
if err != nil {
|
||||||
db = tx.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceKandangIDs []uint
|
|
||||||
if err := db.Table("project_flock_populations pfp").
|
|
||||||
Select("DISTINCT pc.project_flock_kandang_id").
|
|
||||||
Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
|
||||||
Where("pfp.product_warehouse_id IN ?", sourceWarehouseIDs).
|
|
||||||
Where("pfp.deleted_at IS NULL").
|
|
||||||
Where("pc.deleted_at IS NULL").
|
|
||||||
Pluck("pc.project_flock_kandang_id", &sourceKandangIDs).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2651,62 +2717,7 @@ func (s *recordingService) resyncPopulationUsageForDepletions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for kandangID := range kandangIDs {
|
for kandangID := range kandangIDs {
|
||||||
if err := s.resyncPopulationUsageByProjectFlockKandang(ctx, tx, kandangID); err != nil {
|
if err := s.Repository.ResyncProjectFlockPopulationUsage(ctx, tx, kandangID); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *recordingService) resyncPopulationUsageByProjectFlockKandang(ctx context.Context, tx *gorm.DB, projectFlockKandangID uint) error {
|
|
||||||
if projectFlockKandangID == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
db := s.Repository.DB().WithContext(ctx)
|
|
||||||
if tx != nil {
|
|
||||||
db = tx.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
var populationIDs []uint
|
|
||||||
if err := db.Table("project_flock_populations pfp").
|
|
||||||
Select("pfp.id").
|
|
||||||
Joins("JOIN project_chickins pc ON pc.id = pfp.project_chickin_id").
|
|
||||||
Where("pc.project_flock_kandang_id = ?", projectFlockKandangID).
|
|
||||||
Pluck("pfp.id", &populationIDs).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(populationIDs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type usageRow struct {
|
|
||||||
StockableID uint `gorm:"column:stockable_id"`
|
|
||||||
Used float64 `gorm:"column:used"`
|
|
||||||
}
|
|
||||||
var usageRows []usageRow
|
|
||||||
if err := db.Table("stock_allocations").
|
|
||||||
Select("stockable_id, COALESCE(SUM(qty), 0) AS used").
|
|
||||||
Where("stockable_type = ?", fifo.StockableKeyProjectFlockPopulation.String()).
|
|
||||||
Where("status = ?", entity.StockAllocationStatusActive).
|
|
||||||
Where("allocation_purpose = ?", entity.StockAllocationPurposeConsume).
|
|
||||||
Where("stockable_id IN ?", populationIDs).
|
|
||||||
Group("stockable_id").
|
|
||||||
Scan(&usageRows).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.Model(&entity.ProjectFlockPopulation{}).
|
|
||||||
Where("id IN ?", populationIDs).
|
|
||||||
Update("total_used_qty", 0).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, row := range usageRows {
|
|
||||||
if err := db.Model(&entity.ProjectFlockPopulation{}).
|
|
||||||
Where("id = ?", row.StockableID).
|
|
||||||
Update("total_used_qty", row.Used).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
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"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||||
@@ -380,12 +381,13 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
weekBase := 1
|
weekBase := 1
|
||||||
if strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) {
|
isLayingCategory := strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying))
|
||||||
weekBase = 18
|
if isLayingCategory {
|
||||||
|
weekBase = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
if req.Week < weekBase {
|
if req.Week < weekBase {
|
||||||
if weekBase == 18 {
|
if isLayingCategory {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 18 for laying projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
||||||
}
|
}
|
||||||
@@ -399,8 +401,8 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file
|
|||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate uniformity week sequence")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate uniformity week sequence")
|
||||||
}
|
}
|
||||||
if latestWeek == 0 && req.Week != weekBase {
|
if latestWeek == 0 && req.Week != weekBase {
|
||||||
if weekBase == 18 {
|
if isLayingCategory {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 18 for laying projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
||||||
}
|
}
|
||||||
@@ -474,7 +476,7 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
s.Log.Errorf("Failed to create uniformity: %+v", err)
|
s.Log.Errorf("Failed to create uniformity: %+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.DocumentSvc != nil {
|
if s.DocumentSvc != nil {
|
||||||
actorIDCopy := actorID
|
actorIDCopy := actorID
|
||||||
@@ -575,12 +577,13 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
weekBase := 1
|
weekBase := 1
|
||||||
if strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) {
|
isLayingCategory := strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying))
|
||||||
weekBase = 18
|
if isLayingCategory {
|
||||||
|
weekBase = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
if targetWeek < weekBase {
|
if targetWeek < weekBase {
|
||||||
if weekBase == 18 {
|
if isLayingCategory {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 18 for laying projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("week must start from %d for laying projects", weekBase))
|
||||||
}
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "week must start from 1 for growing projects")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
|
||||||
repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories"
|
repportRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/repositories"
|
||||||
@@ -256,11 +257,11 @@ func (s *repportService) GetProductionResult(ctx *fiber.Ctx, params *validation.
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
recordsPerWeek = 7
|
recordsPerWeek = 7
|
||||||
defaultStartWoa = 18
|
|
||||||
defaultStdBw = 1951
|
defaultStdBw = 1951
|
||||||
defaultBw = 0
|
defaultBw = 0
|
||||||
defaultUniformText = "90% up"
|
defaultUniformText = "90% up"
|
||||||
)
|
)
|
||||||
|
defaultStartWoa := config.LayingWeekStart()
|
||||||
|
|
||||||
if params.Limit <= 0 {
|
if params.Limit <= 0 {
|
||||||
params.Limit = 10
|
params.Limit = 10
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
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"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
rProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -13,31 +14,31 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type warnLogger interface {
|
type warnLogger interface {
|
||||||
Warnf(format string, args ...any)
|
Warnf(format string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
type productWarehouseExistsRepo interface {
|
type productWarehouseExistsRepo interface {
|
||||||
ExistsByID(ctx context.Context, id uint) (bool, error)
|
ExistsByID(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordingValidationRepo interface {
|
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 {
|
func EnsureProductWarehousesExist(ctx context.Context, repo productWarehouseExistsRepo, ids []uint) error {
|
||||||
if repo == nil || len(ids) == 0 {
|
if repo == nil || len(ids) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
ok, err := repo.ExistsByID(ctx, id)
|
ok, err := repo.ExistsByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("product warehouse %d not found", id)
|
return fmt.Errorf("product warehouse %d not found", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnsureProductWarehousesByFlags(ctx context.Context, repo recordingValidationRepo, ids []uint, flags []string, label string) error {
|
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 {
|
func ComputeDepletionRate(prevRecording *entity.Recording, currentDepletion float64, totalChick int64) float64 {
|
||||||
base := 0.0
|
base := 0.0
|
||||||
if prevRecording != nil && prevRecording.TotalChickQty != nil && *prevRecording.TotalChickQty > 0 {
|
if prevRecording != nil && prevRecording.TotalChickQty != nil && *prevRecording.TotalChickQty > 0 {
|
||||||
base = *prevRecording.TotalChickQty
|
base = *prevRecording.TotalChickQty
|
||||||
} else if totalChick > 0 {
|
} else if totalChick > 0 {
|
||||||
base = float64(totalChick) + currentDepletion
|
base = float64(totalChick) + currentDepletion
|
||||||
}
|
}
|
||||||
if base <= 0 {
|
if base <= 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return (currentDepletion / base) * 100
|
return (currentDepletion / base) * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
func AttachLatestApprovals(ctx context.Context, items []entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
|
func AttachLatestApprovals(ctx context.Context, items []entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
|
||||||
if len(items) == 0 || approvalSvc == nil {
|
if len(items) == 0 || approvalSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ids := make([]uint, 0, len(items))
|
ids := make([]uint, 0, len(items))
|
||||||
visited := make(map[uint]struct{}, len(items))
|
visited := make(map[uint]struct{}, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.Id == 0 {
|
if item.Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := visited[item.Id]; ok {
|
if _, ok := visited[item.Id]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
visited[item.Id] = struct{}{}
|
visited[item.Id] = struct{}{}
|
||||||
ids = append(ids, item.Id)
|
ids = append(ids, item.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
latestMap, err := approvalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowRecording, ids, func(db *gorm.DB) *gorm.DB {
|
latestMap, err := approvalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowRecording, ids, func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("ActionUser")
|
return db.Preload("ActionUser")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
logger.Warnf("Unable to load latest approvals for recordings: %+v", err)
|
logger.Warnf("Unable to load latest approvals for recordings: %+v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(latestMap) == 0 {
|
if len(latestMap) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range items {
|
for i := range items {
|
||||||
if items[i].Id == 0 {
|
if items[i].Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if approval, ok := latestMap[items[i].Id]; ok {
|
if approval, ok := latestMap[items[i].Id]; ok {
|
||||||
items[i].LatestApproval = approval
|
items[i].LatestApproval = approval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AttachLatestApproval(ctx context.Context, item *entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
|
func AttachLatestApproval(ctx context.Context, item *entity.Recording, approvalSvc commonSvc.ApprovalService, logger warnLogger) error {
|
||||||
if item == nil || item.Id == 0 || approvalSvc == nil {
|
if item == nil || item.Id == 0 || approvalSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
latest, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowRecording, item.Id, func(db *gorm.DB) *gorm.DB {
|
latest, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowRecording, item.Id, func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("ActionUser")
|
return db.Preload("ActionUser")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
logger.Warnf("Unable to load approvals for recording %d: %+v", item.Id, err)
|
logger.Warnf("Unable to load approvals for recording %d: %+v", item.Id, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
item.LatestApproval = latest
|
item.LatestApproval = latest
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type productionStandardValues struct {
|
type productionStandardValues struct {
|
||||||
HenDay *float64
|
HenDay *float64
|
||||||
HenHouse *float64
|
HenHouse *float64
|
||||||
FeedIntake *float64
|
FeedIntake *float64
|
||||||
MaxDepletion *float64
|
MaxDepletion *float64
|
||||||
EggMass *float64
|
EggMass *float64
|
||||||
EggWeight *float64
|
EggWeight *float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func AttachProductionStandards(ctx context.Context, db *gorm.DB, warnOnly bool, logger warnLogger, items ...*entity.Recording) error {
|
func AttachProductionStandards(ctx context.Context, db *gorm.DB, warnOnly bool, logger warnLogger, items ...*entity.Recording) error {
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type standardKey struct {
|
type standardKey struct {
|
||||||
standardID uint
|
standardID uint
|
||||||
week int
|
week int
|
||||||
}
|
}
|
||||||
type standardCacheEntry struct {
|
type standardCacheEntry struct {
|
||||||
values productionStandardValues
|
values productionStandardValues
|
||||||
fcr *float64
|
fcr *float64
|
||||||
}
|
}
|
||||||
|
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
|
||||||
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
|
||||||
cache := make(map[standardKey]standardCacheEntry, len(items))
|
cache := make(map[standardKey]standardCacheEntry, len(items))
|
||||||
|
|
||||||
standardIDs := make(map[uint]struct{}, len(items))
|
standardIDs := make(map[uint]struct{}, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
|
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if item.ProjectFlockKandang.ProjectFlock.ProductionStandardId > 0 {
|
if item.ProjectFlockKandang.ProjectFlock.ProductionStandardId > 0 {
|
||||||
standardIDs[item.ProjectFlockKandang.ProjectFlock.ProductionStandardId] = struct{}{}
|
standardIDs[item.ProjectFlockKandang.ProjectFlock.ProductionStandardId] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
standardDetailByStd := make(map[uint]map[int]*entity.ProductionStandardDetail, len(standardIDs))
|
standardDetailByStd := make(map[uint]map[int]*entity.ProductionStandardDetail, len(standardIDs))
|
||||||
growthDetailByStd := make(map[uint]map[int]*entity.StandardGrowthDetail, len(standardIDs))
|
growthDetailByStd := make(map[uint]map[int]*entity.StandardGrowthDetail, len(standardIDs))
|
||||||
|
|
||||||
for standardID := range standardIDs {
|
for standardID := range standardIDs {
|
||||||
details, err := standardDetailRepo.GetByProductionStandardID(ctx, standardID)
|
details, err := standardDetailRepo.GetByProductionStandardID(ctx, standardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if warnOnly {
|
if warnOnly {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
logger.Warnf("Unable to preload production standard detail for standard %d: %+v", standardID, err)
|
logger.Warnf("Unable to preload production standard detail for standard %d: %+v", standardID, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
detailMap := make(map[int]*entity.ProductionStandardDetail, len(details))
|
detailMap := make(map[int]*entity.ProductionStandardDetail, len(details))
|
||||||
for i := range details {
|
for i := range details {
|
||||||
detail := details[i]
|
detail := details[i]
|
||||||
detailMap[detail.Week] = &detail
|
detailMap[detail.Week] = &detail
|
||||||
}
|
}
|
||||||
standardDetailByStd[standardID] = detailMap
|
standardDetailByStd[standardID] = detailMap
|
||||||
|
|
||||||
growths, err := growthDetailRepo.GetByProductionStandardID(ctx, standardID)
|
growths, err := growthDetailRepo.GetByProductionStandardID(ctx, standardID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if warnOnly {
|
if warnOnly {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
logger.Warnf("Unable to preload standard growth detail for standard %d: %+v", standardID, err)
|
logger.Warnf("Unable to preload standard growth detail for standard %d: %+v", standardID, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
growthMap := make(map[int]*entity.StandardGrowthDetail, len(growths))
|
growthMap := make(map[int]*entity.StandardGrowthDetail, len(growths))
|
||||||
for i := range growths {
|
for i := range growths {
|
||||||
growth := growths[i]
|
growth := growths[i]
|
||||||
growthMap[growth.Week] = &growth
|
growthMap[growth.Week] = &growth
|
||||||
}
|
}
|
||||||
growthDetailByStd[standardID] = growthMap
|
growthDetailByStd[standardID] = growthMap
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
|
if item == nil || item.ProjectFlockKandang == nil || item.ProjectFlockKandang.ProjectFlock.Id == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
standardID := item.ProjectFlockKandang.ProjectFlock.ProductionStandardId
|
standardID := item.ProjectFlockKandang.ProjectFlock.ProductionStandardId
|
||||||
if standardID == 0 {
|
if standardID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
week := RecordingWeekValue(*item)
|
week := RecordingWeekValue(*item)
|
||||||
cacheKey := standardKey{standardID: standardID, week: week}
|
cacheKey := standardKey{standardID: standardID, week: week}
|
||||||
if cached, ok := cache[cacheKey]; ok {
|
if cached, ok := cache[cacheKey]; ok {
|
||||||
applyProductionStandardValues(item, cached.values, cached.fcr)
|
applyProductionStandardValues(item, cached.values, cached.fcr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
values := productionStandardValues{}
|
values := productionStandardValues{}
|
||||||
var fcr *float64
|
var fcr *float64
|
||||||
if detailMap, ok := standardDetailByStd[standardID]; ok {
|
if detailMap, ok := standardDetailByStd[standardID]; ok {
|
||||||
if detail, ok := detailMap[week]; ok {
|
if detail, ok := detailMap[week]; ok {
|
||||||
values.HenDay = detail.TargetHenDayProduction
|
values.HenDay = detail.TargetHenDayProduction
|
||||||
values.HenHouse = detail.TargetHenHouseProduction
|
values.HenHouse = detail.TargetHenHouseProduction
|
||||||
values.EggMass = detail.TargetEggMass
|
values.EggMass = detail.TargetEggMass
|
||||||
values.EggWeight = detail.TargetEggWeight
|
values.EggWeight = detail.TargetEggWeight
|
||||||
fcr = detail.StandardFCR
|
fcr = detail.StandardFCR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if growthMap, ok := growthDetailByStd[standardID]; ok {
|
if growthMap, ok := growthDetailByStd[standardID]; ok {
|
||||||
if growth, ok := growthMap[week]; ok {
|
if growth, ok := growthMap[week]; ok {
|
||||||
values.FeedIntake = growth.FeedIntake
|
values.FeedIntake = growth.FeedIntake
|
||||||
values.MaxDepletion = growth.MaxDepletion
|
values.MaxDepletion = growth.MaxDepletion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache[cacheKey] = standardCacheEntry{values: values, fcr: fcr}
|
cache[cacheKey] = standardCacheEntry{values: values, fcr: fcr}
|
||||||
applyProductionStandardValues(item, values, fcr)
|
applyProductionStandardValues(item, values, fcr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyProductionStandardValues(item *entity.Recording, values productionStandardValues, fcr *float64) {
|
func applyProductionStandardValues(item *entity.Recording, values productionStandardValues, fcr *float64) {
|
||||||
item.StandardHenDay = values.HenDay
|
item.StandardHenDay = values.HenDay
|
||||||
item.StandardHenHouse = values.HenHouse
|
item.StandardHenHouse = values.HenHouse
|
||||||
item.StandardFeedIntake = values.FeedIntake
|
item.StandardFeedIntake = values.FeedIntake
|
||||||
item.StandardMaxDepletion = values.MaxDepletion
|
item.StandardMaxDepletion = values.MaxDepletion
|
||||||
item.StandardEggMass = values.EggMass
|
item.StandardEggMass = values.EggMass
|
||||||
item.StandardEggWeight = values.EggWeight
|
item.StandardEggWeight = values.EggWeight
|
||||||
item.StandardFcr = fcr
|
item.StandardFcr = fcr
|
||||||
}
|
}
|
||||||
|
|
||||||
func RecordingWeekValue(e entity.Recording) int {
|
func RecordingWeekValue(e entity.Recording) int {
|
||||||
@@ -297,7 +298,7 @@ func RecordingWeekValue(e entity.Recording) int {
|
|||||||
}
|
}
|
||||||
weekBase := 1
|
weekBase := 1
|
||||||
if IsLayingRecording(e) {
|
if IsLayingRecording(e) {
|
||||||
weekBase = 18
|
weekBase = config.LayingWeekStart()
|
||||||
}
|
}
|
||||||
return ((day - 1) / 7) + weekBase
|
return ((day - 1) / 7) + weekBase
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user