apskel-pos-backend/internal/processor/inventory_movement_processor.go

200 lines
7.2 KiB
Go
Raw Normal View History

2025-07-30 23:18:20 +07:00
package processor
import (
"context"
"fmt"
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"github.com/google/uuid"
)
type InventoryMovementProcessor interface {
CreateMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error)
GetMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error)
ListMovements(ctx context.Context, req *models.ListInventoryMovementsRequest) (*models.ListInventoryMovementsResponse, error)
GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error)
GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error)
GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error)
}
type InventoryMovementRepository interface {
Create(ctx context.Context, movement *entities.InventoryMovement) error
GetByID(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.InventoryMovement, int64, error)
GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) ([]*entities.InventoryMovement, int64, error)
GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.InventoryMovement, error)
GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.InventoryMovement, error)
Count(ctx context.Context, filters map[string]interface{}) (int64, error)
}
type InventoryMovementProcessorImpl struct {
movementRepo InventoryMovementRepository
inventoryRepo repository.InventoryRepository
}
func NewInventoryMovementProcessorImpl(
movementRepo InventoryMovementRepository,
inventoryRepo repository.InventoryRepository,
) *InventoryMovementProcessorImpl {
return &InventoryMovementProcessorImpl{
movementRepo: movementRepo,
inventoryRepo: inventoryRepo,
}
}
2025-08-03 23:55:51 +07:00
func (p *InventoryMovementProcessorImpl) CreateInventoryMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error) {
currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, req.ItemID, req.OutletID)
2025-07-30 23:18:20 +07:00
if err != nil {
return nil, fmt.Errorf("failed to get current inventory: %w", err)
}
previousQuantity := currentInventory.Quantity
newQuantity := previousQuantity + req.Quantity
movement := &entities.InventoryMovement{
OrganizationID: req.OrganizationID,
OutletID: req.OutletID,
2025-08-03 23:55:51 +07:00
ItemID: req.ItemID,
ItemType: req.ItemType,
2025-07-30 23:18:20 +07:00
MovementType: entities.InventoryMovementType(req.MovementType),
2025-08-03 23:55:51 +07:00
Quantity: float64(req.Quantity),
PreviousQuantity: float64(previousQuantity),
NewQuantity: float64(newQuantity),
2025-07-30 23:18:20 +07:00
UnitCost: req.UnitCost,
TotalCost: float64(req.Quantity) * req.UnitCost,
ReferenceType: (*entities.InventoryMovementReferenceType)(req.ReferenceType),
ReferenceID: req.ReferenceID,
OrderID: req.OrderID,
PaymentID: req.PaymentID,
UserID: req.UserID,
Reason: req.Reason,
Notes: req.Notes,
Metadata: entities.Metadata(req.Metadata),
}
if err := p.movementRepo.Create(ctx, movement); err != nil {
return nil, fmt.Errorf("failed to create inventory movement: %w", err)
}
movementWithRelations, err := p.movementRepo.GetWithRelations(ctx, movement.ID)
if err != nil {
return nil, fmt.Errorf("failed to retrieve created movement: %w", err)
}
response := mappers.InventoryMovementEntityToResponse(movementWithRelations)
return response, nil
}
2025-08-03 23:55:51 +07:00
func (p *InventoryMovementProcessorImpl) GetInventoryMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error) {
2025-07-30 23:18:20 +07:00
movement, err := p.movementRepo.GetWithRelations(ctx, id)
if err != nil {
return nil, fmt.Errorf("movement not found: %w", err)
}
response := mappers.InventoryMovementEntityToResponse(movement)
return response, nil
}
2025-08-03 23:55:51 +07:00
func (p *InventoryMovementProcessorImpl) ListInventoryMovements(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) (*models.PaginatedResponse[models.InventoryMovementResponse], error) {
// Set default values
if page < 1 {
page = 1
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
if limit < 1 {
limit = 10
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
if limit > 100 {
limit = 100
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
filters := make(map[string]interface{})
filters["organization_id"] = organizationID
if outletID != nil {
filters["outlet_id"] = *outletID
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
if search != "" {
filters["search"] = search
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
offset := (page - 1) * limit
movements, total, err := p.movementRepo.List(ctx, filters, limit, offset)
2025-07-30 23:18:20 +07:00
if err != nil {
return nil, fmt.Errorf("failed to list movements: %w", err)
}
// Convert to responses
movementResponses := make([]models.InventoryMovementResponse, len(movements))
for i, movement := range movements {
response := mappers.InventoryMovementEntityToResponse(movement)
if response != nil {
movementResponses[i] = *response
}
}
2025-08-03 23:55:51 +07:00
// Create paginated response
paginatedResponse := &models.PaginatedResponse[models.InventoryMovementResponse]{
Data: movementResponses,
Pagination: models.Pagination{
Page: page,
Limit: limit,
Total: total,
TotalPages: int((total + int64(limit) - 1) / int64(limit)),
},
2025-07-30 23:18:20 +07:00
}
2025-08-03 23:55:51 +07:00
return paginatedResponse, nil
2025-07-30 23:18:20 +07:00
}
func (p *InventoryMovementProcessorImpl) GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error) {
movements, total, err := p.movementRepo.GetByProductAndOutlet(ctx, productID, outletID, limit, offset)
if err != nil {
return nil, fmt.Errorf("failed to get movements by product and outlet: %w", err)
}
movementResponses := make([]models.InventoryMovementResponse, len(movements))
for i, movement := range movements {
response := mappers.InventoryMovementEntityToResponse(movement)
if response != nil {
movementResponses[i] = *response
}
}
totalPages := int(total) / limit
if int(total)%limit > 0 {
totalPages++
}
return &models.ListInventoryMovementsResponse{
Movements: movementResponses,
TotalCount: int(total),
Page: 1,
Limit: limit,
TotalPages: totalPages,
}, nil
}
func (p *InventoryMovementProcessorImpl) GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error) {
movements, err := p.movementRepo.GetByOrderID(ctx, orderID)
if err != nil {
return nil, fmt.Errorf("failed to get movements by order ID: %w", err)
}
responses := mappers.InventoryMovementEntitiesToResponses(movements)
return responses, nil
}
func (p *InventoryMovementProcessorImpl) GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error) {
movements, err := p.movementRepo.GetByPaymentID(ctx, paymentID)
if err != nil {
return nil, fmt.Errorf("failed to get movements by payment ID: %w", err)
}
responses := mappers.InventoryMovementEntitiesToResponses(movements)
return responses, nil
}