diff --git a/docs/api-reference.md b/docs/api-reference.md index ec7ed36..442a9b3 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -15,7 +15,6 @@ the catalog. | Method | Path | Notes | |---|---|---| | `GET` | `/restaurants/{id}` | Restaurant profile | -| `GET` | `/restaurants/by-slug/{slug}` | Restaurant profile by URL slug — used by webapps that route on `/r/:slug` and need to resolve to an `id` before any other lookup. 404 when no match | | `GET` | `/restaurants/{id}/menu` | `{restaurant, tree, items}` — the canonical [[menu-tree|menu tree]] (hydrated children + items per node) plus a flat enriched items list with modifier groups + availability windows pre-joined | | `GET` | `/menu_items/{id}` | Single item | | `GET` | `/menu_nodes/{id}` | Single node row | diff --git a/docs/cms.md b/docs/cms.md index 59bef8c..119be3f 100644 --- a/docs/cms.md +++ b/docs/cms.md @@ -73,26 +73,6 @@ orange, `>15min` red) and offers one-tap state transitions. Today the monitor + KDS poll every 5–8 s. SSE / Nostr push is on the roadmap. -### Dark-mode color discipline - -Quasar's pale `bg-{color}-1` utility classes (e.g. `bg-orange-1`, -`bg-red-1`, `bg-amber-1`) pair fine with the default light theme -but render **white-on-cream** under LNbits' dark theme — the -q-card otherwise inherits the body's light text color. The KDS -cards pin a dark text class alongside every pale background so -the card stays legible regardless of theme: - -```js -if (order.status === 'ready') return 'bg-amber-1 text-grey-9' -if (ageSec > 900) return 'bg-red-1 text-grey-9' -if (ageSec > 300) return 'bg-orange-1 text-grey-9' -return '' // theme-default branch keeps q-card's own text color -``` - -Any future surface that ages / escalates with `bg-{color}-1` must -do the same. Never assume a pale background "just works" on dark -theme. - ## Settings `settings.html` saves restaurant fields via diff --git a/docs/order-flow.md b/docs/order-flow.md index fd29bc7..ffa2888 100644 --- a/docs/order-flow.md +++ b/docs/order-flow.md @@ -37,22 +37,14 @@ States and their meaning: 2. Re-prices every line item against the live menu (modifier ids are matched server-side; the customer's claimed `price_delta` values are ignored). -3. Converts each item's `price` from its declared `currency` to - msat. For sat-denominated items (`currency` ∈ `{sat, sats, - satoshi}`) this is a flat `× 1000`. For fiat (`USD`, `GTQ`, …) - it calls `lnbits.utils.exchange_rates.fiat_amount_as_satoshis` - to look up the live rate, then `× 1000`. The conversion lives - in `services._price_to_msat` so the rate lookup is the same path - the quote endpoint uses — a customer's preview and the recorded - `order.total_msat` cannot drift apart between request and place. -4. Sums `subtotal_msat`, applies `tax_rate`, adds `tip_msat` → +3. Sums `subtotal_msat`, applies `tax_rate`, adds `tip_msat` → `total_msat`. -5. For Lightning / internal: calls +4. For Lightning / internal: calls `lnbits.core.services.create_invoice` with `extra={"tag": "restaurant", "restaurant_id": ...}`. -6. Persists the order with `id = payment_hash` so the listener can +5. Persists the order with `id = payment_hash` so the listener can look it up cheaply, plus one `order_items` row per line. -7. For cash: `payment_method = "cash"` skips invoice creation and +6. For cash: `payment_method = "cash"` skips invoice creation and marks the order `accepted` directly. Returns `(Order, OrderInvoice | None)`. The webapp pays the bolt11. diff --git a/docs/webapp-integration.md b/docs/webapp-integration.md index e37c0c8..4c0a925 100644 --- a/docs/webapp-integration.md +++ b/docs/webapp-integration.md @@ -9,17 +9,7 @@ of restaurants, especially the multi-restaurant cart pattern. A webapp can either talk to one restaurant directly or aggregate many. There's no central directory inside this extension — grouping ("festival", "collective space", "food court") is **emergent** via -NIP-51 list events curated by whoever runs the venue. - -For the **single-venue** case, a webapp that routes on a URL slug -(`/r/big-jays-bustaurant`) resolves the slug → restaurant via the -public `GET /restaurants/by-slug/{slug}` endpoint -([[api-reference]]) and proceeds with that one `id` for menu reads -and order placement. Slug is just a URL nicety — internally -everything continues to key on the restaurant `id`. - -For the **aggregator** case (multiple restaurants in one cart), the -webapp consumes a curated NIP-51 list event: +NIP-51 list events curated by whoever runs the venue: ``` { diff --git a/services.py b/services.py index 793e86e..f187e2a 100644 --- a/services.py +++ b/services.py @@ -25,7 +25,6 @@ from loguru import logger from lnbits.core.services import create_invoice from lnbits.helpers import urlsafe_short_hash -from lnbits.utils.exchange_rates import fiat_amount_as_satoshis from .crud import ( create_order, @@ -56,33 +55,9 @@ from .models import ( # --------------------------------------------------------------------- # -_SAT_ALIASES = {"sat", "sats", "satoshi", "satoshis", "msat", "msats"} - - -def _is_sat_currency(currency: str | None) -> bool: - return (currency or "sat").strip().lower() in _SAT_ALIASES - - -async def _price_to_msat(amount: float, currency: str | None) -> int: - """ - Convert a menu-item-currency amount to integer **msat**. - - - For native sat-denominated prices (currency in - `_SAT_ALIASES`) this is a flat ×1000. - - For any other ISO-ish currency code (GTQ, USD, EUR, BRL, …) - we round-trip through LNbits's `fiat_amount_as_satoshis` - which queries the same exchange-rate pool the rest of LNbits - uses, then multiply by 1000. - - Side-effect-free helper; safe to call multiple times per - request (rates are LNbits-cached internally). - """ - if amount == 0: - return 0 - if _is_sat_currency(currency): - return int(round(amount * 1000)) - sats = await fiat_amount_as_satoshis(amount, (currency or "").upper()) - return int(round(sats * 1000)) +def _to_msat(amount: float) -> int: + """Convert a sat amount (possibly fractional) to integer msat.""" + return int(round(amount * 1000)) async def _price_line_item(line: CreateOrderItem) -> tuple[OrderItemRow, int]: @@ -106,9 +81,7 @@ async def _price_line_item(line: CreateOrderItem) -> tuple[OrderItemRow, int]: if item.stock is not None and item.stock < line.quantity: raise ValueError(f"Menu item {item.name!r} is out of stock") - # Resolve & price modifiers against canonical DB rows. Modifier - # `price_delta` is denominated in the same currency as the parent - # item (we don't carry a per-modifier currency). + # Resolve & price modifiers against canonical DB rows. resolved: list[SelectedModifier] = [] delta_msat_each = 0 requested_ids = {m.modifier_id for m in line.selected_modifiers if m.modifier_id} @@ -128,13 +101,9 @@ async def _price_line_item(line: CreateOrderItem) -> tuple[OrderItemRow, int]: price_delta=mod.price_delta, ) ) - delta_msat_each += await _price_to_msat( - mod.price_delta, item.currency - ) + delta_msat_each += _to_msat(mod.price_delta) - unit_price_msat = ( - await _price_to_msat(item.price, item.currency) - ) + delta_msat_each + unit_price_msat = _to_msat(item.price) + delta_msat_each line_total_msat = unit_price_msat * line.quantity row = OrderItemRow( @@ -365,8 +334,8 @@ async def quote_balance_required(items: list[CreateOrderItem]) -> int: item = await get_menu_item(line.menu_item_id) if not item: continue - unit = await _price_to_msat(item.price, item.currency) + unit = _to_msat(item.price) for sm in line.selected_modifiers: - unit += await _price_to_msat(sm.price_delta or 0, item.currency) + unit += _to_msat(sm.price_delta or 0) total += unit * line.quantity return total diff --git a/static/js/kds.js b/static/js/kds.js index ab4f0cc..bc0980d 100644 --- a/static/js/kds.js +++ b/static/js/kds.js @@ -25,16 +25,10 @@ window.app = Vue.createApp({ }, cardClass(order) { // Visually escalate as orders age. >5min = highlight; >15min = alarm. - // - // Pair every pale `bg-{color}-1` with an explicit dark text color - // — otherwise on LNbits dark mode the q-card inherits light text - // and renders white-on-cream, which is unreadable. The non- - // highlighted branch (default theme) returns '' so the q-card - // keeps its theme-aware defaults. const ageSec = (Date.now() - new Date(order.time).getTime()) / 1000 - if (order.status === 'ready') return 'bg-amber-1 text-grey-9' - if (ageSec > 900) return 'bg-red-1 text-grey-9' - if (ageSec > 300) return 'bg-orange-1 text-grey-9' + if (order.status === 'ready') return 'bg-amber-1' + if (ageSec > 900) return 'bg-red-1' + if (ageSec > 300) return 'bg-orange-1' return '' }, async fetchActive() { diff --git a/views_api.py b/views_api.py index ad26520..74ca8e3 100644 --- a/views_api.py +++ b/views_api.py @@ -59,7 +59,6 @@ from .crud import ( get_print_job, get_print_jobs, get_restaurant, - get_restaurant_by_slug, get_restaurants, get_settings, move_menu_node, @@ -249,20 +248,6 @@ async def api_list_restaurants( return await get_restaurants(wallet_ids) -@restaurant_api_router.get("/api/v1/restaurants/by-slug/{slug}") -async def api_get_restaurant_by_slug(slug: str) -> Restaurant: - """Public — used by the customer webapp to resolve a URL slug - (e.g. /r/big-jays-bustaurant) to a restaurant. Mirrors - api_get_restaurant; declared *before* the bare-id route so the - static prefix wins the path match in FastAPI's router.""" - restaurant = await get_restaurant_by_slug(slug) - if not restaurant: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Restaurant not found." - ) - return restaurant - - @restaurant_api_router.get("/api/v1/restaurants/{restaurant_id}") async def api_get_restaurant(restaurant_id: str) -> Restaurant: """Public — used by the webapp to fetch profile metadata."""