diff --git a/internal/modules/inventory/adjustments/module.go b/internal/modules/inventory/adjustments/module.go index 294cf9dc..cfe01118 100644 --- a/internal/modules/inventory/adjustments/module.go +++ b/internal/modules/inventory/adjustments/module.go @@ -7,6 +7,7 @@ import ( sAdjustment "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/services" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + rproduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" @@ -21,8 +22,9 @@ func (AdjustmentModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validat warehouseRepo := rWarehouse.NewWarehouseRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) userRepo := rUser.NewUserRepository(db) + productRepo := rproduct.NewProductRepository(db) - adjustmentService := sAdjustment.NewAdjustmentService(stockLogsRepo, warehouseRepo, productWarehouseRepo, validate) + adjustmentService := sAdjustment.NewAdjustmentService(productRepo, stockLogsRepo, warehouseRepo, productWarehouseRepo, validate) userService := sUser.NewUserService(userRepo, validate) AdjustmentRoutes(router, userService, adjustmentService) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 601cc6c2..929a5c8a 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -7,6 +7,7 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations" ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" stockLogsRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -29,15 +30,17 @@ type adjustmentService struct { StockLogsRepository stockLogsRepo.StockLogRepository WarehouseRepo warehouseRepo.WarehouseRepository ProductWarehouseRepo ProductWarehouse.ProductWarehouseRepository + ProductRepo productRepo.ProductRepository } -func NewAdjustmentService(stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, validate *validator.Validate) AdjustmentService { +func NewAdjustmentService(productRepo productRepo.ProductRepository, stockLogsRepo stockLogsRepo.StockLogRepository, warehouseRepo warehouseRepo.WarehouseRepository, productWarehouseRepo ProductWarehouse.ProductWarehouseRepository, validate *validator.Validate) AdjustmentService { return &adjustmentService{ Log: utils.Log, Validate: validate, StockLogsRepository: stockLogsRepo, WarehouseRepo: warehouseRepo, ProductWarehouseRepo: productWarehouseRepo, + ProductRepo: productRepo, } } @@ -74,14 +77,27 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } ctx := c.Context() - productWarehouseExists, err := s.ProductWarehouseRepo.ProductWarehouseExists(ctx, uint(req.ProductID), uint(req.WarehouseID), nil) + isProductExist, err := s.ProductRepo.IdExists(c.Context(), uint(req.ProductID)) if err != nil { - return nil, err + s.Log.Errorf("Failed to check product existence: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") } - if !productWarehouseExists { - return nil, fiber.NewError(fiber.StatusBadRequest, "Product warehouse not found") + if !isProductExist { + return nil, fiber.NewError(fiber.StatusBadRequest, "Product not found") } + isWarehouseExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(req.WarehouseID)) + if err != nil { + s.Log.Errorf("Failed to check warehouse existence: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") + } + if !isWarehouseExist { + return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse not found") + } + + if req.Quantity <= 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero") + } transactionType := strings.ToUpper(req.TransactionType) if transactionType != entity.TransactionTypeIncrease && transactionType != entity.TransactionTypeDecrease { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type") @@ -89,16 +105,33 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e var createdLogId uint + isProductWarehouseExist, err := s.ProductWarehouseRepo.ProductWarehouseExistByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) + if err != nil { + s.Log.Errorf("Failed to check product warehouse existence: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product warehouse") + } + if !isProductWarehouseExist { + + newPW := &entity.ProductWarehouse{ + ProductId: uint(req.ProductID), + WarehouseId: uint(req.WarehouseID), + Quantity: 0, + CreatedBy: 1, // TODO: should Get from auth middleware + } + if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil { + s.Log.Errorf("Failed to create product warehouse: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create product warehouse") + } + s.Log.Infof("Product warehouse created: %+v", newPW.Id) + } + err = s.StockLogsRepository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { - // Get product warehouse by product id and warehouse id (read operation, no transaction needed) + productWarehouse, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) if err != nil { - return err + s.Log.Errorf("Failed to get product warehouse: %+v", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse") } - if productWarehouse == nil { - return fiber.NewError(fiber.StatusBadRequest, "Product warehouse not found") - } - s.Log.Infof("Product Warehouse found: %+v", productWarehouse.Id) afterQuantity := productWarehouse.Quantity if transactionType == entity.TransactionTypeIncrease { @@ -135,7 +168,6 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } s.Log.Infof("Product warehouse quantity updated: %+v", productWarehouse.Id) - // Set createdLogId to get the log with relations after transaction createdLogId = newLog.Id return nil }) diff --git a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go index 5e0ea423..a0b72a4d 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -72,70 +72,4 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error { }) } -func (u *ProductWarehouseController) CreateOne(c *fiber.Ctx) error { - req := new(validation.Create) - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } - - result, err := u.ProductWarehouseService.CreateOne(c, req) - if err != nil { - return err - } - - return c.Status(fiber.StatusCreated). - JSON(response.Success{ - Code: fiber.StatusCreated, - Status: "success", - Message: "Create productWarehouse successfully", - Data: dto.ToProductWarehouseListDTO(*result), - }) -} - -func (u *ProductWarehouseController) UpdateOne(c *fiber.Ctx) error { - req := new(validation.Update) - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") - } - - result, err := u.ProductWarehouseService.UpdateOne(c, req, uint(id)) - if err != nil { - return err - } - - return c.Status(fiber.StatusOK). - JSON(response.Success{ - Code: fiber.StatusOK, - Status: "success", - Message: "Update productWarehouse successfully", - Data: dto.ToProductWarehouseListDTO(*result), - }) -} - -func (u *ProductWarehouseController) DeleteOne(c *fiber.Ctx) error { - param := c.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - if err := u.ProductWarehouseService.DeleteOne(c, uint(id)); err != nil { - return err - } - - return c.Status(fiber.StatusOK). - JSON(response.Common{ - Code: fiber.StatusOK, - Status: "success", - Message: "Delete productWarehouse successfully", - }) -} 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 0398a825..cc4adf64 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -13,6 +13,7 @@ type ProductWarehouseRepository interface { ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) IsProductExist(ctx context.Context, productId uint) (bool, error) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) + ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) ExistsByID(ctx context.Context, id uint) (bool, error) GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error) } @@ -53,6 +54,17 @@ func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint return repository.Exists[entity.ProductWarehouse](ctx, r.db, id) } +func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) { + var count int64 + if err := r.db.WithContext(ctx). + Model(&entity.ProductWarehouse{}). + Where("product_id = ? AND warehouse_id = ?", productId, warehouseId). + Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} + func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error) { var productWarehouse entity.ProductWarehouse if err := r.DB().WithContext(ctx).Where("product_id = ? AND warehouse_id = ?", productId, warehouseId).First(&productWarehouse).Error; err != nil { diff --git a/internal/modules/inventory/product-warehouses/route.go b/internal/modules/inventory/product-warehouses/route.go index b0cc9c65..429c1d16 100644 --- a/internal/modules/inventory/product-warehouses/route.go +++ b/internal/modules/inventory/product-warehouses/route.go @@ -21,8 +21,6 @@ func ProductWarehouseRoutes(v1 fiber.Router, u user.UserService, s productWareho // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) route.Get("/", ctrl.GetAll) - route.Post("/", ctrl.CreateOne) route.Get("/:id", ctrl.GetOne) - route.Patch("/:id", ctrl.UpdateOne) - route.Delete("/:id", ctrl.DeleteOne) + } 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 03c7c9a1..7a1ff00e 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -17,9 +17,6 @@ import ( type ProductWarehouseService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProductWarehouse, error) - CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProductWarehouse, error) - UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProductWarehouse, error) - DeleteOne(ctx *fiber.Ctx, id uint) error } type productWarehouseService struct { @@ -79,125 +76,3 @@ func (s productWarehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductW } return productWarehouse, nil } - -func (s *productWarehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProductWarehouse, error) { - if err := s.Validate.Struct(req); err != nil { - return nil, err - } - - isProductExist, err := s.Repository.IsProductExist(c.Context(), req.ProductId) - if err != nil { - s.Log.Errorf("Failed to check product existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check product existence") - } - if !isProductExist { - return nil, fiber.NewError(fiber.StatusBadRequest, "Product not found") - } - - isWarehouseExist, err := s.Repository.IsWarehouseExist(c.Context(), req.WarehouseId) - if err != nil { - s.Log.Errorf("Failed to check warehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check warehouse existence") - } - if !isWarehouseExist { - return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse not found") - } - - // chceking if productWarehouse with same product_id and warehouse_id already - exists, err := s.Repository.ProductWarehouseExists(c.Context(), req.ProductId, req.WarehouseId, nil) - if err != nil { - s.Log.Errorf("Failed to check productWarehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check productWarehouse existence") - } - - if exists { - return nil, fiber.NewError(fiber.StatusConflict, "ProductWarehouse already exists") - } - - createBody := &entity.ProductWarehouse{ - ProductId: req.ProductId, - WarehouseId: req.WarehouseId, - Quantity: req.Quantity, - CreatedBy: 1, // TODO: Ganti dengan user ID dari context setelah middleware auth diimplementasi - } - - if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { - s.Log.Errorf("Failed to create productWarehouse: %+v", err) - return nil, err - } - - return s.GetOne(c, createBody.Id) -} - -func (s productWarehouseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProductWarehouse, error) { - if err := s.Validate.Struct(req); err != nil { - return nil, err - } - - // validation Id exist - if exists, err := s.Repository.ExistsByID(c.Context(), id); err != nil { - s.Log.Errorf("Failed to check productWarehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check productWarehouse existence") - } else if !exists { - return nil, fiber.NewError(fiber.StatusNotFound, "ProductWarehouse not found") - } - // validation productId and warehouseId exist - if req.ProductId != nil { - isProductExist, err := s.Repository.IsProductExist(c.Context(), *req.ProductId) - if err != nil { - s.Log.Errorf("Failed to check product existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check product existence") - } - if !isProductExist { - return nil, fiber.NewError(fiber.StatusBadRequest, "Product not found") - } - } - - if req.WarehouseId != nil { - isWarehouseExist, err := s.Repository.IsWarehouseExist(c.Context(), *req.WarehouseId) - if err != nil { - s.Log.Errorf("Failed to check warehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check warehouse existence") - } - if !isWarehouseExist { - return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") - } - } - - updateBody := make(map[string]any) - - if req.ProductId != nil { - updateBody["product_id"] = *req.ProductId - } - if req.WarehouseId != nil { - updateBody["warehouse_id"] = *req.WarehouseId - } - if req.Quantity != nil { - updateBody["quantity"] = *req.Quantity - } - - if len(updateBody) == 0 { - return s.GetOne(c, id) - } - - if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "ProductWarehouse not found") - } - s.Log.Errorf("Failed to update productWarehouse: %+v", err) - return nil, err - } - - return s.GetOne(c, id) -} - -func (s productWarehouseService) DeleteOne(c *fiber.Ctx, id uint) error { - if err := s.Repository.DeleteOne(c.Context(), id); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.NewError(fiber.StatusNotFound, "ProductWarehouse not found") - } - s.Log.Errorf("Failed to delete productWarehouse: %+v", err) - return err - } - return nil -} diff --git a/internal/modules/master/products/repositories/product.repository.go b/internal/modules/master/products/repositories/product.repository.go index 283b8547..06672f5f 100644 --- a/internal/modules/master/products/repositories/product.repository.go +++ b/internal/modules/master/products/repositories/product.repository.go @@ -13,6 +13,7 @@ type ProductRepository interface { NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) SkuExists(ctx context.Context, sku string, excludeID *uint) (bool, error) UomExists(ctx context.Context, uomID uint) (bool, error) + IdExists(ctx context.Context, id uint) (bool, error) CategoryExists(ctx context.Context, categoryID uint) (bool, error) GetSuppliersByIDs(ctx context.Context, supplierIDs []uint) ([]entity.Supplier, error) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, supplierIDs []uint) error @@ -194,3 +195,7 @@ func (r *ProductRepositoryImpl) GetFlags(ctx context.Context, productID uint) ([ } return flags, nil } + +func (r *ProductRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) { + return repository.Exists[entity.Product](ctx, r.DB(), id) +} diff --git a/internal/modules/master/warehouses/repositories/warehouse.repository.go b/internal/modules/master/warehouses/repositories/warehouse.repository.go index 6a4e6c16..5c791e01 100644 --- a/internal/modules/master/warehouses/repositories/warehouse.repository.go +++ b/internal/modules/master/warehouses/repositories/warehouse.repository.go @@ -14,6 +14,7 @@ type WarehouseRepository interface { LocationExists(ctx context.Context, locationId uint) (bool, error) KandangExists(ctx context.Context, kandangId uint) (bool, error) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) + IdExists(ctx context.Context, id uint) (bool, error) } type WarehouseRepositoryImpl struct { @@ -43,3 +44,6 @@ func (r *WarehouseRepositoryImpl) KandangExists(ctx context.Context, kandangId u func (r *WarehouseRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) { return repository.ExistsByName[entity.Warehouse](ctx, r.db, name, excludeID) } +func (r *WarehouseRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) { + return repository.Exists[entity.Warehouse](ctx, r.db, id) +}