From 1bca29cd31be5b7778c9fae510b6cc86501a4af6 Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 4 Dec 2025 14:55:42 +0700 Subject: [PATCH 1/3] adjustment recording adding weight in recording egg : need info, deleted grading egg, adjustment validation if must be changed again --- go.mod | 8 + go.sum | 19 +++ ...nt_recording_without_grading_eggs.down.sql | 34 +++++ ...ment_recording_without_grading_eggs.up.sql | 19 +++ internal/entities/recording_egg.go | 16 +- .../controllers/recording.controller.go | 21 --- .../recordings/dto/recording.dto.go | 143 ++++-------------- .../repositories/recording.repository.go | 17 +-- .../modules/production/recordings/route.go | 1 - .../recordings/services/recording.service.go | 134 +--------------- .../validations/recording.validation.go | 16 +- internal/utils/constant.go | 6 +- internal/utils/recording/util.recording.go | 2 + 13 files changed, 123 insertions(+), 313 deletions(-) create mode 100644 internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql create mode 100644 internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql diff --git a/go.mod b/go.mod index 517bcdc1..fc28567b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23 require ( github.com/MicahParks/keyfunc/v2 v2.1.0 github.com/bytedance/sonic v1.12.1 + github.com/glebarez/sqlite v1.11.0 github.com/go-playground/validator/v10 v10.27.0 github.com/gofiber/contrib/jwt v1.0.10 github.com/gofiber/fiber/v2 v2.52.5 @@ -25,8 +26,10 @@ require ( github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -51,6 +54,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/philhofer/fwd v1.1.2 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -75,4 +79,8 @@ require ( golang.org/x/text v0.22.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect ) diff --git a/go.sum b/go.sum index c07e37e3..ea477c5d 100644 --- a/go.sum +++ b/go.sum @@ -27,12 +27,18 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -50,6 +56,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -146,6 +154,9 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -306,4 +317,12 @@ gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql new file mode 100644 index 00000000..7654ca00 --- /dev/null +++ b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql @@ -0,0 +1,34 @@ +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, + DROP COLUMN IF EXISTS grade; + +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; diff --git a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql new file mode 100644 index 00000000..91820b0e --- /dev/null +++ b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql @@ -0,0 +1,19 @@ +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), + ADD COLUMN IF NOT EXISTS grade VARCHAR; + +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; diff --git a/internal/entities/recording_egg.go b/internal/entities/recording_egg.go index 28eafeb7..20e6e72e 100644 --- a/internal/entities/recording_egg.go +++ b/internal/entities/recording_egg.go @@ -7,24 +7,12 @@ type RecordingEgg struct { RecordingId uint `gorm:"column:recording_id;not null;index"` ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` Qty int `gorm:"column:qty;not null"` + Weight *float64 `gorm:"column:weight"` + Grade *string `gorm:"column:grade;type:varchar(50)"` CreatedBy uint `gorm:"column:created_by"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` - GradingEggs []GradingEgg `gorm:"foreignKey:RecordingEggId;references:Id"` ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` CreatedUser *User `gorm:"foreignKey:CreatedBy;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"` -} diff --git a/internal/modules/production/recordings/controllers/recording.controller.go b/internal/modules/production/recordings/controllers/recording.controller.go index c348a454..c0f1737b 100644 --- a/internal/modules/production/recordings/controllers/recording.controller.go +++ b/internal/modules/production/recordings/controllers/recording.controller.go @@ -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 { req := new(validation.Approve) diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index f7cc4ee2..986f99cb 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -1,7 +1,6 @@ package dto import ( - "math" "strings" "time" @@ -16,22 +15,19 @@ import ( // === DTO Structs === 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"` - DailyGain float64 `json:"daily_gain"` - AvgDailyGain float64 `json:"avg_daily_gain"` - CumIntake int `json:"cum_intake"` - FcrValue float64 `json:"fcr_value"` - TotalChickQty float64 `json:"total_chick_qty"` - 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"` + 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"` + DailyGain float64 `json:"daily_gain"` + AvgDailyGain float64 `json:"avg_daily_gain"` + CumIntake int `json:"cum_intake"` + FcrValue float64 `json:"fcr_value"` + TotalChickQty float64 `json:"total_chick_qty"` + Approval approvalDTO.ApprovalRelationDTO `json:"approval"` } type RecordingListDTO struct { @@ -72,8 +68,9 @@ type RecordingEggDTO struct { Id uint `json:"id"` ProductWarehouseId uint `json:"product_warehouse_id"` Qty int `json:"qty"` + Weight *float64 `json:"weight,omitempty"` + Grade *string `json:"grade,omitempty"` ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"` - Gradings []RecordingEggGradingDTO `json:"gradings,omitempty"` } type RecordingProductWarehouseDTO struct { @@ -84,11 +81,6 @@ type RecordingProductWarehouseDTO struct { WarehouseName string `json:"warehouse_name"` } -type RecordingEggGradingDTO struct { - Grade string `json:"grade,omitempty"` - Qty float64 `json:"qty"` -} - // === Mapper Functions === func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { @@ -140,25 +132,20 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { latestApproval = snapshot } - gradingStatus, gradingPending, gradingCompleted := computeEggGradingStatus(e) - return RecordingRelationDTO{ - Id: e.Id, - ProjectFlockKandangId: e.ProjectFlockKandangId, - RecordDatetime: e.RecordDatetime, - Day: day, - ProjectFlockCategory: projectFlockCategory, - TotalDepletionQty: totalDepletionQty, - CumDepletionRate: cumDepletionRate, - DailyGain: dailyGain, - AvgDailyGain: avgDailyGain, - CumIntake: cumIntake, - FcrValue: fcrValue, - TotalChickQty: totalChickQty, - Approval: latestApproval, - EggGradingStatus: gradingStatus, - EggGradingPendingQty: gradingPending, - EggGradingCompletedQty: gradingCompleted, + Id: e.Id, + ProjectFlockKandangId: e.ProjectFlockKandangId, + RecordDatetime: e.RecordDatetime, + Day: day, + ProjectFlockCategory: projectFlockCategory, + TotalDepletionQty: totalDepletionQty, + CumDepletionRate: cumDepletionRate, + DailyGain: dailyGain, + AvgDailyGain: avgDailyGain, + CumIntake: cumIntake, + FcrValue: fcrValue, + TotalChickQty: totalChickQty, + Approval: latestApproval, } } @@ -253,29 +240,14 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO { Id: egg.Id, ProductWarehouseId: egg.ProductWarehouseId, Qty: egg.Qty, + Weight: egg.Weight, + Grade: egg.Grade, ProductWarehouse: mapProductWarehouseDTO(&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 mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.ProductWarehouseDTO { if pw == nil { return productWarehouseDTO.ProductWarehouseDTO{} @@ -289,61 +261,6 @@ func mapProductWarehouseDTO(pw *entity.ProductWarehouse) productWarehouseDTO.Pro 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 { result := approvalDTO.ApprovalRelationDTO{} diff --git a/internal/modules/production/recordings/repositories/recording.repository.go b/internal/modules/production/recordings/repositories/recording.repository.go index 5feb8d6b..60457074 100644 --- a/internal/modules/production/recordings/repositories/recording.repository.go +++ b/internal/modules/production/recordings/repositories/recording.repository.go @@ -35,8 +35,6 @@ type RecordingRepository interface { 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) @@ -76,8 +74,7 @@ func (r *RecordingRepositoryImpl) WithRelations(db *gorm.DB) *gorm.DB { Preload("Eggs"). Preload("Eggs.ProductWarehouse"). Preload("Eggs.ProductWarehouse.Product"). - Preload("Eggs.ProductWarehouse.Warehouse"). - Preload("Eggs.GradingEggs") + Preload("Eggs.ProductWarehouse.Warehouse") } 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.ProjectFlock"). Preload("ProductWarehouse"). - Preload("GradingEggs"). Where("id = ?", id) if err := query.First(&egg).Error; err != nil { @@ -197,17 +193,6 @@ func (r *RecordingRepositoryImpl) GetRecordingEggByID( 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 diff --git a/internal/modules/production/recordings/route.go b/internal/modules/production/recordings/route.go index c492c39f..83b426db 100644 --- a/internal/modules/production/recordings/route.go +++ b/internal/modules/production/recordings/route.go @@ -18,7 +18,6 @@ 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) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 82f60433..810e2aae 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -33,7 +33,6 @@ type RecordingService interface { CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, 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) } @@ -273,7 +272,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent } 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) 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 err := s.Repository.DeleteBodyWeights(tx, recordingEntity.Id); err != nil { 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) return err } - - hasExistingGradings = false - hasEggsAfterUpdate = len(req.Eggs) > 0 } 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") } - var step approvalutils.ApprovalStep - 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 - } + step := utils.RecordingStepPengajuan latestApproval := recordingEntity.LatestApproval if latestApproval == nil { @@ -517,109 +490,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin 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) { if err := s.Validate.Struct(req); err != nil { return nil, err diff --git a/internal/modules/production/recordings/validations/recording.validation.go b/internal/modules/production/recordings/validations/recording.validation.go index 28ea8a9f..64a726a0 100644 --- a/internal/modules/production/recordings/validations/recording.validation.go +++ b/internal/modules/production/recordings/validations/recording.validation.go @@ -19,8 +19,10 @@ type ( } Egg struct { - ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"` - Qty int `json:"qty" validate:"required,number,min=0"` + ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"` + Qty int `json:"qty" validate:"required,number,min=0"` + Weight *float64 `json:"weight,omitempty" validate:"omitempty,gte=0"` + Grade *string `json:"grade,omitempty" validate:"omitempty"` } ) @@ -45,16 +47,6 @@ type Query struct { 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"` diff --git a/internal/utils/constant.go b/internal/utils/constant.go index e9d0d60d..4316989a 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -198,13 +198,11 @@ var TransferToLayingApprovalSteps = map[approvalutils.ApprovalStep]string{ const ( ApprovalWorkflowRecording approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("RECORDINGS") - RecordingStepGradingTelur approvalutils.ApprovalStep = 1 - RecordingStepPengajuan approvalutils.ApprovalStep = 2 - RecordingStepDisetujui approvalutils.ApprovalStep = 3 + RecordingStepPengajuan approvalutils.ApprovalStep = 1 + RecordingStepDisetujui approvalutils.ApprovalStep = 2 ) var RecordingApprovalSteps = map[approvalutils.ApprovalStep]string{ - RecordingStepGradingTelur: "Grading-Telur", RecordingStepPengajuan: "Pengajuan", RecordingStepDisetujui: "Disetujui", } diff --git a/internal/utils/recording/util.recording.go b/internal/utils/recording/util.recording.go index 8f0fe81f..52fa0087 100644 --- a/internal/utils/recording/util.recording.go +++ b/internal/utils/recording/util.recording.go @@ -80,6 +80,8 @@ func MapEggs(recordingID uint, createdBy uint, items []validation.Egg) []entity. RecordingId: recordingID, ProductWarehouseId: item.ProductWarehouseId, Qty: item.Qty, + Weight: item.Weight, + Grade: item.Grade, CreatedBy: createdBy, }) } From 70b2a5a2d163cb14c510ef559b00909a3f67502e Mon Sep 17 00:00:00 2001 From: ragilap Date: Fri, 5 Dec 2025 21:58:51 +0700 Subject: [PATCH 2/3] deleted grade in recording egg unfinished: daily gain question, and confirm counting about fcr, adg, mortality and others --- ...03145514_adjustment_recording_without_grading_eggs.down.sql | 3 +-- ...1203145514_adjustment_recording_without_grading_eggs.up.sql | 3 +-- internal/entities/recording_egg.go | 1 - internal/modules/production/recordings/dto/recording.dto.go | 2 -- .../production/recordings/validations/recording.validation.go | 1 - internal/utils/recording/util.recording.go | 1 - 6 files changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql index 7654ca00..294d5e40 100644 --- a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql +++ b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.down.sql @@ -5,8 +5,7 @@ ALTER TABLE recording_eggs DROP CONSTRAINT IF EXISTS chk_recording_eggs_qty; ALTER TABLE recording_eggs - DROP COLUMN IF EXISTS weight, - DROP COLUMN IF EXISTS grade; + DROP COLUMN IF EXISTS weight; ALTER TABLE recording_eggs ADD CONSTRAINT chk_recording_eggs_qty CHECK (qty >= 0); diff --git a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql index 91820b0e..4da8c647 100644 --- a/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql +++ b/internal/database/migrations/20251203145514_adjustment_recording_without_grading_eggs.up.sql @@ -5,8 +5,7 @@ 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), - ADD COLUMN IF NOT EXISTS grade VARCHAR; + ADD COLUMN IF NOT EXISTS weight NUMERIC(10,3); ALTER TABLE recording_eggs DROP CONSTRAINT IF EXISTS chk_recording_eggs_qty; diff --git a/internal/entities/recording_egg.go b/internal/entities/recording_egg.go index 20e6e72e..775d15dc 100644 --- a/internal/entities/recording_egg.go +++ b/internal/entities/recording_egg.go @@ -8,7 +8,6 @@ type RecordingEgg struct { ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` Qty int `gorm:"column:qty;not null"` Weight *float64 `gorm:"column:weight"` - Grade *string `gorm:"column:grade;type:varchar(50)"` CreatedBy uint `gorm:"column:created_by"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index 986f99cb..51fba8a4 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -69,7 +69,6 @@ type RecordingEggDTO struct { ProductWarehouseId uint `json:"product_warehouse_id"` Qty int `json:"qty"` Weight *float64 `json:"weight,omitempty"` - Grade *string `json:"grade,omitempty"` ProductWarehouse productWarehouseDTO.ProductWarehouseDTO `json:"product_warehouse"` } @@ -241,7 +240,6 @@ func ToRecordingEggDTOs(eggs []entity.RecordingEgg) []RecordingEggDTO { ProductWarehouseId: egg.ProductWarehouseId, Qty: egg.Qty, Weight: egg.Weight, - Grade: egg.Grade, ProductWarehouse: mapProductWarehouseDTO(&egg.ProductWarehouse), } } diff --git a/internal/modules/production/recordings/validations/recording.validation.go b/internal/modules/production/recordings/validations/recording.validation.go index 64a726a0..28c38ff5 100644 --- a/internal/modules/production/recordings/validations/recording.validation.go +++ b/internal/modules/production/recordings/validations/recording.validation.go @@ -22,7 +22,6 @@ type ( ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"` Qty int `json:"qty" validate:"required,number,min=0"` Weight *float64 `json:"weight,omitempty" validate:"omitempty,gte=0"` - Grade *string `json:"grade,omitempty" validate:"omitempty"` } ) diff --git a/internal/utils/recording/util.recording.go b/internal/utils/recording/util.recording.go index 52fa0087..f10926dc 100644 --- a/internal/utils/recording/util.recording.go +++ b/internal/utils/recording/util.recording.go @@ -81,7 +81,6 @@ func MapEggs(recordingID uint, createdBy uint, items []validation.Egg) []entity. ProductWarehouseId: item.ProductWarehouseId, Qty: item.Qty, Weight: item.Weight, - Grade: item.Grade, CreatedBy: createdBy, }) } From 6e176688faf78c1c95529a6f1c4969af5dc776a5 Mon Sep 17 00:00:00 2001 From: ragilap Date: Mon, 8 Dec 2025 12:49:50 +0700 Subject: [PATCH 3/3] feat/BE/US-282/TASK-301,302,303-Adjust Schema Database, Adjust Validation and Req Body, and fixing daily gain, and change logic daily gain --- .../recordings/services/recording.service.go | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 810e2aae..a83c1128 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -804,14 +804,10 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm 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) currentAvgKg := recordingutil.GramsToKg(currentAvgGrams) prevAvgGrams := recordingutil.ToGrams(prevAvgWeight) + prevAvgKg := recordingutil.GramsToKg(prevAvgGrams) currentDepletion := float64(totalDepletionQty) cumDepletionQty := prevCumDepletionQty + currentDepletion @@ -821,9 +817,10 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm } recording.TotalDepletionQty = &cumDepletionQty + var remainingChick float64 if totalChick > 0 { totalChickFloat := float64(totalChick) - remainingChick := totalChickFloat - cumDepletionQty + remainingChick = totalChickFloat - cumDepletionQty if remainingChick < 0 { remainingChick = 0 } @@ -848,24 +845,19 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm updates["daily_gain"] = dailyGainKg recording.DailyGain = &dailyGainKg } else { - updates["daily_gain"] = gorm.Expr("NULL") - recording.DailyGain = nil + dailyGainKg := 0.0 + updates["daily_gain"] = dailyGainKg + recording.DailyGain = &dailyGainKg } - if fcrId != 0 && currentAvgKg > 0 && day > 0 { - if fcrWeightKg, ok, err := s.Repository.GetFcrStandardWeightKg(tx, fcrId, currentAvgKg); err != nil { - return fmt.Errorf("getFcrStandardWeightKg: %w", err) - } else if ok { - avgDailyGain := (currentAvgKg - fcrWeightKg) / float64(day) - updates["avg_daily_gain"] = avgDailyGain - recording.AvgDailyGain = &avgDailyGain - } else { - updates["avg_daily_gain"] = gorm.Expr("NULL") - recording.AvgDailyGain = nil - } + if currentAvgKg > 0 && remainingChick > 0 { + avgDailyGain := (currentAvgKg - prevAvgKg) / remainingChick + updates["avg_daily_gain"] = avgDailyGain + recording.AvgDailyGain = &avgDailyGain } else { - updates["avg_daily_gain"] = gorm.Expr("NULL") - recording.AvgDailyGain = nil + avgDailyGain := 0.0 + updates["avg_daily_gain"] = avgDailyGain + recording.AvgDailyGain = &avgDailyGain } if usageInGrams > 0 && totalChick > 0 {