Compare commits
3 commits
42746d7321
...
d61e48b3e6
| Author | SHA1 | Date | |
|---|---|---|---|
| d61e48b3e6 | |||
| 2294bcd0c0 | |||
| 13de28e2e1 |
3 changed files with 63 additions and 11 deletions
47
services.py
47
services.py
|
|
@ -25,6 +25,7 @@ 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,
|
||||
|
|
@ -55,9 +56,33 @@ from .models import (
|
|||
# --------------------------------------------------------------------- #
|
||||
|
||||
|
||||
def _to_msat(amount: float) -> int:
|
||||
"""Convert a sat amount (possibly fractional) to integer msat."""
|
||||
return int(round(amount * 1000))
|
||||
_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))
|
||||
|
||||
|
||||
async def _price_line_item(line: CreateOrderItem) -> tuple[OrderItemRow, int]:
|
||||
|
|
@ -81,7 +106,9 @@ 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.
|
||||
# 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).
|
||||
resolved: list[SelectedModifier] = []
|
||||
delta_msat_each = 0
|
||||
requested_ids = {m.modifier_id for m in line.selected_modifiers if m.modifier_id}
|
||||
|
|
@ -101,9 +128,13 @@ async def _price_line_item(line: CreateOrderItem) -> tuple[OrderItemRow, int]:
|
|||
price_delta=mod.price_delta,
|
||||
)
|
||||
)
|
||||
delta_msat_each += _to_msat(mod.price_delta)
|
||||
delta_msat_each += await _price_to_msat(
|
||||
mod.price_delta, item.currency
|
||||
)
|
||||
|
||||
unit_price_msat = _to_msat(item.price) + delta_msat_each
|
||||
unit_price_msat = (
|
||||
await _price_to_msat(item.price, item.currency)
|
||||
) + delta_msat_each
|
||||
line_total_msat = unit_price_msat * line.quantity
|
||||
|
||||
row = OrderItemRow(
|
||||
|
|
@ -334,8 +365,8 @@ async def quote_balance_required(items: list[CreateOrderItem]) -> int:
|
|||
item = await get_menu_item(line.menu_item_id)
|
||||
if not item:
|
||||
continue
|
||||
unit = _to_msat(item.price)
|
||||
unit = await _price_to_msat(item.price, item.currency)
|
||||
for sm in line.selected_modifiers:
|
||||
unit += _to_msat(sm.price_delta or 0)
|
||||
unit += await _price_to_msat(sm.price_delta or 0, item.currency)
|
||||
total += unit * line.quantity
|
||||
return total
|
||||
|
|
|
|||
|
|
@ -25,10 +25,16 @@ 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'
|
||||
if (ageSec > 900) return 'bg-red-1'
|
||||
if (ageSec > 300) return 'bg-orange-1'
|
||||
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 ''
|
||||
},
|
||||
async fetchActive() {
|
||||
|
|
|
|||
15
views_api.py
15
views_api.py
|
|
@ -59,6 +59,7 @@ from .crud import (
|
|||
get_print_job,
|
||||
get_print_jobs,
|
||||
get_restaurant,
|
||||
get_restaurant_by_slug,
|
||||
get_restaurants,
|
||||
get_settings,
|
||||
move_menu_node,
|
||||
|
|
@ -248,6 +249,20 @@ 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."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue