package service import ( "errors" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-stocks/validations" productRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/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" ) type ProductStockService interface { GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Product, int64, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.Product, error) } type productStockService struct { Log *logrus.Logger Validate *validator.Validate ProductRepository productRepository.ProductRepository } func NewProductStockService( productRepo productRepository.ProductRepository, validate *validator.Validate, ) ProductStockService { return &productStockService{ Log: utils.Log, Validate: validate, ProductRepository: productRepo, } } func (s productStockService) withRelations(db *gorm.DB, locationScope, areaScope m.ScopeFilter) *gorm.DB { warehouseScope := func(db *gorm.DB) *gorm.DB { if locationScope.Restrict { db = db.Where("warehouses.location_id IN ?", locationScope.IDs) } if areaScope.Restrict { db = db.Where("warehouses.area_id IN ?", areaScope.IDs) } return db } productWarehouseScope := func(db *gorm.DB) *gorm.DB { db = db.Joins("JOIN warehouses w ON w.id = product_warehouses.warehouse_id") if locationScope.Restrict { db = db.Where("w.location_id IN ?", locationScope.IDs) } if areaScope.Restrict { db = db.Where("w.area_id IN ?", areaScope.IDs) } return db } stockLogScope := func(db *gorm.DB) *gorm.DB { db = db. Joins("JOIN product_warehouses pw ON pw.id = stock_logs.product_warehouse_id"). Joins("JOIN warehouses w ON w.id = pw.warehouse_id") if locationScope.Restrict { db = db.Where("w.location_id IN ?", locationScope.IDs) } if areaScope.Restrict { db = db.Where("w.area_id IN ?", areaScope.IDs) } return db.Order("stock_logs.created_at ASC") } return db. Preload("CreatedUser"). Preload("Uom"). Preload("ProductCategory"). Preload("Flags"). Preload("ProductWarehouses", productWarehouseScope). Preload("ProductWarehouses.Warehouse", warehouseScope). Preload("ProductWarehouses.Warehouse.Location"). Preload("ProductWarehouses.Warehouse.Location.Area"). Preload("ProductWarehouses.StockLogs", stockLogScope). Preload("ProductWarehouses.StockLogs.CreatedUser"). Preload("ProductSuppliers"). Preload("ProductSuppliers.Supplier", func(db *gorm.DB) *gorm.DB { return db.Order("suppliers.name ASC") }) } func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Product, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err } locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB()) if err != nil { return nil, 0, err } offset := (params.Page - 1) * params.Limit productStocks, total, err := s.ProductRepository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { if locationScope.Restrict || areaScope.Restrict { if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) { return db.Where("1 = 0") } db = db.Where(`EXISTS ( SELECT 1 FROM product_warehouses pw JOIN warehouses w ON w.id = pw.warehouse_id WHERE pw.product_id = products.id AND pw.qty > 0 AND (? OR w.location_id IN ?) AND (? OR w.area_id IN ?) )`, !locationScope.Restrict, locationScope.IDs, !areaScope.Restrict, areaScope.IDs, ) } else { db = db.Where(`EXISTS ( SELECT 1 FROM product_warehouses pw WHERE pw.product_id = products.id AND pw.qty > 0 )`) } db = s.withRelations(db, locationScope, areaScope) if params.Search != "" { db = db.Where("products.name ILIKE ?", "%"+params.Search+"%") } return db.Order("products.created_at DESC").Order("products.updated_at DESC") }) if err != nil { s.Log.Errorf("Failed to get productStocks: %+v", err) return nil, 0, err } return productStocks, total, nil } func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, error) { locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB()) if err != nil { return nil, err } if locationScope.Restrict || areaScope.Restrict { if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) { return nil, fiber.NewError(fiber.StatusNotFound, "Product not found") } var count int64 if err := s.ProductRepository.DB().WithContext(c.Context()). Table("product_warehouses pw"). Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). Where("pw.product_id = ?", id). Where("pw.qty > 0"). Where("(? OR w.location_id IN ?)", !locationScope.Restrict, locationScope.IDs). Where("(? OR w.area_id IN ?)", !areaScope.Restrict, areaScope.IDs). Count(&count).Error; err != nil { return nil, err } if count == 0 { return nil, fiber.NewError(fiber.StatusNotFound, "Product not found") } } product, err := s.ProductRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { return s.withRelations(db, locationScope, areaScope) }) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Product not found") } if err != nil { s.Log.Errorf("Failed get product by id: %+v", err) return nil, err } return product, nil }