Skip to content

Invoice Payment Documentation

Overview

The Invoice Payment model (invoice.payment) is a transient wizard model that provides a streamlined interface for recording payments against invoices. It simplifies the payment process by automatically creating and posting payment records.


Model Information

Model Name: invoice.payment Display Name: Invoice Payment Key Fields: None (transient model)

Features

  • ✅ Transient model (temporary data, not persisted long-term)
  • ❌ Audit logging enabled (_audit_log)
  • ❌ Multi-company support (company_id)
  • ✅ Automatic payment creation and posting
  • ✅ Overpayment detection
  • ✅ Account filtering (bank/cash/cheque accounts)

Key Fields Reference

All Fields

Field Type Required Description
amount Decimal Payment amount
date Date Payment date
account_id Many2One Bank/cash account for payment
invoice_id Many2One Invoice being paid
track_id Many2One Tracking category
ref Char Payment reference
description Char Payment description
amount_overpay Decimal Computed overpayment amount

Account Filtering

The account_id field is filtered to show only: - Bank accounts (type = "bank") - Cash accounts (type = "cash") - Cheque accounts (type = "cheque") - Accounts with enable_payment = True


Default Values

_defaults = {
    "invoice_id": _get_invoice,                    # From context parent_id
    "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),  # Current datetime
}

API Methods

1. Add Payment

Method: add_payment(ids, context)

Creates and posts a payment for the invoice.

Behavior: 1. Validates invoice type is invoice or debit 2. Validates payment amount doesn't exceed amount due 3. Creates payment record with appropriate type (in/out) 4. Posts the payment 5. Returns navigation to invoice view

Example:

# Create wizard with payment details
wizard_id = get_model("invoice.payment").create({
    "invoice_id": invoice_id,
    "amount": 1000.00,
    "date": "2024-12-15",
    "account_id": bank_account_id,
    "ref": "CHQ-001",
})

# Process the payment
result = get_model("invoice.payment").add_payment([wizard_id])
# Payment is created and posted automatically

Raises: - Exception("Wrong invoice type") - If invoice is not invoice or debit - Exception("Amount paid exceeds due amount") - If amount > invoice.amount_due


2. Get Overpay

Method: get_overpay(ids, context)

Calculates the overpayment amount (payment amount minus amount due).

Formula:

amount_overpay = amount - invoice_id.amount_due

Returns: Dict mapping IDs to overpay amounts


Model Relationship Description
account.invoice Many2One (invoice_id) Invoice being paid
account.account Many2One (account_id) Payment account
account.track.categ Many2One (track_id) Tracking category
account.payment Created Payment record created by wizard

Common Use Cases

Use Case 1: Record Full Payment

# Get invoice
invoice = get_model("account.invoice").browse([invoice_id])[0]

# Create payment wizard
wizard_id = get_model("invoice.payment").create({
    "invoice_id": invoice_id,
    "amount": invoice.amount_due,  # Full payment
    "date": "2024-12-15",
    "account_id": bank_account_id,
    "ref": "BANK-TRF-001",
})

# Process payment
get_model("invoice.payment").add_payment([wizard_id])

Use Case 2: Record Partial Payment

# Record partial payment
wizard_id = get_model("invoice.payment").create({
    "invoice_id": invoice_id,
    "amount": 500.00,  # Partial amount
    "date": "2024-12-15",
    "account_id": cash_account_id,
    "ref": "CASH-001",
    "description": "Partial payment - 50%",
})

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

Use Case 3: Payment with Tracking

# Payment with cost center tracking
wizard_id = get_model("invoice.payment").create({
    "invoice_id": invoice_id,
    "amount": 1000.00,
    "date": "2024-12-15",
    "account_id": bank_account_id,
    "track_id": sales_dept_track_id,  # Track to Sales department
})

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

Payment Type Mapping

The wizard automatically determines payment type based on invoice type:

Invoice Type Payment Type Description
out (Customer Invoice) in (Receipt) Money coming in
in (Supplier Invoice) out (Payment) Money going out

Best Practices

1. Validate Amount Before Creating Wizard

# Good: Check amount before creating
invoice = get_model("account.invoice").browse([invoice_id])[0]
payment_amount = 1000.00

if payment_amount > invoice.amount_due:
    raise Exception(f"Payment {payment_amount} exceeds due amount {invoice.amount_due}")

wizard_id = get_model("invoice.payment").create({
    "invoice_id": invoice_id,
    "amount": payment_amount,
    ...
})

2. Use Appropriate Account Types

# Good: Use bank account for bank transfers
wizard = get_model("invoice.payment").create({
    "account_id": bank_account_id,  # type = "bank"
    ...
})

# Good: Use cash account for cash payments
wizard = get_model("invoice.payment").create({
    "account_id": cash_account_id,  # type = "cash"
    ...
})

3. Include Reference for Reconciliation

# Good: Include reference for easy reconciliation
wizard = get_model("invoice.payment").create({
    "ref": "CHQ-12345",  # Cheque number
    "description": "Payment for INV-2024-001",
    ...
})

Troubleshooting

"Wrong invoice type"

Cause: Trying to pay a credit note or prepayment Solution: Use this wizard only for invoice or debit type invoices

"Amount paid exceeds due amount"

Cause: Payment amount is greater than invoice's remaining balance Solution: Reduce payment amount to invoice.amount_due or less

"No accounts available in dropdown"

Cause: No bank/cash/cheque accounts configured Solution: Create accounts with type = bank/cash/cheque or enable_payment = True


Testing Examples

Unit Test: Full Payment

def test_invoice_payment_full():
    # Create invoice
    invoice_id = get_model("account.invoice").create({
        "type": "out",
        "contact_id": customer_id,
        "lines": [("create", {
            "description": "Test",
            "qty": 1,
            "unit_price": 1000.00,
        })]
    })
    get_model("account.invoice").post([invoice_id])

    invoice = get_model("account.invoice").browse([invoice_id])[0]
    assert invoice.amount_due == 1000.00

    # Create payment wizard
    wizard_id = get_model("invoice.payment").create({
        "invoice_id": invoice_id,
        "amount": 1000.00,
        "date": "2024-12-15",
        "account_id": bank_account_id,
    })

    # Process payment
    get_model("invoice.payment").add_payment([wizard_id])

    # Verify invoice is paid
    invoice = get_model("account.invoice").browse([invoice_id])[0]
    assert invoice.amount_due == 0
    assert invoice.state == "paid"

Security Considerations

Permission Model

  • Users need payment creation permissions
  • Account selection limited to appropriate account types

Data Access

  • Transient model - data not persisted long-term
  • Payment records are created in main payment model

Version History

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


Additional Resources

  • Invoice Documentation: account.invoice
  • Payment Documentation: account.payment
  • Account Documentation: account.account

This documentation is generated for developer onboarding and reference purposes.