mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat(BE-48): auto-create product_warehouse on stock adjustment & remove unused APIs
- Change logic: automatically create product_warehouse if it does not exist during stock adjustment - Remove unnecessary/unused API endpoints - Ensure adjustment process continues even if product_warehouse was not previously available
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
-66
@@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user