Lucee's deserializeJSON chokes on control characters in description
fields that JSON.stringify escapes but Lucee can't parse.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Default-checked items nested inside modifier groups were not being found
because the function only looked one level deep. Now it recurses through
all child items to find defaults at any depth.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add IsDeleted=0 filter to existing item search query
- Add support for OrderLineItemID parameter for direct item targeting
- Fixes duplicate items being created on quantity increment
- Fixes item deletion not working due to finding wrong/deleted records
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For inverted display: when user deselects a default-checked item, keep it
in cart with Quantity=0 instead of deleting. Cart displays "NO X" for these.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
For inverted groups, when user deselects a default item, keep it in cart
with Quantity=0 instead of deleting. This allows cart to display "NO X".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Child items need to know if their parent group is inverted for proper
display logic (showing "NO X" instead of listing selected items).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Required for inverted modifier display (showing "NO X" for removed defaults
instead of listing all selected items).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>