diff --git a/buildtime/client/material-editor/src/data/product.ts b/buildtime/client/material-editor/src/data/product.ts new file mode 100644 index 000000000..e69de29bb diff --git a/buildtime/client/material-editor/src/data/productGroup.ts b/buildtime/client/material-editor/src/data/productGroup.ts new file mode 100644 index 000000000..326093d27 --- /dev/null +++ b/buildtime/client/material-editor/src/data/productGroup.ts @@ -0,0 +1,13 @@ +import type { PropertyType } from "./propertyType"; +import type { Product } from "./product"; + +export type ProductGroup = { + id: number; + label: string; + parentGroupId: number | null; + isVisible: boolean; + path: string; + SubGroups: ProductGroup[]; + PropertyTypes: PropertyType[]; + Products: Product[]; +}; \ No newline at end of file diff --git a/buildtime/server/src/scpq-editor-server/controller/productgroupsController.go b/buildtime/server/src/scpq-editor-server/controller/productgroupsController.go new file mode 100644 index 000000000..189884de8 --- /dev/null +++ b/buildtime/server/src/scpq-editor-server/controller/productgroupsController.go @@ -0,0 +1,112 @@ +package controller + +import ( + "encoding/json" + "io" + "log" + "net/http" + "scpq-editor-server/models" + "scpq-editor-server/services" + "strconv" + + "github.com/gin-gonic/gin" +) + +type ProductgroupsController struct { + ProductgroupService *services.ProductgroupService +} + +func NewProductgroupsController(ProductgroupService *services.ProductgroupService) *ProductgroupsController { + return &ProductgroupsController{ + ProductgroupService: ProductgroupService, + } +} + +func (lc ProductgroupsController) CreateProductgroup(ctx *gin.Context) { + + body, err := io.ReadAll(ctx.Request.Body) + if err != nil { + log.Println("Error while reading create Productgroup request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + + var Productgroup models.Productgroup + err = json.Unmarshal(body, &Productgroup) + if err != nil { + log.Println("Error while unmarshaling create Productgroup request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + response, responseErr := lc.ProductgroupService.CreateProductgroup(&Productgroup) + if responseErr != nil { + ctx.AbortWithStatusJSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} + +func (lc ProductgroupsController) UpdateProductgroup(ctx *gin.Context) { + body, err := io.ReadAll(ctx.Request.Body) + if err != nil { + log.Println("Error while reading update Productgroup request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + var Productgroup models.Productgroup + err = json.Unmarshal(body, &Productgroup) + if err != nil { + log.Println("Error while unmarshaling update Productgroup request body", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + responseErr := lc.ProductgroupService.UpdateProductgroup(&Productgroup) + if responseErr != nil { + ctx.AbortWithStatusJSON(responseErr.Status, responseErr) + return + } + ctx.Status(http.StatusNoContent) +} + +func (lc ProductgroupsController) DeleteProductgroup(ctx *gin.Context) { + ProductgroupId := ctx.Param("id") + lId, err := strconv.Atoi(ProductgroupId) + if err != nil { + log.Println("Error ProductgroupId not int", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + + responseErr := lc.ProductgroupService.DeleteProductgroup(lId) + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.Status(http.StatusNoContent) +} + +func (lc ProductgroupsController) GetProductgroup(ctx *gin.Context) { + ProductgroupId := ctx.Param("id") + lId, err := strconv.Atoi(ProductgroupId) + if err != nil { + log.Println("Error ProductgroupId not int", err) + ctx.AbortWithError(http.StatusInternalServerError, err) + return + } + + response, responseErr := lc.ProductgroupService.GetProductgroup(lId) + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} + +func (lc ProductgroupsController) GetAllProductgroups(ctx *gin.Context) { + response, responseErr := lc.ProductgroupService.GetAllProductgroups() + if responseErr != nil { + ctx.JSON(responseErr.Status, responseErr) + return + } + ctx.JSON(http.StatusOK, response) +} diff --git a/buildtime/server/src/scpq-editor-server/repositories/productgroupRepository.go b/buildtime/server/src/scpq-editor-server/repositories/productgroupRepository.go index 3594af70d..ab0490ab6 100644 --- a/buildtime/server/src/scpq-editor-server/repositories/productgroupRepository.go +++ b/buildtime/server/src/scpq-editor-server/repositories/productgroupRepository.go @@ -23,7 +23,7 @@ func NewProductgroupRepository(dbHandler *sql.DB, propertyTypesRepository *Prope } func (rr ProductgroupRepository) CreateProductgroup(Productgroup *models.Productgroup) (*models.Productgroup, *models.ResponseError) { - query := `INSERT INTO product_groups(label, parent_product_group_id, is_visible, path) VALUES($1,$2, $3, $4) RETURNING id` + query := `INSERT INTO product_groups(label, parent_group_id, is_visible, path) VALUES($1,$2, $3, $4) RETURNING id` rows, err := rr.dbHandler.Query(query, Productgroup.Label, Productgroup.ParentProductGroupID, Productgroup.IsVisible, Productgroup.Path) if err != nil { return nil, &models.ResponseError{ @@ -65,12 +65,17 @@ func (rr ProductgroupRepository) CreateProductgroup(Productgroup *models.Product Path: Productgroup.Path, } + addToParentErr := rr.addToParentProductGroup(pg) + if addToParentErr != nil { + return nil, addToParentErr + } + rr.m[pg.ID] = pg return pg, nil } func (rr ProductgroupRepository) UpdateProductgroup(Productgroup *models.Productgroup) *models.ResponseError { - query := `UPDATE product_groups SET label=$1, parent_product_group_id=$2, is_visible=$3, path=$4 WHERE id=$5` + query := `UPDATE product_groups SET label=$1, parent_group_id=$2, is_visible=$3, path=$4 WHERE id=$5` res, err := rr.dbHandler.Exec(query, Productgroup.Label, Productgroup.ParentProductGroupID, Productgroup.IsVisible, Productgroup.Path, Productgroup.ID) if err != nil { return &models.ResponseError{ @@ -96,20 +101,26 @@ func (rr ProductgroupRepository) UpdateProductgroup(Productgroup *models.Product return nil } -func findIndex(slice []*models.Productgroup, target models.Productgroup) int { - for i, v := range slice { - if v.ID == target.ID { - return i +func (rr ProductgroupRepository) DeleteProductgroup(ProductgroupId int) *models.ResponseError { + + // get sub product groups + subProductGroups, err2 := rr.GetSubProductgroups(ProductgroupId) + if err2 != nil { + return &models.ResponseError{ + Message: err2.Message, + Status: http.StatusInternalServerError, } } - return -1 -} + for _, spg := range subProductGroups { + rr.DeleteProductgroup(spg.ID) + } -func RemoveAt[T any](slice []T, index int) []T { - return append(slice[:index], slice[index+1:]...) -} + pg1, err2 := rr.GetProductgroup(ProductgroupId) + if pg1 != nil { + parentPG, _ := rr.GetProductgroup(pg1.ParentProductGroupID) + rr.RemoveProductGroupFromParentGroup(parentPG, pg1) + } -func (rr ProductgroupRepository) DeleteProductgroup(ProductgroupId int) *models.ResponseError { query := `DELETE FROM product_groups WHERE id=$1` res, err := rr.dbHandler.Exec(query, ProductgroupId) if err != nil { @@ -132,7 +143,7 @@ func (rr ProductgroupRepository) DeleteProductgroup(ProductgroupId int) *models. Status: http.StatusNotFound, } } - rr.m[ProductgroupId] = nil + delete(rr.m, ProductgroupId) return nil } @@ -142,7 +153,7 @@ func (rr ProductgroupRepository) GetProductgroup(ProductgroupId int) (*models.Pr return pg, nil } - query := `SELECT id, label, parent_product_group_id, is_visible, path FROM product_groups WHERE id=$1` + query := `SELECT id, label, parent_group_id, is_visible, path FROM product_groups WHERE id=$1` rows, err := rr.dbHandler.Query(query, ProductgroupId) if err != nil { return nil, &models.ResponseError{ @@ -196,7 +207,7 @@ func (rr ProductgroupRepository) GetProductgroup(ProductgroupId int) (*models.Pr subProductGroups, err2 := rr.GetSubProductgroups(pg.ID) if err2 != nil { return nil, &models.ResponseError{ - Message: err2.Error(), + Message: err2.Message, Status: http.StatusInternalServerError, } } @@ -208,7 +219,7 @@ func (rr ProductgroupRepository) GetProductgroup(ProductgroupId int) (*models.Pr propertyTypes, err3 := rr.GetPropertyTypesByProductGroupID(pg.ID) if err3 != nil { return nil, &models.ResponseError{ - Message: err3.Error(), + Message: err3.Message, Status: http.StatusInternalServerError, } } @@ -273,7 +284,7 @@ func (rr ProductgroupRepository) GetPropertyTypesByProductGroupID(productGroupID func (rr ProductgroupRepository) GetSubProductgroups(ProductgroupId int) ([]*models.Productgroup, *models.ResponseError) { var Productgroups []*models.Productgroup - query := `SELECT id FROM product_groups WHERE parent_product_group_id=$1` + query := `SELECT id FROM product_groups WHERE parent_group_id=$1` rows, err := rr.dbHandler.Query(query, ProductgroupId) if err != nil { @@ -372,6 +383,26 @@ func (rr ProductgroupRepository) GetAllProductgroups() ([]*models.Productgroup, return Productgroups, nil } +func (rr ProductgroupRepository) RemoveProductGroupFromParentGroup(parentProductGroup *models.Productgroup, childProductGroup *models.Productgroup) *models.ResponseError { + query := `UPDATE product_groups SET parent_group_id=NULL WHERE id=$1` + _, err := rr.dbHandler.Exec(query, childProductGroup.ID) + if err != nil { + return &models.ResponseError{ + Message: err.Error(), + Status: http.StatusInternalServerError, + } + } + // Remove child from parent's ChildProductGroups slice + for i, cpg := range parentProductGroup.ChildProductGroups { + if cpg.ID == childProductGroup.ID { + parentProductGroup.ChildProductGroups = append(parentProductGroup.ChildProductGroups[:i], parentProductGroup.ChildProductGroups[i+1:]...) + break + } + } + rr.m[parentProductGroup.ID] = parentProductGroup + return nil +} + func (rr ProductgroupRepository) addToParentProductGroup(ProductGroup *models.Productgroup) *models.ResponseError { Query := `UPDATE product_groups SET parent_group_id=$1 WHERE id=$2` _, err := rr.dbHandler.Exec(Query, ProductGroup.ParentProductGroupID, ProductGroup.ID) @@ -384,11 +415,22 @@ func (rr ProductgroupRepository) addToParentProductGroup(ProductGroup *models.Pr ParentProductGroup, err2 := rr.GetProductgroup(ProductGroup.ParentProductGroupID) if err2 != nil { return &models.ResponseError{ - Message: "Parent Productgroup not found with the given id " + strconv.Itoa(ProductGroup.ParentGroupID), + Message: "Parent Productgroup not found with the given id " + strconv.Itoa(ProductGroup.ParentProductGroupID), Status: http.StatusInternalServerError, } } ParentProductGroup.ChildProductGroups = append(ParentProductGroup.ChildProductGroups, *ProductGroup) + propertyTypes, err3 := rr.GetPropertyTypesByProductGroupID(ParentProductGroup.ID) + if err3 != nil { + return &models.ResponseError{ + Message: err3.Message, + Status: http.StatusInternalServerError, + } + } + for _, pt := range propertyTypes { + ProductGroup.PropertyTypes = append(ProductGroup.PropertyTypes, *pt) + } + rr.m[ParentProductGroup.ID] = ParentProductGroup return nil } diff --git a/buildtime/server/src/scpq-editor-server/server/httpServer.go b/buildtime/server/src/scpq-editor-server/server/httpServer.go index 540b217bb..c49941662 100644 --- a/buildtime/server/src/scpq-editor-server/server/httpServer.go +++ b/buildtime/server/src/scpq-editor-server/server/httpServer.go @@ -31,6 +31,7 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { valuecomplexitiesRepository := repositories.NewValuecomplexitiesRepository(dbHandler) translationsRepository := repositories.NewTranslationsRepository(dbHandler) propertytypesRepository := repositories.NewPropertyTypesRepository(dbHandler) + productgroupRepository := repositories.NewProductgroupRepository(dbHandler, propertytypesRepository) // service initialisation: languagesService := services.NewLanguagesService(languagesRepository) @@ -39,6 +40,7 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { valuetypesService := services.NewValuetypesService(valuetypesRepository) valuecomplexitiesService := services.NewValuecomplexitiesService(valuecomplexitiesRepository) translationsService := services.NewTranslationsService(translationsRepository) + productgroupService := services.NewProductgroupService(productgroupRepository) // controller initialisation: languageController := controller.NewLanguagesController(languagesService) @@ -47,6 +49,7 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { valuetypesController := controller.NewValuetypesController(valuetypesService) valuecomplexitiesController := controller.NewValuecomplexitiesController(valuecomplexitiesService) translationsController := controller.NewTranslationsController(translationsService) + productgroupsController := controller.NewProductgroupsController(productgroupService) // Service API router := gin.Default() @@ -67,6 +70,13 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { router.GET("/propertygroup/:id", propertygroupsController.GetPropertygroup) router.GET("/propertygroups", propertygroupsController.GetAllPropertygroups) + // productgroup centric routing + router.POST("/productgroup", productgroupsController.CreateProductgroup) + router.PUT("/productgroup", productgroupsController.UpdateProductgroup) + router.DELETE("/productgroup/:id", productgroupsController.DeleteProductgroup) + router.GET("/productgroup/:id", productgroupsController.GetProductgroup) + router.GET("/productgroups", productgroupsController.GetAllProductgroups) + // translation centric routing router.POST("/translation", translationsController.CreateTranslation) router.PUT("/translation", translationsController.UpdateTranslation) diff --git a/buildtime/server/src/scpq-editor-server/services/productgroupService.go b/buildtime/server/src/scpq-editor-server/services/productgroupService.go index 63afbae10..a76227103 100644 --- a/buildtime/server/src/scpq-editor-server/services/productgroupService.go +++ b/buildtime/server/src/scpq-editor-server/services/productgroupService.go @@ -1,6 +1,8 @@ package services import ( + "net/http" + "scpq-editor-server/models" "scpq-editor-server/repositories" ) @@ -13,3 +15,41 @@ func NewProductgroupService(ProductgroupRepository *repositories.ProductgroupRep ProductgroupRepository: ProductgroupRepository, } } + +func (ls ProductgroupService) CreateProductgroup(Productgroup *models.Productgroup) (*models.Productgroup, *models.ResponseError) { + responseErr := validateProductgroup(Productgroup) + if responseErr != nil { + return nil, responseErr + } + return ls.ProductgroupRepository.CreateProductgroup(Productgroup) +} + +func (ls ProductgroupService) UpdateProductgroup(Productgroup *models.Productgroup) *models.ResponseError { + responseErr := validateProductgroup(Productgroup) + if responseErr != nil { + return responseErr + } + return ls.ProductgroupRepository.UpdateProductgroup(Productgroup) +} + +func (ls ProductgroupService) DeleteProductgroup(ProductgroupId int) *models.ResponseError { + return ls.ProductgroupRepository.DeleteProductgroup(ProductgroupId) +} + +func (ls ProductgroupService) GetProductgroup(ProductgroupId int) (*models.Productgroup, *models.ResponseError) { + return ls.ProductgroupRepository.GetProductgroup(ProductgroupId) +} + +func (ls ProductgroupService) GetAllProductgroups() ([]*models.Productgroup, *models.ResponseError) { + return ls.ProductgroupRepository.GetAllProductgroups() +} + +func validateProductgroup(Productgroup *models.Productgroup) *models.ResponseError { + if Productgroup.Label == "" { + return &models.ResponseError{ + Message: "Invalid label", + Status: http.StatusBadRequest, + } + } + return nil +} diff --git a/database/20260811_backup1.backup b/database/20260811_backup1.backup new file mode 100644 index 000000000..af43a6644 Binary files /dev/null and b/database/20260811_backup1.backup differ diff --git a/database/dump-simply_cpq_master_data-202608110844.sql b/database/dump-simply_cpq_master_data-202608110844.sql new file mode 100644 index 000000000..e69de29bb diff --git a/database/dump-simply_cpq_master_data-202608110845.sql b/database/dump-simply_cpq_master_data-202608110845.sql new file mode 100644 index 000000000..e69de29bb diff --git a/database/dump-simply_cpq_master_data-202608110848.sql b/database/dump-simply_cpq_master_data-202608110848.sql new file mode 100644 index 000000000..e69de29bb diff --git a/database/product_groups.csv b/database/product_groups.csv new file mode 100644 index 000000000..ec03e2188 --- /dev/null +++ b/database/product_groups.csv @@ -0,0 +1,7 @@ +"id","label","parent_group_id","is_visible","path" +1,"Catalog",3,True,"Simply CPQ / Catalog" +2,"Components",3,False,"Simply CPQ / Components" +3,"Simply CPQ",NULL,True,NULL +4,"Vehicle",3,True,"Simply CPQ / Catalog / Vehicle" +5,"Car",4,True,"Simply CPQ / Catalog / Vehicle / Car" +6,"Bike",4,True,"Simply CPQ / Catalog / Vehicle / Car" diff --git a/database/property_types.csv b/database/property_types.csv new file mode 100644 index 000000000..bbb927d2a --- /dev/null +++ b/database/property_types.csv @@ -0,0 +1,8 @@ +"id","label","value_type_id","property_group_id","value_complexity_id","value" +3,"Color",4,2,2,"[""red"",""green"",""yellow"",""blue"",""white"",""black""]" +4,"Voltage in V",1,3,2,"[24,110,220,240]" +5,"Test",4,2,1,"Hallo" +6,"Todo",1,2,1,"12" +7,"PS",1,2,3,"[90;200]" +8,"Motor",4,2,2,"[""Einfach"",""Turbo""]" +9,"Radtyp",4,2,2,"[""Rennrad"", ""Hollandrad"", ""Mountainbike""]"