When deselecting a modifier (IsSelected=false), Quantity was incorrectly
set to 1 instead of 0, preventing the removal logic from working.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Works app can now display inverted modifier groups with "NO" prefix
for removed defaults, matching KDS behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CFML was failing to compile analyzeMenuUrl.cfm because ' contains
a # character that Lucee interprets as variable expression start.
Escaped all 4 occurrences to &##39;.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When enabled on a modifier group, KDS and cart only show removed
defaults (e.g., "NO Mustard") instead of listing all selected items.
Useful for groups where all options are checked by default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Parse JSON-LD structured menu data from saved Uber Eats pages
(categories, items, prices, descriptions, business info)
- Show save-and-upload instructions when user pastes Uber Eats URL
- Always show header image upload step (was skipped for URL imports)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
KDS doesn't send auth tokens, so markStationDone needs to be a public
route like listForKDS and updateStatus already are.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getOrCreateCart: only require ServicePointID for dine-in (OrderTypeID=1)
- get.cfm + items.cfm: return OrderTypes from Businesses table
- saveOrderTypes.cfm: new endpoint to save business order type config
- KDS: add PICKUP/DELIVERY badges on order cards
- Portal: add Order Types toggle card in settings (Dine-In always on, Takeaway toggle)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WooCommerce fast path was returning empty business info. Now the
Playwright script extracts it from the page and the CFML passes it through.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- checkbox groups get MaxNumSelectionReq=0 (unlimited), radio/select get 1
- Pre-checked options (e.g. preselected condiments) saved with IsCheckedByDefault=1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The wizard checks item.imageUrl to decide whether to skip the image
upload step and download images from remote URLs instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The beacon app sends "Phone" (uppercase) but loginOTP.cfm checked for
"phone" (lowercase). With preserveCaseForStructKey=true this fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects WooCommerce sites from Playwright HTML (woocommerce, wc-add-to-cart,
tm-extra-product-options). Runs woo-modifiers.js which navigates all product
pages, extracts items with categories, and scrapes TMEPO/variation modifiers.
Falls through to Claude if extraction fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The revert from the other session disabled this. 123456 must work
alongside real codes on production for Apple reviewer testing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- items.cfm: return BrandColorLight in menu response
- saveWizard.cfm: save BrandColorLight during business creation
- setup-wizard.html: second color picker for light brand color
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New BrandColorLight column on Businesses table. Menu builder cards
(categories, subcategories, items) get a very subtle tint from the
light brand color. White when no color is set. Brand color picker
now has both dark and light fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Staff cash goes to worker payout ledger, admin/manager cash deletes
pending payout and reverses withholding. Add RoleID to myBusinesses
response. Various order and webhook improvements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Post-process Claude menu extraction to detect when >60% of categories
have exactly 1 item (a common misparse). Collapses pseudo-categories
into the nearest preceding real (0-item) category.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added explicit guidance that categories are broad section headings
(5-15 typical) and items are individual products (30-150 typical).
Prevents Claude from treating each menu item as its own category.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude API rejects images when the declared media type doesn't
match the actual content. Now detects JPEG/PNG/GIF/WebP from
base64 magic bytes instead of trusting file extensions or
Content-Type headers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cancel.cfm previously only blocked cancel if orders had StatusID >= 1
(submitted to kitchen). If submitOrder() failed after addOrderToTab()
succeeded, the order stayed at StatusID 0 but was linked to the tab
with ApprovalStatus='approved'. This allowed cancelling the tab free.
Now checks TabOrders directly — any approved orders with non-zero
subtotals block the cancel, regardless of kitchen submission status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect Grubhub URLs and fetch menu data directly via their
REST API instead of scraping HTML. Gets anonymous auth token,
then fetches full restaurant data including categories, items,
modifiers, prices, hours, lat/lng, tax rate, and item images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toast provides latitude/longitude in the location object. Extract
in analyzeMenuUrl.cfm and pass through to saveWizard.cfm, which
now includes lat/lng in the address INSERT. Skips the background
Nominatim geocode when coordinates are already available.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert cfthread from tag-based to cfscript syntax. Lucee 7
no longer allows </cfscript> mid-file to switch to tag mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parses upcomingSchedules from ROOT_QUERY.restaurantV2.schedule
and formats as "Mon 7:30am-6:30pm, Tue 7:30am-6:30pm" text string
that the setup wizard's parseHoursString() can consume.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add item.prices array support to first code path (was only checking
item.price scalar, but Toast now uses prices: [4.50] array)
- Extract individual address fields (addressLine1, city, state, zip)
from ROOT_QUERY restaurant data for saveWizard compatibility
- Update modifier extraction URL detection to match any toasttab.com
domain (not just order.toasttab.com)
- Update slug-based URL construction to use www.toasttab.com/local/order/
format instead of deprecated order.toasttab.com/online/ format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each station can mark their items done independently. When all
stations are done, the order auto-promotes to Ready (status 3)
and delivery/pickup tasks are created automatically.
- New markStationDone.cfm endpoint for per-station completion
- Extract task creation into shared _createOrderTasks.cfm include
- Add line item StatusID to listForKDS.cfm response
- KDS shows per-station "Mark Station Done" button when filtered
- Done items display with strikethrough and checkmark
- Manager view retains full manual control (no station selected)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If a user has a live-mode StripeCustomerId but the API is running in
test mode (or vice versa), the PI creation fails. Now validates the
customer with Stripe first and creates a new one if invalid.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MySQL rejects ORDER BY o.SubmittedOn when SELECT DISTINCT has
DATE_FORMAT(o.SubmittedOn) AS SubmittedOn. Use the alias instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New endpoints: save.cfm (combined add/update) and delete.cfm
(soft-delete with item unassignment). Registered in Application.cfm.
Portal station-assignment page now uses real API calls for add,
edit, and delete instead of client-side-only prompt(). Fixed key
naming mismatch (StationName/StationColor → Name/Color) so the
page works with real API data. Removed hardcoded demo fallbacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend returns all line items for every order (removes station
filter from sub-query). Frontend filters by station, showing only
relevant items by default. An expand toggle reveals other stations'
items dimmed at 35% opacity for full order context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents sub-cent precision (e.g. $0.883125) from accumulating in
BalanceApplied and payment records. All balance math now rounds to
nearest cent first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Balance from cash change now silently reduces the amount owed on the
next order. For cash: deducted immediately in submitCash, reduces cash
the worker needs to collect (or skips cash task entirely if fully
covered). For card: reduces the Stripe PaymentIntent amount, deducted
in webhook on successful payment. Receipt shows "Balance applied" line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
submitCash.cfm: Calculate platform fee from subtotal * PayfritFee,
store in Orders.PlatformFee and Payments.PaymentPayfritsCut on submission.
complete.cfm: Replace bogus 2.25% cash transaction fee with the real
platform fee (customer fee + business fee = 2 × PayfritFee × subtotal).
Credit full Payfrit revenue to User 0. Record business fee in
PaymentPayfritNetworkFees.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PaymentFromCreditCard column is NOT NULL with no default value,
causing INSERT to fail silently. Set to 0 for cash payments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CFML compare() does string comparison, causing distances like 1.9 to
sort after 12.3. Switched to numeric < > operators.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Payments table still uses old prefixed names (PaymentID as PK, no UUID
column, no PaymentTip column, required PaymentReceivedByUserID). Updated
INSERT to match actual production schema.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract geocoding functions into shared api/inc/geocode.cfm and call
geocodeAddressById() via cfthread after every address INSERT or UPDATE.
Uses OpenStreetMap Nominatim (free, no API key). Non-blocking — the
HTTP call runs in a background thread so responses aren't delayed.
Affected endpoints:
- setup/saveWizard.cfm (new business creation)
- businesses/update.cfm (business address update)
- portal/updateSettings.cfm (portal settings save)
- addresses/add.cfm (customer delivery address)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tag stripping via reReplace on 1.7M HTML was likely causing the silent
failure on biz server. Brace-counting doesn't need tag stripping since
HTML tags don't contain { or } and attribute quotes come in balanced
pairs. Also removed var keywords from page-level cfscript (may not be
supported in Lucee at template level) and added detailed error output
to the cfcatch for debugging.
Also auto-detect dev/prod environment from hostname instead of
hardcoded flag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The regex .*? (non-greedy) fails on 500K+ JSON due to Java regex
backtracking limits, causing truncated data (only 3 of 6 menus
extracted). Replace all 3 extraction points with cfscript
brace-counting that reliably handles any JSON size.
Also decode HTML entities (& -> &, < -> <, etc.) from
Chrome View Source saves.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Chrome's Ctrl+U (View Source) saves wraps content in <span> tags
which breaks the regex termination pattern ;\s*window\. because
HTML tags appear between ; and the next window. variable.
Strip HTML tags from a working copy before regex extraction when
View Source format is detected (presence of <span id="line" tags).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toast pages with multiple menus (e.g. "Food", "Beverages", "Merchandise")
now produce parent categories from the menu names with subcategories from
the groups within each menu, using the parentCategoryName field the wizard
already supports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When analyzing Toast menu pages, items with modifiers now have their
modifier groups extracted by clicking each item in a headless browser
and intercepting the GraphQL MenuItemDetails responses. Extracted
modifiers include group name, required/optional flag, min/max selections,
and option names with prices. Items sharing the same itemGroupGuid
inherit modifiers from successfully mapped siblings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Deliver to Table, Order Ready for Pickup, and Deliver to Address
to the default task types created by the setup wizard. These are
required by updateStatus.cfm to auto-create tasks at status 3.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extra </cfif> at line 243 had no matching opener. Was hidden by
Lucee's template cache until the Tomcat restart exposed it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>