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:
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')])}")
Related Models¶
| 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.