The Big Picture
Before getting into specific orders, it helps to understand what each system actually does. Each platform has a job, and they hand off information to each other.
The key idea
Shopify is the traffic controller for the order. ShipStation is the shipping department. Odoo is the accounting office.
For Terre today, the invoice that goes to the customer is sent from Shopify. Odoo is our system of record — it keeps an internal invoice record for accounting, tracks inventory, and reconciles payments, but it does not send invoices to customers.
So when Odoo shows an order in "To Invoice" status, that's about Odoo's internal accounting record — not about whether the customer has been invoiced.
Even though Odoo doesn't send the customer invoice, it still needs to know the order shipped so it can record the sale, draw down inventory, and close out the AR record. Odoo learns this when it sees the order marked as fulfilled — a status that flows from ShipStation → Shopify → Odoo. If anything breaks in that chain, the order sits in "To Invoice" waiting for someone to push it through.
Why Some Orders Auto-Invoice — and Some Don't
Customer Service noticed that some orders close out on their own in Odoo and some don't. Here's what's going on under the hood.
Odoo has a "rulebook" for auto-invoicing
For every order that comes in, Odoo checks three things and looks for a matching rule to decide whether to automatically create its internal invoice record:
We've configured this rulebook thoroughly. The way we'd find out a rule is missing is when an order fails to import or update — and right now, the only failures we're seeing are stock-related, not rule-related. So the rulebook itself appears to be in good shape.
Even with the rulebook complete, an individual order can still slip through — usually because of a one-off sync hiccup, an older order that pre-dates the current setup, or a stock issue at the time of import. Once an order falls outside the connector's "fresh import" window, Odoo won't go back and re-process it on its own — someone has to push it through manually. That's exactly what happened with order #2135.
What this means in practice
| Scenario | Result |
|---|---|
| Order syncs cleanly through Shopify → Odoo | ✅ Auto-invoices in Odoo when it ships |
| Order missed its sync window (older or hiccup) | ⚠️ Sits in "To Invoice" — needs a manual re-import |
| Order has a stock issue (not enough inventory) | ❌ Doesn't import — ends up in the "failed queue" |
Observation 1: Nikkel Iron Works #2135
The customer paid, but Odoo never closed out the order. Here's the story.
What actually happened
What we did to fix it
I ran the "import/update specific orders" operation in the Odoo → Shopify connector, pointed it at order 2135, and forced Odoo to re-read what Shopify had. Now Odoo correctly shows it as fulfilled and the internal invoice record is in place.
Clearing up one part of the original message
The theory
"The customer used the pay button in the email they received for their invoice."
What really happened
Kaya sent the invoice from Shopify on 4/21 — which is exactly how Terre's process is supposed to work today. That's where the customer's invoice always comes from for us. Shopify still shows the order as unpaid, which suggests payment came in by check.
Mark the order as paid in Shopify, then confirm Odoo's internal invoice status moves to "In Payment" (or move it manually if not).
Other older orders may be sitting in the same state — fulfilled in Shopify, but Odoo never caught the update. Someone will need to go through Odoo's "To Invoice" list and manually re-import any that are already shipped in Shopify.
Observation 2: Why "To Invoice" Shows Unshipped Orders
When you opened Odoo's "To Invoice" list, you saw 88 lines. Most of those orders haven't shipped yet. Here's why they're showing up there anyway.
The culprit: the "Shopify Discount Product"
When a business customer gets special pricing through Shopify, the discount comes through as a line item on the order called the "Shopify Discount Product". This line is what makes the math work.
That discount item has a setting called "Prepaid / Fixed Price" — meaning Odoo treats it as something that's invoiceable the moment the order is placed, not when it ships.
The fix when looking at the list
When you open Sales App → To Invoice → Orders to Invoice, turn on the "Shipped Order" filter. That hides the noise and shows only orders where physical goods have actually shipped. On the current list this takes it from 90 orders down to just 3 truly invoiceable orders.
Example: Hytrol Conveyor #6802 — the order flagged in the original email. It's on hold in Shopify until 6/23 and appears in "To Invoice" because of the discount-line behavior above, not because we should be invoicing it. With the "Shipped Order" filter on, it won't appear. All 3 of the truly invoiceable orders currently have stock issues that need to be resolved before they can be closed out. The supply chain side is aware.
Once we have business-customer pricing properly set up in Shopify (without the workaround discount line), this whole problem goes away.
Other Things I Found Along the Way
While digging into the two main observations, three other patterns came up that the team should be aware of.
Finding 1: Orders failing to import into Odoo
There's a section in Odoo called the "Order Queue" — it's where Shopify orders land before being processed. Some of them are getting stuck.
Why these are stuck: Both groups are failing for the same general reason — Odoo thinks the inventory isn't there to fulfill them. Some errors come from stock moves that would push a location into negative inventory; others are general low-stock issues.
Translation: 14 orders from Shopify aren't in Odoo at all yet. And 80 more are in Odoo but can't be updated because of stock issues. (Notably: none of these failures are due to missing invoicing rules — they're all inventory-related, which is why we believe the rulebook itself is solid.)
Finding 2: Replacement orders aren't linked to originals
There are 50 orders in Odoo that look like replacement / no-charge orders — all $0, most already invoiced, all created either manually or through our bulk import tool. I've created a saved search in Sales → Orders → Replacement Orders so you can pull them up easily.
These replacement orders aren't tied back to the original order they're replacing. That makes it hard to answer questions like "did we send a replacement for that complaint?" or to reconcile returns properly. We need a documented return / replacement process.
Finding 3: General data hygiene
The two main issues you raised both come back to the same root cause: we're catching up on history. As we keep tightening up the configuration and clearing the backlog, day-to-day operations should get smoother.
The Road Ahead — What's About to Change
The invoicing picture isn't going to stay where it is today. Here's the direction we're heading so the team knows what to expect.
Direct to Odoo
Retail customers have more complex workflows. We're bringing them into Odoo directly, bypassing Shopify and ShipStation entirely.
Invoiced from: Odoo
Better Shopify setup
Business customers will get properly configured pricing in Shopify (no more discount-line workaround). This is the only group that will continue to be invoiced from Shopify.
Invoiced from: Shopify
Pay before shipping
Consumer customers must pay before we ship. No AR exposure on this group going forward.
Invoiced from: N/A — prepaid
The whole reason the "Shopify Discount Product" exists is to handle business-customer pricing today. Once business customers are properly configured in Shopify, the workaround line goes away — and "To Invoice" in Odoo will finally mean what it sounds like it means.
Going Forward — What Needs to Happen
Here's a clean checklist of what we need to do and what should be on our radar.
Immediate
- Mark order #2135 as paid in Shopify, then confirm Odoo's internal invoice status moves to "In Payment".
- Find the other stragglers. Pull up Odoo's "To Invoice" list with the Shipped Order filter turned off. Cross-check anything fulfilled in Shopify but stuck in Odoo, and run the "import/update specific orders" operation on each one.
- Resolve stock issues on the 3 currently invoiceable orders so they can be closed out.
Short-term
- Work through the 80 failed queue lines in the Shipped Order Queue. Each one needs the inventory issue resolved first, then it'll process.
- Get the 14 missing orders into Odoo — same root cause, same fix.
- Get business-customer pricing handled properly in Shopify. This is the big one — it makes "To Invoice" actually mean "ready to invoice."
Longer-term
- Bring retail customers into Odoo directly, bypassing Shopify and ShipStation for their more complex workflows.
- Build a return / replacement process that links replacement orders to the original. This will give us proper traceability and clean up the 50 floating $0 orders.
Daily working habit for the team
- For Terre today, customer invoices come from Shopify. Odoo's "To Invoice" status is internal.
- In Sales → To Invoice → Orders to Invoice, always turn on the "Shipped Order" filter. That's the real list.
- Anything in there that has shipped but won't close out → the order probably needs a manual re-import via the Shopify connector. Loop in IT if you're not sure.
- Remember: ShipStation does not talk to Odoo. If something looks weird, the question is almost always about whether Shopify and Odoo agree on the fulfillment status.
Happy to walk through any of this on a screen-share, or dig into specific orders if you find more anomalies. Send them my way.