codex/fix: inconsistent stock options and availability

This commit is contained in:
Adnan Zahir
2026-04-04 09:52:59 +07:00
committed by giovanni
parent 480e430289
commit c07ba79ddb
5 changed files with 68 additions and 7 deletions
@@ -3,6 +3,7 @@ package controller
import ( import (
"math" "math"
"strconv" "strconv"
"strings"
"gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto" "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services" service "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
@@ -27,11 +28,13 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
query := &validation.Query{ query := &validation.Query{
Page: c.QueryInt("page", 1), Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10), Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""),
ProductId: uint(c.QueryInt("product_id", 0)), ProductId: uint(c.QueryInt("product_id", 0)),
WarehouseId: uint(c.QueryInt("warehouse_id", 0)), WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
LocationId: uint(c.QueryInt("location_id", 0)), LocationId: uint(c.QueryInt("location_id", 0)),
Flags: c.Query("flags", ""), Flags: c.Query("flags", ""),
KandangId: uint(c.QueryInt("kandang_id", 0)), KandangId: uint(c.QueryInt("kandang_id", 0)),
AvailableOnly: parseBoolQuery(c.Query("available_only", "")),
TransferContext: c.Query(utils.TransferContextKey, ""), TransferContext: c.Query(utils.TransferContextKey, ""),
StockMode: c.Query("stock_mode", ""), StockMode: c.Query("stock_mode", ""),
Type: c.Query("type", ""), Type: c.Query("type", ""),
@@ -61,6 +64,15 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
}) })
} }
func parseBoolQuery(raw string) bool {
switch strings.TrimSpace(strings.ToLower(raw)) {
case "1", "true", "yes", "y":
return true
default:
return false
}
}
func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error { func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error {
param := c.Params("id") param := c.Params("id")
@@ -34,7 +34,7 @@ func TestGetAllParsesLocationID(t *testing.T) {
ctrl := NewProductWarehouseController(stub) ctrl := NewProductWarehouseController(stub)
app.Get("/product-warehouses", ctrl.GetAll) app.Get("/product-warehouses", ctrl.GetAll)
req := httptest.NewRequest("GET", "/product-warehouses?location_id=16&kandang_id=59&limit=25", nil) req := httptest.NewRequest("GET", "/product-warehouses?location_id=16&kandang_id=59&limit=25&search=tektrol&available_only=true", nil)
resp, err := app.Test(req) resp, err := app.Test(req)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@@ -54,6 +54,12 @@ func TestGetAllParsesLocationID(t *testing.T) {
if stub.lastQuery.Limit != 25 { if stub.lastQuery.Limit != 25 {
t.Fatalf("expected limit 25, got %d", stub.lastQuery.Limit) t.Fatalf("expected limit 25, got %d", stub.lastQuery.Limit)
} }
if stub.lastQuery.Search != "tektrol" {
t.Fatalf("expected search tektrol, got %s", stub.lastQuery.Search)
}
if !stub.lastQuery.AvailableOnly {
t.Fatalf("expected available_only true")
}
} }
func TestStubImplementsServiceContract(t *testing.T) { func TestStubImplementsServiceContract(t *testing.T) {
@@ -71,6 +71,13 @@ func applyWarehouseSelectionFilter(db *gorm.DB, kandangID, locationID uint) *gor
} }
} }
func applyAvailableOnlyFilter(db *gorm.DB, availableOnly bool) *gorm.DB {
if !availableOnly {
return db
}
return db.Where("COALESCE(product_warehouses.qty, 0) > 0")
}
func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) { func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) {
if err := s.Validate.Struct(params); err != nil { if err := s.Validate.Struct(params); err != nil {
return nil, 0, err return nil, 0, err
@@ -151,12 +158,31 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
db = db.Where("product_id = ?", params.ProductId) db = db.Where("product_id = ?", params.ProductId)
} }
db = applyAvailableOnlyFilter(db, params.AvailableOnly)
db = applyWarehouseSelectionFilter(db, params.KandangId, params.LocationId) db = applyWarehouseSelectionFilter(db, params.KandangId, params.LocationId)
if params.WarehouseId != 0 { if params.WarehouseId != 0 {
db = db.Where("warehouse_id = ?", params.WarehouseId) db = db.Where("warehouse_id = ?", params.WarehouseId)
} }
if strings.TrimSpace(params.Search) != "" {
searchPattern := "%" + strings.TrimSpace(params.Search) + "%"
db = db.Where(
`(
EXISTS (
SELECT 1
FROM products p_search
WHERE p_search.id = product_warehouses.product_id
AND p_search.name ILIKE ?
)
OR w_scope.name ILIKE ?
)`,
searchPattern,
searchPattern,
)
}
if len(marketingTypes) > 0 { if len(marketingTypes) > 0 {
flagSet := make(map[string]struct{}) flagSet := make(map[string]struct{})
for _, t := range marketingTypes { for _, t := range marketingTypes {
@@ -49,6 +49,20 @@ func TestApplyWarehouseSelectionFilterSupportsLocationOnlyQuery(t *testing.T) {
assertUintIDs(t, ids, []uint{1, 2, 3}) assertUintIDs(t, ids, []uint{1, 2, 3})
} }
func TestApplyAvailableOnlyFilterRemovesZeroQtyRows(t *testing.T) {
db := setupProductWarehouseServiceTestDB(t)
var ids []uint
err := applyAvailableOnlyFilter(baseProductWarehouseSelectionQuery(db), true).
Order("product_warehouses.id").
Pluck("product_warehouses.id", &ids).Error
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertUintIDs(t, ids, []uint{1, 2, 4})
}
func setupProductWarehouseServiceTestDB(t *testing.T) *gorm.DB { func setupProductWarehouseServiceTestDB(t *testing.T) *gorm.DB {
t.Helper() t.Helper()
@@ -67,18 +81,19 @@ func setupProductWarehouseServiceTestDB(t *testing.T) *gorm.DB {
)`, )`,
`CREATE TABLE product_warehouses ( `CREATE TABLE product_warehouses (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
warehouse_id INTEGER NOT NULL warehouse_id INTEGER NOT NULL,
qty NUMERIC NULL
)`, )`,
`INSERT INTO warehouses (id, type, location_id, kandang_id, deleted_at) VALUES `INSERT INTO warehouses (id, type, location_id, kandang_id, deleted_at) VALUES
(1, 'KANDANG', 101, 11, NULL), (1, 'KANDANG', 101, 11, NULL),
(2, 'LOKASI', 101, NULL, NULL), (2, 'LOKASI', 101, NULL, NULL),
(3, 'KANDANG', 101, 12, NULL), (3, 'KANDANG', 101, 12, NULL),
(4, 'LOKASI', 102, NULL, NULL)`, (4, 'LOKASI', 102, NULL, NULL)`,
`INSERT INTO product_warehouses (id, warehouse_id) VALUES `INSERT INTO product_warehouses (id, warehouse_id, qty) VALUES
(1, 1), (1, 1, 10),
(2, 2), (2, 2, 20),
(3, 3), (3, 3, 0),
(4, 4)`, (4, 4, 15)`,
} }
for _, stmt := range statements { for _, stmt := range statements {
@@ -15,11 +15,13 @@ type Update struct {
type Query struct { type Query struct {
Page int `query:"page" 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"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Search string `query:"search" validate:"omitempty"`
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
LocationId uint `query:"location_id" validate:"omitempty,number,min=1"` LocationId uint `query:"location_id" validate:"omitempty,number,min=1"`
Flags string `query:"flags" validate:"omitempty"` Flags string `query:"flags" validate:"omitempty"`
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"` KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
AvailableOnly bool `query:"available_only"`
TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"` TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"`
StockMode string `query:"stock_mode" validate:"omitempty,oneof=exclude_chickin"` StockMode string `query:"stock_mode" validate:"omitempty,oneof=exclude_chickin"`
Type string `query:"type" validate:"omitempty"` Type string `query:"type" validate:"omitempty"`