A modern web application for managing restaurant profit and loss calculations with Square integration.
EasyShiftHQ is a comprehensive solution that helps restaurants track and analyze their profit and loss data. It integrates with Square for real-time data synchronization and provides AI-powered insights for better business decisions.
- Architecture & Technical Guidelines - Detailed technical documentation, design patterns, and best practices
- Integration Patterns - Third-party integrations (banks, POS, AI), security, and performance
- Unit Conversions - Recipe-to-inventory conversion system (critical for inventory accuracy)
- Braintrust Telemetry - AI observability and telemetry setup
- Grafana Faro - Frontend observability, error tracking, and performance monitoring
- GitHub Copilot Instructions - Guidelines for AI coding assistants
- Caching Strategy
- Design System
- Accessibility Standards
- Unit Conversion Constants
- Bank Connections
- POS Integrations
- AI Functionality
- AI Telemetry Setup
- Frontend Observability
- Edge Functions
- Security Best Practices
Data Freshness First: This is a real-time system where stale data causes operational issues. We use React Query with short cache times (30-60s) and NO manual caching (localStorage, etc.).
Design System: All styling uses semantic tokens from index.css - no direct colors like bg-white or text-black. Components follow consistent gradient patterns and animations.
Accessibility: WCAG 2.1 AA compliance with keyboard navigation, ARIA labels, focus management, and screen reader support throughout.
Security: Row Level Security (RLS) enforced at the database level. Client-side checks are for UX only, never for authorization.
- Framework: React 18+ with Vite
- Language: TypeScript
- UI Components: shadcn/ui + Tailwind CSS
- State Management: React Hooks
- Routing: React Router
- Platform: Supabase
- Functions: Edge Functions (Deno runtime)
- Database: PostgreSQL (via Supabase)
- Authentication: Supabase Auth with SSO capabilities
- File Storage: Supabase Storage
- Square & Clover - POS system integrations via adapter pattern
- Stripe Financial Connections - Secure bank account linking
- OpenRouter - AI functionality with multi-model fallback
- Resend - Transactional email service
- SCIM - Enterprise user provisioning
src/
├── components/ # React components
├── hooks/ # Custom React hooks
├── integrations/ # Third-party service integrations
├── lib/ # Utility functions
├── pages/ # Route components
└── services/ # Business logic services
supabase/
├── functions/ # Edge Functions
└── migrations/ # Database migrations
- Node.js 18+ or Bun
- Supabase account
- Square developer account
- OpenAI API key (for AI features)
- Resend API key (for emails)
# Supabase Configuration
SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
SUPABASE_PUBLISHABLE_KEY=your_anon_key
# Square Integration
SQUARE_APPLICATION_ID=your_square_app_id
SQUARE_APPLICATION_SECRET=your_square_app_secret
SQUARE_WEBHOOK_SIGNATURE_KEY=your_webhook_key
ENCRYPTION_KEY=your_encryption_key
# Development Only (Optional)
SQUARE_SANDBOX_APPLICATION_ID=your_sandbox_app_id
SQUARE_SANDBOX_APPLICATION_SECRET=your_sandbox_secret
SQUARE_PERSONAL_ACCESS_TOKEN=your_personal_token
# Additional Services
RESEND_API_KEY=your_resend_key
OPENAI_API_KEY=your_openai_key-
Clone the repository:
git clone https://github.com/toyiyo/easyshifthq.git cd easyshifthq -
Install dependencies:
bun install # or npm install -
Set up environment variables:
- Copy
.env.exampleto.env - Fill in the required environment variables
- Copy
-
Start the development server:
bun run dev # or npm run dev
- Coolify account
- Git repository connected to Coolify
- All environment variables ready
-
Build Settings
- Build Pack: Static
- Base Directory:
/ - Pre Deployment Command:
bun install && bun run build - Output Directory:
dist(automatic)
-
Environment Variables
- Add all required environment variables in Coolify's UI
-
NGINX Configuration Add the following custom NGINX configuration:
server { location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } # Handle 404 errors error_page 404 /index.html; location = /404.html { root /usr/share/nginx/html; internal; } # Handle server errors (50x) error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; internal; } # Enable gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; }
- Connect your repository in Coolify
- Configure the deployment settings as above
- Set up environment variables
- Deploy the application
- Multi-POS Support - Square and Clover integrations with unified data model
- Bank Integration - Secure bank connections via Stripe Financial Connections
- Automated P&L Calculations - Real-time profit and loss tracking
- AI-Powered Categorization - Automatic transaction categorization with multi-model fallback
- OCR & Receipt Processing - Extract data from receipts and product images
- Enterprise SSO Support - SAML-based single sign-on
- SCIM Provisioning - Automated user management for enterprise
- Real-time Sync - Webhooks + polling for up-to-date data
- Team Collaboration - Multi-user support with role-based permissions
- Multi-Restaurant Support - Manage multiple locations from one account
We use Supabase Branching with Vercel to provide isolated preview environments for every PR.
| Component | Production | PR Preview |
|---|---|---|
| Frontend | Vercel Production | Vercel Preview URL |
| Database | Supabase Production | Supabase Preview Branch |
| Env Vars | Production values | Auto-synced branch values |
- GitHub Integration: Connected via Supabase Dashboard → Project Settings → Integrations
- Supabase Directory: Set to
supabase(contains migrations, functions, tests) - Branching Mode: "Supabase changes only" - branches created when
supabase/files change - Branch Limit: 50 concurrent preview branches
- Supabase-Vercel Integration: Installed via Supabase marketplace
- Environment Variable Prefix:
VITE_(for Vite-based apps) - Auto-sync: Preview deployments receive branch-specific database credentials
The app uses environment variables with production fallbacks:
// src/integrations/supabase/client.ts
export const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL || PRODUCTION_URL;
export const SUPABASE_ANON_KEY = import.meta.env.VITE_SUPABASE_ANON_KEY || PRODUCTION_KEY;| Variable | Source | Fallback |
|---|---|---|
VITE_SUPABASE_URL |
Vercel/Supabase integration | Production URL |
VITE_SUPABASE_ANON_KEY |
Vercel/Supabase integration | Production anon key |
This ensures the app works on:
- ✅ Vercel (production + preview with Supabase branching)
- ✅ Netlify (uses production fallback)
- ✅ Lovable (uses production fallback)
- ✅ Local development (uses
.envor fallback)
- Create PR with changes to
supabase/migrations/orsupabase/functions/ - Supabase automatically creates a preview branch database
- Migrations run on the preview branch
- Vercel deploys frontend with preview branch credentials
- Test on isolated environment (no production data affected)
- Merge → Changes deploy to production
Our CI pipeline (.github/workflows/unit-tests.yml) runs:
- Unit Tests: TypeScript tests with Vitest
- Database Tests: pgTAP tests against local Supabase
- E2E Tests: Playwright browser tests
- SonarCloud: Code quality and security analysis
- CodeQL: Security vulnerability scanning
# Run all unit tests
npm run test
# Run with coverage
npm run test:coverage
# Watch mode
npm run test:watchEnd-to-end tests verify critical user flows in the browser.
# Run E2E tests (headless)
npm run test:e2e
# Run with UI (interactive)
npm run test:e2e:ui
# Run headed (see the browser)
npm run test:e2e:headedPrerequisites:
- Local Supabase running:
npm run db:start - Dev server will start automatically
The project includes comprehensive tests for all PostgreSQL database functions using pgTAP.
Running tests locally:
# Navigate to tests directory
cd supabase/tests
# Run all tests
./run_tests.shTest coverage:
- Sales and aggregation functions
- P&L calculation functions
- Inventory management functions
- Search and lookup functions
- Trigger functions
- Security and authentication functions
- Utility and maintenance functions
# Run unit, database, and E2E tests
npm run test:allSee supabase/tests/README.md for detailed testing documentation.
- Revenue & Liabilities:
unified_salestable (consolidates Square, Clover, manual entries) - Costs:
daily_pnltable (food cost, labor cost) - Chart of Accounts:
chart_of_accountstable (account categorization)
To avoid double-counting when sales are split:
- Exclude parent sales that have children (identified by checking if their ID appears as a
parent_sale_id) - Include all child splits (sales with a
parent_sale_id) - Include unsplit sales (no
parent_sale_id, no children)
// Logic: Find parent IDs that have children
const parentIdsWithChildren = new Set(
sales.filter(s => s.parent_sale_id !== null)
.map(s => s.parent_sale_id)
);
// Filter: Keep only sales that are NOT in the parent set
const validSales = sales.filter(s => !parentIdsWithChildren.has(s.id));Formula: Sum of all sales with item_type='sale' AND account_type='revenue'
Gross Revenue = Σ(total_price) where item_type='sale' AND account_type='revenue'Formula: Sum of absolute values for discount items
Discounts = Σ(ABS(total_price)) where item_type='discount'Formula: Sum of absolute values for refund items
Refunds = Σ(ABS(total_price)) where item_type='refund'Formula: Gross Revenue minus Discounts minus Refunds
Net Revenue = Gross Revenue - Discounts - RefundsFormula: Sum of liability sales with "sales" or "tax" in account subtype
Sales Tax = Σ(total_price) where item_type='sale'
AND account_type='liability'
AND (account_subtype LIKE '%sales%' OR account_subtype LIKE '%tax%')Formula: Sum of liability sales with "tip" in account subtype
Tips = Σ(total_price) where item_type='sale'
AND account_type='liability'
AND account_subtype LIKE '%tip%'Formula: Sum of liability sales not categorized as tax or tips
Other Liabilities = Σ(total_price) where item_type='sale'
AND account_type='liability'
AND account_subtype NOT LIKE '%sales%'
AND account_subtype NOT LIKE '%tax%'
AND account_subtype NOT LIKE '%tip%'Formula: Sum of all revenue and liabilities collected
Total Collected = Gross Revenue + Sales Tax + Tips + Other LiabilitiesFormula: Sum of food costs from daily P&L records
Food Cost = Σ(daily_pnl.food_cost) for all dates in monthFormula: Sum of labor costs from daily P&L records
Labor Cost = Σ(daily_pnl.labor_cost) for all dates in monthFormula: Net Revenue minus operating costs
Profit = Net Revenue - Food Cost - Labor CostFormula: Percentage change in net revenue compared to prior month
MoM Change % = ((Current Month Net Revenue - Prior Month Net Revenue) / ABS(Prior Month Net Revenue)) × 100Given sales data for October 2025:
- Sales items (revenue): $10,560.56 + $386.00 = $10,946.56
- Discounts: $0.00
- Refunds: $0.00
- Sales tax (liability): $1.44
- Other liabilities: $2.00
Calculations:
Gross Revenue = $10,946.56
Discounts = $0.00
Refunds = $0.00
Net Revenue = $10,946.56 - $0.00 - $0.00 = $10,946.56
Sales Tax = $1.44
Tips = $0.00
Other Liabilities = $2.00
Total Collected at POS = $10,946.56 + $1.44 + $2.00 = $10,950.00
Food Cost = $0.00
Labor Cost = $0.00
Profit = $10,946.56 - $0.00 - $0.00 = $10,946.56
- No Double-Counting: Split sales are handled by excluding parent records and including only child records
- Integer Math: All calculations use integer cents to avoid floating-point errors
- Liability Separation: Taxes, tips, and other liabilities are tracked separately from revenue
- Account-Based: All categorization relies on the Chart of Accounts (
account_typeandaccount_subtype) - Real-Time: Data syncs via webhooks + periodic polling from POS systems
Please see CONTRIBUTING.md for details on how to contribute to this project.
This project is licensed under the terms of the LICENSE file included in the repository.