apskel-pos-backend/internal/processor/account_processor.go
2025-09-12 15:37:19 +07:00

207 lines
6.8 KiB
Go

package processor
import (
"context"
"fmt"
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"github.com/google/uuid"
)
type AccountProcessor interface {
CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error)
GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error)
UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error)
DeleteAccount(ctx context.Context, id uuid.UUID) error
ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error)
GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error)
GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error)
UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error
GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error)
}
type AccountProcessorImpl struct {
accountRepo AccountRepository
chartOfAccountRepo ChartOfAccountRepository
}
func NewAccountProcessorImpl(accountRepo AccountRepository, chartOfAccountRepo ChartOfAccountRepository) *AccountProcessorImpl {
return &AccountProcessorImpl{
accountRepo: accountRepo,
chartOfAccountRepo: chartOfAccountRepo,
}
}
func (p *AccountProcessorImpl) CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error) {
// Get organization and outlet from context
appCtx := appcontext.FromGinContext(ctx)
organizationID := appCtx.OrganizationID
var outletID *uuid.UUID
if appCtx.OutletID != uuid.Nil {
outletID = &appCtx.OutletID
}
// Check if account number already exists for this organization/outlet
existing, err := p.accountRepo.GetByNumber(ctx, organizationID, req.Number, outletID)
if err == nil && existing != nil {
return nil, fmt.Errorf("account with number %s already exists", req.Number)
}
// Validate chart of account exists
_, err = p.chartOfAccountRepo.GetByID(ctx, req.ChartOfAccountID)
if err != nil {
return nil, fmt.Errorf("chart of account not found: %w", err)
}
entity := mappers.AccountCreateRequestToEntity(req, organizationID, outletID)
err = p.accountRepo.Create(ctx, entity)
if err != nil {
return nil, fmt.Errorf("failed to create account: %w", err)
}
return mappers.AccountEntityToResponse(entity), nil
}
func (p *AccountProcessorImpl) GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error) {
entity, err := p.accountRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("account not found: %w", err)
}
return mappers.AccountEntityToResponse(entity), nil
}
func (p *AccountProcessorImpl) UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error) {
entity, err := p.accountRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("account not found: %w", err)
}
// Check if new number already exists (if number is being updated)
if req.Number != nil && *req.Number != entity.Number {
existing, err := p.accountRepo.GetByNumber(ctx, entity.OrganizationID, *req.Number, entity.OutletID)
if err == nil && existing != nil {
return nil, fmt.Errorf("account with number %s already exists", *req.Number)
}
}
// Validate chart of account exists if provided
if req.ChartOfAccountID != nil {
_, err = p.chartOfAccountRepo.GetByID(ctx, *req.ChartOfAccountID)
if err != nil {
return nil, fmt.Errorf("chart of account not found: %w", err)
}
}
mappers.AccountUpdateRequestToEntity(entity, req)
err = p.accountRepo.Update(ctx, entity)
if err != nil {
return nil, fmt.Errorf("failed to update account: %w", err)
}
return mappers.AccountEntityToResponse(entity), nil
}
func (p *AccountProcessorImpl) DeleteAccount(ctx context.Context, id uuid.UUID) error {
entity, err := p.accountRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("account not found: %w", err)
}
// Prevent deletion of system accounts
if entity.IsSystem {
return fmt.Errorf("cannot delete system account")
}
err = p.accountRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete account: %w", err)
}
return nil
}
func (p *AccountProcessorImpl) ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error) {
// Get organization and outlet from context
appCtx := appcontext.FromGinContext(ctx)
organizationID := appCtx.OrganizationID
var outletID *uuid.UUID
if appCtx.OutletID != uuid.Nil {
outletID = &appCtx.OutletID
}
filterEntity := &entities.Account{
OrganizationID: organizationID,
OutletID: outletID,
ChartOfAccountID: *req.ChartOfAccountID,
AccountType: entities.AccountType(*req.AccountType),
}
entities, total, err := p.accountRepo.List(ctx, filterEntity)
if err != nil {
return nil, 0, fmt.Errorf("failed to list accounts: %w", err)
}
responses := make([]models.AccountResponse, len(entities))
for i, entity := range entities {
responses[i] = *mappers.AccountEntityToResponse(entity)
}
return responses, total, nil
}
func (p *AccountProcessorImpl) GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error) {
entities, err := p.accountRepo.GetByOrganization(ctx, organizationID, outletID)
if err != nil {
return nil, fmt.Errorf("failed to get accounts by organization: %w", err)
}
responses := make([]models.AccountResponse, len(entities))
for i, entity := range entities {
responses[i] = *mappers.AccountEntityToResponse(entity)
}
return responses, nil
}
func (p *AccountProcessorImpl) GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error) {
entities, err := p.accountRepo.GetByChartOfAccount(ctx, chartOfAccountID)
if err != nil {
return nil, fmt.Errorf("failed to get accounts by chart of account: %w", err)
}
responses := make([]models.AccountResponse, len(entities))
for i, entity := range entities {
responses[i] = *mappers.AccountEntityToResponse(entity)
}
return responses, nil
}
func (p *AccountProcessorImpl) UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error {
_, err := p.accountRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("account not found: %w", err)
}
err = p.accountRepo.UpdateBalance(ctx, id, amount)
if err != nil {
return fmt.Errorf("failed to update account balance: %w", err)
}
return nil
}
func (p *AccountProcessorImpl) GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error) {
balance, err := p.accountRepo.GetBalance(ctx, id)
if err != nil {
return 0, fmt.Errorf("failed to get account balance: %w", err)
}
return balance, nil
}