Account Budget Line Documentation¶
Overview¶
The Account Budget Line model (account.budget.line) represents individual line items within a budget. Each line specifies a budgeted amount for a specific account, with computed fields that automatically calculate actual amounts and variance from the general ledger.
Model Information¶
Model Name: account.budget.line
Display Name: Budget Line
Key Fields: None (no unique constraint defined)
Features¶
- ❌ Audit logging enabled (
_audit_log) - ❌ Multi-company support (
company_id) - ❌ Full-text content search (
_content_search) - ✅ Automatic actual amount calculation from GL
- ✅ Variance calculation and percentage
- ✅ Optional tracking and contact filtering
- ✅ Cascade delete with parent budget
Key Fields Reference¶
Required Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
budget_id |
Many2One | ✅ | Parent budget record |
account_id |
Many2One | ✅ | GL account to budget |
budget_amount |
Decimal | ✅ | Budgeted amount |
Optional Fields¶
| Field | Type | Description |
|---|---|---|
sequence |
Integer | Display order sequence |
track_id |
Many2One | Filter actuals by tracking category |
contact_id |
Many2One | Filter actuals by supplier/contact |
Computed Fields¶
| Field | Type | Description |
|---|---|---|
actual_amount |
Decimal | Actual GL balance for the account |
variance |
Decimal | Difference: actual - budget |
variance_percent |
Decimal | Variance as percentage of budget |
paid_amount |
Decimal | Paid portion (placeholder) |
unpaid_amount |
Decimal | Unpaid portion (placeholder) |
Default Ordering¶
Records are ordered by sequence:
Computed Fields Functions¶
get_actual(ids, context)¶
Calculates actual amounts from the general ledger based on the parent budget's date range.
Logic:
1. Gets date range from parent budget (date_from, date_to)
2. Applies optional contact or tracking filter
3. Retrieves account balance for the period
4. Calculates variance and variance percentage
Formula:
actual_amount = account.balance # Within date range
variance = actual_amount - budget_amount
variance_percent = (variance * 100) / budget_amount # if budget_amount > 0
Example Result:
{
"actual_amount": 45000.00, # From GL
"variance": -5000.00, # Under budget by 5000
"variance_percent": -10.00, # 10% under budget
}
get_paid(ids, context)¶
Placeholder function for paid/unpaid tracking.
Current Implementation:
API Methods¶
1. Create Budget Line¶
Method: create(vals, context)
Creates a new budget line. Usually created via parent budget.
Parameters:
vals = {
"budget_id": budget_id, # Required: Parent budget
"account_id": account_id, # Required: GL account
"budget_amount": 50000.00, # Required: Budget amount
"sequence": 10, # Optional: Display order
"track_id": track_id, # Optional: Tracking filter
"contact_id": supplier_id, # Optional: Contact filter
}
Returns: int - New record ID
Example (Standalone):
line_id = get_model("account.budget.line").create({
"budget_id": budget_id,
"account_id": marketing_expense_id,
"budget_amount": 25000.00,
"sequence": 1,
})
Example (Via Parent):
budget_id = get_model("account.budget").create({
"name": "2024 Budget",
"date_from": "2024-01-01",
"date_to": "2024-12-31",
"lines": [
("create", {
"account_id": salary_account_id,
"budget_amount": 500000.00,
"sequence": 1,
}),
("create", {
"account_id": rent_account_id,
"budget_amount": 120000.00,
"sequence": 2,
}),
]
})
2. Read Budget Lines¶
Method: browse(ids, context)
Retrieve budget line records.
Example:
lines = get_model("account.budget.line").browse(line_ids)
for line in lines:
print(f"Account: {line.account_id.name}")
print(f" Budget: {line.budget_amount:,.2f}")
print(f" Actual: {line.actual_amount:,.2f}")
print(f" Variance: {line.variance:,.2f}")
if line.variance_percent:
print(f" Variance %: {line.variance_percent:.1f}%")
3. Update Budget Line¶
Method: write(ids, vals, context)
Update existing budget line records.
Example:
# Increase budget amount
get_model("account.budget.line").write([line_id], {
"budget_amount": 60000.00
})
# Add tracking filter
get_model("account.budget.line").write([line_id], {
"track_id": department_track_id
})
4. Delete Budget Line¶
Method: delete(ids, context)
Delete budget line records. Lines are also automatically deleted when parent budget is deleted (cascade).
Example:
Related Models¶
| Model | Relationship | Description |
|---|---|---|
account.budget |
Many2One (budget_id) | Parent budget record |
account.account |
Many2One (account_id) | GL account being budgeted |
account.track.categ |
Many2One (track_id) | Tracking category filter |
contact |
Many2One (contact_id) | Supplier/contact filter |
Common Use Cases¶
Use Case 1: Budget vs Actual Analysis¶
# Get budget with actual comparison
budget = get_model("account.budget").browse([budget_id])[0]
print(f"Budget: {budget.name}")
print(f"Period: {budget.date_from} to {budget.date_to}")
print("-" * 70)
print(f"{'Account':<30} {'Budget':>12} {'Actual':>12} {'Variance':>12}")
print("-" * 70)
for line in budget.lines:
var_indicator = "!" if (line.variance or 0) > 0 else " "
print(f"{line.account_id.name:<30} "
f"{line.budget_amount:>12,.2f} "
f"{(line.actual_amount or 0):>12,.2f} "
f"{(line.variance or 0):>12,.2f}{var_indicator}")
Use Case 2: Budget by Tracking Category¶
# Create budget with tracking filters (e.g., by department)
budget_id = get_model("account.budget").create({
"name": "2024 Departmental Expenses",
"date_from": "2024-01-01",
"date_to": "2024-12-31",
"lines": [
# Sales department
("create", {
"account_id": expense_account_id,
"budget_amount": 50000.00,
"track_id": sales_dept_id,
"sequence": 1,
}),
# Marketing department
("create", {
"account_id": expense_account_id,
"budget_amount": 75000.00,
"track_id": marketing_dept_id,
"sequence": 2,
}),
# Operations department
("create", {
"account_id": expense_account_id,
"budget_amount": 100000.00,
"track_id": operations_dept_id,
"sequence": 3,
}),
]
})
Use Case 3: Supplier-Specific Budget¶
# Budget spending with specific suppliers
budget_id = get_model("account.budget").create({
"name": "2024 Supplier Contracts",
"date_from": "2024-01-01",
"date_to": "2024-12-31",
"lines": [
("create", {
"account_id": it_services_account_id,
"budget_amount": 200000.00,
"contact_id": cloud_provider_id, # AWS/Azure contract
"sequence": 1,
}),
("create", {
"account_id": office_supplies_id,
"budget_amount": 15000.00,
"contact_id": office_depot_id,
"sequence": 2,
}),
]
})
Use Case 4: Budget Utilization Report¶
# Generate utilization report
budget = get_model("account.budget").browse([budget_id])[0]
print("Budget Utilization Report")
print("=" * 50)
over_budget = []
under_utilized = []
for line in budget.lines:
utilization = ((line.actual_amount or 0) / line.budget_amount * 100) if line.budget_amount else 0
if utilization > 100:
over_budget.append({
"account": line.account_id.name,
"budget": line.budget_amount,
"actual": line.actual_amount,
"over_by": line.variance,
"percent": utilization,
})
elif utilization < 50:
under_utilized.append({
"account": line.account_id.name,
"budget": line.budget_amount,
"actual": line.actual_amount,
"percent": utilization,
})
print("\nOver Budget Items:")
for item in over_budget:
print(f" {item['account']}: {item['percent']:.1f}% utilized (over by {item['over_by']:,.2f})")
print("\nUnder-Utilized Items (< 50%):")
for item in under_utilized:
print(f" {item['account']}: {item['percent']:.1f}% utilized")
Use Case 5: Adjust Budget Mid-Year¶
# Find line and adjust budget
budget = get_model("account.budget").browse([budget_id])[0]
for line in budget.lines:
# If actual is 80% of budget with 6 months remaining, increase budget
if line.budget_amount and line.actual_amount:
utilization = line.actual_amount / line.budget_amount
if utilization > 0.8: # More than 80% used
new_budget = line.budget_amount * 1.5 # Increase by 50%
line.write({"budget_amount": new_budget})
print(f"Increased {line.account_id.name} budget to {new_budget:,.2f}")
Best Practices¶
1. Use Sequence for Logical Ordering¶
# Good: Ordered by importance/category
lines = [
("create", {"account_id": revenue_id, "budget_amount": 1000000, "sequence": 1}),
("create", {"account_id": cogs_id, "budget_amount": 600000, "sequence": 2}),
("create", {"account_id": opex_id, "budget_amount": 200000, "sequence": 3}),
]
# Bad: Random order
lines = [
("create", {"account_id": opex_id, "budget_amount": 200000}),
("create", {"account_id": revenue_id, "budget_amount": 1000000}),
("create", {"account_id": cogs_id, "budget_amount": 600000}),
]
2. Use Tracking for Granular Analysis¶
# Good: Track by department for detailed analysis
("create", {
"account_id": expense_id,
"budget_amount": 50000,
"track_id": sales_department_id, # Filter actuals to this department
})
# Limited: No tracking (all expenses combined)
("create", {
"account_id": expense_id,
"budget_amount": 50000,
})
3. Review Variance Regularly¶
# Check for significant variances (> 10%)
budget = get_model("account.budget").browse([budget_id])[0]
alerts = []
for line in budget.lines:
if line.variance_percent and abs(line.variance_percent) > 10:
alerts.append({
"account": line.account_id.name,
"variance_pct": line.variance_percent,
"variance_amt": line.variance,
})
if alerts:
print("Budget Variance Alerts:")
for alert in alerts:
status = "OVER" if alert["variance_pct"] > 0 else "UNDER"
print(f" {alert['account']}: {abs(alert['variance_pct']):.1f}% {status} budget")
Troubleshooting¶
"Actual amount is None or 0"¶
Cause: Parent budget has no date range, or no transactions in period
Solution: Ensure budget has date_from and date_to set
"Variance percentage is None"¶
Cause: Budget amount is 0 or None Solution: Set a valid budget_amount > 0
"Wrong actual amount"¶
Cause: Account balance includes transactions outside budget period Solution: Verify budget date range matches expected period
"Tracking filter not working"¶
Cause: Transactions not tagged with tracking category Solution: Ensure journal entries have proper track_id values
Testing Examples¶
Unit Test: Budget Line Calculations¶
def test_budget_line_variance():
# Create budget with line
budget_id = get_model("account.budget").create({
"name": "Test Budget",
"date_from": "2024-01-01",
"date_to": "2024-12-31",
"lines": [
("create", {
"account_id": test_account_id,
"budget_amount": 10000.00,
})
]
})
budget = get_model("account.budget").browse([budget_id])[0]
line = budget.lines[0]
# Verify budget amount
assert line.budget_amount == 10000.00
# Verify computed fields exist
assert hasattr(line, 'actual_amount')
assert hasattr(line, 'variance')
assert hasattr(line, 'variance_percent')
# Cleanup
get_model("account.budget").delete([budget_id])
Security Considerations¶
Permission Model¶
- Typically restricted to finance/management
- May grant read access to department managers for their budgets
Data Access¶
- Budget data is sensitive financial information
- Actuals are pulled from GL with proper filtering
Database Behavior¶
Cascade Delete¶
When a parent budget is deleted, all its lines are automatically deleted:
Performance Notes¶
The get_actual function queries the account balance for each line, which involves:
- Reading parent budget dates
- Querying account model with date context
- Filtering by contact or tracking if specified
For budgets with many lines, this may impact performance. Consider caching or batch processing for reports.
Version History¶
Last Updated: December 2024 Model Version: account_budget_line.py Framework: Netforce
Additional Resources¶
- Account Budget Documentation:
account.budget - Account Documentation:
account.account - Tracking Category Documentation:
account.track.categ - Contact Documentation:
contact
This documentation is generated for developer onboarding and reference purposes.