mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
FIX[BE]Lock chickins, accumulate pending qty, use qty key
This commit is contained in:
@@ -68,11 +68,17 @@ func (r *ChickinRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context,
|
|||||||
// GetByProjectFlockKandangIDForUpdate locks chickin rows to prevent race condition
|
// GetByProjectFlockKandangIDForUpdate locks chickin rows to prevent race condition
|
||||||
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangIDForUpdate(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
func (r *ChickinRepositoryImpl) GetByProjectFlockKandangIDForUpdate(ctx context.Context, projectFlockKandangID uint) ([]entity.ProjectChickin, error) {
|
||||||
var chickins []entity.ProjectChickin
|
var chickins []entity.ProjectChickin
|
||||||
|
// CRITICAL: Use FOR UPDATE to lock rows and prevent concurrent chickin requests
|
||||||
|
// This ensures that simultaneous requests wait for each other and read consistent pending_qty
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
Raw(`
|
||||||
Order("created_at DESC").
|
SELECT * FROM project_chickins
|
||||||
Find(&chickins).
|
WHERE project_flock_kandang_id = ?
|
||||||
Error
|
AND deleted_at IS NULL
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
FOR UPDATE
|
||||||
|
`, projectFlockKandangID).
|
||||||
|
Scan(&chickins).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Validate chickins sequentially to prevent over-allocation within the same request
|
||||||
|
// pendingQtyMap is accumulated as we validate each chickin to ensure total pending doesn't exceed available stock
|
||||||
for idx, chickin := range newChikins {
|
for idx, chickin := range newChikins {
|
||||||
pendingQty := pendingQtyMap[chickin.ProductWarehouseId]
|
pendingQty := pendingQtyMap[chickin.ProductWarehouseId]
|
||||||
desiredQty := chickinQtyMap[uint(idx)]
|
desiredQty := chickinQtyMap[uint(idx)]
|
||||||
@@ -210,6 +212,10 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
}
|
}
|
||||||
|
|
||||||
chickinQtyMap[uint(idx)] = availableQty
|
chickinQtyMap[uint(idx)] = availableQty
|
||||||
|
|
||||||
|
// ACCUMULATE pending for this product warehouse so NEXT chickin in same request sees it
|
||||||
|
// This prevents multiple chickins in same request from over-allocating the same stock
|
||||||
|
pendingQtyMap[chickin.ProductWarehouseId] += availableQty
|
||||||
}
|
}
|
||||||
|
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reduce project flock population")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"quantity": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
|
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"qty": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
|||||||
if oldSource.ProductWarehouseId != nil && oldSource.Qty > 0 {
|
if oldSource.ProductWarehouseId != nil && oldSource.Qty > 0 {
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *oldSource.ProductWarehouseId, map[string]any{
|
if err := productWarehouseRepoTx.PatchOne(c.Context(), *oldSource.ProductWarehouseId, map[string]any{
|
||||||
"quantity": gorm.Expr("qty + ?", oldSource.Qty),
|
"qty": gorm.Expr("qty + ?", oldSource.Qty),
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore warehouse quantity")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore warehouse quantity")
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ func (s transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"quantity": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
|
if err := productWarehouseRepoTx.PatchOne(c.Context(), productWarehouseId, map[string]any{"qty": gorm.Expr("qty - ?", sourceDetail.Quantity)}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update source warehouse quantity")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,7 +544,7 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
if source.ProductWarehouseId != nil && source.Qty > 0 {
|
if source.ProductWarehouseId != nil && source.Qty > 0 {
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(c.Context(), *source.ProductWarehouseId, map[string]any{
|
if err := productWarehouseRepoTx.PatchOne(c.Context(), *source.ProductWarehouseId, map[string]any{
|
||||||
"quantity": gorm.Expr("qty + ?", source.Qty),
|
"qty": gorm.Expr("qty + ?", source.Qty),
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore source warehouse quantity")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore source warehouse quantity")
|
||||||
}
|
}
|
||||||
@@ -766,7 +766,7 @@ func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context,
|
|||||||
existing, err := productWarehouseRepoTx.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
existing, err := productWarehouseRepoTx.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
||||||
if err == nil && existing != nil {
|
if err == nil && existing != nil {
|
||||||
|
|
||||||
if err := productWarehouseRepoTx.PatchOne(ctx, existing.Id, map[string]any{"quantity": gorm.Expr("qty + ?", quantity)}, nil); err != nil {
|
if err := productWarehouseRepoTx.PatchOne(ctx, existing.Id, map[string]any{"qty": gorm.Expr("qty + ?", quantity)}, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return existing, nil
|
return existing, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user