ScanLook - Inventory Management System
Barcode-based inventory counting system designed for food production facilities
Replace 5 days of manual year-end physical inventory with a weekend project. Track variances, location errors, phantom lots, and ghost inventory in real-time.
🚀 Quick Start
1. Install Dependencies
pip install flask werkzeug --break-system-packages
2. Initialize Database
cd /path/to/scanlook
python database/init_db.py
This creates the database with default users:
- Owner:
owner/owner123 - Admin:
admin/admin123 - Staff:
staff1/staff123 - Staff:
staff2/staff123
3. Run Application
python app.py
Access at: http://localhost:5000
📱 Accessing from Scanner Devices
For Zebra MC9300 scanners or tablets on the same network:
-
Find your server's IP address:
hostname -I -
On scanner device, open browser and navigate to:
http://YOUR_SERVER_IP:5000 -
Scanner trigger acts as Enter key (keyboard wedge mode)
🏗️ Modular Architecture
ScanLook uses a modular system designed to grow into a full WMS. Modules can be enabled/disabled per user.
Current Modules:
- Counts - Cycle Counts and Physical Inventory
Planned Modules:
- Shipping
- Receiving
- Transfers
- Production
Module Access
- Admins assign modules to users via User Management
- Users only see modules they have access to on the home screen
- Route guards prevent URL bypassing
👥 User Roles
Owner (Level 1)
- Super admin privileges
- Manage all users (admins and staff)
- Access to all modules
- View all sessions across branches
Admin (Level 2)
- Create count sessions
- Upload MASTER baseline (morning snapshot)
- Upload CURRENT baseline (refresh anytime)
- View real-time dashboard
- Export variance reports
- Manage staff users and module assignments
Staff/Counter (Level 3)
- Access assigned modules only
- Select active count session
- Scan locations and lots
- Enter weights
- Edit/delete their own scans
- View their progress
🔄 Core Workflow (Counts Module)
Phase 1: Session Setup (Admin)
- Log in as admin
- Navigate to Admin Console
- Create new count session:
- Session name: "January 17, 2026 - Daily Cycle Count"
- Type: Cycle Count OR Full Physical Inventory
- Upload MASTER baseline CSV from NetSuite:
- Format:
Item, Description, Lot Number, Location, Bin Number, On Hand
- Format:
- Session status: Active
- Counters can now begin
Phase 2: Counting (Staff)
- Log in as staff
- Select Counts module from home screen
- Select active session
- Start a new bin - scan or enter bin number
- Scan lot barcode
- System checks lot against MASTER baseline:
- ✅ MATCH: Expected here, location correct
- ⚠️ WRONG LOCATION: Lot exists but system says different location
- ❌ GHOST LOT: Not in system at all
- 🔵 DUPLICATE: Lot already scanned (same or different location)
- Enter weight
- Save → returns to scan input (autofocus)
- Repeat for all lots in bin
- Click "Finish Location" to finalize
Phase 3: Admin Monitoring
Real-Time Dashboard Shows:
- Total locations & completion percentage
- Active counters with current location
- Variance summary (matches, wrong locations, missing, ghost lots)
Optional CURRENT Baseline Refresh:
- Admin uploads new CSV from NetSuite
- System recalculates CURRENT status for all scans
- Shows what changed in system after count
- Proves count was correct, lot moved later
📊 CSV Import Format
Required columns:
Item,Description,Lot Number,Location,Bin Number,On Hand
100001,Beef MDM,20260108-132620-PO25146,Wisconsin G & H,R903B,2030
100001,Beef MDM,20260108-132700-PO25146,Wisconsin G & H,R903B,2030
100001,Beef MDM,20260106-150649-PO24949,Wisconsin G & H,R905C,2065
Column Descriptions:
- Item: SKU or item number
- Description: Product description
- Lot Number: Unique lot identifier
- Location: Warehouse location name
- Bin Number: Specific bin/rack location code
- On Hand: Current quantity in pounds
🎯 Key Features
Dual Baseline System
MASTER Baseline:
- Uploaded once at session start (e.g., 6:00 AM)
- Never changes during session
- What counters validate against
- Represents "what should have been there when we started"
CURRENT Baseline (Global):
- Admin can refresh unlimited times
- Shared across all sessions
- Only affects admin dashboard/reporting
- Counters never see it
- Shows "where is it in the system NOW"
- Identifies movement after count
Variance Detection
- Perfect Match: Right location, weight within tolerance
- Weight Variance: Right location, weight differs (calculates %)
- Wrong Location: Lot exists but scanned in different location
- Ghost Lot: Scanned but not in baseline at all
- Missing/Phantom: In baseline but not found during count
Duplicate Detection
- Status 00: First scan of lot (no duplicate)
- Status 01: Duplicate in same location by same user
- Status 03: Found in different location
- Status 04: Both same location duplicate AND different location
Count Types
Cycle Count:
- Shows expected lots for the bin
- Staff can see what should be there
- Good for regular inventory checks
Full Physical Inventory:
- Blind count (no expected list shown)
- Staff only sees what they've scanned
- Missing lots determined after finalization
Real-Time Dashboard
- Active sessions with progress stats
- Location completion tracking
- Variance counts updating live
- Dual view (MASTER vs CURRENT) after refresh
Audit Trail
- Every scan timestamped with user
- Soft deletes tracked
- Edits tracked with modified timestamp
- Session archival (archived sessions are read-only)
🗂️ Database Schema
Core Tables:
- Users: Authentication and role management
- Modules: Available system modules
- UserModules: Per-user module access
- CountSessions: Count session metadata
- BaselineInventory_Master: Session-specific baseline (immutable)
- BaselineInventory_Current: Global refreshable baseline
- LocationCounts: Bin-by-bin progress tracking
- ScanEntries: Every lot scan with status tracking
- MissingLots: Expected lots not found after finalization
Key Relationships:
- Users → UserModules → Modules (many-to-many)
- CountSessions → BaselineInventory_Master (one-to-many)
- CountSessions → LocationCounts → ScanEntries (hierarchical)
🔧 Technology Stack
- Backend: Python 3.13 + Flask
- Database: SQLite (auto-initializes on first run)
- Frontend: HTML5 + CSS3 + Vanilla JavaScript
- Templates: Jinja2
- Icons: Font Awesome 6.5
- Deployment: Docker (Linux)
📂 Project Structure
scanlook/
├── app.py # Main Flask application & routes
├── db.py # Database helper functions
├── utils.py # Decorators (login_required, role_required)
├── blueprints/
│ ├── counting.py # Counts module routes
│ ├── sessions.py # Session management routes
│ ├── users.py # User management routes
│ ├── data_imports.py # CSV upload routes
│ └── admin_locations.py # Admin location management
├── database/
│ ├── init_db.py # Database initialization & schema
│ └── scanlook.db # SQLite database (created at runtime)
├── static/
│ ├── css/
│ │ ├── style.css # Base/desktop styles
│ │ ├── mobile.css # Mobile overrides (360-767px)
│ │ └── scanner.css # MC9300 scanner overrides (<360px)
│ ├── js/
│ │ └── main.js # Client-side JavaScript
│ ├── manifest.json # PWA manifest
│ └── sw.js # Service worker
└── templates/
├── base.html # Base template with navbar
├── login.html # Login page
├── home.html # Module selection (landing page)
├── admin_dashboard.html
├── staff_dashboard.html
├── manage_users.html
├── create_session.html
├── session_detail.html
├── my_counts.html
└── count_location.html
🎨 Design Philosophy
Desktop-First, Multi-Device Optimized:
- High contrast dark theme for warehouse lighting
- Large touch targets (44px minimum)
- Autofocus on scan inputs
- Scanner trigger = Enter key
- Visual feedback with color-coded statuses
- Minimal typing required
CSS Architecture:
style.css- Base/desktop styles (768px+)mobile.css- Phone overrides (360-767px)scanner.css- MC9300 scanner overrides (<360px)
Color Coding:
- ✅ Green (
--color-success): Perfect match - ⚠️ Yellow (
--color-warning): Wrong location / Expected - ❌ Red (
--color-danger): Missing or ghost lot - 🔵 Blue (
--color-duplicate): Duplicate scan - 🟠 Orange (
--color-orange): Weight variance
🐳 Docker Deployment
# Build
docker build -t scanlook .
# Run (preserves database between updates)
docker run -d \
-p 5000:5000 \
-v /path/to/data:/app/database \
-e SCANLOOK_SECRET_KEY=your-secret-key \
scanlook
Important: Only /app/database is volume-mounted to preserve data between container updates.
🔐 Security Considerations
- Passwords hashed with Werkzeug
- Session management via Flask sessions
- SQL injection prevention (parameterized queries)
- Role-based access control
- Module-based access control with route guards
- Soft deletes preserve audit trail
🎯 Success Metrics
Current State (Year-End Physical):
- Duration: 5 days
- Data entry: Manual, error-prone
- Variance resolution: Weeks later
Target State (with ScanLook):
- Duration: 1 weekend (2 days max)
- Data entry: Zero (all scanned)
- Variance resolution: Real-time flagging
- Accuracy: 99%+ (no handwriting errors)
- Time savings: 60%+
💡 Future Enhancements
- Additional WMS modules (Shipping, Receiving, Transfers, Production)
- Multi-branch support
- Direct NetSuite API integration
- Photo capture for variance proof
- Barcode generation for location labels
- Native mobile apps
- Offline mode with sync
📝 Version History
- v0.12.0 - Modular architecture with user-based module access
- v0.11.x - CSS refactoring, multi-device support, duplicate detection
- v0.10.x - Global CURRENT baseline, session archival
- v0.9.x - Core counting workflow, dual baseline system
🔧 Support
This is a custom internal tool. Contact your system administrator for support.
Built with ❤️ for efficient inventory management
Inspired by BomLook from the FoxPro days 🚀