InfraForge Docs

InfraNotes CORE · v1.35.9

Welcome

Select a document from the sidebar to read it.

Budget-Expense Integration

This document provides comprehensive information for NextJS frontend developers on how to integrate with the InfraNotes budget-expense system. This integration allows you to allocate expenses to budgets, track usage, and manage budget allocations.

Overview

The budget-expense integration provides:

  1. Budget CRUD Operations: Create, read, update, and delete budgets
  2. Expense Allocation: Allocate individual or multiple expenses to budgets
  3. Budget Tracking: Real-time tracking of budget usage and remaining amounts
  4. Budget Allocations: Hierarchical budget allocation from parent to child budgets
  5. Alert System: Automated alerts when budgets reach thresholds
  6. Category Management: Track budget usage by expense categories
  7. Usage Reports: Comprehensive budget usage analytics

Authentication

All endpoints require Bearer token authentication. Include the token in the Authorization header:

Authorization: Bearer <your-access-token>

Budget Management

Create Budget

POST /budgets

Request Body:

{
  "name": "Q1 Marketing Budget",
  "description": "First quarter marketing activities budget",
  "type": "department",
  "status": "active",
  "period": "quarterly",
  "start_date": "2025-01-01T00:00:00Z",
  "end_date": "2025-03-31T23:59:59Z",
  "total_amount": 50000.00,
  "currency": "USD",
  "team_id": 123,
  "department_id": 456,
  "project_id": 789,
  "parent_budget_id": null,
  "auto_allocate": false,
  "alert_threshold": 80,
  "categories": [
    {
      "category_id": 1,
      "category_name": "Advertising",
      "amount": 30000.00,
      "currency": "USD"
    },
    {
      "category_id": 2,
      "category_name": "Events",
      "amount": 20000.00,
      "currency": "USD"
    }
  ]
}

Response:

{
  "id": 1,
  "name": "Q1 Marketing Budget",
  "description": "First quarter marketing activities budget",
  "type": "department",
  "status": "active",
  "period": "quarterly",
  "start_date": "2025-01-01T00:00:00Z",
  "end_date": "2025-03-31T23:59:59Z",
  "total_amount": 50000.00,
  "currency": "USD",
  "used_amount": 0.00,
  "remaining_amount": 50000.00,
  "user_id": 2,
  "team_id": 123,
  "department_id": 456,
  "project_id": 789,
  "parent_budget_id": null,
  "auto_allocate": false,
  "alert_threshold": 80,
  "categories": [
    {
      "id": 1,
      "budget_id": 1,
      "category_id": 1,
      "category_name": "Advertising",
      "amount": 30000.00,
      "used_amount": 0.00,
      "currency": "USD",
      "created_at": "2025-01-13T10:30:00Z",
      "updated_at": "2025-01-13T10:30:00Z"
    }
  ],
  "created_at": "2025-01-13T10:30:00Z",
  "updated_at": "2025-01-13T10:30:00Z"
}

List Budgets

GET /budgets

Query Parameters:

  • type: Filter by budget type (team, department, project, individual)
  • status: Filter by status (active, inactive, archived, draft)
  • period: Filter by period (monthly, quarterly, annual, custom)
  • team_id: Filter by team ID
  • department_id: Filter by department ID
  • project_id: Filter by project ID
  • parent_budget_id: Filter by parent budget ID
  • start_date: Filter by start date (ISO format)
  • end_date: Filter by end date (ISO format)
  • limit: Number of results per page
  • offset: Number of results to skip

Response:

[
  {
    "id": 1,
    "name": "Q1 Marketing Budget",
    "description": "First quarter marketing activities budget",
    "type": "department",
    "status": "active",
    "period": "quarterly",
    "start_date": "2025-01-01T00:00:00Z",
    "end_date": "2025-03-31T23:59:59Z",
    "total_amount": 50000.00,
    "currency": "USD",
    "used_amount": 12500.00,
    "remaining_amount": 37500.00,
    "user_id": 2,
    "team_id": 123,
    "created_at": "2025-01-13T10:30:00Z",
    "updated_at": "2025-01-13T11:45:00Z"
  }
]

Get Budget by ID

GET /budgets/{id}

Response: Same as create budget response with current usage data.

Update Budget

PUT /budgets/{id}

Request Body: Same as create budget (all fields optional for updates).

Delete Budget

DELETE /budgets/{id}

Response: 204 No Content

Get Budget Usage Report

GET /budgets/{id}/usage-report

Response:

{
  "budget_id": 1,
  "name": "Q1 Marketing Budget",
  "total_amount": 50000.00,
  "used_amount": 12500.00,
  "remaining_amount": 37500.00,
  "usage_percentage": 25.0,
  "category_usage": {
    "Advertising": {
      "category_id": 1,
      "category_name": "Advertising",
      "amount": 30000.00,
      "used_amount": 8000.00,
      "usage_percentage": 26.67
    }
  },
  "time_series_usage": [
    {
      "date": "2025-01-01T00:00:00Z",
      "used_amount": 0.00,
      "budget_amount": 50000.00,
      "expense_count": 0
    },
    {
      "date": "2025-01-13T00:00:00Z",
      "used_amount": 12500.00,
      "budget_amount": 50000.00,
      "expense_count": 5
    }
  ]
}

Budget-Expense Operations

Get Budget Expenses

GET /budgets/{id}/expenses

Query Parameters:

  • category: Filter by expense category
  • type: Filter by expense type (expense, income)
  • min_amount: Minimum amount filter
  • max_amount: Maximum amount filter
  • start_date: Start date filter (YYYY-MM-DD)
  • end_date: End date filter (YYYY-MM-DD)

Response:

{
  "budget": {
    "id": 1,
    "name": "Q1 Marketing Budget",
    "total_amount": 50000.00,
    "used_amount": 12500.00,
    "remaining_amount": 37500.00,
    "currency": "USD"
  },
  "expenses": [
    {
      "id": 123,
      "amount": 2500.00,
      "type": "expense",
      "category": "Advertising",
      "category_id": 1,
      "description": "Google Ads campaign",
      "notes": "Monthly advertising spend",
      "user_id": 2,
      "shared": false,
      "created_at": "2025-01-12T14:30:00Z",
      "updated_at": "2025-01-12T14:30:00Z",
      "receipt_path": {
        "String": "",
        "Valid": false
      },
      "budget_id": 1,
      "budget_name": "Q1 Marketing Budget",
      "budget_allocation_type": "manual",
      "budget_allocation_timestamp": "2025-01-12T14:30:00Z"
    }
  ]
}

Allocate Expense to Budget

POST /budgets/{id}/expenses

Request Body:

{
  "expense_id": 123,
  "allocation_type": "manual"
}

Allocation Types:

  • manual: Manually allocated by user
  • automatic: Automatically allocated by system
  • categories: Allocated based on category matching
  • project: Allocated based on project association

Response:

{
  "id": 123,
  "amount": 2500.00,
  "type": "expense",
  "category": "Advertising",
  "category_id": 1,
  "description": "Google Ads campaign",
  "notes": "Monthly advertising spend",
  "user_id": 2,
  "shared": false,
  "created_at": "2025-01-12T14:30:00Z",
  "updated_at": "2025-01-12T14:35:00Z",
  "receipt_path": {
    "String": "",
    "Valid": false
  },
  "budget_id": 1,
  "budget_name": "Q1 Marketing Budget",
  "budget_allocation_type": "manual",
  "budget_allocation_timestamp": "2025-01-12T14:35:00Z"
}

Remove Expense from Budget

DELETE /budgets/{id}/expenses/{expenseId}

Response:

{
  "message": "Expense removed from budget successfully"
}

Batch Allocate Expenses

POST /budgets/{id}/expenses/batch

Request Body:

{
  "expense_ids": [123, 124, 125],
  "allocation_type": "categories"
}

Response:

{
  "successful": [123, 124],
  "failed": [
    {
      "expense_id": 125,
      "error": "Expense not found"
    }
  ],
  "total": 3
}

Auto-Allocate Expenses

POST /budgets/{id}/expenses/auto-allocate

Request Body:

{
  "date_range": {
    "start_date": "2025-01-01T00:00:00Z",
    "end_date": "2025-03-31T23:59:59Z"
  },
  "categories": ["Advertising", "Events"],
  "max_amount": 5000.00
}

Response:

{
  "allocated": [123, 124, 125],
  "skipped": [
    {
      "expense_id": 126,
      "reason": "Category not in criteria"
    }
  ],
  "total": 4
}

Budget Allocations

Create Budget Allocation

POST /budgets/{id}/allocations

Request Body:

{
  "child_budget_id": 2,
  "amount": 15000.00,
  "currency": "USD",
  "description": "Allocation for Q1 digital marketing"
}

Response:

{
  "id": 1,
  "parent_budget_id": 1,
  "child_budget_id": 2,
  "amount": 15000.00,
  "currency": "USD",
  "description": "Allocation for Q1 digital marketing",
  "allocated_by": 2,
  "created_at": "2025-01-13T10:30:00Z",
  "updated_at": "2025-01-13T10:30:00Z"
}

Get Budget Allocations

GET /budgets/{id}/allocations

Response:

[
  {
    "id": 1,
    "parent_budget_id": 1,
    "child_budget_id": 2,
    "amount": 15000.00,
    "currency": "USD",
    "description": "Allocation for Q1 digital marketing",
    "allocated_by": 2,
    "created_at": "2025-01-13T10:30:00Z",
    "updated_at": "2025-01-13T10:30:00Z"
  }
]

Update Budget Allocation

PUT /budgets/{id}/allocations/{allocationId}

Request Body: Same as create allocation (all fields optional).

Delete Budget Allocation

DELETE /budgets/{id}/allocations/{allocationId}

Response: 204 No Content

Budget Alerts

Get Budget Alerts

GET /budgets/{id}/alerts

Query Parameters:

  • status: Filter by alert status
  • type: Filter by alert type
  • start_date: Start date filter
  • end_date: End date filter

Response:

[
    {
      "id": 1,
      "budget_id": 1,
      "type": "threshold",
    "message": "Budget 'Q1 Marketing Budget' has reached 80% of its total allocation",
      "threshold": 80,
    "current_usage": 82.5,
      "is_read": false,
    "created_at": "2025-01-13T15:23:00Z"
    }
  ]

Update Alert Settings

PUT /budgets/{id}/alerts/settings

Request Body:

{
  "alert_threshold": 85,
  "enabled": true
}

Response:

{
    "id": 1,
  "name": "Q1 Marketing Budget",
    "alert_threshold": 85,
  "auto_allocate": false,
  "updated_at": "2025-01-13T15:30:00Z"
}

Run Alert Check

POST /budgets/{id}/alerts/check

Response:

{
      "budget_id": 1,
  "alerts_generated": 1,
  "threshold_alerts": 1,
  "overspent_alerts": 0,
  "last_check": "2025-01-13T15:35:00Z"
}

Budget Categories

Get Budget Categories

GET /budgets/{id}/categories

Response:

{
  "categories": [
    {
      "id": 1,
      "budget_id": 1,
      "category_id": 5,
      "category_name": "Office Supplies",
      "amount": 500.00,
      "used_amount": 150.00,
      "remaining_amount": 350.00,
      "currency": "USD",
      "description": "Office supplies allocation",
      "created_at": "2025-01-15T10:00:00Z",
      "updated_at": "2025-01-15T10:00:00Z"
    }
  ]
}

Create Budget Category

POST /budgets/{id}/categories

Request Body:

{
  "category_id": 5,
  "category_name": "Office Supplies",
  "amount": 500.00,
  "currency": "USD",
  "description": "Office supplies allocation"
}

Response:

{
  "id": 1,
  "budget_id": 1,
  "category_id": 5,
  "category_name": "Office Supplies",
  "amount": 500.00,
  "used_amount": 0.00,
  "remaining_amount": 500.00,
  "currency": "USD",
  "description": "Office supplies allocation",
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:00:00Z"
}

Update Budget Category

PUT /budgets/{id}/categories/{categoryId}

Request Body:

{
  "amount": 750.00,
  "description": "Updated office supplies allocation"
}

Response:

{
  "id": 1,
  "budget_id": 1,
  "category_id": 5,
  "category_name": "Office Supplies", 
  "amount": 750.00,
  "used_amount": 150.00,
  "remaining_amount": 600.00,
  "currency": "USD",
  "description": "Updated office supplies allocation",
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T12:00:00Z"
}

Delete Budget Category

DELETE /budgets/{id}/categories/{categoryId}

Response:

{
  "message": "Category deleted successfully"
}

NextJS Implementation Guide

Setting Up API Client

// lib/api/budget.ts
import { ApiClient } from './client';

export interface Budget {
  id: number;
  name: string;
  description: string;
  type: 'team' | 'department' | 'project' | 'individual';
  status: 'active' | 'inactive' | 'archived' | 'draft';
  period: 'monthly' | 'quarterly' | 'annual' | 'custom';
  start_date: string;
  end_date: string;
  total_amount: number;
  currency: string;
  used_amount: number;
  remaining_amount: number;
  user_id: number;
  team_id?: number;
  department_id?: number;
  project_id?: number;
  parent_budget_id?: number;
  auto_allocate: boolean;
  alert_threshold: number;
  categories?: BudgetCategory[];
  created_at: string;
  updated_at: string;
}

export interface BudgetCategory {
  id: number;
  budget_id: number;
  category_id?: number;
  category_name: string;
  amount: number;
  used_amount: number;
  currency: string;
  created_at: string;
  updated_at: string;
}

export interface ExpenseAllocation {
  expense_id: number;
  allocation_type: 'manual' | 'automatic' | 'categories' | 'project';
}

export interface BudgetExpensesResponse {
  budget: Budget;
  expenses: Expense[];
}

export class BudgetApi {
  constructor(private client: ApiClient) {}

  async createBudget(budget: Partial<Budget>): Promise<Budget> {
    return this.client.post<Budget>('/budgets', budget);
  }

  async listBudgets(filters?: {
    type?: string;
    status?: string;
    period?: string;
    team_id?: number;
    department_id?: number;
    project_id?: number;
    parent_budget_id?: number;
    start_date?: string;
    end_date?: string;
    limit?: number;
    offset?: number;
  }): Promise<Budget[]> {
    const params = new URLSearchParams();
    if (filters) {
      Object.entries(filters).forEach(([key, value]) => {
        if (value !== undefined) {
          params.append(key, value.toString());
        }
      });
    }
    return this.client.get<Budget[]>(`/budgets?${params}`);
  }

  async getBudget(id: number): Promise<Budget> {
    return this.client.get<Budget>(`/budgets/${id}`);
  }

  async updateBudget(id: number, budget: Partial<Budget>): Promise<Budget> {
    return this.client.put<Budget>(`/budgets/${id}`, budget);
  }

  async deleteBudget(id: number): Promise<void> {
    return this.client.delete(`/budgets/${id}`);
  }

  async getBudgetExpenses(id: number, filters?: {
    category?: string;
    type?: string;
    min_amount?: number;
    max_amount?: number;
    start_date?: string;
    end_date?: string;
  }): Promise<BudgetExpensesResponse> {
    const params = new URLSearchParams();
    if (filters) {
      Object.entries(filters).forEach(([key, value]) => {
        if (value !== undefined) {
          params.append(key, value.toString());
        }
      });
    }
    return this.client.get<BudgetExpensesResponse>(`/budgets/${id}/expenses?${params}`);
  }

  async allocateExpense(budgetId: number, allocation: ExpenseAllocation): Promise<Expense> {
    return this.client.post<Expense>(`/budgets/${budgetId}/expenses`, allocation);
  }

  async removeExpense(budgetId: number, expenseId: number): Promise<{ message: string }> {
    return this.client.delete(`/budgets/${budgetId}/expenses/${expenseId}`);
  }

  async batchAllocateExpenses(budgetId: number, request: {
    expense_ids: number[];
    allocation_type: string;
  }): Promise<{
    successful: number[];
    failed: { expense_id: number; error: string }[];
    total: number;
  }> {
    return this.client.post(`/budgets/${budgetId}/expenses/batch`, request);
  }

  async autoAllocateExpenses(budgetId: number, criteria: {
    date_range: {
      start_date: string;
      end_date: string;
    };
    categories?: string[];
    max_amount?: number;
  }): Promise<{
    allocated: number[];
    skipped: { expense_id: number; reason: string }[];
    total: number;
  }> {
    return this.client.post(`/budgets/${budgetId}/expenses/auto-allocate`, criteria);
  }

  async getUsageReport(id: number): Promise<BudgetUsageReport> {
    return this.client.get<BudgetUsageReport>(`/budgets/${id}/usage-report`);
  }
}

React Hook for Budget Management

// hooks/useBudgets.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { BudgetApi } from '../lib/api/budget';

export function useBudgets(filters?: any) {
  const budgetApi = new BudgetApi(apiClient);
  
  return useQuery({
    queryKey: ['budgets', filters],
    queryFn: () => budgetApi.listBudgets(filters),
  });
}

export function useBudget(id: number) {
  const budgetApi = new BudgetApi(apiClient);
  
  return useQuery({
    queryKey: ['budget', id],
    queryFn: () => budgetApi.getBudget(id),
    enabled: !!id,
  });
}

export function useCreateBudget() {
  const queryClient = useQueryClient();
  const budgetApi = new BudgetApi(apiClient);
  
  return useMutation({
    mutationFn: (budget: Partial<Budget>) => budgetApi.createBudget(budget),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['budgets'] });
    },
  });
}

export function useBudgetExpenses(budgetId: number, filters?: any) {
  const budgetApi = new BudgetApi(apiClient);
  
  return useQuery({
    queryKey: ['budget-expenses', budgetId, filters],
    queryFn: () => budgetApi.getBudgetExpenses(budgetId, filters),
    enabled: !!budgetId,
  });
}

export function useAllocateExpense() {
  const queryClient = useQueryClient();
  const budgetApi = new BudgetApi(apiClient);
  
  return useMutation({
    mutationFn: ({ budgetId, allocation }: { budgetId: number; allocation: ExpenseAllocation }) => 
      budgetApi.allocateExpense(budgetId, allocation),
    onSuccess: (_, { budgetId }) => {
      queryClient.invalidateQueries({ queryKey: ['budget-expenses', budgetId] });
      queryClient.invalidateQueries({ queryKey: ['budget', budgetId] });
    },
  });
}

Error Responses

All endpoints return plain text error messages in the response body, not structured JSON. The actual implementation uses http.Error() which returns simple string responses.

Error Response Format:

Status Code: 400/401/404/500
Content-Type: text/plain
Body: "Error message text"

Common Error Responses:

  • 400 Bad Request: "Invalid budget ID", "Invalid request payload", "Invalid URL format"
  • 401 Unauthorized: "Authentication required", "User not authenticated"
  • 404 Not Found: "Budget not found", "Expense not found"
  • 500 Internal Server Error: "Failed to create budget: [error details]", "Failed to get expenses: [error details]"

Note: While the codebase defines structured ErrorResponse types in models/response.go, the actual budget handlers implementation uses simple text responses.

Success Response Format

Success responses follow this pattern:

{
  "status": "success",
  "message": "Operation completed successfully",
  "data": { /* response data */ },
  "timestamp": "2025-01-11T10:00:00Z"
}

Implementation Notes

  1. Authentication: All requests require valid Bearer token authentication

  2. Budget Usage Tracking: Budget used_amount and remaining_amount are automatically calculated when expenses are allocated or removed

  3. Expense Validation: When removing expenses, the system validates that the expense actually belongs to the specified budget

  4. Category Handling: Use category_id for existing categories or category_name for new categories

  5. Allocation Types: Choose appropriate allocation types based on your use case:

    • manual: User-initiated allocation
    • automatic: System-initiated allocation
    • categories: Category-based allocation
    • project: Project-based allocation
  6. Budget Hierarchies: Use parent_budget_id to create budget hierarchies and allocations between parent and child budgets

  7. Real-time Updates: Budget amounts are updated in real-time when expenses are allocated or removed

  8. Alert System: Configure alert thresholds (percentage) to receive notifications when budgets approach limits

This integration provides a comprehensive budget management system that seamlessly integrates with your NextJS application, offering real-time tracking, flexible allocation options, and robust error handling.