Skip to content

Port Destination Documentation

Overview

The Port Destination module (port.destination) tracks destination ports for import shipments. Each port has a unique code and supports formatted name display for easy identification in logistics operations.


Model Information

Model Name: port.destination
Display Name: Port Destination
Key Fields: None

Features

  • ❌ No audit logging
  • ❌ No multi-company support
  • ✅ Search enabled on name, code
  • ✅ Unique code constraint
  • ✅ Custom name_get formatting

Understanding Key Fields

Unique Code Constraint

_sql_constraints = [
    ("code_uniq", "unique (code)", "Code must be unique")
]

Each port destination must have a globally unique code across the entire system. This ensures no duplicate port codes exist.

Examples:

"VNSGN"  ✅ Valid
"THLCH"  ✅ Valid
"VNSGN"  ❌ ERROR: Code must be unique!


Key Fields Reference

Header Fields

Field Type Required Description
name Char Port name (searchable)
code Char Unique port code (searchable, unique)
description Text Additional details or notes
country_id Many2One Country location

Default Order: Ordered by code


API Methods

1. name_get

Method: name_get(ids, context)

Returns formatted display names with code prefix: "[CODE] Name"

Parameters: - ids (list): Record IDs to format

Returns: List of tuples [(id, formatted_name, image), ...]

Behavior: - If code exists: Returns "[CODE] Name" - If no code: Returns "Name" - Always includes image field (typically None)

Example:

# Get formatted names
port_ids = [1, 2, 3]
names = get_model("port.destination").name_get(port_ids)

# Example output:
# [(1, "[VNSGN] Ho Chi Minh Port", None),
#  (2, "[THLCH] Laem Chabang", None),
#  (3, "[SGSIN] Port of Singapore", None)]

for port_id, display_name, image in names:
    print(f"ID {port_id}: {display_name}")


2. Create Port Destination

Method: create(vals, context)

Creates a new port destination with unique code.

Parameters:

vals = {
    "name": str,           # Required: Port name
    "code": str,           # Required: Unique code
    "description": str,    # Optional: Additional details
    "country_id": int,     # Optional: Country ID
}

Returns: int - New record ID

Example:

# Create port destination
port_id = get_model("port.destination").create({
    "code": "VNSGN",
    "name": "Ho Chi Minh Port",
    "description": "Major port in southern Vietnam",
    "country_id": vietnam_id
})


Common Use Cases

Use Case 1: Setup Asian Destination Ports

# Setup major Asian import destinations
asian_ports = [
    {
        "code": "VNSGN",
        "name": "Ho Chi Minh Port",
        "description": "Saigon Port - Vietnam's largest port",
        "country_id": vietnam_id
    },
    {
        "code": "THLCH",
        "name": "Laem Chabang",
        "description": "Thailand's primary deep-sea port",
        "country_id": thailand_id
    },
    {
        "code": "SGSIN",
        "name": "Port of Singapore",
        "description": "World's busiest transshipment hub",
        "country_id": singapore_id
    },
    {
        "code": "MYPKG",
        "name": "Port Klang",
        "description": "Malaysia's busiest port",
        "country_id": malaysia_id
    },
    {
        "code": "IDTPP",
        "name": "Tanjung Priok",
        "description": "Indonesia's main port in Jakarta",
        "country_id": indonesia_id
    }
]

created_ports = []
for port_data in asian_ports:
    try:
        port_id = get_model("port.destination").create(port_data)
        created_ports.append(port_id)
        print(f"✓ Created: [{port_data['code']}] {port_data['name']}")
    except Exception as e:
        print(f"✗ Failed: [{port_data['code']}] - {str(e)}")

print(f"\nSuccessfully created {len(created_ports)} destination ports")

Use Case 2: Import Port Reference Data

def import_port_destinations_from_csv(csv_file):
    """
    Import port destinations from CSV file

    CSV format: code,name,description,country_code
    """
    import csv

    created = 0
    skipped = 0
    errors = []

    with open(csv_file, 'r') as f:
        reader = csv.DictReader(f)

        for row in reader:
            # Check if port already exists
            existing = get_model("port.destination").search([
                ["code", "=", row['code']]
            ])

            if existing:
                skipped += 1
                continue

            # Get country ID
            country = get_model("country").search([
                ["code", "=", row['country_code']]
            ])
            country_id = country[0] if country else None

            try:
                get_model("port.destination").create({
                    "code": row['code'],
                    "name": row['name'],
                    "description": row.get('description', ''),
                    "country_id": country_id
                })
                created += 1
            except Exception as e:
                errors.append(f"{row['code']}: {str(e)}")

    print(f"Import complete:")
    print(f"  Created: {created}")
    print(f"  Skipped (existing): {skipped}")
    if errors:
        print(f"  Errors: {len(errors)}")
        for err in errors:
            print(f"    - {err}")

# Usage
import_port_destinations_from_csv('destination_ports.csv')

Use Case 3: List Ports by Country

def list_destination_ports_by_country(country_id):
    """List all destination ports for a country"""

    ports = get_model("port.destination").search_browse([
        ["country_id", "=", country_id]
    ])

    country = get_model("country").browse(country_id)

    print(f"Destination Ports in {country.name}:")
    print("-" * 50)

    for port in ports:
        print(f"[{port.code}] {port.name}")
        if port.description:
            print(f"  → {port.description}")

# Usage
list_destination_ports_by_country(thailand_id)

Use Case 4: Validate and Update Port Data

def validate_port_destinations():
    """Check for data quality issues"""

    issues = []

    # Check for missing countries
    ports = get_model("port.destination").search_browse([
        ["country_id", "=", False]
    ])

    if ports:
        issues.append({
            "type": "missing_country",
            "count": len(ports),
            "ports": [p.code for p in ports]
        })

    # Check for missing descriptions
    ports = get_model("port.destination").search_browse([
        ["description", "=", False]
    ])

    if ports:
        issues.append({
            "type": "missing_description",
            "count": len(ports),
            "ports": [p.code for p in ports]
        })

    # Report issues
    if issues:
        print("Port Destination Data Quality Issues:")
        for issue in issues:
            print(f"\n{issue['type']}: {issue['count']} ports")
            for code in issue['ports'][:5]:  # Show first 5
                print(f"  - {code}")
            if issue['count'] > 5:
                print(f"  ... and {issue['count'] - 5} more")
    else:
        print("✓ All port destinations have complete data")

    return issues

# Run validation
validate_port_destinations()

Search Functions

Search by Code

# Exact code match
condition = [["code", "=", "VNSGN"]]
port = get_model("port.destination").search_browse(condition)

Search by Name Pattern

# Case-insensitive partial match
condition = [["name", "ilike", "%singapore%"]]
ports = get_model("port.destination").search_browse(condition)

Search by Country

# All ports in a country
condition = [["country_id", "=", thailand_id]]
ports = get_model("port.destination").search_browse(condition)

Search Ports Without Country

# Find ports missing country assignment
condition = [["country_id", "=", False]]
incomplete_ports = get_model("port.destination").search_browse(condition)

Model Relationship Description
country Many2One Port location country
port.loading Related Origin ports for shipments
sale.order Referenced by Import orders specify destination
purchase.order Referenced by Import purchases track destination

Best Practices

1. Use Standard Port Codes

# Good: Use UN/LOCODE or standard port codes
get_model("port.destination").create({
    "code": "VNSGN",  # UN/LOCODE format
    "name": "Ho Chi Minh Port"
})

# Avoid: Custom or non-standard codes
get_model("port.destination").create({
    "code": "HCM-PORT-01",  # Non-standard
    "name": "Ho Chi Minh Port"
})

2. Always Include Country

# Good: Country specified for clarity
get_model("port.destination").create({
    "code": "VNSGN",
    "name": "Ho Chi Minh Port",
    "country_id": vietnam_id
})

# Bad: No country makes location unclear
get_model("port.destination").create({
    "code": "VNSGN",
    "name": "Ho Chi Minh Port"
    # Missing country_id
})

3. Provide Descriptive Information

# Good: Helpful description
get_model("port.destination").create({
    "code": "THLCH",
    "name": "Laem Chabang",
    "description": "Thailand's primary deep-sea port, located 130km "
                   "southeast of Bangkok. Handles 70% of Thailand's trade.",
    "country_id": thailand_id
})

# Minimal: Just required fields
get_model("port.destination").create({
    "code": "THLCH",
    "name": "Laem Chabang",
    "country_id": thailand_id
})

4. Check Before Creating

# Good: Prevent duplicate creation
def create_or_get_destination_port(code, name, country_id, description=None):
    # Check by code (unique)
    existing = get_model("port.destination").search([
        ["code", "=", code]
    ])

    if existing:
        print(f"Port {code} already exists")
        return existing[0]

    # Create new
    port_id = get_model("port.destination").create({
        "code": code,
        "name": name,
        "country_id": country_id,
        "description": description
    })
    print(f"Created port {code}")
    return port_id

# Usage
port_id = create_or_get_destination_port(
    "VNSGN",
    "Ho Chi Minh Port",
    vietnam_id,
    "Major port in southern Vietnam"
)

Performance Tips

1. Cache Port Lookups

# Good: Cache frequently accessed ports
_dest_port_cache = {}

def get_destination_port_by_code(code):
    if code not in _dest_port_cache:
        port = get_model("port.destination").search([
            ["code", "=", code]
        ])
        if port:
            _dest_port_cache[code] = port[0]
    return _dest_port_cache.get(code)

# Usage
port_id = get_destination_port_by_code("VNSGN")

2. Bulk Load for Selections

# Good: Load all ports once
all_ports = get_model("port.destination").search_browse([])
port_options = [(p.id, f"[{p.code}] {p.name}") for p in all_ports]

# Use cached list for dropdown/selections

Troubleshooting

"Code must be unique"

Cause: Attempting to create port with duplicate code
Solution: Check existing ports or use different code

# Check if code exists
code = "VNSGN"
existing = get_model("port.destination").search([
    ["code", "=", code]
])

if existing:
    port = get_model("port.destination").browse(existing[0])
    print(f"Port code '{code}' already exists:")
    print(f"  ID: {port.id}")
    print(f"  Name: {port.name}")
    print(f"  Country: {port.country_id.name if port.country_id else 'N/A'}")
else:
    # Safe to create
    get_model("port.destination").create({
        "code": code,
        "name": "New Port",
        "country_id": country_id
    })

"Port code format issues"

Cause: Non-standard code format
Solution: Use standard 5-character UN/LOCODE format

# Good: Standard format (2-letter country + 3-letter location)
valid_codes = ["VNSGN", "THLCH", "SGSIN", "MYPKG"]

# Bad: Non-standard formats
invalid_codes = ["Vietnam-SGN", "THAILAND-LCH", "SG"]

Testing Examples

Unit Test: Create and Retrieve

def test_port_destination_crud():
    # Create
    port_id = get_model("port.destination").create({
        "code": "TEST1",
        "name": "Test Port",
        "description": "Test destination port",
        "country_id": usa_id
    })

    # Verify creation
    assert port_id is not None

    # Read back
    port = get_model("port.destination").browse(port_id)
    assert port.code == "TEST1"
    assert port.name == "Test Port"

    # Test name_get formatting
    names = get_model("port.destination").name_get([port_id])
    assert names[0][1] == "[TEST1] Test Port"

    # Cleanup
    get_model("port.destination").delete([port_id])

Unit Test: Unique Code Constraint

def test_unique_code_constraint():
    # Create first port
    port1_id = get_model("port.destination").create({
        "code": "TEST2",
        "name": "Test Port 1",
        "country_id": usa_id
    })

    # Try to create duplicate code
    try:
        port2_id = get_model("port.destination").create({
            "code": "TEST2",  # Duplicate!
            "name": "Test Port 2",
            "country_id": canada_id
        })
        assert False, "Should have raised exception for duplicate code"
    except Exception as e:
        assert "unique" in str(e).lower()

    # Cleanup
    get_model("port.destination").delete([port1_id])

Security Considerations

Permission Model

  • View: Users with logistics/import access
  • Create/Modify: Admin or logistics manager
  • Delete: Admin only (rarely needed)

Data Access

  • Public reference data
  • No sensitive information
  • No multi-company isolation

Version History

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


Additional Resources

  • Port Loading Documentation: port.loading
  • Shipping Port Documentation: ship.port
  • Country Documentation: country
  • UN/LOCODE Standard Reference

This documentation is generated for developer onboarding and reference purposes.