Invoice Refund Documentation¶
Overview¶
The Invoice Refund model (invoice.refund) is a transient wizard model that handles refund processing for credit notes, prepayments, and overpayments. It creates outgoing payments to return funds to customers or receive refunds from suppliers.
Model Information¶
Model Name: invoice.refund
Display Name: Invoice Refund
Key Fields: None (transient model)
Features¶
- ✅ Transient model (temporary data)
- ❌ Audit logging enabled (
_audit_log) - ❌ Multi-company support (
company_id) - ✅ Refund validation against remaining credit
- ✅ Automatic payment creation and posting
Key Fields Reference¶
All Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
amount |
Decimal | ✅ | Refund amount |
date |
Date | ✅ | Refund date |
account_id |
Many2One | ✅ | Bank/cash account for refund |
invoice_id |
Many2One | ✅ | Credit note/prepay being refunded |
ref |
Char | ❌ | Reference number |
Account Filtering¶
The account_id field is filtered to show only:
- Bank accounts (type = "bank")
- Cash accounts (type = "cash")
- Cheque accounts (type = "cheque")
Default Values¶
Supported Invoice Types¶
The refund wizard works with these invoice types:
| Invoice Type | Code | Description |
|---|---|---|
| Credit Note | credit |
Credit note to be refunded |
| Prepayment | prepay |
Advance payment to be returned |
| Overpayment | overpay |
Excess payment to be refunded |
API Methods¶
1. Add Refund¶
Method: add_refund(ids, context)
Creates and posts a refund payment.
Behavior:
1. Validates invoice type is credit, prepay, or overpay
2. Validates refund amount doesn't exceed remaining credit
3. Creates payment record with same type as invoice
4. Posts the payment
5. Returns navigation to invoice view
Example:
# Create refund wizard
wizard_id = get_model("invoice.refund").create({
"invoice_id": credit_note_id,
"amount": 500.00,
"date": "2024-12-15",
"account_id": bank_account_id,
"ref": "REFUND-001",
})
# Process refund
result = get_model("invoice.refund").add_refund([wizard_id])
Raises:
- AssertionError - If invoice type is not credit/prepay/overpay
- Exception("Amount refunded exceeds the remaining credit") - If amount > amount_credit_remain
Payment Type Mapping¶
| Invoice Type | Payment Direction | Description |
|---|---|---|
out (Customer) |
out |
Refund to customer |
in (Supplier) |
in |
Receive refund from supplier |
Related Models¶
| Model | Relationship | Description |
|---|---|---|
account.invoice |
Many2One (invoice_id) | Credit note being refunded |
account.account |
Many2One (account_id) | Payment account |
account.payment |
Created | Refund payment record |
Common Use Cases¶
Use Case 1: Refund Customer Credit Note¶
# Customer has a credit note for returned goods
credit_note = get_model("account.invoice").browse([credit_note_id])[0]
# Create refund wizard
wizard_id = get_model("invoice.refund").create({
"invoice_id": credit_note_id,
"amount": credit_note.amount_credit_remain, # Full refund
"date": "2024-12-15",
"account_id": bank_account_id,
"ref": "CHQ-REFUND-001",
})
# Process refund
get_model("invoice.refund").add_refund([wizard_id])
Use Case 2: Partial Refund of Prepayment¶
# Customer prepaid but cancelled part of order
wizard_id = get_model("invoice.refund").create({
"invoice_id": prepay_invoice_id,
"amount": 200.00, # Partial refund
"date": "2024-12-15",
"account_id": cash_account_id,
"ref": "PARTIAL-REFUND",
})
get_model("invoice.refund").add_refund([wizard_id])
Use Case 3: Receive Supplier Refund¶
# Supplier issued credit note, now sending refund
supplier_credit = get_model("account.invoice").browse([supplier_credit_id])[0]
wizard_id = get_model("invoice.refund").create({
"invoice_id": supplier_credit_id,
"amount": supplier_credit.amount_credit_remain,
"date": "2024-12-15",
"account_id": bank_account_id,
"ref": "SUPPLIER-REFUND-001",
})
get_model("invoice.refund").add_refund([wizard_id])
Refund Flow¶
1. Credit Note/Prepay/Overpay exists with credit balance
│
2. User initiates refund
│
3. Refund wizard displayed
│
├─ Enter refund amount
├─ Select payment account
└─ Add reference
│
4. System validates amount <= credit remaining
│
5. Payment created and posted
│
6. Credit balance reduced
Best Practices¶
1. Verify Credit Balance First¶
# Good: Check remaining credit before refund
invoice = get_model("account.invoice").browse([invoice_id])[0]
if invoice.amount_credit_remain < requested_refund:
raise Exception(f"Cannot refund {requested_refund}, only {invoice.amount_credit_remain} available")
wizard_id = get_model("invoice.refund").create({
"amount": requested_refund,
...
})
2. Include Reference for Tracking¶
# Good: Clear reference for reconciliation
wizard = get_model("invoice.refund").create({
"ref": f"REFUND-{credit_note.number}",
...
})
3. Use Appropriate Account¶
# Good: Match refund method to original payment
# If original was bank transfer, refund via bank
wizard = get_model("invoice.refund").create({
"account_id": bank_account_id,
...
})
Troubleshooting¶
"Amount refunded exceeds the remaining credit"¶
Cause: Trying to refund more than available credit Solution: Reduce amount to invoice.amount_credit_remain or less
"AssertionError (invoice type)"¶
Cause: Trying to refund a regular invoice or debit note Solution: Only use this wizard for credit/prepay/overpay types
"No accounts in dropdown"¶
Cause: No bank/cash/cheque accounts configured Solution: Create appropriate account types
Testing Examples¶
Unit Test: Credit Note Refund¶
def test_credit_note_refund():
# Create and post credit note
credit_id = get_model("account.invoice").create({
"type": "out",
"inv_type": "credit",
"contact_id": customer_id,
"lines": [("create", {
"description": "Return",
"qty": 1,
"unit_price": 500.00,
})]
})
get_model("account.invoice").post([credit_id])
credit = get_model("account.invoice").browse([credit_id])[0]
assert credit.amount_credit_remain == 500.00
# Create refund wizard
wizard_id = get_model("invoice.refund").create({
"invoice_id": credit_id,
"amount": 500.00,
"date": "2024-12-15",
"account_id": bank_account_id,
})
# Process refund
get_model("invoice.refund").add_refund([wizard_id])
# Verify credit is used
credit = get_model("account.invoice").browse([credit_id])[0]
assert credit.amount_credit_remain == 0
Security Considerations¶
Permission Model¶
- Users need refund/payment creation permissions
- Refunds should be reviewed/approved
Data Access¶
- Transient model - wizard data not persisted
- Creates permanent payment records
Version History¶
Last Updated: December 2024 Model Version: invoice_refund.py Framework: Netforce
Additional Resources¶
- Invoice Documentation:
account.invoice - Payment Documentation:
account.payment - Credit Allocation Documentation:
account.credit.alloc
This documentation is generated for developer onboarding and reference purposes.