fix(loyalty): accept store_id in body for merchant PIN create
Some checks failed
Some checks failed
The merchant /pins POST was reading store_id as a query parameter, but the shared loyalty pins JS factory sends the form (including store_id) as a JSON body — matching the store-side endpoint, which gets store_id from the JWT and ignores any body field. Result: a 422 "Field required" on every PIN create from /merchants/loyalty/pins. Add PinCreateForMerchant (PinCreate + store_id) and switch the endpoint to it. Validation that the store belongs to the merchant is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ from app.modules.loyalty.schemas import (
|
|||||||
CardResponse,
|
CardResponse,
|
||||||
MerchantSettingsResponse,
|
MerchantSettingsResponse,
|
||||||
PinCreate,
|
PinCreate,
|
||||||
|
PinCreateForMerchant,
|
||||||
PinDetailListResponse,
|
PinDetailListResponse,
|
||||||
PinDetailResponse,
|
PinDetailResponse,
|
||||||
PinResponse,
|
PinResponse,
|
||||||
@@ -339,22 +340,24 @@ def list_pins(
|
|||||||
|
|
||||||
@router.post("/pins", response_model=PinResponse, status_code=201)
|
@router.post("/pins", response_model=PinResponse, status_code=201)
|
||||||
def create_pin(
|
def create_pin(
|
||||||
data: PinCreate,
|
data: PinCreateForMerchant,
|
||||||
store_id: int = Query(..., gt=0),
|
|
||||||
merchant: Merchant = Depends(get_merchant_for_current_user),
|
merchant: Merchant = Depends(get_merchant_for_current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Create a new staff PIN."""
|
"""Create a new staff PIN. ``store_id`` comes from the body — the
|
||||||
|
merchant portal isn't scoped to a single store like the store API is.
|
||||||
|
"""
|
||||||
# Validate store belongs to merchant
|
# Validate store belongs to merchant
|
||||||
locations = program_service.get_merchant_locations(db, merchant.id)
|
locations = program_service.get_merchant_locations(db, merchant.id)
|
||||||
store_ids = [loc.id for loc in locations]
|
store_ids = [loc.id for loc in locations]
|
||||||
if store_id not in store_ids:
|
if data.store_id not in store_ids:
|
||||||
from app.modules.tenancy.exceptions import StoreNotFoundException
|
from app.modules.tenancy.exceptions import StoreNotFoundException
|
||||||
|
|
||||||
raise StoreNotFoundException(str(store_id), identifier_type="id")
|
raise StoreNotFoundException(str(data.store_id), identifier_type="id")
|
||||||
|
|
||||||
program = program_service.require_program_by_merchant(db, merchant.id)
|
program = program_service.require_program_by_merchant(db, merchant.id)
|
||||||
pin = pin_service.create_pin(db, program.id, store_id, data)
|
pin_data = PinCreate(name=data.name, staff_id=data.staff_id, pin=data.pin)
|
||||||
|
pin = pin_service.create_pin(db, program.id, data.store_id, pin_data)
|
||||||
return PinResponse.model_validate(pin)
|
return PinResponse.model_validate(pin)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from app.modules.loyalty.schemas.card import (
|
|||||||
from app.modules.loyalty.schemas.pin import (
|
from app.modules.loyalty.schemas.pin import (
|
||||||
# Staff PIN
|
# Staff PIN
|
||||||
PinCreate,
|
PinCreate,
|
||||||
|
PinCreateForMerchant,
|
||||||
PinDetailListResponse,
|
PinDetailListResponse,
|
||||||
PinDetailResponse,
|
PinDetailResponse,
|
||||||
PinListResponse,
|
PinListResponse,
|
||||||
@@ -131,6 +132,7 @@ __all__ = [
|
|||||||
"PointsAdjustResponse",
|
"PointsAdjustResponse",
|
||||||
# PIN
|
# PIN
|
||||||
"PinCreate",
|
"PinCreate",
|
||||||
|
"PinCreateForMerchant",
|
||||||
"PinUpdate",
|
"PinUpdate",
|
||||||
"PinResponse",
|
"PinResponse",
|
||||||
"PinDetailResponse",
|
"PinDetailResponse",
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ class PinCreate(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PinCreateForMerchant(PinCreate):
|
||||||
|
"""PinCreate from the merchant portal — carries the target store_id in
|
||||||
|
the body since the merchant has no per-store auth context (unlike the
|
||||||
|
store-side endpoint which reads store_id from the JWT)."""
|
||||||
|
|
||||||
|
store_id: int = Field(..., gt=0, description="Store this PIN belongs to")
|
||||||
|
|
||||||
|
|
||||||
class PinUpdate(BaseModel):
|
class PinUpdate(BaseModel):
|
||||||
"""Schema for updating a staff PIN."""
|
"""Schema for updating a staff PIN."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user