Flow
What now stands between an import and another supplier's product
1Wide lookups deleted
The any-supplier ID, SKU and name lookups are gone. One brand-limited name search still looks at every product.
2Check before each write
If another supplier holds a link to the product, the import stops with a conflict error.
3Catch looks for the conflict
The per-row catch ends the import on that error instead of counting one more failed row.
4Check before any row
If this supplier already shares a product with another one, the whole file is refused.
5Routes check the owner
Edit and delete load the link first and say not found if it belongs to someone else.
6One supplier per product
The migration clears doubles, moves cart lines to the kept link, and adds the database rule last.
The supplier import in the ordering system had a bad habit. When a row in one supplier's sheet didn't match that supplier's products, it went and searched everyone's. Then it wrote to whatever it found.
How the matching worked
The import reads a sheet for one supplier, one row at a time. It looked in that supplier's own products first, by ID, SKU and a loose name match. If those missed, it tried the ID and SKU again over every product in the system. Then it tried a name match across all products, limited by brand. If that missed too, it ran the loose name match over every product as well.
Whatever it found got a new case size. That number sits on the product itself, which every supplier linked to it shares.
Why it mattered
The database let two suppliers link the same product. It only stopped one supplier from linking it twice. So an import for one supplier could find a product that another supplier also used, and change a number that supplier counted on. I can see the path was open. I can't see that it ever actually happened.
What closed it
The any-supplier lookups are gone. A fallback whose job is to search outside the catalog isn't worth patching.
One wide search is still there: the brand-limited name match still looks at every product. So now, before the import writes to a product, it checks whether any other supplier holds a link to it. If one does, it stops with a conflict error. The same check guards hand edits to case size and new links.
That error almost got lost. Each row ran inside a catch that only counted failures, so the new error would have become one more failed row. The catch now checks for the conflict first and ends the import.
There's also a check before any row runs. If this supplier already shares a product with another one, the whole file is refused. Refusing up front matters, since as far as I can tell, stopping halfway leaves the earlier rows written. It won't catch everything, though: a row the brand search matches to another supplier's product still stops the run partway.
The edit and delete routes trusted the supplier ID in the URL without checking that the link was really that supplier's. Now they check and say "not found" when it isn't.
None of this came with tests, and it shipped alongside a big new order-grid screen and some unrelated tweaks. Nothing in it tries to write into another supplier's catalog to see whether the new check says no.
The database rule
Code checks only help the code that calls them. The database now allows just one supplier per product, so no path can add a second link. Before that rule could go on, the migration cleaned up existing doubles. It picked a winner for each product, active and recently updated first, moved cart lines over to it, deleted the rest, and added the rule last. If carts move first, nothing points at a deleted row.
The catch is that the losing links' prices and SKUs are thrown away, and the winner is picked by recency, not by who really owned the product.