diff --git a/AGENTS.md b/AGENTS.md index 2d6be82..b96c2f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,6 +192,10 @@ Notes / constraints - Super admin can create properties and assign users to properties. - Admin can assign ADMIN/MANAGER/STAFF/AGENT; Manager can assign STAFF/AGENT. - Agents can only see free rooms. +- Role hierarchy for visibility/management: SUPER_ADMIN > ADMIN > MANAGER > STAFF/HOUSEKEEPING/FINANCE/SUPERVISOR/GUIDE > AGENT. Users cannot see anyone above their rank in property user lists. Access code invites cannot assign ADMIN. +- Property code is auto-generated (7-char random, no fixed prefix). Property create no longer accepts `code` in request. Join-by-code uses property code, not propertyId. +- Property access codes: 6-digit PIN, 1-minute expiry, single-use. Admin generates; staff joins with property code + PIN. +- Property user disable is property-scoped (not global); hierarchy applies for who can disable. Operational notes - Payment provider migrated: PayU removed; Razorpay now used for settings, QR, payment links, and webhooks. diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/dto/property/UserDirectoryDtos.kt b/src/main/kotlin/com/android/trisolarisserver/controller/dto/property/UserDirectoryDtos.kt index c8e262c..27258ed 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/dto/property/UserDirectoryDtos.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/dto/property/UserDirectoryDtos.kt @@ -33,6 +33,7 @@ data class PropertyAccessCodeResponse( ) data class PropertyAccessCodeJoinRequest( - val propertyCode: String, + val propertyCode: String? = null, + val propertyId: String? = null, val code: String ) diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt b/src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt index d56f477..c73619c 100644 --- a/src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt +++ b/src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt @@ -7,6 +7,7 @@ import com.android.trisolarisserver.controller.dto.property.PropertyAccessCodeJo import com.android.trisolarisserver.controller.dto.property.PropertyAccessCodeResponse import com.android.trisolarisserver.controller.dto.property.PropertyUserResponse import com.android.trisolarisserver.models.property.PropertyAccessCode +import com.android.trisolarisserver.models.property.Property import com.android.trisolarisserver.models.property.PropertyUser import com.android.trisolarisserver.models.property.PropertyUserId import com.android.trisolarisserver.models.property.Role @@ -97,8 +98,7 @@ class PropertyAccessCodes( throw ResponseStatusException(HttpStatus.NOT_FOUND, "Invalid code") } val now = OffsetDateTime.now() - val property = propertyRepo.findByCode(request.propertyCode.trim()) - ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found") + val property = resolveProperty(request) val accessCode = accessCodeRepo.findActiveByPropertyAndCode(property.id!!, code, now) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Invalid code") @@ -130,6 +130,27 @@ class PropertyAccessCodes( ) } + private fun resolveProperty(request: PropertyAccessCodeJoinRequest): Property { + val code = request.propertyCode?.trim().orEmpty() + if (code.isNotBlank()) { + return propertyRepo.findByCode(code) + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found") + } + val rawId = request.propertyId?.trim().orEmpty() + if (rawId.isBlank()) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Property code required") + } + val asUuid = runCatching { UUID.fromString(rawId) }.getOrNull() + return if (asUuid != null) { + propertyRepo.findById(asUuid).orElseThrow { + ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found") + } + } else { + propertyRepo.findByCode(rawId) + ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Property not found") + } + } + private fun parseRoles(input: Set): Set { return try { input.map { Role.valueOf(it) }.toSet()