feat[BE]: Add requested_qty field to LayingTransferSource and update related logic for transfer operations

This commit is contained in:
aguhh18
2026-01-19 14:34:08 +07:00
parent 378d633ea4
commit 8cd9627a51
9 changed files with 109 additions and 20 deletions
@@ -15,8 +15,8 @@ func TransferRoutes(v1 fiber.Router, u user.UserService, s transfer.TransferServ
route := v1.Group("/transfers")
route.Use(m.Auth(u))
route.Get("/",m.RequirePermissions(m.P_TransferGetAll), ctrl.GetAll)
route.Post("/",m.RequirePermissions(m.P_TransferCreateOne), ctrl.CreateOne)
route.Get("/:id",m.RequirePermissions(m.P_TransferGetOne), ctrl.GetOne)
route.Get("/", m.RequirePermissions(m.P_TransferGetAll), ctrl.GetAll)
route.Post("/", m.RequirePermissions(m.P_TransferCreateOne), ctrl.CreateOne)
route.Get("/:id", m.RequirePermissions(m.P_TransferGetOne), ctrl.GetOne)
}
@@ -200,9 +200,9 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
newChikins = append(newChikins, newChickin)
totalPopulationQty, err := s.ProjectflockPopulationRepo.GetTotalQtyByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
totalPopulationQty, err := s.ProjectflockPopulationRepo.GetTotalQtyByProductWarehouseID(c.Context(), chickinReq.ProductWarehouseId)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get total population quantity for project_flock_kandang %d", req.ProjectFlockKandangId))
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to get total population quantity for product warehouse %d", chickinReq.ProductWarehouseId))
}
availableQty := productWarehouse.Quantity - totalPopulationQty
@@ -2,6 +2,7 @@ package repository
import (
"context"
"math"
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -16,6 +17,7 @@ type ProjectFlockPopulationRepository interface {
GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
GetTotalQtyByProductWarehouseID(ctx context.Context, productWarehouseID uint) (float64, error)
GetAvailableQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error)
CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error
PatchOne(ctx context.Context, id uint, updates map[string]any, modifier func(*gorm.DB) *gorm.DB) error
@@ -111,7 +113,7 @@ func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProductWarehouseID(c
err := r.DB().WithContext(ctx).
Model(&entity.ProjectFlockPopulation{}).
Where("product_warehouse_id = ?", productWarehouseID).
Select("COALESCE(SUM(total_qty), 0)").
Select("COALESCE(SUM(total_qty - total_used_qty), 0)").
Scan(&total).Error
if err != nil {
return 0, err
@@ -135,3 +137,22 @@ func (r *projectFlockPopulationRepositoryImpl) GetAvailableQtyByProjectFlockKand
}
return total, nil
}
func (r *projectFlockPopulationRepositoryImpl) GetTotalChickInByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (int64, error) {
var total float64
err := r.DB().WithContext(ctx).
Table("project_flock_populations").
Select("COALESCE(SUM(project_flock_populations.total_qty - project_flock_populations.total_used_qty), 0) AS total_qty").
Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id").
Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID).
Scan(&total).Error
if err != nil {
return 0, err
}
if total < 0 {
total = 0
}
return int64(math.Round(total)), nil
}
@@ -162,9 +162,19 @@ func ToProductWarehouseSummaryDTO(pw *entity.ProductWarehouse) *ProductWarehouse
}
func ToLayingTransferSourceDTO(source entity.LayingTransferSource) LayingTransferSourceDTO {
// Tampilkan requested qty sebelum approve, consumed qty setelah approve
var displayQty float64
if source.UsageQty > 0 {
// Sudah di-approve dan di-consume, tampilkan actual consumed quantity
displayQty = source.UsageQty
} else {
// Belum di-approve, tampilkan requested quantity
displayQty = source.RequestedQty
}
return LayingTransferSourceDTO{
SourceProjectFlockKandang: ToProjectFlockKandangSummaryDTO(source.SourceProjectFlockKandang),
Qty: source.UsageQty, // Ambil dari UsageQty (FIFO consumed quantity)
Qty: displayQty,
ProductWarehouse: ToProductWarehouseSummaryDTO(source.ProductWarehouse),
Note: source.Note,
}
@@ -110,8 +110,32 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
offset := (params.Page - 1) * params.Limit
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
db = s.withRelations(db)
// Apply search and filters
if params.Search != "" {
searchPattern := "%" + params.Search + "%"
db = db.Joins("LEFT JOIN project_flocks AS pf_from ON laying_transfers.from_project_flock_id = pf_from.id").
Joins("LEFT JOIN project_flocks AS pf_to ON laying_transfers.to_project_flock_id = pf_to.id").
Where("laying_transfers.transfer_number ILIKE ? OR laying_transfers.notes ILIKE ? OR pf_from.flock_name ILIKE ? OR pf_to.flock_name ILIKE ?",
searchPattern, searchPattern, searchPattern, searchPattern)
}
if params.TransferDate != "" {
db = db.Where("transfer_date::date = ?::date", params.TransferDate)
}
if params.FlockSource > 0 {
db = db.Where("from_project_flock_id = ?", params.FlockSource)
}
if params.FlockDestination > 0 {
db = db.Where("to_project_flock_id = ?", params.FlockDestination)
}
db = db.Order("created_at DESC")
// Apply relations for eager loading
db = s.withRelations(db)
return db
})
@@ -216,7 +240,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
for _, sourceDetail := range req.SourceKandangs {
if sourceDetail.Quantity <= 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang sumber harus lebih dari 0")
continue
}
totalSourceQty += sourceDetail.Quantity
@@ -247,11 +271,18 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
for _, targetDetail := range req.TargetKandangs {
if targetDetail.Quantity <= 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Jumlah kandang tujuan harus lebih dari 0")
continue
}
totalTargetQty += targetDetail.Quantity
}
if totalSourceQty == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Minimal harus ada 1 kandang sumber dengan jumlah lebih dari 0")
}
if totalTargetQty == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "Minimal harus ada 1 kandang tujuan dengan jumlah lebih dari 0")
}
if totalSourceQty != totalTargetQty {
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Jumlah total sumber (%.0f) harus sama dengan jumlah total tujuan (%.0f)", totalSourceQty, totalTargetQty))
}
@@ -279,11 +310,16 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
}
for _, sourceDetail := range req.SourceKandangs {
if sourceDetail.Quantity == 0 {
continue
}
productWarehouseId := sourceWarehouseMap[sourceDetail.ProjectFlockKandangId]
source := entity.LayingTransferSource{
LayingTransferId: createBody.Id,
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
RequestedQty: sourceDetail.Quantity, // Quantity yang diminta user
UsageQty: 0,
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
ProductWarehouseId: &productWarehouseId,
@@ -295,6 +331,9 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
}
for _, targetDetail := range req.TargetKandangs {
if targetDetail.Quantity == 0 {
continue
}
targetprojectFlockKandang, err := s.ProjectFlockKandangRepo.GetByID(c.Context(), targetDetail.ProjectFlockKandangId)
if err != nil {
@@ -463,8 +502,9 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
source := entity.LayingTransferSource{
LayingTransferId: id,
SourceProjectFlockKandangId: sourceDetail.ProjectFlockKandangId,
RequestedQty: sourceDetail.Quantity, // Quantity yang diminta user
UsageQty: 0,
PendingUsageQty: sourceDetail.Quantity,
PendingUsageQty: 0, // Di-set 0, biarkan FIFO Consume yang handle saat Approval
ProductWarehouseId: &productWarehouseId,
}
if err := sourceRepo.CreateOne(c.Context(), &source, nil); err != nil {
@@ -700,7 +740,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Target product warehouse tidak ditemukan untuk transfer %d", approvableID))
}
note := fmt.Sprintf("Transfer to Laying #%s - Target Kandang", transfer.TransferNumber)
note := fmt.Sprintf("Transfer to Laying #%s", transfer.TransferNumber)
replenishResult, err := s.FifoSvc.Replenish(c.Context(), commonSvc.StockReplenishRequest{
StockableKey: fifo.StockableKeyTransferToLayingIn,
StockableID: target.Id,
@@ -814,15 +854,15 @@ func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, project
kandangAvailableQty := make(map[uint]float64)
for _, kandang := range kandangs {
totalQty, err := s.ProjectFlockPopulationRepo.GetTotalQtyByProjectFlockKandangID(ctx.Context(), kandang.Id)
// Gunakan fungsi repository yang sama dengan recording service
totalAvailable, err := s.ProjectFlockPopulationRepo.GetAvailableQtyByProjectFlockKandangID(ctx.Context(), kandang.Id)
if err != nil {
s.Log.Warnf("Failed to get total qty for kandang %d: %+v", kandang.Id, err)
s.Log.Warnf("Failed to get available qty for kandang %d: %+v", kandang.Id, err)
kandangAvailableQty[kandang.Id] = 0
continue
}
kandangAvailableQty[kandang.Id] = totalQty
kandangAvailableQty[kandang.Id] = totalAvailable
}
return pf, kandangAvailableQty, nil
@@ -2,12 +2,12 @@ package validation
type SourceKandangDetail struct {
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
Quantity float64 `json:"quantity"`
}
type TargetKandangDetail struct {
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
Quantity float64 `json:"quantity"`
}
type Create struct {
@@ -29,8 +29,12 @@ type Update struct {
}
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty"`
TransferDate string `query:"transfer_date" validate:"omitempty"`
FlockSource uint `query:"flock_source" validate:"omitempty,number"`
FlockDestination uint `query:"flock_destination" validate:"omitempty,number"`
}
type Approve struct {