feat: Add catalog generation functionality and related models
- Implemented CatalogResource for handling catalog generation requests and status checks. - Created CatalogGenerationService to manage the asynchronous generation process. - Added models for catalog generation responses, requests, and structured catalog data. - Introduced utility scripts for database interaction and Kubernetes pod retrieval. - Enhanced error handling and logging throughout the catalog generation workflow.
This commit is contained in:
@@ -365,7 +365,7 @@ BEGIN
|
||||
|
||||
-- Markdown-Bericht generieren (Fehler ignoriert, Projekt bleibt COMPLETED)
|
||||
BEGIN
|
||||
dc_generate_report(:project_id);
|
||||
DC_UTILS_PKG.generate_report(:project_id);
|
||||
EXCEPTION WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
|
||||
@@ -406,7 +406,7 @@ BEGIN
|
||||
p_comments => 'Generiert report_markdown neu (z.B. nach Konfigurationsänderung)',
|
||||
p_source => q'[
|
||||
BEGIN
|
||||
dc_generate_report(:project_id);
|
||||
DC_UTILS_PKG.generate_report(:project_id);
|
||||
:status_code := 200;
|
||||
HTP.P('{"project_id":' || :project_id || ',"report_generated":true}');
|
||||
EXCEPTION
|
||||
@@ -731,6 +731,301 @@ BEGIN
|
||||
]'
|
||||
);
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- TEMPLATE 13: catalogs/ – POST (Katalog anlegen)
|
||||
-- Ergaenzt das bestehende catalogs/-Template um eine Schreib-Operation.
|
||||
-- ===========================================================================
|
||||
|
||||
-- 13. POST /api/dc/catalogs/
|
||||
-- Body: {"name":"ADSp 2017","description":"...","document_type_id":1}
|
||||
-- Legt den Katalog mit generation_status=GENERATING an (async Flow).
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/',
|
||||
p_method => 'POST',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => '201 Created mit neuer catalog_id; 400 bei fehlendem name/document_type_id',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_body CLOB;
|
||||
v_name VARCHAR2(100);
|
||||
v_desc CLOB;
|
||||
v_dt_id NUMBER;
|
||||
v_new_id NUMBER;
|
||||
BEGIN
|
||||
v_body := :body_text;
|
||||
v_name := JSON_VALUE(v_body, '$.name');
|
||||
v_desc := JSON_VALUE(v_body, '$.description' RETURNING CLOB);
|
||||
v_dt_id := TO_NUMBER(JSON_VALUE(v_body, '$.document_type_id'));
|
||||
|
||||
IF v_name IS NULL OR v_dt_id IS NULL THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"name und document_type_id sind Pflichtfelder"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO dc_question_catalogs (name, description, document_type_id, generation_status)
|
||||
VALUES (v_name, v_desc, v_dt_id, 'GENERATING')
|
||||
RETURNING id INTO v_new_id;
|
||||
|
||||
:status_code := 201;
|
||||
HTP.P('{"id":' || v_new_id
|
||||
|| ',"name":"' || REPLACE(v_name, '"', '''') || '"'
|
||||
|| ',"document_type_id":' || v_dt_id
|
||||
|| ',"generation_status":"GENERATING"}');
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
ROLLBACK;
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- TEMPLATE 16: catalogs/:catalog_id/generation-status (GET + PUT)
|
||||
-- ===========================================================================
|
||||
ORDS.DEFINE_TEMPLATE(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/:catalog_id/generation-status',
|
||||
p_priority => 0,
|
||||
p_comments => 'Status der asynchronen Katalog-Generierung lesen/schreiben'
|
||||
);
|
||||
|
||||
-- 16a. GET /api/dc/catalogs/:catalog_id/generation-status
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/:catalog_id/generation-status',
|
||||
p_method => 'GET',
|
||||
p_source_type => ORDS.SOURCE_TYPE_COLLECTION_ITEM,
|
||||
p_items_per_page => 1,
|
||||
p_comments => 'Gibt generation_status und generation_error zurueck',
|
||||
p_source => q'[
|
||||
SELECT
|
||||
c.id AS catalog_id,
|
||||
c.name,
|
||||
c.description,
|
||||
c.generation_status,
|
||||
c.generation_error
|
||||
FROM dc_question_catalogs c
|
||||
WHERE c.id = :catalog_id
|
||||
]'
|
||||
);
|
||||
|
||||
-- 16b. PUT /api/dc/catalogs/:catalog_id/generation-status
|
||||
-- Body: {"generation_status":"READY"} oder {"generation_status":"ERROR","generation_error":"..."}
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/:catalog_id/generation-status',
|
||||
p_method => 'PUT',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => 'Setzt generation_status (GENERATING|READY|ERROR) und optionalen Fehlertext',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_body CLOB;
|
||||
v_status VARCHAR2(20);
|
||||
v_error VARCHAR2(4000);
|
||||
v_rows INTEGER;
|
||||
BEGIN
|
||||
v_body := :body_text;
|
||||
v_status := JSON_VALUE(v_body, '$.generation_status');
|
||||
v_error := JSON_VALUE(v_body, '$.generation_error');
|
||||
|
||||
IF v_status NOT IN ('GENERATING', 'READY', 'ERROR') THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"generation_status muss GENERATING, READY oder ERROR sein"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
UPDATE dc_question_catalogs
|
||||
SET generation_status = v_status,
|
||||
generation_error = v_error
|
||||
WHERE id = :catalog_id;
|
||||
|
||||
v_rows := SQL%ROWCOUNT;
|
||||
IF v_rows = 0 THEN
|
||||
:status_code := 404;
|
||||
HTP.P('{"error":"Katalog nicht gefunden","catalog_id":' || :catalog_id || '}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
:status_code := 200;
|
||||
HTP.P('{"catalog_id":' || :catalog_id
|
||||
|| ',"generation_status":"' || v_status || '"'
|
||||
|| ',"updated":true}');
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
ROLLBACK;
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- TEMPLATE 14: catalogs/:catalog_id/categories/ (POST)
|
||||
-- ===========================================================================
|
||||
ORDS.DEFINE_TEMPLATE(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/:catalog_id/categories/',
|
||||
p_priority => 0,
|
||||
p_comments => 'Fragenkategorien eines Katalogs anlegen'
|
||||
);
|
||||
|
||||
-- 14. POST /api/dc/catalogs/:catalog_id/categories/
|
||||
-- Body: {"name":"Haftung","description":"...","order_nr":1}
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'catalogs/:catalog_id/categories/',
|
||||
p_method => 'POST',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => '201 Created mit neuer category_id; 400 bei fehlendem name',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_body CLOB;
|
||||
v_name VARCHAR2(100);
|
||||
v_desc CLOB;
|
||||
v_order NUMBER;
|
||||
v_new_id NUMBER;
|
||||
BEGIN
|
||||
v_body := :body_text;
|
||||
v_name := JSON_VALUE(v_body, '$.name');
|
||||
v_desc := JSON_VALUE(v_body, '$.description' RETURNING CLOB);
|
||||
v_order := NVL(TO_NUMBER(JSON_VALUE(v_body, '$.order_nr')), 0);
|
||||
|
||||
IF v_name IS NULL THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"name ist ein Pflichtfeld"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO dc_question_categories (catalog_id, name, description, order_nr)
|
||||
VALUES (:catalog_id, v_name, v_desc, v_order)
|
||||
RETURNING id INTO v_new_id;
|
||||
|
||||
:status_code := 201;
|
||||
HTP.P('{"id":' || v_new_id
|
||||
|| ',"catalog_id":' || :catalog_id
|
||||
|| ',"name":"' || REPLACE(v_name, '"', '''') || '"'
|
||||
|| ',"order_nr":' || v_order || '}');
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
ROLLBACK;
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
|
||||
-- ===========================================================================
|
||||
-- TEMPLATE 15: categories/:category_id/questions/ (POST)
|
||||
-- ===========================================================================
|
||||
ORDS.DEFINE_TEMPLATE(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'categories/:category_id/questions/',
|
||||
p_priority => 0,
|
||||
p_comments => 'Fragen einer Kategorie anlegen'
|
||||
);
|
||||
|
||||
-- 15. POST /api/dc/categories/:category_id/questions/
|
||||
-- Body: {
|
||||
-- "question_text": "...",
|
||||
-- "evaluation_type": "SCORE",
|
||||
-- "threshold": 70,
|
||||
-- "result_handling": "ABWEICHUNG",
|
||||
-- "example_0_percent": "...",
|
||||
-- "example_100_percent": "...",
|
||||
-- "order_nr": 1
|
||||
-- }
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'categories/:category_id/questions/',
|
||||
p_method => 'POST',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => '201 Created mit neuer question_id; 400 bei fehlendem question_text',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_body CLOB;
|
||||
v_text CLOB;
|
||||
v_eval_type VARCHAR2(20);
|
||||
v_threshold NUMBER;
|
||||
v_handling VARCHAR2(20);
|
||||
v_ex0 CLOB;
|
||||
v_ex100 CLOB;
|
||||
v_order NUMBER;
|
||||
v_new_id NUMBER;
|
||||
BEGIN
|
||||
v_body := :body_text;
|
||||
v_text := JSON_VALUE(v_body, '$.question_text' RETURNING CLOB);
|
||||
v_eval_type := NVL(JSON_VALUE(v_body, '$.evaluation_type'), 'SCORE');
|
||||
v_threshold := NVL(TO_NUMBER(JSON_VALUE(v_body, '$.threshold')), 70);
|
||||
v_handling := NVL(JSON_VALUE(v_body, '$.result_handling'), 'ABWEICHUNG');
|
||||
v_ex0 := JSON_VALUE(v_body, '$.example_0_percent' RETURNING CLOB);
|
||||
v_ex100 := JSON_VALUE(v_body, '$.example_100_percent' RETURNING CLOB);
|
||||
v_order := NVL(TO_NUMBER(JSON_VALUE(v_body, '$.order_nr')), 0);
|
||||
|
||||
IF v_text IS NULL THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"question_text ist ein Pflichtfeld"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_eval_type NOT IN ('SCORE', 'BINARY') THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"evaluation_type muss SCORE oder BINARY sein"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF v_handling NOT IN ('ABWEICHUNG', 'HINWEIS') THEN
|
||||
:status_code := 400;
|
||||
HTP.P('{"error":"result_handling muss ABWEICHUNG oder HINWEIS sein"}');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
INSERT INTO dc_questions (
|
||||
category_id,
|
||||
question_text,
|
||||
evaluation_type,
|
||||
threshold,
|
||||
result_handling,
|
||||
example_0_percent,
|
||||
example_100_percent,
|
||||
order_nr
|
||||
) VALUES (
|
||||
:category_id,
|
||||
v_text,
|
||||
v_eval_type,
|
||||
v_threshold,
|
||||
v_handling,
|
||||
v_ex0,
|
||||
v_ex100,
|
||||
v_order
|
||||
)
|
||||
RETURNING id INTO v_new_id;
|
||||
|
||||
:status_code := 201;
|
||||
HTP.P('{"id":' || v_new_id
|
||||
|| ',"category_id":' || :category_id
|
||||
|| ',"evaluation_type":"' || v_eval_type || '"'
|
||||
|| ',"threshold":' || v_threshold
|
||||
|| ',"order_nr":' || v_order || '}');
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
ROLLBACK;
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
|
||||
Reference in New Issue
Block a user