submitCash.php had no task creation — cash orders were invisible to KDS.
submit.php also lacked it (tab orders never hit webhook.php).
Both now create "Prepare Order #X for Table" task at StatusID=1.
submit.php includes duplicate guard since webhook.php also creates tasks
for card payments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MySQL PDO returns DECIMAL/FLOAT columns as strings, causing json_encode
to emit them as JSON strings ("9.99") instead of numbers (9.99).
Android's Gson Map parsing fails the as? Number cast on strings,
defaulting to 0.0. Cast Price to (float) before building the response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Only Call Staff, Chat With Staff, and Pay With Cash should appear
on the customer service bell. New column distinguishes service bell
items from internal task types.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Got reverted during earlier merge conflict resolution. Without cd,
node can't find modules. Without stderr redirect, log lines corrupt
the JSON output causing json_decode to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- analyzeMenuUrl.php: Extract og:image and JSON-LD image during discovery, return as headerImageUrl
- downloadImages.php: Add User-Agent header, detect image format from content-type/magic bytes, update HeaderImageExtension in DB
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
order.online embeds displayPrice alongside images — now extracted and
returned so items missing prices from image-based menus get filled in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
downloadItemImage() saved files to disk but never updated the DB column,
so items appeared to have no images despite files existing on disk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Moved directory on both dev and biz servers
- Updated nginx configs on both servers
- Added appRoot() helper, uploadsRoot() uses it
- No more hardcoded /opt/payfrit-api paths in codebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Moved uploads from Lucee webroot to /opt/payfrit-api/uploads/
- Updated nginx on both dev and biz to alias /uploads/ to new path
- Replaced luceeWebroot() with uploadsRoot() helper
- Temp files now use /opt/payfrit-api/temp/
- No more /opt/lucee or /var/www/biz.payfrit.com references
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added luceeWebroot() helper to avoid repeating the path. The previous
fix incorrectly used /var/www/biz.payfrit.com for production, but both
dev and biz use the same Lucee webroot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upload endpoints were saving files to PHP's DOCUMENT_ROOT instead of
the Lucee webroot where the Android app loads them from. Also fix
verifyLoginOTP and verifyOTP to accept both UUID/OTP and uuid/otp keys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Detect contact/about/location/hours links on main page
- Fetch contact page and extract phone, address, hours
- Phone: regex for US phone formats + tel: links
- Address: US street address pattern (number + street type)
- Hours: day + time range patterns from plain text
- Overrides bad JSON-LD data with actual contact page info
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Prefer title tag for name over JSON-LD (sites often put address in LD name)
- Parse full address string into components (addressLine1, city, state, zip)
- Handle newlines in addresses (Squarespace puts newlines in JSON-LD)
- Convert 24h hours to 12h format
- Strip country suffix from addresses
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Claude prompt now asks for menuSchedule (days/times this menu is served)
- Separates menu schedule from overall business hours
- Returns menuSchedule in response for frontend to use
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Parse JSON-LD structured data (Restaurant, FoodEstablishment, etc.)
- Extract phone from tel: links, address from og: meta tags
- Return businessInfo in discovery response so sub-pages don't need it
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Discovery mode: quick Playwright crawl returns detected menu sub-pages
- Extract_page mode: processes single menu page through Claude individually
- More aggressive HTML stripping: removes SVG, nav, footer, form, attributes
- Increased truncation limit from 100KB to 200KB for generic fallback path
- Enables interactive wizard flow: discover → confirm → extract each page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude returns address as one string with country suffix.
Now strips "United States/USA", extracts ZIP, state, splits
address line and city server-side before sending to wizard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated Claude prompt to detect separate menus vs categories
- Added platformImageMap and subPagesVisited parsing from Playwright
- Bumped Playwright wait from 5s to 10s for sub-page crawling
- saveWizard.php creates separate Menus rows and assigns categories/items
to the correct menu based on each item's "menu" field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sendSMS() to helpers.php using Twilio REST API with cURL,
credentials loaded from config/twilio.json. Wire into sendOTP,
loginOTP, and sendLoginOTP endpoints, replacing TODO stubs.
SMS is auto-skipped on dev environments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete port of all 163 API endpoints from Lucee/CFML to PHP 8.3.
Shared helpers in api/helpers.php (DB, auth, request/response, security).
PDO prepared statements throughout. Same JSON response shapes as CFML.