mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'fix/BE/US-282/adjustment-recording-egg' into 'feat/BE/Sprint-6'
fix/BE/US-282/TASK-301,302,303-Adjust Schema Database, Adjust Validation and Req Body, and fixing daily gain, and change logic daily gain See merge request mbugroup/lti-api!85
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Remove grading details from recording_eggs
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_eggs_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
DROP COLUMN IF EXISTS weight;
|
||||||
|
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
ADD CONSTRAINT chk_recording_eggs_qty CHECK (qty >= 0);
|
||||||
|
|
||||||
|
-- Restore grading_eggs table for rollback scenarios
|
||||||
|
CREATE TABLE grading_eggs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_egg_id BIGINT NOT NULL,
|
||||||
|
qty NUMERIC(15,3) NOT NULL,
|
||||||
|
grade VARCHAR,
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_grading_eggs_recording_egg
|
||||||
|
FOREIGN KEY (recording_egg_id) REFERENCES recording_eggs(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_grading_eggs_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_grading_eggs_qty CHECK (qty >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_grading_eggs_recording_egg
|
||||||
|
ON grading_eggs (recording_egg_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Remove separate grading table and move grading details into recording_eggs
|
||||||
|
DROP INDEX IF EXISTS idx_grading_eggs_recording_egg;
|
||||||
|
DROP TABLE IF EXISTS grading_eggs;
|
||||||
|
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
ADD COLUMN IF NOT EXISTS weight NUMERIC(10,3);
|
||||||
|
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_eggs_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recording_eggs
|
||||||
|
ADD CONSTRAINT chk_recording_eggs_qty CHECK (
|
||||||
|
qty >= 0 AND (weight IS NULL OR weight >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -7,24 +7,11 @@ type RecordingEgg struct {
|
|||||||
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
||||||
Qty int `gorm:"column:qty;not null"`
|
Qty int `gorm:"column:qty;not null"`
|
||||||
|
Weight *float64 `gorm:"column:weight"`
|
||||||
CreatedBy uint `gorm:"column:created_by"`
|
CreatedBy uint `gorm:"column:created_by"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
GradingEggs []GradingEgg `gorm:"foreignKey:RecordingEggId;references:Id"`
|
|
||||||
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GradingEgg struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
RecordingEggId uint `gorm:"column:recording_egg_id;not null;index"`
|
|
||||||
Qty float64 `gorm:"column:qty;not null"`
|
|
||||||
Grade string `gorm:"column:grade;type:varchar(50)"`
|
|
||||||
CreatedBy uint `gorm:"column:created_by"`
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
|
||||||
|
|
||||||
RecordingEgg RecordingEgg `gorm:"foreignKey:RecordingEggId;references:Id"`
|
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -146,27 +146,6 @@ 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 {
|
func (u *RecordingController) Approve(c *fiber.Ctx) error {
|
||||||
req := new(validation.Approve)
|
req := new(validation.Approve)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,22 +15,19 @@ import (
|
|||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type RecordingRelationDTO struct {
|
type RecordingRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
RecordDatetime time.Time `json:"record_datetime"`
|
RecordDatetime time.Time `json:"record_datetime"`
|
||||||
Day int `json:"day"`
|
Day int `json:"day"`
|
||||||
ProjectFlockCategory string `json:"project_flock_category"`
|
ProjectFlockCategory string `json:"project_flock_category"`
|
||||||
TotalDepletionQty float64 `json:"total_depletion_qty"`
|
TotalDepletionQty float64 `json:"total_depletion_qty"`
|
||||||
CumDepletionRate float64 `json:"cum_depletion_rate"`
|
CumDepletionRate float64 `json:"cum_depletion_rate"`
|
||||||
DailyGain float64 `json:"daily_gain"`
|
DailyGain float64 `json:"daily_gain"`
|
||||||
AvgDailyGain float64 `json:"avg_daily_gain"`
|
AvgDailyGain float64 `json:"avg_daily_gain"`
|
||||||
CumIntake int `json:"cum_intake"`
|
CumIntake int `json:"cum_intake"`
|
||||||
FcrValue float64 `json:"fcr_value"`
|
FcrValue float64 `json:"fcr_value"`
|
||||||
TotalChickQty float64 `json:"total_chick_qty"`
|
TotalChickQty float64 `json:"total_chick_qty"`
|
||||||
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
|
Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
|
||||||
EggGradingStatus *string `json:"egg_grading_status"`
|
|
||||||
EggGradingPendingQty *int `json:"egg_grading_pending_qty"`
|
|
||||||
EggGradingCompletedQty *int `json:"egg_grading_completed_qty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingListDTO struct {
|
type RecordingListDTO struct {
|
||||||
@@ -72,8 +68,8 @@ type RecordingEggDTO struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
Qty int `json:"qty"`
|
Qty int `json:"qty"`
|
||||||
|
Weight *float64 `json:"weight,omitempty"`
|
||||||
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
|
ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"`
|
||||||
Gradings []RecordingEggGradingDTO `json:"gradings,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingProductWarehouseDTO struct {
|
type RecordingProductWarehouseDTO struct {
|
||||||
@@ -84,11 +80,6 @@ type RecordingProductWarehouseDTO struct {
|
|||||||
WarehouseName string `json:"warehouse_name"`
|
WarehouseName string `json:"warehouse_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingEggGradingDTO struct {
|
|
||||||
Grade string `json:"grade,omitempty"`
|
|
||||||
Qty float64 `json:"qty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
||||||
@@ -140,25 +131,20 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
|
|||||||
latestApproval = snapshot
|
latestApproval = snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
gradingStatus, gradingPending, gradingCompleted := computeEggGradingStatus(e)
|
|
||||||
|
|
||||||
return RecordingRelationDTO{
|
return RecordingRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
||||||
RecordDatetime: e.RecordDatetime,
|
RecordDatetime: e.RecordDatetime,
|
||||||
Day: day,
|
Day: day,
|
||||||
ProjectFlockCategory: projectFlockCategory,
|
ProjectFlockCategory: projectFlockCategory,
|
||||||
TotalDepletionQty: totalDepletionQty,
|
TotalDepletionQty: totalDepletionQty,
|
||||||
CumDepletionRate: cumDepletionRate,
|
CumDepletionRate: cumDepletionRate,
|
||||||
DailyGain: dailyGain,
|
DailyGain: dailyGain,
|
||||||
AvgDailyGain: avgDailyGain,
|
AvgDailyGain: avgDailyGain,
|
||||||
CumIntake: cumIntake,
|
CumIntake: cumIntake,
|
||||||
FcrValue: fcrValue,
|
FcrValue: fcrValue,
|
||||||
TotalChickQty: totalChickQty,
|
TotalChickQty: totalChickQty,
|
||||||
Approval: latestApproval,
|
Approval: latestApproval,
|
||||||
EggGradingStatus: gradingStatus,
|
|
||||||
EggGradingPendingQty: gradingPending,
|
|
||||||
EggGradingCompletedQty: gradingCompleted,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,29 +239,13 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO {
|
|||||||
Id: egg.Id,
|
Id: egg.Id,
|
||||||
ProductWarehouseId: egg.ProductWarehouseId,
|
ProductWarehouseId: egg.ProductWarehouseId,
|
||||||
Qty: egg.Qty,
|
Qty: egg.Qty,
|
||||||
|
Weight: egg.Weight,
|
||||||
ProductWarehouse: mapProductWarehouseDTO(&egg.ProductWarehouse),
|
ProductWarehouse: mapProductWarehouseDTO(&egg.ProductWarehouse),
|
||||||
Gradings: ToRecordingEggGradingDTOs(egg.GradingEggs),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
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 mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO {
|
func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO {
|
||||||
if pw == nil {
|
if pw == nil {
|
||||||
return productWarehouseDTO.ProductWarehouseDTO{}
|
return productWarehouseDTO.ProductWarehouseDTO{}
|
||||||
@@ -289,61 +259,6 @@ func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.Pro
|
|||||||
return *mapped
|
return *mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
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.ApprovalRelationDTO {
|
func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalRelationDTO {
|
||||||
result := approvalDTO.ApprovalRelationDTO{}
|
result := approvalDTO.ApprovalRelationDTO{}
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ type RecordingRepository interface {
|
|||||||
DeleteEggs(tx *gorm.DB, recordingID uint) error
|
DeleteEggs(tx *gorm.DB, recordingID uint) error
|
||||||
ListEggs(tx *gorm.DB, recordingID uint) ([]entity.RecordingEgg, 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)
|
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)
|
ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error)
|
||||||
|
|
||||||
@@ -76,8 +74,7 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("Eggs").
|
Preload("Eggs").
|
||||||
Preload("Eggs.ProductWarehouse").
|
Preload("Eggs.ProductWarehouse").
|
||||||
Preload("Eggs.ProductWarehouse.Product").
|
Preload("Eggs.ProductWarehouse.Product").
|
||||||
Preload("Eggs.ProductWarehouse.Warehouse").
|
Preload("Eggs.ProductWarehouse.Warehouse")
|
||||||
Preload("Eggs.GradingEggs")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error) {
|
func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKandangId uint) (int, error) {
|
||||||
@@ -188,7 +185,6 @@ func (r *RecordingRepositoryImpl) GetRecordingEggByID(
|
|||||||
Preload("Recording.ProjectFlockKandang").
|
Preload("Recording.ProjectFlockKandang").
|
||||||
Preload("Recording.ProjectFlockKandang.ProjectFlock").
|
Preload("Recording.ProjectFlockKandang.ProjectFlock").
|
||||||
Preload("ProductWarehouse").
|
Preload("ProductWarehouse").
|
||||||
Preload("GradingEggs").
|
|
||||||
Where("id = ?", id)
|
Where("id = ?", id)
|
||||||
|
|
||||||
if err := query.First(&egg).Error; err != nil {
|
if err := query.First(&egg).Error; err != nil {
|
||||||
@@ -197,17 +193,6 @@ func (r *RecordingRepositoryImpl) GetRecordingEggByID(
|
|||||||
return &egg, nil
|
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) {
|
func (r *RecordingRepositoryImpl) ExistsOnDate(ctx context.Context, projectFlockKandangId uint, recordTime time.Time) (bool, error) {
|
||||||
if projectFlockKandangId == 0 {
|
if projectFlockKandangId == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingS
|
|||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Get("/next-day", ctrl.GetNextDay)
|
route.Get("/next-day", ctrl.GetNextDay)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
route.Post("/gradings", ctrl.SubmitGrading)
|
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
route.Post("/approvals", ctrl.Approve)
|
route.Post("/approvals", ctrl.Approve)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ type RecordingService interface {
|
|||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error)
|
||||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error)
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
SubmitGrading(ctx *fiber.Ctx, req *validation.SubmitGrading) (*entity.Recording, error)
|
|
||||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error)
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +272,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
action := entity.ApprovalActionCreated
|
action := entity.ApprovalActionCreated
|
||||||
if err := s.createRecordingApproval(ctx, tx, createdRecording.Id, utils.RecordingStepGradingTelur, action, createdRecording.CreatedBy, nil); err != nil {
|
if err := s.createRecordingApproval(ctx, tx, createdRecording.Id, utils.RecordingStepPengajuan, action, createdRecording.CreatedBy, nil); err != nil {
|
||||||
s.Log.Errorf("Failed to create recording approval for %d: %+v", createdRecording.Id, err)
|
s.Log.Errorf("Failed to create recording approval for %d: %+v", createdRecording.Id, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -347,16 +346,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasExistingGradings := false
|
|
||||||
for _, egg := range recordingEntity.Eggs {
|
|
||||||
if len(egg.GradingEggs) > 0 {
|
|
||||||
hasExistingGradings = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasEggsAfterUpdate := len(recordingEntity.Eggs) > 0
|
|
||||||
|
|
||||||
if hasBodyChanges {
|
if hasBodyChanges {
|
||||||
if err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil {
|
if err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil {
|
||||||
s.Log.Errorf("Failed to clear body weights: %+v", err)
|
s.Log.Errorf("Failed to clear body weights: %+v", err)
|
||||||
@@ -441,9 +430,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
s.Log.Errorf("Failed to adjust product warehouses for eggs: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasExistingGradings = false
|
|
||||||
hasEggsAfterUpdate = len(req.Eggs) > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasBodyChanges || hasStockChanges || hasDepletionChanges {
|
if hasBodyChanges || hasStockChanges || hasDepletionChanges {
|
||||||
@@ -459,20 +445,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval")
|
return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval")
|
||||||
}
|
}
|
||||||
|
|
||||||
var step approvalutils.ApprovalStep
|
step := utils.RecordingStepPengajuan
|
||||||
if isLaying {
|
|
||||||
if !hasEggsAfterUpdate {
|
|
||||||
step = utils.RecordingStepGradingTelur
|
|
||||||
} else if hasEggChanges {
|
|
||||||
step = utils.RecordingStepGradingTelur
|
|
||||||
} else if hasExistingGradings {
|
|
||||||
step = utils.RecordingStepPengajuan
|
|
||||||
} else {
|
|
||||||
step = utils.RecordingStepGradingTelur
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
step = utils.RecordingStepPengajuan
|
|
||||||
}
|
|
||||||
|
|
||||||
latestApproval := recordingEntity.LatestApproval
|
latestApproval := recordingEntity.LatestApproval
|
||||||
if latestApproval == nil {
|
if latestApproval == nil {
|
||||||
@@ -517,109 +490,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) SubmitGrading(c *fiber.Ctx, req *validation.SubmitGrading) (*entity.Recording, error) {
|
|
||||||
if err := s.Validate.Struct(req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(req.EggsGrading) == 0 {
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "eggs_grading must contain at least one item")
|
|
||||||
}
|
|
||||||
|
|
||||||
recordingEggID := req.EggsGrading[0].RecordingEggId
|
|
||||||
for _, grading := range req.EggsGrading[1:] {
|
|
||||||
if grading.RecordingEggId != recordingEggID {
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "semua grading harus untuk recording egg yang sama")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := c.Context()
|
|
||||||
var recordingID uint
|
|
||||||
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
recordingEgg, err := s.Repository.GetRecordingEggByID(ctx, recordingEggID, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return tx
|
|
||||||
})
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Recording egg not found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to get recording egg %d: %+v", recordingEggID, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var category string
|
|
||||||
if recordingEgg.Recording.ProjectFlockKandang != nil && recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Id != 0 {
|
|
||||||
category = strings.ToUpper(recordingEgg.Recording.ProjectFlockKandang.ProjectFlock.Category)
|
|
||||||
}
|
|
||||||
if category != strings.ToUpper(string(utils.ProjectFlockCategoryLaying)) {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Grading eggs hanya diperbolehkan pada project flock dengan kategori laying")
|
|
||||||
}
|
|
||||||
|
|
||||||
totalGradingQty := 0.0
|
|
||||||
for _, grading := range req.EggsGrading {
|
|
||||||
totalGradingQty += grading.Qty
|
|
||||||
}
|
|
||||||
|
|
||||||
availableRecorded := float64(recordingEgg.Qty)
|
|
||||||
if totalGradingQty > availableRecorded {
|
|
||||||
return fiber.NewError(
|
|
||||||
fiber.StatusBadRequest,
|
|
||||||
fmt.Sprintf("Total grading (%.2f) melebihi jumlah telur tercatat (%.2f)", totalGradingQty, availableRecorded),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if recordingEgg.ProductWarehouse.Id != 0 {
|
|
||||||
availableWarehouse := recordingEgg.ProductWarehouse.Quantity
|
|
||||||
if totalGradingQty > availableWarehouse {
|
|
||||||
return fiber.NewError(
|
|
||||||
fiber.StatusBadRequest,
|
|
||||||
fmt.Sprintf("Total grading (%.2f) melebihi stok telur baik (%.2f)", totalGradingQty, availableWarehouse),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Repository.DeleteGradingEggs(tx, recordingEgg.Id); err != nil {
|
|
||||||
s.Log.Errorf("Failed to clear grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
gradings := make([]entity.GradingEgg, 0, len(req.EggsGrading))
|
|
||||||
createdBy := recordingEgg.CreatedBy
|
|
||||||
if createdBy == 0 {
|
|
||||||
createdBy = recordingEgg.Recording.CreatedBy
|
|
||||||
}
|
|
||||||
for _, item := range req.EggsGrading {
|
|
||||||
gradings = append(gradings, entity.GradingEgg{
|
|
||||||
RecordingEggId: recordingEgg.Id,
|
|
||||||
Grade: strings.TrimSpace(item.Grade),
|
|
||||||
Qty: item.Qty,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(gradings) > 0 {
|
|
||||||
if err := s.Repository.CreateGradingEggs(tx, gradings); err != nil {
|
|
||||||
s.Log.Errorf("Failed to persist grading eggs for recording egg %d: %+v", recordingEgg.Id, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
action := entity.ApprovalActionUpdated
|
|
||||||
if err := s.createRecordingApproval(ctx, tx, recordingEgg.RecordingId, utils.RecordingStepPengajuan, action, createdBy, nil); err != nil {
|
|
||||||
s.Log.Errorf("Failed to create approval after grading for recording %d: %+v", recordingEgg.RecordingId, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
recordingID = recordingEgg.RecordingId
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if transactionErr != nil {
|
|
||||||
return nil, transactionErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.GetOne(c, recordingID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error) {
|
func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error) {
|
||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -934,14 +804,10 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
|||||||
return fmt.Errorf("getFeedUsageInGrams: %w", err)
|
return fmt.Errorf("getFeedUsageInGrams: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fcrId, err := s.Repository.GetFcrID(tx, recording.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getFcrID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAvgGrams := recordingutil.ToGrams(currentAvgWeight)
|
currentAvgGrams := recordingutil.ToGrams(currentAvgWeight)
|
||||||
currentAvgKg := recordingutil.GramsToKg(currentAvgGrams)
|
currentAvgKg := recordingutil.GramsToKg(currentAvgGrams)
|
||||||
prevAvgGrams := recordingutil.ToGrams(prevAvgWeight)
|
prevAvgGrams := recordingutil.ToGrams(prevAvgWeight)
|
||||||
|
prevAvgKg := recordingutil.GramsToKg(prevAvgGrams)
|
||||||
|
|
||||||
currentDepletion := float64(totalDepletionQty)
|
currentDepletion := float64(totalDepletionQty)
|
||||||
cumDepletionQty := prevCumDepletionQty + currentDepletion
|
cumDepletionQty := prevCumDepletionQty + currentDepletion
|
||||||
@@ -951,9 +817,10 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
|||||||
}
|
}
|
||||||
recording.TotalDepletionQty = &cumDepletionQty
|
recording.TotalDepletionQty = &cumDepletionQty
|
||||||
|
|
||||||
|
var remainingChick float64
|
||||||
if totalChick > 0 {
|
if totalChick > 0 {
|
||||||
totalChickFloat := float64(totalChick)
|
totalChickFloat := float64(totalChick)
|
||||||
remainingChick := totalChickFloat - cumDepletionQty
|
remainingChick = totalChickFloat - cumDepletionQty
|
||||||
if remainingChick < 0 {
|
if remainingChick < 0 {
|
||||||
remainingChick = 0
|
remainingChick = 0
|
||||||
}
|
}
|
||||||
@@ -978,24 +845,19 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
|||||||
updates["daily_gain"] = dailyGainKg
|
updates["daily_gain"] = dailyGainKg
|
||||||
recording.DailyGain = &dailyGainKg
|
recording.DailyGain = &dailyGainKg
|
||||||
} else {
|
} else {
|
||||||
updates["daily_gain"] = gorm.Expr("NULL")
|
dailyGainKg := 0.0
|
||||||
recording.DailyGain = nil
|
updates["daily_gain"] = dailyGainKg
|
||||||
|
recording.DailyGain = &dailyGainKg
|
||||||
}
|
}
|
||||||
|
|
||||||
if fcrId != 0 && currentAvgKg > 0 && day > 0 {
|
if currentAvgKg > 0 && remainingChick > 0 {
|
||||||
if fcrWeightKg, ok, err := s.Repository.GetFcrStandardWeightKg(tx, fcrId, currentAvgKg); err != nil {
|
avgDailyGain := (currentAvgKg - prevAvgKg) / remainingChick
|
||||||
return fmt.Errorf("getFcrStandardWeightKg: %w", err)
|
updates["avg_daily_gain"] = avgDailyGain
|
||||||
} else if ok {
|
recording.AvgDailyGain = &avgDailyGain
|
||||||
avgDailyGain := (currentAvgKg - fcrWeightKg) / float64(day)
|
|
||||||
updates["avg_daily_gain"] = avgDailyGain
|
|
||||||
recording.AvgDailyGain = &avgDailyGain
|
|
||||||
} else {
|
|
||||||
updates["avg_daily_gain"] = gorm.Expr("NULL")
|
|
||||||
recording.AvgDailyGain = nil
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updates["avg_daily_gain"] = gorm.Expr("NULL")
|
avgDailyGain := 0.0
|
||||||
recording.AvgDailyGain = nil
|
updates["avg_daily_gain"] = avgDailyGain
|
||||||
|
recording.AvgDailyGain = &avgDailyGain
|
||||||
}
|
}
|
||||||
|
|
||||||
if usageInGrams > 0 && totalChick > 0 {
|
if usageInGrams > 0 && totalChick > 0 {
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
Egg struct {
|
Egg struct {
|
||||||
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
|
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
|
||||||
Qty int `json:"qty" validate:"required,number,min=0"`
|
Qty int `json:"qty" validate:"required,number,min=0"`
|
||||||
|
Weight *float64 `json:"weight,omitempty" validate:"omitempty,gte=0"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,16 +46,6 @@ type Query struct {
|
|||||||
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
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 {
|
type Approve struct {
|
||||||
Action string `json:"action" validate:"required_strict"`
|
Action string `json:"action" validate:"required_strict"`
|
||||||
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||||
|
|||||||
@@ -200,13 +200,11 @@ var TransferToLayingApprovalSteps = map[approvalutils.ApprovalStep]string{
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
ApprovalWorkflowRecording approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("RECORDINGS")
|
ApprovalWorkflowRecording approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("RECORDINGS")
|
||||||
RecordingStepGradingTelur approvalutils.ApprovalStep = 1
|
RecordingStepPengajuan approvalutils.ApprovalStep = 1
|
||||||
RecordingStepPengajuan approvalutils.ApprovalStep = 2
|
RecordingStepDisetujui approvalutils.ApprovalStep = 2
|
||||||
RecordingStepDisetujui approvalutils.ApprovalStep = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var RecordingApprovalSteps = map[approvalutils.ApprovalStep]string{
|
var RecordingApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||||
RecordingStepGradingTelur: "Grading-Telur",
|
|
||||||
RecordingStepPengajuan: "Pengajuan",
|
RecordingStepPengajuan: "Pengajuan",
|
||||||
RecordingStepDisetujui: "Disetujui",
|
RecordingStepDisetujui: "Disetujui",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func MapEggs(recordingID uint, createdBy uint, items []validation.Egg) []entity.
|
|||||||
RecordingId: recordingID,
|
RecordingId: recordingID,
|
||||||
ProductWarehouseId: item.ProductWarehouseId,
|
ProductWarehouseId: item.ProductWarehouseId,
|
||||||
Qty: item.Qty,
|
Qty: item.Qty,
|
||||||
|
Weight: item.Weight,
|
||||||
CreatedBy: createdBy,
|
CreatedBy: createdBy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user