Merge branch 'development-before-sso' of https://gitlab.com/mbugroup/lti-api into refactor-to-serve/with-middleware

This commit is contained in:
ragilap
2025-11-03 16:57:10 +07:00
70 changed files with 3089 additions and 1065 deletions
@@ -146,6 +146,60 @@ func (u *RecordingController) UpdateOne(c *fiber.Ctx) error {
})
}
func (u *RecordingController) SubmitGrading(c *fiber.Ctx) error {
req := new(validation.SubmitGrading)
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
result, err := u.RecordingService.SubmitGrading(c, req)
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Submit grading eggs successfully",
Data: dto.ToRecordingDetailDTO(*result),
})
}
func (u *RecordingController) Approve(c *fiber.Ctx) error {
req := new(validation.Approve)
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
results, err := u.RecordingService.Approval(c, req)
if err != nil {
return err
}
var (
data interface{}
message = "Submit recording approvals successfully"
)
if len(results) == 1 {
message = "Submit recording approval successfully"
data = dto.ToRecordingDetailDTO(results[0])
} else {
data = dto.ToRecordingListDTOs(results)
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: message,
Data: data,
})
}
func (u *RecordingController) DeleteOne(c *fiber.Ctx) error {
param := c.Params("id")
@@ -1,30 +1,35 @@
package dto
import (
"math"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
)
// === DTO Structs ===
type RecordingBaseDTO struct {
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
RecordDatetime time.Time `json:"record_datetime"`
RecordDate *time.Time `json:"record_date,omitempty"`
Ontime bool `json:"ontime"`
Day *int `json:"day,omitempty"`
TotalDepletion *int `json:"total_depletion,omitempty"`
CumDepletionRate *float64 `json:"cum_depletion_rate,omitempty"`
DailyGain *float64 `json:"daily_gain,omitempty"`
AvgDailyGain *float64 `json:"avg_daily_gain,omitempty"`
CumIntake *int64 `json:"cum_intake,omitempty"`
FcrValue *float64 `json:"fcr_value,omitempty"`
TotalChick *int64 `json:"total_chick,omitempty"`
DailyDepletionRate *float64 `json:"daily_depletion_rate,omitempty"`
CumDepletion *int `json:"cum_depletion,omitempty"`
Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
RecordDatetime time.Time `json:"record_datetime"`
Day *int `json:"day,omitempty"`
ProjectFlockCategory *string `json:"project_flock_category,omitempty"`
TotalDepletionQty *float64 `json:"total_depletion_qty,omitempty"`
CumDepletionRate *float64 `json:"cum_depletion_rate,omitempty"`
DailyGain *float64 `json:"daily_gain,omitempty"`
AvgDailyGain *float64 `json:"avg_daily_gain,omitempty"`
CumIntake *int `json:"cum_intake,omitempty"`
FcrValue *float64 `json:"fcr_value,omitempty"`
TotalChickQty *float64 `json:"total_chick_qty,omitempty"`
Approval approvalDTO.ApprovalBaseDTO `json:"approval"`
EggGradingStatus *string `json:"egg_grading_status,omitempty"`
EggGradingPendingQty *int `json:"egg_grading_pending_qty,omitempty"`
EggGradingCompletedQty *int `json:"egg_grading_completed_qty,omitempty"`
}
type RecordingListDTO struct {
@@ -39,30 +44,35 @@ type RecordingDetailDTO struct {
BodyWeights []RecordingBodyWeightDTO `json:"body_weights"`
Depletions []RecordingDepletionDTO `json:"depletions"`
Stocks []RecordingStockDTO `json:"stocks"`
Eggs []RecordingEggDTO `json:"eggs"`
}
type RecordingBodyWeightDTO struct {
Weight float64 `json:"weight"`
Qty int `json:"qty"`
Notes *string `json:"notes,omitempty"`
AvgWeight float64 `json:"avg_weight"`
Qty float64 `json:"qty"`
TotalWeight float64 `json:"total_weight"`
}
type RecordingDepletionDTO struct {
ProductWarehouseId uint `json:"product_warehouse_id"`
Total int64 `json:"total"`
Notes *string `json:"notes,omitempty"`
Qty float64 `json:"qty"`
ProductWarehouse *RecordingProductWarehouseDTO `json:"product_warehouse,omitempty"`
}
type RecordingStockDTO struct {
ProductWarehouseId uint `json:"product_warehouse_id"`
Increase *float64 `json:"increase,omitempty"`
Decrease *float64 `json:"decrease,omitempty"`
UsageAmount *int64 `json:"usage_amount,omitempty"`
Notes *string `json:"notes,omitempty"`
UsageAmount *float64 `json:"usage_amount,omitempty"`
PendingQty *float64 `json:"pending_qty,omitempty"`
ProductWarehouse *RecordingProductWarehouseDTO `json:"product_warehouse,omitempty"`
}
type RecordingEggDTO struct {
ProductWarehouseId uint `json:"product_warehouse_id"`
Qty int `json:"qty"`
ProductWarehouse *RecordingProductWarehouseDTO `json:"product_warehouse,omitempty"`
Gradings []RecordingEggGradingDTO `json:"gradings,omitempty"`
}
type RecordingProductWarehouseDTO struct {
Id uint `json:"id"`
ProductId uint `json:"product_id"`
@@ -71,36 +81,47 @@ type RecordingProductWarehouseDTO struct {
WarehouseName string `json:"warehouse_name"`
}
type RecordingEggGradingDTO struct {
Grade string `json:"grade,omitempty"`
Qty float64 `json:"qty"`
}
// === Mapper Functions ===
func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO {
recordDate := e.RecordDate
if recordDate == nil {
rd := time.Date(
e.RecordDatetime.Year(),
e.RecordDatetime.Month(),
e.RecordDatetime.Day(),
0, 0, 0, 0,
e.RecordDatetime.Location(),
)
recordDate = &rd
var projectFlockCategory *string
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.ProjectFlock.Id != 0 {
category := e.ProjectFlockKandang.ProjectFlock.Category
if category != "" {
projectFlockCategory = &category
}
}
latestApproval := defaultRecordingLatestApproval(e)
if e.LatestApproval != nil {
snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
latestApproval = snapshot
}
gradingStatus, gradingPending, gradingCompleted := computeEggGradingStatus(e)
return RecordingBaseDTO{
Id: e.Id,
ProjectFlockKandangId: e.ProjectFlockKandangId,
RecordDatetime: e.RecordDatetime,
RecordDate: recordDate,
Ontime: e.Ontime == 1,
Day: e.Day,
TotalDepletion: e.TotalDepletion,
CumDepletionRate: e.CumDepletionRate,
DailyGain: e.DailyGain,
AvgDailyGain: e.AvgDailyGain,
CumIntake: e.CumIntake,
FcrValue: e.FcrValue,
TotalChick: e.TotalChick,
DailyDepletionRate: e.DailyDepletionRate,
CumDepletion: e.CumDepletion,
Id: e.Id,
ProjectFlockKandangId: e.ProjectFlockKandangId,
RecordDatetime: e.RecordDatetime,
Day: e.Day,
ProjectFlockCategory: projectFlockCategory,
TotalDepletionQty: e.TotalDepletionQty,
CumDepletionRate: e.CumDepletionRate,
DailyGain: e.DailyGain,
AvgDailyGain: e.AvgDailyGain,
CumIntake: e.CumIntake,
FcrValue: e.FcrValue,
TotalChickQty: e.TotalChickQty,
Approval: latestApproval,
EggGradingStatus: gradingStatus,
EggGradingPendingQty: gradingPending,
EggGradingCompletedQty: gradingCompleted,
}
}
@@ -133,6 +154,7 @@ func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
BodyWeights: ToRecordingBodyWeightDTOs(e.BodyWeights),
Depletions: ToRecordingDepletionDTOs(e.Depletions),
Stocks: ToRecordingStockDTOs(e.Stocks),
Eggs: ToRecordingEggDTOs(e.Eggs),
}
}
@@ -140,9 +162,9 @@ func ToRecordingBodyWeightDTOs(bodyWeights []entity.RecordingBW) []RecordingBody
result := make([]RecordingBodyWeightDTO, len(bodyWeights))
for i, bw := range bodyWeights {
result[i] = RecordingBodyWeightDTO{
Weight: bw.Weight,
Qty: bw.Qty,
Notes: bw.Notes,
AvgWeight: bw.AvgWeight,
Qty: bw.Qty,
TotalWeight: bw.TotalWeight,
}
}
return result
@@ -153,8 +175,7 @@ func ToRecordingDepletionDTOs(depletions []entity.RecordingDepletion) []Recordin
for i, d := range depletions {
result[i] = RecordingDepletionDTO{
ProductWarehouseId: d.ProductWarehouseId,
Total: d.Total,
Notes: d.Notes,
Qty: d.Qty,
ProductWarehouse: toRecordingProductWarehouseDTO(&d.ProductWarehouse),
}
}
@@ -166,16 +187,43 @@ func ToRecordingStockDTOs(stocks []entity.RecordingStock) []RecordingStockDTO {
for i, s := range stocks {
result[i] = RecordingStockDTO{
ProductWarehouseId: s.ProductWarehouseId,
Increase: s.Increase,
Decrease: s.Decrease,
UsageAmount: s.UsageAmount,
Notes: s.Notes,
UsageAmount: s.UsageQty,
PendingQty: s.PendingQty,
ProductWarehouse: toRecordingProductWarehouseDTO(&s.ProductWarehouse),
}
}
return result
}
func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO {
result := make([]RecordingEggDTO, len(eggs))
for i, egg := range eggs {
result[i] = RecordingEggDTO{
ProductWarehouseId: egg.ProductWarehouseId,
Qty: egg.Qty,
ProductWarehouse: toRecordingProductWarehouseDTO(&egg.ProductWarehouse),
Gradings: ToRecordingEggGradingDTOs(egg.GradingEggs),
}
}
return result
}
func ToRecordingEggGradingDTOs(gradings []entity.GradingEgg) []RecordingEggGradingDTO {
if len(gradings) == 0 {
return nil
}
result := make([]RecordingEggGradingDTO, len(gradings))
for i, grading := range gradings {
result[i] = RecordingEggGradingDTO{
Grade: grading.Grade,
Qty: grading.Qty,
}
}
return result
}
func toRecordingProductWarehouseDTO(pw *entity.ProductWarehouse) *RecordingProductWarehouseDTO {
if pw == nil || pw.Id == 0 {
return nil
@@ -196,3 +244,81 @@ func toRecordingProductWarehouseDTO(pw *entity.ProductWarehouse) *RecordingProdu
return &dto
}
const goodEggProductWarehouseID uint = 5
func computeEggGradingStatus(e entity.Recording) (*string, *int, *int) {
goodEggs := filterGoodEggs(e.Eggs)
if len(goodEggs) == 0 {
return nil, nil, nil
}
totalEggs := 0
totalGraded := 0.0
for _, egg := range goodEggs {
totalEggs += egg.Qty
for _, grading := range egg.GradingEggs {
totalGraded += grading.Qty
}
}
if totalEggs == 0 {
return nil, nil, nil
}
pendingFloat := float64(totalEggs) - totalGraded
if pendingFloat < 0 {
pendingFloat = 0
}
pendingInt := int(math.Round(pendingFloat))
completedInt := int(math.Round(totalGraded))
if completedInt < 0 {
completedInt = 0
}
if pendingInt > 0 {
status := "GRADING_TELUR"
return &status, &pendingInt, &completedInt
}
status := "GRADING_SELESAI"
zero := 0
return &status, &zero, &completedInt
}
func filterGoodEggs(eggs []entity.RecordingEgg) []entity.RecordingEgg {
if len(eggs) == 0 {
return nil
}
result := make([]entity.RecordingEgg, 0, len(eggs))
for _, egg := range eggs {
if egg.ProductWarehouseId == goodEggProductWarehouseID {
result = append(result, egg)
}
}
return result
}
func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalBaseDTO {
result := approvalDTO.ApprovalBaseDTO{}
step := utils.RecordingStepPengajuan
result.StepNumber = uint16(step)
if label, ok := approvalutils.ApprovalStepName(utils.ApprovalWorkflowRecording, step); ok {
result.StepName = label
} else if label, ok := utils.RecordingApprovalSteps[step]; ok {
result.StepName = label
}
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
result.ActionBy = userDTO.ToUserBaseDTO(*e.CreatedUser)
} else if e.CreatedBy != 0 {
result.ActionBy = userDTO.UserBaseDTO{
Id: e.CreatedBy,
IdUser: int64(e.CreatedBy),
}
}
return result
}
@@ -1,14 +1,19 @@
package recordings
import (
"fmt"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
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"
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
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"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -18,11 +23,26 @@ type RecordingModule struct{}
func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
recordingRepo := rRecording.NewRecordingRepository(db)
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowRecording, utils.RecordingApprovalSteps); err != nil {
panic(fmt.Sprintf("failed to register recording approval workflow: %v", err))
}
userRepo := rUser.NewUserRepository(db)
recordingService := sRecording.NewRecordingService(recordingRepo, projectFlockKandangRepo, productWarehouseRepo, validate)
recordingService := sRecording.NewRecordingService(
recordingRepo,
projectFlockKandangRepo,
productWarehouseRepo,
projectFlockPopulationRepo,
approvalRepo,
approvalService,
validate,
)
userService := sUser.NewUserService(userRepo, validate)
RecordingRoutes(router, userService, recordingService)
@@ -0,0 +1,8 @@
package recordings
const (
PermissionRecordingRead = "recording.read"
PermissionRecordingCreate = "recording.write"
PermissionRecordingUpdate = "recording.update"
PermissionRecordingDelete = "recording.delete"
)
@@ -1,10 +1,12 @@
package repository
import (
"context"
"errors"
"math"
"sort"
"strings"
"time"
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -22,11 +24,22 @@ type RecordingRepository interface {
CreateStocks(tx *gorm.DB, stocks []entity.RecordingStock) error
DeleteStocks(tx *gorm.DB, recordingID uint) error
ListStocks(tx *gorm.DB, recordingID uint) ([]entity.RecordingStock, error)
CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error
DeleteDepletions(tx *gorm.DB, recordingID uint) error
ListDepletions(tx *gorm.DB, recordingID uint) ([]entity.RecordingDepletion, error)
SumRecordingDepletions(tx *gorm.DB, recordingID uint) (int64, error)
CreateEggs(tx *gorm.DB, eggs []entity.RecordingEgg) error
DeleteEggs(tx *gorm.DB, recordingID uint) error
ListEggs(tx *gorm.DB, recordingID uint) ([]entity.RecordingEgg, error)
GetRecordingEggByID(ctx context.Context, id uint, modifier func(*gorm.DB) *gorm.DB) (*entity.RecordingEgg, error)
CreateGradingEggs(tx *gorm.DB, gradings []entity.GradingEgg) error
DeleteGradingEggs(tx *gorm.DB, recordingEggID uint) error
ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error)
SumRecordingDepletions(tx *gorm.DB, recordingID uint) (float64, error)
FindPreviousRecording(tx *gorm.DB, projectFlockKandangId uint, currentDay int) (*entity.Recording, error)
GetTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error)
GetAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error)
@@ -58,13 +71,18 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB {
Preload("Stocks").
Preload("Stocks.ProductWarehouse").
Preload("Stocks.ProductWarehouse.Product").
Preload("Stocks.ProductWarehouse.Warehouse")
Preload("Stocks.ProductWarehouse.Warehouse").
Preload("Eggs").
Preload("Eggs.ProductWarehouse").
Preload("Eggs.ProductWarehouse.Product").
Preload("Eggs.ProductWarehouse.Warehouse").
Preload("Eggs.GradingEggs")
}
func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error) {
var days []int
if err := tx.Model(&entity.Recording{}).
Where("project_flock_id = ?", projectFlockKandangId).
Where("project_flock_kandangs_id = ?", projectFlockKandangId).
Where("day IS NOT NULL").
Pluck("day", &days).Error; err != nil {
return 0, err
@@ -94,6 +112,14 @@ func (r *RecordingRepositoryImpl) DeleteStocks(tx *gorm.DB, recordingID uint) er
return tx.Where("recording_id = ?", recordingID).Delete(&entity.RecordingStock{}).Error
}
func (r *RecordingRepositoryImpl) ListStocks(tx *gorm.DB, recordingID uint) ([]entity.RecordingStock, error) {
var items []entity.RecordingStock
if err := tx.Where("recording_id = ?", recordingID).Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func (r *RecordingRepositoryImpl) CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error {
if len(depletions) == 0 {
return nil
@@ -105,11 +131,100 @@ func (r *RecordingRepositoryImpl) DeleteDepletions(tx *gorm.DB, recordingID uint
return tx.Where("recording_id = ?", recordingID).Delete(&entity.RecordingDepletion{}).Error
}
func (r *RecordingRepositoryImpl) SumRecordingDepletions(tx *gorm.DB, recordingID uint) (int64, error) {
var result int64
func (r *RecordingRepositoryImpl) ListDepletions(tx *gorm.DB, recordingID uint) ([]entity.RecordingDepletion, error) {
var items []entity.RecordingDepletion
if err := tx.Where("recording_id = ?", recordingID).Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func (r *RecordingRepositoryImpl) CreateEggs(tx *gorm.DB, eggs []entity.RecordingEgg) error {
if len(eggs) == 0 {
return nil
}
return tx.Create(&eggs).Error
}
func (r *RecordingRepositoryImpl) DeleteEggs(tx *gorm.DB, recordingID uint) error {
return tx.Where("recording_id = ?", recordingID).Delete(&entity.RecordingEgg{}).Error
}
func (r *RecordingRepositoryImpl) ListEggs(tx *gorm.DB, recordingID uint) ([]entity.RecordingEgg, error) {
var items []entity.RecordingEgg
if err := tx.Where("recording_id = ?", recordingID).Find(&items).Error; err != nil {
return nil, err
}
return items, nil
}
func (r *RecordingRepositoryImpl) GetRecordingEggByID(
ctx context.Context,
id uint,
modifier func(*gorm.DB) *gorm.DB,
) (*entity.RecordingEgg, error) {
if id == 0 {
return nil, gorm.ErrRecordNotFound
}
db := r.DB()
if modifier != nil {
db = modifier(db)
}
var egg entity.RecordingEgg
query := db.WithContext(ctx).
Preload("Recording").
Preload("Recording.ProjectFlockKandang").
Preload("Recording.ProjectFlockKandang.ProjectFlock").
Preload("ProductWarehouse").
Preload("GradingEggs").
Where("id = ?", id)
if err := query.First(&egg).Error; err != nil {
return nil, err
}
return &egg, nil
}
func (r *RecordingRepositoryImpl) CreateGradingEggs(tx *gorm.DB, gradings []entity.GradingEgg) error {
if len(gradings) == 0 {
return nil
}
return tx.Create(&gradings).Error
}
func (r *RecordingRepositoryImpl) DeleteGradingEggs(tx *gorm.DB, recordingEggID uint) error {
return tx.Where("recording_egg_id = ?", recordingEggID).Delete(&entity.GradingEgg{}).Error
}
func (r *RecordingRepositoryImpl) ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error) {
if projectFlockKandangId == 0 {
return false, nil
}
ref := recordTime.In(time.UTC)
startOfDay := time.Date(ref.Year(), ref.Month(), ref.Day(), 0, 0, 0, 0, time.UTC)
endOfDay := startOfDay.Add(24 * time.Hour)
var count int64
err := r.DB().
WithContext(ctx).
Model(&entity.Recording{}).
Where("project_flock_kandangs_id = ?", projectFlockKandangId).
Where("record_datetime >= ? AND record_datetime < ?", startOfDay, endOfDay).
Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}
func (r *RecordingRepositoryImpl) SumRecordingDepletions(tx *gorm.DB, recordingID uint) (float64, error) {
var result float64
if err := tx.Model(&entity.RecordingDepletion{}).
Where("recording_id = ?", recordingID).
Select("COALESCE(SUM(total), 0)").
Select("COALESCE(SUM(qty), 0)").
Scan(&result).Error; err != nil {
return 0, err
}
@@ -123,7 +238,7 @@ func (r *RecordingRepositoryImpl) FindPreviousRecording(tx *gorm.DB, projectFloc
var prev entity.Recording
err := tx.
Where("project_flock_id = ? AND day < ?", projectFlockKandangId, currentDay).
Where("project_flock_kandangs_id = ? AND day < ?", projectFlockKandangId, currentDay).
Where("day IS NOT NULL").
Order("day DESC").
Limit(1).
@@ -159,7 +274,7 @@ func (r *RecordingRepositoryImpl) GetAverageBodyWeight(tx *gorm.DB, recordingID
TotalQty float64
}
if err := tx.Model(&entity.RecordingBW{}).
Select("COALESCE(SUM(weight * qty), 0) AS total_weight, COALESCE(SUM(qty), 0) AS total_qty").
Select("COALESCE(SUM(total_weight), 0) AS total_weight, COALESCE(SUM(qty), 0) AS total_qty").
Where("recording_id = ?", recordingID).
Scan(&result).Error; err != nil {
return 0, err
@@ -172,13 +287,13 @@ func (r *RecordingRepositoryImpl) GetAverageBodyWeight(tx *gorm.DB, recordingID
func (r *RecordingRepositoryImpl) GetFeedUsageInGrams(tx *gorm.DB, recordingID uint) (float64, error) {
var rows []struct {
UsageAmount float64
UomName string
UsageQty float64
UomName string
}
if err := tx.
Table("recording_stocks").
Select("COALESCE(recording_stocks.usage_amount, 0) AS usage_amount, LOWER(uoms.name) AS uom_name").
Select("COALESCE(recording_stocks.usage_qty, 0) AS usage_qty, LOWER(uoms.name) AS uom_name").
Joins("JOIN product_warehouses ON product_warehouses.id = recording_stocks.product_warehouse_id").
Joins("JOIN products ON products.id = product_warehouses.product_id").
Joins("JOIN uoms ON uoms.id = products.uom_id").
@@ -189,16 +304,16 @@ func (r *RecordingRepositoryImpl) GetFeedUsageInGrams(tx *gorm.DB, recordingID u
var total float64
for _, row := range rows {
if row.UsageAmount <= 0 {
if row.UsageQty <= 0 {
continue
}
switch strings.TrimSpace(row.UomName) {
case "kilogram", "kg", "kilograms", "kilo":
total += row.UsageAmount * 1000
total += row.UsageQty * 1000
case "gram", "g", "grams":
total += row.UsageAmount
total += row.UsageQty
default:
total += row.UsageAmount
total += row.UsageQty
}
}
return total, nil
@@ -18,7 +18,9 @@ func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingS
route.Get("/", ctrl.GetAll)
route.Get("/next-day", ctrl.GetNextDay)
route.Post("/", ctrl.CreateOne)
route.Post("/gradings", ctrl.SubmitGrading)
route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne)
route.Post("/approvals", ctrl.Approve)
route.Delete("/:id", ctrl.DeleteOne)
}
File diff suppressed because it is too large Load Diff
@@ -2,23 +2,25 @@ package validation
type (
BodyWeight struct {
Weight float64 `json:"weight" validate:"required"`
Qty int `json:"qty" validate:"required,number,min=1"`
Notes *string `json:"notes,omitempty" validate:"omitempty"`
AvgWeight float64 `json:"avg_weight" validate:"required"`
Qty float64 `json:"qty" validate:"required,gt=0"`
TotalWeight *float64 `json:"total_weight,omitempty" validate:"omitempty,gt=0"`
}
Stock struct {
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
Increase *float64 `json:"increase,omitempty" validate:"omitempty"`
Decrease *float64 `json:"decrease,omitempty" validate:"omitempty"`
UsageAmount *int64 `json:"usage_amount,omitempty" validate:"omitempty,min=0"`
Notes *string `json:"notes,omitempty" validate:"omitempty"`
Qty *float64 `json:"qty,omitempty" validate:"required_without=UsageAmount,gte=0"`
PendingQty *float64 `json:"pending_qty,omitempty" validate:"omitempty,gte=0"`
}
Depletion struct {
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
Total int64 `json:"total" validate:"required,number,min=0"`
Notes *string `json:"notes,omitempty" validate:"omitempty"`
Qty float64 `json:"qty" validate:"required,gte=0"`
}
Egg struct {
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
Qty int `json:"qty" validate:"required,number,min=0"`
}
)
@@ -27,12 +29,14 @@ type Create struct {
BodyWeights []BodyWeight `json:"body_weights,omitempty" validate:"omitempty,dive"`
Stocks []Stock `json:"stocks,omitempty" validate:"omitempty,dive"`
Depletions []Depletion `json:"depletions,omitempty" validate:"omitempty,dive"`
Eggs []Egg `json:"eggs,omitempty" validate:"omitempty,dive"`
}
type Update struct {
BodyWeights []BodyWeight `json:"body_weights,omitempty" validate:"omitempty,dive"`
Stocks []Stock `json:"stocks,omitempty" validate:"omitempty,dive"`
Depletions []Depletion `json:"depletions,omitempty" validate:"omitempty,dive"`
Eggs []Egg `json:"eggs,omitempty" validate:"omitempty,dive"`
}
type Query struct {
@@ -40,3 +44,19 @@ type Query struct {
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
}
type EggGrading struct {
RecordingEggId uint `json:"recording_egg_id" validate:"required,number,min=1"`
Grade string `json:"grade" validate:"required"`
Qty float64 `json:"qty" validate:"required,gte=0"`
}
type SubmitGrading struct {
EggsGrading []EggGrading `json:"eggs_grading" validate:"required,dive"`
}
type Approve struct {
Action string `json:"action" validate:"required_strict"`
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}