# Store Address Normalization Implementation Plan ## Executive Summary **Problem**: The database schema has a properly normalized structure for stores and addresses (`stores` → `store_locations` → `addresses`), but the application code does NOT fully utilize this structure. Currently: - TypeScript types exist (`Store`, `Address`, `StoreLocation`) ✅ - AddressRepository exists for basic CRUD ✅ - E2E tests now create data using normalized structure ✅ - **BUT**: No functionality to CREATE/MANAGE stores with addresses in the application - **BUT**: No API endpoints to handle store location data - **BUT**: No frontend forms to input address data when creating stores - **BUT**: Queries don't join stores with their addresses for display **Impact**: Users see stores without addresses, making features like "deals near me", "store finder", and location-based features impossible. --- ## Current State Analysis ### ✅ What EXISTS and WORKS: 1. **Database Schema**: Properly normalized (stores, addresses, store_locations) 2. **TypeScript Types** ([src/types.ts](src/types.ts)): - `Store` type (lines 2-9) - `Address` type (lines 712-724) - `StoreLocation` type (lines 704-710) 3. **AddressRepository** ([src/services/db/address.db.ts](src/services/db/address.db.ts)): - `getAddressById()` - `upsertAddress()` 4. **Test Helpers** ([src/tests/utils/storeHelpers.ts](src/tests/utils/storeHelpers.ts)): - `createStoreWithLocation()` - for test data creation - `cleanupStoreLocations()` - for test cleanup ### ❌ What's MISSING: 1. **No StoreRepository/StoreService** - No database layer for stores 2. **No StoreLocationRepository** - No functions to link stores to addresses 3. **No API endpoints** for: - POST /api/stores - Create store with address - GET /api/stores/:id - Get store with address(es) - PUT /api/stores/:id - Update store details - POST /api/stores/:id/locations - Add location to store - etc. 4. **No frontend components** for: - Store creation form (with address fields) - Store editing form - Store location display 5. **Queries don't join** - Existing queries (admin.db.ts, flyer.db.ts) join stores but don't include address data 6. **No store management UI** - Admin dashboard doesn't have store management --- ## Detailed Investigation Findings ### Places Where Stores Are Used (Need Address Data): 1. **Flyer Display** ([src/features/flyer/FlyerDisplay.tsx](src/features/flyer/FlyerDisplay.tsx)) - Shows store name, but could show "Store @ 123 Main St, Toronto" 2. **Deal Listings** (deals.db.ts queries) - `deal_store_name` field exists (line 691 in types.ts) - Should show "Milk $4.99 @ Store #123 (456 Oak Ave)" 3. **Receipt Processing** (receipt.db.ts) - Receipts link to store_id - Could show "Receipt from Store @ 789 Budget St" 4. **Admin Dashboard** (admin.db.ts) - Joins stores for flyer review (line 720) - Should show store address in admin views 5. **Flyer Item Analysis** (admin.db.ts line 334) - Joins stores for unmatched items - Address context would help with store identification ### Test Files That Need Updates: **Unit Tests** (may need store+address mocks): - src/services/db/flyer.db.test.ts - src/services/db/receipt.db.test.ts - src/services/aiService.server.test.ts - src/features/flyer/\*.test.tsx (various component tests) **Integration Tests** (create stores): - src/tests/integration/admin.integration.test.ts (line 164: INSERT INTO stores) - src/tests/integration/flyer.integration.test.ts (line 28: INSERT INTO stores) - src/tests/integration/price.integration.test.ts (line 48: INSERT INTO stores) - src/tests/integration/public.routes.integration.test.ts (line 66: INSERT INTO stores) - src/tests/integration/receipt.integration.test.ts (line 252: INSERT INTO stores) **E2E Tests** (already fixed): - ✅ src/tests/e2e/deals-journey.e2e.test.ts - ✅ src/tests/e2e/budget-journey.e2e.test.ts - ✅ src/tests/e2e/receipt-journey.e2e.test.ts --- ## Implementation Plan (NO CODE YET - APPROVAL REQUIRED) ### Phase 1: Database Layer (Foundation) #### 1.1 Create StoreRepository ([src/services/db/store.db.ts](src/services/db/store.db.ts)) Functions needed: - `getStoreById(storeId)` - Returns Store (basic) - `getStoreWithLocations(storeId)` - Returns Store + Address[] - `getAllStores()` - Returns Store[] (basic) - `getAllStoresWithLocations()` - Returns Array - `createStore(name, logoUrl?, createdBy?)` - Returns storeId - `updateStore(storeId, updates)` - Updates name/logo - `deleteStore(storeId)` - Cascades to store_locations - `searchStoresByName(query)` - For autocomplete **Test file**: [src/services/db/store.db.test.ts](src/services/db/store.db.test.ts) #### 1.2 Create StoreLocationRepository ([src/services/db/storeLocation.db.ts](src/services/db/storeLocation.db.ts)) Functions needed: - `createStoreLocation(storeId, addressId)` - Links store to address - `getLocationsByStoreId(storeId)` - Returns StoreLocation[] with Address data - `deleteStoreLocation(storeLocationId)` - Unlinks - `updateStoreLocation(storeLocationId, newAddressId)` - Changes address **Test file**: [src/services/db/storeLocation.db.test.ts](src/services/db/storeLocation.db.test.ts) #### 1.3 Enhance AddressRepository ([src/services/db/address.db.ts](src/services/db/address.db.ts)) Add functions: - `searchAddressesByText(query)` - For autocomplete - `getAddressesByStoreId(storeId)` - Convenience method **Files to modify**: - [src/services/db/address.db.ts](src/services/db/address.db.ts) - [src/services/db/address.db.test.ts](src/services/db/address.db.test.ts) --- ### Phase 2: TypeScript Types & Validation #### 2.1 Add Extended Types ([src/types.ts](src/types.ts)) ```typescript // Store with address data for API responses export interface StoreWithLocation { ...Store; locations: Array<{ store_location_id: number; address: Address; }>; } // For API requests when creating store export interface CreateStoreRequest { name: string; logo_url?: string; address?: { address_line_1: string; city: string; province_state: string; postal_code: string; country?: string; }; } ``` #### 2.2 Add Zod Validation Schemas Create [src/schemas/store.schema.ts](src/schemas/store.schema.ts): - `createStoreSchema` - Validates POST /stores body - `updateStoreSchema` - Validates PUT /stores/:id body - `addLocationSchema` - Validates POST /stores/:id/locations body --- ### Phase 3: API Routes #### 3.1 Create Store Routes ([src/routes/store.routes.ts](src/routes/store.routes.ts)) Endpoints: - `GET /api/stores` - List all stores (with pagination) - Query params: `?includeLocations=true`, `?search=name` - `GET /api/stores/:id` - Get single store with locations - `POST /api/stores` - Create store (optionally with address) - `PUT /api/stores/:id` - Update store name/logo - `DELETE /api/stores/:id` - Delete store (admin only) - `POST /api/stores/:id/locations` - Add location to store - `DELETE /api/stores/:id/locations/:locationId` - Remove location **Test file**: [src/routes/store.routes.test.ts](src/routes/store.routes.test.ts) **Permissions**: - Create/Update/Delete: Admin only - Read: Public (for store listings in flyers/deals) #### 3.2 Update Existing Routes to Include Address Data **Files to modify**: - [src/routes/flyer.routes.ts](src/routes/flyer.routes.ts) - GET /flyers should include store address - [src/routes/deals.routes.ts](src/routes/deals.routes.ts) - GET /deals should include store address - [src/routes/receipt.routes.ts](src/routes/receipt.routes.ts) - GET /receipts/:id should include store address --- ### Phase 4: Update Database Queries #### 4.1 Modify Existing Queries to JOIN Addresses **Files to modify**: - [src/services/db/admin.db.ts](src/services/db/admin.db.ts) - Line 334: JOIN store_locations and addresses for unmatched items - Line 720: JOIN store_locations and addresses for flyers needing review - [src/services/db/flyer.db.ts](src/services/db/flyer.db.ts) - Any query that returns flyers with store data - [src/services/db/deals.db.ts](src/services/db/deals.db.ts) - Add address fields to deal queries **Pattern to use**: ```sql SELECT s.*, json_agg( json_build_object( 'store_location_id', sl.store_location_id, 'address', row_to_json(a.*) ) ) FILTER (WHERE sl.store_location_id IS NOT NULL) as locations FROM stores s LEFT JOIN store_locations sl ON s.store_id = sl.store_id LEFT JOIN addresses a ON sl.address_id = a.address_id GROUP BY s.store_id ``` --- ### Phase 5: Frontend Components #### 5.1 Admin Store Management Create [src/pages/admin/components/AdminStoreManager.tsx](src/pages/admin/components/AdminStoreManager.tsx): - Table listing all stores with locations - Create store button → opens modal/form - Edit store button → opens modal with store+address data - Delete store button (with confirmation) #### 5.2 Store Form Component Create [src/features/store/StoreForm.tsx](src/features/store/StoreForm.tsx): - Store name input - Logo URL input - Address section: - Address line 1 (required) - City (required) - Province/State (required) - Postal code (required) - Country (default: Canada) - Reusable for create & edit #### 5.3 Store Display Components Create [src/features/store/StoreCard.tsx](src/features/store/StoreCard.tsx): - Shows store name + logo - Shows primary address (if exists) - "View all locations" link (if multiple) Update existing components to use StoreCard: - Flyer listings - Deal listings - Receipt displays #### 5.4 Location Selector Component Create [src/features/store/LocationSelector.tsx](src/features/store/LocationSelector.tsx): - Dropdown or map view - Filter stores by proximity (future: use lat/long) - Used in "Find deals near me" feature --- ### Phase 6: Update Integration Tests All integration tests that create stores need to use `createStoreWithLocation()`: **Files to update** (5 files): 1. [src/tests/integration/admin.integration.test.ts](src/tests/integration/admin.integration.test.ts) (line 164) 2. [src/tests/integration/flyer.integration.test.ts](src/tests/integration/flyer.integration.test.ts) (line 28) 3. [src/tests/integration/price.integration.test.ts](src/tests/integration/price.integration.test.ts) (line 48) 4. [src/tests/integration/public.routes.integration.test.ts](src/tests/integration/public.routes.integration.test.ts) (line 66) 5. [src/tests/integration/receipt.integration.test.ts](src/tests/integration/receipt.integration.test.ts) (line 252) **Change pattern**: ```typescript // OLD: const storeResult = await pool.query('INSERT INTO stores (name) VALUES ($1) RETURNING store_id', [ 'Test Store', ]); // NEW: import { createStoreWithLocation } from '../utils/storeHelpers'; const store = await createStoreWithLocation(pool, { name: 'Test Store', address: '123 Test St', city: 'Test City', province: 'ON', postalCode: 'M5V 1A1', }); const storeId = store.storeId; ``` --- ### Phase 7: Update Unit Tests & Mocks #### 7.1 Update Mock Factories [src/tests/utils/mockFactories.ts](src/tests/utils/mockFactories.ts) - Add: - `createMockStore(overrides?): Store` - `createMockAddress(overrides?): Address` - `createMockStoreLocation(overrides?): StoreLocation` - `createMockStoreWithLocation(overrides?): StoreWithLocation` #### 7.2 Update Component Tests Files that display stores need updated mocks: - [src/features/flyer/FlyerDisplay.test.tsx](src/features/flyer/FlyerDisplay.test.tsx) - [src/features/flyer/FlyerList.test.tsx](src/features/flyer/FlyerList.test.tsx) - Any other components that show store data --- ### Phase 8: Schema Migration (IF NEEDED) **Check**: Do we need to migrate existing data? - If production has stores without addresses, we need to handle this - Options: 1. Make addresses optional (store can exist without location) 2. Create "Unknown Location" placeholder addresses 3. Manual data entry for existing stores **Migration file**: [sql/migrations/XXX_add_store_locations_data.sql](sql/migrations/XXX_add_store_locations_data.sql) (if needed) --- ### Phase 9: Documentation & Cache Invalidation #### 9.1 Update API Documentation - Add store endpoints to API docs - Document request/response formats - Add examples #### 9.2 Cache Invalidation [src/services/cacheService.server.ts](src/services/cacheService.server.ts): - Add `invalidateStores()` method - Add `invalidateStoreLocations(storeId)` method - Call after create/update/delete operations --- ## Files Summary ### New Files to Create (12 files): 1. `src/services/db/store.db.ts` - Store repository 2. `src/services/db/store.db.test.ts` - Store repository tests 3. `src/services/db/storeLocation.db.ts` - StoreLocation repository 4. `src/services/db/storeLocation.db.test.ts` - StoreLocation tests 5. `src/schemas/store.schema.ts` - Validation schemas 6. `src/routes/store.routes.ts` - API endpoints 7. `src/routes/store.routes.test.ts` - Route tests 8. `src/pages/admin/components/AdminStoreManager.tsx` - Admin UI 9. `src/features/store/StoreForm.tsx` - Store creation/edit form 10. `src/features/store/StoreCard.tsx` - Display component 11. `src/features/store/LocationSelector.tsx` - Location picker 12. `STORE_ADDRESS_IMPLEMENTATION_PLAN.md` - This document ### Files to Modify (20+ files): **Database Layer (3)**: - `src/services/db/address.db.ts` - Add search functions - `src/services/db/admin.db.ts` - Update JOINs - `src/services/db/flyer.db.ts` - Update JOINs - `src/services/db/deals.db.ts` - Update queries - `src/services/db/receipt.db.ts` - Update queries **API Routes (3)**: - `src/routes/flyer.routes.ts` - Include address in responses - `src/routes/deals.routes.ts` - Include address in responses - `src/routes/receipt.routes.ts` - Include address in responses **Types (1)**: - `src/types.ts` - Add StoreWithLocation and CreateStoreRequest types **Tests (10+)**: - `src/tests/integration/admin.integration.test.ts` - `src/tests/integration/flyer.integration.test.ts` - `src/tests/integration/price.integration.test.ts` - `src/tests/integration/public.routes.integration.test.ts` - `src/tests/integration/receipt.integration.test.ts` - `src/tests/utils/mockFactories.ts` - `src/features/flyer/FlyerDisplay.test.tsx` - `src/features/flyer/FlyerList.test.tsx` - Component tests for new store UI **Frontend (2+)**: - `src/pages/admin/Dashboard.tsx` - Add store management link - Any components displaying store data **Services (1)**: - `src/services/cacheService.server.ts` - Add store cache methods --- ## Estimated Complexity **Low Complexity** (Well-defined, straightforward): - Phase 1: Database repositories (patterns exist) - Phase 2: Type definitions (simple) - Phase 6: Update integration tests (mechanical) **Medium Complexity** (Requires design decisions): - Phase 3: API routes (standard REST) - Phase 4: Update queries (SQL JOINs) - Phase 7: Update mocks (depends on types) - Phase 9: Cache invalidation (pattern exists) **High Complexity** (Requires UX design, edge cases): - Phase 5: Frontend components (UI/UX decisions) - Phase 8: Data migration (if needed) - Multi-location handling (one store, many addresses) --- ## Dependencies & Risks **Critical Dependencies**: 1. Address data quality - garbage in, garbage out 2. Google Maps API integration (future) - for geocoding/validation 3. Multi-location handling - some stores have 100+ locations **Risks**: 1. **Breaking changes**: Existing queries might break if address data is required 2. **Performance**: Joining 3 tables (stores+store_locations+addresses) could be slow 3. **Data migration**: Existing production stores have no addresses 4. **Scope creep**: "Find stores near me" leads to mapping features **Mitigation**: - Make addresses OPTIONAL initially - Add database indexes on foreign keys - Use caching aggressively - Implement in phases (can stop after Phase 3 and assess) --- ## Questions for Approval 1. **Scope**: Implement all 9 phases, or start with Phase 1-3 (backend only)? 2. **Addresses required**: Should stores REQUIRE an address, or is it optional? 3. **Multi-location**: How to handle store chains with many locations? - Option A: One "primary" location - Option B: All locations equal - Option C: User selects location when viewing deals 4. **Existing data**: How to handle production stores without addresses? 5. **Priority**: Is this blocking other features, or can it wait? 6. **Frontend design**: Do we have mockups for store management UI? --- ## Approval Checklist Before starting implementation, confirm: - [ ] Plan reviewed and approved by project lead - [ ] Scope defined (which phases to implement) - [ ] Multi-location strategy decided - [ ] Data migration plan approved (if needed) - [ ] Frontend design approved (if doing Phase 5) - [ ] Testing strategy approved - [ ] Estimated timeline acceptable --- ## Next Steps After Approval 1. Create feature branch: `feature/store-address-integration` 2. Start with Phase 1.1 (StoreRepository) 3. Write tests first (TDD approach) 4. Implement phase by phase 5. Request code review after each phase 6. Merge only after ALL tests pass