apskel-pos-backend/internal/processor/chart_of_account_processor.go
Aditya Siregar 75ec5274d2 coa fix
2025-09-12 16:37:16 +07:00

202 lines
7.5 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 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
}