Skip to content

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

_defaults = {
    "invoice_id": _get_invoice,   # From context parent_id
}

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

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.