mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(be): add GetByProjectFlockKandangIDForUpdate method to lock chickin rows and prevent race conditions
This commit is contained in:
@@ -16,6 +16,7 @@ type ProjectChickinRepository interface {
|
||||
GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
GetTotalPendingUsageQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error)
|
||||
GetTotalChickinQtyByProjectFlockID(ctx context.Context, projectFlockID uint) (float64, error)
|
||||
GetByProjectFlockKandangIDForUpdate(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error)
|
||||
}
|
||||
|
||||
type ChickinRepositoryImpl struct {
|
||||
@@ -64,6 +65,20 @@ func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context,
|
||||
return chickins, nil
|
||||
}
|
||||
|
||||
// GetByProjectFlockKandangIDForUpdate locks chickin rows to prevent race condition
|
||||
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangIDForUpdate(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||
var chickins []entity.ProjectChickin
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Order("created_at DESC").
|
||||
Find(&chickins).
|
||||
Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chickins, nil
|
||||
}
|
||||
|
||||
func (r *ChickinRepositoryImpl) GetPendingByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||
var chickins []entity.ProjectChickin
|
||||
err := r.db.WithContext(ctx).
|
||||
|
||||
@@ -137,6 +137,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChikins := make([]*entity.ProjectChickin, 0)
|
||||
chickinQtyMap := make(map[uint]float64)
|
||||
|
||||
@@ -151,8 +152,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d is not bound to kandang's warehouse", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
if productWarehouse.ProjectFlockKandangId == nil || *productWarehouse.ProjectFlockKandangId != req.ProjectFlockKandangId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d is not attached to project_flock_kandang %d. Only product warehouses with matching project_flock_kandang_id can be chickin-ed", chickinReq.ProductWarehouseId, req.ProjectFlockKandangId))
|
||||
if productWarehouse.ProjectFlockKandangId != nil && *productWarehouse.ProjectFlockKandangId != req.ProjectFlockKandangId {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d belongs to different flock. Only product warehouses with project_flock_kandang_id = NULL or = %d can be used", chickinReq.ProductWarehouseId, req.ProjectFlockKandangId))
|
||||
}
|
||||
|
||||
chickinDate, err := utils.ParseDateString(chickinReq.ChickInDate)
|
||||
@@ -160,11 +161,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid ChickInDate format for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
availableQty := productWarehouse.Quantity
|
||||
if availableQty <= 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No available stock in product warehouse %d for chickin", chickinReq.ProductWarehouseId))
|
||||
}
|
||||
|
||||
newChickin := &entity.ProjectChickin{
|
||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||
ChickInDate: chickinDate,
|
||||
@@ -176,22 +172,46 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
||||
}
|
||||
|
||||
newChikins = append(newChikins, newChickin)
|
||||
chickinQtyMap[uint(idx)] = availableQty
|
||||
chickinQtyMap[uint(idx)] = productWarehouse.Quantity
|
||||
}
|
||||
|
||||
if len(newChikins) == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "No chickins to create")
|
||||
}
|
||||
|
||||
existingChikins, err := s.Repository.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing chickins")
|
||||
}
|
||||
|
||||
isFirstTime := len(existingChikins) == 0
|
||||
|
||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||
|
||||
repositoryTx := repository.NewChickinRepository(dbTransaction)
|
||||
existingChikins, err := repositoryTx.GetByProjectFlockKandangIDForUpdate(c.Context(), req.ProjectFlockKandangId)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing chickins")
|
||||
}
|
||||
|
||||
isFirstTime := len(existingChikins) == 0
|
||||
|
||||
pendingQtyMap := make(map[uint]float64)
|
||||
for _, existingChickin := range existingChikins {
|
||||
if existingChickin.PendingUsageQty > 0 {
|
||||
pendingQtyMap[existingChickin.ProductWarehouseId] += existingChickin.PendingUsageQty
|
||||
}
|
||||
}
|
||||
|
||||
for idx, chickin := range newChikins {
|
||||
pendingQty := pendingQtyMap[chickin.ProductWarehouseId]
|
||||
desiredQty := chickinQtyMap[uint(idx)]
|
||||
|
||||
availableQty := desiredQty - pendingQty
|
||||
if availableQty < 0 {
|
||||
availableQty = 0
|
||||
}
|
||||
|
||||
if availableQty <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No available stock in product warehouse %d for chickin. Warehouse: %.0f, Pending: %.0f, Available: %.0f", chickin.ProductWarehouseId, desiredQty, pendingQty, availableQty))
|
||||
}
|
||||
|
||||
chickinQtyMap[uint(idx)] = availableQty
|
||||
}
|
||||
|
||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||
|
||||
if err := s.Repository.WithTx(dbTransaction).CreateMany(c.Context(), newChikins, nil); err != nil {
|
||||
|
||||
+1
-1
@@ -191,7 +191,7 @@ func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, project
|
||||
result := make(map[uint]float64)
|
||||
for _, pw := range products {
|
||||
|
||||
if pw.ProjectFlockKandangId != nil && *pw.ProjectFlockKandangId == projectFlockKandang.Id {
|
||||
if pw.ProjectFlockKandangId == nil || *pw.ProjectFlockKandangId == projectFlockKandang.Id {
|
||||
availableQty, err := s.calculateAvailableQuantityForProductWarehouse(c, projectFlockKandang, &pw)
|
||||
if err != nil {
|
||||
s.Log.Warnf("Failed to calculate available quantity for product warehouse %d: %v", pw.Id, err)
|
||||
|
||||
Reference in New Issue
Block a user