From a73b44808ff2e6bebbc090d7e309f7016c9ede6d Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 21 Jan 2026 15:16:30 +0700 Subject: [PATCH] FIX[BE]: fixing wrong index data on adjustment. change get from stocklogs to adjustment table --- internal/entities/adjustment_stock.go | 29 ++---- .../adjustments/dto/adjustment.dto.go | 30 +++--- .../adjustment_stock.repository.go | 8 ++ .../services/adjustment.service.go | 99 ++++++++++++------- .../services/product_warehouse.service.go | 7 +- 5 files changed, 99 insertions(+), 74 deletions(-) diff --git a/internal/entities/adjustment_stock.go b/internal/entities/adjustment_stock.go index bbc93167..ef27d0c2 100644 --- a/internal/entities/adjustment_stock.go +++ b/internal/entities/adjustment_stock.go @@ -2,28 +2,17 @@ package entities import "time" -// AdjustmentStock tracks FIFO allocation for stock adjustments -// - For INCREASE adjustments (Stockable): Tracks stock added to warehouse -// - For DECREASE adjustments (Usable): Tracks stock consumed from warehouse type AdjustmentStock struct { - Id uint `gorm:"primaryKey"` - StockLogId uint `gorm:"column:stock_log_id;not null;index"` - ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` + Id uint `gorm:"primaryKey"` + StockLogId uint `gorm:"column:stock_log_id;not null;index"` + ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"` + TotalQty float64 `gorm:"column:total_qty;default:0"` + TotalUsed float64 `gorm:"column:total_used;default:0"` + UsageQty float64 `gorm:"column:usage_qty;default:0"` + PendingQty float64 `gorm:"column:pending_qty;default:0"` + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` - // === FIFO FIELDS FOR INCREASE ADJUSTMENT (Stockable) === - // Tracks stock added to warehouse via adjustment INCREASE - TotalQty float64 `gorm:"column:total_qty;default:0"` // Total lot quantity available - TotalUsed float64 `gorm:"column:total_used;default:0"` // Quantity already used from this lot - - // === FIFO FIELDS FOR DECREASE ADJUSTMENT (Usable) === - // Tracks stock consumed from warehouse via adjustment DECREASE - UsageQty float64 `gorm:"column:usage_qty;default:0"` // Actual quantity consumed - PendingQty float64 `gorm:"column:pending_qty;default:0"` // Pending quantity (waiting for stock) - - CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"` - UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"` - - // Relations StockLog *StockLog `gorm:"foreignKey:StockLogId;references:Id"` ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` } diff --git a/internal/modules/inventory/adjustments/dto/adjustment.dto.go b/internal/modules/inventory/adjustments/dto/adjustment.dto.go index 008f9966..1ce3da1b 100644 --- a/internal/modules/inventory/adjustments/dto/adjustment.dto.go +++ b/internal/modules/inventory/adjustments/dto/adjustment.dto.go @@ -100,38 +100,42 @@ func ToProductWarehouseDTO(e *entity.ProductWarehouse) *ProductWarehouseDTO { } } -func ToAdjustmentRelationDTO(e *entity.StockLog) AdjustmentRelationDTO { +func ToAdjustmentRelationDTO(e *entity.AdjustmentStock) AdjustmentRelationDTO { return AdjustmentRelationDTO{ Id: e.Id, - Note: e.Notes, - Increase: e.Increase, - Decrease: e.Decrease, + Note: e.StockLog.Notes, + Increase: e.TotalQty, + Decrease: e.UsageQty, ProductWarehouseId: e.ProductWarehouseId, ProductWarehouse: ToProductWarehouseDTO(e.ProductWarehouse), } } -func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO { +func ToAdjustmentListDTO(e *entity.AdjustmentStock) AdjustmentListDTO { var createdUser *userDTO.UserRelationDTO - if e.CreatedUser != nil { + if e.StockLog != nil && e.StockLog.CreatedUser != nil { createdUser = &userDTO.UserRelationDTO{ - Id: e.CreatedUser.Id, - IdUser: e.CreatedUser.IdUser, - Email: e.CreatedUser.Email, - Name: e.CreatedUser.Name, + Id: e.StockLog.CreatedUser.Id, + IdUser: e.StockLog.CreatedUser.IdUser, + Email: e.StockLog.CreatedUser.Email, + Name: e.StockLog.CreatedUser.Name, } } + createdAt := time.Time{} + if e.StockLog != nil { + createdAt = e.StockLog.CreatedAt + } + return AdjustmentListDTO{ AdjustmentRelationDTO: ToAdjustmentRelationDTO(e), CreatedUser: createdUser, - CreatedAt: e.CreatedAt, + CreatedAt: createdAt, } } -func ToAdjustmentDetailDTO(e *entity.StockLog) AdjustmentDetailDTO { +func ToAdjustmentDetailDTO(e *entity.AdjustmentStock) AdjustmentDetailDTO { return AdjustmentDetailDTO{ AdjustmentListDTO: ToAdjustmentListDTO(e), - // UpdatedAt: e.UpdatedAt, } } diff --git a/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go b/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go index 8d62b05c..fa2685e7 100644 --- a/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go +++ b/internal/modules/inventory/adjustments/repositories/adjustment_stock.repository.go @@ -33,6 +33,14 @@ func (r *adjustmentStockRepositoryImpl) CreateOne(ctx context.Context, data *ent func (r *adjustmentStockRepositoryImpl) GetByStockLogID(ctx context.Context, stockLogID uint) (*entity.AdjustmentStock, error) { var record entity.AdjustmentStock err := r.db.WithContext(ctx). + Preload("StockLog"). + Preload("StockLog.ProductWarehouse"). + Preload("StockLog.ProductWarehouse.Product"). + Preload("StockLog.ProductWarehouse.Warehouse"). + Preload("StockLog.CreatedUser"). + Preload("ProductWarehouse"). + Preload("ProductWarehouse.Product"). + Preload("ProductWarehouse.Warehouse"). Where("stock_log_id = ?", stockLogID). First(&record).Error if err != nil { diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 71b985c2..c92d059b 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -25,9 +25,9 @@ import ( ) type AdjustmentService interface { - Adjustment(ctx *fiber.Ctx, req *validation.Create) (*entity.StockLog, error) - GetOne(ctx *fiber.Ctx, id uint) (*entity.StockLog, error) - AdjustmentHistory(ctx *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) + Adjustment(ctx *fiber.Ctx, req *validation.Create) (*entity.AdjustmentStock, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) + AdjustmentHistory(ctx *fiber.Ctx, query *validation.Query) ([]*entity.AdjustmentStock, int64, error) } type adjustmentService struct { @@ -73,10 +73,8 @@ func (s *adjustmentService) withRelations(db *gorm.DB) *gorm.DB { Preload("CreatedUser") } -func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, error) { - stockLog, err := s.StockLogsRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { - return s.withRelations(db).Preload("ProductWarehouse.Product.ProductCategory") - }) +func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) { + adjustmentStock, err := s.AdjustmentStockRepository.GetByStockLogID(c.Context(), id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found") @@ -85,14 +83,10 @@ func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, err return nil, err } - if stockLog.LoggableType != string(utils.StockLogTypeAdjustment) { - return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found") - } - - return stockLog, nil + return adjustmentStock, nil } -func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*entity.StockLog, error) { +func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*entity.AdjustmentStock, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } @@ -111,12 +105,13 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e if req.Quantity <= 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero") } + transactionType := strings.ToUpper(req.TransactionType) if transactionType != string(utils.StockLogTransactionTypeIncrease) && transactionType != string(utils.StockLogTransactionTypeDecrease) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type") } - var createdLogId uint + var createdAdjustmentStockId uint var projectFlockKandangID *uint pfkID, err := s.getActiveProjectFlockKandangID(ctx, uint(req.WarehouseID)) @@ -151,7 +146,8 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, err } err = s.StockLogsRepository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { - productWarehouse, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) + + productWarehouse, err := s.ProductWarehouseRepo.FindByProductWarehouseAndPfk(ctx, uint(req.ProductID), uint(req.WarehouseID), projectFlockKandangID) if err != nil { s.Log.Errorf("Failed to get product warehouse: %+v", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse") @@ -171,14 +167,14 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e newLog.Increase = afterQuantity } else { if productWarehouse.Quantity < req.Quantity { - return fiber.NewError(fiber.StatusBadRequest, "Insufficient stock for adjustment") + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock. Current: %.2f, Requested: %.2f", productWarehouse.Quantity, req.Quantity)) } afterQuantity -= req.Quantity newLog.Decrease = afterQuantity } if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil { - s.Log.Errorf("Failed to create stock log: %+v", err) + return err } @@ -187,7 +183,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e ProductWarehouseId: productWarehouse.Id, } if err := s.AdjustmentStockRepository.WithTx(tx).CreateOne(ctx, adjustmentStock, nil); err != nil { - s.Log.Errorf("Failed to create adjustment stock: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create adjustment stock record") } @@ -212,7 +208,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e UsableID: adjustmentStock.Id, ProductWarehouseID: uint(productWarehouse.Id), Quantity: req.Quantity, - AllowPending: false, // Don't allow pending for adjustment + AllowPending: false, Tx: tx, }) if err != nil { @@ -220,24 +216,27 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } } - // Update ProductWarehouse quantity (for backward compatibility/reporting) - + // LEGACY: Update ProductWarehouse quantity (for backward compatibility/reporting) productWarehouse.Quantity = afterQuantity if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(ctx, productWarehouse.Id, productWarehouse, nil); err != nil { s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return err } - createdLogId = newLog.Id + createdAdjustmentStockId = adjustmentStock.Id return nil }) if err != nil { s.Log.Errorf("Transaction failed in CreateOne: %+v", err) + var fiberErr *fiber.Error + if errors.As(err, &fiberErr) { + return nil, err + } return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to process adjustment transaction") } - return s.GetOne(c, createdLogId) + return s.GetOne(c, createdAdjustmentStockId) } func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context, warehouseID uint) (uint, error) { @@ -266,13 +265,15 @@ func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context, return uint(projectFlockKandang.Id), nil } -func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.StockLog, int64, error) { +func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.AdjustmentStock, int64, error) { if err := s.Validate.Struct(query); err != nil { return nil, 0, err } offset := (query.Page - 1) * query.Limit + var isProductsExist bool isWarehousesExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(query.WarehouseID)) + if err != nil { return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") } @@ -280,7 +281,8 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") } - isProductsExist, err := s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) + isProductsExist, err = s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) + if err != nil { s.Log.Errorf("Failed to check product existence: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") @@ -289,28 +291,51 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found") } - stockLogs, total, err := s.StockLogsRepository.GetAll(c.Context(), offset, query.Limit, func(db *gorm.DB) *gorm.DB { + var adjustmentStocks []entity.AdjustmentStock + var total int64 - db = s.withRelations(db) + q := s.AdjustmentStockRepository.DB().WithContext(c.Context()).Model(&entity.AdjustmentStock{}). + Preload("StockLog"). + Preload("StockLog.ProductWarehouse"). + Preload("StockLog.ProductWarehouse.Product"). + Preload("StockLog.ProductWarehouse.Warehouse"). + Preload("StockLog.CreatedUser"). + Preload("ProductWarehouse"). + Preload("ProductWarehouse.Product"). + Preload("ProductWarehouse.Warehouse") - db = db.Where("loggable_type = ?", string(utils.StockLogTypeAdjustment)) + if query.ProductID > 0 { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id"). + Where("product_warehouses.product_id = ?", query.ProductID) + } - if query.TransactionType != "" { - db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType)) - } - db = s.StockLogsRepository.ApplyProductWarehouseFilters(db, uint(query.ProductID), uint(query.WarehouseID)) + if query.WarehouseID > 0 { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id"). + Where("product_warehouses.warehouse_id = ?", query.WarehouseID) + } - return db.Order("created_at DESC") - }) + if query.TransactionType != "" { + q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id"). + Where("stock_logs.transaction_type = ?", strings.ToUpper(query.TransactionType)) + } + + if err = q.Count(&total).Error; err != nil { + s.Log.Errorf("Failed to get adjustments: %+v", err) + return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to get adjustment history") + } + + err = q.Offset(offset).Limit(query.Limit).Order("created_at DESC").Find(&adjustmentStocks).Error if err != nil { s.Log.Errorf("Failed to get adjustments: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to get adjustment history") } - result := make([]*entity.StockLog, len(stockLogs)) - for i, v := range stockLogs { - result[i] = &v + result := make([]*entity.AdjustmentStock, len(adjustmentStocks)) + for i := range adjustmentStocks { + result[i] = &adjustmentStocks[i] } return result, total, nil diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index ea194c36..5b89808c 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -3,15 +3,14 @@ package service import ( "errors" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/sirupsen/logrus" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations" kandangrepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" - - "github.com/go-playground/validator/v10" - "github.com/gofiber/fiber/v2" - "github.com/sirupsen/logrus" "gorm.io/gorm" )