Shipping Terms Documentation¶
Overview¶
The Shipping Terms module (ship.term) defines Incoterms and other shipping agreements that affect product pricing. Supports both fixed amount and percentage-based price adjustments for different shipping arrangements between buyer and seller.
Model Information¶
Model Name: ship.term
Display Name: Shipping Terms
Key Fields: None
Features¶
- ❌ No audit logging
- ❌ No multi-company support
- ❌ No full-text search
- ❌ No unique constraints
Key Fields Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
Char | ✅ | Terms name (e.g., FOB, CIF, EXW) |
description |
Text | ❌ | Detailed explanation of terms |
unit_price_diff |
Decimal | ❌ | Fixed price adjustment amount |
unit_price_diff_percent |
Decimal | ❌ | Percentage price adjustment |
Default Order: Ordered by name alphabetically
Understanding Price Adjustments¶
Fixed vs Percentage Adjustments¶
Fixed Amount Adjustment:
- Adds or subtracts a specific amount from unit price
- Example: -5.00 reduces price by $5 per unit
- Example: +10.00 increases price by $10 per unit
Percentage Adjustment:
- Adds or subtracts a percentage of unit price
- Example: -5.0 reduces price by 5%
- Example: +3.0 increases price by 3%
Note: Typically only one adjustment type is used per term, but both can be applied if needed.
Common Incoterms¶
International Commercial Terms¶
| Incoterm | Full Name | Seller Responsibility | Typical Adjustment |
|---|---|---|---|
| EXW | Ex Works | Minimal (buyer collects) | -5% to -10% |
| FOB | Free on Board | Delivers to ship | -3% to -5% |
| CIF | Cost, Insurance, Freight | Pays shipping + insurance | +2% to +5% |
| DDP | Delivered Duty Paid | Full delivery + duties | +5% to +10% |
| FCA | Free Carrier | Delivers to carrier | -2% to -4% |
| CFR | Cost and Freight | Pays shipping only | +1% to +3% |
API Methods¶
1. Create Shipping Term¶
Method: create(vals, context)
Creates a new shipping term with price adjustment rules.
Parameters:
vals = {
"name": str, # Required: Term name/code
"description": str, # Optional: Explanation
"unit_price_diff": Decimal, # Optional: Fixed amount adjustment
"unit_price_diff_percent": Decimal # Optional: Percentage adjustment
}
Returns: int - New record ID
Example:
# FOB with percentage discount
fob_id = get_model("ship.term").create({
"name": "FOB",
"description": "Free on Board - Seller delivers goods on board vessel",
"unit_price_diff_percent": -5.0 # 5% discount
})
# CIF with percentage increase
cif_id = get_model("ship.term").create({
"name": "CIF",
"description": "Cost, Insurance, Freight - Seller pays shipping and insurance",
"unit_price_diff_percent": 3.0 # 3% increase
})
# Fixed amount adjustment
ddp_id = get_model("ship.term").create({
"name": "DDP",
"description": "Delivered Duty Paid - Seller handles all costs",
"unit_price_diff": 15.00 # $15 increase per unit
})
2. Calculate Adjusted Price¶
Helper Function:
def calculate_adjusted_price(base_price, term_id):
"""
Calculate adjusted price based on shipping term
Args:
base_price: Original unit price
term_id: Shipping term ID
Returns:
Adjusted unit price
"""
term = get_model("ship.term").browse(term_id)
adjusted_price = base_price
# Apply fixed amount adjustment
if term.unit_price_diff:
adjusted_price += term.unit_price_diff
# Apply percentage adjustment
if term.unit_price_diff_percent:
adjustment = base_price * (term.unit_price_diff_percent / 100)
adjusted_price += adjustment
return adjusted_price
# Usage
base_price = 100.00
term_id = fob_id
final_price = calculate_adjusted_price(base_price, term_id)
print(f"Base: ${base_price}, Adjusted: ${final_price}")
Common Use Cases¶
Use Case 1: Setup Standard Incoterms¶
# EXW (Ex Works) - Buyer collects, lowest seller cost
get_model("ship.term").create({
"name": "EXW",
"description": "Ex Works - Buyer responsible for all transportation from seller's location",
"unit_price_diff_percent": -8.0 # 8% discount
})
# FOB (Free on Board)
get_model("ship.term").create({
"name": "FOB",
"description": "Free on Board - Seller delivers goods on board vessel, buyer pays shipping",
"unit_price_diff_percent": -5.0 # 5% discount
})
# CFR (Cost and Freight)
get_model("ship.term").create({
"name": "CFR",
"description": "Cost and Freight - Seller pays freight to destination port",
"unit_price_diff_percent": 2.0 # 2% increase
})
# CIF (Cost, Insurance, and Freight)
get_model("ship.term").create({
"name": "CIF",
"description": "Cost, Insurance, Freight - Seller pays cost, insurance, and freight",
"unit_price_diff_percent": 3.0 # 3% increase
})
# DDP (Delivered Duty Paid)
get_model("ship.term").create({
"name": "DDP",
"description": "Delivered Duty Paid - Seller responsible for all costs including duties",
"unit_price_diff_percent": 10.0 # 10% increase
})
Use Case 2: Product-Specific Shipping Terms¶
# Heavy machinery with fixed shipping cost
get_model("ship.term").create({
"name": "FOB + Crating",
"description": "FOB with specialized crating for heavy equipment",
"unit_price_diff": 500.00 # $500 crating fee per unit
})
# Hazardous materials with higher cost
get_model("ship.term").create({
"name": "CIF HAZMAT",
"description": "CIF with hazardous materials handling",
"unit_price_diff_percent": 8.0 # 8% increase for special handling
})
Use Case 3: Apply Shipping Terms to Order¶
def apply_shipping_terms_to_order(order_id, term_id):
"""Apply shipping term adjustments to order line items"""
order = get_model("sale.order").browse(order_id)
term = get_model("ship.term").browse(term_id)
# Update order header
get_model("sale.order").write([order_id], {
"ship_term_id": term_id
})
# Adjust line prices
for line in order.lines:
base_price = line.unit_price
adjusted_price = base_price
# Apply fixed adjustment
if term.unit_price_diff:
adjusted_price += term.unit_price_diff
# Apply percentage adjustment
if term.unit_price_diff_percent:
adjustment = base_price * (term.unit_price_diff_percent / 100)
adjusted_price += adjustment
# Update line
get_model("sale.order.line").write([line.id], {
"unit_price": adjusted_price
})
print(f"Applied {term.name} to order {order.number}")
print(f"Adjustment: {term.unit_price_diff_percent}% / ${term.unit_price_diff}")
# Usage
apply_shipping_terms_to_order(order_id=123, term_id=fob_id)
Use Case 4: Compare Shipping Term Costs¶
def compare_shipping_terms(base_price, quantity):
"""Compare costs under different shipping terms"""
terms = get_model("ship.term").search_browse([])
print(f"Base Price: ${base_price} per unit")
print(f"Quantity: {quantity} units")
print(f"Base Total: ${base_price * quantity}\n")
print("Comparison of Shipping Terms:")
print("-" * 60)
comparisons = []
for term in terms:
adjusted_price = base_price
# Apply adjustments
if term.unit_price_diff:
adjusted_price += term.unit_price_diff
if term.unit_price_diff_percent:
adjustment = base_price * (term.unit_price_diff_percent / 100)
adjusted_price += adjustment
total_cost = adjusted_price * quantity
difference = total_cost - (base_price * quantity)
comparisons.append({
"term": term.name,
"unit_price": adjusted_price,
"total": total_cost,
"difference": difference
})
# Sort by total cost
comparisons.sort(key=lambda x: x["total"])
for comp in comparisons:
diff_sign = "+" if comp["difference"] >= 0 else ""
print(f"{comp['term']:10} ${comp['unit_price']:7.2f}/unit "
f"Total: ${comp['total']:10.2f} "
f"({diff_sign}${comp['difference']:.2f})")
# Usage
compare_shipping_terms(base_price=100.00, quantity=1000)
Search Functions¶
Search by Name¶
# Find specific term
condition = [["name", "=", "FOB"]]
term = get_model("ship.term").search_browse(condition)
Search Terms with Discounts¶
# Find terms with negative adjustments (discounts)
condition = [["unit_price_diff_percent", "<", 0]]
discount_terms = get_model("ship.term").search_browse(condition)
Search Terms with Increases¶
# Find terms with positive adjustments
condition = [["unit_price_diff_percent", ">", 0]]
increase_terms = get_model("ship.term").search_browse(condition)
Related Models¶
| Model | Relationship | Description |
|---|---|---|
sale.order |
Referenced by | Orders specify shipping terms |
purchase.order |
Referenced by | Purchases specify terms |
sale.quot |
Referenced by | Quotes may include terms |
Best Practices¶
1. Use Standard Incoterm Codes¶
# Good: Official Incoterm abbreviations
get_model("ship.term").create({
"name": "FOB",
"description": "Free on Board"
})
# Avoid: Non-standard names
get_model("ship.term").create({
"name": "Free Shipping", # Not a standard Incoterm
})
2. Provide Clear Descriptions¶
# Good: Detailed explanation
get_model("ship.term").create({
"name": "CIF",
"description": "Cost, Insurance, and Freight - Seller pays for cost, "
"insurance, and freight to named port of destination. "
"Risk transfers when goods are on board the vessel.",
"unit_price_diff_percent": 3.0
})
# Bad: No description
get_model("ship.term").create({
"name": "CIF",
"unit_price_diff_percent": 3.0
# Missing description
})
3. Use Appropriate Adjustment Type¶
# Good: Percentage for relative adjustments
get_model("ship.term").create({
"name": "FOB",
"unit_price_diff_percent": -5.0 # 5% off any price
})
# Good: Fixed amount for absolute costs
get_model("ship.term").create({
"name": "Custom Crating",
"unit_price_diff": 50.00 # Always $50 per unit
})
# Avoid: Mixing both unless necessary
get_model("ship.term").create({
"name": "Mixed Term",
"unit_price_diff": 10.00,
"unit_price_diff_percent": 5.0 # Confusing to calculate
})
4. Consider Product Categories¶
# Create industry-specific terms
# Electronics
get_model("ship.term").create({
"name": "FOB (Electronics)",
"description": "FOB with anti-static packaging",
"unit_price_diff_percent": -4.0
})
# Perishables
get_model("ship.term").create({
"name": "CIF (Refrigerated)",
"description": "CIF with temperature-controlled shipping",
"unit_price_diff_percent": 6.0
})
Performance Tips¶
1. Cache Shipping Terms¶
# Good: Cache terms for pricing calculations
_terms_cache = {}
def get_term_adjustment(term_id):
if term_id not in _terms_cache:
term = get_model("ship.term").browse(term_id)
_terms_cache[term_id] = {
"name": term.name,
"fixed": term.unit_price_diff or 0,
"percent": term.unit_price_diff_percent or 0
}
return _terms_cache[term_id]
2. Batch Price Calculations¶
# Good: Calculate all adjustments in single pass
def batch_calculate_prices(products, term_id):
term = get_model("ship.term").browse(term_id)
results = []
for product in products:
adjusted = product.price
if term.unit_price_diff:
adjusted += term.unit_price_diff
if term.unit_price_diff_percent:
adjusted += product.price * (term.unit_price_diff_percent / 100)
results.append({
"product_id": product.id,
"base_price": product.price,
"adjusted_price": adjusted
})
return results
Troubleshooting¶
"Unexpected price calculation"¶
Cause: Both fixed and percentage adjustments applied
Solution: Review term configuration
# Check term settings
term = get_model("ship.term").browse(term_id)
print(f"Fixed adjustment: ${term.unit_price_diff or 0}")
print(f"Percent adjustment: {term.unit_price_diff_percent or 0}%")
# Calculate example
base = 100.00
if term.unit_price_diff:
print(f"After fixed: ${base + term.unit_price_diff}")
if term.unit_price_diff_percent:
percent_adj = base * (term.unit_price_diff_percent / 100)
print(f"Percent adjustment: ${percent_adj}")
print(f"Final: ${base + (term.unit_price_diff or 0) + percent_adj}")
"Negative prices after adjustment"¶
Cause: Discount percentage too high
Solution: Validate adjustment values
# Validate adjustment won't cause negative price
def validate_term_adjustment(base_price, term_id):
term = get_model("ship.term").browse(term_id)
adjusted = base_price
if term.unit_price_diff:
adjusted += term.unit_price_diff
if term.unit_price_diff_percent:
adjusted += base_price * (term.unit_price_diff_percent / 100)
if adjusted < 0:
raise ValueError(
f"Shipping term {term.name} would result in negative price "
f"(${base_price} -> ${adjusted})"
)
return adjusted
Testing Examples¶
Unit Test: Price Adjustment¶
def test_shipping_term_adjustment():
# Create test term
term_id = get_model("ship.term").create({
"name": "TEST",
"unit_price_diff_percent": -10.0 # 10% discount
})
# Test calculation
base_price = 100.00
term = get_model("ship.term").browse(term_id)
adjusted = base_price + (base_price * (term.unit_price_diff_percent / 100))
assert adjusted == 90.00 # 10% off
# Cleanup
get_model("ship.term").delete([term_id])
Security Considerations¶
Permission Model¶
- View: Users with sales/purchasing access
- Create/Modify: Admin or finance manager
- Delete: Admin only
Data Access¶
- Pricing-sensitive information
- May affect margins and profitability
- Audit trail recommended for changes
Version History¶
Last Updated: October 2025
Model File: ship_term.py
Framework: Netforce
Additional Resources¶
- Incoterms 2020 Official Guide
- Sales Order Documentation:
sale.order - Purchase Order Documentation:
purchase.order - Pricing Configuration
This documentation is generated for developer onboarding and reference purposes.