-
Notifications
You must be signed in to change notification settings - Fork 2
Run Sheet Job Status Update Summary
This document explains how status updates are implemented in the Run Sheet doctype.
Run Sheet status is automatically calculated and updated based on:
- Document lifecycle events (save, submit, cancel)
- Transport Leg status changes
- Document state (docstatus)
The Run Sheet status field can have the following values:
- Draft - Initial state for new documents (docstatus = 0)
- Dispatched - Run Sheet is submitted and all legs are "Open" or "Assigned"
- In-Progress - At least one leg has status "Started"
- Completed - All legs have status "Completed" or "Billed"
- Hold - Manually set status (prevents auto-updates)
- Cancelled - Document has been cancelled (docstatus = 2)
Location: run_sheet.py lines 533-630
This is the core method that calculates the correct status based on document state and Transport Leg statuses.
def update_status(self):
"""Update status based on document state and Transport Leg statuses
This method automatically determines the correct status based on:
- Document state (docstatus 0 = Draft, docstatus 1 = Dispatched/In-Progress/Completed, docstatus 2 = Cancelled)
- Transport Leg statuses for submitted documents
- Ensures status always follows docstatus
"""-
New Documents (
is_new()):- Sets status to "Draft"
- Returns early
-
Cancelled Documents (
docstatus == 2):- Always sets status to "Cancelled"
- Returns early
-
Draft Documents (
docstatus == 0):- Ensures status is "Draft" (unless manually set to "Hold" or "Cancelled")
- Returns early
-
Submitted Documents (
docstatus == 1):- Skips auto-update if status is "Hold" (manually set)
- If no legs exist, sets status to "Dispatched"
- Fetches all Transport Leg statuses from database (fresh query)
- Calculates status based on leg statuses:
- All legs "Completed" or "Billed" → "Completed"
- Any leg "Started" → "In-Progress"
- Any leg "Assigned" → "Dispatched"
- All legs "Open" → "Dispatched"
- Mixed statuses → "In-Progress" (if any leg is in progress/completed)
- Updates status via
db_set()if changed (for submitted documents)
- Fresh Database Queries: Always fetches leg statuses directly from database to ensure latest values
-
db_set() for Submitted Documents: Uses
db_set()instead ofsave()to avoid validation loops - Hold Status Protection: Respects manually set "Hold" status and prevents auto-updates
Location: run_sheet.py lines 23-28
def before_save(self):
"""Update status before saving"""
# Call update_status() to calculate correct status
# For submitted documents (docstatus = 1), update_status() will use db_set()
# to persist status changes if the status changed
self.update_status()Behavior:
- Called before every save operation
- Calls
update_status()to recalculate status - For submitted documents,
update_status()usesdb_set()internally
Note: During submission, a _submitting flag is set to prevent status reset (see before_submit()).
Location: run_sheet.py lines 30-34
def before_submit(self):
"""Validate required fields before submission and mark as submitting"""
self.validate_vehicle_required()
# Set flag to prevent before_save from calling update_status during submission
self._submitting = TrueBehavior:
- Validates that vehicle is required
- Sets
_submittingflag to prevent status reset during submission
Location: run_sheet.py lines 36-60
def after_submit(self):
"""Set initial status after submission and check leg states"""
# IMPORTANT: Ensure docstatus is 1 (sometimes it's not set in the object yet)
# Reload from database to get the actual docstatus
current_docstatus = frappe.db.get_value(self.doctype, self.name, "docstatus")
if current_docstatus != 1:
# If not submitted, something went wrong - log and return
frappe.log_error(...)
return
# Ensure docstatus is set in the object for update_status to work
self.docstatus = 1
# Clear submitting flag
if hasattr(self, '_submitting'):
delattr(self, '_submitting')
# Set status to "Dispatched" via db_set()
self.db_set("status", "Dispatched", update_modified=False)
# Call update_status() to calculate correct status based on leg states
self.update_status()Behavior:
- Verifies docstatus is 1 (reloads from database if needed)
- Clears
_submittingflag - Sets initial status to "Dispatched" via
db_set() - Calls
update_status()to calculate correct status based on leg states
Location: run_sheet.py lines 62-64
def on_cancel(self):
"""Set status to Cancelled when document is cancelled"""
self.db_set("status", "Cancelled", update_modified=False)Behavior:
- Sets status to "Cancelled" when document is cancelled
- Uses
db_set()to update directly in database
Location: transport_leg.py line 39-44
When a Transport Leg is saved, it triggers Run Sheet status update:
def after_save(self):
"""Sync changes back to Run Sheet and trigger auto-vehicle assignment after Transport Leg is saved"""
self.sync_to_run_sheet()
self.sync_route_to_run_sheet()
self.update_transport_job_status()
self.update_run_sheet_status() # <-- Updates Run Sheet status
self._trigger_auto_vehicle_assignment()Location: transport_leg.py lines 400-491
This method is called whenever a Transport Leg status changes.
Process:
- Checks if leg has a
run_sheetassigned - Verifies Run Sheet is submitted (docstatus = 1)
- Skips if Run Sheet status is "Hold" (manually set)
- Gets all leg statuses for the Run Sheet from database
- Uses in-memory status for current leg (most up-to-date)
- Calculates new Run Sheet status based on leg statuses
- Updates Run Sheet status via
db_set()if changed
Status Mapping:
- All legs "Completed" or "Billed" → Run Sheet "Completed"
- Any leg "Started" → Run Sheet "In-Progress"
- Any leg "Assigned" → Run Sheet "Dispatched"
- All legs "Open" → Run Sheet "Dispatched"
- Mixed statuses → Run Sheet "In-Progress" (if any leg is in progress/completed)
Implementation:
def update_run_sheet_status(self):
"""Update the parent Run Sheet status when this leg's status changes"""
if not self.run_sheet:
return
try:
# Get Run Sheet docstatus from database
run_sheet_docstatus = frappe.db.get_value("Run Sheet", self.run_sheet, "docstatus")
# Only update status for submitted Run Sheets
if run_sheet_docstatus != 1:
return
# Get current Run Sheet status from database
db_status = frappe.db.get_value("Run Sheet", self.run_sheet, "status")
# Skip if status is manually set to "Hold"
if db_status == "Hold":
return
# Get all leg statuses for this Run Sheet from database
leg_statuses = frappe.db.get_all(
"Transport Leg",
filters={"run_sheet": self.run_sheet},
pluck="status"
)
# Replace current leg's status with in-memory status (most up-to-date)
if self.name in leg_statuses:
leg_statuses = [s for s in leg_statuses if s != self.status]
leg_statuses.append(self.status)
# Calculate new status based on leg statuses
if all(status in ["Completed", "Billed"] for status in leg_statuses):
new_status = "Completed"
elif any(status == "Started" for status in leg_statuses):
new_status = "In-Progress"
elif any(status == "Assigned" for status in leg_statuses):
new_status = "Dispatched"
elif all(status == "Open" for status in leg_statuses):
new_status = "Dispatched"
else:
# Mixed statuses - prioritize Started > Assigned > Completed
new_status = "In-Progress" # or "Dispatched" based on logic
# Update if status changed
if new_status != db_status:
frappe.db.set_value(
"Run Sheet",
self.run_sheet,
"status",
new_status,
update_modified=False
)
frappe.db.commit()
except Exception as e:
frappe.log_error(...)Location: run_sheet.py lines 948-1014
Whitelisted API that can be called from client-side JavaScript to manually trigger status update.
@frappe.whitelist()
def update_status_from_client(run_sheet_name):
"""
Update Run Sheet status from client-side JavaScript.
This method recalculates the status based on current leg statuses
and returns the updated status.
"""Process:
- Validates Run Sheet name is provided
- Loads Run Sheet document
- Only updates status for submitted documents (docstatus = 1)
- Skips update if status is "Hold"
- Gets current status from database
- Calls
update_status()to recalculate - Persists to database via
db_set()if changed - Returns status information
Response Format:
{
"success": True,
"status": "In-Progress",
"old_status": "Dispatched",
"db_status": "Dispatched",
"changed": True
}- All legs "Completed" or "Billed" → "Completed"
- Any leg "Started" → "In-Progress"
- Any leg "Assigned" → "Dispatched"
- All legs "Open" → "Dispatched"
- Mixed statuses → "In-Progress" (if any leg is in progress/completed)
- Open - No run_sheet assigned, no dates set
- Assigned - run_sheet assigned but not started
- Started - start_date is set
- Completed - end_date is set
- Billed - sales_invoice is set (highest priority)
Why:
-
save()would trigger validation which might fail for submitted documents -
db_set()updates the database directly, bypassing validation - Prevents recursive save loops and validation errors
Example:
if self.docstatus == 1:
self.db_set("status", new_status, update_modified=False)Why:
- Leg statuses may have changed since Run Sheet was loaded
- Ensures status calculations are based on the most current data
- Prevents stale status issues
Example:
# Always fetch fresh status from database
leg_status = frappe.db.get_value("Transport Leg", transport_leg_name, "status")Why:
- When Transport Leg is being saved, in-memory status is most up-to-date
- Database may not yet reflect the current change
- Ensures accurate status calculation
Example:
# Replace current leg's status with in-memory status (most up-to-date)
leg_statuses.append(self.status) # Uses in-memory statusWhy:
- Allows manual override of automatic status updates
- Useful for pausing Run Sheet without affecting leg statuses
- Prevents automatic updates from overwriting manual status
Implementation:
# Don't auto-update if status is manually set to "Hold"
if self.status == "Hold":
returnRun Sheet Save/Submit
↓
before_save() / after_submit()
↓
update_status()
↓
├─→ Check docstatus
│ ├─→ 0 (Draft) → Status = "Draft"
│ ├─→ 2 (Cancelled) → Status = "Cancelled"
│ └─→ 1 (Submitted) → Calculate from leg statuses
│ ↓
│ Fetch leg statuses from database
│ ↓
│ Calculate status based on leg statuses
│ ↓
│ Update via db_set() if changed
│
Transport Leg Status Change
↓
TransportLeg.after_save()
↓
update_run_sheet_status()
↓
├─→ Check if leg has run_sheet
├─→ Verify Run Sheet is submitted
├─→ Skip if status is "Hold"
├─→ Get all leg statuses from database
├─→ Use in-memory status for current leg
├─→ Calculate new Run Sheet status
└─→ Update via db_set() if changed
| Trigger | Location | When | Action |
|---|---|---|---|
before_save() |
run_sheet.py:23 |
Every save | Calls update_status()
|
after_submit() |
run_sheet.py:36 |
After submission | Sets "Dispatched", then calls update_status()
|
on_cancel() |
run_sheet.py:62 |
On cancel | Sets "Cancelled" |
TransportLeg.after_save() |
transport_leg.py:39 |
When leg is saved | Calls update_run_sheet_status()
|
update_status_from_client() |
run_sheet.py:948 |
Manual API call | Recalculates and updates status |
-
Always use
db_set()for submitted documents - Prevents validation loops - Fetch fresh data from database - Ensures accuracy
- Respect "Hold" status - Don't auto-update when manually set
- Use in-memory status for current leg - Most up-to-date value
- Log significant status changes - Helps with debugging
- Handle exceptions gracefully - Prevents status update failures from breaking other operations
- Check docstatus: Status updates only work for submitted Run Sheets (docstatus = 1)
- Check Hold status: Hold status prevents automatic updates
-
Check Transport Leg linkage: Ensure legs are properly linked (
run_sheetfield) - Check Transport Leg statuses: Run Sheet status is calculated from leg statuses
- Check for errors: Look for errors in logs when hooks are triggered
- Verify leg statuses: Check if leg statuses are correct
- Check for Hold status: Remove Hold status to allow auto-updates
-
Manually trigger update: Use
update_status_from_client()API - Check database directly: Verify status in database matches expectations
-
RUN_SHEET_JOB_STATUS_UPDATE_SUMMARY.md- Explains relationship between Run Sheet and Transport Job status updates -
transport_leg.py- Transport Leg status update implementation -
transport_job.py- Transport Job status update implementation
Getting Started
- Getting Started
- Recent Platform Updates
- CargoNext v1 — Release Notes
- CargoNext v1 — Astraea Press Release
- Document Management
- Milestone Tracking
- Customer Portal
Setup and Settings
- Logistics Settings
- Credit Management
- Default Details and Relationships
- Sea Freight Settings
- Air Freight Settings
- Transport Settings
- Warehouse Settings
- Customs Settings
Sea Freight
- Sea Freight Module
- Sea Booking
- Sea Shipment
- Sea Consolidation
- Master Bill
- Shipper
- Consignee
- Container Type
- Container Management
Air Freight
Transport
- Transport Module
- Transport Order
- Transport Job
- Transport Consolidation
- Transport Leg
- Transport Plan
- Run Sheet
- Proof of Delivery
- Transport Template
- Load Type
- Transport Order — Inter-module Field Copy
Customs
Warehousing
- Warehousing Module
- Inbound Order
- Release Order
- Transfer Order
- VAS Order
- Stocktake Order
- Warehouse Job
- Warehouse Contract
- Gate Pass
- Periodic Billing
- Storage Location
- Handling Unit Type
Pricing Center
- Sales Quote
- Sales Quote — Separate Billings and Internal Job
- Change Request
- Sales Quote – Calculation Method
Job Management
- Job Management Module
- Revenue Recognition Policy — Accounts, Dates, and Charges
- Proforma GL Entries
- WIP and Accrual Reversal on Invoicing
Sustainability
Intercompany
Special Projects
Pages
Features
Reports
Glossary