From 3b2c6f16c339748f9ec3a4560503879d00a42b16 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sat, 10 Jan 2026 21:22:54 +0700 Subject: [PATCH 1/2] feat(BE-74-76-78-278):adjustment project flock,recording,purchase getall --- .../20260110105231_adjust_recording.down.sql | 59 +++++++++++++ .../20260110105231_adjust_recording.up.sql | 57 ++++++++++++ internal/entities/recording.go | 12 +-- .../product_warehouse.repository.go | 36 ++++++++ .../services/projectflock.service.go | 86 ++++++++++--------- .../recordings/dto/recording.dto.go | 42 ++++----- .../modules/production/recordings/route.go | 2 +- .../recordings/services/recording.service.go | 54 ++++++------ .../repositories/purchase.repository.go | 39 +++++++++ .../purchases/services/purchase.service.go | 1 + 10 files changed, 294 insertions(+), 94 deletions(-) create mode 100644 internal/database/migrations/20260110105231_adjust_recording.down.sql create mode 100644 internal/database/migrations/20260110105231_adjust_recording.up.sql diff --git a/internal/database/migrations/20260110105231_adjust_recording.down.sql b/internal/database/migrations/20260110105231_adjust_recording.down.sql new file mode 100644 index 00000000..2bcbcb67 --- /dev/null +++ b/internal/database/migrations/20260110105231_adjust_recording.down.sql @@ -0,0 +1,59 @@ +BEGIN; + +ALTER TABLE recordings + DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives_v4; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hen_day' + ) THEN + ALTER TABLE recordings RENAME COLUMN hen_day TO hand_day; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hen_house' + ) THEN + ALTER TABLE recordings RENAME COLUMN hen_house TO hand_house; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'egg_mass' + ) THEN + ALTER TABLE recordings RENAME COLUMN egg_mass TO egg_mesh; + END IF; +END $$; + +ALTER TABLE recordings + ADD COLUMN IF NOT EXISTS daily_gain NUMERIC(7,3), + ADD COLUMN IF NOT EXISTS avg_daily_gain NUMERIC(7,3); + +ALTER TABLE recordings + ADD CONSTRAINT chk_recordings_nonnegatives_v3 CHECK ( + (total_depletion_qty IS NULL OR total_depletion_qty >= 0) AND + (cum_depletion_rate IS NULL OR cum_depletion_rate >= 0) AND + (daily_gain IS NULL OR daily_gain >= 0) AND + (avg_daily_gain IS NULL OR avg_daily_gain >= 0) AND + (cum_intake IS NULL OR cum_intake >= 0) AND + (fcr_value IS NULL OR fcr_value >= 0) AND + (total_chick_qty IS NULL OR total_chick_qty >= 0) AND + (hand_day IS NULL OR hand_day >= 0) AND + (hand_house IS NULL OR hand_house >= 0) AND + (feed_intake IS NULL OR feed_intake >= 0) AND + (egg_mesh IS NULL OR egg_mesh >= 0) AND + (egg_weight IS NULL OR egg_weight >= 0) + ); + +COMMIT; diff --git a/internal/database/migrations/20260110105231_adjust_recording.up.sql b/internal/database/migrations/20260110105231_adjust_recording.up.sql new file mode 100644 index 00000000..ac947910 --- /dev/null +++ b/internal/database/migrations/20260110105231_adjust_recording.up.sql @@ -0,0 +1,57 @@ +BEGIN; + +ALTER TABLE recordings + DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives_v3; + +ALTER TABLE recordings + DROP COLUMN IF EXISTS daily_gain, + DROP COLUMN IF EXISTS avg_daily_gain; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hand_day' + ) THEN + ALTER TABLE recordings RENAME COLUMN hand_day TO hen_day; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'hand_house' + ) THEN + ALTER TABLE recordings RENAME COLUMN hand_house TO hen_house; + END IF; +END $$; + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'recordings' AND column_name = 'egg_mesh' + ) THEN + ALTER TABLE recordings RENAME COLUMN egg_mesh TO egg_mass; + END IF; +END $$; + +ALTER TABLE recordings + ADD CONSTRAINT chk_recordings_nonnegatives_v4 CHECK ( + (total_depletion_qty IS NULL OR total_depletion_qty >= 0) AND + (cum_depletion_rate IS NULL OR cum_depletion_rate >= 0) AND + (cum_intake IS NULL OR cum_intake >= 0) AND + (fcr_value IS NULL OR fcr_value >= 0) AND + (total_chick_qty IS NULL OR total_chick_qty >= 0) AND + (hen_day IS NULL OR hen_day >= 0) AND + (hen_house IS NULL OR hen_house >= 0) AND + (feed_intake IS NULL OR feed_intake >= 0) AND + (egg_mass IS NULL OR egg_mass >= 0) AND + (egg_weight IS NULL OR egg_weight >= 0) + ); + +COMMIT; diff --git a/internal/entities/recording.go b/internal/entities/recording.go index 7f952a62..0cc5dc03 100644 --- a/internal/entities/recording.go +++ b/internal/entities/recording.go @@ -16,10 +16,10 @@ type Recording struct { CumIntake *int `gorm:"column:cum_intake"` FcrValue *float64 `gorm:"column:fcr_value"` TotalChickQty *float64 `gorm:"column:total_chick_qty"` - HandDay *float64 `gorm:"column:hand_day"` - HandHouse *float64 `gorm:"column:hand_house"` + HenDay *float64 `gorm:"column:hen_day"` + HenHouse *float64 `gorm:"column:hen_house"` FeedIntake *float64 `gorm:"column:feed_intake"` - EggMesh *float64 `gorm:"column:egg_mesh"` + EggMass *float64 `gorm:"column:egg_mass"` EggWeight *float64 `gorm:"column:egg_weight"` CreatedBy uint `gorm:"column:created_by"` CreatedAt time.Time `gorm:"autoCreateTime"` @@ -34,11 +34,11 @@ type Recording struct { LatestApproval *Approval `gorm:"-" json:"-"` - StandardHandDay *float64 `gorm:"-"` - StandardHandHouse *float64 `gorm:"-"` + StandardHenDay *float64 `gorm:"-"` + StandardHenHouse *float64 `gorm:"-"` StandardFeedIntake *float64 `gorm:"-"` StandardMaxDepletion *float64 `gorm:"-"` - StandardEggMesh *float64 `gorm:"-"` + StandardEggMass *float64 `gorm:"-"` StandardEggWeight *float64 `gorm:"-"` StandardFcr *float64 `gorm:"-"` } diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index a8a44eb7..6acb4f69 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -23,6 +23,7 @@ type ProductWarehouseRepository interface { GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error) GetByFlagAndWarehouseID(ctx context.Context, flagName string, warehouseId uint) ([]entity.ProductWarehouse, error) GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error) + ListProductIDsByFlagPrefixes(ctx context.Context, prefixes []string, visibleStatus *bool) ([]uint, error) ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error GetDetailByID(ctx context.Context, id uint) (*entity.ProductWarehouse, error) @@ -380,3 +381,38 @@ func (r *ProductWarehouseRepositoryImpl) GetFirstProductByFlag(ctx context.Conte } return &product, nil } + +func (r *ProductWarehouseRepositoryImpl) ListProductIDsByFlagPrefixes(ctx context.Context, prefixes []string, visibleStatus *bool) ([]uint, error) { + if len(prefixes) == 0 { + return []uint{}, nil + } + + db := r.DB().WithContext(ctx). + Model(&entity.Product{}). + Distinct("products.id"). + Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", entity.FlagableTypeProduct) + + applied := false + for _, prefix := range prefixes { + if prefix == "" { + continue + } + like := prefix + "%" + if !applied { + db = db.Where("flags.name LIKE ?", like) + applied = true + continue + } + db = db.Or("flags.name LIKE ?", like) + } + + if visibleStatus != nil { + db = db.Where("products.is_visible = ?", *visibleStatus) + } + + var ids []uint + if err := db.Pluck("products.id", &ids).Error; err != nil { + return nil, err + } + return ids, nil +} diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 5f643dee..3dbe3f4b 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -23,6 +23,7 @@ import ( validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations" recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" uniformityRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/uniformities/repositories" + purchaseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/repositories" utils "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" @@ -308,12 +309,12 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* } createBody := &entity.ProjectFlock{ - AreaId: req.AreaId, - Category: cat, - FcrId: req.FcrId, + AreaId: req.AreaId, + Category: cat, + FcrId: req.FcrId, ProductionStandardId: req.ProductionStandardId, - LocationId: req.LocationId, - CreatedBy: actorID, + LocationId: req.LocationId, + CreatedBy: actorID, } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { @@ -823,22 +824,7 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction * return nil } - blocked, err := s.pivotRepoWithTx(dbTransaction).FindKandangsWithRecordings(ctx, projectFlockID, kandangIDs) - if err != nil { - s.Log.Errorf("Failed to check recordings before detaching kandangs: %+v", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandang detachment") - } - if len(blocked) > 0 { - names := make([]string, 0, len(blocked)) - for _, item := range blocked { - label := fmt.Sprintf("ID %d", item.Id) - if strings.TrimSpace(item.Name) != "" { - label = fmt.Sprintf("%s (%s)", label, item.Name) - } - names = append(names, label) - } - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak dapat melepas kandang karena sudah memiliki recording: %s", strings.Join(names, ", "))) - } + // NOTE: Recording constraints are enforced via FK cascade; allow detachment even if recordings exist. pfkIDs, err := s.pivotRepoWithTx(dbTransaction).ListIDsByProjectAndKandang(ctx, projectFlockID, kandangIDs) if err != nil { @@ -854,6 +840,14 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction * return fiber.NewError(fiber.StatusInternalServerError, "Failed to remove uniformity data for project flock kandang") } + db := s.Repository.DB() + if dbTransaction != nil { + db = dbTransaction + } + purchaseRepo := purchaseRepository.NewPurchaseRepository(db) + if err := purchaseRepo.SoftDeleteByProjectFlockKandangIDs(ctx, pfkIDs); err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to soft delete purchases for project flock kandang") + } pwRepo := s.ProductWarehouseRepo if dbTransaction != nil { pwRepo = productWarehouseRepository.NewProductWarehouseRepository(dbTransaction) @@ -906,6 +900,11 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont return nil } + projectFlockID := records[0].ProjectFlockId + if projectFlockID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Project flock id tidak ditemukan") + } + pwRepo := s.ProductWarehouseRepo if dbTransaction != nil { pwRepo = productWarehouseRepository.NewProductWarehouseRepository(dbTransaction) @@ -920,24 +919,34 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont warehouseRepo = warehouseRepository.NewWarehouseRepository(s.Repository.DB()) } - flags := []utils.FlagType{ - utils.FlagAyamAfkir, - utils.FlagAyamCulling, - utils.FlagAyamMati, - utils.FlagTelurPecah, - utils.FlagTelurUtuh, + db := s.Repository.DB() + if dbTransaction != nil { + db = dbTransaction + } + var category string + if err := db.WithContext(ctx). + Model(&entity.ProjectFlock{}). + Select("category"). + Where("id = ?", projectFlockID). + Scan(&category).Error; err != nil { + return err + } + if strings.TrimSpace(category) == "" { + return fiber.NewError(fiber.StatusBadRequest, "Project flock category tidak ditemukan") } - productIDs := make(map[utils.FlagType]uint, len(flags)) - for _, flag := range flags { - product, err := pwRepo.GetFirstProductByFlag(ctx, string(flag)) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product untuk flag %s tidak ditemukan", flag)) - } - return err - } - productIDs[flag] = product.Id + prefixes := []string{"AYAM-"} + if strings.EqualFold(category, string(utils.ProjectFlockCategoryLaying)) { + prefixes = append(prefixes, "TELUR") + } + + invisibleOnly := false + productIDs, err := pwRepo.ListProductIDsByFlagPrefixes(ctx, prefixes, &invisibleOnly) + if err != nil { + return err + } + if len(productIDs) == 0 { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product dengan flag %s tidak ditemukan", strings.Join(prefixes, ", "))) } for _, record := range records { @@ -953,8 +962,7 @@ func (s projectflockService) ensureProjectFlockKandangProductWarehouses(ctx cont return err } - for _, flag := range flags { - productID := productIDs[flag] + for _, productID := range productIDs { if _, err := pwRepo.GetByProductWarehouseAndProjectFlockKandang(ctx, productID, warehouse.Id, record.Id); err == nil { continue } else if !errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/internal/modules/production/recordings/dto/recording.dto.go b/internal/modules/production/recordings/dto/recording.dto.go index c34651ba..f5a04821 100644 --- a/internal/modules/production/recordings/dto/recording.dto.go +++ b/internal/modules/production/recordings/dto/recording.dto.go @@ -25,16 +25,16 @@ type RecordingRelationDTO struct { CumIntake int `json:"cum_intake"` FcrValue float64 `json:"fcr_value"` TotalChickQty float64 `json:"total_chick_qty"` - HandDay float64 `json:"hand_day"` - HandHouse float64 `json:"hand_house"` + HenDay float64 `json:"hen_day"` + HenHouse float64 `json:"hen_house"` FeedIntake float64 `json:"feed_intake"` - EggMesh float64 `json:"egg_mesh"` + EggMass float64 `json:"egg_mass"` EggWeight float64 `json:"egg_weight"` - StandardHandDay *float64 `json:"hand_day_std,omitempty"` - StandardHandHouse *float64 `json:"hand_house_std,omitempty"` + StandardHenDay *float64 `json:"hen_day_std,omitempty"` + StandardHenHouse *float64 `json:"hen_house_std,omitempty"` StandardFeedIntake *float64 `json:"feed_intake_std,omitempty"` StandardMaxDepletion *float64 `json:"max_depletion_std,omitempty"` - StandardEggMesh *float64 `json:"egg_mesh_std,omitempty"` + StandardEggMass *float64 `json:"egg_mass_std,omitempty"` StandardEggWeight *float64 `json:"egg_weight_std,omitempty"` StandardFcr *float64 `json:"fcr_std,omitempty"` Approval approvalDTO.ApprovalRelationDTO `json:"approval"` @@ -94,10 +94,10 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { cumIntake int fcrValue float64 totalChickQty float64 - handDay float64 - handHouse float64 + henDay float64 + henHouse float64 feedIntake float64 - eggMesh float64 + eggMass float64 eggWeight float64 ) @@ -119,17 +119,17 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { if e.TotalChickQty != nil { totalChickQty = *e.TotalChickQty } - if e.HandDay != nil { - handDay = *e.HandDay + if e.HenDay != nil { + henDay = *e.HenDay } - if e.HandHouse != nil { - handHouse = *e.HandHouse + if e.HenHouse != nil { + henHouse = *e.HenHouse } if e.FeedIntake != nil { feedIntake = *e.FeedIntake } - if e.EggMesh != nil { - eggMesh = *e.EggMesh + if e.EggMass != nil { + eggMass = *e.EggMass } if e.EggWeight != nil { eggWeight = *e.EggWeight @@ -157,16 +157,16 @@ func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO { CumIntake: cumIntake, FcrValue: fcrValue, TotalChickQty: totalChickQty, - HandDay: handDay, - HandHouse: handHouse, + HenDay: henDay, + HenHouse: henHouse, FeedIntake: feedIntake, - EggMesh: eggMesh, + EggMass: eggMass, EggWeight: eggWeight, - StandardHandDay: e.StandardHandDay, - StandardHandHouse: e.StandardHandHouse, + StandardHenDay: e.StandardHenDay, + StandardHenHouse: e.StandardHenHouse, StandardFeedIntake: e.StandardFeedIntake, StandardMaxDepletion: e.StandardMaxDepletion, - StandardEggMesh: e.StandardEggMesh, + StandardEggMass: e.StandardEggMass, StandardEggWeight: e.StandardEggWeight, StandardFcr: e.StandardFcr, Approval: latestApproval, diff --git a/internal/modules/production/recordings/route.go b/internal/modules/production/recordings/route.go index f05d054d..e7f1b081 100644 --- a/internal/modules/production/recordings/route.go +++ b/internal/modules/production/recordings/route.go @@ -16,10 +16,10 @@ func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingS route.Use(m.Auth(u)) route.Get("/",m.RequirePermissions(m.P_RecordingGetAll), ctrl.GetAll) + route.Get("/next-day",m.RequirePermissions(m.P_RecordingNextDay), ctrl.GetNextDay) route.Get("/:id",m.RequirePermissions(m.P_RecordingGetOne), ctrl.GetOne) route.Post("/",m.RequirePermissions(m.P_RecordingCreateOne), ctrl.CreateOne) route.Patch("/:id",m.RequirePermissions(m.P_RecordingUpdateOne), ctrl.UpdateOne) route.Delete("/:id",m.RequirePermissions(m.P_RecordingDeleteOne), ctrl.DeleteOne) - route.Get("/next-day",m.RequirePermissions(m.P_RecordingNextDay), ctrl.GetNextDay) route.Post("/approvals",m.RequirePermissions(m.P_RecordingApproval), ctrl.Approve) } diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 54052518..e46056e5 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -1156,34 +1156,34 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm recording.FeedIntake = nil } - var handDay float64 + var henDay float64 if remainingChick > 0 && totalEggQty >= 0 { - handDay = (totalEggQty / remainingChick) * 100 - updates["hand_day"] = handDay - recording.HandDay = &handDay + henDay = (totalEggQty / remainingChick) * 100 + updates["hen_day"] = henDay + recording.HenDay = &henDay } else { - updates["hand_day"] = gorm.Expr("NULL") - recording.HandDay = nil + updates["hen_day"] = gorm.Expr("NULL") + recording.HenDay = nil } - var handHouse float64 + var henHouse float64 if initialChickin > 0 && cumulativeEggQty >= 0 { - handHouse = cumulativeEggQty / initialChickin - updates["hand_house"] = handHouse - recording.HandHouse = &handHouse + henHouse = cumulativeEggQty / initialChickin + updates["hen_house"] = henHouse + recording.HenHouse = &henHouse } else { - updates["hand_house"] = gorm.Expr("NULL") - recording.HandHouse = nil + updates["hen_house"] = gorm.Expr("NULL") + recording.HenHouse = nil } - var eggMesh float64 + var eggMass float64 if remainingChick > 0 && totalEggWeightGrams > 0 { - eggMesh = (totalEggWeightGrams / remainingChick) * 1000 - updates["egg_mesh"] = eggMesh - recording.EggMesh = &eggMesh + eggMass = (totalEggWeightGrams / remainingChick) * 1000 + updates["egg_mass"] = eggMass + recording.EggMass = &eggMass } else { - updates["egg_mesh"] = gorm.Expr("NULL") - recording.EggMesh = nil + updates["egg_mass"] = gorm.Expr("NULL") + recording.EggMass = nil } var eggWeight float64 @@ -1334,11 +1334,11 @@ func (s *recordingService) attachLatestApproval(ctx context.Context, item *entit } type productionStandardValues struct { - HandDay *float64 - HandHouse *float64 + HenDay *float64 + HenHouse *float64 FeedIntake *float64 MaxDepletion *float64 - EggMesh *float64 + EggMass *float64 EggWeight *float64 } @@ -1389,10 +1389,10 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e return err } if detail != nil { - standard.HandDay = detail.TargetHenDayProduction - standard.HandHouse = detail.TargetHenHouseProduction + standard.HenDay = detail.TargetHenDayProduction + standard.HenHouse = detail.TargetHenHouseProduction standard.EggWeight = detail.TargetEggWeight - standard.EggMesh = detail.TargetEggMass + standard.EggMass = detail.TargetEggMass } } @@ -1420,11 +1420,11 @@ func (s *recordingService) attachProductionStandard(ctx context.Context, item *e } } - item.StandardHandDay = standard.HandDay - item.StandardHandHouse = standard.HandHouse + item.StandardHenDay = standard.HenDay + item.StandardHenHouse = standard.HenHouse item.StandardFeedIntake = standard.FeedIntake item.StandardMaxDepletion = standard.MaxDepletion - item.StandardEggMesh = standard.EggMesh + item.StandardEggMass = standard.EggMass item.StandardEggWeight = standard.EggWeight item.StandardFcr = standardFcr diff --git a/internal/modules/purchases/repositories/purchase.repository.go b/internal/modules/purchases/repositories/purchase.repository.go index fc599877..f6e48aeb 100644 --- a/internal/modules/purchases/repositories/purchase.repository.go +++ b/internal/modules/purchases/repositories/purchase.repository.go @@ -25,6 +25,7 @@ type PurchaseRepository interface { NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error) NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error) BackfillProjectFlockKandang(ctx context.Context, purchaseID uint) error + SoftDeleteByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) error GetItemsByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) GetItemsByWarehouseKandang(ctx context.Context, projectFlockID uint) ([]entity.PurchaseItem, error) } @@ -89,6 +90,44 @@ WHERE pi.purchase_id = ? return r.DB().WithContext(ctx).Exec(query, purchaseID).Error } +func (r *PurchaseRepositoryImpl) SoftDeleteByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) error { + if len(projectFlockKandangIDs) == 0 { + return nil + } + + return r.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var purchaseIDs []uint + query := ` +SELECT pi.purchase_id +FROM purchase_items pi +WHERE pi.project_flock_kandang_id IN (?) +GROUP BY pi.purchase_id +HAVING COUNT(*) = COUNT(CASE WHEN pi.project_flock_kandang_id IN (?) THEN 1 END) +` + if err := tx.Raw(query, projectFlockKandangIDs, projectFlockKandangIDs).Scan(&purchaseIDs).Error; err != nil { + return err + } + + now := time.Now().UTC() + if len(purchaseIDs) > 0 { + if err := tx.Model(&entity.Purchase{}). + Where("id IN (?) AND deleted_at IS NULL", purchaseIDs). + Update("deleted_at", now).Error; err != nil { + return err + } + if err := tx.Where("purchase_id IN (?)", purchaseIDs).Delete(&entity.PurchaseItem{}).Error; err != nil { + return err + } + } + + deleteItems := tx.Where("project_flock_kandang_id IN (?)", projectFlockKandangIDs) + if len(purchaseIDs) > 0 { + deleteItems = deleteItems.Where("purchase_id NOT IN (?)", purchaseIDs) + } + return deleteItems.Delete(&entity.PurchaseItem{}).Error + }) +} + func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error { if len(items) == 0 { return nil diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index e46788d8..35ca2f75 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -133,6 +133,7 @@ func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti purchases, total, err := s.PurchaseRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + db = db.Where("purchases.deleted_at IS NULL") if params.SupplierID > 0 { db = db.Where("supplier_id = ?", params.SupplierID) From b42ca5e6fb17dce9f1a269eae784d84617c4c5f7 Mon Sep 17 00:00:00 2001 From: ragilap Date: Sat, 10 Jan 2026 21:34:19 +0700 Subject: [PATCH 2/2] feat(BE-74-76-78-278):delete unused code recording --- .../recordings/services/recording.service.go | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index e46056e5..819552dc 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -901,47 +901,6 @@ type eggTotals struct { Weight float64 } -type stockTotals struct { - Usage float64 - Pending float64 - Total float64 -} - -func summarizeExistingStocks(stocks []entity.RecordingStock) map[uint]stockTotals { - totals := make(map[uint]stockTotals) - for _, stock := range stocks { - var usage float64 - var pending float64 - if stock.UsageQty != nil { - usage = *stock.UsageQty - } - if stock.PendingQty != nil { - pending = *stock.PendingQty - } - current := totals[stock.ProductWarehouseId] - current.Usage += usage - current.Pending += pending - current.Total += usage + pending - totals[stock.ProductWarehouseId] = current - } - return totals -} - -func summarizeIncomingStocks(stocks []validation.Stock) map[uint]stockTotals { - totals := make(map[uint]stockTotals) - for _, stock := range stocks { - var pending float64 - if stock.PendingQty != nil { - pending = *stock.PendingQty - } - current := totals[stock.ProductWarehouseId] - current.Usage += stock.Qty - current.Pending += pending - current.Total += stock.Qty + pending - totals[stock.ProductWarehouseId] = current - } - return totals -} func stocksMatch(existing []entity.RecordingStock, incoming []validation.Stock) bool { hasPending := false