Skip to content

Conversation

@joshheald
Copy link
Contributor

@joshheald joshheald commented Nov 17, 2025

Merge after: #16353

Description

This PR ensures that trashed products are removed from POS after an incremental sync.

We already hide them if they're trashed when we do a full sync – because the API doesn't return them for a normal request. However, that works against us if they've been trashed when we do an incremental sync, because the newly-trashed product won't be returned for those requests either.

To resolve that, we do an extra request for any newly trashed products, and include those in the local database. When we've done that, we filter those products from all our queries.

Note that Variations can't be trashed, they just get deleted.

Test Steps

Set up a new product with a GTIN that you can scan with a barcode scanner

  1. Ensure you've done a full sync
  2. Trash a product in WP-Admin
  3. Pull to refresh
  4. Observe that product is no longer shown in the list
  5. Scan the barcode
  6. Observe that a Not found error is shown, same as for deleted products

Search isn't affected by this, as it doesn't use the local catalog yet.

Fully deleted products are still not removed by incremental sync, but will be by the next full sync. Without extensive backend work, this is unavoidable.

Screenshots

This shows:

  1. View a product
  2. Trash it
  3. Pull to refresh
  4. Scroll down – it's gone
  5. Trash another product on the first page
  6. Pull to refresh
  7. Product is gone
  8. Restore product
  9. Pull to refresh
  10. Product is back
trashed.products.mp4

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

joshheald and others added 18 commits November 14, 2025 16:21
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add tests verifying include_status parameter is correctly added/omitted
- Add test verifying status is included in _fields request parameter
- Update MockPOSCatalogSyncRemote to support includeStatus parameter
- Update test fixtures to include required statusKey field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add tests verifying both regular and trashed products are fetched
- Add test ensuring same modifiedAfter date is used for both requests
- Add test verifying includeStatus parameter values
- Add tests for combined persistence of regular and trashed products
- Add tests for edge cases (empty trash, only trash updates)
- Enhance MockPOSCatalogSyncRemote to support separate trashed product results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Replace non-thread-safe array append with actor-based IncludeStatusTracker
to prevent EXC_BAD_ACCESS crash when concurrent requests modify the array.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add "status": "publish" to product fixtures that were missing the field,
fixing decoding errors in POSCatalogSyncRemoteTests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Wrap single product objects in {"data": [...]} envelope to match
standard WooCommerce REST API response format expected by tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add statusKey field with default value "publish" to TestProduct struct
to match updated PersistedProduct schema, fixing NOT NULL constraint errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@joshheald joshheald added this to the 23.8 milestone Nov 17, 2025
@joshheald joshheald added type: task An internally driven task. feature: POS labels Nov 17, 2025
@joshheald joshheald changed the base branch from trunk to issue/woomob-1686-local-catalog-handle-errors-from-order-creation-with November 17, 2025 12:31
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Nov 17, 2025

App Icon📲 You can test the changes from this Pull Request in WooCommerce iOS Prototype by scanning the QR code below to install the corresponding build.

App NameWooCommerce iOS Prototype
Build Numberpr16361-9538e40
Version23.7
Bundle IDcom.automattic.alpha.woocommerce
Commit9538e40
Installation URL489bq3lm85hpo
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

The catalog download parser decodes all items as POSProduct first before
filtering by type, so variations also need the status field to avoid
decoding errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@joshheald joshheald marked this pull request as ready for review November 17, 2025 13:46
Base automatically changed from issue/woomob-1686-local-catalog-handle-errors-from-order-creation-with to trunk November 18, 2025 14:37
@iamgabrielma iamgabrielma self-assigned this Nov 19, 2025
Copy link
Contributor

@iamgabrielma iamgabrielma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a comment about excluding private products

🚀

productTable.column("stockQuantity", .double)
productTable.column("stockStatusKey", .text).notNull()

productTable.column("statusKey", .text).notNull()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, with GRDB is as easy to update the model? Or we just happen to work with the initial schema so it's straight-forward?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once it's released, we'll need to make new schemas and migrations for changes like this... that's the reason for the V001 in the name. It's still very lightweight to change.

During development, we delete the database when a schema change is detected and make it fresh. I guess we could even do that in production, but it risks someone having to do an extra full sync at an inconvenient time.

/// Returns a request for POS-supported products (simple and variable, non-downloadable) for a given site, ordered by name
/// Filters out products with trash, draft, pending, or private status to ensure only published and 3rd party custom status products are shown
static func posProductsRequest(siteID: Int64) -> QueryInterfaceRequest<PersistedProduct> {
let excludedStatuses = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would make sense to import Networking here and use the raw values for ProductStatus rather than the string directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't import Networking from Storage as they're siblings

"trash",
"draft",
"pending",
"private"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we want to exclude private products. While these are hidden from public view on the shop page and in search results, this is a web-based thing, for POS it would make sense to keep it included as the product is still visible to any logged-in user that is not a customer (admins, editors, etc, ... )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, thank you!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the try await sut.startIncrementalSync(... in this file throw a warning since the result is unused, let's make them let _ = or discardableresult

@joshheald joshheald enabled auto-merge November 19, 2025 17:27
@joshheald joshheald merged commit 20876f1 into trunk Nov 19, 2025
14 checks passed
@joshheald joshheald deleted the issue/woomob-1493-local-catalog-filter-trashed-products branch November 19, 2025 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: POS type: task An internally driven task.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants