Account Track Entry Documentation¶
Overview¶
The Account Track Entry model (account.track.entry) records individual financial transactions linked to tracking categories. It enables detailed cost and revenue tracking by project, department, or any other business dimension defined in tracking categories.
Model Information¶
Model Name: account.track.entry
Display Name: Tracking Entries
Key Fields: None (no unique constraint defined)
Features¶
- ❌ Audit logging enabled (
_audit_log) - ❌ Multi-company support (
company_id) - ❌ Full-text content search (
_content_search) - ✅ Product-based entry with cost lookup
- ✅ Quantity and unit price calculation
- ✅ Copy to invoice functionality
- ✅ Links to multiple document types
Key Fields Reference¶
Core Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
track_id |
Many2One | ✅ | Tracking category (cascade delete) |
date |
Date | ✅ | Entry date |
amount |
Decimal | ✅ | Entry amount |
description |
Text | ❌ | Entry description |
Product/Quantity Fields¶
| Field | Type | Description |
|---|---|---|
product_id |
Many2One | Associated product |
qty |
Decimal | Quantity |
uom_id |
Many2One | Unit of measure |
unit_price |
Decimal | Unit price |
Reference Fields¶
| Field | Type | Description |
|---|---|---|
related_id |
Reference | Related document (Invoice, Picking, etc.) |
move_id |
Many2One | Related journal entry |
invoice_id |
Many2One | Generated invoice |
contact_id |
Many2One | Contact |
Default Values¶
Default Ordering¶
Records are ordered by date descending, then ID descending:
Reference Types¶
The related_id field can link to:
| Model | Label | Description |
|---|---|---|
account.invoice |
Invoice | Customer/Supplier invoice |
stock.picking |
Stock Picking | Inventory movement |
work.time |
Work Time | Time entry |
expense.claim |
Expense Claim | Employee expense |
nd.order |
Delivery Order | Delivery document |
API Methods¶
1. On Change Product¶
Method: onchange_product(context={})
Auto-fills entry details when product is selected.
Behavior: 1. Gets product cost price 2. Converts to tracking category currency if different 3. Sets unit_price as negative (cost) 4. Sets qty to 1 5. Sets UoM from product 6. Calculates amount
Example:
# In form context
data = {
"track_id": track_id,
"product_id": product_id,
}
result = get_model("account.track.entry").onchange_product(context={"data": data})
# result contains: unit_price, qty, uom_id, amount
2. Update Amount¶
Method: update_amount(context={})
Recalculates amount from unit_price and qty.
Formula: amount = unit_price × qty
Example:
data = {
"unit_price": -50.00,
"qty": 10,
}
result = get_model("account.track.entry").update_amount(context={"data": data})
# result["amount"] = -500.00
3. Copy to Invoice¶
Method: copy_to_invoice(ids, context={})
Creates invoices from tracking entries grouped by contact.
Behavior: 1. Groups entries by contact (from track_id.contact_id) 2. Creates customer invoice if contact is customer 3. Creates supplier invoice if contact is supplier 4. Links entries to created invoice 5. Validates entries aren't already invoiced
Raises:
- Exception("Entry is already invoiced") - If invoice_id already set
- Exception("Missing contact") - If track category has no contact
- Exception("Contact is not a supplier or customer") - If contact type unknown
Example:
# Select entries to invoice
entry_ids = [1, 2, 3]
# Create invoices
get_model("account.track.entry").copy_to_invoice(entry_ids)
# Entries are now linked to invoice
for entry in get_model("account.track.entry").browse(entry_ids):
print(f"Entry {entry.id} -> Invoice {entry.invoice_id.number}")
Related Models¶
| Model | Relationship | Description |
|---|---|---|
account.track.categ |
Many2One (track_id) | Tracking category |
product |
Many2One (product_id) | Product |
contact |
Many2One (contact_id) | Contact |
uom |
Many2One (uom_id) | Unit of measure |
account.move |
Many2One (move_id) | Journal entry |
account.invoice |
Many2One (invoice_id) | Invoice |
Common Use Cases¶
Use Case 1: Record Project Cost¶
# Record material cost for a project
entry_id = get_model("account.track.entry").create({
"track_id": project_track_id,
"date": "2024-12-15",
"product_id": material_product_id,
"qty": 10,
"unit_price": -25.00,
"amount": -250.00,
"description": "Construction materials",
})
Use Case 2: Record Revenue Entry¶
# Record service revenue
entry_id = get_model("account.track.entry").create({
"track_id": project_track_id,
"date": "2024-12-15",
"amount": 5000.00,
"description": "Consulting services - December",
"contact_id": client_id,
})
Use Case 3: Generate Invoice from Entries¶
# Find unbilled entries for a tracking category
entries = get_model("account.track.entry").search_browse([
["track_id", "=", project_track_id],
["invoice_id", "=", None],
["amount", ">", 0], # Revenue entries only
])
if entries:
entry_ids = [e.id for e in entries]
get_model("account.track.entry").copy_to_invoice(entry_ids)
print(f"Created invoice for {len(entry_ids)} entries")
Use Case 4: Track Project Profitability¶
# Calculate project profit
entries = get_model("account.track.entry").search_browse([
["track_id", "=", project_track_id],
])
revenue = sum(e.amount for e in entries if e.amount > 0)
costs = sum(abs(e.amount) for e in entries if e.amount < 0)
profit = revenue - costs
margin = (profit / revenue * 100) if revenue else 0
print(f"Revenue: {revenue:,.2f}")
print(f"Costs: {costs:,.2f}")
print(f"Profit: {profit:,.2f}")
print(f"Margin: {margin:.1f}%")
Use Case 5: Monthly Tracking Report¶
# Get entries for a date range
entries = get_model("account.track.entry").search_browse([
["date", ">=", "2024-12-01"],
["date", "<=", "2024-12-31"],
], order="track_id,date")
# Group by tracking category
by_track = {}
for entry in entries:
track_name = entry.track_id.name
by_track.setdefault(track_name, []).append(entry)
# Print report
for track_name, track_entries in by_track.items():
total = sum(e.amount for e in track_entries)
print(f"{track_name}: {total:,.2f}")
for entry in track_entries:
print(f" {entry.date}: {entry.description} - {entry.amount:,.2f}")
Entry Flow¶
1. Create tracking entry
│
├─ Manual entry
│ └─ User enters amount directly
│
└─ Product-based entry
├─ Select product
├─ onchange_product fills details
└─ Adjust qty/price as needed
│
2. Entry accumulates in track category balance
│
3. Optional: Generate invoice
│
├─ Select entries to invoice
├─ copy_to_invoice creates invoice
└─ Entry linked to invoice_id
Best Practices¶
1. Use Products for Standardized Costs¶
# Good: Product-based entry with consistent pricing
entry = get_model("account.track.entry").create({
"track_id": project_id,
"product_id": labor_product_id,
"qty": 8, # 8 hours
# unit_price from product cost
})
# Less ideal: Manual amounts vary
entry = get_model("account.track.entry").create({
"track_id": project_id,
"amount": -400.00,
"description": "Labor",
})
2. Always Set Description¶
# Good: Clear description
entry = get_model("account.track.entry").create({
"description": "Server hosting - December 2024",
...
})
# Bad: No description makes reporting difficult
entry = get_model("account.track.entry").create({
"amount": -100.00,
...
})
3. Link to Source Documents¶
# Good: Reference original document
entry = get_model("account.track.entry").create({
"related_id": f"stock.picking,{picking_id}",
...
})
Troubleshooting¶
"Entry is already invoiced"¶
Cause: Attempting to copy entry that has invoice_id set Solution: Select only unbilled entries (invoice_id = None)
"Missing contact"¶
Cause: Track category has no contact_id Solution: Set contact_id on the tracking category
"Contact is not a supplier or customer"¶
Cause: Contact doesn't have customer or supplier flag Solution: Set customer=True or supplier=True on contact
Testing Examples¶
Unit Test: Track Entry Creation¶
def test_track_entry():
# Create entry
entry_id = get_model("account.track.entry").create({
"track_id": track_id,
"date": "2024-12-15",
"amount": -100.00,
"description": "Test entry",
})
# Verify
entry = get_model("account.track.entry").browse([entry_id])[0]
assert entry.amount == -100.00
assert entry.track_id.id == track_id
# Check track balance updated
track = get_model("account.track.categ").browse([track_id])[0]
assert entry.amount in track.balance # Balance includes entry
# Cleanup
get_model("account.track.entry").delete([entry_id])
Unit Test: Copy to Invoice¶
def test_copy_to_invoice():
# Create entries
entry_ids = []
for i in range(3):
entry_id = get_model("account.track.entry").create({
"track_id": track_with_contact_id,
"amount": 100.00,
"description": f"Service {i+1}",
})
entry_ids.append(entry_id)
# Copy to invoice
get_model("account.track.entry").copy_to_invoice(entry_ids)
# Verify invoice created
entries = get_model("account.track.entry").browse(entry_ids)
invoice_id = entries[0].invoice_id.id
assert invoice_id is not None
# All entries linked to same invoice
for entry in entries:
assert entry.invoice_id.id == invoice_id
Security Considerations¶
Permission Model¶
- Track entries typically restricted by role
- Invoice creation requires invoice permissions
Data Access¶
- Cascade delete from track category removes entries
- Invoice link prevents double-billing
Version History¶
Last Updated: December 2024 Model Version: account_track_entry.py Framework: Netforce
Additional Resources¶
- Track Category Documentation:
account.track.categ - Invoice Documentation:
account.invoice - Product Documentation:
product
This documentation is generated for developer onboarding and reference purposes.