adjustment recording adding weight in recording egg : need info, deleted grading egg, adjustment validation if must be changed again

This commit is contained in:
ragilap
2025-12-04 14:55:42 +07:00
parent 5650253307
commit 1bca29cd31
13 changed files with 123 additions and 313 deletions
+8
View File
@@ -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
)
+19
View File
@@ -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=
@@ -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;
@@ -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;
+2 -14
View File
@@ -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"`
}
@@ -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)
@@ -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{}
@@ -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
@@ -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)
@@ -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
@@ -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"`
+2 -4
View File
@@ -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",
}
@@ -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,
})
}