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:
- Budget CRUD Operations: Create, read, update, and delete budgets
- Expense Allocation: Allocate individual or multiple expenses to budgets
- Budget Tracking: Real-time tracking of budget usage and remaining amounts
- Budget Allocations: Hierarchical budget allocation from parent to child budgets
- Alert System: Automated alerts when budgets reach thresholds
- Category Management: Track budget usage by expense categories
- 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 IDdepartment_id: Filter by department IDproject_id: Filter by project IDparent_budget_id: Filter by parent budget IDstart_date: Filter by start date (ISO format)end_date: Filter by end date (ISO format)limit: Number of results per pageoffset: 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 categorytype: Filter by expense type (expense, income)min_amount: Minimum amount filtermax_amount: Maximum amount filterstart_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 userautomatic: Automatically allocated by systemcategories: Allocated based on category matchingproject: 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 statustype: Filter by alert typestart_date: Start date filterend_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
-
Authentication: All requests require valid Bearer token authentication
-
Budget Usage Tracking: Budget
used_amountandremaining_amountare automatically calculated when expenses are allocated or removed -
Expense Validation: When removing expenses, the system validates that the expense actually belongs to the specified budget
-
Category Handling: Use
category_idfor existing categories orcategory_namefor new categories -
Allocation Types: Choose appropriate allocation types based on your use case:
manual: User-initiated allocationautomatic: System-initiated allocationcategories: Category-based allocationproject: Project-based allocation
-
Budget Hierarchies: Use
parent_budget_idto create budget hierarchies and allocations between parent and child budgets -
Real-time Updates: Budget amounts are updated in real-time when expenses are allocated or removed
-
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.