📄 Reference: AP Invoice → Float Automation (Invoices Received)

Reference: AP Invoice → Float Automation (Invoices Received)

Vendor and contractor invoices are pushed into Float automatically from the Invoices Received project board. Staff only step in when a card lands in Needs Attention. Odoo never pays anything — coding, GM approval, and payment scheduling happen in Float, and that approval flow is the payment gate.

The flow

Three ways onto the board, all ending in the same forward:

  1. Email — a message to invoices@soundintown.com becomes a card in stage New; the automation forwards the invoice PDF(s) to Float's bill intake within a minute.
  2. Helpdesk Convert to Task — a ticket converted into the Invoices Received project fires the convert path: the automation follows the conversion note's backlink to the source ticket, copies the invoice document(s) off the ticket, and backfills the vendor's sender address (Odoo's native convert copies neither), then forwards. A ticket with no invoice document sends the card straight to Needs Attention — a deliberate handoff deserves instant feedback.
  3. Document attached — an invoice document landing on any card in this project triggers the forward. This covers hand-made cards, PDFs downloaded from link-only vendor emails, and cards held as No attachment — attaching the missing PDF re-arms them with no field editing. A card with no document waits quietly (until the sweep flags it); small images such as email-signature logos are ignored.

The forward renames each document with a SIT-TASK-<number> token, emails the copies to Float's bill-intake address, and moves the card to Sent to Float. The forward is posted as an internal note, so only the Float intake address receives it — vendors, who are chatter followers of email-created cards, get nothing (until 2026-08-17 the forward was a customer-visible comment and vendors received a copy of every forward). No acknowledgement is sent to the sender — by design (auto-replies to arbitrary inbound senders are a fraud/spam surface; vendors get the payment notice when payment goes out).

Delivery is watched, not assumed. "Forwarded" only means the email was handed to the mail system; Float's intake can still bounce it or (rarely) drop it silently — on 2026-08-10/11 Float's intake rejected mail for ~6 hours, six forwards bounced, and the cards sat on "forwarded" for six days until a human noticed. The hourly sync now reads each forward's per-recipient delivery record (mail.notification, which survives mail vacuuming): a bounced forward is re-sent automatically with backoff (1/2/4/8 h), a delivered forward with no bill after 8 h is re-sent too, and no card ever sends more than 5 forwards total. A re-forward is not harmless: Float's intake does not reliably dedup — from 2026-09-20 to 09-24 five invoices were each forwarded five times and Float made 20 bills for 5 documents (see History). Human approval in Float is the only payment gate, so the sync never re-forwards a card when Float already holds a bill that looks like the card's but whose reference it cannot read; it flags the card instead. Staff are alerted (Needs Attention + to-do) when the cap is reached or 24 h passes since the first forward, whichever comes first, with the full delivery history in the note; if a bill then appears and links, the to-do completes itself.

Float reads each PDF and creates a draft bill. Float rewrites the attachment filename on intake — since 2026-09-20 SIT-TASK-7854__Invoice 065.pdf comes back as sittask7854__invoice_065.pdf — so the sync reads the token in any spelling. An hourly sync matches the bill back to the card by its token (when several bills carry one card's token, the one furthest along in Float is linked and each other one is noted on the card), posts a chatter note with the Float link, and advances the card as the bill moves: In Float → Payment Scheduled → Paid. Every status change is noted in the card's chatter.

Sweep: the same hourly sync first runs any card no automation ever touched (empty Float Forward State, older than ~6 h) through the identical forward action — harvesting from the source ticket where possible — so unprocessed cards cannot accumulate.

Stage meanings

Stage Meaning
New Just arrived (email, ticket conversion, or hand-made). Cleared within a minute; anything untouched is swept within ~6 h
Sent to Float PDF forwarded; waiting for Float to create the bill (usually minutes; bounced or bill-less forwards are re-sent automatically, alerted at latest after 24 h)
In Float Bill exists — being coded or awaiting approval
Payment Scheduled Payment approved and scheduled in Float
Paid Paid (or marked as paid). Folded away.
Needs Attention A human needs to act — see below

The Float Forward State field

The card's Float Forward field is both the status record and the automation's fire-once guard: automations only act on cards whose state is empty, and every code path sets it. Clearing the field re-fires the forward; a re-fire from Needs Attention skips the duplicate gate (the documented force-forward override).

Value Meaning
(empty) Not yet processed — the only state automations will act on
Forwarded to Float PDF(s) sent to Float intake; the sync tracks the bill from here
No attachment Held — no invoice document on the email, ticket, or card. Attaching a document auto-forwards; no field editing needed
Possible duplicate — held Held — see duplicate handling below. Clear the field (card still in Needs Attention) to force-forward
Forward error The outbound email errored. Clear the field to retry
Forwarded — no bill found (alerted) Forwarded, but no Float bill matched; the sync auto-retried (bounces with backoff, silent drops every 8 h, max 5 forwards ever) and has now alerted, with the delivery history of every attempt and candidate orphan bills in the note. Auto-retries continue after the alert until the cap
Handled manually — no auto-forward Terminal (v4). Staff handled the bill outside the pipeline (entered in Float by hand, statement, oversized file). No automation, retry, sweep, or sync ever touches the card again

Needs Attention — causes and fixes

The Customer Support Admin seat owner gets a to-do whenever a card lands here. The chatter note on the card says which case it is:

  • No attachment — get the PDF and attach it to the card; the forward fires the moment the document lands. Alternatively enter the bill in Float by hand and set the state to Handled manually — no auto-forward.
  • Possible duplicate — check the linked earlier card; the note says which kind of match it is (see Duplicate handling). If it genuinely is a new invoice, clear the Float Forward State field while the card sits in Needs Attention — that forces the forward. A card moved out of Needs Attention must go back first: from any other column the re-fire runs the duplicate check again and re-holds it.
  • Forward failed — the outbound email errored. Clear Float Forward State to retry, or forward the PDF manually to the Float intake address.
  • No bill appeared — Float never created a bill despite automatic re-forwards (the note lists every attempt with its delivery status: delivered / BOUNCED / send failed). Repeated bounces usually mean Float's intake is down — forward the PDF manually to the intake address or enter the bill in Float by hand. If the bill actually exists, paste its id into the card's Float Bill Id field — the sync adopts it within the hour. That manual-link field is the escape hatch for any weird case; the to-do completes itself if a bill later links.
  • Payment failed / bill voided — investigate in Float; the card note carries the bill link.

Duplicate handling

  • Sender-keyed: a card is compared with the cards its sender sent in the last 60 days. An earlier card counts against it in three ways (rule of 2026-10-01):
  • Same document — every document on the new card is already on the earlier card, as the same file (checksum) or the same text (Odoo's own index of the PDF's text, so a re-rendered copy of the same invoice matches). Held, however far apart. QuickBooks and Zoho re-render the PDF on every send, which is how a "Reminder:" email re-sending an invoice already paid (task 7915) got past the old rule as a second bill. "Every document" so that a static PDF a vendor attaches to each invoice email (terms, a W-9) never makes the next invoice look like the last one. A scan whose only text is an app watermark (under 40 characters) is compared by file only.
  • Numbered look-alike — the same subject or attachment name, containing a digit ("Invoice - 00053 from Matt Alan Currie", "Sound in town Invoice 063.pdf"): it names one invoice, so it is held as before.
  • Generic look-alike — the same plain subject or attachment name ("Invoice", "INVOICE.pdf") but a different document: held only when the two cards arrived within 7 days of each other. Every real re-send and revision on the board came within 3 days, while a contractor who uses the same plain subject every time sends the next invoice weeks later (Erica Fisher's invoices were all held under the old rule: tasks 7600, 7805, 7907). An older look-alike is named in a line on the forward note instead.
  • No-sender fallback: converted and hand-made cards can lack a sender — for those, the same comparison runs across the whole board's 60-day window. This catches the real failure it was built for: one vendor email ticketed in two helpdesk teams and both tickets converted (2026-07-31).
  • Platform senders: invoicing platforms (QuickBooks, Zoho) share one sender address across all their vendors, so attachment-name matches are ignored for them (Zoho names PDFs 00033.pdf, and two unrelated vendors can collide); the same document and exact-subject matches still count.
  • Proof before deploy: scripts/test_float_dup_rule.py runs the real action code against cases from the board's history, shows each part of the rule is load-bearing by mutation, and with --replay judges every card since go-live by the old and new rule (read-only). The 2026-10-01 replay: the old rule's replay reproduced the board's recorded holds exactly for the 55 cards it judged live; the new rule released only Erica Fisher's three, kept every other hold, and added 7915.

Duplicate holds are noise reduction, not payment protection: every bill requires human coding and approval before payment, and that approval is the payment gate. Do not count on Float's intake to catch duplicates — it did not catch the 2026-09-20 re-forward duplicates (same PDF, same invoice number, same amount). A false hold lands in Needs Attention (the safe direction) and is forced through by clearing the state field. A duplicate bill that still reaches Float is voided there by a human, like always.

Caveats

  • One card, one bill. Float creates one bill per PDF attachment (confirmed live: 4 PDFs in one email → 4 bills). The card links one bill (the one furthest along in Float; on a tie, the oldest); each extra bill carrying the card's token gets its own chatter note + to-do. Prefer one invoice per email when talking to vendors.
  • Large files: attachments over ~25 MB can't be emailed — enter those bills in Float manually and set the card's state to Handled manually — no auto-forward.
  • Fraud guard: never act on emailed bank-detail changes for a payee without verifying by phone on a known number. Payee bank details live in Float and changing them is the single most fraud-sensitive action in AP.
  • Pay on terms: Float schedules payment by due date. If a card sits in "In Float" a long time that's often correct (net-30) — the My Week dashboard separately flags drafts stuck without coding.
  • Go-live floor: cards created before 2026-07-17 are never touched by any automation or sweep.

For admins

  • One action source, four copies. An ir.actions.server belongs to exactly one automation rule — binding a shared action to a second rule silently unlinks it from the first (bit us live 2026-07-17) — so each rule carries its own copy of the same code. Source of truth scripts/odoo_actions/float_forward_action.py, deployed by scripts/deploy_float_forward_automation.py. The four rules:
  • Float AP: forward invoice email to Float (rule 126, action 2287) — mail.message on-create, message type email, model project.task. Why mail.message: a task on-create trigger races the mail gateway's attachment linking, and on-message-received never fires for the alias email that creates the task.
  • Float AP: forward converted helpdesk invoice to Float (rule 140, action 2464) — mail.message on-create, message type notification with the helpdesk.ticket backlink Odoo's own conversion note embeds. There is no relational field between task and ticket; the backlink is the only join, and the ticket is where the PDF and sender live.
  • Float AP: forward when invoice document attached (rule 141, action 2465) — ir.attachment on-create on project.task, excluding SIT-TASK-% names. The forward's own attachment copies are additionally excluded by a context guard, so it can never re-trigger itself.
  • Float AP: retry forward when state cleared (rule 125, action 2286) — project.task on-write scoped to the Float Forward State trigger field. Also the sweep's entry point: the sync executes this action server-side on stale cards.
  • Rule 122 is the deactivated v1.
  • Hourly sync + sweep: scripts/float_bill_sync.py (loops-tick GitHub Action, hourly at :45 between 9:45 am and 10:45 pm Dawson Creek time; --dry-run supported). Phases: 0 sweep, 1 status refresh, 2 token match, 3 delivery check + auto-retry + stuck alert. Only cards in state Forwarded to Float / no bill found are tracked — Handled manually cards are invisible to it. The delivery check reads the forward's mail.notification row for the Float intake partner (mail.mail is vacuumed after send; the notification row is the durable delivery record) and re-forwards by clearing the state field — the same mechanism as a staff retry, with context float_ap_skip_dup so the duplicate gate is not re-run on a card that already passed it. Bill-absence decisions are made only when the Float bill walk succeeded that run; delivery-failure (bounce) retries work even with Float unreachable. Offline unit suite: scripts/test_float_retry_logic.py.
  • Schema (stages, field values, intake contact, form views): scripts/setup_float_ap_schema.py (idempotent; the manual state ships from here).
  • Config gate: scripts/test_float_pipeline.py — read-only; pins schema, rule triggers/filters/bindings, live action code against the repo source, and the no-stale-cards invariant. Safe to run any time.
  • History: replaced the broken manual AP entry flow (L10 issue 6795). v4 (2026-08-04) added the convert + attachment paths, the hourly sweep, and the Handled manually state after helpdesk-converted invoices stalled in New (FLOAT_AP_HELPDESK_CATCH_PLAN.md in the repo has the full investigation). v5 (2026-08-17) added delivery tracking + auto-retry and switched the forward to an internal note, after Float's intake rejected mail for ~6 h on 2026-08-10/11: six forwards bounced and a seventh was silently dropped after a clean send (Matt Currie's five invoices plus two other vendor bills, tasks 7538-7544), the cards sat six days on "forwarded", and the vendor-copy leak was found in the same investigation (vendors had been receiving every forward as followers of their own cards). 2026-09-24 fix: between 09-14 and 09-20 Float's email intake began lowercasing attachment filenames and dropping hyphens (SIT-TASK-7851__00061.pdf → sittask7851__00061.pdf). The sync's token parser could not read the new spelling, so every new card looked bill-less and was re-forwarded to the 5-forward cap — 20 bills for 5 documents (Derek Wilder 063/065, Matt Currie 00061/00062, a Driving Force statement; tasks 7851-7857). Not caused by the Odoo 19.3 upgrade: Odoo's outgoing filename never changed, and five cards on 19.3 (09-07 to 09-14) linked on their first forward. The parser now reads any spelling, the sync links the most-advanced bill when a token appears on several, and a retry circuit breaker refuses to re-forward when a look-alike bill exists. 2026-10-01 duplicate rule: Erica Fisher sends every invoice as "Invoice" with INVOICE.pdf, so the subject/name rule held every invoice after her first (7600 forced through by hand in August; 7805, $300, sat 17 days until she chased it; 7907, $287.50, held too; both forced through 10-01). The hold now compares what the documents say — see Duplicate handling.

Staff-facing guide: How-to: Contractor Invoices — the Invoices Received Board