How Product Deletion and Soft-Delete Logic Works
Source: products_controller.rb:240-264, products_controller.rb:249-257, product.rb:7
- Three-tier deletion: (1) If the product has active invoice items, PO items, vendor invoice items, or a Xero ID, deletion is completely blocked. (2) If it only has historical (unscoped) references, it is soft-deleted (deleted = true). (3) If it has zero references and no active stocktake, it is hard-deleted permanently.
- A product currently included in an active stocktake cannot be deleted at all -- the error message explicitly mentions the stocktake
- The default scope on the Product model excludes deleted and inactive products from all standard queries:
where("products.deleted is not true and products.inactive is not true") - Soft-deleted products still exist in the database and may appear in reports or unscoped queries
- Reactivating a product requires an unscoped query because the default scope hides it from normal lookups
Support scenarios
- "Why can't I delete this product?" → It has active invoice items, a purchase order reference, or a Xero ID linked to it
- "Why does my deleted product still show up in reports?" → It was soft-deleted (marked deleted = true) rather than permanently removed because it had historical references
- "I'm getting an error about stocktake when deleting" → The product is in an active stocktake; process or cancel the stocktake first
- "How do I find my deleted product to reactivate it?" → Use the reactivate function, which uses an unscoped query to find inactive/deleted products