# QR Code Storage Location System Design ## Overview Implement a hierarchical storage location system with QR code generation and scanning capabilities to enable quick component location entry and filtering. ## Database Schema ### 1. Storage Locations Table ```sql CREATE TABLE storage_locations ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, qr_code VARCHAR(100) UNIQUE NOT NULL, parent_id INTEGER REFERENCES storage_locations(id), level INTEGER NOT NULL DEFAULT 0, path TEXT NOT NULL, -- Materialized path: "shelf1/drawer2/box3" is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_storage_locations_parent_id ON storage_locations(parent_id); CREATE INDEX idx_storage_locations_qr_code ON storage_locations(qr_code); CREATE INDEX idx_storage_locations_path ON storage_locations USING gin(path gin_trgm_ops); CREATE UNIQUE INDEX idx_storage_locations_name_parent ON storage_locations(name, parent_id); ``` ### 2. Modified Components Table ```sql -- Migration to add storage_location_id to components ALTER TABLE components ADD COLUMN storage_location_id INTEGER REFERENCES storage_locations(id), ADD COLUMN legacy_position VARCHAR(255); -- Keep old position data for migration -- Move existing position data to legacy_position UPDATE components SET legacy_position = position; ``` ## QR Code Format Design ### Hierarchical QR Code Strategy To avoid confusion with multiple QR codes in the same image, use a hierarchical encoding strategy: ``` Format: SL:{level}:{unique_id}:{parent_path_hash} Examples: - Shelf: "SL:1:ABC123:ROOT" - Drawer: "SL:2:DEF456:ABC123" - Box: "SL:3:GHI789:DEF456" ``` ### QR Code Components: - **SL**: Storage Location prefix - **Level**: Hierarchy level (1=shelf, 2=drawer, 3=box, etc.) - **Unique ID**: Short alphanumeric code (6-8 chars) - **Parent Hash**: Reference to parent location ## Multi-QR Code Detection Strategy ### 1. Spatial Filtering ``` When multiple QR codes detected: 1. Calculate distance between codes 2. If distance < threshold: - Prefer higher hierarchy level (lower number) - Present disambiguation UI 3. If distance > threshold: - Allow user to tap/select desired code ``` ### 2. Context-Aware Selection ``` Selection Priority: 1. Exact level match (if user scanning for specific level) 2. Deepest level in hierarchy (most specific location) 3. Recently used locations (user preference learning) 4. Manual disambiguation prompt ``` ### 3. Visual Feedback ``` Camera Overlay: - Draw bounding boxes around each detected QR code - Color-code by hierarchy level - Show location path preview on hover/tap - Highlight "best match" with different color ``` ## Implementation Components ### 1. Elixir Modules #### Storage Location Schema ```elixir defmodule ComponentsElixir.Inventory.StorageLocation do use Ecto.Schema import Ecto.Changeset schema "storage_locations" do field :name, :string field :description, :string field :qr_code, :string field :level, :integer, default: 0 field :path, :string field :is_active, :boolean, default: true belongs_to :parent, __MODULE__ has_many :children, __MODULE__, foreign_key: :parent_id has_many :components, Component timestamps() end end ``` #### QR Code Generation ```elixir defmodule ComponentsElixir.QRCode do def generate_storage_qr(location) do qr_data = "SL:#{location.level}:#{location.qr_code}:#{parent_hash(location)}" # Use :qr_code library to generate QR image :qr_code.encode(qr_data) |> :qr_code.png() end def parse_storage_qr(qr_string) do case String.split(qr_string, ":") do ["SL", level, code, parent] -> {:ok, %{level: level, code: code, parent: parent}} _ -> {:error, :invalid_format} end end end ``` ### 2. Phoenix LiveView Components #### QR Scanner Component ```elixir defmodule ComponentsElixirWeb.QRScannerLive do use ComponentsElixirWeb, :live_view def mount(_params, _session, socket) do socket = socket |> assign(:scanning, false) |> assign(:detected_codes, []) |> assign(:selected_location, nil) |> allow_upload(:qr_scan, accept: ~w(.jpg .jpeg .png), max_entries: 1, auto_upload: true) {:ok, socket} end def handle_event("start_scan", _, socket) do {:noreply, assign(socket, :scanning, true)} end def handle_event("qr_detected", %{"codes" => codes}, socket) do parsed_codes = Enum.map(codes, &parse_and_resolve_location/1) socket = socket |> assign(:detected_codes, parsed_codes) |> maybe_auto_select_location(parsed_codes) {:noreply, socket} end defp maybe_auto_select_location(socket, [single_code]) do assign(socket, :selected_location, single_code) end defp maybe_auto_select_location(socket, multiple_codes) do # Show disambiguation UI assign(socket, :selected_location, nil) end end ``` ### 3. JavaScript QR Detection #### Camera Integration ```javascript // assets/js/qr_scanner.js import jsQR from "jsqr"; export const QRScanner = { mounted() { this.video = this.el.querySelector('video'); this.canvas = this.el.querySelector('canvas'); this.context = this.canvas.getContext('2d'); this.startCamera(); this.scanLoop(); }, async startCamera() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', // Use back camera width: { ideal: 1280 }, height: { ideal: 720 } } }); this.video.srcObject = stream; } catch (err) { console.error('Camera access denied:', err); } }, scanLoop() { if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) { this.canvas.width = this.video.videoWidth; this.canvas.height = this.video.videoHeight; this.context.drawImage(this.video, 0, 0); const imageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); // Detect multiple QR codes const codes = this.detectMultipleQRCodes(imageData); if (codes.length > 0) { this.pushEvent("qr_detected", { codes: codes }); } } requestAnimationFrame(() => this.scanLoop()); }, detectMultipleQRCodes(imageData) { // Implementation for detecting multiple QR codes // This is a simplified version - you'd need a more robust library const detected = []; // Scan in grid pattern to find multiple codes const gridSize = 4; const width = imageData.width / gridSize; const height = imageData.height / gridSize; for (let x = 0; x < gridSize; x++) { for (let y = 0; y < gridSize; y++) { const subImageData = this.getSubImageData( imageData, x * width, y * height, width, height ); const code = jsQR(subImageData.data, subImageData.width, subImageData.height); if (code && this.isStorageLocationQR(code.data)) { detected.push({ data: code.data, location: { x: x * width, y: y * height }, corners: code.location }); } } } return this.filterDuplicates(detected); }, isStorageLocationQR(data) { return data.startsWith('SL:'); } }; ``` ## User Experience Flow ### 1. Adding Components with QR Scan ``` 1. User clicks "Add Component" 2. Position field shows camera icon 3. Click camera → QR scanner opens 4. Scan storage location QR code 5. If multiple codes detected: - Show overlay with detected locations - User taps to select specific location 6. Location path auto-filled: "Shelf A → Drawer 2 → Box 5" 7. Component saved with storage_location_id ``` ### 2. Filtering by Storage Location ``` 1. Component list shows location filter dropdown 2. Filter options show hierarchical tree: ├── Shelf A │ ├── Drawer 1 │ │ ├── Box 1 │ │ └── Box 2 │ └── Drawer 2 └── Shelf B 3. Select any level to filter components 4. Breadcrumb shows: "Shelf A → Drawer 2" (23 components) ``` ### 3. Location Management ``` 1. New "Storage Locations" section in admin 2. Add/edit locations with auto QR generation 3. Print QR labels with location hierarchy 4. Bulk QR code generation for initial setup ``` ## Handling Multiple QR Codes in Same Image ### Strategy 1: Spatial Separation - Calculate euclidean distance between QR code centers - If distance < 100px → show disambiguation - If distance > 100px → allow selection by tap ### Strategy 2: Hierarchy Preference - Always prefer deepest level (most specific) - If same level → show all options - Color-code by hierarchy level in UI ### Strategy 3: Machine Learning (Future) - Learn user selection patterns - Predict most likely intended QR code - Still allow manual override ## Migration Strategy ### Phase 1: Add Storage Locations 1. Create migration for storage_locations table 2. Add storage_location_id to components 3. Create admin interface for location management ### Phase 2: QR Code Generation 1. Add QR code generation to location creation 2. Implement QR code printing/export functionality 3. Generate codes for existing locations ### Phase 3: QR Code Scanning 1. Add camera permissions and JavaScript QR scanner 2. Implement single QR code detection first 3. Add multi-QR detection and disambiguation ### Phase 4: Advanced Features 1. Location-based filtering and search 2. Bulk operations by location 3. Location analytics and optimization ## Technical Dependencies ### Elixir Dependencies ```elixir # mix.exs {:qr_code, "~> 3.1"}, # QR code generation {:image, "~> 0.37"}, # Image processing {:ex_image_info, "~> 0.2.4"} # Image metadata ``` ### JavaScript Dependencies ??? ## Database Indexes for Performance ```sql -- Fast location lookups CREATE INDEX idx_components_storage_location_id ON components(storage_location_id); -- Hierarchical queries CREATE INDEX idx_storage_locations_path_gin ON storage_locations USING gin(path gin_trgm_ops); -- QR code uniqueness and fast lookup CREATE UNIQUE INDEX idx_storage_locations_qr_code ON storage_locations(qr_code); ``` This design provides a robust foundation for QR code-based storage management while handling the complexity of multiple codes in the same image through spatial analysis and user interaction patterns.