package su.xserver.iikocon.iiko; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.sqlclient.Row; import io.vertx.sqlclient.Tuple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import su.xserver.iikocon.handler.AdminHandler; import su.xserver.iikocon.handler.AuthHandler; import su.xserver.iikocon.service.DataBaseService; import su.xserver.iikocon.service.RestaurantService; public class IikoHandler { private final Logger log = LoggerFactory.getLogger("[IikoHandler]"); private final DataBaseService db; private final Vertx vertx; private final RestaurantService restaurantService; public IikoHandler(Vertx vertx, Router router, DataBaseService db, RestaurantService restaurantService, AuthHandler authHandler) { this.vertx = vertx; this.restaurantService = restaurantService; this.db = db; createTablesIfNotExist().onFailure(err -> { log.error("Failed to initialize database", err); }); router.route("/api/reports/olap/*").handler(authHandler::requireAuth); router.get("/api/reports/olap/columns").handler(this::getColumns); router.delete("/api/reports/olap/columns/:fieldKey").handler(AdminHandler::requireAdmin).handler(this::deleteColumn); router.post("/api/reports/olap/initialize").handler(AdminHandler::requireAdmin).handler(this::postInitialize); } private void getColumns(RoutingContext ctx) { getAllFieldsWithReportAndTags() .onSuccess(ar -> ctx.response() .putHeader("Content-Type", "application/json") .end(ar.encodePrettily())) .onFailure(err -> ctx.response() .setStatusCode(500) .end(err.getMessage())); } public void deleteColumn(RoutingContext ctx) { String fieldKey = ctx.pathParam("fieldKey"); String sql = "DELETE FROM iiko_fields_common WHERE field_key = ?"; db.getPool().preparedQuery(sql) .execute(Tuple.of(fieldKey)) .onSuccess(res -> { ctx.end(); }) .onFailure(err -> ctx.response().setStatusCode(500).end(err.getMessage())); } private void postInitialize(RoutingContext ctx) { JsonObject body = ctx.body().asJsonObject(); if (body == null) { ctx.response() .setStatusCode(400) .end("Request body is missing or not a JSON object"); return; } if (!body.containsKey("restaurantId") || body.getValue("restaurantId") == null) { ctx.response() .setStatusCode(400) .end("restaurantId is required"); return; } Integer restaurantId; try { restaurantId = body.getInteger("restaurantId"); if (restaurantId == null) { throw new IllegalArgumentException("restaurantId must be a number"); } } catch (ClassCastException e) { ctx.response() .setStatusCode(400) .end("restaurantId must be a valid integer"); return; } restaurantService.findById(restaurantId) .onSuccess(rest -> { IikoOlapClient iiko = new IikoOlapClient(vertx, rest); iiko.checkConnection() .onSuccess(ping -> clearTables() .onSuccess(data -> { IikoOlapColumnsImporter importer = new IikoOlapColumnsImporter(iiko, db); importer.fetchAndStoreAll() .onSuccess(res -> ctx.end("OK")) .onFailure(err -> ctx.response() .setStatusCode(400) .end(err.getMessage())); }) .onFailure(err -> ctx.response() .setStatusCode(400) .end(err.getMessage()))) .onFailure(err -> ctx.response().setStatusCode(400).end(err.getMessage())); }) .onFailure(err -> ctx.response() .setStatusCode(400) .end(err.getMessage())); } public Future getAllFieldsWithReportAndTags() { String sql = """ SELECT fc.field_key, fc.field_key_normal, fc.name, fc.type, fc.type_normal, fc.aggregation_allowed, fc.grouping_allowed, fc.filtering_allowed, GROUP_CONCAT(DISTINCT rt.name ORDER BY rt.name SEPARATOR ',') AS report_names, GROUP_CONCAT(DISTINCT t.tag_name ORDER BY t.tag_name SEPARATOR ',') AS tag_names FROM iiko_fields_common fc LEFT JOIN iiko_report_type_fields rtf ON fc.field_id = rtf.field_id LEFT JOIN iiko_report_types rt ON rtf.report_type_id = rt.report_type_id LEFT JOIN iiko_field_tags ft ON fc.field_id = ft.field_id LEFT JOIN iiko_tags t ON ft.tag_id = t.tag_id GROUP BY fc.field_id ORDER BY fc.field_key """; return db.getPool().query(sql).execute() .map(rows -> { JsonArray columnsArray = new JsonArray(); for (Row row : rows) { String reportNamesStr = row.getString("report_names"); JsonArray reportTypes = new JsonArray(); if (reportNamesStr != null && !reportNamesStr.isBlank()) { for (String name : reportNamesStr.split(",")) { reportTypes.add(name.trim()); } } String tagNamesStr = row.getString("tag_names"); JsonArray tags = new JsonArray(); if (tagNamesStr != null && !tagNamesStr.isBlank()) { for (String tag : tagNamesStr.split(",")) { tags.add(tag.trim()); } } JsonObject fieldObj = new JsonObject() .put("fieldKey", row.getString("field_key")) .put("fieldKeyNormal", row.getString("field_key_normal")) .put("reportTypes", reportTypes) .put("name", row.getString("name")) .put("type", row.getString("type")) .put("typeNormal", row.getString("type_normal")) .put("aggregationAllowed", row.getBoolean("aggregation_allowed")) .put("groupingAllowed", row.getBoolean("grouping_allowed")) .put("filteringAllowed", row.getBoolean("filtering_allowed")) .put("tags", tags); columnsArray.add(fieldObj); } return new JsonObject().put("columns", columnsArray); }); } private Future createTablesIfNotExist() { String createReportTypes = """ CREATE TABLE IF NOT EXISTS iiko_report_types ( report_type_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description TEXT NOT NULL ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci """; String createFieldsCommon = """ CREATE TABLE IF NOT EXISTS iiko_fields_common ( field_id INT AUTO_INCREMENT PRIMARY KEY, field_key VARCHAR(255) NOT NULL UNIQUE, field_key_normal VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(50) NOT NULL, type_normal VARCHAR(50) NOT NULL, aggregation_allowed BOOLEAN NOT NULL DEFAULT 0, grouping_allowed BOOLEAN NOT NULL DEFAULT 0, filtering_allowed BOOLEAN NOT NULL DEFAULT 0 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci """; String createReportTypeFields = """ CREATE TABLE IF NOT EXISTS iiko_report_type_fields ( report_type_id INT NOT NULL, field_id INT NOT NULL, PRIMARY KEY (report_type_id, field_id), FOREIGN KEY (report_type_id) REFERENCES iiko_report_types(report_type_id) ON DELETE CASCADE, FOREIGN KEY (field_id) REFERENCES iiko_fields_common(field_id) ON DELETE CASCADE ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci """; String createTags = """ CREATE TABLE IF NOT EXISTS iiko_tags ( tag_id INT AUTO_INCREMENT PRIMARY KEY, tag_name VARCHAR(100) UNIQUE NOT NULL ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci """; String createFieldTags = """ CREATE TABLE IF NOT EXISTS iiko_field_tags ( field_id INT NOT NULL, tag_id INT NOT NULL, PRIMARY KEY (field_id, tag_id), FOREIGN KEY (field_id) REFERENCES iiko_fields_common(field_id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES iiko_tags(tag_id) ON DELETE CASCADE ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci """; String idxKeyNormal = "CREATE INDEX IF NOT EXISTS idx_fields_common_key_normal ON iiko_fields_common(field_key_normal)"; String idxFieldName = "CREATE INDEX IF NOT EXISTS idx_fields_common_name ON iiko_fields_common(name)"; String idxFieldTagsTag = "CREATE INDEX IF NOT EXISTS idx_field_tags_tag_id ON iiko_field_tags(tag_id)"; return db.getPool().query(createReportTypes).execute() .compose(v -> db.getPool().query(createFieldsCommon).execute()) .compose(v -> db.getPool().query(createReportTypeFields).execute()) .compose(v -> db.getPool().query(createTags).execute()) .compose(v -> db.getPool().query(createFieldTags).execute()) .compose(v -> db.getPool().query(idxKeyNormal).execute()) .compose(v -> db.getPool().query(idxFieldName).execute()) .compose(v -> db.getPool().query(idxFieldTagsTag).execute()) .mapEmpty(); } private Future clearTables() { String sql = """ -- Отключаем проверку внешних ключей SET FOREIGN_KEY_CHECKS = 0; -- Удаляем данные из всех таблиц (порядок не важен при отключённой проверке) DELETE FROM iiko_field_tags; DELETE FROM iiko_report_type_fields; DELETE FROM iiko_fields_common; DELETE FROM iiko_tags; DELETE FROM iiko_report_types; -- Сбрасываем счётчики AUTO_INCREMENT (чтобы новые ID начинались с 1) ALTER TABLE iiko_fields_common AUTO_INCREMENT = 1; ALTER TABLE iiko_tags AUTO_INCREMENT = 1; ALTER TABLE iiko_report_types AUTO_INCREMENT = 1; -- Включаем проверку обратно SET FOREIGN_KEY_CHECKS = 1; """; return db.getPool().query(sql).execute().mapEmpty(); } }