Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into FEAT/BE/report_customer_payment

This commit is contained in:
aguhh18
2026-01-14 20:10:31 +07:00
35 changed files with 1249 additions and 526 deletions
@@ -14,44 +14,83 @@ import (
// === DTO Structs ===
type RecordingProjectFlockDTO struct {
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
FlockName string `json:"flock_name"`
ProjectFlockCategory string `json:"project_flock_category"`
Period int `json:"period"`
ProductionStandart *RecordingProductionStandardDTO `json:"production_standart,omitempty"`
Fcr *RecordingFcrDTO `json:"fcr,omitempty"`
TotalChickQty float64 `json:"total_chick_qty"`
}
type RecordingProductionStandardDTO struct {
Id uint `json:"id"`
Week int `json:"week"`
Name string `json:"name"`
HenDayStd float64 `json:"hen_day_std"`
HenHouseStd float64 `json:"hen_house_std"`
FeedIntakeStd float64 `json:"feed_intake_std"`
MaxDepletionStd float64 `json:"max_depletion_std"`
EggMassStd float64 `json:"egg_mass_std"`
EggWeightStd float64 `json:"egg_weight_std"`
}
type RecordingFcrDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
FcrStd float64 `json:"fcr_std"`
}
type RecordingAreaDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
}
type RecordingLocationDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
}
type RecordingWarehouseDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Area *RecordingAreaDTO `json:"area,omitempty"`
Location *RecordingLocationDTO `json:"location,omitempty"`
}
type RecordingRelationDTO struct {
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
RecordDatetime time.Time `json:"record_datetime"`
Day int `json:"day"`
ProjectFlockCategory string `json:"project_flock_category"`
TotalDepletionQty float64 `json:"total_depletion_qty"`
CumDepletionRate float64 `json:"cum_depletion_rate"`
CumIntake int `json:"cum_intake"`
FcrValue float64 `json:"fcr_value"`
TotalChickQty float64 `json:"total_chick_qty"`
HenDay float64 `json:"hen_day"`
HenHouse float64 `json:"hen_house"`
FeedIntake float64 `json:"feed_intake"`
EggMass float64 `json:"egg_mass"`
EggWeight float64 `json:"egg_weight"`
StandardHenDay *float64 `json:"hen_day_std,omitempty"`
StandardHenHouse *float64 `json:"hen_house_std,omitempty"`
StandardFeedIntake *float64 `json:"feed_intake_std,omitempty"`
StandardMaxDepletion *float64 `json:"max_depletion_std,omitempty"`
StandardEggMass *float64 `json:"egg_mass_std,omitempty"`
StandardEggWeight *float64 `json:"egg_weight_std,omitempty"`
StandardFcr *float64 `json:"fcr_std,omitempty"`
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
Id uint `json:"id"`
ProjectFlock RecordingProjectFlockDTO `json:"project_flock"`
RecordDatetime time.Time `json:"record_datetime"`
Day int `json:"day"`
TotalDepletionQty float64 `json:"total_depletion_qty"`
CumDepletionRate float64 `json:"cum_depletion_rate"`
CumIntake int `json:"cum_intake"`
FcrValue float64 `json:"fcr_value"`
HenDay float64 `json:"hen_day"`
HenHouse float64 `json:"hen_house"`
FeedIntake float64 `json:"feed_intake"`
EggMass float64 `json:"egg_mass"`
EggWeight float64 `json:"egg_weight"`
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
}
type RecordingListDTO struct {
RecordingRelationDTO
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type RecordingDetailDTO struct {
RecordingListDTO
Depletions []RecordingDepletionDTO `json:"depletions"`
Stocks []RecordingStockDTO `json:"stocks"`
Eggs []RecordingEggDTO `json:"eggs"`
Warehouse *RecordingWarehouseDTO `json:"warehouse,omitempty"`
ProductCategory string `json:"product_category"`
Depletions []RecordingDepletionDTO `json:"depletions"`
Stocks []RecordingStockDTO `json:"stocks"`
Eggs []RecordingEggDTO `json:"eggs"`
}
type RecordingDepletionDTO struct {
@@ -63,7 +102,7 @@ type RecordingDepletionDTO struct {
type RecordingStockDTO struct {
ProductWarehouseId uint `json:"product_warehouse_id"`
UsageAmount float64 `json:"usage_amount"`
PendingQty *float64 `json:"pending_qty,omitempty"`
PendingQty float64 `json:"pending_qty"`
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}
@@ -75,117 +114,10 @@ type RecordingEggDTO struct {
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
}
type RecordingProductWarehouseDTO struct {
Id uint `json:"id"`
ProductId uint `json:"product_id"`
ProductName string `json:"product_name"`
WarehouseId uint `json:"warehouse_id"`
WarehouseName string `json:"warehouse_name"`
}
// === Mapper Functions ===
func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
var (
projectFlockCategory string
day int
totalDepletionQty float64
cumDepletionRate float64
cumIntake int
fcrValue float64
totalChickQty float64
henDay float64
henHouse float64
feedIntake float64
eggMass float64
eggWeight float64
)
if e.Day != nil {
day = *e.Day
}
if e.TotalDepletionQty != nil {
totalDepletionQty = *e.TotalDepletionQty
}
if e.CumDepletionRate != nil {
cumDepletionRate = *e.CumDepletionRate
}
if e.CumIntake != nil {
cumIntake = *e.CumIntake
}
if e.FcrValue != nil {
fcrValue = *e.FcrValue
}
if e.TotalChickQty != nil {
totalChickQty = *e.TotalChickQty
}
if e.HenDay != nil {
henDay = *e.HenDay
}
if e.HenHouse != nil {
henHouse = *e.HenHouse
}
if e.FeedIntake != nil {
feedIntake = *e.FeedIntake
}
if e.EggMass != nil {
eggMass = *e.EggMass
}
if e.EggWeight != nil {
eggWeight = *e.EggWeight
}
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.ProjectFlock.Id != 0 {
category := e.ProjectFlockKandang.ProjectFlock.Category
projectFlockCategory = category
}
latestApproval := defaultRecordingLatestApproval(e)
if e.LatestApproval != nil {
snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
latestApproval = snapshot
}
return RecordingRelationDTO{
Id: e.Id,
ProjectFlockKandangId: e.ProjectFlockKandangId,
RecordDatetime: e.RecordDatetime,
Day: day,
ProjectFlockCategory: projectFlockCategory,
TotalDepletionQty: totalDepletionQty,
CumDepletionRate: cumDepletionRate,
CumIntake: cumIntake,
FcrValue: fcrValue,
TotalChickQty: totalChickQty,
HenDay: henDay,
HenHouse: henHouse,
FeedIntake: feedIntake,
EggMass: eggMass,
EggWeight: eggWeight,
StandardHenDay: e.StandardHenDay,
StandardHenHouse: e.StandardHenHouse,
StandardFeedIntake: e.StandardFeedIntake,
StandardMaxDepletion: e.StandardMaxDepletion,
StandardEggMass: e.StandardEggMass,
StandardEggWeight: e.StandardEggWeight,
StandardFcr: e.StandardFcr,
Approval: latestApproval,
}
}
func ToRecordingListDTO(e entity.Recording) RecordingListDTO {
var createdUser *userDTO.UserRelationDTO
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
createdUser = &mapped
}
return RecordingListDTO{
RecordingRelationDTO: ToRecordingRelationDTO(e),
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser,
}
return toRecordingListDTO(e)
}
func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO {
@@ -197,20 +129,15 @@ func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO {
}
func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
listDTO := ToRecordingListDTO(e)
var eggs []RecordingEggDTO
if strings.EqualFold(listDTO.ProjectFlockCategory, string(utils.ProjectFlockCategoryLaying)) {
eggs = ToRecordingEggDTOs(e.Eggs)
} else if len(e.Eggs) > 0 {
eggs = ToRecordingEggDTOs(e.Eggs)
}
listDTO := toRecordingListDTO(e)
return RecordingDetailDTO{
RecordingListDTO: listDTO,
Depletions: ToRecordingDepletionDTOs(e.Depletions),
Stocks: ToRecordingStockDTOs(e.Stocks),
Eggs: eggs,
Warehouse: recordingWarehouseDTO(e),
ProductCategory: recordingProductCategory(e),
Depletions: ToRecordingDepletionDTOs(e.Depletions),
Stocks: ToRecordingStockDTOs(e.Stocks),
Eggs: ToRecordingEggDTOs(e.Eggs),
}
}
@@ -233,11 +160,15 @@ func ToRecordingStockDTOs(stocks []entity.RecordingStock) []RecordingStockDTO {
if s.UsageQty != nil {
usageAmount = *s.UsageQty
}
var pendingQty float64
if s.PendingQty != nil {
pendingQty = *s.PendingQty
}
result[i] = RecordingStockDTO{
ProductWarehouseId: s.ProductWarehouseId,
UsageAmount: usageAmount,
PendingQty: s.PendingQty,
PendingQty: pendingQty,
ProductWarehouse: mapProductWarehouseDTO(&s.ProductWarehouse),
}
}
@@ -258,6 +189,184 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO {
return result
}
func toRecordingListDTO(e entity.Recording) RecordingListDTO {
relation := toRecordingRelationDTO(e)
var createdUser *userDTO.UserRelationDTO
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
createdUser = &mapped
}
return RecordingListDTO{
RecordingRelationDTO: relation,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser,
}
}
func toRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
latestApproval := defaultRecordingLatestApproval(e)
if e.LatestApproval != nil {
snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
latestApproval = snapshot
}
return RecordingRelationDTO{
Id: e.Id,
ProjectFlock: toRecordingProjectFlockDTO(e),
RecordDatetime: e.RecordDatetime,
Day: intValue(e.Day),
TotalDepletionQty: floatValue(e.TotalDepletionQty),
CumDepletionRate: floatValue(e.CumDepletionRate),
CumIntake: intValue(e.CumIntake),
FcrValue: floatValue(e.FcrValue),
HenDay: floatValue(e.HenDay),
HenHouse: floatValue(e.HenHouse),
FeedIntake: floatValue(e.FeedIntake),
EggMass: floatValue(e.EggMass),
EggWeight: floatValue(e.EggWeight),
Approval: latestApproval,
}
}
func toRecordingProjectFlockDTO(e entity.Recording) RecordingProjectFlockDTO {
result := RecordingProjectFlockDTO{
ProjectFlockKandangId: e.ProjectFlockKandangId,
}
pfk := e.ProjectFlockKandang
if pfk == nil {
return result
}
if pfk.ProjectFlock.Id != 0 {
result.FlockName = pfk.ProjectFlock.FlockName
if pfk.ProjectFlock.Category != "" {
result.ProjectFlockCategory = strings.ToUpper(pfk.ProjectFlock.Category)
}
}
result.Period = pfk.Period
if pfk.ProjectFlock.ProductionStandard.Id != 0 {
result.ProductionStandart = &RecordingProductionStandardDTO{
Id: pfk.ProjectFlock.ProductionStandard.Id,
Week: recordingWeekValue(e),
Name: pfk.ProjectFlock.ProductionStandard.Name,
HenDayStd: floatValue(e.StandardHenDay),
HenHouseStd: floatValue(e.StandardHenHouse),
FeedIntakeStd: floatValue(e.StandardFeedIntake),
MaxDepletionStd: floatValue(e.StandardMaxDepletion),
EggMassStd: floatValue(e.StandardEggMass),
EggWeightStd: floatValue(e.StandardEggWeight),
}
}
if pfk.ProjectFlock.Fcr.Id != 0 || e.StandardFcr != nil {
result.Fcr = &RecordingFcrDTO{
Id: pfk.ProjectFlock.Fcr.Id,
Name: pfk.ProjectFlock.Fcr.Name,
FcrStd: floatValue(e.StandardFcr),
}
}
result.TotalChickQty = floatValue(e.TotalChickQty)
return result
}
func recordingWeekValue(e entity.Recording) int {
day := intValue(e.Day)
if day <= 0 {
return 0
}
weekBase := 1
if isLayingRecording(e) {
weekBase = 18
}
return ((day - 1) / 7) + weekBase
}
func isLayingRecording(e entity.Recording) bool {
if e.ProjectFlockKandang == nil {
return false
}
return strings.EqualFold(e.ProjectFlockKandang.ProjectFlock.Category, string(utils.ProjectFlockCategoryLaying))
}
func recordingProductCategory(e entity.Recording) string {
if e.ProjectFlockKandang == nil {
return ""
}
project := e.ProjectFlockKandang.ProjectFlock
if project.Id == 0 {
return ""
}
if project.ProductionStandard.Id != 0 && project.ProductionStandard.ProjectCategory != "" {
return strings.ToUpper(project.ProductionStandard.ProjectCategory)
}
if project.Category != "" {
return strings.ToUpper(project.Category)
}
return ""
}
func recordingWarehouseDTO(e entity.Recording) *RecordingWarehouseDTO {
pw := primaryProductWarehouse(e)
if pw == nil || pw.Warehouse.Id == 0 {
return nil
}
return mapWarehouseDTO(&pw.Warehouse)
}
func primaryProductWarehouse(e entity.Recording) *entity.ProductWarehouse {
if len(e.Stocks) > 0 {
pw := e.Stocks[0].ProductWarehouse
if pw.Id != 0 {
return &pw
}
}
if len(e.Depletions) > 0 {
pw := e.Depletions[0].ProductWarehouse
if pw.Id != 0 {
return &pw
}
}
if len(e.Eggs) > 0 {
pw := e.Eggs[0].ProductWarehouse
if pw.Id != 0 {
return &pw
}
}
return nil
}
func mapWarehouseDTO(wh *entity.Warehouse) *RecordingWarehouseDTO {
if wh == nil || wh.Id == 0 {
return nil
}
dto := &RecordingWarehouseDTO{
Id: wh.Id,
Name: wh.Name,
}
if wh.Area.Id != 0 {
dto.Area = &RecordingAreaDTO{
Id: wh.Area.Id,
Name: wh.Area.Name,
}
}
if wh.Location != nil && wh.Location.Id != 0 {
dto.Location = &RecordingLocationDTO{
Id: wh.Location.Id,
Name: wh.Location.Name,
Address: wh.Location.Address,
}
}
return dto
}
func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO {
if pw == nil {
return productWarehouseDTO.ProductWarehouseDTO{}
@@ -271,6 +380,20 @@ func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.Pro
return *mapped
}
func floatValue(value *float64) float64 {
if value == nil {
return 0
}
return *value
}
func intValue(value *int) int {
if value == nil {
return 0
}
return *value
}
func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalRelationDTO {
result := approvalDTO.ApprovalRelationDTO{}
@@ -11,6 +11,8 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
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"
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
@@ -29,6 +31,16 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
productionStandardRepo := rProductionStandard.NewProductionStandardRepository(db)
productionStandardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
standardGrowthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
productionStandardService := sProductionStandard.NewProductionStandardService(
productionStandardRepo,
productionStandardDetailRepo,
standardGrowthDetailRepo,
validate,
)
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
if err := fifoService.RegisterUsable(fifo.UsableConfig{
@@ -63,6 +75,7 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
approvalRepo,
approvalService,
fifoService,
productionStandardService,
validate,
)
userService := sUser.NewUserService(userRepo, validate)
@@ -48,12 +48,30 @@ type RecordingRepository interface {
GetTotalDepletionByProjectFlockID(ctx context.Context, projectFlockID uint) (totalDepletion float64, err error)
GetLatestAvgWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (avgWeight float64, err error)
GetTotalEggProductionWeightByProjectFlockID(ctx context.Context, projectFlockID uint) (totalWeightKg float64, err error)
GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error)
}
type RecordingRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.Recording]
}
type RecordingTargetAverages struct {
HenDayAvg float64
HenDayCount int64
HenHouseAvg float64
HenHouseCount int64
EggWeightAvg float64
EggWeightCount int64
EggMassAvg float64
EggMassCount int64
FeedIntakeAvg float64
FeedIntakeCount int64
FcrAvg float64
FcrCount int64
CumDepletionRateAvg float64
CumDepletionRateCount int64
}
func NewRecordingRepository(db *gorm.DB) RecordingRepository {
return &RecordingRepositoryImpl{
BaseRepositoryImpl: repository.NewBaseRepository[entity.Recording](db),
@@ -64,19 +82,28 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB {
return db.
Preload("CreatedUser").
Preload("ProjectFlockKandang").
Preload("ProjectFlockKandang.Kandang").
Preload("ProjectFlockKandang.ProjectFlock").
Preload("ProjectFlockKandang.ProjectFlock.ProductionStandard").
Preload("ProjectFlockKandang.ProjectFlock.Fcr").
Preload("Depletions").
Preload("Depletions.ProductWarehouse").
Preload("Depletions.ProductWarehouse.Product").
Preload("Depletions.ProductWarehouse.Warehouse").
Preload("Depletions.ProductWarehouse.Warehouse.Area").
Preload("Depletions.ProductWarehouse.Warehouse.Location").
Preload("Stocks").
Preload("Stocks.ProductWarehouse").
Preload("Stocks.ProductWarehouse.Product").
Preload("Stocks.ProductWarehouse.Warehouse").
Preload("Stocks.ProductWarehouse.Warehouse.Area").
Preload("Stocks.ProductWarehouse.Warehouse.Location").
Preload("Eggs").
Preload("Eggs.ProductWarehouse").
Preload("Eggs.ProductWarehouse.Product").
Preload("Eggs.ProductWarehouse.Warehouse")
Preload("Eggs.ProductWarehouse.Warehouse").
Preload("Eggs.ProductWarehouse.Warehouse.Area").
Preload("Eggs.ProductWarehouse.Warehouse.Location")
}
func (r *RecordingRepositoryImpl) GetLatestByProjectFlockKandangID(ctx context.Context, projectFlockKandangId uint) (*entity.Recording, error) {
@@ -433,6 +460,67 @@ func (r *RecordingRepositoryImpl) GetTotalEggProductionWeightByProjectFlockID(ct
return result, err
}
func (r *RecordingRepositoryImpl) GetAverageTargetMetricsByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint, includeTargets bool) (RecordingTargetAverages, error) {
var row struct {
HenDayTotal float64
HenHouseTotal float64
EggWeightTotal float64
EggMassTotal float64
FeedIntakeTotal float64
FcrTotal float64
CumDepletionRateTotal float64
TotalCount int64
}
selectParts := []string{
"COALESCE(SUM(feed_intake), 0) AS feed_intake_total",
"COALESCE(SUM(fcr_value), 0) AS fcr_total",
"COALESCE(SUM(cum_depletion_rate), 0) AS cum_depletion_rate_total",
"COUNT(*) AS total_count",
}
if includeTargets {
selectParts = append([]string{
"COALESCE(SUM(hen_day), 0) AS hen_day_total",
"COALESCE(SUM(hen_house), 0) AS hen_house_total",
"COALESCE(SUM(egg_weight), 0) AS egg_weight_total",
"COALESCE(SUM(egg_mass), 0) AS egg_mass_total",
}, selectParts...)
}
if err := r.DB().WithContext(ctx).
Table("recordings").
Select(strings.Join(selectParts, ", ")).
Where("project_flock_kandangs_id = ? AND deleted_at IS NULL", projectFlockKandangID).
Scan(&row).Error; err != nil {
return RecordingTargetAverages{}, err
}
result := RecordingTargetAverages{
FeedIntakeCount: row.TotalCount,
FcrCount: row.TotalCount,
CumDepletionRateCount: row.TotalCount,
}
if includeTargets {
result.HenDayCount = row.TotalCount
result.HenHouseCount = row.TotalCount
result.EggWeightCount = row.TotalCount
result.EggMassCount = row.TotalCount
}
if row.TotalCount > 0 {
if includeTargets {
result.HenDayAvg = row.HenDayTotal / float64(row.TotalCount)
result.HenHouseAvg = row.HenHouseTotal / float64(row.TotalCount)
result.EggWeightAvg = row.EggWeightTotal / float64(row.TotalCount)
result.EggMassAvg = row.EggMassTotal / float64(row.TotalCount)
}
result.FeedIntakeAvg = row.FeedIntakeTotal / float64(row.TotalCount)
result.FcrAvg = row.FcrTotal / float64(row.TotalCount)
result.CumDepletionRateAvg = row.CumDepletionRateTotal
}
return result, nil
}
func nextRecordingDay(days []int) int {
if len(days) == 0 {
return 1
@@ -10,6 +10,7 @@ import (
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"
sProductionStandard "gitlab.com/mbugroup/lti-api.git/internal/modules/master/production-standards/services"
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"
@@ -53,6 +54,7 @@ type recordingService struct {
ProjectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
ApprovalRepo commonRepo.ApprovalRepository
ApprovalSvc commonSvc.ApprovalService
ProductionStandardSvc sProductionStandard.ProductionStandardService
FifoSvc commonSvc.FifoService
}
@@ -64,6 +66,7 @@ func NewRecordingService(
approvalRepo commonRepo.ApprovalRepository,
approvalSvc commonSvc.ApprovalService,
fifoSvc commonSvc.FifoService,
productionStandardSvc sProductionStandard.ProductionStandardService,
validate *validator.Validate,
) RecordingService {
return &recordingService{
@@ -75,6 +78,7 @@ func NewRecordingService(
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
ApprovalRepo: approvalRepo,
ApprovalSvc: approvalSvc,
ProductionStandardSvc: productionStandardSvc,
FifoSvc: fifoSvc,
}
}
@@ -169,6 +173,14 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
}
ctx := c.Context()
recordTime := time.Now().UTC()
if req.RecordDate != nil && strings.TrimSpace(*req.RecordDate) != "" {
parsed, err := time.Parse("2006-01-02", strings.TrimSpace(*req.RecordDate))
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "record_date must be in YYYY-MM-DD format")
}
recordTime = parsed.UTC()
}
pfk, err := s.ProjectFlockKandangRepo.GetByID(ctx, req.ProjectFlockKandangId)
if err != nil {
@@ -188,6 +200,11 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
if err := s.ensureChickInExists(ctx, pfk.Id); err != nil {
return nil, err
}
if s.ProductionStandardSvc != nil {
if err := s.ProductionStandardSvc.EnsureWeekStart(ctx, pfk.ProjectFlock.ProductionStandardId, category); err != nil {
return nil, err
}
}
if !isLaying && len(req.Eggs) > 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Egg details permitted only for laying project flocks")
@@ -210,8 +227,12 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
s.Log.Errorf("Failed to determine recording day: %+v", err)
return err
}
if s.ProductionStandardSvc != nil {
if err := s.ProductionStandardSvc.EnsureWeekAvailable(ctx, pfk.ProjectFlock.ProductionStandardId, category, nextDay); err != nil {
return err
}
}
recordTime := time.Now().UTC()
existsToday, err := s.Repository.ExistsOnDate(ctx, req.ProjectFlockKandangId, recordTime)
if err != nil {
s.Log.Errorf("Failed to verify existing recording on date: %+v", err)
@@ -1157,7 +1178,7 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
var fcrValue float64
if usageInGrams > 0 && totalEggWeightGrams > 0 {
fcrValue = totalEggWeightGrams / usageInGrams
fcrValue = usageInGrams / totalEggWeightGrams
updates["fcr_value"] = fcrValue
recording.FcrValue = &fcrValue
} else {
@@ -1330,12 +1351,16 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e
return nil
}
week := ((int(*item.Day) - 1) / 7) + 1
category := strings.ToUpper(item.ProjectFlockKandang.ProjectFlock.Category)
weekBase := 1
if category == string(utils.ProjectFlockCategoryLaying) {
weekBase = 18
}
week := ((int(*item.Day) - 1) / 7) + weekBase
if week <= 0 {
return nil
}
category := strings.ToUpper(item.ProjectFlockKandang.ProjectFlock.Category)
db := s.Repository.DB()
standardDetailRepo := rProductionStandard.NewProductionStandardDetailRepository(db)
growthDetailRepo := rProductionStandard.NewStandardGrowthDetailRepository(db)
@@ -21,6 +21,7 @@ type (
type Create struct {
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
RecordDate *string `json:"record_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
Stocks []Stock `json:"stocks" validate:"dive"`
Depletions []Depletion `json:"depletions" validate:"dive"`
Eggs []Egg `json:"eggs" validate:"omitempty,dive"`