From 79b3dd47b8ca8f89ad061321fe6913e3206c01d9 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Thu, 23 Oct 2025 22:00:55 +0700 Subject: [PATCH 1/5] fix[BE]: validate query page/limit defaults and add exists helpers --- .../repository/common.exists.repository.go | 19 +++++++++++++++++++ .../areas/controllers/area.controller.go | 4 ++++ .../banks/controllers/bank.controller.go | 4 ++++ .../banks/repositories/bank.repository.go | 5 +++++ .../master/banks/services/bank.service.go | 7 +++++++ .../controllers/customer.controller.go | 4 ++++ .../master/fcrs/controllers/fcr.controller.go | 4 ++++ .../flocks/controllers/flock.controller.go | 4 ++++ .../controllers/kandang.controller.go | 4 ++++ .../controllers/location.controller.go | 4 ++++ .../controllers/nonstock.controller.go | 4 ++++ .../product-category.controller.go | 4 ++++ .../controllers/product.controller.go | 4 ++++ .../controllers/supplier.controller.go | 4 ++++ .../repositories/supplier.repository.go | 6 +++++- .../suppliers/services/supplier.service.go | 13 +++++++++++++ .../master/uoms/controllers/uom.controller.go | 4 ++++ .../controllers/warehouse.controller.go | 4 ++++ tools/templates/controller.tmpl | 4 ++++ 19 files changed, 105 insertions(+), 1 deletion(-) diff --git a/internal/common/repository/common.exists.repository.go b/internal/common/repository/common.exists.repository.go index ef371330..c6bc11f0 100644 --- a/internal/common/repository/common.exists.repository.go +++ b/internal/common/repository/common.exists.repository.go @@ -2,6 +2,7 @@ package repository import ( "context" + "fmt" "gorm.io/gorm" ) @@ -32,3 +33,21 @@ func ExistsByName[T any](ctx context.Context, db *gorm.DB, name string, excludeI } return count > 0, nil } + +func ExistsByField[T any](ctx context.Context, db *gorm.DB, field string, value any, excludeID *uint) (bool, error) { + if field == "" { + return false, fmt.Errorf("field is required") + } + var count int64 + q := db.WithContext(ctx). + Model(new(T)). + Where(fmt.Sprintf("%s = ?", field), value). + Where("deleted_at IS NULL") + if excludeID != nil { + q = q.Where("id <> ?", *excludeID) + } + if err := q.Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} diff --git a/internal/modules/master/areas/controllers/area.controller.go b/internal/modules/master/areas/controllers/area.controller.go index e08dba7d..252bc769 100644 --- a/internal/modules/master/areas/controllers/area.controller.go +++ b/internal/modules/master/areas/controllers/area.controller.go @@ -29,6 +29,10 @@ func (u *AreaController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.AreaService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/banks/controllers/bank.controller.go b/internal/modules/master/banks/controllers/bank.controller.go index 7625d078..ffe61cea 100644 --- a/internal/modules/master/banks/controllers/bank.controller.go +++ b/internal/modules/master/banks/controllers/bank.controller.go @@ -29,6 +29,10 @@ func (u *BankController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.BankService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/banks/repositories/bank.repository.go b/internal/modules/master/banks/repositories/bank.repository.go index 53d27713..d309d3c1 100644 --- a/internal/modules/master/banks/repositories/bank.repository.go +++ b/internal/modules/master/banks/repositories/bank.repository.go @@ -11,6 +11,7 @@ import ( type BankRepository interface { repository.BaseRepository[entity.Bank] NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) + AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error) } type BankRepositoryImpl struct { @@ -28,3 +29,7 @@ func NewBankRepository(db *gorm.DB) BankRepository { func (r *BankRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) { return repository.ExistsByName[entity.Bank](ctx, r.db, name, excludeID) } + +func (r *BankRepositoryImpl) AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error) { + return repository.ExistsByField[entity.Bank](ctx, r.db, "account_number", accountNumber, excludeID) +} diff --git a/internal/modules/master/banks/services/bank.service.go b/internal/modules/master/banks/services/bank.service.go index b62bf864..83d3029d 100644 --- a/internal/modules/master/banks/services/bank.service.go +++ b/internal/modules/master/banks/services/bank.service.go @@ -87,6 +87,13 @@ func (s *bankService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.B return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with name %s already exists", req.Name)) } + if exists, err := s.Repository.AccountNumberExists(c.Context(), req.AccountNumber, nil); err != nil { + s.Log.Errorf("Failed to check bank account number: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check bank account number") + } else if exists { + return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with account number %s already exists", req.AccountNumber)) + } + createBody := &entity.Bank{ Name: req.Name, Alias: req.Alias, diff --git a/internal/modules/master/customers/controllers/customer.controller.go b/internal/modules/master/customers/controllers/customer.controller.go index 2f9c0ed4..02805f6f 100644 --- a/internal/modules/master/customers/controllers/customer.controller.go +++ b/internal/modules/master/customers/controllers/customer.controller.go @@ -29,6 +29,10 @@ func (u *CustomerController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.CustomerService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/fcrs/controllers/fcr.controller.go b/internal/modules/master/fcrs/controllers/fcr.controller.go index 33353ffa..52db463d 100644 --- a/internal/modules/master/fcrs/controllers/fcr.controller.go +++ b/internal/modules/master/fcrs/controllers/fcr.controller.go @@ -29,6 +29,10 @@ func (u *FcrController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.FcrService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/flocks/controllers/flock.controller.go b/internal/modules/master/flocks/controllers/flock.controller.go index 8265f3e4..f8df0587 100644 --- a/internal/modules/master/flocks/controllers/flock.controller.go +++ b/internal/modules/master/flocks/controllers/flock.controller.go @@ -29,6 +29,10 @@ func (u *FlockController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.FlockService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/kandangs/controllers/kandang.controller.go b/internal/modules/master/kandangs/controllers/kandang.controller.go index 23d22334..b1d016df 100644 --- a/internal/modules/master/kandangs/controllers/kandang.controller.go +++ b/internal/modules/master/kandangs/controllers/kandang.controller.go @@ -31,6 +31,10 @@ func (u *KandangController) GetAll(c *fiber.Ctx) error { PicId: c.QueryInt("pic_id", 0), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.KandangService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/locations/controllers/location.controller.go b/internal/modules/master/locations/controllers/location.controller.go index 8f8211d7..f360a9c9 100644 --- a/internal/modules/master/locations/controllers/location.controller.go +++ b/internal/modules/master/locations/controllers/location.controller.go @@ -30,6 +30,10 @@ func (u *LocationController) GetAll(c *fiber.Ctx) error { AreaId: c.QueryInt("area_id", 0), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.LocationService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/nonstocks/controllers/nonstock.controller.go b/internal/modules/master/nonstocks/controllers/nonstock.controller.go index d8b688b7..d991c4da 100644 --- a/internal/modules/master/nonstocks/controllers/nonstock.controller.go +++ b/internal/modules/master/nonstocks/controllers/nonstock.controller.go @@ -29,6 +29,10 @@ func (u *NonstockController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.NonstockService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/product-categories/controllers/product-category.controller.go b/internal/modules/master/product-categories/controllers/product-category.controller.go index 778a3188..e4531a1f 100644 --- a/internal/modules/master/product-categories/controllers/product-category.controller.go +++ b/internal/modules/master/product-categories/controllers/product-category.controller.go @@ -29,6 +29,10 @@ func (u *ProductCategoryController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.ProductCategoryService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/products/controllers/product.controller.go b/internal/modules/master/products/controllers/product.controller.go index ee2c95f8..197a6b5f 100644 --- a/internal/modules/master/products/controllers/product.controller.go +++ b/internal/modules/master/products/controllers/product.controller.go @@ -30,6 +30,10 @@ func (u *ProductController) GetAll(c *fiber.Ctx) error { ProductCategoryID: c.QueryInt("product_category_id", 0), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.ProductService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/suppliers/controllers/supplier.controller.go b/internal/modules/master/suppliers/controllers/supplier.controller.go index a76904a9..5d70e43e 100644 --- a/internal/modules/master/suppliers/controllers/supplier.controller.go +++ b/internal/modules/master/suppliers/controllers/supplier.controller.go @@ -29,6 +29,10 @@ func (u *SupplierController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.SupplierService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/suppliers/repositories/supplier.repository.go b/internal/modules/master/suppliers/repositories/supplier.repository.go index 46fb2983..6b5a0ae2 100644 --- a/internal/modules/master/suppliers/repositories/supplier.repository.go +++ b/internal/modules/master/suppliers/repositories/supplier.repository.go @@ -11,7 +11,7 @@ import ( type SupplierRepository interface { repository.BaseRepository[entity.Supplier] NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) - + AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) } type SupplierRepositoryImpl struct { @@ -29,3 +29,7 @@ func NewSupplierRepository(db *gorm.DB) SupplierRepository { func (r *SupplierRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) { return repository.ExistsByName[entity.Supplier](ctx, r.db, name, excludeID) } + +func (r *SupplierRepositoryImpl) AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) { + return repository.ExistsByField[entity.Supplier](ctx, r.db, "alias", alias, excludeID) +} diff --git a/internal/modules/master/suppliers/services/supplier.service.go b/internal/modules/master/suppliers/services/supplier.service.go index f8422350..99e15b29 100644 --- a/internal/modules/master/suppliers/services/supplier.service.go +++ b/internal/modules/master/suppliers/services/supplier.service.go @@ -88,6 +88,13 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with name %s already exists", req.Name)) } + if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(req.Alias)), nil); err != nil { + s.Log.Errorf("Failed to check supplier alias: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias") + } else if exists { + return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(req.Alias)))) + } + typ := strings.ToUpper(req.Type) if !utils.IsValidCustomerSupplierType(typ) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid supplier type") @@ -143,6 +150,12 @@ func (s supplierService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint } if req.Alias != nil { + if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(*req.Alias)), &id); err != nil { + s.Log.Errorf("Failed to check supplier alias: %+v", err) + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias") + } else if exists { + return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(*req.Alias)))) + } updateBody["alias"] = strings.TrimSpace(strings.ToUpper(*req.Alias)) } diff --git a/internal/modules/master/uoms/controllers/uom.controller.go b/internal/modules/master/uoms/controllers/uom.controller.go index 0bd3a382..ecef1f69 100644 --- a/internal/modules/master/uoms/controllers/uom.controller.go +++ b/internal/modules/master/uoms/controllers/uom.controller.go @@ -29,6 +29,10 @@ func (u *UomController) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.UomService.GetAll(c, query) if err != nil { return err diff --git a/internal/modules/master/warehouses/controllers/warehouse.controller.go b/internal/modules/master/warehouses/controllers/warehouse.controller.go index b841d4ef..afa90660 100644 --- a/internal/modules/master/warehouses/controllers/warehouse.controller.go +++ b/internal/modules/master/warehouses/controllers/warehouse.controller.go @@ -30,6 +30,10 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error { AreaId: c.QueryInt("area_id", 0), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.WarehouseService.GetAll(c, query) if err != nil { return err diff --git a/tools/templates/controller.tmpl b/tools/templates/controller.tmpl index 9fcf6d9b..f2eb615e 100644 --- a/tools/templates/controller.tmpl +++ b/tools/templates/controller.tmpl @@ -29,6 +29,10 @@ func (u *{{Pascal .Entity}}Controller) GetAll(c *fiber.Ctx) error { Search: c.Query("search", ""), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.{{Pascal .Entity}}Service.GetAll(c, query) if err != nil { return err From aeeb5a38c137a1044898f7409a310d9e4e72165b Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 24 Oct 2025 09:51:50 +0700 Subject: [PATCH 2/5] Feat[BE] : add avaibility DOC on lookup porject flock API, add note request json on chickin --- .../product_warehouse.controller.go | 6 +- .../services/product_warehouse.service.go | 20 +++ .../chickins/services/chickin.service.go | 6 +- .../validations/chickin.validation.go | 1 + .../controllers/projectflock.controller.go | 30 ++++- .../dto/projectflock_kandang.dto.go | 41 +++--- .../production/project_flocks/module.go | 6 +- .../services/projectflock.service.go | 123 +++++++++--------- 8 files changed, 137 insertions(+), 96 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 a0b72a4d..b44eab28 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -30,6 +30,10 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error { WarehouseId: uint(c.QueryInt("warehouse_id", 0)), } + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + result, totalResults, err := u.ProductWarehouseService.GetAll(c, query) if err != nil { return err @@ -71,5 +75,3 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error { Data: dto.ToProductWarehouseListDTO(*result), }) } - - 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 4fad5dc5..e9e31ab5 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -49,6 +49,26 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) return nil, 0, err } + if params.ProductId > 0 { + isProductExist, err := s.Repository.IsProductExist(c.Context(), params.ProductId) + if err != nil { + return nil, 0, err + } + if !isProductExist { + return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found") + } + } + + if params.WarehouseId > 0 { + isWarehouseExist, err := s.Repository.IsWarehouseExist(c.Context(), params.WarehouseId) + if err != nil { + return nil, 0, err + } + if !isWarehouseExist { + return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") + } + } + offset := (params.Page - 1) * params.Limit productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 0df1b6b5..66793c8c 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -136,8 +136,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit if len(productWarehouses) == 0 { return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") } - - // Jumlahkan semua quantity DOC totalQuantity := 0.0 for _, pw := range productWarehouses { totalQuantity += pw.Quantity @@ -147,7 +145,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses") } - // Buat satu chickin dengan total quantity chickinDate, err := utils.ParseDateString(req.ChickInDate) if err != nil { s.Log.Errorf("Failed to parse chickin date: %+v", err) @@ -157,7 +154,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit ProjectFlockKandangId: projectflockkandang.Id, ChickInDate: chickinDate, Quantity: totalQuantity, - Note: "", + Note: req.Note, CreatedBy: 1, //todo: ganti dengan user login } err = s.Repository.CreateOne(c.Context(), newChickin, nil) @@ -176,7 +173,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - // add ke detail chickin newChickinDetail := &entity.ProjectChickinDetail{ ProjectChickinId: newChickin.Id, ProductWarehouseId: pw.Id, diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go index c122c100..66d4924c 100644 --- a/internal/modules/production/chickins/validations/chickin.validation.go +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -3,6 +3,7 @@ package validation type Create struct { ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"` ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` + Note string `json:"note" validate:"omitempty` } type Update struct { diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index ca60d5df..668743b3 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -246,17 +246,39 @@ func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error { } func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error { - projectFlockIdStr := c.Query("project_flock_id", "") - kandangIdStr := c.Query("kandang_id", "") + projectFlockId := c.QueryInt("project_flock_id", 0) + kandangId := c.QueryInt("kandang_id", 0) - result, err := u.ProjectflockService.GetProjectFlockKandangByParams(c, "", projectFlockIdStr, kandangIdStr) + if projectFlockId == 0 || kandangId == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id or kandang_id") + } + + result, availableStock, err := u.ProjectflockService.GetProjectFlockKandangByProjectAndKandang(c, uint(projectFlockId), uint(kandangId)) if err != nil { return err } + dtoResult := dto.ToProjectFlockKandangDTO(*result) + dtoResult.AvailableQuantity = float64(availableStock) + + // populate available quantity for each kandang inside project_flock + if dtoResult.ProjectFlock != nil { + for i := range dtoResult.ProjectFlock.Kandangs { + kand := &dtoResult.ProjectFlock.Kandangs[i] + if kand.Id == 0 { + continue + } + if q, qerr := u.ProjectflockService.GetAvailableDocQuantity(c, kand.Id); qerr == nil { + kand.AvailableQuantity = q + } + } + // remove inner kandangs from project_flock to avoid duplication + dtoResult.ProjectFlock.Kandangs = nil + } + return c.Status(fiber.StatusOK). JSON(response.Success{Code: fiber.StatusOK, Status: "success", Message: "Get projectflock kandang successfully", - Data: dto.ToProjectFlockKandangDTO(*result)}) + Data: dtoResult}) } diff --git a/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go index ff82fba9..27a68011 100644 --- a/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock_kandang.dto.go @@ -10,10 +10,9 @@ import ( userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) -// internal DTO used only for lookup response: project flock with kandangs carrying pivot ids type KandangWithPivotDTO struct { kandangDTO.KandangBaseDTO - ProjectFlockKandangId *uint `json:"project_flock_kandang_id,omitempty"` + AvailableQuantity float64 `json:"available_quantity"` } type ProjectFlockWithPivotDTO struct { @@ -28,11 +27,13 @@ type ProjectFlockWithPivotDTO struct { } type ProjectFlockKandangDTO struct { - Id uint `json:"id"` - ProjectFlockId uint `json:"project_flock_id"` - KandangId uint `json:"kandang_id"` - Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"` - ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"` + Id uint `json:"id"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + ProjectFlockId uint `json:"project_flock_id"` + KandangId uint `json:"kandang_id"` + Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"` + ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"` + AvailableQuantity float64 `json:"available_quantity"` } func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO { @@ -44,7 +45,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD var pf *ProjectFlockWithPivotDTO if e.ProjectFlock.Id != 0 { - // build project flock with kandangs that include pivot ids + pfLocal := ProjectFlockWithPivotDTO{ ProjectFlockBaseDTO: ProjectFlockBaseDTO{ Id: e.ProjectFlock.Id, @@ -53,7 +54,6 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD Category: e.ProjectFlock.Category, } - // fill related small summaries if e.ProjectFlock.Flock.Id != 0 { mapped := ToFlockSummaryDTO(e.ProjectFlock.Flock) pfLocal.Flock = &mapped @@ -75,23 +75,16 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD pfLocal.CreatedUser = &mapped } - // build pivot map pivotMap := make(map[uint]uint) for _, ph := range e.ProjectFlock.KandangHistory { pivotMap[ph.KandangId] = ph.Id } - // populate kandangs with pivot ids for _, k := range e.ProjectFlock.Kandangs { kb := kandangDTO.ToKandangBaseDTO(k) - var pid *uint - if v, ok := pivotMap[k.Id]; ok { - vv := v - pid = &vv - } pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{ - KandangBaseDTO: kb, - ProjectFlockKandangId: pid, + KandangBaseDTO: kb, + AvailableQuantity: 0, }) } @@ -99,10 +92,12 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD } return ProjectFlockKandangDTO{ - Id: e.Id, - ProjectFlockId: e.ProjectFlockId, - KandangId: e.KandangId, - Kandang: kandang, - ProjectFlock: pf, + Id: e.Id, + ProjectFlockKandangId: e.Id, + ProjectFlockId: e.ProjectFlockId, + KandangId: e.KandangId, + Kandang: kandang, + ProjectFlock: pf, + AvailableQuantity: 0, } } diff --git a/internal/modules/production/project_flocks/module.go b/internal/modules/production/project_flocks/module.go index 994eb4a4..4fd932a4 100644 --- a/internal/modules/production/project_flocks/module.go +++ b/internal/modules/production/project_flocks/module.go @@ -9,8 +9,10 @@ import ( commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" "gorm.io/gorm" + rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" + rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services" @@ -27,6 +29,8 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid kandangRepo := rKandang.NewKandangRepository(db) projectflockRepo := rProjectflock.NewProjectflockRepository(db) projectflockKandangRepo := rProjectflock.NewProjectFlockKandangRepository(db) + warehouseRepo := rWarehouse.NewWarehouseRepository(db) + productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) userRepo := rUser.NewUserRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) @@ -35,7 +39,7 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid panic(fmt.Sprintf("failed to register project flock approval workflow: %v", err)) } - projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, approvalService, validate) + projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, warehouseRepo, productWarehouseRepo, approvalService, validate) userService := sUser.NewUserService(userRepo, validate) ProjectflockRoutes(router, userService, projectflockService) diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index f9c7881e..23097585 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -4,14 +4,15 @@ import ( "context" "errors" "fmt" - "strconv" "strings" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" + warehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations" utils "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -29,20 +30,23 @@ type ProjectflockService interface { CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) DeleteOne(ctx *fiber.Ctx, id uint) error - GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) + GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, int, error) + GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) } type projectflockService struct { - Log *logrus.Logger - Validate *validator.Validate - Repository repository.ProjectflockRepository - FlockRepo flockRepository.FlockRepository - KandangRepo kandangRepository.KandangRepository - PivotRepo repository.ProjectFlockKandangRepository - ApprovalSvc commonSvc.ApprovalService - approvalWorkflow approvalutils.ApprovalWorkflowKey + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ProjectflockRepository + FlockRepo flockRepository.FlockRepository + KandangRepo kandangRepository.KandangRepository + WarehouseRepo warehouseRepository.WarehouseRepository + ProductWarehouseRepo productWarehouseRepository.ProductWarehouseRepository + ProjectFlockKandangRepo repository.ProjectFlockKandangRepository + ApprovalSvc commonSvc.ApprovalService + approvalWorkflow approvalutils.ApprovalWorkflowKey } type FlockPeriodSummary struct { @@ -54,19 +58,23 @@ func NewProjectflockService( repo repository.ProjectflockRepository, flockRepo flockRepository.FlockRepository, kandangRepo kandangRepository.KandangRepository, - pivotRepo repository.ProjectFlockKandangRepository, + ProjectFlockKandangRepo repository.ProjectFlockKandangRepository, + warehouseRepo warehouseRepository.WarehouseRepository, + productWarehouseRepo productWarehouseRepository.ProductWarehouseRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate, ) ProjectflockService { return &projectflockService{ - Log: utils.Log, - Validate: validate, - Repository: repo, - FlockRepo: flockRepo, - KandangRepo: kandangRepo, - PivotRepo: pivotRepo, - ApprovalSvc: approvalSvc, - approvalWorkflow: utils.ApprovalWorkflowProjectFlock, + Log: utils.Log, + Validate: validate, + Repository: repo, + FlockRepo: flockRepo, + KandangRepo: kandangRepo, + WarehouseRepo: warehouseRepo, + ProductWarehouseRepo: productWarehouseRepo, + ProjectFlockKandangRepo: ProjectFlockKandangRepo, + ApprovalSvc: approvalSvc, + approvalWorkflow: utils.ApprovalWorkflowProjectFlock, } } @@ -641,55 +649,48 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error { return nil } -func (s projectflockService) GetProjectFlockKandang(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, error) { - // keep for backward compatibility; delegate to new consolidated method - return s.GetProjectFlockKandangByParams(ctx, fmt.Sprintf("%d", id), "", "") -} +func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, int, error) { -func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) { + availableStock, err := s.GetAvailableDocQuantity(ctx, kandangID) + if err != nil { + return nil, 0, err + } - pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID) + projectFlockKandang, err := s.ProjectFlockKandangRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") + return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") } - return nil, err + return nil, 0, err } - return pfk, nil + + return projectFlockKandang, int(availableStock), nil } -func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) { - idStr = strings.TrimSpace(idStr) - projectFlockIdStr = strings.TrimSpace(projectFlockIdStr) - kandangIdStr = strings.TrimSpace(kandangIdStr) +func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) { - if idStr != "" { - id, err := strconv.Atoi(idStr) - if err != nil || id <= 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id)) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") - } - return nil, err - } - return pfk, nil + wh, err := s.WarehouseRepo.GetByKandangID(ctx.Context(), kandangID) + if err != nil { + return 0, err } - if projectFlockIdStr == "" || kandangIdStr == "" { - return nil, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters") + var productWarehouses []entity.ProductWarehouse + err = s.ProductWarehouseRepo.DB(). + WithContext(ctx.Context()). + Joins("JOIN products ON products.id = product_warehouses.product_id"). + Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", wh.Id). + Order("created_at DESC"). + Find(&productWarehouses).Error + if err != nil { + return 0, err } - pfid, err := strconv.Atoi(projectFlockIdStr) - if err != nil || pfid <= 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id") + + total := 0.0 + for _, pw := range productWarehouses { + total += pw.Quantity } - kid, err := strconv.Atoi(kandangIdStr) - if err != nil || kid <= 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id") - } - return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid)) + return total, nil } func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) { @@ -784,7 +785,7 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction * return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs") } - pivotRepo := s.pivotRepoWithTx(dbTransaction) + ProjectFlockKandangRepo := s.ProjectFlockKandangRepoWithTx(dbTransaction) records := make([]*entity.ProjectFlockKandang, len(kandangIDs)) for i, id := range kandangIDs { records[i] = &entity.ProjectFlockKandang{ @@ -792,7 +793,7 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction * KandangId: id, } } - if err := pivotRepo.CreateMany(ctx, records); err != nil { + if err := ProjectFlockKandangRepo.CreateMany(ctx, records); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history") } return nil @@ -814,15 +815,15 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction * return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs") } - if err := s.pivotRepoWithTx(dbTransaction).DeleteMany(ctx, projectFlockID, kandangIDs); err != nil { + if err := s.ProjectFlockKandangRepoWithTx(dbTransaction).DeleteMany(ctx, projectFlockID, kandangIDs); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history") } return nil } -func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository { - if s.PivotRepo == nil { +func (s projectflockService) ProjectFlockKandangRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository { + if s.ProjectFlockKandangRepo == nil { return repository.NewProjectFlockKandangRepository(dbTransaction) } - return s.PivotRepo.WithTx(dbTransaction) + return s.ProjectFlockKandangRepo.WithTx(dbTransaction) } From 222d53aa37cd276eef2f2439a533fb39ecb76fe8 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 24 Oct 2025 10:25:05 +0700 Subject: [PATCH 3/5] FIX[BE] : use repository instead of raw query on service on productflock service --- .../product_warehouse.repository.go | 39 +++++++++++++------ .../chickins/services/chickin.service.go | 11 +----- 2 files changed, 30 insertions(+), 20 deletions(-) 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 cc4adf64..f1f1fa57 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -16,6 +16,7 @@ type ProductWarehouseRepository interface { 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) + GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) } type ProductWarehouseRepositoryImpl struct { @@ -30,6 +31,17 @@ func NewProductWarehouseRepository(db *gorm.DB) ProductWarehouseRepository { } } +func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) { + return repository.Exists[entity.Product](ctx, r.db, productId) +} +func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) { + return repository.Exists[entity.Warehouse](ctx, r.db, warehouseId) +} + +func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) { + return repository.Exists[entity.ProductWarehouse](ctx, r.db, id) +} + func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) { var count int64 query := r.db.WithContext(ctx).Model(&entity.ProductWarehouse{}). @@ -43,17 +55,6 @@ func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Cont return count > 0, nil } -func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) { - return repository.Exists[entity.Product](ctx, r.db, productId) -} -func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) { - return repository.Exists[entity.Warehouse](ctx, r.db, warehouseId) -} - -func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) { - 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). @@ -72,3 +73,19 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous } return &productWarehouse, nil } + +func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) { + var productWarehouses []entity.ProductWarehouse + err := r.db.WithContext(ctx). + Table("product_warehouses"). + Select("product_warehouses.*"). + Joins("JOIN products ON products.id = product_warehouses.product_id"). + Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId). + Order("product_warehouses.created_at DESC"). + Find(&productWarehouses).Error + if err != nil { + return nil, err + } + return productWarehouses, nil +} diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 66793c8c..ec2b31aa 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -121,14 +121,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - var productWarehouses []entity.ProductWarehouse - err = s.ProductWarehouseRepo.DB(). - WithContext(c.Context()). - Joins("JOIN products ON products.id = product_warehouses.product_id"). - Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). - Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id). - Order("created_at DESC"). - Find(&productWarehouses).Error + // move complex DB query into repository for cleaner service + productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id) if err != nil { s.Log.Errorf("Failed to get product warehouses: %+v", err) return nil, err @@ -289,7 +283,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { return rollback(err) } - // helper: restore quantities from details; returns (restored bool, error) restoreFromDetails := func() (bool, error) { var details []entity.ProjectChickinDetail if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil { From 7f2175a8cfc1a4741068c9913f008dfa03a35edf Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 24 Oct 2025 11:16:12 +0700 Subject: [PATCH 4/5] Feat[Be-117]: Menambahkan note upda update chickin api --- .../modules/production/chickins/services/chickin.service.go | 3 +++ .../production/chickins/validations/chickin.validation.go | 1 + 2 files changed, 4 insertions(+) diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index ec2b31aa..f422666f 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -222,6 +222,9 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) if req.ChickInDate != "" { updateBody["chick_in_date"] = req.ChickInDate } + if req.Note != "" { + updateBody["note"] = req.Note + } if len(updateBody) == 0 { return s.GetOne(c, id) } diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go index 66d4924c..9747ee07 100644 --- a/internal/modules/production/chickins/validations/chickin.validation.go +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -8,6 +8,7 @@ type Create struct { type Update struct { ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` + Note string `json:"note" validate:"omitempty"` } type Query struct { From ef99a4a3c1f79e1f99b642328242218c0afdea51 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 24 Oct 2025 13:29:37 +0700 Subject: [PATCH 5/5] FIX[BE] : fix productwarehouses flags faram become multiple param --- .../product_warehouse.controller.go | 1 + .../services/product_warehouse.service.go | 8 ++++ .../product_warehouse.validation.go | 9 +++-- internal/utils/strings.go | 38 ++++++++++++++++++- 4 files changed, 51 insertions(+), 5 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 b44eab28..26f23278 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -28,6 +28,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error { Limit: c.QueryInt("limit", 10), ProductId: uint(c.QueryInt("product_id", 0)), WarehouseId: uint(c.QueryInt("warehouse_id", 0)), + Flags: c.Query("flags", ""), } if query.Page < 1 || query.Limit < 1 { 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 e9e31ab5..3a0468ca 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -71,6 +71,8 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) offset := (params.Page - 1) * params.Limit + cleanFlags := utils.ParseFlags(params.Flags) + productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) @@ -82,6 +84,12 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) db = db.Where("warehouse_id = ?", params.WarehouseId) } + if len(cleanFlags) > 0 { + db = 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 ?", cleanFlags) + } + return db.Order("created_at DESC").Order("updated_at 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 02648300..3a3acb28 100644 --- a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go +++ b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go @@ -13,8 +13,9 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` - ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` - WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` + WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` + Flags string `query:"flags" validate:"omitempty"` } diff --git a/internal/utils/strings.go b/internal/utils/strings.go index f6560191..a58ba1ac 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -1,6 +1,9 @@ package utils -import "strings" +import ( + "sort" + "strings" +) // NormalizeTrim returns the input string without leading/trailing whitespace. func NormalizeTrim(value string) string { @@ -11,3 +14,36 @@ func NormalizeTrim(value string) string { func NormalizeUpper(value string) string { return strings.ToUpper(NormalizeTrim(value)) } + +// NormalizeFlag trims whitespace, removes surrounding brackets/quotes and returns upper-case flag +func NormalizeFlag(value string) string { + v := NormalizeTrim(value) + v = strings.Trim(v, "[]\"'") + return strings.ToUpper(v) +} + +// ParseFlags parses a raw flags string like "[DOC, PAKAN]" or "DOC,PAKAN" +// and returns a deduplicated, sorted slice of normalized flags (upper-case, trimmed). +func ParseFlags(raw string) []string { + if raw == "" { + return nil + } + parts := strings.Split(raw, ",") + set := make(map[string]struct{}, len(parts)) + for _, p := range parts { + f := NormalizeFlag(p) + if f == "" { + continue + } + set[f] = struct{}{} + } + if len(set) == 0 { + return nil + } + res := make([]string, 0, len(set)) + for k := range set { + res = append(res, k) + } + sort.Strings(res) + return res +}