Skip to content

TJK: Restore stock alert emails broken since upstream PR #2018 (OBPIH-3280)#6

Open
gqcorneby wants to merge 3 commits into
release/est/tjk/0.9.7from
feature/stock-alert-restore
Open

TJK: Restore stock alert emails broken since upstream PR #2018 (OBPIH-3280)#6
gqcorneby wants to merge 3 commits into
release/est/tjk/0.9.7from
feature/stock-alert-restore

Conversation

@gqcorneby
Copy link
Copy Markdown

📌 References

🐛 Bug

Stock alert emails (low-stock, out-of-stock, overstock) have been broken upstream since November 2020. Expiry alerts were unaffected.

Upstream PR openboxes#2018 (OBPIH-3280) refactored how live quantity-on-hand is tracked — moving it from transaction_entry aggregations into the product_availability table. That part works correctly (stock cards show accurate QOH). However, the same PR deleted RefreshInventorySnapshotAfterTransactionJob, the job responsible for writing rows into inventory_snapshot, without delivering the stated Phase 2 follow-up: "need to ETL product availability records to inventory snapshot."

The stock-alert view chain (product_inventory_snapshot_viewproduct_inventory_extended_viewproduct_inventory_pivot_viewproduct_inventory_compare_view) reads exclusively from inventory_snapshot. With nothing writing to it after transactions, the snapshot stays stale and the SendStockAlertsJob finds nothing to alert on. Upstream issue openboxes#4742 tracks the symptom since July 2024 — open, no comments, no linked PR.

📝 Implementation

InventorySnapshotEtlService under org.pih.warehouse.custom.stockAlertRestore — finishing Phase 2 as custom code rather than rolling back upstream's pivot. product_availability remains the live source of truth; inventory_snapshot becomes a derived projection of it.

How it works:

  • Listens to RefreshProductAvailabilityEvent (fired by Transaction.afterInsert/afterUpdate) — same trigger the deleted job used
  • Registers work inside TransactionSynchronizationManager.afterCommit() so transaction_entry rows are visible before product_availability is recomputed (running synchronously inside the still-open transaction would race the insert and read stale state)
  • Calls productAvailabilityService.refreshProductsAvailability with forceRefresh=true to purge stale lot/bin rows before reinserting
  • Replaces stale snapshot rows with a DELETE + INSERT (not ON DUPLICATE KEY UPDATE, which would leave orphaned lot/bin combinations from previous transactions)
  • Writes rows dated current_date + 1, matching what product_inventory_snapshot_view expects for current_quantity
  • Gated by openboxes.jobs.refreshInventorySnapshotAfterTransactionJob.enabled (reuses the config key from the deleted job, already true in application.yml)

Activity gating is preserved by design: alerts only fire on days with transactions, matching the original intent and avoiding daily-email spam for unchanged stock levels.

✨ Description of Change

A single new service in the custom/stockAlertRestore package. No upstream files modified.

🔥 Notes to the tester

cd docker && docker compose up -d
  1. Find a product with a low-stock threshold configured
  2. Adjust stock so QOH falls below the threshold
  3. Trigger SendStockAlertsJob via Admin > Quartz > Jobs
  4. Verify a stock alert email is received
  5. Verify the QOH in the email matches the current stock card value
  6. Verify expiry alerts continue to work (regression check)

gqcorneby added 3 commits May 15, 2026 16:07
Restores Phase 2 of OBPIH-3280. PR openboxes#2018 replaced inventory_snapshot
with product_availability as the live qty source but deleted
RefreshInventorySnapshotAfterTransactionJob with an explicit deferred
follow-up "need to ETL product availability records to inventory
snapshot" — never delivered, leaving stock-alert reports empty for
5+ years.

This service listens to RefreshProductAvailabilityEvent (already fires
on every Transaction.afterInsert), synchronously refreshes
product_availability for the affected products, and copies the rows
into inventory_snapshot with date = current_date + 1 — the exact
shape the stock-alert compare view expects. INSERT column list and
ON DUPLICATE KEY UPDATE behavior match the pre-2020
generateInsertInventorySnapshotStatement; data source is the new
event-driven product_availability table, finishing the architectural
pivot OBPIH-3280 started.

Preserves activity-gated alerting: snapshot rows only appear when
transactions happen, so the view's BETWEEN current_date AND
current_date + 1 filter still acts as the no-spam gate.

Gated by openboxes.jobs.refreshInventorySnapshotAfterTransactionJob.enabled
(truthy-check matching the deleted job's pattern). Upstream
application.yml still ships this key at true, so default behavior is on.

Refs: openboxes#4742, openboxes#2018
Defensive override so the stock-alert ETL keeps running on TJK even
if upstream eventually removes the orphan
openboxes.jobs.refreshInventorySnapshotAfterTransactionJob.enabled
default from application.yml during a future housekeeping pass.

The key is read by our InventorySnapshotEtlService (custom code) but
the deployment-side override here is what guarantees we don't depend
on upstream's default staying around.
- defer ETL to afterCommit so transaction_entry rows are visible
  before product_availability is recomputed
- use forceRefresh=true to purge stale pa rows before reinserting
- replace ON DUPLICATE KEY UPDATE with DELETE+INSERT to remove
  stale (lot, bin) combinations from the snapshot
@gqcorneby gqcorneby changed the base branch from develop to release/est/0.9.7 May 22, 2026 04:38
@gqcorneby gqcorneby changed the base branch from release/est/0.9.7 to release/est/tjk/0.9.7 May 22, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant