✅ New functionality added (soft delete system) ✅ Backward compatible (existing features unchanged) ✅ Significant enhancement (complete temporal tracking system) ✅ API additions (new endpoints, parameters) ✅ UI enhancements (new components, visual indicators)
271 lines
9.3 KiB
Markdown
271 lines
9.3 KiB
Markdown
# Soft Delete Implementation with Historical Tracking
|
|
|
|
## Overview
|
|
|
|
This implementation extends the existing temporal tables system to support soft deletes with proper historical tracking. When a product is "deleted", it's not actually removed from the database but marked as deleted with a timestamp, while maintaining full historical data.
|
|
|
|
## Key Features
|
|
|
|
### 1. Soft Delete Mechanism
|
|
- Products are marked as `deleted = true` instead of being physically removed
|
|
- Deletion creates a historical record of the product's state before deletion
|
|
- Deleted products get a new `valid_from` date set to the deletion date
|
|
- All historical versions remain intact for audit purposes
|
|
|
|
### 2. UI Enhancements
|
|
- **Product List**: "Show deleted" toggle next to "Add New Product" button
|
|
- **Visual Indicators**: Deleted products shown with:
|
|
- Red background tint and reduced opacity
|
|
- Strikethrough text
|
|
- 🗑️ emoji indicator
|
|
- Disabled edit/duplicate/delete actions
|
|
- **Shopping Events**: Products deleted before/on shopping date are automatically filtered out
|
|
|
|
### 3. API Behavior
|
|
- **Default**: Deleted products are hidden from all product listings
|
|
- **Optional**: `show_deleted=true` parameter shows all products including deleted ones
|
|
- **Shopping Events**: New endpoint `/products/available-for-shopping/{date}` filters products based on deletion status at specific date
|
|
|
|
## Database Schema Changes
|
|
|
|
### Products Table
|
|
```sql
|
|
ALTER TABLE products
|
|
ADD COLUMN deleted BOOLEAN DEFAULT FALSE NOT NULL;
|
|
```
|
|
|
|
### Products History Table
|
|
```sql
|
|
-- Already included in products_history table
|
|
deleted BOOLEAN DEFAULT FALSE NOT NULL
|
|
```
|
|
|
|
### Updated Trigger
|
|
The `products_versioning_trigger` now **consistently handles ALL historization**:
|
|
- **UPDATE operations**: Creates history records for both automatic and manual versioning
|
|
- **DELETE operations**: Creates history records when products are deleted
|
|
- **Smart versioning**: Automatically detects manual vs automatic versioning based on `valid_from` changes
|
|
- **Centralized logic**: All temporal logic is in the database trigger, not split between trigger and application
|
|
|
|
### Trigger Benefits
|
|
1. **Consistency**: All versioning operations follow the same pattern
|
|
2. **Reliability**: Database-level enforcement prevents inconsistencies
|
|
3. **Simplicity**: Application code just sets fields, trigger handles the rest
|
|
4. **Performance**: Single database operation handles both data update and history creation
|
|
|
|
## API Endpoints
|
|
|
|
### Modified Endpoints
|
|
|
|
#### `GET /products/`
|
|
- **New Parameter**: `show_deleted: bool = False`
|
|
- **Behavior**: Filters out deleted products by default
|
|
- **Usage**: `GET /products/?show_deleted=true` to include deleted products
|
|
|
|
#### `PUT /products/{id}` & `DELETE /products/{id}`
|
|
- **Enhanced**: Now properly handles soft delete with historical tracking
|
|
- **Validation**: Prevents operations on already-deleted products
|
|
|
|
### New Endpoints
|
|
|
|
#### `GET /products/available-for-shopping/{shopping_date}`
|
|
- **Purpose**: Get products that were available (not deleted) on a specific shopping date
|
|
- **Logic**: Returns products where:
|
|
- `deleted = false` (never deleted), OR
|
|
- `deleted = true` AND `valid_from > shopping_date` (deleted after shopping date)
|
|
- **Usage**: Used by shopping event modals to filter product lists
|
|
|
|
## Frontend Implementation
|
|
|
|
### ProductList Component
|
|
```typescript
|
|
// New state for toggle
|
|
const [showDeleted, setShowDeleted] = useState(false);
|
|
|
|
// Updated API call
|
|
const response = await productApi.getAll(showDeleted);
|
|
|
|
// Visual styling for deleted products
|
|
className={`hover:bg-gray-50 ${product.deleted ? 'bg-red-50 opacity-75' : ''}`}
|
|
```
|
|
|
|
### AddShoppingEventModal Component
|
|
```typescript
|
|
// Dynamic product fetching based on shopping date
|
|
const response = formData.date
|
|
? await productApi.getAvailableForShopping(formData.date)
|
|
: await productApi.getAll(false);
|
|
|
|
// Refetch products when date changes
|
|
useEffect(() => {
|
|
if (isOpen && formData.date) {
|
|
fetchProducts();
|
|
}
|
|
}, [formData.date, isOpen]);
|
|
```
|
|
|
|
## Deletion Process Flow
|
|
|
|
### 1. User Initiates Delete
|
|
- User clicks "Delete" button on a product
|
|
- Confirmation modal appears
|
|
|
|
### 2. Backend Processing
|
|
```python
|
|
# Simple application code - trigger handles the complexity
|
|
product.deleted = True
|
|
product.valid_from = date.today() # Manual versioning date
|
|
product.updated_at = func.now()
|
|
|
|
# Trigger automatically:
|
|
# 1. Detects the change (deleted field + valid_from change)
|
|
# 2. Creates history record with old data (deleted=False, valid_to=today)
|
|
# 3. Ensures new record has valid_to='9999-12-31'
|
|
```
|
|
|
|
**Trigger Logic (Automatic)**:
|
|
- Detects manual versioning because `valid_from` changed
|
|
- Uses the new `valid_from` as the cutoff date for history
|
|
- Creates history record: `{...old_data, valid_to: new_valid_from, operation: 'U'}`
|
|
- No additional application logic needed
|
|
|
|
### 3. Frontend Updates
|
|
- Product list refreshes
|
|
- Deleted product appears with visual indicators (if "Show deleted" is enabled)
|
|
- Product becomes unavailable for new shopping events
|
|
|
|
## Historical Data Integrity
|
|
|
|
### Shopping Events
|
|
- **Guarantee**: Shopping events always show products exactly as they existed when purchased
|
|
- **Implementation**: Uses `/products/{id}/at/{date}` endpoint to fetch historical product state
|
|
- **Benefit**: Even if a product is deleted later, historical shopping events remain accurate
|
|
|
|
### Audit Trail
|
|
- **Complete History**: All product versions are preserved in `products_history`
|
|
- **Deletion Tracking**: History records show when and why products were deleted
|
|
- **Temporal Queries**: Can reconstruct product state at any point in time
|
|
|
|
## Usage Examples
|
|
|
|
### 1. View All Products (Default)
|
|
```bash
|
|
GET /products/
|
|
# Returns only non-deleted products
|
|
```
|
|
|
|
### 2. View All Products Including Deleted
|
|
```bash
|
|
GET /products/?show_deleted=true
|
|
# Returns all products with deleted status
|
|
```
|
|
|
|
### 3. Get Products Available for Shopping on Specific Date
|
|
```bash
|
|
GET /products/available-for-shopping/2024-01-15
|
|
# Returns products that were not deleted on 2024-01-15
|
|
```
|
|
|
|
### 4. View Historical Shopping Event
|
|
```bash
|
|
GET /shopping-events/123/products-as-purchased
|
|
# Returns products exactly as they were when purchased, regardless of current deletion status
|
|
```
|
|
|
|
## Benefits
|
|
|
|
1. **Data Preservation**: No data is ever lost
|
|
2. **Audit Compliance**: Complete audit trail of all changes
|
|
3. **Historical Accuracy**: Shopping events remain accurate over time
|
|
4. **User Experience**: Clean interface with optional deleted product visibility
|
|
5. **Flexibility**: Easy to "undelete" products if needed (future enhancement)
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Undelete Functionality**: Add ability to restore deleted products
|
|
2. **Bulk Operations**: Delete/restore multiple products at once
|
|
3. **Deletion Reasons**: Add optional reason field for deletions
|
|
4. **Advanced Filtering**: Filter by deletion date, reason, etc.
|
|
5. **Reporting**: Generate reports on deleted products and their impact
|
|
|
|
## Migration Instructions
|
|
|
|
1. **Run Migration**: Execute `temporal_migration.sql` to add `deleted` column
|
|
2. **Deploy Backend**: Update backend with new API endpoints and logic
|
|
3. **Deploy Frontend**: Update frontend with new UI components and API calls
|
|
4. **Test**: Verify soft delete functionality and historical data integrity
|
|
|
|
## Testing Scenarios
|
|
|
|
1. **Basic Deletion**: Delete a product and verify it's hidden from default view
|
|
2. **Show Deleted Toggle**: Enable "Show deleted" and verify deleted products appear with proper styling
|
|
3. **Shopping Event Filtering**: Create shopping event and verify deleted products don't appear in product list
|
|
4. **Historical Accuracy**: Delete a product that was in a past shopping event, verify the shopping event still shows correct historical data
|
|
5. **Date-based Filtering**: Test `/products/available-for-shopping/{date}` with various dates before/after product deletions
|
|
|
|
## Database Initialization
|
|
|
|
### ⚠️ Important: Trigger Creation
|
|
|
|
The temporal triggers are essential for the soft delete functionality. They must be created in **all environments**:
|
|
|
|
### **Method 1: Automatic (Recommended)**
|
|
The triggers are now automatically created when using SQLAlchemy's `create_all()`:
|
|
|
|
```python
|
|
# This now creates both tables AND triggers
|
|
models.Base.metadata.create_all(bind=engine)
|
|
```
|
|
|
|
**How it works**:
|
|
- SQLAlchemy event listener detects when `ProductHistory` table is created
|
|
- Automatically executes trigger creation SQL
|
|
- Works for fresh dev, test, and production databases
|
|
|
|
### **Method 2: Manual Database Script**
|
|
For explicit control, use the initialization script:
|
|
|
|
```bash
|
|
# Run this for fresh database setup
|
|
python backend/database_init.py
|
|
```
|
|
|
|
**Features**:
|
|
- Creates all tables
|
|
- Creates all triggers
|
|
- Checks for existing triggers (safe to run multiple times)
|
|
- Provides detailed feedback
|
|
|
|
### **Method 3: Migration File**
|
|
For existing databases, run the migration:
|
|
|
|
```sql
|
|
-- Execute temporal_migration.sql
|
|
\i temporal_migration.sql
|
|
```
|
|
|
|
## Environment Setup Guide
|
|
|
|
### **Development (Fresh DB)**
|
|
```bash
|
|
# Option A: Automatic (when starting app)
|
|
python backend/main.py
|
|
# ✅ Tables + triggers created automatically
|
|
|
|
# Option B: Explicit setup
|
|
python backend/database_init.py
|
|
python backend/main.py
|
|
```
|
|
|
|
### **Production (Fresh DB)**
|
|
```bash
|
|
# Recommended: Explicit initialization
|
|
python backend/database_init.py
|
|
# Then start the application
|
|
```
|
|
|
|
### **Existing Database (Migration)**
|
|
```bash
|
|
# Apply migration to add soft delete functionality
|
|
docker-compose exec db psql -U postgres -d groceries -f /tmp/temporal_migration.sql
|
|
``` |