diff --git a/app/routers/items.py b/app/routers/items.py
index 6e88802..f52a42b 100644
--- a/app/routers/items.py
+++ b/app/routers/items.py
@@ -39,7 +39,7 @@ def update_item_category(item_id: int, category: str | None = None, db: Session
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
-
+
# Treat empty string as None
item.category = category if category and category.strip() else None
db.commit()
@@ -77,7 +77,7 @@ def get_price_history(item_id: int, db: Session = Depends(database.get_db)):
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
-
+
# Get price history sorted by timestamp descending (most recent first)
price_history = (
db.query(models.PriceHistory)
@@ -85,5 +85,24 @@ def get_price_history(item_id: int, db: Session = Depends(database.get_db)):
.order_by(models.PriceHistory.timestamp.desc())
.all()
)
-
+
return price_history
+
+
+@router.patch("/{item_id}/availability")
+def update_item_availability(item_id: int, available: bool = True, db: Session = Depends(database.get_db)):
+ """Mark an item as available or unavailable manually"""
+ item = db.query(models.Item).filter(models.Item.id == item_id).first()
+ if not item:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ item.is_available = available
+ # Also clear the error if marking as available
+ if available and item.last_error and "indisponible" in item.last_error.lower():
+ item.last_error = None
+ db.commit()
+ db.refresh(item)
+ return {
+ "message": f"Item marked as {'available' if available else 'unavailable'}",
+ "is_available": item.is_available,
+ }
diff --git a/app/services/scheduler_service.py b/app/services/scheduler_service.py
index 98b0798..a5b1e23 100644
--- a/app/services/scheduler_service.py
+++ b/app/services/scheduler_service.py
@@ -57,6 +57,12 @@ def _update_db_result(
item.current_price = price
item.current_price_confidence = p_conf
+ # Auto-reset availability if price was successfully extracted
+ # A valid price proves the product is still available
+ if item.is_available is False:
+ item.is_available = True
+ logger.info(f"Item {item_id} marked as available again (price found: {price})")
+
session.add(
models.PriceHistory(
item_id=item.id,
diff --git a/frontend/src/components/dashboard/ItemCard.jsx b/frontend/src/components/dashboard/ItemCard.jsx
index 3484587..0cc05a3 100644
--- a/frontend/src/components/dashboard/ItemCard.jsx
+++ b/frontend/src/components/dashboard/ItemCard.jsx
@@ -10,7 +10,7 @@ import { Label } from '@/components/ui/label';
import { Marquee } from '@/components/ui/marquee';
import { cn } from '@/lib/utils';
-export function ItemCard({ item, onEdit, onDelete, onCheck, onZoom, onCategoryUpdate, categories, onViewHistory }) {
+export function ItemCard({ item, onEdit, onDelete, onCheck, onZoom, onCategoryUpdate, categories, onViewHistory, onMarkAvailable }) {
const { t } = useTranslation();
const [showCategorySelect, setShowCategorySelect] = useState(false);
const [showNewCategoryDialog, setShowNewCategoryDialog] = useState(false);
@@ -73,11 +73,24 @@ export function ItemCard({ item, onEdit, onDelete, onCheck, onZoom, onCategoryUp
{/* Product Unavailable Overlay */}
{isProductUnavailable && (
-
+
Produit retiré du site
+ {onMarkAvailable && (
+
+ )}
)}
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index 49e9427..72aad2b 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -102,6 +102,17 @@ export default function Dashboard() {
}
};
+ const handleMarkAvailable = async (itemId) => {
+ try {
+ await axios.patch(`${API_URL}/items/${itemId}/availability?available=true`);
+ toast.success('Produit marqué comme disponible');
+ refreshItems();
+ } catch (error) {
+ console.error('Error marking item as available:', error);
+ toast.error('Erreur lors de la mise à jour');
+ }
+ };
+
const filteredItems = items.filter(item => {
const term = searchTerm.toLowerCase();
const matchesSearch = (
@@ -170,6 +181,7 @@ export default function Dashboard() {
onZoom={setZoomedImage}
onCategoryUpdate={handleCategoryUpdate}
onViewHistory={setHistoryItem}
+ onMarkAvailable={handleMarkAvailable}
categories={categories}
/>
))}
diff --git a/task.md b/task.md
index 86cb895..273c28c 100644
--- a/task.md
+++ b/task.md
@@ -1,43 +1,24 @@
-# Task Board: Catalog Image Fix
+# Correction Statut Disponibilité Produit B&M
-## 🚀 Current Focus
+## Contexte
-- [ ] Investigate Comparator Price Extraction (User reported false prices)
-- [x] Fix Catalog Images (Generic/Missing icons)
-- [/] **Amazon Login Wall Fix** (Redirected by User)
+Le produit "Lot de 24 bougies blanches" sur bmstores.fr est affiché comme "Produit retiré du site" dans Priceflow alors qu'il est bien disponible sur le site (0,88€).
-## 📋 Master Plan
+## Master Plan
-- [x] Analyze `verify_extraction_logic.py`, `ai_service.py`, `ai_schema.py`, `tracking_scraper_service.py`.
-- [x] Proposal Phase: Vision Priority accepted.
-- [x] Implementation Phase:
- - [x] Update `ai_schema.py`.
- - [x] Fix `scheduler_service.py`.
-- [x] Verification Phase: Verified with `verify_vision_priority.py`.
-- [x] **Catalog Retrieval Fix**:
- - [x] Reproduce failure (Confirmed Browserless issue).
- - [x] Fix `cataloguemate_scraper.py` with HTTP fallback.
- - [x] Cleanup debug files.
-- [x] **Catalog Image Fix**:
- - [x] Analyze page HTML for correct image selectors (Found Thumbor URL).
- - [x] **Check PDF availability** (Browser Task).
- - [x] **Refine Scraper Logic**:
- - [x] Update `cataloguemate_scraper.py` to grab `src` and prioritize Thumbor.
- - [x] Update `routers/catalogues.py` to clean up bad images.
- - [x] **Cleanup Bad Data**:
- - [x] Run cleanup endpoint.
- - [x] Verify fix with new scrape.
-- [/] **Comparator Price Fix**:
- - [ ] Reproduce "False Price" issue with script.
- - [ ] Audit `improved_search_service.py` extraction logic (Lowest Price vs AI).
- - [ ] Implement robust price validation/filtering.
- - [ ] Verify fix.
+- [x] Correction automatique du statut lors d'un refresh réussi
+ - [x] Modifier `_update_db_result` dans `scheduler_service.py` pour remettre `is_available = True` quand un prix est détecté
+- [x] Endpoint API pour correction manuelle
+ - [x] Ajouter un endpoint `PATCH /{item_id}/availability` dans `items.py` router
+ - [x] Permettre de modifier `is_available` via l'API
+- [x] Interface utilisateur
+ - [x] Ajouter `onMarkAvailable` prop dans `ItemCard.jsx`
+ - [x] Ajouter le handler `handleMarkAvailable` dans `Dashboard.jsx`
+ - [x] Afficher un bouton "Marquer comme disponible" dans l'overlay des produits indisponibles
-## 📝 Progress Log
+## Progress Log
-- **2025-12-22**: Vision Priority implemented and verified.
-- **2025-12-22**: Fixed Catalog Retrieval using HTTP fallback.
-- **2025-12-22**: Investigating generic icon issue. Found images are served via Thumbor.
-- **2025-12-22**: **FIXED**: Scraper now targets Thumbor images. Wiped bad catalogs.
-- **2025-12-24**: User prioritized Amazon Login Wall fix.
-- **2025-12-24**: Reproduction script `test_amazon_scraper.py` patched to use local browser. Verifying issue.
+- Diagnostic effectué : produit B&M bien disponible (0,88€) mais marqué indisponible dans la DB
+- Modifié `scheduler_service.py` : reset auto de `is_available` quand prix détecté
+- Ajouté endpoint API `PATCH /api/items/{id}/availability`
+- Ajouté bouton dans l'overlay frontend pour correction manuelle