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 ChartOfAccountProcessor interface { CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error) UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error) GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error } type ChartOfAccountProcessorImpl struct { chartOfAccountRepo ChartOfAccountRepository chartOfAccountTypeRepo ChartOfAccountTypeRepository } func NewChartOfAccountProcessorImpl(chartOfAccountRepo ChartOfAccountRepository, chartOfAccountTypeRepo ChartOfAccountTypeRepository) *ChartOfAccountProcessorImpl { return &ChartOfAccountProcessorImpl{ chartOfAccountRepo: chartOfAccountRepo, chartOfAccountTypeRepo: chartOfAccountTypeRepo, } } func (p *ChartOfAccountProcessorImpl) CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, 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 code already exists for this organization/outlet existing, err := p.chartOfAccountRepo.GetByCode(ctx, organizationID, req.Code, outletID) if err == nil && existing != nil { return nil, fmt.Errorf("chart of account with code %s already exists", req.Code) } // Validate parent exists if provided if req.ParentID != nil { _, err := p.chartOfAccountRepo.GetByID(ctx, *req.ParentID) if err != nil { return nil, fmt.Errorf("parent chart of account not found: %w", err) } } // Validate chart of account type exists _, err = p.chartOfAccountTypeRepo.GetByID(ctx, req.ChartOfAccountTypeID) if err != nil { return nil, fmt.Errorf("chart of account type not found: %w", err) } entity := mappers.ChartOfAccountCreateRequestToEntity(req, organizationID, outletID) err = p.chartOfAccountRepo.Create(ctx, entity) if err != nil { return nil, fmt.Errorf("failed to create chart of account: %w", err) } return mappers.ChartOfAccountEntityToResponse(entity), nil } func (p *ChartOfAccountProcessorImpl) GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error) { entity, err := p.chartOfAccountRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("chart of account not found: %w", err) } return mappers.ChartOfAccountEntityToResponse(entity), nil } func (p *ChartOfAccountProcessorImpl) UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) { entity, err := p.chartOfAccountRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("chart of account not found: %w", err) } // Check if new code already exists (if code is being updated) if req.Code != nil && *req.Code != entity.Code { existing, err := p.chartOfAccountRepo.GetByCode(ctx, entity.OrganizationID, *req.Code, &entity.OutletID) if err == nil && existing != nil { return nil, fmt.Errorf("chart of account with code %s already exists", *req.Code) } } // Validate parent exists if provided if req.ParentID != nil { _, err := p.chartOfAccountRepo.GetByID(ctx, *req.ParentID) if err != nil { return nil, fmt.Errorf("parent chart of account not found: %w", err) } } // Validate chart of account type exists if provided if req.ChartOfAccountTypeID != nil { _, err = p.chartOfAccountTypeRepo.GetByID(ctx, *req.ChartOfAccountTypeID) if err != nil { return nil, fmt.Errorf("chart of account type not found: %w", err) } } mappers.ChartOfAccountUpdateRequestToEntity(entity, req) err = p.chartOfAccountRepo.Update(ctx, entity) if err != nil { return nil, fmt.Errorf("failed to update chart of account: %w", err) } return mappers.ChartOfAccountEntityToResponse(entity), nil } func (p *ChartOfAccountProcessorImpl) DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error { entity, err := p.chartOfAccountRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("chart of account not found: %w", err) } // Prevent deletion of system accounts if entity.IsSystem { return fmt.Errorf("cannot delete system chart of account") } err = p.chartOfAccountRepo.Delete(ctx, id) if err != nil { return fmt.Errorf("failed to delete chart of account: %w", err) } return nil } func (p *ChartOfAccountProcessorImpl) ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error) { filterEntity := &entities.ChartOfAccount{ OrganizationID: req.OrganizationID, OutletID: req.OutletID, ParentID: req.ParentID, } // Apply optional filter for ChartOfAccountTypeID if req.ChartOfAccountTypeID != nil { filterEntity.ChartOfAccountTypeID = *req.ChartOfAccountTypeID } entities, total, err := p.chartOfAccountRepo.List(ctx, filterEntity) if err != nil { return nil, 0, fmt.Errorf("failed to list chart of accounts: %w", err) } responses := make([]models.ChartOfAccountResponse, len(entities)) for i, entity := range entities { responses[i] = *mappers.ChartOfAccountEntityToResponse(entity) } return responses, total, nil } func (p *ChartOfAccountProcessorImpl) GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) { entities, err := p.chartOfAccountRepo.GetByOrganization(ctx, organizationID, outletID) if err != nil { return nil, fmt.Errorf("failed to get chart of accounts by organization: %w", err) } responses := make([]models.ChartOfAccountResponse, len(entities)) for i, entity := range entities { responses[i] = *mappers.ChartOfAccountEntityToResponse(entity) } return responses, nil } func (p *ChartOfAccountProcessorImpl) GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) { entities, err := p.chartOfAccountRepo.GetByType(ctx, organizationID, chartOfAccountTypeID, outletID) if err != nil { return nil, fmt.Errorf("failed to get chart of accounts by type: %w", err) } responses := make([]models.ChartOfAccountResponse, len(entities)) for i, entity := range entities { responses[i] = *mappers.ChartOfAccountEntityToResponse(entity) } return responses, nil } func (p *ChartOfAccountProcessorImpl) CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error { // This method will be implemented to create default chart of accounts // based on the JSON template when a new organization/outlet is created // For now, we'll return nil as this is a placeholder return nil }