Skip to content

Sales Payment Report Documentation

Overview

The Sales Payment Report module (report.sale.payment) tracks and analyzes customer payments received for sales invoices. It provides detailed cash flow analysis grouped by payment method and bank account, enabling businesses to monitor collection efficiency and reconcile payment receipts.


Model Information

Model Name: report.sale.payment Display Name: Sales Payment Report Type: Transient Report Model (_transient = True)

Features

  • Payment tracking by invoice and order
  • Grouping by payment method and account
  • Account-level subtotals
  • Customer contact information
  • Date range filtering for orders and invoices
  • Integration with accounting system

Key Fields Reference

Report Parameters

Field Type Required Description
order_date_from Date No Filter by order date from
order_date_to Date No Filter by order date to
invoice_date_from Date No Filter by invoice date from (default: first day of month)
invoice_date_to Date No Filter by invoice date to (default: last day of month)
pay_method_id Many2One No Filter by specific payment method
account_id Many2One No Filter by specific bank/cash account

Output Fields

Field Description
order_id Sales order ID
order_number Sales order number
invoice_id Invoice ID
invoice_number Invoice number
related_id Related document ID (if any)
related_number Related document number
ref Customer reference
invoice_date Invoice date
payment_id Payment record ID
payment_date Date payment received
customer_name Customer name
phone Customer phone number
pay_method Payment method name
account_name Bank/cash account name
amount Payment amount

Payment Query Logic

The report queries posted payments linked to sales invoices:

cond = [
    ["payment_id.state", "=", "posted"],        # Only posted payments
    ["type", "=", "invoice"],                    # Payment allocations to invoices
    ["invoice_id.type", "=", "out"]             # Outgoing (sales) invoices only
]

# Optional filters
if invoice_date_from:
    cond.append(["invoice_id.date", ">=", invoice_date_from])
if invoice_date_to:
    cond.append(["invoice_id.date", "<=", invoice_date_to])
if account_id:
    cond.append(["payment_id.account_id", "=", account_id])

Key Behavior: - Only includes posted (not draft) payments - Requires link from payment to sales invoice - Invoice must be linked to sales order via related_id - Orders by payment date, then ID


Report Grouping and Subtotals

The report automatically groups payments and calculates subtotals:

Grouping Structure

Payment Method: Cash
  Account: Cash Register 1
    - Payment line 1
    - Payment line 2
    - Payment line 3
    --- Account Total: $5,000

  Account: Cash Register 2
    - Payment line 4
    - Payment line 5
    --- Account Total: $3,000

Payment Method: Bank Transfer
  Account: Bank Account ABC
    - Payment line 6
    - Payment line 7
    --- Account Total: $10,000

Grand Total: $18,000

Subtotal Rows

Account total rows have this structure:

{
    "order_number": "Account Total",
    "pay_method": "Cash",
    "account_name": "Cash Register 1",
    "amount": 5000.00,
    # Other fields are empty strings
}


API Methods

1. Generate Payment Report

Method: get_report_data(ids, context)

Generates payment tracking report with account subtotals.

Parameters:

ids = [report_id]  # Report configuration ID

context = {}  # Optional context

Returns: dict - Report data:

{
    "company_name": "ABC Company",
    "order_date_from": "2024-01-01",
    "order_date_to": "2024-01-31",
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31",
    "total": 50000.00,
    "lines": [
        {
            "order_id": 123,
            "order_number": "SO-2024-001",
            "invoice_id": 456,
            "invoice_number": "INV-2024-001",
            "related_id": 789,
            "related_number": "REL-001",
            "ref": "CUST-REF-001",
            "invoice_date": "2024-01-15",
            "payment_id": 321,
            "payment_date": "2024-01-20",
            "customer_name": "ABC Corp",
            "phone": "555-1234",
            "pay_method": "Bank Transfer",
            "account_name": "Main Bank Account",
            "amount": 10000.00
        },
        # ... more payment lines
        {
            "order_number": "Account Total",
            "pay_method": "Bank Transfer",
            "account_name": "Main Bank Account",
            "amount": 10000.00
            # Subtotal row
        }
    ]
}

Example:

# Generate payment report for January 2024
report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31"
})

data = get_model("report.sale.payment").get_report_data([report_id])

print(f"Total Payments: ${data['total']:,.2f}")
print(f"Number of payment lines: {len([l for l in data['lines'] if l.get('payment_id')])}")


Model Relationship Description
account.payment Data Source Payment records
account.payment.line Data Source Payment allocation to invoices
account.invoice Many2One Sales invoices
sale.order Many2One Related sales orders
contact Many2One Customer information
payment.method Many2One Payment method (cash, bank, etc.)
account.account Many2One Bank/cash account

Common Use Cases

Use Case 1: Daily Cash Receipts Report

# Track all cash received today

from datetime import date

today = date.today().strftime("%Y-%m-%d")

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": today,
    "invoice_date_to": today
})

data = get_model("report.sale.payment").get_report_data([report_id])

# Group by payment method
from collections import defaultdict
by_method = defaultdict(float)

for line in data["lines"]:
    if line.get("payment_id"):  # Skip subtotal rows
        by_method[line["pay_method"]] += line["amount"]

print("Daily Cash Receipts:")
for method, amount in by_method.items():
    print(f"  {method}: ${amount:,.2f}")
print(f"Total: ${data['total']:,.2f}")

Use Case 2: Bank Reconciliation

# Generate report for specific bank account

bank_account_id = 45  # Your bank account ID

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31",
    "account_id": bank_account_id
})

data = get_model("report.sale.payment").get_report_data([report_id])

# Extract payments for reconciliation
bank_payments = []
for line in data["lines"]:
    if line.get("payment_id"):
        bank_payments.append({
            "date": line["payment_date"],
            "reference": line["invoice_number"],
            "customer": line["customer_name"],
            "amount": line["amount"]
        })

# Compare with bank statement
print(f"Total deposits to reconcile: ${data['total']:,.2f}")
print(f"Number of transactions: {len(bank_payments)}")

Use Case 3: Customer Payment History

# Track payment patterns for credit review

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-12-31"
})

data = get_model("report.sale.payment").get_report_data([report_id])

# Aggregate by customer
from collections import defaultdict
from datetime import datetime

customer_payments = defaultdict(lambda: {"amount": 0, "count": 0, "last_payment": None})

for line in data["lines"]:
    if line.get("payment_id"):
        cust = line["customer_name"]
        customer_payments[cust]["amount"] += line["amount"]
        customer_payments[cust]["count"] += 1

        payment_date = datetime.strptime(line["payment_date"], "%Y-%m-%d")
        if not customer_payments[cust]["last_payment"] or payment_date > customer_payments[cust]["last_payment"]:
            customer_payments[cust]["last_payment"] = payment_date

# Show top paying customers
sorted_customers = sorted(customer_payments.items(), key=lambda x: x[1]["amount"], reverse=True)
print("Top 10 Paying Customers:")
for i, (customer, info) in enumerate(sorted_customers[:10], 1):
    print(f"{i}. {customer}:")
    print(f"   Total Paid: ${info['amount']:,.2f}")
    print(f"   Transactions: {info['count']}")
    print(f"   Last Payment: {info['last_payment'].strftime('%Y-%m-%d')}")

Use Case 4: Payment Method Analysis

# Analyze preferred payment methods

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-12-31"
})

data = get_model("report.sale.payment").get_report_data([report_id])

# Count and sum by payment method
method_stats = {}
for line in data["lines"]:
    if line.get("payment_id"):
        method = line["pay_method"] or "Unknown"
        if method not in method_stats:
            method_stats[method] = {"count": 0, "total": 0}
        method_stats[method]["count"] += 1
        method_stats[method]["total"] += line["amount"]

# Calculate averages and percentages
grand_total = data["total"]
print("Payment Method Analysis:")
for method, stats in sorted(method_stats.items(), key=lambda x: x[1]["total"], reverse=True):
    avg = stats["total"] / stats["count"]
    pct = (stats["total"] / grand_total) * 100
    print(f"\n{method}:")
    print(f"  Transactions: {stats['count']}")
    print(f"  Total: ${stats['total']:,.2f} ({pct:.1f}%)")
    print(f"  Average: ${avg:,.2f}")

Use Case 5: Monthly Collection Trend

# Track collection trends over time

from datetime import datetime
from dateutil.relativedelta import relativedelta

# Generate reports for each month of the year
monthly_data = []
start_date = datetime(2024, 1, 1)

for i in range(12):
    month_start = (start_date + relativedelta(months=i)).strftime("%Y-%m-01")
    month_end = (start_date + relativedelta(months=i, day=31)).strftime("%Y-%m-%d")

    report_id = get_model("report.sale.payment").create({
        "invoice_date_from": month_start,
        "invoice_date_to": month_end
    })

    data = get_model("report.sale.payment").get_report_data([report_id])

    monthly_data.append({
        "month": (start_date + relativedelta(months=i)).strftime("%B %Y"),
        "total": data["total"],
        "transactions": len([l for l in data["lines"] if l.get("payment_id")])
    })

# Display trend
print("Monthly Collection Trend:")
for month_info in monthly_data:
    print(f"{month_info['month']}: ${month_info['total']:,.2f} ({month_info['transactions']} txns)")

Performance Tips

1. Date Range Filtering

  • Use specific date ranges to limit query scope
  • Invoice date filtering is more efficient than order date filtering
  • Default to monthly reports for routine monitoring
# Good: Monthly range
invoice_date_from = "2024-01-01"
invoice_date_to = "2024-01-31"

# Less efficient: Open-ended range
invoice_date_from = "2020-01-01"
invoice_date_to = None  # Queries all invoices

2. Account Filtering

  • Filter by specific account when reconciling
  • Reduces result set and processing time
# Reconciling specific account
report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31",
    "account_id": specific_account_id  # Much faster
})

3. Payment State

  • Report only includes posted payments
  • Draft payments are excluded automatically
  • No additional filtering needed

Troubleshooting

"No payment data found"

Cause: No posted payments in date range, or invoices not linked to sales orders Solution: - Verify payments are in "posted" state - Check invoice dates fall within filter range - Ensure invoices have related_id pointing to sale.order - Confirm payment lines allocate to invoices (not journal entries)

"Customer phone number missing"

Cause: Phone not set on contact or addresses Solution: - Update contact record with phone number - Add phone to customer address records - Report will check both contact and address phone fields

"Account totals don't match"

Cause: Grouping/sorting issues or manual report data manipulation Solution: - Use report data as generated (pre-sorted and subtotaled) - Don't modify line order after generation - Verify payment amounts are correct in source records

"Payment shown but order number missing"

Cause: Payment not properly linked to sales order via invoice Solution: - Verify invoice related_id points to sale.order record - Check invoice type is "out" (outgoing/sales) - Ensure payment line type is "invoice"


Integration Points

Accounting System

# Export for accounting reconciliation

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31"
})

data = get_model("report.sale.payment").get_report_data([report_id])

# Format for accounting system
journal_entries = []
for line in data["lines"]:
    if line.get("payment_id"):  # Skip subtotals
        journal_entries.append({
            "date": line["payment_date"],
            "account": line["account_name"],
            "reference": line["invoice_number"],
            "description": f"Payment from {line['customer_name']}",
            "debit": line["amount"],
            "credit": 0
        })

export_to_accounting(journal_entries)

Cash Flow Forecasting

# Compare payment timing vs invoice dates

report_id = get_model("report.sale.payment").create({
    "invoice_date_from": "2024-01-01",
    "invoice_date_to": "2024-01-31"
})

data = get_model("report.sale.payment").get_report_data([report_id])

from datetime import datetime

payment_delays = []
for line in data["lines"]:
    if line.get("payment_id"):
        inv_date = datetime.strptime(line["invoice_date"], "%Y-%m-%d")
        pay_date = datetime.strptime(line["payment_date"], "%Y-%m-%d")
        days_delay = (pay_date - inv_date).days
        payment_delays.append(days_delay)

avg_delay = sum(payment_delays) / len(payment_delays)
print(f"Average payment delay: {avg_delay:.1f} days")

Version History

Last Updated: 2026-01-05 Model Version: report_sale_payment.py (174 lines) Framework: Netforce


Additional Resources

  • Payment Model Documentation: account.payment
  • Invoice Model Documentation: account.invoice
  • Sales Order Documentation: sale.order

Support & Feedback

For issues or questions about this module: 1. Verify payment states are "posted" 2. Check invoice-to-order linkages 3. Confirm date ranges cover expected data 4. Review payment method and account configurations 5. Test with narrow date ranges first


This documentation is generated for developer onboarding and reference purposes.