Files
warehouse-management-BE/internal/repositories/component_item_repository.go

239 lines
7.5 KiB
Go

package repositories
import (
"context"
"errors"
"fmt"
"time"
"wm-backend/global"
"wm-backend/internal/mapper"
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/rs/zerolog/log"
)
func CreateComponentItem(ctx context.Context, queries *db.Queries, body models.ComponentItem) (models.ComponentItem, error) {
result, err := queries.CreateComponentItem(ctx, *mapper.ToModelComponentItem(&body))
if err != nil {
log.Error().Err(err).Msg("Failed to create component item")
return models.ComponentItem{}, err
}
return *mapper.ToDomainComponentItem(result), nil
}
func GetComponentItemByID(ctx context.Context, queries *db.Queries, id int64) (models.ComponentItem, error) {
result, err := queries.GetComponentItemByID(ctx, id)
if err != nil {
return models.ComponentItem{}, err
}
return *mapper.ToDomainComponentItem(result), nil
}
func ListComponentItems(ctx context.Context, queries *db.Queries) ([]models.ComponentItem, error) {
results, err := queries.ListComponentItems(ctx)
if err != nil {
return nil, err
}
var items []models.ComponentItem
for _, r := range results {
items = append(items, *mapper.ToDomainComponentItem(r))
}
return items, nil
}
func UpdateComponentItem(ctx context.Context, queries *db.Queries, body models.ComponentItem) (models.ComponentItem, error) {
result, err := queries.UpdateComponentItem(ctx, *mapper.ToUpdateModelComponentItem(&body))
if err != nil {
return models.ComponentItem{}, err
}
return *mapper.ToDomainComponentItem(result), nil
}
func DeleteComponentItem(ctx context.Context, queries *db.Queries, id int64) (int64, error) {
rowsAffected, err := queries.DeleteComponentItem(ctx, id)
if err != nil {
return rowsAffected, err
}
return rowsAffected, nil
}
func FindComponentItems(ctx context.Context, queries *db.Queries, componentID int64) ([]models.FindComponentItemResult, error) {
results, err := queries.FindComponentItem(ctx, componentID)
if err != nil {
return nil, err
}
var items []models.FindComponentItemResult
for _, r := range results {
items = append(items, *mapper.ToDomainFindComponentItem(r))
}
return items, nil
}
// UpdateComponentItemStatus changes the status of a component item within a transaction.
// It handles three cases:
// - Case 1: changedQuantity is nil or >= quantity → change status of entire record
// - Case 2: changedQuantity < quantity, no existing record with target status → split into 2 records
// - Case 3: changedQuantity < quantity, existing record with target status → merge quantities
//
// Returns UpdateStatusResult with case-specific fields populated.
func UpdateComponentItemStatus(ctx context.Context, dbPool *pgxpool.Pool, id int64, newStatus db.ComponentItemStatusEnum,
changedQuantity *int32,
note string,
changedBy string) (result models.UpdateStatusResult, err error) {
// 1. Begin transaction
tx, err := dbPool.Begin(ctx)
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("begin tx: %w", err)
}
defer func() {
if err != nil {
tx.Rollback(ctx)
}
}()
txQueries := global.Queries.WithTx(tx)
// 2. Get existing item
existingItem, err := txQueries.GetComponentItemByID(ctx, id)
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("item not found: %w", err)
}
// 3. Check status unchanged
if existingItem.Status == newStatus {
tx.Rollback(ctx)
return models.UpdateStatusResult{}, fmt.Errorf("status unchanged")
}
// 4. Determine changed quantity (nil = change all)
var changedQty int32
if changedQuantity == nil {
changedQty = existingItem.Quantity
} else {
changedQty = *changedQuantity
}
// 5. Validate changed quantity
if changedQty <= 0 {
return models.UpdateStatusResult{}, fmt.Errorf("changed_quantity must be positive, got %d", changedQty)
}
if changedQty > existingItem.Quantity {
return models.UpdateStatusResult{}, fmt.Errorf("changed_quantity (%d) exceeds current quantity (%d)", changedQty, existingItem.Quantity)
}
// 6. Branch by case
if changedQty >= existingItem.Quantity {
// ── Case 1: Change status of entire record ──
updatedItem, err := txQueries.UpdateComponentItemStatus(ctx, db.UpdateComponentItemStatusParams{
ID: id,
Status: newStatus,
UpdatedAt: time.Now(),
})
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("update status: %w", err)
}
result.ComponentItem = *mapper.ToDomainComponentItem(updatedItem)
} else {
// ── Case 2 & 3: Partial change ──
// 6a. Reduce quantity on original record (or delete if qty becomes 0)
newQty := existingItem.Quantity - changedQty
if newQty == 0 {
_, err = txQueries.DeleteComponentItem(ctx, id)
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("delete zero-qty record: %w", err)
}
} else {
_, err = txQueries.UpdateComponentItemQuantity(ctx, db.UpdateComponentItemQuantityParams{
ID: id,
Quantity: newQty,
UpdatedAt: time.Now(),
})
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("update quantity: %w", err)
}
}
// 6b. Find existing record with same (component_id, container_id, new_status)
existingNewStatus, findErr := txQueries.GetComponentItemByComponentContainerStatus(ctx,
db.GetComponentItemByComponentContainerStatusParams{
ComponentID: existingItem.ComponentID,
ContainerID: existingItem.ContainerID,
Status: newStatus,
})
if findErr != nil {
if errors.Is(findErr, pgx.ErrNoRows) {
// ── Case 2: No existing record → create new one ──
newItem, createErr := txQueries.CreateComponentItem(ctx, db.CreateComponentItemParams{
ComponentID: existingItem.ComponentID,
ContainerID: existingItem.ContainerID,
Quantity: changedQty,
Status: newStatus,
Metadata: existingItem.Metadata,
CreatedAt: time.Now(),
})
if createErr != nil {
return models.UpdateStatusResult{}, fmt.Errorf("create new item: %w", createErr)
}
newID := newItem.ID
result.NewComponentItemID = &newID
} else {
return models.UpdateStatusResult{}, fmt.Errorf("find existing new status: %w", findErr)
}
} else {
// ── Case 3: Existing record → merge quantities ──
mergedQty := existingNewStatus.Quantity + changedQty
_, err = txQueries.UpdateComponentItemQuantity(ctx, db.UpdateComponentItemQuantityParams{
ID: existingNewStatus.ID,
Quantity: mergedQty,
UpdatedAt: time.Now(),
})
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("merge quantity: %w", err)
}
mergedID := existingNewStatus.ID
result.MergedComponentItemID = &mergedID
}
}
// 7. Insert status history record
history, err := txQueries.CreateComponentStatusHistory(ctx, db.CreateComponentStatusHistoryParams{
ComponentItemID: id,
OldStatus: db.NullComponentItemStatusEnum{
ComponentItemStatusEnum: existingItem.Status,
Valid: true,
},
NewStatus: newStatus,
ChangedQuantity: pgtype.Int4{
Int32: changedQty,
Valid: true,
},
Note: pgtype.Text{
String: note,
Valid: note != "",
},
ChangedBy: pgtype.Text{
String: changedBy,
Valid: true,
},
ChangedAt: time.Now(),
})
if err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("insert history: %w", err)
}
result.StatusHistory = *mapper.ToDomainComponentStatusHistory(history)
// 8. Commit transaction
if err = tx.Commit(ctx); err != nil {
return models.UpdateStatusResult{}, fmt.Errorf("commit: %w", err)
}
return result, nil
}