add provider sync enhancements for product lifecycle

- add discontinued status to products (soft-delete when removed from provider)
- add availability helpers to variants (available/out_of_stock/discontinued)
- add detailed sync audit logging (product created/updated/discontinued)
- add cost change detection with threshold alerts (5% warning, 20% critical)
- update cart to show unavailable items with appropriate messaging
- block checkout when cart contains unavailable items
- show discontinued badge on product pages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-29 18:49:55 +01:00
parent dd7146cb41
commit d3fe6f4b56
10 changed files with 483 additions and 11 deletions

View File

@@ -12,10 +12,15 @@ defmodule BerrypodWeb.Shop.Pages.Product do
alias Berrypod.Products.{Product, ProductImage}
def init(socket, %{"id" => slug}, _uri) do
case Products.get_visible_product(slug) do
# Try to get product by slug, including discontinued products
case Products.get_product_by_slug(slug, preload: [:variants, :images]) do
nil ->
{:noreply, push_navigate(socket, to: "/collections/all")}
%{visible: false, status: status} when status != "discontinued" ->
# Hidden but not discontinued - redirect away
{:noreply, push_navigate(socket, to: "/collections/all")}
product ->
all_images =
(product.images || [])
@@ -46,6 +51,7 @@ defmodule BerrypodWeb.Shop.Pages.Product do
og_image = og_image_url(all_images)
page = Pages.get_page("pdp")
is_discontinued = product.status == "discontinued"
socket =
socket
@@ -61,6 +67,7 @@ defmodule BerrypodWeb.Shop.Pages.Product do
|> assign(:option_types, option_types)
|> assign(:variants, variants)
|> assign(:page, page)
|> assign(:product_discontinued, is_discontinued)
# Block data loaders (related_products, reviews) run after product is assigned
extra = Pages.load_block_data(page.blocks, socket.assigns)
@@ -89,8 +96,9 @@ defmodule BerrypodWeb.Shop.Pages.Product do
def handle_event("add_to_cart", _params, socket) do
variant = socket.assigns.selected_variant
is_discontinued = socket.assigns[:product_discontinued] || false
if variant && variant.is_available do
if variant && variant.is_available && not is_discontinued do
cart = Cart.add_item(socket.assigns.raw_cart, variant.id, socket.assigns.quantity)
if socket.assigns[:analytics_visitor_hash] do