From adc4179747031d6e3c7ba76ab9cee7e5d5d8edf8 Mon Sep 17 00:00:00 2001 From: tsindt Date: Wed, 26 Aug 2026 19:27:07 +0200 Subject: [PATCH] BOM start implementing basic CRUD operations for Bill of Materials and Variant Nodes --- buildtime/database/init.sql | 39 ++++ buildtime/server/controller/bomController.go | 186 +++++++++++++++++++ buildtime/server/models/bom.go | 30 +++ buildtime/server/server/httpServer.go | 17 ++ 4 files changed, 272 insertions(+) create mode 100644 buildtime/server/controller/bomController.go create mode 100644 buildtime/server/models/bom.go diff --git a/buildtime/database/init.sql b/buildtime/database/init.sql index 17c1d0eff..0fa9f5794 100644 --- a/buildtime/database/init.sql +++ b/buildtime/database/init.sql @@ -69,3 +69,42 @@ CREATE TABLE IF NOT EXISTS media ( externalId BIGINT ); +CREATE TABLE IF NOT EXISTS variantNodeContent ( + id bigint NOT NULL DEFAULT nextval('global_id_seq'::regclass), + "valueNodeId" bigint, + "productId" bigint, + CONSTRAINT "valueNodeContent_pkey" PRIMARY KEY (id) +) + +CREATE TABLE IF NOT EXISTS variantNode +( + id bigint NOT NULL DEFAULT nextval('global_id_seq'::regclass), + label text COLLATE pg_catalog."default", + description text COLLATE pg_catalog."default", + "bomId" bigint, + min integer DEFAULT 1, + max integer DEFAULT 1, + CONSTRAINT "variantNode_pkey" PRIMARY KEY (id) +) + + +CREATE TABLE IF NOT EXISTS billOfMaterial +( + id bigint NOT NULL DEFAULT nextval('global_id_seq'::regclass), + "productId" bigint, + label text COLLATE pg_catalog."default", + description text COLLATE pg_catalog."default", + "parentBOMId" bigint, + CONSTRAINT "billOfMaterial_pkey" PRIMARY KEY (id) +) + +CREATE TABLE IF NOT EXISTS bomContent +( + id bigint NOT NULL DEFAULT nextval('global_id_seq'::regclass), + "bomId" bigint, + "productId" bigint, + min integer DEFAULT 1, + max integer DEFAULT 1, + CONSTRAINT "bomContent_pkey" PRIMARY KEY (id) +) + diff --git a/buildtime/server/controller/bomController.go b/buildtime/server/controller/bomController.go new file mode 100644 index 000000000..9389b552d --- /dev/null +++ b/buildtime/server/controller/bomController.go @@ -0,0 +1,186 @@ +package controller + +import ( + "net/http" + "strconv" + + "simplycpq/models" + "simplycpq/services" + + "github.com/gin-gonic/gin" +) + +type BOMController struct { + bomService *services.BOMService +} + +func NewBOMController(b *services.BOMService) *BOMController { + return &BOMController{ + bomService: b, + } +} + +func (bc *BOMController) CreateBillOfMaterial(c *gin.Context) { + var bom models.BillOfMaterial + err := c.ShouldBindJSON(&bom) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.CreateBillOfMaterial(&bom) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusCreated) +} + +func (bc *BOMController) GetBOMProductsByBOMId(c *gin.Context) { + bomIdStr := c.Query("bomId") + bomId, err := strconv.ParseInt(bomIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + contents, err := bc.bomService.GetBOMProductsByBOMId(bomId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, contents) +} + +func (bc *BOMController) GetBOMVariantNodesByBOMId(c *gin.Context) { + bomIdStr := c.Query("bomId") + bomId, err := strconv.ParseInt(bomIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + variantNodes, err := bc.bomService.GetBOMVariantNodesByBOMId(bomId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, variantNodes) +} + +func (bc *BOMController) CreateVariantNode(c *gin.Context) { + var variantNode models.VariantNode + err := c.ShouldBindJSON(&variantNode) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.CreateVariantNode(&variantNode) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusCreated) +} + +func (bc *BOMController) UpdateVariantNode(c *gin.Context) { + var variantNode models.VariantNode + err := c.ShouldBindJSON(&variantNode) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.UpdateVariantNode(&variantNode) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusOK) +} + +func (bc *BOMController) DeleteVariantNode(c *gin.Context) { + variantNodeIdStr := c.Query("variantNodeId") + variantNodeId, err := strconv.ParseInt(variantNodeIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.DeleteVariantNode(variantNodeId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusOK) +} + +func (bc *BOMController) GetVariantNode(c *gin.Context) { + variantNodeIdStr := c.Query("variantNodeId") + variantNodeId, err := strconv.ParseInt(variantNodeIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + variantNode, err := bc.bomService.GetVariantNodeById(variantNodeId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, variantNode) +} + +func (bc *BOMController) GetVariantNodeProductsByVariantNodeId(c *gin.Context) { + variantNodeIdStr := c.Query("variantNodeId") + variantNodeId, err := strconv.ParseInt(variantNodeIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + products, err := bc.bomService.GetVariantNodeProductsByVariantNodeId(variantNodeId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, products) +} + +func (bc *BOMController) UpdateBillOfMaterial(c *gin.Context) { + var bom models.BillOfMaterial + err := c.ShouldBindJSON(&bom) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.UpdateBillOfMaterial(&bom) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusOK) +} + +func (bc *BOMController) DeleteBillOfMaterial(c *gin.Context) { + bomIdStr := c.Query("id") + bomId, err := strconv.ParseInt(bomIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + err = bc.bomService.DeleteBillOfMaterial(bomId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.Status(http.StatusOK) +} + +func (bc *BOMController) GetBillOfMaterial(c *gin.Context) { + bomIdStr := c.Query("id") + bomId, err := strconv.ParseInt(bomIdStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + bom, err := bc.bomService.GetBillOfMaterial(bomId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, bom) +} diff --git a/buildtime/server/models/bom.go b/buildtime/server/models/bom.go new file mode 100644 index 000000000..58e807538 --- /dev/null +++ b/buildtime/server/models/bom.go @@ -0,0 +1,30 @@ +package models + +type BillOfMaterial struct { + ID int64 `json:"id,omitempty"` + ProductID int64 `json:"productId"` + Label string `json:"label"` + Description string `json:"description"` + ParentBOMID int64 `json:"parentBOMId,omitempty"` +} + +type BOMContent struct { + ID int64 `json:"id,omitempty"` + BOMID int64 `json:"bomId"` + ProductID int64 `json:"productId"` + Min int `json:"min,omitempty"` + Max int `json:"max,omitempty"` +} + +type VariantNode struct { + ID int64 `json:"id,omitempty"` + Label string `json:"label"` + Description string `json:"description"` + BOMID int64 `json:"bomId,omitempty"` +} + +type VariantNodeContent struct { + ID int64 `json:"id,omitempty"` + VariantNodeID int64 `json:"variantNodeId"` + ProductID int64 `json:"productId"` +} diff --git a/buildtime/server/server/httpServer.go b/buildtime/server/server/httpServer.go index 124a3b7f1..2e2e21c01 100644 --- a/buildtime/server/server/httpServer.go +++ b/buildtime/server/server/httpServer.go @@ -21,6 +21,7 @@ type HttpServer struct { valueController *controller.ValueController productGroupController *controller.ProductGroupController productController *controller.ProductController + bomController *controller.BOMController } func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { @@ -30,17 +31,20 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { productGroupRepository := repositories.NewProductGroupRepository(dbHandler, valueRepository) productRepository := repositories.NewProductRepository(dbHandler, productGroupRepository, valueRepository) importRepository := repositories.NewImportRepository(dbHandler, productGroupRepository, productRepository, valueRepository) + bomRepository := repositories.NewBOMRepository(dbHandler, productRepository) // service initialisation: valueService := services.NewValueService(valueRepository) productGroupService := services.NewProductGroupService(productGroupRepository) productService := services.NewProductService(productRepository) importService := services.NewImportService(importRepository) + bomService := services.NewBOMService(bomRepository) // controller initialisation: valueController := controller.NewValueController(valueService) productGroupController := controller.NewProductGroupController(productGroupService, valueService) productController := controller.NewProductController(productService, productGroupService, valueService) + bomController := controller.NewBOMController(bomService) importController := controller.NewImportController(importService) // Service API @@ -92,6 +96,19 @@ func InitHttpServer(config *viper.Viper, dbHandler *sql.DB) HttpServer { router.POST("/addPropertyToProductId/:productId", productController.AddPropertyToProductId) // add Property to Product by ID router.DELETE("/deletePropertyFromProductId/:productId", productController.DeletePropertyFromProductId) // delete Property from Product by ID + // BOM centric routing + router.POST("/createBillOfMaterial", bomController.CreateBillOfMaterial) + router.PUT("/updateBillOfMaterial", bomController.UpdateBillOfMaterial) + router.DELETE("/deleteBillOfMaterial/:id", bomController.DeleteBillOfMaterial) + router.GET("/getBillOfMaterial/:id", bomController.GetBillOfMaterial) + router.GET("/getBOMProductsByBOMId/:bomId", bomController.GetBOMProductsByBOMId) // get all BOMContents by BOM ID + router.GET("/getBOMVariantNodesByBOMId/:bomId", bomController.GetBOMVariantNodesByBOMId) // get all VariantNodes by BOM ID + router.POST("/createVariantNode/:bomId", bomController.CreateVariantNode) + router.PUT("/updateVariantNode", bomController.UpdateVariantNode) + router.DELETE("/deleteVariantNode/:id", bomController.DeleteVariantNode) + router.GET("/getVariantNode/:id", bomController.GetVariantNode) + router.GET("/getVariantNodeProductsByVariantNodeId/:variantNodeId", bomController.GetVariantNodeProductsByVariantNodeId) + // Import functionality router.POST("/importPropertyTypeGroups", importController.ImportPropertyTypeGroups) // import PropertyTypeGroups router.POST("/importProductGroups", importController.ImportProductGroups) // import ProductGroups