mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
fix(BE): sorting by date approval get all
This commit is contained in:
@@ -15,7 +15,7 @@ type ApprovalService interface {
|
|||||||
WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string
|
WorkflowSteps(workflow approvalutils.ApprovalWorkflowKey) map[approvalutils.ApprovalStep]string
|
||||||
WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool)
|
WorkflowStepName(workflow approvalutils.ApprovalWorkflowKey, step approvalutils.ApprovalStep) (string, bool)
|
||||||
CreateApproval(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, step approvalutils.ApprovalStep, action *entity.ApprovalAction, actorID uint, note *string) (*entity.Approval, error)
|
CreateApproval(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, step approvalutils.ApprovalStep, action *entity.ApprovalAction, actorID uint, note *string) (*entity.Approval, error)
|
||||||
List(ctx context.Context, module string, approvableID *uint, page, limit int, search string) ([]entity.Approval, int64, error)
|
List(ctx context.Context, module string, approvableID *uint, page, limit int, search string, sortByDate string) ([]entity.Approval, int64, error)
|
||||||
ListByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Approval, error)
|
ListByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.Approval, error)
|
||||||
LatestByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) (*entity.Approval, error)
|
LatestByTarget(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableID uint, modifier func(*gorm.DB) *gorm.DB) (*entity.Approval, error)
|
||||||
LatestByTargets(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableIDs []uint, modifier func(*gorm.DB) *gorm.DB) (map[uint]*entity.Approval, error)
|
LatestByTargets(ctx context.Context, workflow approvalutils.ApprovalWorkflowKey, approvableIDs []uint, modifier func(*gorm.DB) *gorm.DB) (map[uint]*entity.Approval, error)
|
||||||
@@ -70,9 +70,14 @@ func (s *approvalService) List(
|
|||||||
approvableID *uint,
|
approvableID *uint,
|
||||||
page, limit int,
|
page, limit int,
|
||||||
search string,
|
search string,
|
||||||
|
sortByDate string,
|
||||||
) ([]entity.Approval, int64, error) {
|
) ([]entity.Approval, int64, error) {
|
||||||
module = strings.TrimSpace(strings.ToUpper(module))
|
module = strings.TrimSpace(strings.ToUpper(module))
|
||||||
search = strings.TrimSpace(search)
|
search = strings.TrimSpace(search)
|
||||||
|
sortByDate = strings.TrimSpace(strings.ToUpper(sortByDate))
|
||||||
|
if sortByDate != "ASC" && sortByDate != "DESC" {
|
||||||
|
sortByDate = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
@@ -90,7 +95,7 @@ func (s *approvalService) List(
|
|||||||
func(db *gorm.DB) *gorm.DB {
|
func(db *gorm.DB) *gorm.DB {
|
||||||
query := db.
|
query := db.
|
||||||
Where("approvable_type = ?", module).
|
Where("approvable_type = ?", module).
|
||||||
Order("action_at DESC").
|
Order("action_at " + sortByDate).
|
||||||
Preload("ActionUser")
|
Preload("ActionUser")
|
||||||
|
|
||||||
if approvableID != nil {
|
if approvableID != nil {
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ func (u *ApprovalController) GetAll(c *fiber.Ctx) error {
|
|||||||
page := c.QueryInt("page", 1)
|
page := c.QueryInt("page", 1)
|
||||||
limit := c.QueryInt("limit", 10)
|
limit := c.QueryInt("limit", 10)
|
||||||
search := strings.TrimSpace(c.Query("search", ""))
|
search := strings.TrimSpace(c.Query("search", ""))
|
||||||
|
sortByDate := strings.TrimSpace(c.Query("sort_by_date", ""))
|
||||||
|
if sortByDate == "" {
|
||||||
|
sortByDate = "DESC"
|
||||||
|
} else {
|
||||||
|
sortByDate = strings.ToUpper(sortByDate)
|
||||||
|
if sortByDate != "ASC" && sortByDate != "DESC" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "sort_by_date must be either ASC or DESC")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
ModuleName: moduleName,
|
ModuleName: moduleName,
|
||||||
@@ -52,6 +61,7 @@ func (u *ApprovalController) GetAll(c *fiber.Ctx) error {
|
|||||||
Page: page,
|
Page: page,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Search: search,
|
Search: search,
|
||||||
|
SortByDate: sortByDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
records, totalResults, err := u.ApprovalService.List(
|
records, totalResults, err := u.ApprovalService.List(
|
||||||
@@ -61,6 +71,7 @@ func (u *ApprovalController) GetAll(c *fiber.Ctx) error {
|
|||||||
query.Page,
|
query.Page,
|
||||||
query.Limit,
|
query.Limit,
|
||||||
query.Search,
|
query.Search,
|
||||||
|
query.SortByDate,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ 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,gt=0"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
SortByDate string `query:"sort_by_date" validate:"omitempty,oneof=ASC DESC"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ func (s closingService) getClosingSummaryByKandang(ctx context.Context, projectF
|
|||||||
statusProject := "Belum Selesai"
|
statusProject := "Belum Selesai"
|
||||||
var approvalDate string
|
var approvalDate string
|
||||||
if s.ApprovalSvc != nil {
|
if s.ApprovalSvc != nil {
|
||||||
records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlockKandang.String(), &kandang.Id, 1, 1000, "")
|
records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlockKandang.String(), &kandang.Id, 1, 1000, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to fetch approvals for project flock kandang %d: %+v", kandang.Id, err)
|
s.Log.Errorf("Failed to fetch approvals for project flock kandang %d: %+v", kandang.Id, err)
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval data")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch approval data")
|
||||||
@@ -542,7 +542,7 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID
|
|||||||
return "", "Belum Selesai", nil
|
return "", "Belum Selesai", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlock.String(), &projectFlockID, 1, 1000, "")
|
records, _, err := s.ApprovalSvc.List(ctx, utils.ApprovalWorkflowProjectFlock.String(), &projectFlockID, 1, 1000, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,304 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
|
|
||||||
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"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test Transfer FIFO with Purchase as initial stockable
|
|
||||||
func TestTransferFIFO_PurchaseToTransfer(t *testing.T) {
|
|
||||||
db, fifoSvc := setupTransferFIFOTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Setup warehouses
|
|
||||||
sourcePW := createProductWarehouseRow(t, db, 100) // 100 qty from purchase
|
|
||||||
destPW := createProductWarehouseRow(t, db, 0) // 0 qty initially
|
|
||||||
|
|
||||||
// Step 1: Simulate Purchase - Replenish stock to source warehouse
|
|
||||||
purchaseStockableKey := fifo.StockableKey("PURCHASE_ITEMS")
|
|
||||||
if _, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: purchaseStockableKey,
|
|
||||||
StockableID: 1, // PurchaseItem ID
|
|
||||||
ProductWarehouseID: sourcePW.Id,
|
|
||||||
Quantity: 100,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("Failed to replenish from purchase: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify source warehouse has stock
|
|
||||||
assertWarehouseQuantity(t, db, sourcePW.Id, 100)
|
|
||||||
assertAllocationCount(t, db, 1) // 1 allocation from purchase
|
|
||||||
|
|
||||||
// Step 2: Create Transfer - will consume from source (usable) and replenish to dest (stockable)
|
|
||||||
|
|
||||||
// Register Transfer as Usable (source warehouse - STOCK_TRANSFER_OUT)
|
|
||||||
transferUsableKey := fifo.UsableKey("STOCK_TRANSFER_OUT")
|
|
||||||
if err := fifoSvc.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: transferUsableKey,
|
|
||||||
Table: "stock_transfer_details",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "source_product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
t.Fatalf("Failed to register STOCK_TRANSFER_OUT as Usable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register Transfer as Stockable (destination warehouse - STOCK_TRANSFER_IN)
|
|
||||||
transferStockableKey := fifo.StockableKey("STOCK_TRANSFER_IN")
|
|
||||||
if err := fifoSvc.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: transferStockableKey,
|
|
||||||
Table: "stock_transfer_details",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "dest_product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
t.Fatalf("Failed to register STOCK_TRANSFER_IN as Stockable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create transfer detail record
|
|
||||||
transferDetail := entity.StockTransferDetail{
|
|
||||||
Id: 1,
|
|
||||||
StockTransferId: 1,
|
|
||||||
ProductId: 1,
|
|
||||||
SourceProductWarehouseID: uint64Ptr(uint64(sourcePW.Id)),
|
|
||||||
DestProductWarehouseID: uint64Ptr(uint64(destPW.Id)),
|
|
||||||
UsageQty: 0,
|
|
||||||
PendingQty: 0,
|
|
||||||
TotalQty: 0,
|
|
||||||
TotalUsed: 0,
|
|
||||||
}
|
|
||||||
transferDetailID := uint(transferDetail.Id)
|
|
||||||
if err := db.Create(&transferDetail).Error; err != nil {
|
|
||||||
t.Fatalf("Failed to create transfer detail: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
transferQty := 50.0
|
|
||||||
|
|
||||||
// Consume from source warehouse (STOCK_TRANSFER_OUT)
|
|
||||||
consumeResult, err := fifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
|
||||||
UsableKey: "STOCK_TRANSFER_OUT",
|
|
||||||
UsableID: transferDetailID,
|
|
||||||
ProductWarehouseID: sourcePW.Id,
|
|
||||||
Quantity: transferQty,
|
|
||||||
AllowPending: false, // Don't allow pending
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to consume from source warehouse: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify consumption
|
|
||||||
if mathAbs(consumeResult.UsageQuantity-transferQty) > 1e-6 {
|
|
||||||
t.Fatalf("Expected usage quantity %.2f, got %.2f", transferQty, consumeResult.UsageQuantity)
|
|
||||||
}
|
|
||||||
if mathAbs(consumeResult.PendingQuantity) > 1e-6 {
|
|
||||||
t.Fatalf("Expected pending quantity 0, got %.2f", consumeResult.PendingQuantity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update transfer detail usable fields
|
|
||||||
if err := db.Model(&entity.StockTransferDetail{}).
|
|
||||||
Where("id = ?", transferDetail.Id).
|
|
||||||
Updates(map[string]interface{}{
|
|
||||||
"usage_qty": consumeResult.UsageQuantity,
|
|
||||||
"pending_qty": consumeResult.PendingQuantity,
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("Failed to update transfer detail usable fields: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify source warehouse decreased
|
|
||||||
assertWarehouseQuantity(t, db, sourcePW.Id, 50) // 100 - 50 = 50
|
|
||||||
|
|
||||||
// Verify allocation updated - should have 50 allocated to transfer
|
|
||||||
allocations := fetchAllocationsByUsable(t, db, "STOCK_TRANSFER_OUT", transferDetailID)
|
|
||||||
if len(allocations) != 1 {
|
|
||||||
t.Fatalf("Expected 1 allocation, got %d", len(allocations))
|
|
||||||
}
|
|
||||||
if mathAbs(allocations[0].Qty-transferQty) > 1e-6 {
|
|
||||||
t.Fatalf("Expected allocation qty %.2f, got %.2f", transferQty, allocations[0].Qty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replenish to destination warehouse (STOCK_TRANSFER_IN)
|
|
||||||
note := "Transfer #1"
|
|
||||||
replenishResult, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: "STOCK_TRANSFER_IN",
|
|
||||||
StockableID: transferDetailID,
|
|
||||||
ProductWarehouseID: destPW.Id,
|
|
||||||
Quantity: transferQty,
|
|
||||||
Note: ¬e,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to replenish to destination warehouse: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify replenishment
|
|
||||||
if mathAbs(replenishResult.AddedQuantity-transferQty) > 1e-6 {
|
|
||||||
t.Fatalf("Expected added quantity %.2f, got %.2f", transferQty, replenishResult.AddedQuantity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update transfer detail stockable fields
|
|
||||||
if err := db.Model(&entity.StockTransferDetail{}).
|
|
||||||
Where("id = ?", transferDetail.Id).
|
|
||||||
Updates(map[string]interface{}{
|
|
||||||
"total_qty": replenishResult.AddedQuantity,
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("Failed to update transfer detail stockable fields: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify destination warehouse increased
|
|
||||||
assertWarehouseQuantity(t, db, destPW.Id, transferQty)
|
|
||||||
|
|
||||||
// Verify new stockable allocation created
|
|
||||||
stockableAllocations := fetchAllocationsByStockable(t, db, "STOCK_TRANSFER_IN", transferDetailID)
|
|
||||||
if len(stockableAllocations) != 1 {
|
|
||||||
t.Fatalf("Expected 1 stockable allocation, got %d", len(stockableAllocations))
|
|
||||||
}
|
|
||||||
if mathAbs(stockableAllocations[0].Qty-transferQty) > 1e-6 {
|
|
||||||
t.Fatalf("Expected stockable allocation qty %.2f, got %.2f", transferQty, stockableAllocations[0].Qty)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("✅ Transfer FIFO test passed:")
|
|
||||||
t.Logf(" - Source warehouse: 100 → 50 (consumed %d)", int(transferQty))
|
|
||||||
t.Logf(" - Destination warehouse: 0 → %d (replenished)", int(transferQty))
|
|
||||||
t.Logf(" - Usable allocation: %.2f allocated to transfer", allocations[0].Qty)
|
|
||||||
t.Logf(" - Stockable allocation: %.2f available at destination", stockableAllocations[0].Qty)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup function for transfer FIFO test
|
|
||||||
func setupTransferFIFOTest(t *testing.T) (*gorm.DB, commonSvc.FifoService) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
|
||||||
&entity.ProductWarehouse{},
|
|
||||||
&entity.StockAllocation{},
|
|
||||||
&entity.StockTransferDetail{},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatalf("auto migrate entities: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stockAllocRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
|
||||||
fifoSvc := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
|
||||||
|
|
||||||
// Register Purchase as Stockable
|
|
||||||
purchaseStockableKey := fifo.StockableKey("PURCHASE_ITEMS")
|
|
||||||
if err := fifoSvc.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: purchaseStockableKey,
|
|
||||||
Table: "purchase_items",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
t.Fatalf("register purchase stockable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db, fifoSvc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
|
|
||||||
func createProductWarehouseRow(t *testing.T, db *gorm.DB, qty float64) entity.ProductWarehouse {
|
|
||||||
t.Helper()
|
|
||||||
pw := entity.ProductWarehouse{
|
|
||||||
ProductId: 1,
|
|
||||||
WarehouseId: 1,
|
|
||||||
Quantity: qty,
|
|
||||||
}
|
|
||||||
if err := db.Create(&pw).Error; err != nil {
|
|
||||||
t.Fatalf("create product warehouse: %v", err)
|
|
||||||
}
|
|
||||||
return pw
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertWarehouseQuantity(t *testing.T, db *gorm.DB, pwID uint, expected float64) {
|
|
||||||
t.Helper()
|
|
||||||
var pw entity.ProductWarehouse
|
|
||||||
if err := db.First(&pw, pwID).Error; err != nil {
|
|
||||||
t.Fatalf("fetch product warehouse %d: %v", pwID, err)
|
|
||||||
}
|
|
||||||
if mathAbs(pw.Quantity-expected) > 1e-6 {
|
|
||||||
t.Fatalf("expected warehouse quantity %.2f, got %.2f", expected, pw.Quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAllocationCount(t *testing.T, db *gorm.DB, expected int) {
|
|
||||||
t.Helper()
|
|
||||||
var count int64
|
|
||||||
if err := db.Model(&entity.StockAllocation{}).Count(&count).Error; err != nil {
|
|
||||||
t.Fatalf("count allocations: %v", err)
|
|
||||||
}
|
|
||||||
if int(count) != expected {
|
|
||||||
t.Fatalf("expected %d allocations, got %d", expected, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAllocationsByUsable(t *testing.T, db *gorm.DB, usableType string, usableID uint) []entity.StockAllocation {
|
|
||||||
t.Helper()
|
|
||||||
var allocations []entity.StockAllocation
|
|
||||||
if err := db.Where("usable_type = ? AND usable_id = ?", usableType, usableID).
|
|
||||||
Find(&allocations).Error; err != nil {
|
|
||||||
t.Fatalf("fetch allocations by usable: %v", err)
|
|
||||||
}
|
|
||||||
return allocations
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAllocationsByStockable(t *testing.T, db *gorm.DB, stockableType string, stockableID uint) []entity.StockAllocation {
|
|
||||||
t.Helper()
|
|
||||||
var allocations []entity.StockAllocation
|
|
||||||
if err := db.Where("stockable_type = ? AND stockable_id = ?", stockableType, stockableID).
|
|
||||||
Find(&allocations).Error; err != nil {
|
|
||||||
t.Fatalf("fetch allocations by stockable: %v", err)
|
|
||||||
}
|
|
||||||
return allocations
|
|
||||||
}
|
|
||||||
|
|
||||||
func floatPtr(f float64) *float64 {
|
|
||||||
return &f
|
|
||||||
}
|
|
||||||
|
|
||||||
func uint64Ptr(u uint64) *uint64 {
|
|
||||||
return &u
|
|
||||||
}
|
|
||||||
|
|
||||||
func mathAbs(f float64) float64 {
|
|
||||||
return math.Abs(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeKey(name string) string {
|
|
||||||
return strings.Map(func(r rune) rune {
|
|
||||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
return '_'
|
|
||||||
}, name)
|
|
||||||
}
|
|
||||||
@@ -1,446 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
|
|
||||||
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"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
||||||
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
|
||||||
servicePkg "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRecordingFIFO_CreatePendingWithoutStock(t *testing.T) {
|
|
||||||
db, svc, _, _ := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (pending) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 0, updated.UsageQty, "usage_qty should remain zero when no stock is available")
|
|
||||||
assertFloatEqual(t, 10, updated.PendingQty, "pending_qty should capture the entire request")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 0)
|
|
||||||
assertAllocationCount(t, db, 0)
|
|
||||||
|
|
||||||
assertAllocationCount(t, db, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecordingFIFO_EditReallocatesUsage(t *testing.T) {
|
|
||||||
db, svc, fifoSvc, stockableKey := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
lot := createStockLot(t, db, productWarehouse.Id)
|
|
||||||
|
|
||||||
if _, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: stockableKey,
|
|
||||||
StockableID: lot.Id,
|
|
||||||
ProductWarehouseID: productWarehouse.Id,
|
|
||||||
Quantity: 12,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("replenish failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (initial) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 2)
|
|
||||||
|
|
||||||
desired := 4.0
|
|
||||||
stock.UsageQty = &desired
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (edit) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 4, updated.UsageQty, "usage_qty should reflect edited request")
|
|
||||||
assertFloatEqual(t, 0, updated.PendingQty, "pending_qty should remain zero after downsize")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 8)
|
|
||||||
|
|
||||||
alloc := fetchSingleAllocation(t, db, stock.Id)
|
|
||||||
if alloc.Status != entity.StockAllocationStatusActive {
|
|
||||||
t.Fatalf("expected ACTIVE allocation, got %s", alloc.Status)
|
|
||||||
}
|
|
||||||
if mathAbs(alloc.Qty-4) > 1e-6 {
|
|
||||||
t.Fatalf("expected allocation qty 4, got %.3f", alloc.Qty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecordingFIFO_DeleteReleasesStock(t *testing.T) {
|
|
||||||
db, svc, fifoSvc, stockableKey := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
lot := createStockLot(t, db, productWarehouse.Id)
|
|
||||||
|
|
||||||
if _, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: stockableKey,
|
|
||||||
StockableID: lot.Id,
|
|
||||||
ProductWarehouseID: productWarehouse.Id,
|
|
||||||
Quantity: 10,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("replenish failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svc.ReleaseRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("releaseRecordingStocks failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 0, updated.UsageQty, "usage_qty should be cleared after delete")
|
|
||||||
assertFloatEqual(t, 0, updated.PendingQty, "pending_qty should be cleared after delete")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 10)
|
|
||||||
|
|
||||||
alloc := fetchSingleAllocation(t, db, stock.Id)
|
|
||||||
if alloc.Status != entity.StockAllocationStatusReleased {
|
|
||||||
t.Fatalf("expected allocation to be released, got %s", alloc.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helpers ----------------------------------------------------------------
|
|
||||||
|
|
||||||
type recordingStockTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
RecordingId uint `gorm:"column:recording_id;not null"`
|
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
|
||||||
UsageQty *float64 `gorm:"column:usage_qty"`
|
|
||||||
PendingQty *float64 `gorm:"column:pending_qty"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (recordingStockTable) TableName() string { return "recording_stocks" }
|
|
||||||
|
|
||||||
type productWarehouseTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductId uint `gorm:"column:product_id"`
|
|
||||||
WarehouseId uint `gorm:"column:warehouse_id"`
|
|
||||||
Quantity float64 `gorm:"column:quantity"`
|
|
||||||
CreatedBy uint `gorm:"column:created_by"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (productWarehouseTable) TableName() string { return "product_warehouses" }
|
|
||||||
|
|
||||||
type stockAllocationTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductWarehouseId uint `gorm:"not null"`
|
|
||||||
StockableType string `gorm:"size:100"`
|
|
||||||
StockableId uint
|
|
||||||
UsableType string `gorm:"size:100"`
|
|
||||||
UsableId uint
|
|
||||||
Qty float64 `gorm:"column:qty"`
|
|
||||||
Status string `gorm:"size:20"`
|
|
||||||
Note *string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
ReleasedAt *time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stockAllocationTable) TableName() string { return "stock_allocations" }
|
|
||||||
|
|
||||||
type testStockSource struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
|
||||||
TotalUsedQty float64 `gorm:"column:total_used_qty"`
|
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (testStockSource) TableName() string { return "test_fifo_stockables" }
|
|
||||||
|
|
||||||
func setupRecordingFIFOTableTest(t *testing.T) (*gorm.DB, servicePkg.RecordingFIFOIntegrationService, commonSvc.FifoService, fifo.StockableKey) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open sqlite: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
|
||||||
&recordingStockTable{},
|
|
||||||
&productWarehouseTable{},
|
|
||||||
&stockAllocationTable{},
|
|
||||||
&testStockSource{},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatalf("auto migrate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
|
||||||
&entity.ProductWarehouse{},
|
|
||||||
&entity.StockAllocation{},
|
|
||||||
&entity.RecordingStock{},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatalf("auto migrate entities: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stockAllocRepo := newFifoTestStockAllocationRepo(db)
|
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
|
||||||
fifoSvc := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
|
||||||
|
|
||||||
registerRecordingUsable(t, fifoSvc)
|
|
||||||
|
|
||||||
key := fifo.StockableKey(fmt.Sprintf("TEST_STOCKABLE_%s_%d", sanitizeKey(t.Name()), time.Now().UnixNano()))
|
|
||||||
if err := fifoSvc.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: key,
|
|
||||||
Table: "test_fifo_stockables",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("register stockable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := servicePkg.NewRecordingFIFOIntegrationService(
|
|
||||||
recordingRepo.NewRecordingRepository(db),
|
|
||||||
productWarehouseRepo,
|
|
||||||
fifoSvc,
|
|
||||||
)
|
|
||||||
|
|
||||||
return db, svc, fifoSvc, key
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerRecordingUsable(t *testing.T, fifoSvc commonSvc.FifoService) {
|
|
||||||
t.Helper()
|
|
||||||
err := fifoSvc.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyRecordingStock,
|
|
||||||
Table: "recording_stocks",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
t.Fatalf("register usable: %v", err)
|
|
||||||
}
|
|
||||||
if _, ok := fifo.Usable(fifo.UsableKeyRecordingStock); !ok {
|
|
||||||
t.Fatal("recording stock usable key not registered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createProductWarehouseRow(t *testing.T, db *gorm.DB, qty float64) entity.ProductWarehouse {
|
|
||||||
t.Helper()
|
|
||||||
pw := entity.ProductWarehouse{
|
|
||||||
ProductId: 1,
|
|
||||||
WarehouseId: 1,
|
|
||||||
Quantity: qty,
|
|
||||||
// CreatedBy: 1,
|
|
||||||
}
|
|
||||||
if err := db.Create(&pw).Error; err != nil {
|
|
||||||
t.Fatalf("create product warehouse: %v", err)
|
|
||||||
}
|
|
||||||
return pw
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRecordingStockRow(t *testing.T, db *gorm.DB, recordingID, productWarehouseID uint, desired float64) entity.RecordingStock {
|
|
||||||
t.Helper()
|
|
||||||
stock := entity.RecordingStock{
|
|
||||||
RecordingId: recordingID,
|
|
||||||
ProductWarehouseId: productWarehouseID,
|
|
||||||
UsageQty: floatPtr(0),
|
|
||||||
PendingQty: floatPtr(0),
|
|
||||||
}
|
|
||||||
if err := db.Create(&stock).Error; err != nil {
|
|
||||||
t.Fatalf("create recording stock: %v", err)
|
|
||||||
}
|
|
||||||
stock.UsageQty = floatPtr(desired)
|
|
||||||
return stock
|
|
||||||
}
|
|
||||||
|
|
||||||
func createStockLot(t *testing.T, db *gorm.DB, productWarehouseID uint) testStockSource {
|
|
||||||
t.Helper()
|
|
||||||
lot := testStockSource{
|
|
||||||
ProductWarehouseId: productWarehouseID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
if err := db.Create(&lot).Error; err != nil {
|
|
||||||
t.Fatalf("create stock lot: %v", err)
|
|
||||||
}
|
|
||||||
return lot
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchRecordingStock(t *testing.T, db *gorm.DB, id uint) entity.RecordingStock {
|
|
||||||
t.Helper()
|
|
||||||
var stock entity.RecordingStock
|
|
||||||
if err := db.First(&stock, id).Error; err != nil {
|
|
||||||
t.Fatalf("fetch recording stock: %v", err)
|
|
||||||
}
|
|
||||||
return stock
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchSingleAllocation(t *testing.T, db *gorm.DB, usableID uint) entity.StockAllocation {
|
|
||||||
t.Helper()
|
|
||||||
var alloc entity.StockAllocation
|
|
||||||
if err := db.Where("usable_id = ?", usableID).Order("created_at ASC").First(&alloc).Error; err != nil {
|
|
||||||
t.Fatalf("fetch allocation: %v", err)
|
|
||||||
}
|
|
||||||
return alloc
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAllocationCount(t *testing.T, db *gorm.DB, expected int64) {
|
|
||||||
t.Helper()
|
|
||||||
var count int64
|
|
||||||
if err := db.Model(&entity.StockAllocation{}).Count(&count).Error; err != nil {
|
|
||||||
t.Fatalf("count allocations: %v", err)
|
|
||||||
}
|
|
||||||
if count != expected {
|
|
||||||
t.Fatalf("expected %d allocations, got %d", expected, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertWarehouseQuantity(t *testing.T, db *gorm.DB, id uint, expected float64) {
|
|
||||||
t.Helper()
|
|
||||||
var pw entity.ProductWarehouse
|
|
||||||
if err := db.First(&pw, id).Error; err != nil {
|
|
||||||
t.Fatalf("fetch product warehouse: %v", err)
|
|
||||||
}
|
|
||||||
if mathAbs(pw.Quantity-expected) > 1e-6 {
|
|
||||||
t.Fatalf("expected warehouse quantity %.3f, got %.3f", expected, pw.Quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFloatEqual(t *testing.T, expected float64, value *float64, msg string) {
|
|
||||||
t.Helper()
|
|
||||||
if value == nil {
|
|
||||||
t.Fatalf("expected %s %.3f, got nil", msg, expected)
|
|
||||||
}
|
|
||||||
if mathAbs(*value-expected) > 1e-6 {
|
|
||||||
t.Fatalf("%s: expected %.3f, got %.3f", msg, expected, *value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func floatPtr(v float64) *float64 {
|
|
||||||
p := new(float64)
|
|
||||||
*p = v
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func mathAbs(v float64) float64 {
|
|
||||||
if v < 0 {
|
|
||||||
return -v
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeKey(name string) string {
|
|
||||||
if name == "" {
|
|
||||||
return "CASE"
|
|
||||||
}
|
|
||||||
clean := strings.Map(func(r rune) rune {
|
|
||||||
if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r >= 'a' && r <= 'z' {
|
|
||||||
return r - 32
|
|
||||||
}
|
|
||||||
return '_'
|
|
||||||
}, name)
|
|
||||||
return clean
|
|
||||||
}
|
|
||||||
|
|
||||||
type fifoTestStockAllocationRepo struct {
|
|
||||||
commonRepo.StockAllocationRepository
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFifoTestStockAllocationRepo(db *gorm.DB) commonRepo.StockAllocationRepository {
|
|
||||||
return &fifoTestStockAllocationRepo{
|
|
||||||
StockAllocationRepository: commonRepo.NewStockAllocationRepository(db),
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fifoTestStockAllocationRepo) PatchOne(
|
|
||||||
ctx context.Context,
|
|
||||||
id uint,
|
|
||||||
updates map[string]any,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) error {
|
|
||||||
base := r.db
|
|
||||||
|
|
||||||
setClauses := make([]string, 0, len(updates))
|
|
||||||
args := make([]any, 0, len(updates)+1)
|
|
||||||
for column, value := range updates {
|
|
||||||
colName := column
|
|
||||||
if strings.EqualFold(column, "quantity") {
|
|
||||||
colName = "qty"
|
|
||||||
}
|
|
||||||
setClauses = append(setClauses, fmt.Sprintf("%s = ?", colName))
|
|
||||||
args = append(args, value)
|
|
||||||
}
|
|
||||||
args = append(args, id)
|
|
||||||
sql := fmt.Sprintf("UPDATE stock_allocations SET %s WHERE id = ?", strings.Join(setClauses, ", "))
|
|
||||||
|
|
||||||
result := base.Exec(sql, args...)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fifoTestStockAllocationRepo) ReleaseByUsable(
|
|
||||||
ctx context.Context,
|
|
||||||
usableType string,
|
|
||||||
usableID uint,
|
|
||||||
note *string,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) error {
|
|
||||||
base := r.db
|
|
||||||
|
|
||||||
setClause := "status = ?, released_at = ?"
|
|
||||||
args := []any{entity.StockAllocationStatusReleased, time.Now()}
|
|
||||||
if note != nil {
|
|
||||||
setClause += ", note = ?"
|
|
||||||
args = append(args, *note)
|
|
||||||
}
|
|
||||||
args = append(args, usableType, usableID, entity.StockAllocationStatusActive)
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"UPDATE stock_allocations SET %s WHERE usable_type = ? AND usable_id = ? AND status = ?",
|
|
||||||
setClause,
|
|
||||||
)
|
|
||||||
|
|
||||||
result := base.Exec(sql, args...)
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user