package com.estateunified.panel import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.util.UUID internal data class TicketReply( val author: String, val body: String, val createdAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) internal data class TicketDto( val id: String = "tkt-${UUID.randomUUID().toString().take(8)}", val ownerUserId: String, val subject: String, val body: String, val category: String = "GENERAL", var status: String = "OPEN", val replies: MutableList = mutableListOf(), val createdAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), var updatedAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) internal class TicketsStore( private val gson: Gson, private val file: Path, ) { private val lock = Any() fun listForUser(userId: String): List = synchronized(lock) { load().filter { it.ownerUserId == userId } } fun add(dto: TicketDto): TicketDto = synchronized(lock) { val list = load().toMutableList() list.add(0, dto) val canned = TicketReply( author = "Поддержка EstateUnified", body = "Спасибо за обращение! Демо-режим: реальный оператор не подключён. Мы скоро ответим в этом окне.", ) dto.replies.add(canned) persist(list) dto } fun reply(id: String, reply: TicketReply): TicketDto? = synchronized(lock) { val list = load().toMutableList() val idx = list.indexOfFirst { it.id == id } if (idx < 0) return@synchronized null val cur = list[idx] cur.replies.add(reply) val updated = cur.copy( updatedAtIso = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) list[idx] = updated persist(list) updated } fun setStatus(id: String, userId: String, status: String): TicketDto? = synchronized(lock) { val list = load().toMutableList() val idx = list.indexOfFirst { it.id == id && it.ownerUserId == userId } if (idx < 0) return@synchronized null val updated = list[idx].copy( status = status, updatedAtIso = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) list[idx] = updated persist(list) updated } private fun load(): MutableList { if (!Files.isRegularFile(file)) return mutableListOf() val text = Files.readString(file, StandardCharsets.UTF_8) if (text.isBlank()) return mutableListOf() return runCatching { gson.fromJson>(text, listType) ?: mutableListOf() }.getOrElse { mutableListOf() } } private fun persist(list: List) { Files.createDirectories(file.toAbsolutePath().parent) Files.writeString(file, gson.toJson(list), StandardCharsets.UTF_8) } companion object { private val listType = object : TypeToken>() {}.type } }