Use Case Documentation
Overview
This document outlines the use cases for the Expense Management module in the Bee O'clock panel service. Each use case represents a specific business operation that can be performed within the expense management system, following the CQRS pattern and Clean Architecture principles.
Architecture Overview
The expense management use cases are organized following the Clean Architecture pattern:
Application Layer: Use cases that orchestrate business operations
Domain Layer: Business rules and domain entities
Infrastructure Layer: Data persistence and external service integration
Each use case implements a specific interface and encapsulates the business logic for a particular operation while maintaining separation of concerns.
Command Use Cases
1. Create Expense Use Case
File: applications/panel.beeoclock/src/modules/expense/application/use-cases/create-expense.usecase.ts
Purpose: Creates a new expense record with validation, business rule enforcement, and data persistence.
import { Inject, Injectable } from '@nestjs/common';
import { ICreateExpenseUseCase } from '../interfaces/i.create-expense.use-case';
import { ExpenseDto } from '../dtos/expense.dto';
import { IExpenseRepository } from '../../domain/interfaces/i.expense.repository';
import { EXPENSE_REPOSITORY_TOKEN } from '../tokens/expense.repository.token';
import { ExpenseMapperToDomain } from '../mappers/expense.mapper-to-domain';
import { ExpenseMapperToDto } from '../mappers/expense.mapper-to-dto';
@Injectable()
export class CreateExpenseUseCase implements ICreateExpenseUseCase {
constructor(
@Inject(EXPENSE_REPOSITORY_TOKEN)
private readonly expenseRepository: IExpenseRepository
) {}
/**
* Execute expense creation with comprehensive validation and business rule enforcement
* @param expenseDto - Expense data to create
* @returns Promise resolving to created expense DTO
*/
async execute(expenseDto: ExpenseDto): Promise<ExpenseDto> {
// Convert DTO to domain entity
const expenseDomain = ExpenseMapperToDomain.map(expenseDto);
// Validate business rules
this.validateExpenseBusinessRules(expenseDomain);
// Calculate and validate total amount
this.validateAndCalculateTotalAmount(expenseDomain);
// Validate expense items
this.validateExpenseItems(expenseDomain);
// Persist the expense
const createdExpense = await this.expenseRepository.create(expenseDomain);
// Convert back to DTO and return
return ExpenseMapperToDto.map(createdExpense);
}
/**
* Validate core business rules for expense creation
* @param expense - Domain expense entity
*/
private validateExpenseBusinessRules(expense: IExpense): void {
// Validate expense date is not in the future beyond reasonable limits
const maxFutureDate = new Date();
maxFutureDate.setDate(maxFutureDate.getDate() + 30); // Allow 30 days in future
if (expense.expensedAt > maxFutureDate) {
throw new BadRequestException('Expense date cannot be more than 30 days in the future');
}
// Validate minimum expense amount
if (expense.totalValue.amount <= 0) {
throw new BadRequestException('Expense amount must be greater than zero');
}
// Validate maximum expense amount for fraud prevention
const maxAllowedAmount = 100000; // $100,000 limit
if (expense.totalValue.amount > maxAllowedAmount) {
throw new BadRequestException(`Expense amount exceeds maximum allowed limit of ${maxAllowedAmount}`);
}
}
/**
* Validate and calculate total amount from expense items
* @param expense - Domain expense entity
*/
private validateAndCalculateTotalAmount(expense: IExpense): void {
if (!expense.items || expense.items.length === 0) {
return; // Simple expense without line items
}
// Calculate total from line items
const calculatedTotal = expense.items.reduce(
(sum, item) => sum + item.itemValue.amount,
0
);
// Validate currency consistency across all items
const baseCurrency = expense.totalValue.currency;
const invalidCurrencyItems = expense.items.filter(
item => item.itemValue.currency !== baseCurrency
);
if (invalidCurrencyItems.length > 0) {
throw new BadRequestException(
`All expense items must use the same currency: ${baseCurrency}`
);
}
// Validate total amount matches sum of line items (with small tolerance for rounding)
const tolerance = 0.01; // 1 cent tolerance
const difference = Math.abs(calculatedTotal - expense.totalValue.amount);
if (difference > tolerance) {
throw new BadRequestException(
`Total expense amount (${expense.totalValue.amount}) does not match sum of line items (${calculatedTotal})`
);
}
}
/**
* Validate individual expense items
* @param expense - Domain expense entity
*/
private validateExpenseItems(expense: IExpense): void {
if (!expense.items) {
return;
}
expense.items.forEach((item, index) => {
// Validate item amount
if (item.itemValue.amount <= 0) {
throw new BadRequestException(`Expense item ${index + 1} amount must be greater than zero`);
}
// Validate item description
if (!item.description || item.description.trim().length === 0) {
throw new BadRequestException(`Expense item ${index + 1} must have a description`);
}
// Validate categories
if (!item.categories || item.categories.length === 0) {
throw new BadRequestException(`Expense item ${index + 1} must have at least one category`);
}
// Validate source information
if (!item.source || !item.source.sourceId) {
throw new BadRequestException(`Expense item ${index + 1} must have a valid source`);
}
});
}
}Business Rules:
Expense amounts must be positive and within reasonable limits
Future-dated expenses are limited to 30 days ahead
Currency consistency across all line items is enforced
Total amount must match the sum of line items (within rounding tolerance)
Each line item must have description, categories, and valid source
Maximum expense amount is enforced for fraud prevention
Error Scenarios:
Invalid expense amount (negative, zero, or exceeding limits)
Future date beyond allowed range
Currency mismatch between total and line items
Missing required fields (description, categories, source)
Total amount mismatch with line item sum
Query Use Cases
1. Get Paged Expenses Use Case
File: applications/panel.beeoclock/src/modules/expense/application/use-cases/get-paged-expenses.usecase.ts
Purpose: Retrieves a paginated list of expenses with advanced filtering capabilities.
Business Rules:
Page size is limited to 100 items for performance
Date ranges are limited to 1 year to prevent performance issues
Search phrases are limited to 100 characters
Maximum of 20 expense categories can be filtered at once
Start date must be before or equal to end date
Empty or invalid category names are filtered out
Query Capabilities:
Pagination with configurable page size
Text search across expense descriptions
Date range filtering (start and end dates)
Multiple expense category filtering
Sorting by expense date and creation time
2. Get Expense by ID Use Case
Purpose: Retrieves a specific expense by its unique identifier with permission validation.
Business Rules:
Expense ID must be a valid MongoDB ObjectId format
Expense must exist in the current tenant's scope
User must have read permissions for expense data
Category Management Use Cases
1. Create Expense Category Use Case
Purpose: Creates a new expense category with validation and uniqueness checks.
Business Rules:
Category names must be unique within a tenant
Category names are limited to 50 characters
Category names can only contain alphanumeric characters, hyphens, underscores, and spaces
Category descriptions are optional and limited to 500 characters
Category names cannot be empty or whitespace-only
2. Create Multiple Expense Categories Use Case
Purpose: Creates multiple expense categories in a single operation with batch validation.
Business Rules:
Maximum of 50 categories can be created in a single batch operation
No duplicate names are allowed within the batch
All category names must be unique across the tenant (including existing categories)
Each category must pass individual validation rules
Batch operations are atomic (all succeed or all fail)
3. Get Paged Expense Categories Use Case
Purpose: Retrieves a paginated list of expense categories for selection and management.
Business Rules:
Default page size is 20 categories (higher than expenses since categories are typically fewer)
Maximum page size is 100 categories
Categories are sorted alphabetically by name for consistency
Only active (non-deleted) categories are returned
Use Case Dependencies and Integration
Dependency Injection Tokens
Module Configuration
Use Case Interfaces
Error Handling Patterns
Common Error Types
Error Response Format
Transaction Management
Database Transactions
Performance Considerations
Caching Strategies
Batch Operations
Testing Patterns
Use Case Testing
Integration Testing
Monitoring and Observability
Use Case Metrics
Business Event Logging
This comprehensive use case documentation provides detailed insights into the business operations of the expense management system, including validation rules, error handling, performance considerations, and testing patterns. Each use case is designed to be maintainable, testable, and aligned with Clean Architecture principles.
Last updated
Was this helpful?