Skip to content

Shipping Rate Documentation

Overview

The Shipping Rate module (ship.rate) defines location-based and order-based pricing rules for shipping methods. Rates can be configured by country, province, district, postal code, and order criteria (minimum amount/weight) to provide flexible shipping cost calculations.


Model Information

Model Name: ship.rate
Display Name: Shipping Rate
Key Fields: None

Features

  • ❌ No audit logging
  • ❌ No multi-company support
  • ✅ Search enabled on sequence, method, location fields, address name
  • ✅ Auto-generated sequence numbers
  • ✅ Cascade delete with parent shipping method

Key Fields Reference

Header Fields

Field Type Required Description
sequence Char Auto-generated unique sequence
method_id Many2One Parent shipping method (cascade delete)
ship_price Decimal Shipping cost amount

Location Filter Fields

Field Type Description
country_id Many2One Country filter (if specified, must match)
province_id Many2One Province/state filter (if specified, must match)
district_id Many2One District filter (if specified, must match)
postal_code Char Specific postal code (exact match required)
address_name Char Specific address name (exact match required)

Order Criteria Fields

Field Type Description
min_amount Decimal Minimum order amount (order must meet or exceed)
min_weight Decimal Minimum total weight in Kg (order must meet or exceed)

Relationship Fields

Field Type Description
comments One2Many Comments and notes (message)

Default Order: sequence, method_id.name, country_id.name, province_id.name, postal_code, min_amount, min_weight, ship_price


Understanding Sequence Generation

Auto-Generated Sequences

The model uses an intelligent sequence generation system to ensure unique identifiers:

def _get_number(self, context={}):
    while 1:
        seq = get_model("sequence").get_number("shipping_rates")
        if not seq:
            return None
        res = self.search([["sequence", "=", seq]])
        if not res:
            return seq
        get_model("sequence").increment("shipping_rates")

How it works: 1. Gets next number from shipping_rates sequence 2. Checks if that sequence already exists 3. If exists, increments sequence and tries again 4. Returns first available unique sequence

Note: The sequence field is automatically populated on creation; you don't need to provide it.


API Methods

1. Create Rate

Method: create(vals, context)

Creates a new shipping rate with auto-generated sequence.

Parameters:

vals = {
    "method_id": int,              # Required: Shipping method ID
    "ship_price": Decimal,         # Required: Shipping cost

    # Optional location filters (at least one recommended)
    "country_id": int,             # Country filter
    "province_id": int,            # Province filter
    "district_id": int,            # District filter
    "postal_code": str,            # Exact postal code
    "address_name": str,           # Exact address name

    # Optional order criteria
    "min_amount": Decimal,         # Minimum order amount
    "min_weight": Decimal          # Minimum weight in Kg
}

Returns: int - New record ID

Example:

# Basic rate - country only
rate_id = get_model("ship.rate").create({
    "method_id": 1,
    "country_id": usa_id,
    "ship_price": 25.00
})

# Specific postal code rate
rate_id = get_model("ship.rate").create({
    "method_id": 1,
    "postal_code": "10001",
    "ship_price": 5.00
})

# Tiered rate by order amount
rate_id = get_model("ship.rate").create({
    "method_id": 1,
    "country_id": usa_id,
    "min_amount": 100.00,
    "ship_price": 0.00  # Free over $100
})


Common Use Cases

Use Case 1: Geographic Tiered Pricing

method_id = 1  # Standard shipping

# Tier 1: Urban area (low cost)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "province_id": california_id,
    "postal_code": "90210",
    "ship_price": 5.00
})

# Tier 2: Suburban area (medium cost)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "province_id": california_id,
    "ship_price": 10.00
})

# Tier 3: State-wide (higher cost)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "ship_price": 20.00
})

Use Case 2: Free Shipping Promotion

method_id = 2  # Free shipping method

# Free shipping on orders over $100
get_model("ship.rate").create({
    "method_id": method_id,
    "min_amount": 100.00,
    "ship_price": 0.00
})

# Free shipping on orders over $75 within state
get_model("ship.rate").create({
    "method_id": method_id,
    "province_id": california_id,
    "min_amount": 75.00,
    "ship_price": 0.00
})

Use Case 3: Weight-Based Pricing

method_id = 3  # Weight-based shipping

# Light packages (0-5 Kg)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "ship_price": 10.00
})

# Medium packages (5-20 Kg)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "min_weight": 5.0,
    "ship_price": 25.00
})

# Heavy packages (20+ Kg)
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "min_weight": 20.0,
    "ship_price": 50.00
})

Rate Matching Logic

How Rates are Selected

When ship.method.get_ship_amount() is called, rates are filtered in this order:

  1. Country Match: If rate has country_id, address must match
  2. Province Match: If rate has province_id, address must match
  3. District Match: If rate has district_id, address must match
  4. Postal Code Match: If rate has postal_code, address must match exactly
  5. Address Name Match: If rate has address_name, address must match exactly
  6. Min Amount Check: If rate has min_amount, order must meet/exceed it
  7. Min Weight Check: If rate has min_weight, order must meet/exceed it

Result: The lowest matching rate is selected.


Model Relationship Description
ship.method Many2One Parent shipping method (cascade delete)
country Many2One Country filter
province Many2One Province/state filter
district Many2One District filter
message One2Many Comments

Best Practices

1. Rate Specificity Pyramid

Configure rates from most specific (lowest cost) to least specific (highest cost):

method_id = 1

# Most Specific: Exact address + postal code
get_model("ship.rate").create({
    "method_id": method_id,
    "address_name": "Main Warehouse",
    "postal_code": "10001",
    "ship_price": 0.00
})

# Very Specific: Postal code only
get_model("ship.rate").create({
    "method_id": method_id,
    "postal_code": "10001",
    "ship_price": 5.00
})

# Medium: Province
get_model("ship.rate").create({
    "method_id": method_id,
    "province_id": new_york_id,
    "ship_price": 12.00
})

# General: Country
get_model("ship.rate").create({
    "method_id": method_id,
    "country_id": usa_id,
    "ship_price": 20.00
})

# Fallback: No filters
get_model("ship.rate").create({
    "method_id": method_id,
    "ship_price": 50.00
})

2. Always Provide Fallback Rate

# Good: Always have a catch-all rate
get_model("ship.rate").create({
    "method_id": method_id,
    "ship_price": 30.00  # Default rate
})

# Bad: No fallback rate results in None

Troubleshooting

"Rate not matching expected address"

Cause: Filter criteria too specific or address data incomplete
Solution: Check address has required fields (country, province, postal code)


"Free shipping not applying"

Cause: Order doesn't meet min_amount or min_weight
Solution: Verify order meets rate criteria

# Check rate requirements
rate = get_model("ship.rate").browse(rate_id)
print(f"Min Amount: ${rate.min_amount or 'None'}")
print(f"Min Weight: {rate.min_weight or 'None'} Kg")

Version History

Last Updated: October 2025
Model File: ship_rate.py
Framework: Netforce


Additional Resources

  • Shipping Method Documentation: ship.method
  • Address Documentation: address
  • Geographic Models: country, province, district

This documentation is generated for developer onboarding and reference purposes.