# Provider Sync Enhancements > Status: Planned > Tier: 3.5 (Business tools) Small improvements to provider sync for better visibility and resilience. Not versioning products (overkill), just better audit trail and handling of edge cases. --- ## Overview Three enhancements to the provider sync system: 1. **Detailed audit logging** — Field-level change tracking, not just "3 updated" 2. **Soft-delete for discontinued products** — Don't break carts/orders/links 3. **Variant-level availability** — Handle out of stock / discontinued variants 4. **Price change alerts** — Warn when margins might be affected --- ## 1. Detailed sync audit logging Currently `sync.completed` logs "47 products, 3 updated" but doesn't capture *what* changed. Enhance to log field-level changes. **Implementation:** ```elixir # In product sync, before update def sync_product(product, provider_data) do changes = diff_product(product, provider_data) if changes != %{} do ActivityLog.log_event("sync.product_updated", "#{product.title}: #{summarize_changes(changes)}", payload: %{product_id: product.id, changes: changes} ) end update_product(product, provider_data) end defp diff_product(current, new) do fields = [:title, :description, :base_price, :status] Enum.reduce(fields, %{}, fn field, acc -> old_val = Map.get(current, field) new_val = Map.get(new, field) if old_val != new_val, do: Map.put(acc, field, {old_val, new_val}), else: acc end) end defp summarize_changes(changes) do changes |> Map.keys() |> Enum.map(&to_string/1) |> Enum.join(", ") # => "title, base_price" end ``` **Activity log entries:** - `sync.product_updated` — "Awesome T-Shirt: title, base_price changed" - `sync.product_added` — "New product: Cool Hoodie" - `sync.product_removed` — "Product removed: Old Mug" (if provider removes it) --- ## 2. Soft-delete for discontinued products When a product disappears from the provider catalogue (sync returns 404 or product not in list), don't hard-delete it. Set `status: "discontinued"` instead. **Why:** - Existing carts might have this product - Order history references it - Links from external sites won't 404 immediately **Implementation:** ```elixir # products schema field :status, :string, default: "active" # "active", "discontinued", "draft" # In sync def handle_missing_product(product) do Products.update_product(product, %{status: "discontinued"}) ActivityLog.log_event("sync.product_discontinued", "#{product.title} no longer available from provider", level: "warning", payload: %{product_id: product.id} ) end ``` **UI behaviour:** - Discontinued products hidden from shop browse/search - Product page shows "This product is no longer available" - Cart shows warning if item is discontinued - Admin can see discontinued products in a filtered view --- ## 2b. Variant-level availability Variants can go out of stock or be discontinued independently of the product. This matters because: - A customer may have "Blue / Large" in their cart when that specific variant becomes unavailable - The product is still active, just that combination isn't **Variant statuses:** ```elixir # variants schema field :availability, :string, default: "available" # "available", "out_of_stock", "discontinued" ``` **Sync handling:** ```elixir def sync_variant(variant, provider_data) do new_availability = case provider_data do %{is_available: false, is_enabled: false} -> "discontinued" %{is_available: false} -> "out_of_stock" %{is_available: true} -> "available" end if variant.availability != new_availability do Products.update_variant(variant, %{availability: new_availability}) if new_availability in ["out_of_stock", "discontinued"] do ActivityLog.log_event("sync.variant_unavailable", "#{product.title} (#{variant.title}): now #{new_availability}", level: "warning", payload: %{variant_id: variant.id, product_id: variant.product_id} ) end end end ``` **Cart impact:** - On cart load: check all variants still available - If out of stock: show warning "This size/colour is currently out of stock" - If discontinued: show warning "This option is no longer available" - Prevent checkout if cart contains unavailable variants (or offer to remove them) **Product page:** - Out of stock variants: show crossed out or greyed, not selectable - Discontinued variants: hide entirely from selector - If all variants unavailable: show "Currently unavailable" on product --- ## 3. Price change alerts Log a warning when a product's cost basis changes significantly (>5% or configurable threshold). Helps shop owner know when their margins might be affected. **Implementation:** ```elixir def check_price_change(product, new_cost) do old_cost = product.base_cost return if old_cost == nil or old_cost == 0 change_pct = abs((new_cost - old_cost) / old_cost * 100) if change_pct > 5 do ActivityLog.log_event("sync.price_change", "#{product.title}: cost changed from £#{old_cost} to £#{new_cost} (#{round(change_pct)}%)", level: if(change_pct > 20, do: "warning", else: "info"), payload: %{product_id: product.id, old_cost: old_cost, new_cost: new_cost, change_pct: change_pct} ) end end ``` --- ## Files to Modify | File | Changes | |------|---------| | `lib/berrypod/products/product.ex` | Add `status` field if not present | | `lib/berrypod/products/variant.ex` | Add `availability` field | | `lib/berrypod/providers/printify.ex` | Add diff logic, price change check, soft-delete handling, variant availability sync | | `lib/berrypod/providers/printful.ex` | Same as above | | `lib/berrypod/products.ex` | Query helpers for discontinued products/variants | | `lib/berrypod_web/live/shop/pages/product_page.ex` | Handle discontinued status, variant availability display | | `lib/berrypod_web/components/shop_components/cart.ex` | Warn on discontinued/unavailable items | | `lib/berrypod_web/components/shop_components/product.ex` | Grey out/hide unavailable variants in selector | --- ## Tasks | # | Task | Est | |---|------|-----| | 1 | Add `status` field to products, `availability` field to variants | 30m | | 2 | Product discontinued handling in sync (soft-delete) | 45m | | 3 | Variant availability sync (out of stock / discontinued) | 45m | | 4 | Detailed sync audit logging (field-level diff) | 1h | | 5 | Price change alerts with threshold | 30m | | 6 | Product page UI: discontinued products, variant availability display | 1h | | 7 | Cart UI: warn on discontinued/unavailable items, prevent checkout | 1h | | **Total** | | **5.5h** | --- ## Verification 1. Manually trigger a sync 2. Check activity log shows field-level changes 3. Remove a product from provider (or mock it) — verify soft-delete 4. Change a product's cost — verify price change alert appears 5. Try to add discontinued product to cart — verify warning 6. Mark a variant as out of stock — verify greyed out on product page 7. Mark a variant as discontinued — verify hidden from selector 8. Add item to cart, then mark its variant unavailable — verify cart warning 9. Try to checkout with unavailable variant — verify blocked or prompted to remove