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:
Returns: Dict mapping IDs to overpay amounts
Related Models¶
| 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.