Skip to content

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)

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.