From 6a13d7c5c6ff5be037f1eb75aa77b8b924b44e2a Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 17 Jun 2025 20:47:43 +0200 Subject: [PATCH] fix: update trigger function for RoutingFormResponseField (#21868) * fix: update trigger function for RoutingFormResponseField * Update packages/prisma/migrations/20250617123959_update_handle_routing_form_response_fields/migration.sql Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * add reusable function * handle number type more flexibly * handle multiselect value flexibly --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Benny Joo --- .../migration.sql | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 packages/prisma/migrations/20250617123959_update_handle_routing_form_response_fields/migration.sql diff --git a/packages/prisma/migrations/20250617123959_update_handle_routing_form_response_fields/migration.sql b/packages/prisma/migrations/20250617123959_update_handle_routing_form_response_fields/migration.sql new file mode 100644 index 0000000000..769babcef4 --- /dev/null +++ b/packages/prisma/migrations/20250617123959_update_handle_routing_form_response_fields/migration.sql @@ -0,0 +1,207 @@ +-- Shared function that contains the core processing logic +CREATE OR REPLACE FUNCTION _process_routing_form_response_fields(response_id integer, response_data jsonb, form_id text) +RETURNS void AS $$ +DECLARE + form_fields jsonb; + field_record jsonb; + response_field jsonb; + field_type text; +BEGIN + -- Validate response_data exists and is a valid JSON object + IF response_data IS NULL OR jsonb_typeof(response_data) != 'object' THEN + RAISE WARNING 'Invalid response data for id %. Type: %', response_id, COALESCE(jsonb_typeof(response_data), 'null'); + RETURN; + END IF; + + -- Get the fields from App_RoutingForms_Form + SELECT fields::jsonb INTO form_fields + FROM "App_RoutingForms_Form" + WHERE id = form_id; + + -- Delete existing entries for this response + DELETE FROM "RoutingFormResponseField" + WHERE "responseId" = response_id; + + -- Exit early if form_fields is NULL or not an array + IF form_fields IS NULL OR jsonb_typeof(form_fields) != 'array' THEN + RAISE WARNING 'form_fields is NULL or not an array for formId %, skipping processing', form_id; + RETURN; + END IF; + + -- Iterate through each field in the response + FOR field_record IN SELECT * FROM jsonb_array_elements(form_fields) + LOOP + BEGIN + -- Validate field_record has required properties + IF field_record->>'id' IS NULL THEN + RAISE WARNING 'Field record is missing id property, skipping field'; + CONTINUE; + END IF; + IF field_record->>'type' IS NULL THEN + RAISE WARNING 'Field record % is missing type property, skipping field', field_record->>'id'; + CONTINUE; + END IF; + + -- Get the response field object for this field + response_field := response_data->(field_record->>'id'); + + -- Skip if no response for this field + IF response_field IS NULL THEN + CONTINUE; + END IF; + + -- Get field type + field_type := field_record->>'type'; + + -- Insert new record based on field type + IF field_type = 'multiselect' THEN + -- Handle array values for multiselect + -- Accept both array values and string values + IF response_field->>'value' IS NOT NULL THEN + BEGIN + IF jsonb_typeof(response_field->'value') = 'array' THEN + -- If it's already an array, use it directly + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueStringArray") + VALUES ( + response_id, + field_record->>'id', + ARRAY(SELECT jsonb_array_elements_text(response_field->'value')) + ); + ELSIF jsonb_typeof(response_field->'value') = 'string' THEN + -- If it's a string, convert it to a single-element array + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueStringArray") + VALUES ( + response_id, + field_record->>'id', + ARRAY[response_field->>'value'] + ); + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Failed to insert multiselect values for field %', field_record->>'id'; + END; + END IF; + ELSIF field_type = 'number' THEN + -- Handle number values + -- Accept both JSON number values and string values that can be converted to numbers + IF response_field->>'value' IS NOT NULL THEN + BEGIN + IF jsonb_typeof(response_field->'value') = 'number' THEN + -- If it's already a JSON number, use it directly + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueNumber") + VALUES ( + response_id, + field_record->>'id', + (response_field->>'value')::decimal + ); + ELSIF jsonb_typeof(response_field->'value') = 'string' THEN + -- If it's a string, try to convert it to a number + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueNumber") + VALUES ( + response_id, + field_record->>'id', + (response_field->>'value')::decimal + ); + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Failed to insert number value for field % (value: %)', field_record->>'id', response_field->>'value'; + END; + END IF; + ELSIF field_type = 'select' THEN + -- Handle select values - can be either string or array + IF response_field->>'value' IS NOT NULL THEN + BEGIN + IF jsonb_typeof(response_field->'value') = 'array' THEN + -- If it's an array, take the first element + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueString") + VALUES ( + response_id, + field_record->>'id', + (response_field->'value'->>0) + ); + ELSIF jsonb_typeof(response_field->'value') = 'string' THEN + -- If it's a string, use it directly + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueString") + VALUES ( + response_id, + field_record->>'id', + response_field->>'value' + ); + END IF; + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Failed to insert select value for field %', field_record->>'id'; + END; + END IF; + ELSE + -- Handle all other types as strings + -- Only insert if value is not null and is a valid string + IF response_field->>'value' IS NOT NULL AND jsonb_typeof(response_field->'value') = 'string' THEN + BEGIN + INSERT INTO "RoutingFormResponseField" ("responseId", "fieldId", "valueString") + VALUES ( + response_id, + field_record->>'id', + response_field->>'value' + ); + EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'Failed to insert string value for field %', field_record->>'id'; + END; + END IF; + END IF; + EXCEPTION WHEN OTHERS THEN + -- Log the error and continue processing other fields + RAISE WARNING 'Error processing field %', field_record->>'id'; + CONTINUE; + END; + END LOOP; +EXCEPTION WHEN OTHERS THEN + -- Log any unhandled errors and continue + RAISE WARNING 'Unhandled error in _process_routing_form_response_fields for response %', response_id; +END; +$$ LANGUAGE plpgsql; + +-- Updated trigger function that uses the shared core function +CREATE OR REPLACE FUNCTION handle_routing_form_response_fields() +RETURNS TRIGGER AS $$ +DECLARE + response_data jsonb; + form_id text; +BEGIN + -- For INSERT trigger on RoutingFormResponseDenormalized, we need to get the response data from App_RoutingForms_FormResponse + -- For UPDATE trigger on App_RoutingForms_FormResponse, we can use NEW.response directly + IF TG_TABLE_NAME = 'RoutingFormResponseDenormalized' THEN + SELECT response, "formId" INTO response_data, form_id + FROM "App_RoutingForms_FormResponse" + WHERE id = NEW.id; + ELSE + response_data := NEW.response::jsonb; + form_id := NEW."formId"; + END IF; + + -- Call the shared core function + PERFORM _process_routing_form_response_fields(NEW.id, response_data, form_id); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- New function to reprocess routing form response fields for a specific response ID +CREATE OR REPLACE FUNCTION reprocess_routing_form_response_fields(response_id integer) +RETURNS void AS $$ +DECLARE + response_data jsonb; + form_id text; +BEGIN + -- Get the response data and form ID from App_RoutingForms_FormResponse + SELECT response, "formId" INTO response_data, form_id + FROM "App_RoutingForms_FormResponse" + WHERE id = response_id; + + -- Check if the response exists + IF response_data IS NULL THEN + RAISE EXCEPTION 'Response with id % not found', response_id; + END IF; + + -- Call the shared core function + PERFORM _process_routing_form_response_fields(response_id, response_data, form_id); +END; +$$ LANGUAGE plpgsql;