diff --git a/cmd/main.go b/cmd/main.go index c3053d7..5dae251 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,6 +12,7 @@ import ( maintenancegroup "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group" "github.com/Caknoooo/go-gin-clean-starter/modules/menu" "github.com/Caknoooo/go-gin-clean-starter/modules/permissions" + "github.com/Caknoooo/go-gin-clean-starter/modules/product" "github.com/Caknoooo/go-gin-clean-starter/modules/role" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" @@ -131,6 +132,7 @@ func main() { maintenancegroup.RegisterRoutes(server, injector) client.RegisterRoutes(server, injector) permissions.RegisterRoutes(server, injector) + product.RegisterRoutes(server, injector) // register swagger route server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/database/entities/m_product_entity.go b/database/entities/m_product_entity.go new file mode 100644 index 0000000..25e5abc --- /dev/null +++ b/database/entities/m_product_entity.go @@ -0,0 +1,59 @@ +package entities + +import ( + "github.com/google/uuid" +) + +type MProductEntity struct { + ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"id"` + Name string `gorm:"type:varchar(255);not null" json:"name"` + RefNumber string `gorm:"type:varchar(100);not null;uniqueIndex:idx_product_refnumber_client" json:"ref_number"` + SKU string `gorm:"type:varchar(100);not null;uniqueIndex:idx_product_sku_client" json:"sku"` + Description string `gorm:"type:text" json:"description"` + Status string `gorm:"type:varchar(50);not null" json:"status"` + IsReturnable bool `gorm:"type:boolean;default:false" json:"is_returnable"` + DimLength float64 `gorm:"type:decimal(10,2);" json:"dim_length"` + DimWidth float64 `gorm:"type:decimal(10,2);" json:"dim_width"` + DimHeight float64 `gorm:"type:decimal(10,2);" json:"dim_height"` + Weight float64 `gorm:"type:decimal(10,2);" json:"weight"` + Volume float64 `gorm:"type:decimal(10,2);" json:"volume"` + MaxStackHeight int `gorm:"type:int;" json:"max_stack_height"` + Temperature string `gorm:"type:varchar(50)" json:"temperature"` + IsHazardous bool `gorm:"type:boolean;default:false" json:"is_hazardous"` + MinStock int `gorm:"type:int;default:0" json:"min_stock"` + MaxStock int `gorm:"type:int;default:0" json:"max_stock"` + ReplenishType string `gorm:"type:varchar(50)" json:"replenish_type"` + CycleCount string `gorm:"type:varchar(50);default:0" json:"cycle_count"` + LotRules string `gorm:"type:varchar(100)" json:"lot_rules"` + LeadTime int `gorm:"type:int;default:0" json:"lead_time"` + MultiplyRate string `gorm:"type:varchar(50)" json:"multiply_rate"` + DivideRate float64 `gorm:"type:decimal(10,2)" json:"divide_rate"` + + ClientID uuid.UUID `gorm:"type:uuid;index;uniqueIndex:idx_product_refnumber_client,uniqueIndex:idx_product_sku_client" json:"client_id"` + CategoryID uuid.UUID `gorm:"type:uuid;index" json:"category_id"` + UomID uuid.UUID `gorm:"type:uuid;index" json:"uom_id"` + DimUomID uuid.UUID `gorm:"type:uuid;index" json:"dim_uom_id"` + WeightUomID uuid.UUID `gorm:"type:uuid;index" json:"weight_uom_id"` + VolumeUomID uuid.UUID `gorm:"type:uuid;index" json:"volume_uom_id"` + MinStockUomID uuid.UUID `gorm:"type:uuid;index" json:"min_stock_uom_id"` + MaxStockUomID uuid.UUID `gorm:"type:uuid;index" json:"max_stock_uom_id"` + LeadTimeUomID uuid.UUID `gorm:"type:uuid;index" json:"lead_time_uom_id"` + UomToUomID uuid.UUID `gorm:"type:uuid;index" json:"uom_to_uom_id"` + + Client M_Client `gorm:"foreignKey:ClientID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` + // Category M_Category `gorm:"foreignKey:CategoryID;references:ID"` + // Uom M_Uom `gorm:"foreignKey:UomID;references:ID"` + // DimUom M_Uom `gorm:"foreignKey:DimUomID;references:ID"` + // WeightUom M_Uom `gorm:"foreignKey:WeightUomID;references:ID"` + // VolumeUom M_Uom `gorm:"foreignKey:VolumeUomID;references:ID"` + // MinStockUom M_Uom `gorm:"foreignKey:MinStockUomID;references:ID"` + // MaxStockUom M_Uom `gorm:"foreignKey:MaxStockUomID;references:ID"` + // LeadTimeUom M_Uom `gorm:"foreignKey:LeadTimeUomID;references:ID"` + // UomToUom M_Uom `gorm:"foreignKey:UomToUomID;references:ID"` + + FullAuditTrail +} + +func (MProductEntity) TableName() string { + return "m_products" +} diff --git a/database/migration.go b/database/migration.go index 08bee7b..1fe32ed 100644 --- a/database/migration.go +++ b/database/migration.go @@ -20,6 +20,7 @@ func Migrate(db *gorm.DB) error { &entities.M_MaintenanceGroup{}, &entities.M_MaintenanceGroupRole{}, &entities.M_MaintenanceGroupRoleUser{}, + &entities.MProductEntity{}, ); err != nil { return err } @@ -43,6 +44,7 @@ func MigrateFresh(db *gorm.DB) error { &entities.M_MaintenanceGroup{}, &entities.M_MaintenanceGroupRole{}, &entities.M_MaintenanceGroupRoleUser{}, + &entities.MProductEntity{}, ); err != nil { return err } diff --git a/modules/product/controller/product_controller.go b/modules/product/controller/product_controller.go new file mode 100644 index 0000000..0ef6d68 --- /dev/null +++ b/modules/product/controller/product_controller.go @@ -0,0 +1,117 @@ +package controller + +import ( + "net/http" + + "github.com/Caknoooo/go-gin-clean-starter/modules/product/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/gin-gonic/gin" + "github.com/samber/do" + "gorm.io/gorm" +) + +type ( + ProductController interface { + Create(ctx *gin.Context) + Update(ctx *gin.Context) + Delete(ctx *gin.Context) + GetById(ctx *gin.Context) + GetAll(ctx *gin.Context) + } + + productController struct { + productService service.ProductService + db *gorm.DB + } +) + +func NewProductController(i *do.Injector, productService service.ProductService) ProductController { + db := do.MustInvokeNamed[*gorm.DB](i, constants.DB) + return &productController{ + productService: productService, + db: db, + } +} + +func (c *productController) Create(ctx *gin.Context) { + var req dto.ProductCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + created, err := c.productService.Create(ctx, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_PRODUCT, created) + ctx.JSON(http.StatusOK, res) +} + +func (c *productController) Update(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.ProductUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + updated, err := c.productService.Update(ctx, req, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_PRODUCT, updated) + ctx.JSON(http.StatusOK, res) +} + +func (c *productController) Delete(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.productService.Delete(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_PRODUCT, nil) + ctx.JSON(http.StatusOK, res) +} + +func (c *productController) GetById(ctx *gin.Context) { + id := ctx.Param("id") + product, err := c.productService.GetById(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusNotFound, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_PRODUCT, product) + ctx.JSON(http.StatusOK, res) +} + +func (c *productController) GetAll(ctx *gin.Context) { + var filter query.ProductFilter + if err := ctx.ShouldBindQuery(&filter); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + perPage := utils.ParseInt(ctx.DefaultQuery("per_page", "10")) + page := utils.ParseInt(ctx.DefaultQuery("page", "1")) + filter.PerPage = perPage + filter.Page = (page - 1) * perPage + products, total, err := c.productService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_PRODUCT, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + paginationResponse := utils.BuildPaginationResponse(perPage, page, total) + response := utils.BuildResponseSuccessWithPagination(http.StatusOK, dto.MESSAGE_SUCCESS_GET_PRODUCT, products, paginationResponse) + ctx.JSON(http.StatusOK, response) +} diff --git a/modules/product/dto/product_dto.go b/modules/product/dto/product_dto.go new file mode 100644 index 0000000..e6c459e --- /dev/null +++ b/modules/product/dto/product_dto.go @@ -0,0 +1,132 @@ +package dto + +import ( + "errors" +) + +const ( + MESSAGE_FAILED_CREATE_PRODUCT = "failed create product" + MESSAGE_SUCCESS_CREATE_PRODUCT = "success create product" + MESSAGE_FAILED_GET_PRODUCT = "failed get product" + MESSAGE_SUCCESS_GET_PRODUCT = "success get product" + MESSAGE_FAILED_UPDATE_PRODUCT = "failed update product" + MESSAGE_SUCCESS_UPDATE_PRODUCT = "success update product" + MESSAGE_FAILED_DELETE_PRODUCT = "failed delete product" + MESSAGE_SUCCESS_DELETE_PRODUCT = "success delete product" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" +) + +var ( + ErrCreateProduct = errors.New("failed to create product") + ErrGetProductById = errors.New("failed to get product by id") + ErrUpdateProduct = errors.New("failed to update product") + ErrDeleteProduct = errors.New("failed to delete product") +) + +type ( + ProductCreateRequest struct { + Name string `json:"name" binding:"required"` + RefNumber string `json:"ref_number" binding:"required"` + SKU string `json:"sku" binding:"required"` + Description string `json:"description"` + Status string `json:"status" binding:"required"` + IsReturnable bool `json:"is_returnable"` + DimLength float64 `json:"dim_length"` + DimWidth float64 `json:"dim_width"` + DimHeight float64 `json:"dim_height"` + Weight float64 `json:"weight"` + Volume float64 `json:"volume"` + MaxStackHeight int `json:"max_stack_height"` + Temperature string `json:"temperature"` + IsHazardous bool `json:"is_hazardous"` + MinStock int `json:"min_stock"` + MaxStock int `json:"max_stock"` + ReplenishType string `json:"replenish_type"` + CycleCount string `json:"cycle_count"` + LotRules string `json:"lot_rules"` + LeadTime int `json:"lead_time"` + MultiplyRate string `json:"multiply_rate"` + DivideRate float64 `json:"divide_rate"` + ClientID string `json:"client_id" binding:"required"` + CategoryID string `json:"category_id"` + UomID string `json:"uom_id"` + DimUomID string `json:"dim_uom_id"` + WeightUomID string `json:"weight_uom_id"` + VolumeUomID string `json:"volume_uom_id"` + MinStockUomID string `json:"min_stock_uom_id"` + MaxStockUomID string `json:"max_stock_uom_id"` + LeadTimeUomID string `json:"lead_time_uom_id"` + UomToUomID string `json:"uom_to_uom_id"` + } + + ProductUpdateRequest struct { + Name *string `json:"name"` + RefNumber *string `json:"ref_number"` + SKU *string `json:"sku"` + Description *string `json:"description"` + Status *string `json:"status"` + IsReturnable *bool `json:"is_returnable"` + DimLength *float64 `json:"dim_length"` + DimWidth *float64 `json:"dim_width"` + DimHeight *float64 `json:"dim_height"` + Weight *float64 `json:"weight"` + Volume *float64 `json:"volume"` + MaxStackHeight *int `json:"max_stack_height"` + Temperature *string `json:"temperature"` + IsHazardous *bool `json:"is_hazardous"` + MinStock *int `json:"min_stock"` + MaxStock *int `json:"max_stock"` + ReplenishType *string `json:"replenish_type"` + CycleCount *string `json:"cycle_count"` + LotRules *string `json:"lot_rules"` + LeadTime *int `json:"lead_time"` + MultiplyRate *string `json:"multiply_rate"` + DivideRate *float64 `json:"divide_rate"` + ClientID *string `json:"client_id"` + CategoryID *string `json:"category_id"` + UomID *string `json:"uom_id"` + DimUomID *string `json:"dim_uom_id"` + WeightUomID *string `json:"weight_uom_id"` + VolumeUomID *string `json:"volume_uom_id"` + MinStockUomID *string `json:"min_stock_uom_id"` + MaxStockUomID *string `json:"max_stock_uom_id"` + LeadTimeUomID *string `json:"lead_time_uom_id"` + UomToUomID *string `json:"uom_to_uom_id"` + } + + ProductResponse struct { + ID string `json:"id"` + Name string `json:"name"` + RefNumber string `json:"ref_number"` + SKU string `json:"sku"` + Description string `json:"description"` + Status string `json:"status"` + IsReturnable bool `json:"is_returnable"` + DimLength float64 `json:"dim_length"` + DimWidth float64 `json:"dim_width"` + DimHeight float64 `json:"dim_height"` + Weight float64 `json:"weight"` + Volume float64 `json:"volume"` + MaxStackHeight int `json:"max_stack_height"` + Temperature string `json:"temperature"` + IsHazardous bool `json:"is_hazardous"` + MinStock int `json:"min_stock"` + MaxStock int `json:"max_stock"` + ReplenishType string `json:"replenish_type"` + CycleCount string `json:"cycle_count"` + LotRules string `json:"lot_rules"` + LeadTime int `json:"lead_time"` + MultiplyRate string `json:"multiply_rate"` + DivideRate float64 `json:"divide_rate"` + ClientID string `json:"client_id"` + CategoryID string `json:"category_id"` + UomID string `json:"uom_id"` + DimUomID string `json:"dim_uom_id"` + WeightUomID string `json:"weight_uom_id"` + VolumeUomID string `json:"volume_uom_id"` + MinStockUomID string `json:"min_stock_uom_id"` + MaxStockUomID string `json:"max_stock_uom_id"` + LeadTimeUomID string `json:"lead_time_uom_id"` + UomToUomID string `json:"uom_to_uom_id"` + } +) diff --git a/modules/product/query/product_query.go b/modules/product/query/product_query.go new file mode 100644 index 0000000..c12b1fb --- /dev/null +++ b/modules/product/query/product_query.go @@ -0,0 +1,38 @@ +package query + +import ( + "gorm.io/gorm" +) + +type ProductFilter struct { + Name string `form:"name"` + RefNumber string `form:"ref_number"` + SKU string `form:"sku"` + Status string `form:"status"` + ClientID string `form:"client_id"` + CategoryID string `form:"category_id"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyProductFilters(db *gorm.DB, filter ProductFilter) *gorm.DB { + if filter.Name != "" { + db = db.Where("name ILIKE ?", "%"+filter.Name+"%") + } + if filter.RefNumber != "" { + db = db.Where("ref_number ILIKE ?", "%"+filter.RefNumber+"%") + } + if filter.SKU != "" { + db = db.Where("sku ILIKE ?", "%"+filter.SKU+"%") + } + if filter.Status != "" { + db = db.Where("status = ?", filter.Status) + } + if filter.ClientID != "" { + db = db.Where("client_id = ?", filter.ClientID) + } + if filter.CategoryID != "" { + db = db.Where("category_id = ?", filter.CategoryID) + } + return db +} diff --git a/modules/product/repository/product_repository.go b/modules/product/repository/product_repository.go new file mode 100644 index 0000000..c9c9ba4 --- /dev/null +++ b/modules/product/repository/product_repository.go @@ -0,0 +1,78 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/query" + "gorm.io/gorm" +) + +type ProductRepository interface { + Create(ctx context.Context, tx *gorm.DB, product entities.MProductEntity) (entities.MProductEntity, error) + GetById(ctx context.Context, tx *gorm.DB, productId string) (entities.MProductEntity, error) + GetAll(ctx context.Context, filter query.ProductFilter) ([]entities.MProductEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, product entities.MProductEntity) (entities.MProductEntity, error) + Delete(ctx context.Context, tx *gorm.DB, productId string) error +} + +type productRepository struct { + db *gorm.DB +} + +func NewProductRepository(db *gorm.DB) ProductRepository { + return &productRepository{db: db} +} + +func (r *productRepository) Create(ctx context.Context, tx *gorm.DB, product entities.MProductEntity) (entities.MProductEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&product).Error; err != nil { + return product, err + } + return product, nil +} + +func (r *productRepository) GetById(ctx context.Context, tx *gorm.DB, productId string) (entities.MProductEntity, error) { + if tx == nil { + tx = r.db + } + var product entities.MProductEntity + if err := tx.WithContext(ctx).First(&product, "id = ?", productId).Error; err != nil { + return product, err + } + return product, nil +} + +func (r *productRepository) GetAll(ctx context.Context, filter query.ProductFilter) ([]entities.MProductEntity, int64, error) { + var products []entities.MProductEntity + var total int64 + db := r.db.Model(&entities.MProductEntity{}) + db = query.ApplyProductFilters(db, filter) + if err := db.Count(&total).Error; err != nil { + return nil, 0, err + } + err := db.Limit(filter.PerPage).Offset(filter.Page).Find(&products).Error + return products, total, err +} + +func (r *productRepository) Update(ctx context.Context, tx *gorm.DB, product entities.MProductEntity) (entities.MProductEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Save(&product).Error; err != nil { + return product, err + } + return product, nil +} + +func (r *productRepository) Delete(ctx context.Context, tx *gorm.DB, productId string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.MProductEntity{}, "id = ?", productId).Error; err != nil { + return err + } + return nil +} diff --git a/modules/product/routes.go b/modules/product/routes.go new file mode 100644 index 0000000..32b93df --- /dev/null +++ b/modules/product/routes.go @@ -0,0 +1,24 @@ +package product + +import ( + "github.com/Caknoooo/go-gin-clean-starter/middlewares" + "github.com/Caknoooo/go-gin-clean-starter/modules/auth/service" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/controller" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/gin-gonic/gin" + "github.com/samber/do" +) + +func RegisterRoutes(server *gin.Engine, injector *do.Injector) { + productController := do.MustInvoke[controller.ProductController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + productRoutes := server.Group("/api/v1/products") + { + productRoutes.POST("", middlewares.Authenticate(jwtService), productController.Create) + productRoutes.GET("/:id", middlewares.Authenticate(jwtService), productController.GetById) + productRoutes.PUT("/:id", middlewares.Authenticate(jwtService), productController.Update) + productRoutes.DELETE("/:id", middlewares.Authenticate(jwtService), productController.Delete) + productRoutes.GET("", middlewares.Authenticate(jwtService), productController.GetAll) + } +} diff --git a/modules/product/service/product_service.go b/modules/product/service/product_service.go new file mode 100644 index 0000000..04a1226 --- /dev/null +++ b/modules/product/service/product_service.go @@ -0,0 +1,300 @@ +package service + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type ProductService interface { + Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error) + GetById(ctx context.Context, productId string) (dto.ProductResponse, error) + GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error) + Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error) + Delete(ctx context.Context, productId string) error +} + +type productService struct { + db *gorm.DB + productRepo repository.ProductRepository +} + +func NewProductService(productRepo repository.ProductRepository, db *gorm.DB) ProductService { + return &productService{ + productRepo: productRepo, + db: db, + } +} + +func (s *productService) Create(ctx context.Context, req dto.ProductCreateRequest) (dto.ProductResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + product := entities.MProductEntity{ + Name: req.Name, + RefNumber: req.RefNumber, + SKU: req.SKU, + Description: req.Description, + Status: req.Status, + IsReturnable: req.IsReturnable, + DimLength: req.DimLength, + DimWidth: req.DimWidth, + DimHeight: req.DimHeight, + Weight: req.Weight, + Volume: req.Volume, + MaxStackHeight: req.MaxStackHeight, + Temperature: req.Temperature, + IsHazardous: req.IsHazardous, + MinStock: req.MinStock, + MaxStock: req.MaxStock, + ReplenishType: req.ReplenishType, + CycleCount: req.CycleCount, + LotRules: req.LotRules, + LeadTime: req.LeadTime, + MultiplyRate: req.MultiplyRate, + DivideRate: req.DivideRate, + } + // UUID fields + product.ClientID = parseUUID(req.ClientID) + product.CategoryID = parseUUID(req.CategoryID) + product.UomID = parseUUID(req.UomID) + product.DimUomID = parseUUID(req.DimUomID) + product.WeightUomID = parseUUID(req.WeightUomID) + product.VolumeUomID = parseUUID(req.VolumeUomID) + product.MinStockUomID = parseUUID(req.MinStockUomID) + product.MaxStockUomID = parseUUID(req.MaxStockUomID) + product.LeadTimeUomID = parseUUID(req.LeadTimeUomID) + product.UomToUomID = parseUUID(req.UomToUomID) + + created, err := s.productRepo.Create(ctx, tx, product) + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } + err = tx.Commit().Error + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } + return s.GetById(ctx, created.ID.String()) +} + +func (s *productService) GetById(ctx context.Context, productId string) (dto.ProductResponse, error) { + product, err := s.productRepo.GetById(ctx, nil, productId) + if err != nil { + return dto.ProductResponse{}, err + } + return mapProductToResponse(product), nil +} + +func (s *productService) GetAll(ctx context.Context, filter query.ProductFilter) ([]dto.ProductResponse, int64, error) { + products, total, err := s.productRepo.GetAll(ctx, filter) + if err != nil { + return nil, 0, err + } + var responses []dto.ProductResponse + for _, p := range products { + responses = append(responses, mapProductToResponse(p)) + } + if len(responses) == 0 { + responses = []dto.ProductResponse{} // <-- pastikan slice kosong, bukan nil + } + + return responses, total, nil +} + +func (s *productService) Update(ctx context.Context, req dto.ProductUpdateRequest, productId string) (dto.ProductResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + product, err := s.productRepo.GetById(ctx, tx, productId) + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } + if req.Name != nil { + product.Name = *req.Name + } + if req.RefNumber != nil { + product.RefNumber = *req.RefNumber + } + if req.SKU != nil { + product.SKU = *req.SKU + } + if req.Description != nil { + product.Description = *req.Description + } + if req.Status != nil { + product.Status = *req.Status + } + if req.IsReturnable != nil { + product.IsReturnable = *req.IsReturnable + } + if req.DimLength != nil { + product.DimLength = *req.DimLength + } + if req.DimWidth != nil { + product.DimWidth = *req.DimWidth + } + if req.DimHeight != nil { + product.DimHeight = *req.DimHeight + } + if req.Weight != nil { + product.Weight = *req.Weight + } + if req.Volume != nil { + product.Volume = *req.Volume + } + if req.MaxStackHeight != nil { + product.MaxStackHeight = *req.MaxStackHeight + } + if req.Temperature != nil { + product.Temperature = *req.Temperature + } + if req.IsHazardous != nil { + product.IsHazardous = *req.IsHazardous + } + if req.MinStock != nil { + product.MinStock = *req.MinStock + } + if req.MaxStock != nil { + product.MaxStock = *req.MaxStock + } + if req.ReplenishType != nil { + product.ReplenishType = *req.ReplenishType + } + if req.CycleCount != nil { + product.CycleCount = *req.CycleCount + } + if req.LotRules != nil { + product.LotRules = *req.LotRules + } + if req.LeadTime != nil { + product.LeadTime = *req.LeadTime + } + if req.MultiplyRate != nil { + product.MultiplyRate = *req.MultiplyRate + } + if req.DivideRate != nil { + product.DivideRate = *req.DivideRate + } + if req.ClientID != nil { + product.ClientID = parseUUID(*req.ClientID) + } + if req.CategoryID != nil { + product.CategoryID = parseUUID(*req.CategoryID) + } + if req.UomID != nil { + product.UomID = parseUUID(*req.UomID) + } + if req.DimUomID != nil { + product.DimUomID = parseUUID(*req.DimUomID) + } + if req.WeightUomID != nil { + product.WeightUomID = parseUUID(*req.WeightUomID) + } + if req.VolumeUomID != nil { + product.VolumeUomID = parseUUID(*req.VolumeUomID) + } + if req.MinStockUomID != nil { + product.MinStockUomID = parseUUID(*req.MinStockUomID) + } + if req.MaxStockUomID != nil { + product.MaxStockUomID = parseUUID(*req.MaxStockUomID) + } + if req.LeadTimeUomID != nil { + product.LeadTimeUomID = parseUUID(*req.LeadTimeUomID) + } + if req.UomToUomID != nil { + product.UomToUomID = parseUUID(*req.UomToUomID) + } + updated, err := s.productRepo.Update(ctx, tx, product) + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } + err = tx.Commit().Error + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } + return s.GetById(ctx, updated.ID.String()) +} + +func (s *productService) Delete(ctx context.Context, productId string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + err := s.productRepo.Delete(ctx, tx, productId) + if err != nil { + tx.Rollback() + return err + } + err = tx.Commit().Error + if err != nil { + tx.Rollback() + return err + } + return nil +} + +// Helper +func parseUUID(id string) uuid.UUID { + u, err := uuid.Parse(id) + if err != nil { + return uuid.Nil + } + return u +} + +func mapProductToResponse(product entities.MProductEntity) dto.ProductResponse { + return dto.ProductResponse{ + ID: product.ID.String(), + Name: product.Name, + RefNumber: product.RefNumber, + SKU: product.SKU, + Description: product.Description, + Status: product.Status, + IsReturnable: product.IsReturnable, + DimLength: product.DimLength, + DimWidth: product.DimWidth, + DimHeight: product.DimHeight, + Weight: product.Weight, + Volume: product.Volume, + MaxStackHeight: product.MaxStackHeight, + Temperature: product.Temperature, + IsHazardous: product.IsHazardous, + MinStock: product.MinStock, + MaxStock: product.MaxStock, + ReplenishType: product.ReplenishType, + CycleCount: product.CycleCount, + LotRules: product.LotRules, + LeadTime: product.LeadTime, + MultiplyRate: product.MultiplyRate, + DivideRate: product.DivideRate, + ClientID: product.ClientID.String(), + CategoryID: product.CategoryID.String(), + UomID: product.UomID.String(), + DimUomID: product.DimUomID.String(), + WeightUomID: product.WeightUomID.String(), + VolumeUomID: product.VolumeUomID.String(), + MinStockUomID: product.MinStockUomID.String(), + MaxStockUomID: product.MaxStockUomID.String(), + LeadTimeUomID: product.LeadTimeUomID.String(), + UomToUomID: product.UomToUomID.String(), + } +} diff --git a/providers/core.go b/providers/core.go index 42518a4..f593f5f 100644 --- a/providers/core.go +++ b/providers/core.go @@ -9,13 +9,13 @@ import ( clientService "github.com/Caknoooo/go-gin-clean-starter/modules/client/service" "github.com/sirupsen/logrus" - // productController "github.com/Caknoooo/go-gin-clean-starter/modules/product/controller" - // productRepo "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" - // productService "github.com/Caknoooo/go-gin-clean-starter/modules/product/service" logsController "github.com/Caknoooo/go-gin-clean-starter/modules/logs/controller" menuController "github.com/Caknoooo/go-gin-clean-starter/modules/menu/controller" menuRepo "github.com/Caknoooo/go-gin-clean-starter/modules/menu/repository" menuService "github.com/Caknoooo/go-gin-clean-starter/modules/menu/service" + productController "github.com/Caknoooo/go-gin-clean-starter/modules/product/controller" + productRepo "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" + productService "github.com/Caknoooo/go-gin-clean-starter/modules/product/service" roleController "github.com/Caknoooo/go-gin-clean-starter/modules/role/controller" roleRepo "github.com/Caknoooo/go-gin-clean-starter/modules/role/repository" roleService "github.com/Caknoooo/go-gin-clean-starter/modules/role/service" @@ -72,10 +72,11 @@ func RegisterDependencies(injector *do.Injector) { maintenanceGroupRoleRepository := maintGroupRepoRole.NewMaintGroupRoleRepository(db) maintenanceGroupRoleUserRepository := maintGroupRepoRoleUser.NewMaintGroupRoleUserRepository(db) permissionsRepository := permissionsRepo.NewPermissionsRepository(db) + productRepository := productRepo.NewProductRepository(db) // Service userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db) - // productService := productService.NewProductService(productRepository, db) + productService := productService.NewProductService(productRepository, db) roleService := roleService.NewRoleService(roleRepository, refreshTokenRepository, jwtService, userServ, db) menuSvc := menuService.NewMenuService(menuRepository, jwtService, db) maintenanceGroupServ := maintGroupService.NewMaintenanceGroupService(maintenanceGroupRepository, maintenanceGroupRoleRepository, maintenanceGroupRoleUserRepository, db) @@ -128,4 +129,9 @@ func RegisterDependencies(injector *do.Injector) { return permissionsController.NewPermissionsController(permissionsServ), nil }, ) + do.Provide( + injector, func(i *do.Injector) (productController.ProductController, error) { + return productController.NewProductController(i, productService), nil + }, + ) }