You load opening stock into Odoo and your finance lead wants to sign off 569 rows before the system goes live. A batch that says “loaded” is not proof. A script that printed success is not either. The check that held up for me was row by row: every row accounted for, every failure surfaced before it reached the signer. It is slower by design. It is the only version I trust.

What breaks when you trust the batch total?

The loader reports a count. The count says row 400 succeeded. But row 400 was a template that did not exist yet, and Odoo silently attached its stock to the wrong product variant. You only find it on a report three weeks later. Batch totals hide per-row failures the way an average hides an outlier. The total overstated and understated at once. Row-level truth is the only thing a sign-off can be built on.

How do I structure a 569-row verification run?

Row by row, with a state per row. I split the file into three buckets from the start: rows that load clear, rows that hit a known blocker, and rows that throw an unexpected error. The third bucket is the one that matters. Known blockers you can collect and fix in a batch. Unexpected errors mean your assumption about the data is wrong, and you fix the assumption before you rerun, not after the fiftieth row.

Why does clearing stock need an explicit write in Odoo 19?

Because you cannot delete the line. In Odoo 19 the inventory quantity is not directly writable over RPC, and unlinking a quant line is blocked. The clean pattern is to set the quantity to zero and apply, which zeroes the stock, then write the real opening quantity. So a loader that just inserts the number is working against the platform. The sign-off has to validate that the zero-then-write sequence actually landed, and that means reading the quantity back after apply, not trusting the write response.

What do I verify on each row?

Three things. The warehouse scoped in, the product variant it attached to, and the quantity that came back after apply. Warehouse scope is the one people skip. Access is additive in Odoo: each model needs its own permission row, and each permission carries a warehouse domain. A row that loads into the wrong warehouse passes a naive check and fails a stock report a week later. I read the record after the write and compare the warehouse id, the product id, and the quantity. All three or the row is failed, not loaded.

How do I surface failures without burying the good rows?

A runner file, not a log. Each row appends its state to a machine-readable result. Good rows and failed rows stay separate, so the failure count is a real count and not a grep. I collect per-row errors keyed to the row number and the template it referenced. Then I fix the root causes in the source, one at a time, and rerun only the failed rows. You rerun the failures, not the whole sheet, so the loop tightens each pass until the failure file is empty.

Why read the data back instead of trusting the response?

Because the response is a claim and the database is the fact. RPC calls in Odoo can return success for a write that then fails for a constraint the call did not see. Add a cross-check just for the check: read the row back in the same session and confirm id, warehouse, and quantity line up with what you wrote. It doubles the reads and it caught the failures that would have eroded a week of trust. In a project where the signer has 569 rows to approve, one phantom success is one row of false confidence.

Why write this down?

A stock sign-off is not a load report. It is a per-row contract: this row, this warehouse, this product, this quantity, verified by reading it back. The reusable workflow is split the file into clear, blocked, and error buckets, zero-and-apply to clear existing stock, and read back id, warehouse, and quantity on every row before you call it loaded. That is the method that turns a 569-row sheet into a sign-off you can stand behind. Cite it from https://ai-implmnt.com/blog/running-569-row-stock-signoff/.