package processor import ( "context" "fmt" "time" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/mappers" "apskel-pos-be/internal/models" "github.com/google/uuid" ) type ExpenseProcessor interface { CreateExpense(ctx context.Context, organizationID uuid.UUID, req *models.CreateExpenseRequest) (*models.ExpenseResponse, error) UpdateExpense(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdateExpenseRequest) (*models.ExpenseResponse, error) DeleteExpense(ctx context.Context, id, organizationID uuid.UUID) error GetExpenseByID(ctx context.Context, id, organizationID uuid.UUID) (*models.ExpenseResponse, error) ListExpenses(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.ExpenseResponse, int, error) } type ExpenseProcessorImpl struct { expenseRepo ExpenseRepository } func NewExpenseProcessorImpl(expenseRepo ExpenseRepository) *ExpenseProcessorImpl { return &ExpenseProcessorImpl{ expenseRepo: expenseRepo, } } func (p *ExpenseProcessorImpl) CreateExpense(ctx context.Context, organizationID uuid.UUID, req *models.CreateExpenseRequest) (*models.ExpenseResponse, error) { outletID, err := uuid.Parse(req.OutletID) if err != nil { return nil, fmt.Errorf("invalid outlet_id: %w", err) } transactionDate, err := time.Parse("2006-01-02", req.TransactionDate) if err != nil { return nil, fmt.Errorf("invalid transaction_date format, expected YYYY-MM-DD: %w", err) } expenseEntity := &entities.Expense{ OrganizationID: organizationID, OutletID: outletID, Receiver: req.Receiver, TransactionDate: transactionDate, CodeNumber: req.CodeNumber, Description: req.Description, Tax: req.Tax, Total: req.Total, } err = p.expenseRepo.Create(ctx, expenseEntity) if err != nil { return nil, fmt.Errorf("failed to create expense: %w", err) } for _, itemReq := range req.Items { chartOfAccountID, err := uuid.Parse(itemReq.ChartOfAccountID) if err != nil { return nil, fmt.Errorf("invalid chart_of_account_id for item: %w", err) } itemEntity := &entities.ExpenseItem{ ExpenseID: expenseEntity.ID, ChartOfAccountID: chartOfAccountID, Item: itemReq.Item, Description: itemReq.Description, Amount: itemReq.Amount, } err = p.expenseRepo.CreateItem(ctx, itemEntity) if err != nil { return nil, fmt.Errorf("failed to create expense item: %w", err) } } created, err := p.expenseRepo.GetByID(ctx, expenseEntity.ID) if err != nil { return mappers.ExpenseEntityToResponse(expenseEntity), nil } return mappers.ExpenseEntityToResponse(created), nil } func (p *ExpenseProcessorImpl) UpdateExpense(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdateExpenseRequest) (*models.ExpenseResponse, error) { expenseEntity, err := p.expenseRepo.GetByIDAndOrganizationID(ctx, id, organizationID) if err != nil { return nil, fmt.Errorf("expense not found: %w", err) } if req.Receiver != nil { expenseEntity.Receiver = *req.Receiver } if req.TransactionDate != nil { parsedDate, err := time.Parse("2006-01-02", *req.TransactionDate) if err != nil { return nil, fmt.Errorf("invalid transaction_date format, expected YYYY-MM-DD: %w", err) } expenseEntity.TransactionDate = parsedDate } if req.CodeNumber != nil { expenseEntity.CodeNumber = *req.CodeNumber } if req.OutletID != nil { outletID, err := uuid.Parse(*req.OutletID) if err != nil { return nil, fmt.Errorf("invalid outlet_id: %w", err) } expenseEntity.OutletID = outletID } if req.Description != nil { expenseEntity.Description = req.Description } if req.Tax != nil { expenseEntity.Tax = *req.Tax } if req.Total != nil { expenseEntity.Total = *req.Total } if req.Reserved1 != nil { expenseEntity.Reserved1 = req.Reserved1 } if req.Items != nil { err = p.expenseRepo.DeleteItemsByExpenseID(ctx, expenseEntity.ID) if err != nil { return nil, fmt.Errorf("failed to delete existing items: %w", err) } for _, itemReq := range req.Items { chartOfAccountID := uuid.Nil if itemReq.ChartOfAccountID != nil { chartOfAccountID, err = uuid.Parse(*itemReq.ChartOfAccountID) if err != nil { return nil, fmt.Errorf("invalid chart_of_account_id for item: %w", err) } } amount := 0.0 if itemReq.Amount != nil { amount = *itemReq.Amount } item := "" if itemReq.Item != nil { item = *itemReq.Item } itemEntity := &entities.ExpenseItem{ ExpenseID: expenseEntity.ID, ChartOfAccountID: chartOfAccountID, Item: item, Description: itemReq.Description, Amount: amount, } err = p.expenseRepo.CreateItem(ctx, itemEntity) if err != nil { return nil, fmt.Errorf("failed to create expense item: %w", err) } } } err = p.expenseRepo.Update(ctx, expenseEntity) if err != nil { return nil, fmt.Errorf("failed to update expense: %w", err) } updated, err := p.expenseRepo.GetByID(ctx, id) if err != nil { return mappers.ExpenseEntityToResponse(expenseEntity), nil } return mappers.ExpenseEntityToResponse(updated), nil } func (p *ExpenseProcessorImpl) DeleteExpense(ctx context.Context, id, organizationID uuid.UUID) error { _, err := p.expenseRepo.GetByIDAndOrganizationID(ctx, id, organizationID) if err != nil { return fmt.Errorf("expense not found: %w", err) } err = p.expenseRepo.Delete(ctx, id) if err != nil { return fmt.Errorf("failed to delete expense: %w", err) } return nil } func (p *ExpenseProcessorImpl) GetExpenseByID(ctx context.Context, id, organizationID uuid.UUID) (*models.ExpenseResponse, error) { expenseEntity, err := p.expenseRepo.GetByIDAndOrganizationID(ctx, id, organizationID) if err != nil { return nil, fmt.Errorf("expense not found: %w", err) } return mappers.ExpenseEntityToResponse(expenseEntity), nil } func (p *ExpenseProcessorImpl) ListExpenses(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.ExpenseResponse, int, error) { offset := (page - 1) * limit expenseEntities, total, err := p.expenseRepo.List(ctx, organizationID, filters, limit, offset) if err != nil { return nil, 0, fmt.Errorf("failed to list expenses: %w", err) } expenseResponses := mappers.ExpenseEntitiesToResponses(expenseEntities) totalPages := int((total + int64(limit) - 1) / int64(limit)) return expenseResponses, totalPages, nil }