Skip to content

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

_defaults = {
    "date": lambda *a: time.strftime("%Y-%m-%d"),  # Today
}

Default Ordering

Records are ordered by date descending, then ID descending:

_order = "date desc,id desc"

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}")


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,
    ...
})

# 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.