From 90de167fcd700eacc46ca18780748a8c1c270b9f Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Wed, 4 Feb 2026 09:59:15 +0700 Subject: [PATCH] FEAT[BE] :add type filtering and validation to product warehouse services --- .../product_warehouse.controller.go | 1 + .../product_warehouse.repository.go | 7 ++-- .../services/product_warehouse.service.go | 23 ++++++++++- .../product_warehouse.validation.go | 1 + .../services/deliveryorder.service.go | 30 +++++++------- .../marketing/services/salesorder.service.go | 40 +++++++++++-------- .../validations/salesorder.validation.go | 2 +- internal/utils/constant.go | 2 +- 8 files changed, 70 insertions(+), 36 deletions(-) diff --git a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go index 47d85a65..bc6cdaed 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -32,6 +32,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error { Flags: c.Query("flags", ""), KandangId: uint(c.QueryInt("kandang_id", 0)), TransferContext: c.Query(utils.TransferContextKey, ""), + Type: c.Query("type", ""), } if query.Page < 1 || query.Limit < 1 { diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index a7fe452b..e49fc421 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -168,9 +168,10 @@ func (r *ProductWarehouseRepositoryImpl) ApplyFlagsFilter(db *gorm.DB, flags []s } return db. - Joins("JOIN products ON products.id = product_warehouses.product_id"). - Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products"). - Where("flags.name IN ?", flags) + Joins("JOIN products p_flag ON p_flag.id = product_warehouses.product_id"). + Joins("JOIN flags f_flag ON f_flag.flagable_id = p_flag.id AND f_flag.flagable_type = ?", "products"). + Where("f_flag.name IN ?", flags). + Distinct() } func (r *ProductWarehouseRepositoryImpl) AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error { diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 5bb3f692..98656de1 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -99,6 +99,12 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) offset := (params.Page - 1) * params.Limit + if params.Type != "" { + if !utils.IsValidMarketingType(params.Type) { + return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing type") + } + } + cleanFlags := utils.ParseFlags(params.Flags) productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { @@ -128,7 +134,22 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) db = db.Where("warehouse_id = ?", params.WarehouseId) } - db = s.Repository.ApplyFlagsFilter(db, cleanFlags) + if params.Type != "" { + switch params.Type { + case string(utils.MarketingTypeAyamPullet): + db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagDOC), string(utils.FlagPullet), string(utils.FlagLayer)}) + case string(utils.MarketingTypeAyam): + db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagAyamAfkir), string(utils.FlagAyamCulling), string(utils.FlagAyamMati)}) + case string(utils.MarketingTypeTelur): + db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagTelur), string(utils.FlagTelurUtuh), string(utils.FlagTelurPecah), string(utils.FlagTelurPutih), string(utils.FlagTelurRetak)}) + case string(utils.MarketingTypeTrading): + db = s.Repository.ApplyFlagsFilter(db, []string{string(utils.FlagPakan), string(utils.FlagPreStarter), string(utils.FlagStarter), string(utils.FlagFinisher), string(utils.FlagOVK), string(utils.FlagObat), string(utils.FlagVitamin), string(utils.FlagKimia), string(utils.FlagEkspedisi)}) + } + } + + if len(cleanFlags) > 0 { + db = s.Repository.ApplyFlagsFilter(db, cleanFlags) + } return db.Order("product_warehouses.id DESC") }) diff --git a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go index 61a41ad0..7e7da7a6 100644 --- a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go +++ b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go @@ -20,4 +20,5 @@ type Query struct { Flags string `query:"flags" validate:"omitempty"` KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"` TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"` + Type string `query:"type" validate:"omitempty,oneof=AYAM TELUR TRADING AYAM_PULLET"` } diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index 80045027..268a81eb 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -289,13 +289,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery itemDeliveryDate = &parsedDate } - totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight - var totalPrice float64 - if marketing.MarketingType == string(utils.MarketingTypeTrading) { - totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice - } else { - totalPrice = totalWeight * requestedProduct.UnitPrice - } + totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice @@ -421,13 +415,7 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO itemDeliveryDate = deliveryProduct.DeliveryDate } - totalWeight := requestedProduct.Qty * requestedProduct.AvgWeight - var totalPrice float64 - if marketing.MarketingType == string(utils.MarketingTypeTrading) { - totalPrice = requestedProduct.Qty * requestedProduct.UnitPrice - } else { - totalPrice = totalWeight * requestedProduct.UnitPrice - } + totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, requestedProduct.Qty, requestedProduct.AvgWeight, requestedProduct.UnitPrice, foundMarketingProduct.Week) deliveryProduct.ProductWarehouseId = foundMarketingProduct.ProductWarehouseId deliveryProduct.UnitPrice = requestedProduct.UnitPrice @@ -471,6 +459,20 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return s.getMarketingWithDeliveries(c, id) } +func (s *deliveryOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) { + if marketingType == string(utils.MarketingTypeTrading) { + totalWeight = 0 + totalPrice = qty * unitPrice + } else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 { + totalWeight = qty * avgWeight + totalPrice = unitPrice * float64(*week) * qty + } else { + totalWeight = qty * avgWeight + totalPrice = totalWeight * unitPrice + } + return totalWeight, totalPrice +} + func (s deliveryOrdersService) consumeDeliveryStock(ctx context.Context, tx *gorm.DB, deliveryProduct *entity.MarketingDeliveryProduct, marketingProduct *entity.MarketingProduct, requestedQty float64, actorID uint) error { if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 { return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found") diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index a43370d5..a64caa9f 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -104,7 +104,7 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e } if !utils.IsValidMarketingType(req.MarketingType) { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing_type. Must be one of: AYAM, TELUR, TRADING, AYAM PULLET") + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing_type. Must be one of: AYAM, TELUR, TRADING, AYAM_PULLET") } actorID, err := m.ActorIDFromContext(c) @@ -119,6 +119,9 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e } for _, item := range req.MarketingProducts { + if req.MarketingType != string(utils.MarketingTypeTrading) && item.AvgWeight == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "avg_weight is required for non-TRADING marketing type") + } if item.ConvertionUnit != nil && !utils.IsValidConvertionUnit(*item.ConvertionUnit) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid convertion_unit. Must be one of: PETI, KG") } @@ -215,7 +218,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } if req.MarketingType != "" && !utils.IsValidMarketingType(req.MarketingType) { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing_type. Must be one of: AYAM, TELUR, TRADING, AYAM PULLET") + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid marketing_type. Must be one of: AYAM, TELUR, TRADING, AYAM_PULLET") } if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { @@ -245,6 +248,9 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u if len(req.MarketingProducts) > 0 { for _, item := range req.MarketingProducts { + if req.MarketingType != "" && req.MarketingType != string(utils.MarketingTypeTrading) && item.AvgWeight == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "avg_weight is required for non-TRADING marketing type") + } if item.ConvertionUnit != nil && !utils.IsValidConvertionUnit(*item.ConvertionUnit) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid convertion_unit. Must be one of: PETI, KG") } @@ -331,13 +337,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u for _, rp := range req.MarketingProducts { if old, ok := oldByPW[rp.ProductWarehouseId]; ok { - totalWeight := rp.Qty * rp.AvgWeight - var totalPrice float64 - if marketing.MarketingType == string(utils.MarketingTypeTrading) { - totalPrice = rp.Qty * rp.UnitPrice - } else { - totalPrice = totalWeight * rp.UnitPrice - } + totalWeight, totalPrice := s.calculatePriceByMarketingType(marketing.MarketingType, rp.Qty, rp.AvgWeight, rp.UnitPrice, rp.Week) deliveryProduct, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { @@ -688,13 +688,7 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, marketingType string, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo repository.MarketingDeliveryProductRepository) error { - totalWeight := rp.Qty * rp.AvgWeight - var totalPrice float64 - if marketingType == string(utils.MarketingTypeTrading) { - totalPrice = rp.Qty * rp.UnitPrice - } else { - totalPrice = totalWeight * rp.UnitPrice - } + totalWeight, totalPrice := s.calculatePriceByMarketingType(marketingType, rp.Qty, rp.AvgWeight, rp.UnitPrice, rp.Week) marketingProduct := &entity.MarketingProduct{ MarketingId: marketingId, @@ -730,3 +724,17 @@ func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Cont return nil } + +func (s *salesOrdersService) calculatePriceByMarketingType(marketingType string, qty, avgWeight, unitPrice float64, week *int) (totalWeight, totalPrice float64) { + if marketingType == string(utils.MarketingTypeTrading) { + totalWeight = 0 + totalPrice = qty * unitPrice + } else if marketingType == string(utils.MarketingTypeAyamPullet) && week != nil && *week > 0 { + totalWeight = qty * avgWeight + totalPrice = unitPrice * float64(*week) * qty + } else { + totalWeight = qty * avgWeight + totalPrice = totalWeight * unitPrice + } + return totalWeight, totalPrice +} diff --git a/internal/modules/marketing/validations/salesorder.validation.go b/internal/modules/marketing/validations/salesorder.validation.go index 9a3cee29..bf38417f 100644 --- a/internal/modules/marketing/validations/salesorder.validation.go +++ b/internal/modules/marketing/validations/salesorder.validation.go @@ -17,7 +17,7 @@ type CreateMarketingProduct struct { ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,gt=0"` UnitPrice float64 `json:"unit_price" validate:"required,gt=0"` Qty float64 `json:"qty" validate:"required,gt=0"` - AvgWeight float64 `json:"avg_weight" validate:"required,gt=0"` + AvgWeight float64 `json:"avg_weight" validate:"omitempty,gt=0"` } type Update struct { diff --git a/internal/utils/constant.go b/internal/utils/constant.go index cb8a0ba2..1de04fa3 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -222,7 +222,7 @@ const ( MarketingTypeAyam MarketingType = "AYAM" MarketingTypeTelur MarketingType = "TELUR" MarketingTypeTrading MarketingType = "TRADING" - MarketingTypeAyamPullet MarketingType = "AYAM PULLET" + MarketingTypeAyamPullet MarketingType = "AYAM_PULLET" ) // -------------------------------------------------------------------