package com.android.trisolarisserver.controller import com.android.trisolarisserver.component.BookingEvents import com.android.trisolarisserver.component.PropertyAccess import com.android.trisolarisserver.controller.dto.ChargeCreateRequest import com.android.trisolarisserver.controller.dto.ChargeResponse import com.android.trisolarisserver.db.repo.BookingRepo import com.android.trisolarisserver.models.booking.Charge import com.android.trisolarisserver.models.booking.ChargeType import com.android.trisolarisserver.models.property.Role import com.android.trisolarisserver.repo.AppUserRepo import com.android.trisolarisserver.repo.ChargeRepo import com.android.trisolarisserver.security.MyPrincipal import org.springframework.http.HttpStatus import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException import java.util.UUID @RestController @RequestMapping("/properties/{propertyId}/bookings/{bookingId}/charges") class Charges( private val propertyAccess: PropertyAccess, private val bookingRepo: BookingRepo, private val chargeRepo: ChargeRepo, private val appUserRepo: AppUserRepo, private val bookingEvents: BookingEvents ) { @PostMapping @ResponseStatus(HttpStatus.CREATED) @Transactional fun create( @PathVariable propertyId: UUID, @PathVariable bookingId: UUID, @AuthenticationPrincipal principal: MyPrincipal?, @RequestBody request: ChargeCreateRequest ): ChargeResponse { val actor = requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER, Role.FINANCE) val booking = bookingRepo.findById(bookingId).orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found") } if (booking.property.id != propertyId) { throw ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found for property") } if (request.amount <= 0) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "amount must be > 0") } val type = parseType(request.type) val occurredAt = parseOffset(request.occurredAt) ?: nowForProperty(booking.property.timezone) val createdBy = appUserRepo.findById(actor.userId).orElse(null) val charge = Charge( property = booking.property, booking = booking, type = type, amount = request.amount, currency = request.currency.trim(), notes = request.notes?.trim()?.ifBlank { null }, occurredAt = occurredAt, createdBy = createdBy ) val saved = chargeRepo.save(charge).toResponse() bookingEvents.emit(propertyId, bookingId) return saved } @GetMapping fun list( @PathVariable propertyId: UUID, @PathVariable bookingId: UUID, @AuthenticationPrincipal principal: MyPrincipal? ): List { requireMember(propertyAccess, propertyId, principal) val booking = bookingRepo.findById(bookingId).orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found") } if (booking.property.id != propertyId) { throw ResponseStatusException(HttpStatus.NOT_FOUND, "Booking not found for property") } return chargeRepo.findByBookingIdOrderByOccurredAtDesc(bookingId).map { it.toResponse() } } private fun parseType(value: String): ChargeType { return try { ChargeType.valueOf(value.trim().uppercase()) } catch (_: Exception) { throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown charge type") } } } private fun Charge.toResponse(): ChargeResponse { val id = id ?: throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Charge id missing") return ChargeResponse( id = id, bookingId = booking.id!!, type = type.name, amount = amount, currency = currency, occurredAt = occurredAt, notes = notes ) }