This commit is contained in:
Iliyan Angelov
2025-11-21 01:20:51 +02:00
parent a38ab4fa82
commit 6f85b8cf17
242 changed files with 7154 additions and 14492 deletions

View File

@@ -21,22 +21,18 @@ from ..config.settings import settings
router = APIRouter(prefix="/service-bookings", tags=["service-bookings"])
def generate_service_booking_number() -> str:
"""Generate unique service booking number"""
prefix = "SB"
timestamp = datetime.utcnow().strftime("%Y%m%d")
random_suffix = random.randint(1000, 9999)
return f"{prefix}{timestamp}{random_suffix}"
@router.post("/")
async def create_service_booking(
booking_data: dict,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create a new service booking"""
try:
services = booking_data.get("services", [])
total_amount = float(booking_data.get("total_amount", 0))
@@ -48,7 +44,7 @@ async def create_service_booking(
if total_amount <= 0:
raise HTTPException(status_code=400, detail="Total amount must be greater than 0")
# Validate services and calculate total
calculated_total = 0
service_items_data = []
@@ -59,7 +55,7 @@ async def create_service_booking(
if not service_id:
raise HTTPException(status_code=400, detail="Service ID is required for each item")
# Check if service exists and is active
service = db.query(Service).filter(Service.id == service_id).first()
if not service:
raise HTTPException(status_code=404, detail=f"Service with ID {service_id} not found")
@@ -78,17 +74,17 @@ async def create_service_booking(
"total_price": item_total
})
# Verify calculated total matches provided total (with small tolerance for floating point)
if abs(calculated_total - total_amount) > 0.01:
raise HTTPException(
status_code=400,
detail=f"Total amount mismatch. Calculated: {calculated_total}, Provided: {total_amount}"
)
# Generate booking number
booking_number = generate_service_booking_number()
# Create service booking
service_booking = ServiceBooking(
booking_number=booking_number,
user_id=current_user.id,
@@ -98,9 +94,9 @@ async def create_service_booking(
)
db.add(service_booking)
db.flush() # Flush to get the ID
db.flush()
# Create service booking items
for item_data in service_items_data:
booking_item = ServiceBookingItem(
service_booking_id=service_booking.id,
@@ -114,12 +110,12 @@ async def create_service_booking(
db.commit()
db.refresh(service_booking)
# Load relationships
service_booking = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
).filter(ServiceBooking.id == service_booking.id).first()
# Format response
booking_dict = {
"id": service_booking.id,
"booking_number": service_booking.booking_number,
@@ -157,13 +153,11 @@ async def create_service_booking(
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
@router.get("/me")
async def get_my_service_bookings(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get all service bookings for current user"""
try:
bookings = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
@@ -204,14 +198,12 @@ async def get_my_service_bookings(
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{id}")
async def get_service_booking_by_id(
id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get service booking by ID"""
try:
booking = db.query(ServiceBooking).options(
joinedload(ServiceBooking.service_items).joinedload(ServiceBookingItem.service)
@@ -220,7 +212,7 @@ async def get_service_booking_by_id(
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
# Check access
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
@@ -259,7 +251,6 @@ async def get_service_booking_by_id(
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{id}/payment/stripe/create-intent")
async def create_service_stripe_payment_intent(
id: int,
@@ -267,9 +258,8 @@ async def create_service_stripe_payment_intent(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Create Stripe payment intent for service booking"""
try:
# Check if Stripe is configured
secret_key = get_stripe_secret_key(db)
if not secret_key:
secret_key = settings.STRIPE_SECRET_KEY
@@ -286,7 +276,7 @@ async def create_service_stripe_payment_intent(
if amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be greater than 0")
# Verify service booking exists and user has access
booking = db.query(ServiceBooking).filter(ServiceBooking.id == id).first()
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
@@ -294,22 +284,22 @@ async def create_service_stripe_payment_intent(
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
# Verify amount matches booking total
if abs(float(booking.total_amount) - amount) > 0.01:
raise HTTPException(
status_code=400,
detail=f"Amount mismatch. Booking total: {booking.total_amount}, Provided: {amount}"
)
# Create payment intent
intent = StripeService.create_payment_intent(
amount=amount,
currency=currency,
description=f"Service Booking #{booking.booking_number}",
description=f"Service Booking
db=db
)
# Get publishable key
publishable_key = get_stripe_publishable_key(db)
if not publishable_key:
publishable_key = settings.STRIPE_PUBLISHABLE_KEY
@@ -333,7 +323,6 @@ async def create_service_stripe_payment_intent(
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{id}/payment/stripe/confirm")
async def confirm_service_stripe_payment(
id: int,
@@ -341,14 +330,13 @@ async def confirm_service_stripe_payment(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Confirm Stripe payment for service booking"""
try:
payment_intent_id = payment_data.get("payment_intent_id")
if not payment_intent_id:
raise HTTPException(status_code=400, detail="payment_intent_id is required")
# Verify service booking exists and user has access
booking = db.query(ServiceBooking).filter(ServiceBooking.id == id).first()
if not booking:
raise HTTPException(status_code=404, detail="Service booking not found")
@@ -356,7 +344,7 @@ async def confirm_service_stripe_payment(
if booking.user_id != current_user.id and current_user.role_id != 1:
raise HTTPException(status_code=403, detail="Forbidden")
# Retrieve and verify payment intent
intent_data = StripeService.retrieve_payment_intent(payment_intent_id, db)
if intent_data["status"] != "succeeded":
@@ -365,15 +353,15 @@ async def confirm_service_stripe_payment(
detail=f"Payment intent status is {intent_data['status']}, expected 'succeeded'"
)
# Verify amount matches
amount_paid = intent_data["amount"] / 100 # Convert from cents
amount_paid = intent_data["amount"] / 100
if abs(float(booking.total_amount) - amount_paid) > 0.01:
raise HTTPException(
status_code=400,
detail="Payment amount does not match booking total"
)
# Create payment record
payment = ServicePayment(
service_booking_id=booking.id,
amount=booking.total_amount,
@@ -386,7 +374,7 @@ async def confirm_service_stripe_payment(
db.add(payment)
# Update booking status
booking.status = ServiceBookingStatus.confirmed
db.commit()