Relay data model

erd

What a parcel is, where it has been, and who was meant to receive it

BookingNetworkMovementcurrently at · current_depot_id → id · many ↔ zero-or-onedelivers to · destination_id → id · many ↔ oneaddressed to · recipient_id → id · many ↔ onebooked by · merchant_id → id · many ↔ one · on delete restrictleaves from · depot_id → id · many ↔ onepart of · shipment_id → id · many ↔ one · on delete cascadesited at · address_id → id · many ↔ onedriven by · driver_id → id · many ↔ zero-or-onedelivers · parcel_id → id · many ↔ onebased at · depot_id → id · many ↔ onerecords · parcel_id → id · many ↔ one · on delete restrictagainst · parcel_id → id · many ↔ oneon · route_id → id · many ↔ one · on delete cascademerchants — Businesses that book shipmentsmerchantsid · uuid · primary keyPKiduuidname · textnametextaccount_ref · citextUQaccount_refcitextcreated_at · timestamptz · default now()created_attimestamptzshipments — One booking, which may contain several parcelsshipmentsid · uuid · primary keyPKiduuidmerchant_id · uuidmerchant_iduuidreference · text · nullablereference?textservice_level · service_levelservice_levelservice_levelrecipient_id · uuidrecipient_iduuiddestination_id · uuiddestination_iduuidbooked_at · timestamptzbooked_attimestamptzparcels — The physical thing. One row per barcode.parcelsid · uuid · primary keyPKiduuidshipment_id · uuidshipment_iduuidtracking_number · textUQtracking_numbertextstatus · parcel_statusstatusparcel_statusweight_grams · intweight_gramsintattempts · int · default 0attemptsintcurrent_depot_id · uuid · nullablecurrent_depot_id?uuidrecipients — Who the parcel is for. Deliberately not an account.recipientsid · uuid · primary keyPKiduuidname · textnametextphone · text · nullablephone?textemail · citext · nullableemail?citextaddresses — Validated and geocoded before a shipment is acceptedaddressesid · uuid · primary keyPKiduuidline1 · textline1textline2 · text · nullableline2?textcity · textcitytextpostcode · textpostcodetextcountry · char(2)countrychar(2)latitude · numeric · nullablelatitude?numericlongitude · numeric · nullablelongitude?numericvalidated_at · timestamptz · nullablevalidated_at?timestamptzdepots — A sorting site. Every parcel passes through at least one.depotsid · uuid · primary keyPKiduuidcode · textUQcodetextname · textnametextaddress_id · uuidaddress_iduuiddrivers — Couriers, each based at one depotdriversid · uuid · primary keyPKiduuiddepot_id · uuiddepot_iduuidname · textnametextlicence_ref · textUQlicence_reftextroutes — One driver, one van, one dayroutesid · uuid · primary keyPKiduuiddepot_id · uuiddepot_iduuiddriver_id · uuid · nullabledriver_id?uuidservice_date · dateservice_datedatesequenced_at · timestamptz · nullablesequenced_at?timestamptzstops — One parcel's place in a route, with the window promised to the recipientstopsid · uuid · primary keyPKiduuidroute_id · uuidroute_iduuidparcel_id · uuidparcel_iduuidsequence · intsequenceintwindow_start · timestamptzwindow_starttimestamptzwindow_end · timestamptzwindow_endtimestamptzscans — Append-only. Every barcode read, in the order it happened, not the order it arrived.scansid · uuid · primary keyPKiduuidparcel_id · uuidparcel_iduuidtype · scan_typetypescan_typedepot_id · uuid · nullabledepot_id?uuiddriver_id · uuid · nullabledriver_id?uuiddevice_id · textdevice_idtextscanned_at · timestamptzscanned_attimestamptzreceived_at · timestamptz · default now()received_attimestamptzidempotency_key · textUQidempotency_keytextdelivery_attempts — Why a parcel came back on the vandelivery_attemptsid · uuid · primary keyPKiduuidparcel_id · uuidparcel_iduuidstop_id · uuid · nullablestop_id?uuidoutcome · textoutcometextattempted_at · timestamptzattempted_attimestamptzphoto_url · text · nullablephoto_url?textparcel_statusparcel_statusenumcreatedcreatedcollectedcollectedat_depotat_depotin_transitin_transitout_for_deliveryout_for_deliverydelivereddeliveredattempt_failedattempt_failedawaiting_instructionawaiting_instructionreturningreturningreturnedreturnedlostlostcancelledcancelledscan_typescan_typeenumcollectioncollectiondepot_inbounddepot_inbounddepot_outbounddepot_outboundvehicle_loadvehicle_loaddeliverydeliveryattempt_failedattempt_failedreturnreturncurrently at · current_depot_id → id · many ↔ zero-or-onecurrently atcurrent_depot_id → iddelivers to · destination_id → id · many ↔ onedelivers todestination_id → idaddressed to · recipient_id → id · many ↔ oneaddressed torecipient_id → idbooked by · merchant_id → id · many ↔ one · on delete restrictbooked bymerchant_id → idleaves from · depot_id → id · many ↔ oneleaves fromdepot_id → idpart of · shipment_id → id · many ↔ one · on delete cascadepart ofshipment_id → idsited at · address_id → id · many ↔ onesited ataddress_id → iddriven by · driver_id → id · many ↔ zero-or-onedriven bydriver_id → iddelivers · parcel_id → id · many ↔ onedeliversparcel_id → idbased at · depot_id → id · many ↔ onebased atdepot_id → idrecords · parcel_id → id · many ↔ one · on delete restrictrecordsparcel_id → idagainst · parcel_id → id · many ↔ oneagainstparcel_id → idon · route_id → id · many ↔ one · on delete cascadeonroute_id → id
drag to pan · wheel to zoom · click a node · Esc clears

Conventions

  • All ids are uuid v7, so they sort by creation time
  • Weights are integer grams; there are no floats anywhere in this schema
  • Timestamps are timestamptz in UTC

Watch out

  • scans is append-only and never updated — corrections are new rows
  • scanned_at is device time and can be wrong; received_at is server time and cannot
  • parcels.attempts is denormalised from delivery_attempts for the route planner