Expense Documentation¶
Overview¶
The Expense model (expense) manages individual expense records submitted by employees. It supports an approval workflow, multi-currency transactions, and integration with expense claims for reimbursement processing.
Model Information¶
Model Name: expense
Display Name: Expense
Key Fields: None (no unique constraint defined)
Features¶
- ❌ Audit logging enabled (
_audit_log) - ✅ Multi-company support (
company_id) - ❌ Full-text content search (
_content_search) - ✅ Approval workflow integration
- ✅ Multi-currency support
State Workflow¶
| State | Code | Description |
|---|---|---|
| Draft | draft |
Initial state, expense can be edited |
| Awaiting Approval | wait_approve |
Submitted for approval, pending review |
| Approved | approved |
Expense has been approved |
| Declined | decline |
Expense was rejected |
Key Fields Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
merchant |
Char | ✅ | Name of the merchant/vendor |
amount |
Decimal | ✅ | Total expense amount |
date |
Date | ✅ | Date of the expense |
employee_id |
Many2One | ✅ | Employee who incurred the expense |
currency_id |
Many2One | ✅ | Currency of the expense |
state |
Selection | ✅ | Current workflow state |
Optional Fields¶
| Field | Type | Description |
|---|---|---|
description |
Text | Detailed description of the expense |
account_id |
Many2One | GL account for posting |
tax_id |
Many2One | Applicable tax rate |
currency_rate |
Decimal | Exchange rate if foreign currency |
project_id |
Many2One | Related project |
image |
File | Receipt image attachment |
categ_id |
Many2One | Expense category |
product_id |
Many2One | Related product |
Tracking Fields¶
| Field | Type | Description |
|---|---|---|
track_id |
Many2One | Track-1 cost tracking category |
track2_id |
Many2One | Track-2 cost tracking category |
Relationship Fields¶
| Field | Type | Description |
|---|---|---|
claim_id |
Many2One | Parent expense claim |
invoice_id |
Many2One | Related invoice |
company_id |
Many2One | Company for multi-company support |
related_id |
Reference | Reference to related job |
Approval Fields¶
| Field | Type | Description |
|---|---|---|
approvals |
One2Many | Approval records |
date_approve |
DateTime | Date when expense was approved |
show_approve |
Boolean | Computed: Shows if current user can approve |
Deprecated Fields¶
| Field | Type | Description |
|---|---|---|
qty |
Decimal | ~~Quantity~~ (deprecated) |
unit_price |
Decimal | ~~Unit price~~ (deprecated) |
supplier_id |
Many2One | ~~Supplier~~ (deprecated) |
Default Values¶
_defaults = {
"date": lambda *a: time.strftime("%Y-%m-%d"), # Today's date
"employee_id": _get_employee, # Current user's employee record
"currency_id": _get_currency, # Company default currency
"state": "draft", # Initial state
}
API Methods¶
1. Create Expense¶
Method: create(vals, context)
Creates a new expense record.
Parameters:
vals = {
"merchant": "Office Depot", # Required: Vendor name
"amount": 150.00, # Required: Expense amount
"date": "2024-12-15", # Required: Expense date
"employee_id": employee_id, # Required: Employee ID
"currency_id": currency_id, # Required: Currency ID
"description": "Office supplies", # Optional: Description
"account_id": account_id, # Optional: GL account
"categ_id": categ_id, # Optional: Category
"image": file_path, # Optional: Receipt image
}
Returns: int - New record ID
Example:
# Create a new expense
expense_id = get_model("expense").create({
"merchant": "Starbucks",
"amount": 25.50,
"date": "2024-12-15",
"description": "Client meeting coffee"
})
2. Submit for Approval¶
Method: submit_for_approval(ids, context)
Submits the expense for approval workflow.
Behavior:
- Changes state from draft to wait_approve
- Finds applicable approval workflow based on employee, company, and amount
- Creates approval records for first-level approvers
- Triggers notification to approvers
Example:
Raises:
- Exception("Missing user") - If employee_id is not set
- Exception("Missing approval workflow") - If no workflow matches
- Exception("Missing approval workflow steps") - If workflow has no steps
3. Approve Expense¶
Method: approve(ids, context)
Approves the expense (called by authorized approver).
Behavior:
- Validates user has pending approval for this expense
- Marks approval as approved
- Cancels other pending approvals at same sequence level
- Creates next-level approval if workflow continues
- Sets state to approved if all approval levels complete
Example:
Raises:
- Exception("Invalid expense status") - If not in wait_approve state
- Exception("User does not have permission...") - If user is not an approver
4. Reject Expense¶
Method: reject(ids, context)
Rejects the expense (called by authorized approver).
Behavior:
- Validates user has pending approval for this expense
- Marks approval as rejected
- Sets expense state to decline
- Cancels all other pending approvals
- Triggers rejection notification
Example:
5. Get Show Approve¶
Method: get_show_approve(ids, context)
Computed function that determines if the current user can approve the expense.
Returns: dict - Mapping of expense IDs to boolean values
Example:
# Check if current user can approve
result = get_model("expense").get_show_approve([expense_id])
can_approve = result[expense_id] # True or False
UI Events (onchange methods)¶
onchange_product¶
Triggered when product is selected. Updates:
- account_id - Sets to product's purchase account
- tax_id - Sets to product's purchase tax rate
Usage:
data = {
"product_id": product_id
}
result = get_model("expense").onchange_product(context={"data": data})
# result contains updated account_id and tax_id
Search Functions¶
Search by Employee¶
# Find all expenses for an employee
expenses = get_model("expense").search([["employee_id", "=", employee_id]])
Search by Date Range¶
# Find expenses in date range
expenses = get_model("expense").search([
["date", ">=", "2024-01-01"],
["date", "<=", "2024-12-31"]
])
Search by State¶
# Find pending approval expenses
expenses = get_model("expense").search([["state", "=", "wait_approve"]])
# Find approved expenses
expenses = get_model("expense").search([["state", "=", "approved"]])
Search by Claim¶
# Find expenses linked to a claim
expenses = get_model("expense").search([["claim_id", "=", claim_id]])
Related Models¶
| Model | Relationship | Description |
|---|---|---|
expense.categ |
Many2One (categ_id) | Expense category |
expense.claim |
Many2One (claim_id) | Parent expense claim |
hr.employee |
Many2One (employee_id) | Employee who submitted |
account.account |
Many2One (account_id) | GL account for posting |
account.tax.rate |
Many2One (tax_id) | Tax rate |
account.track.categ |
Many2One (track_id, track2_id) | Cost tracking categories |
currency |
Many2One (currency_id) | Transaction currency |
project |
Many2One (project_id) | Related project |
product |
Many2One (product_id) | Related product |
account.invoice |
Many2One (invoice_id) | Related invoice |
company |
Many2One (company_id) | Company |
approval |
One2Many (approvals) | Approval records |
approve.wkf |
Reference | Approval workflow definition |
Common Use Cases¶
Use Case 1: Create and Submit Expense¶
# 1. Create the expense
expense_id = get_model("expense").create({
"merchant": "Singapore Airlines",
"amount": 850.00,
"date": "2024-12-10",
"description": "Flight to KL for client meeting",
"categ_id": travel_categ_id,
"account_id": travel_expense_account_id,
})
# 2. Attach receipt image (optional)
get_model("expense").write([expense_id], {
"image": "/path/to/receipt.pdf"
})
# 3. Submit for approval
get_model("expense").submit_for_approval([expense_id])
Use Case 2: Approve Multiple Expenses¶
# Find all pending expenses for current approver
user_id = access.get_active_user()
pending = get_model("approval").search([
["state", "=", "pending"],
["user_id", "=", user_id],
["related_id", "like", "expense,%"]
])
for app in get_model("approval").browse(pending):
expense_id = int(app.related_id.split(",")[1])
get_model("expense").approve([expense_id])
Use Case 3: Get Employee Expense Summary¶
# Get total expenses by category for an employee
employee_id = 123
expenses = get_model("expense").search_browse([
["employee_id", "=", employee_id],
["state", "=", "approved"],
["date", ">=", "2024-01-01"],
["date", "<=", "2024-12-31"]
])
by_category = {}
for exp in expenses:
cat_name = exp.categ_id.name if exp.categ_id else "Uncategorized"
by_category[cat_name] = by_category.get(cat_name, 0) + exp.amount
for cat, total in by_category.items():
print(f"{cat}: {total:.2f}")
Best Practices¶
1. Always Set Required Fields¶
# Good: All required fields provided
expense_id = get_model("expense").create({
"merchant": "Amazon",
"amount": 99.00,
"date": "2024-12-15",
"employee_id": employee_id,
"currency_id": currency_id
})
# Bad: Missing required fields (will fail)
expense_id = get_model("expense").create({
"merchant": "Amazon"
}) # Error: Missing amount, date
2. Use Categories for Reporting¶
# Good: Categorize expenses for better reporting
expense_id = get_model("expense").create({
"merchant": "Grab",
"amount": 15.00,
"date": "2024-12-15",
"categ_id": transport_categ_id, # Enables category-based reports
"description": "Taxi to airport"
})
3. Attach Receipts¶
# Good: Always attach receipt for audit trail
expense_id = get_model("expense").create({
"merchant": "Hotel ABC",
"amount": 250.00,
"date": "2024-12-15",
"image": receipt_file_path # Receipt for verification
})
Troubleshooting¶
"Missing user"¶
Cause: Employee ID is not set when submitting for approval
Solution: Ensure employee_id is populated before calling submit_for_approval
"Missing approval workflow"¶
Cause: No approval workflow configured for the employee, company, or amount threshold
Solution: Configure approval workflow in approve.wkf model matching the expense criteria
"User does not have permission to approve"¶
Cause: Current user is not an assigned approver for this expense Solution: Only users with pending approval records can approve/reject
"Invalid expense status"¶
Cause: Attempting to approve/reject an expense not in wait_approve state
Solution: Only expenses in wait_approve state can be approved or rejected
Testing Examples¶
Unit Test: Expense Approval Flow¶
def test_expense_approval_flow():
# 1. Create expense
expense_id = get_model("expense").create({
"merchant": "Test Vendor",
"amount": 100.00,
"date": time.strftime("%Y-%m-%d"),
})
# Verify draft state
exp = get_model("expense").browse([expense_id])[0]
assert exp.state == "draft"
# 2. Submit for approval
get_model("expense").submit_for_approval([expense_id])
exp = get_model("expense").browse([expense_id])[0]
assert exp.state == "wait_approve"
# 3. Approve (as authorized user)
get_model("expense").approve([expense_id])
exp = get_model("expense").browse([expense_id])[0]
assert exp.state == "approved"
Security Considerations¶
Permission Model¶
- Users can typically only create/view their own expenses
- Approvers can view and approve expenses in their approval queue
- Admin/Accounting can view all expenses
Data Access¶
employee_iddefaults to current user's employee record- Multi-company filtering via
company_id - Approval records track who approved/rejected
Workflow Integration¶
Trigger Events¶
The expense model fires workflow triggers:
These can be configured in workflow automation for notifications.
Version History¶
Last Updated: December 2024 Model Version: expense.py Framework: Netforce
Additional Resources¶
- Expense Category Documentation:
expense.categ - Expense Claim Documentation:
expense.claim - Account Expense Documentation:
account.expense - Approval Workflow Documentation:
approve.wkf
This documentation is generated for developer onboarding and reference purposes.