Skip to main content

Mobile Transport And Vehicles

This page documents the mobile transport-detail and vehicle-status workflows.

Scope

This page is based on:

  • apps/mobile/src/screens/transportStack/transportDetailScreen/index.tsx
  • apps/mobile/src/screens/vehicleStack/vehicleListScreen/index.tsx
  • apps/mobile/src/services/common.ts
  • apps/mobile/src/services/googleRoutes.ts
  • apps/mobile/src/navigation/RootNavigator.tsx
  • apps/mobile/src/screens/homeScreen/index.tsx

Vehicle menu entry

The Home screen now includes a ยานพาหนะ menu item in the ข้อมูล group.

  • It uses menus.menu11.
  • It is placed after ผู้ใช้ทั้งหมด.
  • It uses the same visibility permission as ผู้ใช้ทั้งหมด, which is MENU_EMPLOYEE.has(deptCode).
  • It navigates to VehicleList.
  • It has no create shortcut.

Vehicle list screen

VehicleList is registered in RootNavigator and renders vehicleStack/vehicleListScreen.

The screen:

  • loads vehicle data with getVehicles({ search }) from services/common
  • supports search through the same header/search pattern used by list screens such as invoiceListScreen
  • refreshes the list after status updates
  • uses licensePlate as the list identity when available

Each vehicle card displays the same core fields used by the vehicle picker in transportDetailScreen:

  • license plate with vehicle status label
  • vehicle type name
  • max payload capacity in kilograms

Card background color is status-based:

vehicleStatusCard tone
UNCHECKEDlight red
NOT_READYlight yellow
READYlight green

Vehicle status update modal

Pressing a vehicle card opens a status modal.

The modal includes:

  • a three-option radio group:
    • UNCHECKED: ยังไม่ตรวจสอบ
    • READY: พร้อมใช้งาน
    • NOT_READY: ยังไม่พร้อมใช้งาน
  • vehicleStatusNote as a multiline text area
  • ยกเลิก and บันทึก actions

The active radio style is theme-aware so the selected text remains readable in dark mode.

Saving calls:

PATCH api/vehicles/{licensePlate}/vehicle-status

Payload:

{
"vehicleStatus": "READY",
"vehicleStatusNote": "ตรวจสอบแล้ว พร้อมใช้งาน"
}

The update uses the same mstApi domain/base URL as getVehicles.

Transport delivery finalization

transportDetailScreen can finalize delivery sheets by updating each delivery to:

{
"deliveryStatus": "FINISHED"
}

The finalize permission is controlled by canFinalizeTransportDeliveries.

It is true only when:

  • the screen is not in create mode
  • the current user can edit the transport
  • the transport has not started yet (detail.startedAt is empty)
  • canFinalizeShippingSheet(user) allows the user's department

canFinalizeShippingSheet(user) currently allows department codes:

-2, 1, 2, 4, 7, 8, 9, 13, 16, 29

Bottom approve-all action

The transport-detail bottom bar now supports an approve-all action beside the existing save action.

When visible:

  • left button: อนุมัติใบจัดทั้งหมด
  • right button: existing primary action, such as บันทึก

The approve-all button is shown only when:

  • the user is on the delivery-list step
  • canFinalizeTransportDeliveries is true
  • at least one delivery item has deliveryStatus as null or an empty string

Pressing อนุมัติใบจัดทั้งหมด opens a confirmation alert before any API calls are made. After confirmation, the screen loops through all pending delivery items, finalizes each one, then refreshes the current transport detail by transportId.

Transport start requirements

The เริ่มการขนส่ง action in transportDetailScreen can start a trip only when every delivery item in the transport has a non-empty deliveryStatus.

A delivery item is treated as not ready to start when:

  • deliveryStatus is null
  • deliveryStatus is empty after trim()

If any delivery item has no status, the screen stops the flow before opening the start-trip modal. The same guard also runs before submitting the start-trip action so a stale open modal cannot bypass the requirement.

Google route customer sorting

The transport-detail sort planner supports a Google-based route sort action.

The sort modal footer now has two rows:

  • first row: เคลียร์รายชื่อจัดเรียง, the name toggle (แสดงชื่อเดิม / แสดงชื่อย่อ), close, and save
  • second row: a full-width จัดเรียงลูกค้าตามพิกัดด้วย Google button

Pressing จัดเรียงลูกค้าตามพิกัดด้วย Google shows a confirmation alert. After confirmation:

  • the screen reads the device's current GPS location with Geolocation.getCurrentPosition
  • that current location is used as the route origin
  • customers with valid U_BFP_Latitude and U_BFP_Longitude are used as route points
  • the customer farthest from the current location is used as the Google Routes destination
  • the remaining customers with GPS coordinates are sent as intermediates
  • services/googleRoutes.optimizeGoogleRoute(...) calls Google Routes computeRoutes with optimizeWaypointOrder: true
  • the screen reads routes.optimizedIntermediateWaypointIndex from the response and reorders the sort planner list
  • customers without valid GPS coordinates stay in the payload and are appended after the optimized GPS customers in their existing order

After the route is sorted, the screen shows เรียงเสร็จสิ้นแล้ว.

This action only updates the sort planner state. It does not save to the backend immediately. Users must still press the modal save action, which persists the current order through the existing sortTransportCustomers({ transportId, customerCodes }) flow.

Operational notes:

  • The Google API key is read from constants/config.ts as GOOGLE_ROUTE_API_KEY.
  • Because the origin is the device's current GPS location, the route starts from wherever the device is when the user confirms the action. If the driver should start from a warehouse, the device should be physically at that warehouse or the flow must be changed to use a stored warehouse coordinate.
  • The current implementation limits Google route sorting to 26 customers with valid GPS coordinates per run.

Header print actions

The transport-detail header menu no longer exposes ยืนยันใบจัดสายส่งทั้งหมด. Delivery finalization now lives in the bottom approve-all action.

The header menu now exposes print actions only when:

  • the screen is not in create mode
  • transportId is available
  • the transport has at least one delivery item
  • every delivery item has deliveryStatus that is not null and not an empty string

When those conditions are met, the header menu shows:

  • พิมพ์ใบแจ้งหนี้ทั้งหมด
  • พิมพ์ใบจัดสินค้า

Both actions load invoice data through:

GET api/transports/{transportId}/invoices

This is implemented as getTransportCustomerInvoices(transportId) in services/transport.

พิมพ์ใบแจ้งหนี้ทั้งหมด uses the response from getTransportCustomerInvoices(transportId) directly.

  • The response is grouped by customer.
  • Each customer can contain multiple invoices.
  • The screen flattens all customer invoices in customer sort order.
  • It calls printInvoice(invoice, { printIndex }) directly, so it does not call getInvoiceDetail again.
  • The invoice print template is the same invoice template used elsewhere by printInvoice.

To reduce native print instability when printing many invoices in sequence, the loop waits for React Native interactions to finish and then waits 800ms after each print job before starting the next one.

พิมพ์ใบจัดสินค้า also uses getTransportCustomerInvoices(transportId).

  • The screen flattens every invoice from every customer.
  • It maps each invoice's items into the products shape used by the shipping screen.
  • It combines all products with buildGroupedProducts.
  • It prints through printShippingProductSummary(...), using the same product-summary template as shippingDetailScreen.

Other customer payment handling

On the customer delivery step, selecting payment type อื่นๆ (OTHER) uses the exception-payment flow for cases such as partial item receipt, no receipt, or split payment.

Main validation rules:

  • รายละเอียดเพิ่มเติม is required every time.
  • รูปส่งของ is required.
  • Customer signature is required because OTHER is not TRANSFER or BILL_PAYMENT.
  • If ไม่รับสินค้า is selected, the screen does not validate collected amount, and submit sends cash = 0 and transfer = 0.
  • If ไม่รับสินค้า is not selected, the screen validates cash + transfer against grandAmount.
  • cash + transfer must never exceed grandAmount, including รับสินค้าบางรายการ and แบ่งชำระ.
  • If รับสินค้าบางรายการ is not selected, cash + transfer must also be at least grandAmount.
  • If รับสินค้าบางรายการ is selected, cash + transfer may be less than grandAmount, but still may not exceed grandAmount.

On submit, OTHER appends a system note after the user's note with the cash amount, transfer amount, and selected flags such as รับสินค้าบางรายการ, ไม่รับสินค้า, or แบ่งชำระ for warehouse/finance review.

Maintenance notes

  • Keep the Home menu documentation in workflows/home-menu-and-permissions aligned with VehicleList routing and permissions.
  • Keep vehicle status API calls on mstApi unless the backend route moves away from the MST domain.
  • If the finalize permission model changes in shipping, re-check transportDetailScreen because it reuses canFinalizeShippingSheet(user).
  • Keep transport print actions aligned with shippingDetailScreen print helpers and templates.