Odoo ePOD & Last-Mile Dispatch documentation
A guide to Odoo ePOD & Last-Mile Dispatch: installing the module and building the driver app, runs, the optimizer, proof of delivery, customer codes, failed deliveries, backorders, cash on delivery and settlement, reports, roles, settings and the API.
Documents version 1.0.0Updated Oct 29, 2026
Start here
taqnix_epod_core) for the dispatcher and the office, and the Flutter source code of an offline-first driver app for Android and iPhone. The module plans the daily runs, optimizes the stop order, receives the proof of every drop (GPS and time, photos, signature, customer code), validates the delivery order, creates the backorder for goods that were not delivered, and settles the cash that the driver collected.What is in the package
| Item | What it is |
|---|---|
taqnix_epod_core.zip | The Odoo 19 module: runs (route manifests) and stops, the route optimizer, geofenced arrival, proof of delivery, customer codes, exception reasons, cash on delivery, driver settlement with automatic payments, reports, the REST API of the app, Arabic translation and 217 automated tests. |
flutter-app-source.zip | The Flutter project of the driver app (Android and iOS): offline database and outbox, background sync, the stop stepper, camera with watermark, signature pad, deep links to the navigation apps. |
documentation/ | This guide in English (index.html) and Arabic (index-ar.html) with PDFs, and API.md for developers. |
licensing/ | The licence (OPL-1: one purchase = one company, updates included). |
preview-images/ | Screenshots of the Odoo screens and of the app. |
changelog.txt | History of the versions. |
How a day works
| Moment | Who | What happens | In Odoo |
|---|---|---|---|
| Morning | Dispatcher | Creates the run: driver, vehicle, date, and adds the ready delivery orders. Presses Optimize sequence, then Confirm. | A run in Confirmed with ordered stops. Customers whose order is worth more than the limit receive their delivery code by e-mail. |
| On the road | Driver | Opens the app, sees the stops in order, taps Start navigation (Google Maps, Waze or Apple Maps) and comes back to the app. | The stop is En route; the run is In progress. |
| At the door | Driver | The app detects the arrival inside the geofence. The driver scans the parcels, takes photos, collects the signature or the customer code, takes the cash and finishes the stop. | The stop is Delivered with the proof; the delivery order is validated; a short quantity makes a backorder. |
| No one home | Driver | Chooses a reason (customer not home, access restricted, refused...), takes the required photo. | The stop is Failed; the dispatcher gets a to-do to reschedule. |
| Back at the hub | Driver, cashier | The driver declares the cash by method; the cashier counts it. | A settlement sheet in Submitted. |
| Evening | Manager | Approves the settlement. | One customer payment per collection, posted and reconciled with the open invoices. |
What you need
| Item | Details |
|---|---|
| Odoo | 19.0 Community or Enterprise. The apps Inventory, Fleet, Invoicing, Contacts geolocation and Discuss are installed automatically with the module. Sales is optional: when it is installed the cash to collect is read from the sales order. |
| Chart of accounts | A chart of accounts and, for the settlement, one journal per payment method (cash, cheque, card, transfer); they can be the same journal. |
| Outgoing e-mail | An outgoing mail server for the delivery codes and the proof e-mails (Settings, Technical, Outgoing Mail Servers). |
| PDF engine | wkhtmltopdf as for any Odoo (the ePOD, run sheet and settlement reports). |
| Address | A public https address for your Odoo: the phones must reach it. |
| Phones | Android or iPhone with GPS and a camera. To build the iPhone app you need a Mac with Xcode. |
Install the Odoo module
- Unzip the package. Inside is
taqnix_epod_core.zip; unzip it to get the foldertaqnix_epod_core. - Copy the folder into one of the
addons_pathfolders of your Odoo server and restart Odoo. - Log in as an administrator, activate Developer mode, open Apps and press Update Apps List.
- Remove the Apps filter, search ePOD and press Install.
- Open Settings, Users and give your people the roles ePOD Dispatch / Driver, Dispatcher or Manager (chapter 22).
# or from the command line ./odoo-bin -c odoo.conf -d YOUR_DATABASE -i taqnix_epod_core --stop-after-init
epod_) and does not change any existing data.Build and install the driver app
- Unzip
flutter-app-source.zipand open a terminal in the folder. - Fetch the packages:
flutter pub get. The database code (Drift) and the translation files are already generated and included; rundart run build_runner buildonly after you change the tables andflutter gen-l10nonly after you change the texts. - Check the project:
flutter analyze(no issues) andflutter test(356 tests). - Run it on a phone or a simulator:
flutter run --dart-define=SITE_URL=https://odoo.example.com.SITE_URLis optional: without it the sign-in screen shows a Server address field and remembers what you type. - Build the Android file:
flutter build apk --release --dart-define=SITE_URL=https://odoo.example.com, orflutter build appbundle --release ...for the Play Store. - Build the iPhone app on a Mac:
flutter build ios --release --dart-define=SITE_URL=https://odoo.example.com, then archive and upload with Xcode. - Open the app and sign in with the login and password of a user who has the role ePOD Dispatch / Driver (chapter 22). The app downloads the configuration and today's run.
| Setting | Where |
|---|---|
Application id / bundle id com.taqnix.epoddriver, display name "Taqnix ePOD Driver" | android/app/build.gradle.kts, android/app/src/main/res/values/strings.xml, ios/Runner/Info.plist and the Xcode project. Keep the iOS background task identifier (<bundle id>.sync) in step with lib/core/services/background_sync.dart. |
| Brand colour | AppConfig.seedColorValue in lib/core/constants/app_config.dart |
| Icon and splash | Replace assets/branding/*.png, then dart run flutter_launcher_icons and dart run flutter_native_splash:create |
| Release signing (Android) | Create android/key.properties (storeFile, storePassword, keyAlias, keyPassword). Without it the release build is signed with the debug key. |
| Permission | Used for |
|---|---|
| Location (when in use, always) | Distance to the stops, geofence arrival, the coordinates stamped on photos, the GPS trail. "Always" lets arrivals be detected with the screen off. |
| Camera | Proof photos and the barcode scanner. |
| Notifications | The "arrived at..." notification and the Android foreground-service notice. |
| Network | Talking to Odoo. |
python3 tool/fake_server.py --port 8197 (Python standard library only, a driver and six Dubai stops).https address: phones refuse plain http on recent systems. Google Play asks for a declaration for background location (the app uses it only while the driver has open stops: arrival detection) and Apple asks you to explain it in the review notes. The app has no ads, no analytics and no SDK that sends data anywhere except to your Odoo.First setup in ten minutes
- Open ePOD Dispatch, Configuration, Settings. Check the geofence radius (default 100 m) and the proof rules (chapter 23).
- In the same screen choose the journals for cash, cheque, card and transfer: the settlement approval needs them.
- Create your drivers as internal users with the role ePOD Dispatch / Driver and a password. Attach each driver to a vehicle in Fleet (field Driver).
- Check that your customers have a position: open the contact, tab Delivery (ePOD), press Geolocate (chapter 8).
- Create the first run (chapter 9), confirm it and sign in on a phone.
Customers: position, gate code, window
| Field | Use |
|---|---|
| Gate / door code | Shown to the driver on the stop (gate, intercom, building access). |
| Delivery window | For example 09:00-12:00. Shown to the driver; it is information, not a rule. |
| Delivery instructions | Where to park, which entrance, whom to call. |
| Geofence radius | Overrides the company radius for this address (0 = company setting). |
| Geolocate | Looks the address up with the provider of Odoo's geolocation and fills latitude and longitude. If nothing is found you get a message and can type the coordinates. |
Runs: create and fill
RUN/2026/0001.- Open Operations, Runs and press New. Choose the date, the driver and the vehicle (choosing a vehicle fills its driver). Leave the depot empty to start from the company address.
- Press Add deliveries: the wizard lists the ready outgoing delivery orders of the company (all stock reserved) that are not on an open stop. Select them and press Add to the run. Running it again never duplicates a stop.
- You can also select delivery orders in Inventory, Delivery orders and use the action Add to an ePOD run, or the button Add to a run on the delivery order.
- Drag the stops by the handle on the left to change the order, or use Optimize sequence (chapter 10).
- Press Confirm.
Optimize the stop order
| Rule | Detail |
|---|---|
| Stops without a position | Go last, in their current order. |
| Delivered and failed stops | Keep their place at the top; only open stops are re-ordered. |
| No depot position | The first located stop stays first. |
| Apply | Press Apply this order; the sequence numbers are renumbered 10, 20, 30... You can still drag stops afterwards. |
Confirm, start, close, reassign, reschedule
| Action | What it does |
|---|---|
| Confirm | Needs a driver with the Driver role and at least one pending stop. Refreshes the cash to collect from the invoices, creates the customer codes and e-mails them. |
| Start the run | Optional: the run also starts by itself when the driver taps the first stop. |
| Close the run | Finishes the run; stops that are still open are cancelled and the driver is told. A run also closes by itself when its last stop is closed. |
| Reopen | A finished run (less than a week old) goes back to In progress to take a late stop. |
| Reassign driver | Gives the open stops to another driver (and vehicle). The old phone loses them, the new one gets them; cash already collected stays with the driver who took it. |
| Cancel | Cancels the run when nothing was delivered yet. |
| Reschedule | On a failed stop: creates a new stop on another run (or a new run). The failed stop stays failed with its proof. |
Geofenced arrival
| Field on the stop | Meaning |
|---|---|
| Arrived at | First arrival stamp (a second arrival ping never changes it). |
| Distance at arrival | Meters between the phone and the customer, computed by the server. |
| Arrived inside the geofence | True when that distance is within the radius. |
| Arrival detected by | Geofence (automatic) or Driver tap. |
The proof of delivery
| Point | What is stored |
|---|---|
| GPS and time | Position, accuracy, time of completion, distance from the address and whether it was inside the geofence. |
| Photos | Doorstep drop, open box / serial, damaged parcel, place of delivery. Stored as attachments with their time, position, size and SHA-256 fingerprint; the content is checked (WebP, JPEG or PNG only). |
| Signature | A PNG of the signature with the printed name of the receiver and the relationship (recipient, neighbor, receptionist, concierge, other) and the time. |
| Customer code | Whether the code was checked on the phone or by the server (chapter 14). |
| Goods | Ordered and delivered quantity of every line, lot and serial numbers, the reason of every short line. |
| Cash | Every payment taken at the door (chapter 17). |
Customer code (one-time password)
| Detail | How it works |
|---|---|
| When | A stop needs a code when the order is worth at least the setting One-time password from this order value (0 = never), or when you tick Always ask for the customer code on the stop. |
| What is stored | Only hashes: a server-side hash (HMAC-SHA256 keyed with a secret that never leaves the server) that the server uses for every check, and a separate salted hash for the phone. The code itself is never stored; it is only in the e-mail, which is sent by the system, is not linked to the stop, and cannot be read by drivers or in the chatter. |
| Assurance levels | Checked by the server (best): the digits were verified online through the server, or the setting Offline code check on the phone is OFF and the digits typed at the door matched when the stop was completed. Checked on the phone: the phone had a hash of the code, so the proof is recorded as On the phone (offline) with the flag Code checked on the phone only. |
| Offline code check setting | ON (default): the app receives the salt and hash and can check the code without a network. OFF: the app receives no hash at all; it checks online when it can, or sends the digits when it completes the stop and the server verifies them. |
| Wrong tries | 5 wrong codes lock the stop for 10 minutes; the counter is never reset by a resend, and after 15 wrong codes over the life of the stop only a dispatcher can unlock it (button Unlock the code). A locked stop is never recorded as verified: the flag Code locked is raised. |
| Resend | The driver or the dispatcher can send a new code (old code invalid) up to 3 times per stop, only while the run is confirmed or in progress and the code is not locked. |
| Validity | Setting validity (minutes), default 24 hours. An hourly job wipes expired hashes; ask for a new code if that happens (flag Code expired if it was too late). |
| Other channels | The method _epod_send_otp of the stop is a hook: another module can send an SMS or WhatsApp message instead of the e-mail. |
Failed deliveries and exception reasons
| Kind | Meaning | Examples |
|---|---|---|
| Failed stop | Nothing was delivered. | Customer not home, Access restricted, Wrong address, Unsafe to deliver |
| Line | Goods rejected or damaged on a stop that was delivered. | Damaged in transit, Wrong item delivered, Missing item |
| Both | Can be used for either. | Refused by customer, Customer did not order it |
Partial deliveries and backorders
| Case | Result |
|---|---|
| All lines complete | Stop Delivered, delivery order Done. |
| A line is short | Stop Partially delivered (flag Partial delivery), delivery order Done, a backorder with the remaining quantity, and a to-do to put it on a new run. |
| Quantity above the order, negative, not a number | Refused by the server (malformed). |
| Serial and lot numbers | The app shows the lots reserved on the line and defaults to them; the driver can name other lots (matched ignoring case and blanks). If no lot is named, the reserved lots are delivered. A name that does not exist is not refused: the delivery is recorded and the stop shows the valid lots to choose from. |
| Odoo refuses to validate | For example an unknown serial number. The proof and the delivery stay recorded, the stop shows the problem and the flag Validation failed, and the button Validate the delivery order tries again after you fix it. |
Cash on delivery
| Field | Meaning |
|---|---|
| Amount to collect | From the open invoices / order (automatic), Amount set by the dispatcher or No cash on delivery. |
| Collections | Every payment the driver took: cash, cheque, card / POS or bank transfer / wallet, with a reference for the last three. |
| Difference | Collected minus expected; a difference raises the flag Cash difference but never refuses the delivery. |
Driver settlement
- The driver sends the declaration from the app (or the dispatcher creates the sheet under Cash, Driver settlements and presses Submit). The sheet shows the expected amount per method, taken from the collections of that driver and day. The declaration is then locked.
- The cashier counts the money, corrects the Counted column if it differs from the declaration, and presses Cash counted. The declared and counted amounts are written in the chatter.
- If the difference between counted and expected is larger than the tolerance of the settings, a note is mandatory.
- A manager (not the driver himself) presses Approve and register payments. It is refused until the cash is counted and while the driver still has open stops on that day.
| On approval | Detail |
|---|---|
| Payments | One posted customer payment per collection, at the amount the customer paid, in the journal of its method, dated on the run day, with the delivery and cheque numbers in the memo. |
| Reconciliation | Each payment is reconciled with the open invoices of the stop, oldest first. Invoices in another currency are left open and noted in the chatter; a payment without an open invoice stays as customer credit. |
| Shortage or surplus | The difference between the cash counted and the cash collected is booked in one journal entry: a shortage debits the Driver shortage account (partner: the driver) and credits the liquidity account of the method's journal; a surplus does the opposite. Choose the journal and the account in the settings. If they are not configured, a difference beyond the tolerance can only be approved when a manager writes a note and ticks Accept the difference without booking it; that choice is logged. |
| Safety | Approving twice creates nothing; you cannot approve your own settlement; the approved sheet is locked. |
| Reject | Sends the sheet back: the driver declares again. |
| Late collections | A collection that reaches the server after the sheet of the day was submitted or approved (a phone that was offline, a reopened run) goes to a follow-up sheet of the same day (sheet 2, 3...) and the dispatcher gets a to-do. The driver declares it from the app like the first one. |
Printable reports
| Report | Where | Content |
|---|---|---|
| Proof of delivery (ePOD) | Print on a stop, on a delivery order, or e-mailed to the customer | Customer, run and driver, arrival and completion with time and place, customer code result, goods, receiver, signature, photos, flags. |
| Run sheet | Print on a run | The stops in order with address, instructions, code required and cash to collect, and signature lines. |
| Driver settlement | Print on a settlement | Expected, declared and difference, the collections and signature lines. |
Stops to review (flags)
| Flag | Raised when |
|---|---|
| Outside geofence | The distance at arrival or completion is larger than the radius. |
| Low GPS accuracy / GPS missing | The accuracy is worse than the setting, or no position came with the stop. |
| Code missing / mismatch / expired / locked | The stop needs a customer code and none, a wrong one, or an expired one was entered, or the stop was locked after too many wrong tries. |
| Code checked on the phone only | The code matched but the phone had a hash of it: weaker proof (chapter 14). |
| Signature / Photo missing | The stop needs them and they were not sent. |
| Cash difference | Collected is not what was expected. |
| Late sync | The proof arrived more than 6 hours after the delivery (the phone was offline). |
| Partial delivery | A line was short; a backorder exists. |
| Reference missing | A cheque, card or transfer payment has no reference. |
| Validation failed | Odoo refused to validate the delivery order. |
| To reschedule | A failed stop whose reason asks for a new attempt. |
Dashboard and tracking
Roles and access rights
| Role | Can do |
|---|---|
| Driver | Signs in to the app. In Odoo he sees only his own runs, stops, collections and settlements, read only. |
| Dispatcher | Sees everything of the company: creates and confirms runs, adds deliveries, optimizes, reassigns, reschedules, reviews flags, counts the cash. Cannot change the proof or approve settlements. Includes Inventory user and Fleet user rights. |
| Manager | Everything of the dispatcher plus: approve or reject settlements, the settings, exception reasons, flags, devices and the sync log. |
Settings
| Setting | Default | Meaning |
|---|---|---|
| Geofence radius | 100 m | Arrival and completion are checked against this distance (10 to 5000 m). A contact can override it. |
| Worst GPS accuracy | 60 m | A worse position raises the low accuracy flag. |
| Photo required / minimum / maximum | yes / 1 / 12 | Proof rules sent to the app; missing photos only raise flags. |
| Maximum photo size | 3 MB | Larger photos are refused (413). |
| Photo long side / quality | 1600 px / 70 | Sent to the app for WebP compression. |
| Signature required from value | 0 (always) | Signature required on stops worth at least this. |
| One-time password from value / digits / validity | 500 / 6 / 1440 min | See chapter 14. |
| Offline code check on the phone | on | OFF = the phone never receives a hash of the code (chapter 14). |
| E-mail the proof | off | Send the ePOD PDF to the customer. |
| Journals by method | - | Used by the settlement approval. |
| Settlement tolerance | 1.00 | A difference up to this per method needs no note. |
| Shortage journal and account | - | Where the difference between counted and collected cash is booked (chapter 18). |
| GPS trail retention | 30 days | Older breadcrumbs are deleted by a daily job. |
| Forget devices after | 180 days | Phones not seen for this long must sign in again. |
The driver app: sign in, the run and navigation
| Screen | What the driver does |
|---|---|
| Sign in | Server address (when the build has no fixed address), e-mail login and password, Remember me on this phone. A language switch is on the screen. |
| Today's run | The run of the day: number, vehicle and plate, a progress bar, the counts (stops, delivered, failed, remaining) and the cash collected against the cash expected. Below, the To do list in order with the next stop highlighted, distance markers in meters or kilometers, the delivery window and the amount to collect. |
| My order | A drag handle lets the driver re-order the stops for himself. This is local only: the API has no re-order call, nothing is sent to Odoo, and a re-order by the dispatcher arrives by itself. |
| Stop details | The customer, address, delivery window, a Call button, the gate and access instructions from the contact (with a copy button), the items with their quantities, and the proofs this stop needs (photo, signature, one-time code, scan). |
geo: link. The first time the driver chooses (and can tick Always use this app); in Settings he can change it, and the default can come from Odoo. There is no turn-by-turn guidance inside the app. When the driver comes back from the navigation app, a sheet asks Welcome back: have you arrived? with Yes, I have arrived, Open the stop and Not yet.The driver app: the six-step proof of delivery
| Step | What it does |
|---|---|
| 1 Arrived | Confirms the arrival (automatic by the geofence, or by hand) and records the time and the distance. |
| 2 Scan | Scan a package with the camera or type the code, or Mark all as delivered. Every line shows the ordered quantity and a stepper for the delivered quantity: a lower quantity asks for a reason and makes a partial delivery. |
| 3 Photos | The camera shows the watermark live (latitude, longitude, accuracy, time and the delivery number) and burns it into the picture. Photo types: doorstep drop, open box / serial, damaged parcel, place of delivery. Without a live camera the system camera or the gallery can be used. Photos are encoded to WebP with flutter_image_compress; when a device cannot, the photo is kept as JPEG and the screen says so (the server accepts WebP, JPEG and PNG). |
| 4 Sign / OTP | The receiver signs on the canvas (smoothed strokes, Clear), his printed name and his relationship (recipient, neighbor, receptionist, concierge, other). When the stop needs the customer's code the driver types it and presses Check code; Resend code asks Odoo for a new one. |
| 5 Cash | The amount expected, the total received and the payment methods (cash, cheque, card / POS, bank transfer / wallet) with the amount and the reference where the method needs one. A short collection is shown in red. |
| 6 Finish | The summary (lines checked, photos, signature, receiver, cash) and the warnings, then Complete delivery. |
The driver app: failed stops, outbox and cash settlement
The driver app: settings, languages and themes
Offline behaviour
| Situation | What happens |
|---|---|
| No network at the door | The status changes, photos, signature, code, cash and the outcome are saved in a local database queue on the phone. |
| Network is back | The queue is sent in order; photos are compressed to WebP and streamed; every call carries a unique ID. |
| The answer is lost | The app repeats the call; the server answers with the stored result, nothing is done twice. |
| The dispatcher edits the run meanwhile | Re-ordering, reassignment and cancellation reach the phone with the next refresh (every 2 minutes by default). |
| A stop was already closed | A second completion of a closed stop is refused (409). |
| One day at a time | The phone caches the run of today. Yesterday's operations that were not sent are still sent. A stop that the dispatcher removes after the driver worked on it stays on the phone, marked, so queued work is not lost. |
The API (for developers)
/api/v1/epod: ping, login, logout, config, manifest, status, location, otp/verify, otp/resend, complete, settlement and changes. Authentication is a bearer token (stored hashed); a login replaces the token of the same device. API.md in the package is the complete contract.# is the server reachable?
curl https://odoo.example.com/api/v1/epod/ping
# {"ok": true, "api_version": 1, "server_time": "2026-10-28T06:00:00Z"}
# sign in (returns a token, the vehicle and the configuration)
curl -X POST https://odoo.example.com/api/v1/epod/login -H 'Content-Type: application/json' \
-d '{"login": "omar@atlas-trading.example", "password": "...", "device": {"id": "abc", "name": "Pixel 7", "platform": "android", "app_version": "1.0.0"}}'
# today's run of the driver
curl https://odoo.example.com/api/v1/epod/manifest -H "Authorization: Bearer $TOKEN"
# complete a stop (multipart: the JSON payload and the photo files; every call has an op_uuid, repeating it is safe)
curl -X POST https://odoo.example.com/api/v1/epod/complete -H "Authorization: Bearer $TOKEN" \
-F 'payload={"op_uuid": "5f0e2c1a-8d7b-4c10-9a55-0d6a1b2c3d4e", "local_seq": 12, "stop_id": 101, "outcome": "delivered", "completed_at": "2026-10-28T09:30:00Z",
"location": {"lat": 25.2, "lng": 55.27, "accuracy": 7.5}, "receiver": {"name": "Sara Ali", "relationship": "self"},
"media": [{"field": "photo_0", "kind": "doorstep"}], "signature": {"field": "signature", "strokes": 41}}' \
-F 'photo_0=@door.webp' -F 'signature=@signature.png'| Rule | Detail |
|---|---|
| Idempotency | Every state-changing call has op_uuid and local_seq. A repeat answers 200 with duplicate: true and the stored result. A rejected call can be sent again with the same ID. |
| Errors | HTTP status plus {error: {code, message}}: 400 invalid_payload, 401 unauthorized, 403 forbidden / not_driver / otp_locked, 404 not_found, 409 stop_closed / conflict / settlement_closed, 413 too_large. |
| Language | Accept-Language selects the language of the server texts. |
| Ownership | A driver can only touch his own stops; another driver's stop or operation ID is refused with 403. |
Security notes
| Topic | What the module does |
|---|---|
| Tokens | Stored as SHA-256 hashes; revoked on logout; devices are listed and a manager can only switch them off (the token and the user cannot be rewritten). |
| Customer codes | Server-side keyed hash plus a separate salted hash for the phone; the code is never stored and the e-mail with it cannot be read by drivers; 5 tries per window, 15 in total, resends limited to 3. |
| Uploads | Photos and signatures are checked by their content (WebP, JPEG, PNG), their size in bytes, their size in pixels (at most 12000 px a side and 40 megapixels) and their number per stop. |
| Workflow | State and proof fields are written only by the workflow with the right role; checks use the user and groups, never a value sent by the client. |
| Evidence | Photos, signatures, lines and collections cannot be edited or deleted through the screens or RPC. |
| Privacy | Only the driver of the run and the dispatchers see a stop; the GPS trail is deleted after the retention period. |
Limits and what is not verified
| Topic | Limit |
|---|---|
| SMS | No SMS or WhatsApp channel is built in: the code goes by e-mail. The hook _epod_send_otp lets a developer add another channel. |
| Offline code check | The hash check on the phone can be brute-forced by someone who holds the phone. Such proof is recorded as checked on the phone and flagged; the server cannot make it stronger. Switch the setting OFF for server-side proof (chapter 14). |
| Navigation | No turn-by-turn guidance inside the app: it opens Google Maps, Waze or Apple Maps through links. |
| Route optimization | Straight-line distance, nearest neighbour and 2-opt; no traffic, no time windows, no vehicle capacity. |
| Cash to collect | Read from posted invoices and, with Sales, from the order. An order that was prepaid online without an invoice would be shown as cash to collect: set No cash on delivery on the stop. |
| Settlement | Payments are created in the company currency; invoices in another currency are not reconciled automatically. A shortage is booked against the liquidity account of the journal of the method, which is a simplification if your payment methods use outstanding accounts. |
| Ready stock | Only delivery orders that are ready with all stock reserved can go on a run. If the reservation disappears before the door, the delivery is recorded but Odoo is not asked to validate it (no negative stock): the stop shows the problem. |
| Tested here | The module has 217 automated tests on a fresh database (the reports were rendered as HTML in them; e-mail was tested up to the outgoing queue). The app has 356 Dart tests, flutter analyze is clean, and it was run on the iOS simulator against this module with a scripted GPS. |
| No physical device | The app was not run on a physical phone and not on an Android emulator: only the Android debug build was verified; all runtime checks were done on the iOS simulator. The screenshots of this guide come from the simulator. |
| Real camera and scanner | Simulators have no camera: the live preview, the torch, the camera switch, the system camera and the barcode scanner with a real camera were not tried (photos came from a debug sample image and the gallery path; manual code entry and the matching logic are tested). |
| Background geofence | The geofence with the screen off on a real device (Android foreground service, iOS "Always" permission) was not verified, nor the notification in the background nor the Android "all the time" permission flow. The logic is unit-tested and the foreground flow ran with a scripted GPS. In basements or garages the app says the position is not available and the dispatcher gets GPS missing. |
| Navigation apps | Apple Maps (deep link, return, Welcome back) was verified on the simulator; Google Maps, Waze and the Android geo: intents were not opened on a device. |
| Release builds and tablets | Release builds (signing, shrinking, App Store archive) were not made, and the tablet layout is covered by a widget test only. |
| Only unit-tested | The follow-up settlement sheet in the app, the 413 answer for an oversized photo, otp_expired and the dispatcher's unlock were covered by unit and widget tests, not run against the server from the app. |
| Long offline periods | Hundreds of photos in the outbox were not tried; the queue is limited only by the phone's storage. |
| Real mail and PDF | A real mail server and the PDF engine of a production Odoo were not part of the tests of the module. |
Troubleshooting
| Problem | What to check |
|---|---|
| The driver cannot sign in | He needs an internal user with the role Driver (error not_driver otherwise) and the right password; accounts with two-factor authentication cannot use the app. |
| No stops on the phone | The run must be Confirmed or In progress and its date must be today in the time zone of the driver. Check the user's time zone. |
| The arrival never triggers | The customer needs a position; check the radius; check that the phone allows location all the time. |
| The delivery order was not validated | Open the stop: the red message says why (for example an unknown serial number). Fix it and press Validate the delivery order. |
| The customer got no code | The contact needs an e-mail address and Odoo needs an outgoing mail server. The stop shows where the code was sent. |
| Approval says to choose a journal | Settings, Cash on delivery and settlement, choose the journals. |
| Photos are refused | Larger than the limit, not WebP/JPEG/PNG, or more than the maximum per stop. |
| See what the phone sent | Configuration, Sync log shows every call with its answer. |
Update to a new version
Back up your database, replace the module folder with the new version, restart Odoo and run -u taqnix_epod_core. Runs, proofs, settlements and settings are kept. Rebuild the app from the new source when the changelog says the API changed.
Support
Installation help and bug fixes are included for 6 months, with lifetime updates. Contact us through the marketplace page where you bought the module or write to support@taqnix.com with your Odoo version and a screenshot.