Skip to content

Account Claim Documentation

Overview

The Account Claim model (account.claim) manages expense reimbursement claims from users. It aggregates expense receipts, handles approval workflows, creates journal entries, and tracks payment status until the claim is fully paid.


Model Information

Model Name: account.claim Display Name: Claim Name Field: number Key Fields: None (no unique constraint defined)

Features

  • ❌ Audit logging enabled (_audit_log)
  • ❌ Multi-company support (company_id)
  • ❌ Full-text content search (_content_search)
  • ✅ UUID for unique identification
  • ✅ Approval workflow
  • ✅ Journal entry creation
  • ✅ Payment tracking
  • ✅ Computed state based on payment status

State Workflow

waiting_approval → waiting_payment → paid
                                  voided
State Code Description
Waiting Approval waiting_approval Claim submitted, pending authorization
Waiting Payment waiting_payment Approved, awaiting reimbursement
Paid paid Fully reimbursed
Voided voided Cancelled/voided

Note: State transitions between waiting_payment and paid are automatic based on amount_due.


Key Fields Reference

Header Fields

Field Type Required Description
number Char Claim number
user_id Many2One Claim owner (readonly)
date Date Date submitted (readonly)
due_date Date Payment due date
account_id Many2One Unpaid claims account
uuid Char Unique identifier

Computed Amount Fields

Field Type Description
amount_total Decimal Sum of all expense totals (stored)
amount_approved Decimal Sum of approved expenses (stored)
amount_paid Decimal Sum of payments made (stored)
amount_due Decimal Approved minus paid (stored)
num_receipts Integer Count of expenses
can_authorize Boolean All expenses approved/declined

Relationship Fields

Field Type Description
expenses One2Many Expense receipts
payments One2Many Payment lines
move_id Many2One Journal entry

Default Values

_defaults = {
    "state": "waiting_approval",
    "date": lambda *a: time.strftime("%Y-%m-%d"),
    "uuid": lambda *a: str(uuid.uuid4()),
}

API Methods

1. Authorize Claim

Method: do_authorize(ids, context)

Authorizes the claim and creates journal entry.

Prerequisites: - due_date must be set - All expenses must be approved or declined

Behavior: 1. Validates due date is set 2. Calls post() to create journal entry 3. Changes state to waiting_payment

Example:

# Set due date and authorize
get_model("account.claim").write([claim_id], {"due_date": "2024-12-31"})
get_model("account.claim").do_authorize([claim_id])


2. Post Journal Entry

Method: post(ids, context)

Creates and posts the journal entry for the claim.

Journal Entry Structure: - Credit: Unpaid claims account (total approved amount) - Debit: Expense accounts (by line) - Debit: Tax accounts (if applicable)

Prerequisites: - unpaid_claim_id must be configured in Settings - general_journal_id must be configured in Settings

Example:

get_model("account.claim").post([claim_id])


3. Void Claim

Method: void(ids, context)

Voids/cancels the claim.

Prerequisites: - Claim must not have any payments

Behavior: 1. Validates no payments exist 2. Deletes journal entry if exists 3. Changes state to voided

Example:

get_model("account.claim").void([claim_id])


4. Get Amount

Method: get_amount(ids, context)

Calculates totals from expenses and payments.

Returns:

{
    "amount_total": sum(expense.amount_total for all expenses),
    "amount_approved": sum(expense.amount_total for approved expenses),
    "amount_paid": sum(payment.amount for all payments),
    "amount_due": amount_approved - amount_paid,
}


5. Get State

Method: get_state(ids, context)

Computes state based on payment status.

Logic:

if state == "waiting_payment" and amount_due == 0:
    return "paid"
elif state == "paid" and amount_due > 0:
    return "waiting_payment"
else:
    return state


6. Get Can Authorize

Method: get_can_authorize(ids, context)

Checks if all expenses have been reviewed.

Returns: True if all expenses are approved or declined


Model Relationship Description
account.expense One2Many (expenses) Expense receipts
account.payment.line One2Many (payments) Payment allocations
account.move Many2One (move_id) Journal entry
base.user Many2One (user_id) Claim owner
account.account Many2One (account_id) Unpaid claims account

Common Use Cases

Use Case 1: Create and Submit Claim

# Create claim
claim_id = get_model("account.claim").create({
    "user_id": user_id,
})

# Add expenses to claim
expense_ids = get_model("account.expense").search([
    ["user_id", "=", user_id],
    ["claim_id", "=", None],
    ["state", "=", "approved"]
])
get_model("account.expense").write(expense_ids, {"claim_id": claim_id})

Use Case 2: Authorize Claim

# Set due date
get_model("account.claim").write([claim_id], {
    "due_date": "2024-12-31"
})

# Check if can authorize
claim = get_model("account.claim").browse([claim_id])[0]
if claim.can_authorize:
    get_model("account.claim").do_authorize([claim_id])

Use Case 3: Process Payment

# Use claim.payment wizard
wizard_id = get_model("claim.payment").create({
    "claim_id": claim_id,
    "amount": claim.amount_due,
    "date": "2024-12-15",
    "account_id": bank_account_id,
})

get_model("claim.payment").add_payment([wizard_id])

Use Case 4: Track Claim Status

claim = get_model("account.claim").browse([claim_id])[0]

print(f"Claim: {claim.number}")
print(f"State: {claim.state}")
print(f"Total: {claim.amount_total}")
print(f"Approved: {claim.amount_approved}")
print(f"Paid: {claim.amount_paid}")
print(f"Due: {claim.amount_due}")
print(f"Receipts: {claim.num_receipts}")

Journal Entry Structure

When authorized, creates:

Date: [claim date]
Narration: "Claim: [number]"

Credit:
  - Unpaid Claims Account: [amount_approved]

Debit:
  - Expense Account 1: [base_amount_1]
  - Expense Account 2: [base_amount_2]
  - Tax Account: [tax_amount]  (if applicable)

Best Practices

1. Set Due Date Before Authorization

# Good: Due date set
get_model("account.claim").write([claim_id], {"due_date": "2024-12-31"})
get_model("account.claim").do_authorize([claim_id])

# Bad: Missing due date (will fail)
get_model("account.claim").do_authorize([claim_id])

2. Review All Expenses First

# Good: Ensure all expenses are reviewed
claim = get_model("account.claim").browse([claim_id])[0]
if not claim.can_authorize:
    # Find pending expenses
    for exp in claim.expenses:
        if exp.state not in ("approved", "declined"):
            print(f"Pending: {exp.ref}")

3. Configure Settings

Ensure these are configured: - settings.unpaid_claim_id - Account for unpaid claims - settings.general_journal_id - General journal


Troubleshooting

"Missing payment due date"

Cause: Trying to authorize without due_date Solution: Set due_date before calling do_authorize

"Missing unpaid expense claims account"

Cause: unpaid_claim_id not configured in Settings Solution: Configure the unpaid claims account in Settings

"General journal not found"

Cause: general_journal_id not configured in Settings Solution: Configure the general journal in Settings

"This claim is already paid"

Cause: Trying to void a claim with payments Solution: Reverse payments first, then void


Testing Examples

Unit Test: Claim Authorization

def test_claim_authorization():
    # Create claim with approved expense
    claim_id = get_model("account.claim").create({})

    expense_id = get_model("account.expense").create({
        "claim_id": claim_id,
        "ref": "TEST-001",
        "contact_id": vendor_id,
        "date": "2024-12-15",
        "tax_type": "no_tax",
        "state": "approved",
        "lines": [("create", {
            "description": "Test",
            "qty": 1,
            "unit_price": 100.00,
            "account_id": expense_account_id,
        })]
    })

    claim = get_model("account.claim").browse([claim_id])[0]
    assert claim.amount_approved == 100.00
    assert claim.can_authorize == True

    # Authorize
    claim.write({"due_date": "2024-12-31"})
    get_model("account.claim").do_authorize([claim_id])

    claim = get_model("account.claim").browse([claim_id])[0]
    assert claim.state == "waiting_payment"
    assert claim.move_id is not None

Security Considerations

Permission Model

  • Users can view their own claims
  • Managers can authorize claims
  • Finance can view and process all claims

Data Access

  • Claims linked to users via user_id
  • Journal entries require proper accounting permissions

Configuration Requirements

Setting Location Description
unpaid_claim_id Settings Account for unpaid claims liability
general_journal_id Settings Journal for claim entries

Version History

Last Updated: December 2024 Model Version: account_claim.py Framework: Netforce


Additional Resources

  • Account Expense Documentation: account.expense
  • Claim Payment Wizard: claim.payment
  • Payment Documentation: account.payment
  • Journal Entry Documentation: account.move

This documentation is generated for developer onboarding and reference purposes.