# Vorteq Quest Portal - Task Tracking > **Reference:** `docs/Vorteq_Quest_PRD_v1.0.docx` for full specifications > **Instructions:** `CLAUDE.md` for coding conventions and patterns > > Mark tasks: `[ ]` Not started | `[~]` In progress | `[x]` Complete | `[!]` Blocked --- ## Phase 1: Foundation (Est. 30-40 hrs) ### F-001: Project Scaffold - [x] Initialize Next.js 15 with App Router, TypeScript strict mode - [x] Install and configure Tailwind CSS - [x] Install and configure shadcn/ui (default theme) - [x] Configure ESLint + Prettier - [x] Configure `tsconfig.json` with path aliases (`@/`) - [x] Create base project structure per CLAUDE.md - [x] Create `.dockerignore` for build optimization - **Deps:** None | **Est:** 2 hrs | **Status:** ✅ Complete ### F-002: Docker & Infrastructure Alignment - [x] `Dockerfile` for Next.js app (Node.js 22, multi-stage build) - [x] `docker-compose.yml`: Traefik, postgres:16-alpine, redis:7-alpine, app-dev, app-test, app-prod - [x] Traefik reverse proxy with Cloudflare DNS challenge for auto-SSL - [x] `.env.example` with all required variables - [x] `init-databases.sh` for multi-database PostgreSQL setup - [x] `setup.sh` for server provisioning - [x] Health check endpoints in Next.js app (`/api/health`) - [x] Verify `next.config.js` has `output: 'standalone'` for Docker - **Deps:** F-001 | **Est:** 1 hr (remaining) | **Status:** ✅ Complete ### F-003: Prisma Schema - Auth Domain - [x] `auth_user` table (email, password hash, deactivated, verified, login counts, 2FA, SSO fields) - [x] `auth_domain` table - [x] `auth_user_type` table - [x] `auth_oauth_client` + `auth_oauth_access_token` + `auth_oauth_refresh_token` - [x] `auth_password_history` + `auth_password_reset` - [x] `auth_permission_group` + `auth_permission_rule` + junction table - [x] `auth_permission_rule_category` - [x] `auth_security_question` + `auth_security_answer` - [x] Run initial migration, verify schema - **Deps:** F-001 | **Est:** 4 hrs | **Status:** ✅ Complete ### F-004: Prisma Schema - Quest Domain - [x] `quest_company` (EpicorCustID, display name, invoicing flags) - [x] `quest_user` (FK to auth_user, IsSubUser, API token) - [x] `quest_user_company` junction table - [x] `quest_account_request` - [x] `quest_notification` + `quest_user_notification_alert_read` - [x] `quest_email_event` + `quest_user_email_event` junction - [x] `quest_email_log` - [x] `quest_plant` + `quest_inventory_type` + `quest_inventory_plant` - [x] `quest_processed_order_acknowledgement_email` - [x] Run migration, verify - **Deps:** F-003 | **Est:** 3 hrs | **Status:** ✅ Complete ### F-005: Prisma Schema - Remaining Domains - [x] `ship_request` + `ship_request_detail` - [x] `alloc_request` + `alloc_request_detail` + `alloc_plant` - [x] `inv_upload` + `inv_upload_entry` + `inv_upload_status` - [x] `finance_ap_check_processing_batch` + `finance_ap_check_processing_status` - [x] `paint_plant` + `paint_line` + `paint_line_type` + `paint_plant_line` + `paint_schedule` - [x] `doc_article` + `doc_category` + `doc_article_permission_group` - [x] `wave_process` + `wave_process_history` - [x] `lts_task` + `lts_task_type` + `lts_task_schedule` + related tables - [x] Run migration, verify all relationships - **Deps:** F-004 | **Est:** 4 hrs | **Status:** ✅ Complete ### F-006: Epicor MSSQL Connection Service - [x] `src/lib/epicor.ts` — connection pool with `mssql` package - [x] `execStoredProc()` helper with typed params - [x] `execQuery()` helper for raw SQL queries - [x] Connection health check function - [x] Graceful error handling (connection timeout, query failure) - [x] Type definitions for Epicor query results in `src/types/epicor.ts` - [~] Test connection against Epicor10Live with `portal` user (pending actual credentials) - **Deps:** F-001 | **Est:** 3 hrs | **Status:** ✅ Complete ### F-007: Better Auth Setup - [x] Install and configure Better Auth - [x] Credentials provider: validate against `auth_user` table (bcrypt) - [x] Session strategy with user ID, email, role, company context - [~] Session includes: userId, questUserId, activeCompanyId, isSubUser, permissionRules[] (requires middleware enhancement) - [x] Login page at `/login` - [x] Forgot password page at `/forgot-password` - [x] Account request page at `/account-request` - [ ] reCAPTCHA integration on public forms (deferred to later) - **Deps:** F-003 | **Est:** 4 hrs | **Status:** ✅ Complete ### F-008: Middleware & Guards - [x] `src/middleware.ts` — redirect unauthenticated users to /login - [x] Company context middleware: ensure active company is selected - [x] `src/lib/permissions.ts` — `requirePermission(rule)` helper - [x] Admin route protection (check user type) - [x] Sub-user detection helper (`isSubUser()`) - [x] Company selector: `/select-company` page for multi-company users - [x] Rate limiting on auth endpoints - [x] Session management utilities for Quest-specific data - [x] API route for setting active company - **Deps:** F-007 | **Est:** 4 hrs | **Status:** ✅ Complete ### F-009: Base Layout & Navigation - [x] Root layout with sidebar navigation - [x] Header: company name, user menu, notification bell - [x] Sidebar: Dashboard, Inventory, Orders, Shipments, Coil Activity, Invoices, Requests - [x] Admin section in sidebar (conditional on role) - [x] Company switcher dropdown (admin) or display (customer) - [~] Responsive: collapsible sidebar on mobile (basic implementation, needs enhancement) - [x] Breadcrumb component - **Deps:** F-007 | **Est:** 3 hrs | **Status:** ✅ Complete ### F-010: Data Migration Scripts - [x] `scripts/migrate-data.ts` — connects to both SQL Server and PostgreSQL - [x] Migrate `auth_user` + `auth_user_type` + `auth_domain` (preserve bcrypt hashes) - [x] Migrate `auth_permission_group` + `auth_permission_rule` + junction - [x] Migrate `quest_user` + `quest_company` + `quest_user_company` - [x] Migrate `quest_plant` + `quest_inventory_type/plant` - [x] Migrate `quest_email_event` - [ ] Migrate `ship_request` + `ship_request_detail` (historical data) (deferred - can be done before go-live) - [ ] Migrate `alloc_request` + `alloc_request_detail` (deferred - can be done before go-live) - [ ] Migrate supporting tables (notifications, docs, email events, wave, paint) (deferred - can be done before go-live) - [x] Validation: count comparison, spot-check key records - [x] Handle: IDENTITY → SERIAL, datetime2 → TIMESTAMPTZ, bit → BOOLEAN, nvarchar(MAX) → TEXT - **Deps:** F-005 | **Est:** 6 hrs | **Status:** ✅ Complete (core tables migrated) --- ## Phase 2: Core Features (Est. 80-110 hrs) ### C-001: Dashboard - [ ] Dashboard page at `/(portal)/dashboard/page.tsx` - [ ] Server component: fetch top 5 orders, top 5 shipments from Epicor - [ ] Summary cards: inventory overview, recent activity - [ ] Notification alert banner (unread alerts) - [ ] Quick-nav cards to major features - [ ] Loading skeletons for async data - **Deps:** F-009, F-006 | **Est:** 6 hrs ### C-002: Inventory Summary Views - [ ] `/(portal)/inventory/page.tsx` — category selector - [ ] `/(portal)/inventory/[category]/page.tsx` — summary data table - [ ] Service: `src/services/inventory.ts` - [ ] `getWorkInProgressSummary(custId, dbName, sub)` - [ ] `getFinishedGoodsSummary(custId, dbName, sub)` - [ ] `getProcessedOtherSummary(custId, dbName, sub)` - [ ] `getUnprocessedSummary(custId, dbName)` — block sub-users - [ ] `getUnprocessedRRSummary(custId, dbName)` — block sub-users - [ ] `getProcessedRRSummary(custId, dbName)` — block sub-users - [ ] V6 procedures: pass `sub` param (0/1 based on user) - [ ] Row count pre-fetch for detail drill-down (ALLV6 with count flag) - [ ] Data table with sorting, filtering, export to CSV - [ ] Click row → navigate to detail view - **Deps:** F-006, F-009 | **Est:** 12 hrs ### C-003: Inventory Detail Views - [ ] `/(portal)/inventory/[category]/details/page.tsx` - [ ] Query params: `part`, `plant`, `warehouse` (for specific) or none (for all) - [ ] Service functions for each detail stored procedure (V6 and legacy) - [ ] Paint code display (from Epicor Part_UD table) - [ ] Part description with paint code lookup (`getVorPartDescriptionFromPartNumberWithPaintCode`) - [ ] On-hand quantity for specific lines - [ ] Data table with full column set - [ ] Back navigation to summary - **Deps:** C-002 | **Est:** 8 hrs ### C-004: Order List - [ ] `/(portal)/orders/page.tsx` - [ ] Service: `getTop100Orders(custId)` using `portal_Orders.sql` - [ ] HDC/HDM exception handling (use `portal_OrdersHDC` view, map CustID) - [ ] Data table: Order Number, PO, Customer Part, Vorteq Part, Dates, Qty, Status - [ ] Click-through to order acknowledgement - **Deps:** F-006, F-009 | **Est:** 6 hrs ### C-005: Order Acknowledgement Detail + PDF - [ ] `/(portal)/orders/[id]/page.tsx` - [ ] Service: `getAcknowledgement(customer, poNumber?, orderNum?)` using `SalesOrder.sql` - [ ] Detail view with all order line information - [ ] PDF export button (generate PDF server-side) - **Deps:** C-004 | **Est:** 6 hrs ### C-006: Shipment List - [ ] `/(portal)/shipments/page.tsx` - [ ] Service: `getTop100Shipments(custId, dbName)` via `portal_GetShipmentsV1` - [ ] ShipToLoc formatting (replace `, ` with line breaks) - [ ] Data table with shipment details - [ ] Click-through to BOL detail - **Deps:** F-006, F-009 | **Est:** 5 hrs ### C-007: BOL Detail + PDF - [ ] `/(portal)/shipments/[bol]/page.tsx` - [ ] Service: `getBOL(bolNum, custId)` using `getBOL.sql` - [ ] Detail view with line items - [ ] PDF export - **Deps:** C-006 | **Est:** 4 hrs ### C-008: Coil Activity - Usage Report - [ ] `/(portal)/coil-activity/usage/page.tsx` - [ ] Date range picker (with validation) - [ ] Service: `getCoilActivityUsage(custId, startDate, endDate)` using `portal_CoilActivityUsage.sql` - [ ] Deduplication logic: only show OnHandQty for most recent DateUsed per LotNum - [ ] Zero weights → null display - [ ] Data table with full column set - **Deps:** F-006, F-009 | **Est:** 6 hrs ### C-009: Coil Activity - Receipts Report - [ ] `/(portal)/coil-activity/receipts/page.tsx` - [ ] Date range picker - [ ] Service: `getCoilActivityReceipts(custId, startDate, endDate)` - [ ] VGL customer exception (use special SQL) - [ ] Data table with receipt columns - **Deps:** F-006, F-009 | **Est:** 4 hrs ### C-010: Coil-by-Coil Report - [ ] `/(portal)/coil-activity/coil-by-coil/page.tsx` - [ ] Job number search input - [ ] Service: `getCoilByCoil(jobNum, customer)` using `CoilByCoil.sql` - [ ] Detail display - **Deps:** F-006 | **Est:** 3 hrs ### C-011: Job Status by Plant - [ ] `/(portal)/jobs/status/page.tsx` - [ ] Service: `getJobStatusByPlantByCustomer(custId, dbName)` using `JobStatusByPlantByCustomer.sql` - [ ] Note: requires DBNAME cross-database parameter - [ ] Data table grouped by plant - **Deps:** F-006 | **Est:** 3 hrs ### C-012: Job Traveler + PDF - [ ] `/(portal)/jobs/[jobNum]/traveler/page.tsx` - [ ] Service: `getJobTraveler(jobNum)` using `jobTraveler.sql` - [ ] Detailed routing/operations view - [ ] PDF export - **Deps:** F-006 | **Est:** 4 hrs ### C-013: Shipment Request Cart Workflow - [ ] `/(portal)/shipment-requests/page.tsx` — list existing requests - [ ] `/(portal)/shipment-requests/new/page.tsx` — new request flow - [ ] `/(portal)/shipment-requests/[id]/page.tsx` — edit/view request - [ ] Step 1: Select ship-to address (from `portal_CustomerShipToAddresses`) - [ ] Sub-user filter: only '%NB HANDY%' addresses - [ ] Step 2: Add inventory items to cart - [ ] Browse available inventory, select items - [ ] Set quantity per line item - [ ] Step 3: Enter details (Order#, PO#, Release#, Pickup Date, Instructions, Email Recipients) - [ ] Submit: create `ship_request` + `ship_request_detail` records, send emails - [ ] Cancel: set `IsCancelled=true`, record canceller, send cancellation emails - [ ] Cart persistence: save progress to DB, show `LastCartActivity` - [ ] Confirmation page after submit - **Deps:** F-005, C-003 | **Est:** 12 hrs ### C-014: Coil Allocation Request Cart Workflow - [ ] Mirror of C-013 but for allocation requests - [ ] Additional field: JobNum (from Epicor) - [ ] Uses `alloc_request` + `alloc_request_detail` tables - [ ] `getCoilAllocationsByJobNumber()` for allocation data - **Deps:** F-005, C-003 | **Est:** 8 hrs ### C-015: Invoice Viewing (Customer) - [ ] `/(portal)/invoices/page.tsx` - [ ] Only visible if company `CanAccessInvoices=true` - [ ] List invoices from `inv_upload_entry` filtered by company - [ ] PDF download endpoint `/api/invoices/:id/pdf` - [ ] File serving from storage directory - **Deps:** F-005, F-009 | **Est:** 4 hrs ### C-016: Notifications Display - [ ] Notification bell in header with unread count - [ ] Alert banner on dashboard for IsAlert=true notifications - [ ] Notification list view - [ ] Mark as read → create `quest_user_notification_alert_read` record - **Deps:** F-005, F-009 | **Est:** 3 hrs --- ## Phase 3: Admin Features (Est. 60-80 hrs) ### A-001: User Management - [ ] `/(portal)/admin/users/page.tsx` — user list with search/filter - [ ] `/(portal)/admin/users/[id]/page.tsx` — user detail/edit - [ ] `/(portal)/admin/users/new/page.tsx` — create user - [ ] CRUD operations via server actions - [ ] Assign/remove company associations - [ ] Assign permission groups - [ ] Activate/deactivate users - [ ] Set IsSubUser flag - [ ] View login history (success/failure counts) - **Deps:** F-007, F-005 | **Est:** 8 hrs ### A-002: Company Management - [ ] `/(portal)/admin/companies/page.tsx` — company list - [ ] `/(portal)/admin/companies/[id]/page.tsx` — company detail/edit - [ ] Edit: display name, InvoicingEmail, OrderAckEmail, CanAccessInvoices, ReceivesSOEmails - [ ] Activate/deactivate companies - [ ] View associated users - **Deps:** F-005 | **Est:** 5 hrs ### A-003: Account Request Management - [ ] `/(portal)/admin/account-requests/page.tsx` - [ ] List pending requests - [ ] Approve: create auth_user + quest_user + company association - [ ] Reject: mark RequestProcessed without creating user - [ ] Send approval/rejection email - **Deps:** F-005, A-001 | **Est:** 4 hrs ### A-004: Invoice Upload + Processing - [ ] `/(portal)/admin/invoices/upload/page.tsx` - [ ] Multi-page PDF upload - [ ] Parse PDF to extract individual invoices by invoice number - [ ] Match to CustID via `getCustomerIDsForInvoiceNumbers()` - [ ] Match to JobNum via `getJobNumberByInvoiceNumber()` - [ ] Create `inv_upload` + `inv_upload_entry` records - [ ] Status tracking (upload status, per-entry errors) - [ ] Send invoices to companies with InvoicingEmail - [ ] Handle: ignored entries, replaced entries, send errors - [ ] Upload history view - **Deps:** F-005, C-015 | **Est:** 12 hrs ### A-005: AP Check Processing - [ ] `/(portal)/admin/ap-check/page.tsx` - [ ] Excel upload (XLS/XLSX) - [ ] Extract Group ID from cell AB9 - [ ] Query Epicor CheckLines.sql by Group ID - [ ] Generate CSV with specific formatting - [ ] Country code handling: 'USA' vs 'FOR' (foreign) - [ ] Address field processing - [ ] Download generated CSV - [ ] Batch status tracking in `finance_ap_check_processing_batch` - **Deps:** F-005 | **Est:** 8 hrs ### A-006: Notification Management - [ ] `/(portal)/admin/notifications/page.tsx` — list + CRUD - [ ] Create: Title, Text, IsAlert, Date, Time - [ ] Edit/delete existing notifications - [ ] Preview notification as customer would see it - **Deps:** F-005 | **Est:** 3 hrs ### A-007: Documentation / Knowledge Base - [ ] `/(portal)/admin/documentation/page.tsx` — category + article management - [ ] Category CRUD (name, icon) - [ ] Article CRUD (content, category assignment) - [ ] Permission group assignment per article - [ ] Customer-facing documentation viewer - **Deps:** F-005 | **Est:** 5 hrs ### A-008: Shipping Reports - [ ] `/(portal)/admin/shipping-reports/page.tsx` - [ ] View generated reports (stored Excel files) - [ ] Manual trigger: generate previous month report - [ ] Manual trigger: generate YTD report - [ ] Service: `getShippingForMonth()`, `getShippingYTD()` via Epicor - [ ] Excel generation using a library (e.g., ExcelJS) - **Deps:** F-006 | **Est:** 4 hrs ### A-009: Wave/EDI Process Management - [ ] `/(portal)/admin/wave/page.tsx` - [ ] List `wave_process` entries with enable/disable toggles - [ ] View `wave_process_history` with run results - [ ] Manual trigger for processes - [ ] Minimal implementation — client migrating EDI to another product - **Deps:** F-005 | **Est:** 4 hrs ### A-010: Paint Schedule Management - [ ] `/(portal)/admin/paint-schedule/page.tsx` - [ ] CRUD for paint schedule entries - [ ] Plant/line selection from `paint_plant`, `paint_line`, `paint_plant_line` - [ ] Job lookup from Epicor (`getJobForPaintLine`, `getJob`) - [ ] Schedule view (calendar or table format) - [ ] Fields: JobID, PartDescription, Customer, Qty, EstimatedRunTime, StartTime, Notes, Complete, LineSpeed - **Deps:** F-005, F-006 | **Est:** 6 hrs ### A-011: Admin Password Reset Links - [ ] Generate password reset link for any user - [ ] Create `auth_password_reset` record with token - [ ] Optional: bypass minimum time restriction (`min_time_bypass`) - [ ] Copy link to clipboard for admin to send manually - **Deps:** F-007 | **Est:** 2 hrs --- ## Phase 4: Background Jobs & Polish (Est. 30-40 hrs) ### J-001: BullMQ Infrastructure - [ ] Install and configure BullMQ with Redis connection - [ ] `src/jobs/worker.ts` — worker process setup - [ ] Job registration and scheduling system - [ ] Dashboard: Bull Board or similar for job monitoring (admin only) - [ ] Retry logic with exponential backoff - [ ] Dead letter queue for failed jobs - **Deps:** F-002 | **Est:** 4 hrs ### J-002: Shipment Request Nag Job - [ ] Runs every 15 minutes - [ ] Find open (unsubmitted) shipment requests - [ ] Send reminder email to request owner - [ ] Track `LastNagDT` to avoid duplicate nags - [ ] Respects `withoutOverlapping` (skip if previous run still active) - **Deps:** J-001, C-013 | **Est:** 3 hrs ### J-003: Order Acknowledgement Email Job - [ ] Runs hourly - [ ] Query Epicor for new orders (`getOrdersForCustomerOnOrAfterDate`) - [ ] Cross-reference against `quest_processed_order_acknowledgement_email` - [ ] Send to companies with `ReceivesSOEmails=true` and `OrderAckEmail` set - [ ] Record in `quest_processed_order_acknowledgement_email` to prevent duplicates - **Deps:** J-001, C-004 | **Est:** 4 hrs ### J-004: Invoice Cleanup Job - [ ] Runs daily - [ ] `disc:clearoldinvoices` equivalent - [ ] Clean up old/expired invoice temporary data - **Deps:** J-001, A-004 | **Est:** 2 hrs ### J-005: Shipping Report Generation Jobs - [ ] Monthly report: 1st of month at 3am - [ ] Call `getShippingForMonth(lastMonth)` - [ ] Generate Excel file, save to storage - [ ] YTD report: twice daily - [ ] Call `getShippingYTD()` - [ ] Generate/overwrite Excel file - **Deps:** J-001, A-008 | **Est:** 4 hrs ### J-006: Wave EDI End-of-Month Job - [ ] Runs monthly on 1st - [ ] Execute Wave EDI end-of-month process - [ ] Log results to `wave_process_history` - [ ] Send completion email - [ ] Minimal implementation per client direction - **Deps:** J-001, A-009 | **Est:** 3 hrs ### J-007: Email Service (Microsoft Graph) - [ ] `src/lib/email.ts` — Microsoft Graph API client - [ ] OAuth2 client credentials flow (tenant + client ID + secret) - [ ] Send email function: `sendEmail(to, subject, htmlBody, from?)` - [ ] Email logging to `quest_email_log` - [ ] Template system for common emails (request confirmation, nag, acknowledgement) - [ ] Error handling + retry for transient Graph API failures - **Deps:** F-001 | **Est:** 5 hrs ### J-008: PDF Generation Service - [ ] `src/lib/pdf.ts` — PDF generation abstraction - [ ] Method 1: Puppeteer (render HTML → PDF, replaces wkhtmltopdf) - [ ] Method 2: @react-pdf/renderer for structured documents - [ ] Templates: Order Acknowledgement, BOL, Job Traveler, Invoice - [ ] Consistent header/footer styling - **Deps:** F-001 | **Est:** 4 hrs ### J-009: E2E Test Suite - [ ] Playwright configuration - [ ] Test: Login flow (success + failure + rate limiting) - [ ] Test: Dashboard loads with data - [ ] Test: Inventory browse → category → summary → detail drill-down - [ ] Test: Shipment request full workflow (create → add items → submit) - [ ] Test: Admin invoice upload - [ ] Test: Company switcher (admin) - [ ] Data validation tests comparing Epicor query results - **Deps:** All C-* tasks | **Est:** 8 hrs ### J-010: Production Deployment & Cutover - [ ] Verify all Traefik routes serving correctly with SSL - [ ] Run data migration scripts against production database - [ ] Verify all Epicor connections from expvtcasp01 - [ ] DNS cutover: update `quest.vorteq.wulf.cloud` or `portal.vorteqcoil.com` as needed - [ ] Smoke test all features post-cutover - [ ] Rollback plan documented - [ ] Security hardening: change Traefik dashboard password, secure .env, firewall rules - **Deps:** All tasks | **Est:** 4 hrs --- ## Infrastructure Tasks (Completed) ### I-001: Server Provisioning - [x] Linux server provisioned (expvtcasp01) - [x] Docker installed - [x] Docker Compose installed - [x] Netbird VPN configured (100.89.62.20) ### I-002: Forgejo Repository - [x] Repository created at `forgejo.wulfconsulting.cloud/lorentz/quest-vorteq` - [x] SSH key configured (port 222) - [x] Git identity configured on server - [x] Initial infrastructure files pushed ### I-003: CI/CD Pipeline - [x] Forgejo Actions enabled on repository - [x] Self-hosted runner registered (`expvtcasp01`) using Docker-in-Docker - [x] Build workflow: checkout → Docker build → push to Forgejo registry - [x] Deploy workflow: SSH into server → pull → restart containers - [x] Secrets configured (registry credentials, deploy SSH key) ### I-004: Infrastructure Stack - [x] Traefik v3 running with Cloudflare DNS challenge - [x] SSL certificate issued for `traefik.vorteq.wulf.cloud` - [x] PostgreSQL 16 running with dev/test/prod databases - [x] Redis 7 running with separate DB numbers per environment - [x] Cloudflare DNS A records pointing to Netbird IP ### I-005: Domain Configuration - [x] `quest.vorteq.wulf.cloud` → 100.89.62.20 (production) - [x] `dev.quest.vorteq.wulf.cloud` → 100.89.62.20 (development) - [x] `testing.vorteq.wulf.cloud` → 100.89.62.20 (testing) - [x] `traefik.vorteq.wulf.cloud` → 100.89.62.20 (dashboard) --- ## Summary | Phase | Tasks | Est. Hours (Low) | Est. Hours (High) | |-------|-------|-------------------|---------------------| | Phase 1: Foundation | F-001 to F-010 | 28 | 38 | | Phase 2: Core Features | C-001 to C-016 | 80 | 110 | | Phase 3: Admin Features | A-001 to A-011 | 60 | 80 | | Phase 4: Jobs & Polish | J-001 to J-010 | 30 | 40 | | Infrastructure | I-001 to I-005 | Done | Done | | **TOTAL** | **52 tasks** | **198** | **268** |