Sales Return Line Documentation¶
Overview¶
The Sales Return Line module (sale.return.line) represents individual line items within a sales return document. Each line captures details about a specific product being returned, including quantity, pricing, return reason, and condition. This model works in conjunction with sale.return to provide granular tracking of returned items and integrates with inventory and accounting systems for accurate financial and stock reporting.
Model Information¶
Model Name: sale.return.line
Display Name: Sales Return Line
Name Field: order_id (references parent return)
Features¶
- ❌ Audit logging enabled
- ❌ Multi-company support (inherited from parent)
- ❌ Full-text content search
- ❌ Unique key constraint
- ✅ Automatic cascade delete with parent return
Key Fields Reference¶
Core Line Item Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
Many2One | ✅ | Parent sales return (on_delete="cascade") |
product_id |
Many2One | ❌ | Product being returned |
description |
Text | ✅ | Description of the return item |
qty |
Decimal | ❌ | Quantity being returned |
uom_id |
Many2One | ❌ | Unit of measure |
unit_price |
Decimal | ✅ | Unit price for the return (scale=6) |
tax_id |
Many2One | ❌ | Tax rate applied |
Return-Specific Fields¶
| Field | Type | Description |
|---|---|---|
return_type |
Selection | Type of return: refund/exchange |
reason_code_id |
Many2One | Reason code for the return (type="sale_return") |
remark |
Char | Additional notes about the return |
sequence |
Char | Item sequence/line number |
Quantity Tracking Fields¶
| Field | Type | Description |
|---|---|---|
qty_stock |
Decimal | Quantity in stock UoM (for UoM conversion) |
qty_received |
Decimal | Quantity received from customer (computed) |
qty_invoiced |
Decimal | Quantity already credited (computed) |
qty2 |
Decimal | Secondary quantity (for dual UoM tracking) |
Financial Fields (Computed)¶
| Field | Type | Description |
|---|---|---|
amount |
Decimal | Line total amount (stored, function order 1) |
amount_cur |
Decimal | Amount in company currency (stored) |
amount_discount |
Decimal | Total discount amount on this line |
discount |
Decimal | Discount percentage |
discount_amount |
Decimal | Fixed discount amount |
Location & Logistics¶
| Field | Type | Description |
|---|---|---|
location_id |
Many2One | Stock location where items will be received |
ship_method_id |
Many2One | Shipping method for this line item |
qty_avail |
Decimal | Current stock available at location (computed) |
Related Fields (Function Fields)¶
| Field | Type | Description |
|---|---|---|
contact_id |
Many2One | Customer (from order_id.contact_id) |
date |
Date | Return date (from order_id.date) |
user_id |
Many2One | Owner (from order_id.user_id) |
state |
Selection | Return state (from order_id.state) |
product_categs |
Many2Many | Product categories (from product_id.categs) |
Aggregation Fields¶
| Field | Type | Description |
|---|---|---|
agg_amount |
Decimal | Sum of line amounts (for reporting) |
agg_qty |
Decimal | Sum of quantities (for reporting) |
API Methods¶
1. Create Record¶
Method: create(vals, context)
Creates a new return line item.
Parameters:
vals = {
"order_id": 123, # Required: Parent return ID
"product_id": 789, # Product being returned
"description": "Defective", # Required: Description
"qty": 2, # Quantity to return
"unit_price": 100.00, # Required: Unit price
"uom_id": 1, # Unit of measure
"tax_id": 5, # Tax rate
"location_id": 10, # Destination location
"return_type": "refund", # Return type
"reason_code_id": 15, # Reason code
"discount": 10.0, # Discount percentage
"discount_amount": 5.00 # Fixed discount
}
Behavior: - Auto-triggers function_store to calculate amounts - Cascades to parent return for amount recalculation
Returns: int - New line ID
Example:
# Add a return line for defective product
line_id = get_model("sale.return.line").create({
"order_id": 123,
"product_id": 789,
"description": "Widget - manufacturing defect",
"qty": 2,
"unit_price": 100.00,
"uom_id": 1,
"tax_id": 5,
"location_id": 10,
"return_type": "refund",
"reason_code_id": 15
})
2. Update Record¶
Method: write(ids, vals, context)
Updates existing return line(s).
Behavior: - Triggers function_store to recalculate amounts - Updates parent return totals automatically
Example:
# Update quantity and price
get_model("sale.return.line").write([line_id], {
"qty": 3,
"unit_price": 95.00
})
Computed Fields Functions¶
get_amount(ids, context)¶
Calculates financial amounts for each line:
- amount: qty × unit_price - discount - discount_amount
- amount_cur: Amount converted to company currency
- amount_discount: Total discount (percentage + fixed)
Calculation Logic:
amt = line.qty * line.unit_price
if line.discount:
disc = amt * line.discount / 100
else:
disc = 0
if line.discount_amount:
disc += line.discount_amount
amt -= disc
Returns: Dictionary with calculated amounts for each line
get_qty_received(ids, context)¶
Calculates quantity received from customer based on completed stock moves.
Behavior: - Aggregates done stock moves from parent return - Groups by product and location - Handles multiple lines for same product - Allocates received quantities to lines in order - Handles surplus quantities by adding to last lines
Returns: Dictionary mapping line IDs to received quantities
get_qty_invoiced(ids, context)¶
Calculates quantity already credited on invoices.
Behavior: - Scans all credit notes from parent return - Matches by product_id (or description if no product) - Excludes voided invoices - Handles both out and in invoice types
Returns: Dictionary mapping line IDs to invoiced quantities
get_qty_avail(ids, context)¶
Returns current stock available at the line's location.
Behavior: - Calls stock.location.compute_balance for the product/location - Returns None if product or location not specified
Returns: Dictionary mapping line IDs to available quantities
Related Fields Functions¶
_get_related(ids, context)¶
Generic function to retrieve related field values from parent return or product.
Used For:
- contact_id: From order_id.contact_id
- date: From order_id.date
- user_id: From order_id.user_id
- state: From order_id.state
- product_categs: From product_id.categs
_search_related(clause, context)¶
Enables searching on related fields.
Example:
# Search lines by customer
lines = get_model("sale.return.line").search([
["contact_id", "=", 123]
])
# Search lines by state
lines = get_model("sale.return.line").search([
["state", "=", "confirmed"]
])
Search Functions¶
Search by Product¶
# Find all return lines for a specific product
condition = [["product_id", "=", product_id]]
line_ids = get_model("sale.return.line").search(condition)
Search by Customer¶
# Find all return lines for a customer
condition = [["contact_id", "=", customer_id]]
line_ids = get_model("sale.return.line").search(condition)
Search by State¶
# Find all confirmed return lines
condition = [["state", "=", "confirmed"]]
line_ids = get_model("sale.return.line").search(condition)
Search by Product Category¶
# Find returns for products in a category
condition = [["product_categs", "in", [category_id]]]
line_ids = get_model("sale.return.line").search(condition)
Search by Date Range¶
# Find return lines in a date range
condition = [
["date", ">=", "2025-01-01"],
["date", "<=", "2025-01-31"]
]
line_ids = get_model("sale.return.line").search(condition)
Best Practices¶
1. Always Set Location for Stock Products¶
# Bad: Missing location for stock product
{
"product_id": 789, # Stock product
"qty": 5,
"unit_price": 100.00
# Missing location_id!
}
# Good: Location specified
{
"product_id": 789,
"qty": 5,
"unit_price": 100.00,
"location_id": 10, # Warehouse location
"return_type": "refund",
"reason_code_id": 15
}
2. Use Reason Codes for Better Tracking¶
# Good: Detailed return tracking
{
"product_id": 789,
"qty": 2,
"unit_price": 100.00,
"return_type": "refund",
"reason_code_id": 15, # e.g., "Defective"
"remark": "Widget not functioning - motor failure"
}
3. Handle UoM Conversions Properly¶
# When using different UoM than product default
line_vals = {
"product_id": 789,
"qty": 10, # 10 boxes
"uom_id": box_uom_id,
"qty_stock": 100, # 100 pieces in stock UoM
"unit_price": 50.00 # Price per box
}
Related Models¶
| Model | Relationship | Description |
|---|---|---|
sale.return |
Many2One | Parent return document |
product |
Many2One | Product being returned |
contact |
Function Field | Customer (from parent) |
uom |
Many2One | Unit of measure |
account.tax.rate |
Many2One | Tax rate for line |
stock.location |
Many2One | Destination warehouse location |
ship.method |
Many2One | Shipping method |
reason.code |
Many2One | Return reason code |
Common Use Cases¶
Use Case 1: Create Return Line with Full Details¶
# Complete return line with all tracking fields
line_id = get_model("sale.return.line").create({
"order_id": return_id,
"product_id": 789,
"description": "Wireless Mouse - Model XYZ",
"qty": 5,
"uom_id": 1, # PCS
"unit_price": 25.00,
"tax_id": 5, # VAT 7%
"location_id": 10, # Main Warehouse
"discount": 10.0, # 10% discount
"return_type": "refund",
"reason_code_id": 15, # Defective
"remark": "Mouse buttons not working properly",
"sequence": "001"
})
Use Case 2: Check Line Status¶
# Check quantities and status
line = get_model("sale.return.line").browse([line_id])[0]
print(f"Product: {line.product_id.name}")
print(f"Ordered Qty: {line.qty}")
print(f"Received Qty: {line.qty_received}")
print(f"Invoiced Qty: {line.qty_invoiced}")
print(f"Pending Receipt: {line.qty - line.qty_received}")
print(f"Pending Credit: {line.qty - line.qty_invoiced}")
print(f"Amount: {line.amount}")
print(f"State: {line.state}")
Use Case 3: Bulk Update Return Lines¶
# Update multiple lines with same discount
return_obj = get_model("sale.return").browse([return_id])[0]
line_ids = [line.id for line in return_obj.lines]
get_model("sale.return.line").write(line_ids, {
"discount": 15.0 # Apply 15% discount to all lines
})
Use Case 4: Find Unreceived Return Lines¶
# Find lines where goods not yet received
all_lines = get_model("sale.return.line").search([
["state", "=", "confirmed"]
])
unreceived_lines = []
for line in get_model("sale.return.line").browse(all_lines):
if line.product_id and line.product_id.type == "stock":
if line.qty_received < line.qty:
unreceived_lines.append(line.id)
print(f"Found {len(unreceived_lines)} lines waiting for receipt")
Performance Tips¶
1. Stored Function Fields¶
amountandamount_curare stored for fast retrieval- Recalculated automatically on line changes
- Use
function_order=1to ensure proper calculation sequence
2. Batch Browsing¶
# Bad: One query per line
for line_id in line_ids:
line = get_model("sale.return.line").browse([line_id])[0]
process_line(line)
# Good: Single query for all lines
lines = get_model("sale.return.line").browse(line_ids)
for line in lines:
process_line(line)
3. Aggregation Fields¶
Use aggregation fields for reporting instead of manual sum:
# Use built-in aggregation
result = get_model("sale.return.line").search_read(
[["contact_id", "=", 123]],
["agg_amount", "agg_qty"],
group=["contact_id"]
)
Troubleshooting¶
"Description is required"¶
Cause: Creating line without description field Solution: Always provide description, even if using product name
Incorrect Amount Calculation¶
Cause: Amount not recalculating after changes Solution: Amounts auto-recalculate via function_store; check parent return state
Qty Received Not Updating¶
Cause: Stock moves not in "done" state Solution: Complete the goods receipt picking to update qty_received
Qty Invoiced Not Matching¶
Cause: Credit note voided or not posted Solution: Check credit note state; only non-voided invoices count
Testing Examples¶
Unit Test: Line Amount Calculation¶
def test_line_amount_calculation():
# Create return
return_id = get_model("sale.return").create({
"contact_id": 123,
"date": "2025-01-05",
"currency_id": 1,
"tax_type": "tax_ex"
})
# Create line with discount
line_id = get_model("sale.return.line").create({
"order_id": return_id,
"description": "Test Product",
"qty": 10,
"unit_price": 100.00,
"discount": 10.0, # 10% discount
"discount_amount": 50.00 # Additional $50 off
})
# Verify calculation
line = get_model("sale.return.line").browse([line_id])[0]
# Expected: (10 * 100) - (1000 * 0.1) - 50 = 1000 - 100 - 50 = 850
assert line.amount == 850.00
assert line.amount_discount == 150.00
Security Considerations¶
Data Access¶
- Lines inherit security from parent return
- Cascade delete ensures data integrity
- No direct multi-company field (uses parent)
Validation¶
- Parent return must exist (on_delete="cascade")
- Unit price required for financial accuracy
- Description required for clarity
Configuration Settings¶
Required Settings¶
| Setting | Location | Description |
|---|---|---|
| UoM | Product | Default unit of measure |
| Tax Rates | Account Settings | Tax configurations |
| Locations | Stock | Warehouse locations |
| Reason Codes | Settings | Return reason codes (type="sale_return") |
Integration Points¶
Stock Management¶
- stock.move: Tracked via qty_received computation
- stock.location: Destination for returned goods
- product: Product information and stock tracking
Accounting¶
- account.invoice.line: Credit note lines (qty_invoiced)
- account.tax.rate: Tax calculations
- currency: Multi-currency amounts
Parent Document¶
- sale.return: Parent document for all line data
- Changes propagate to parent totals
Version History¶
Last Updated: 2025-01-05 Model Version: sale_return_line.py (168 lines) Framework: Netforce
Additional Resources¶
- Sales Return Documentation:
sale.return - Product Documentation:
product - Stock Location Documentation:
stock.location - Reason Code Documentation:
reason.code
Support & Feedback¶
For issues or questions about this module: 1. Check parent return documentation 2. Verify product and location configuration 3. Check reason code setup 4. Review stock move status for qty_received issues 5. Check invoice status for qty_invoiced issues
This documentation is generated for developer onboarding and reference purposes.