fix: add GET /cards/lookup endpoint for loyalty terminal customer search
Some checks failed
Some checks failed
The terminal JS uses GET with a free-text ?q= parameter, but only a POST endpoint existed with typed params (card_id, qr_code, card_number). - Add search_card_for_store service method (tries card number then email) - Add GET /cards/lookup route using the service method - Extract _build_card_lookup_response helper to DRY up POST and GET endpoints Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -311,6 +311,73 @@ def list_cards(
|
||||
return CardListResponse(cards=card_responses, total=total)
|
||||
|
||||
|
||||
def _build_card_lookup_response(card, db=None) -> CardLookupResponse:
|
||||
"""Build a CardLookupResponse from a card object."""
|
||||
from datetime import timedelta
|
||||
|
||||
program = card.program
|
||||
|
||||
can_stamp, _ = card.can_stamp(program.cooldown_minutes)
|
||||
cooldown_ends = None
|
||||
if not can_stamp and card.last_stamp_at:
|
||||
cooldown_ends = card.last_stamp_at + timedelta(minutes=program.cooldown_minutes)
|
||||
|
||||
stamps_today = card_service.get_stamps_today(db, card.id) if db else 0
|
||||
|
||||
available_rewards = []
|
||||
for reward in program.points_rewards or []:
|
||||
if reward.get("is_active", True) and card.points_balance >= reward.get(
|
||||
"points_required", 0
|
||||
):
|
||||
available_rewards.append(reward)
|
||||
|
||||
return CardLookupResponse(
|
||||
card_id=card.id,
|
||||
card_number=card.card_number,
|
||||
customer_id=card.customer_id,
|
||||
customer_name=card.customer.full_name if card.customer else None,
|
||||
customer_email=card.customer.email if card.customer else "",
|
||||
merchant_id=card.merchant_id,
|
||||
merchant_name=card.merchant.name if card.merchant else None,
|
||||
stamp_count=card.stamp_count,
|
||||
stamps_target=program.stamps_target,
|
||||
stamps_until_reward=max(0, program.stamps_target - card.stamp_count),
|
||||
points_balance=card.points_balance,
|
||||
can_redeem_stamps=card.stamp_count >= program.stamps_target,
|
||||
stamp_reward_description=program.stamps_reward_description,
|
||||
available_rewards=available_rewards,
|
||||
can_stamp=can_stamp,
|
||||
cooldown_ends_at=cooldown_ends,
|
||||
stamps_today=stamps_today,
|
||||
max_daily_stamps=program.max_daily_stamps,
|
||||
can_earn_more_stamps=stamps_today < program.max_daily_stamps,
|
||||
)
|
||||
|
||||
|
||||
@store_router.get("/cards/lookup", response_model=CardLookupResponse)
|
||||
def search_card(
|
||||
request: Request,
|
||||
q: str = Query(..., description="Search by email, card number, or name"),
|
||||
current_user: User = Depends(get_current_store_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Search for a card by email, card number, or customer name.
|
||||
|
||||
Tries matching in order: card number, then customer email.
|
||||
Card must belong to the same merchant as the store.
|
||||
"""
|
||||
store_id = current_user.token_store_id
|
||||
|
||||
card = card_service.search_card_for_store(db, store_id, q)
|
||||
if not card:
|
||||
from app.modules.loyalty.exceptions import LoyaltyCardNotFoundException
|
||||
|
||||
raise LoyaltyCardNotFoundException(q)
|
||||
|
||||
return _build_card_lookup_response(card, db)
|
||||
|
||||
|
||||
@store_router.post("/cards/lookup", response_model=CardLookupResponse)
|
||||
def lookup_card(
|
||||
request: Request,
|
||||
@@ -337,46 +404,7 @@ def lookup_card(
|
||||
card_number=card_number,
|
||||
)
|
||||
|
||||
program = card.program
|
||||
|
||||
# Check cooldown
|
||||
can_stamp, _ = card.can_stamp(program.cooldown_minutes)
|
||||
cooldown_ends = None
|
||||
if not can_stamp and card.last_stamp_at:
|
||||
from datetime import timedelta
|
||||
|
||||
cooldown_ends = card.last_stamp_at + timedelta(minutes=program.cooldown_minutes)
|
||||
|
||||
# Get stamps today
|
||||
stamps_today = card_service.get_stamps_today(db, card.id)
|
||||
|
||||
# Get available points rewards
|
||||
available_rewards = []
|
||||
for reward in program.points_rewards or []:
|
||||
if reward.get("is_active", True) and card.points_balance >= reward.get("points_required", 0):
|
||||
available_rewards.append(reward)
|
||||
|
||||
return CardLookupResponse(
|
||||
card_id=card.id,
|
||||
card_number=card.card_number,
|
||||
customer_id=card.customer_id,
|
||||
customer_name=card.customer.full_name if card.customer else None,
|
||||
customer_email=card.customer.email if card.customer else "",
|
||||
merchant_id=card.merchant_id,
|
||||
merchant_name=card.merchant.name if card.merchant else None,
|
||||
stamp_count=card.stamp_count,
|
||||
stamps_target=program.stamps_target,
|
||||
stamps_until_reward=max(0, program.stamps_target - card.stamp_count),
|
||||
points_balance=card.points_balance,
|
||||
can_redeem_stamps=card.stamp_count >= program.stamps_target,
|
||||
stamp_reward_description=program.stamps_reward_description,
|
||||
available_rewards=available_rewards,
|
||||
can_stamp=can_stamp,
|
||||
cooldown_ends_at=cooldown_ends,
|
||||
stamps_today=stamps_today,
|
||||
max_daily_stamps=program.max_daily_stamps,
|
||||
can_earn_more_stamps=stamps_today < program.max_daily_stamps,
|
||||
)
|
||||
return _build_card_lookup_response(card, db)
|
||||
|
||||
|
||||
@store_router.post("/cards/enroll", response_model=CardResponse, status_code=201)
|
||||
|
||||
Reference in New Issue
Block a user