SET PATH *LIBL ; CREATE OR REPLACE FUNCTION RESTAPI.SFP_GET_OEE_DETAILS ( IN_PARM CLOB(2147483647) ) RETURNS CLOB(2147483647) LANGUAGE SQL SPECIFIC RESTAPI.SFP_GET_OEE_DETAILS NOT DETERMINISTIC MODIFIES SQL DATA CALLED ON NULL INPUT SET OPTION ALWBLK = *ALLREAD , ALWCPYDTA = *OPTIMIZE , COMMIT = *NONE , DECRESULT = (31, 31, 00) , DYNDFTCOL = *NO , DYNUSRPRF = *USER , SRTSEQ = *HEX BEGIN DECLARE ENV VARCHAR ( 20 ) DEFAULT '' ; DECLARE LL VARCHAR ( 1024 ) DEFAULT '' ; -- Error handling variables DECLARE V_SQLSTATE CHAR ( 5 ) DEFAULT '00000' ; DECLARE V_SQLCODE INTEGER DEFAULT 0 ; DECLARE V_ERRMSG VARCHAR ( 500 ) DEFAULT '' ; DECLARE V_HTTP_STATUS INTEGER DEFAULT 200 ; DECLARE V_HAS_ERROR INTEGER DEFAULT 0 ; -- Continue handlers for error capture DECLARE CONTINUE HANDLER FOR SQLSTATE '22007' -- Invalid datetime BEGIN SET V_SQLSTATE = '22007' ; GET DIAGNOSTICS CONDITION 1 V_ERRMSG = MESSAGE_TEXT ; SET V_HTTP_STATUS = 400 ; SET V_HAS_ERROR = 1 ; END ; DECLARE CONTINUE HANDLER FOR SQLSTATE '22003' -- Numeric out of range BEGIN SET V_SQLSTATE = '22003' ; GET DIAGNOSTICS CONDITION 1 V_ERRMSG = MESSAGE_TEXT ; SET V_HTTP_STATUS = 400 ; SET V_HAS_ERROR = 1 ; END ; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' -- No data found BEGIN SET V_SQLSTATE = '02000' ; SET V_ERRMSG = 'No data found for the specified criteria' ; SET V_HTTP_STATUS = 404 ; SET V_HAS_ERROR = 1 ; END ; DECLARE CONTINUE HANDLER FOR SQLSTATE '42501' -- Not authorized BEGIN SET V_SQLSTATE = '42501' ; GET DIAGNOSTICS CONDITION 1 V_ERRMSG = MESSAGE_TEXT ; SET V_HTTP_STATUS = 403 ; SET V_HAS_ERROR = 1 ; END ; DECLARE CONTINUE HANDLER FOR SQLSTATE '42704' -- Object not found BEGIN SET V_SQLSTATE = '42704' ; GET DIAGNOSTICS CONDITION 1 V_ERRMSG = MESSAGE_TEXT ; SET V_HTTP_STATUS = 404 ; SET V_HAS_ERROR = 1 ; END ; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN GET DIAGNOSTICS CONDITION 1 V_SQLSTATE = RETURNED_SQLSTATE , V_SQLCODE = DB2_RETURNED_SQLCODE , V_ERRMSG = MESSAGE_TEXT ; SET V_HTTP_STATUS = 500 ; SET V_HAS_ERROR = 1 ; END ; WHILE CISTOOLS . JSON_TYPE ( IN_PARM ) IN ( 'object' , 'array' , 'string' ) DOSET IN_PARM = CASE CISTOOLS . JSON_TYPE ( IN_PARM ) WHEN 'object' THEN JSON_VALUE ( IN_PARM , '$.env' ) WHEN 'array' THEN JSON_VALUE ( IN_PARM , '$[0]' ) WHEN 'string' THEN REPLACE ( IN_PARM , '"' , '' ) ELSE TRIM ( IN_PARM ) END ; END WHILE ; SET ENV = IN_PARM ; SET LL = CISTOOLS . SET_LIBRARY_LIST ( ENV ) ; -- Validate environment was set IF ENV = '' OR ENV IS NULL THEN RETURN JSON_OBJECT ( 'error' : JSON_OBJECT ( 'http_status' : 400 , 'sqlstate' : '22001' , 'message' : 'Environment parameter is required' ) ) ; END IF ; -- Check for errors from library list setup IF V_HAS_ERROR = 1 THEN RETURN JSON_OBJECT ( 'error' : JSON_OBJECT ( 'http_status' : V_HTTP_STATUS , 'sqlstate' : V_SQLSTATE , 'message' : V_ERRMSG ) ) ; END IF ; RETURN WITH BOUNDS AS ( SELECT CURRENT_DATE AS FOR_DATE , 1 AS WEEK_END_DOW , 2 AS WEEK_START_DOW , ZONED ( CURRENT_DATE ) AS YYYYMMDD_FOR , ZONED ( LAST_DAY ( ADD_MONTHS ( CURRENT_DATE , - 1 ) ) + 1 DAY ) AS YYYYMMDD_MONTH_FROM , ZONED ( LAST_DAY ( CURRENT_DATE ) ) AS YYYYMMDD_MONTH_THRU , DAYOFWEEK ( CURRENT_DATE ) AS DOW_FOR FROM SYSIBM . SYSDUMMY1 ) , WEEK_CALC AS ( SELECT B . * , MOD ( B . DOW_FOR - B . WEEK_START_DOW + 7 , 7 ) AS DELTA_DAYS FROM BOUNDS B ) , RANGES AS ( SELECT 'M' AS RANGE_TYPE , ( YYYYMMDD_MONTH_FROM - 19000000 ) AS FROM_CY , ( YYYYMMDD_MONTH_THRU - 19000000 ) AS THRU_CY FROM WEEK_CALC UNION ALL SELECT 'W' AS RANGE_TYPE , ( ( YEAR ( FOR_DATE - DELTA_DAYS DAYS ) * 10000 + MONTH ( FOR_DATE - DELTA_DAYS DAYS ) * 100 + DAY ( FOR_DATE - DELTA_DAYS DAYS ) ) - 19000000 ) AS FROM_CY , ( YYYYMMDD_FOR - 19000000 ) AS THRU_CY FROM WEEK_CALC UNION ALL SELECT 'D' AS RANGE_TYPE , ( YYYYMMDD_FOR - 19000000 ) AS FROM_CY , ( YYYYMMDD_FOR - 19000000 ) AS THRU_CY FROM WEEK_CALC ) , -- Work center level data WC_DATA AS ( SELECT R . RANGE_TYPE , TRIM ( P . KIGRP1 ) AS PLANT , TRIM ( P . KIGRP2 ) AS DEPT , TRIM ( P . KIWKCTR ) AS WC , SUM ( P . KIUP ) AS UP_SEC , SUM ( P . KISDWN ) AS SDWN_SEC , SUM ( P . KIUDWN ) AS UDWN_SEC , SUM ( P . KIGAP ) AS GAP_SEC , SUM ( P . KIUA ) AS UA_SEC , SUM ( P . KIRCVD ) AS GOOD_QTY , SUM ( P . KISCRP ) AS BAD_QTY , SUM ( P . KIEXPT ) AS TARGET_QTY , SUM ( P . KIARUN ) AS ARUN_SEC , SUM ( P . KIASET ) AS ASET_SEC , SUM ( P . KIASDW ) AS ASDW_SEC , SUM ( P . KIAUDW ) AS AUDW_SEC FROM RANGES R JOIN SFCPERI P ON P . KIDATE BETWEEN R . FROM_CY AND R . THRU_CY GROUP BY R . RANGE_TYPE , P . KIGRP1 , P . KIGRP2 , P . KIWKCTR ) , -- Calculate metrics at WC level WC_CALC AS ( SELECT RANGE_TYPE , PLANT , DEPT , WC , UP_SEC , SDWN_SEC , UDWN_SEC , GAP_SEC , UA_SEC , GOOD_QTY , BAD_QTY , TARGET_QTY , ARUN_SEC , ASET_SEC , ASDW_SEC , AUDW_SEC , INT ( ( COALESCE ( ( DECFLOAT ( UP_SEC - UDWN_SEC - GAP_SEC ) / NULLIF ( DECFLOAT ( UP_SEC ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( GOOD_QTY + BAD_QTY ) / NULLIF ( DECFLOAT ( TARGET_QTY ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( GOOD_QTY ) / NULLIF ( DECFLOAT ( GOOD_QTY + BAD_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) AS OEE_PCT , DEC ( DEC ( UP_SEC + SDWN_SEC + UDWN_SEC + GAP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS ELAPSED_HRS , DEC ( DEC ( ARUN_SEC + ASET_SEC + ASDW_SEC + AUDW_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS APPLIED_HRS , DEC ( DEC ( UP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS TIME_UP_HRS , DEC ( DEC ( SDWN_SEC + UDWN_SEC + GAP_SEC + UA_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS DOWN_TOTAL_HRS , DEC ( DEC ( SDWN_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS SCHED_DOWN_HRS , DEC ( DEC ( UDWN_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS UNSCHED_DOWN_HRS , DEC ( DEC ( GAP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS GAP_HRS , DEC ( DEC ( UA_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS UNAVAIL_HRS , DEC ( GOOD_QTY , 31 , 2 ) AS GOOD_PARTS , DEC ( BAD_QTY , 31 , 2 ) AS BAD_PARTS , DEC ( TARGET_QTY , 31 , 2 ) AS TARGET_PARTS , INT ( ( COALESCE ( ( DECFLOAT ( UP_SEC - UDWN_SEC - GAP_SEC ) / NULLIF ( DECFLOAT ( UP_SEC ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) AS AVAILABILITY_PCT , INT ( ( COALESCE ( ( DECFLOAT ( GOOD_QTY + BAD_QTY ) / NULLIF ( DECFLOAT ( TARGET_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) AS PERFORMANCE_PCT , INT ( ( COALESCE ( ( DECFLOAT ( GOOD_QTY ) / NULLIF ( DECFLOAT ( GOOD_QTY + BAD_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) AS QUALITY_PCT , DEC ( DEC ( ARUN_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS RUN_HRS , DEC ( DEC ( ASET_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS SETUP_HRS , DEC ( DEC ( ASDW_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS APPL_SDWN_HRS , DEC ( DEC ( AUDW_SEC , 31 , 10 ) / 3600 , 13 , 2 ) AS APPL_UDWN_HRS FROM WC_DATA ) , -- Pre-aggregate totals at each level to avoid join multiplication DEPT_TOTALS AS ( SELECT RANGE_TYPE , PLANT , DEPT , SUM ( UP_SEC ) AS UP_SEC , SUM ( SDWN_SEC ) AS SDWN_SEC , SUM ( UDWN_SEC ) AS UDWN_SEC , SUM ( GAP_SEC ) AS GAP_SEC , SUM ( GOOD_QTY ) AS GOOD_QTY , SUM ( BAD_QTY ) AS BAD_QTY , SUM ( TARGET_QTY ) AS TARGET_QTY FROM WC_DATA GROUP BY RANGE_TYPE , PLANT , DEPT ) , PLANT_TOTALS AS ( SELECT RANGE_TYPE , PLANT , SUM ( UP_SEC ) AS UP_SEC , SUM ( SDWN_SEC ) AS SDWN_SEC , SUM ( UDWN_SEC ) AS UDWN_SEC , SUM ( GAP_SEC ) AS GAP_SEC , SUM ( GOOD_QTY ) AS GOOD_QTY , SUM ( BAD_QTY ) AS BAD_QTY , SUM ( TARGET_QTY ) AS TARGET_QTY FROM WC_DATA GROUP BY RANGE_TYPE , PLANT ) , GRAND_TOTALS AS ( SELECT RANGE_TYPE , SUM ( UP_SEC ) AS UP_SEC , SUM ( SDWN_SEC ) AS SDWN_SEC , SUM ( UDWN_SEC ) AS UDWN_SEC , SUM ( GAP_SEC ) AS GAP_SEC , SUM ( GOOD_QTY ) AS GOOD_QTY , SUM ( BAD_QTY ) AS BAD_QTY , SUM ( TARGET_QTY ) AS TARGET_QTY FROM WC_DATA GROUP BY RANGE_TYPE ) , -- Build workcenter JSON objects WC_JSON AS ( SELECT RANGE_TYPE , PLANT , DEPT , JSON_OBJECT ( 'workcenterName' : WC , 'oeePercentage' : OEE_PCT , 'elapsedHours' : ELAPSED_HRS , 'appliedHours' : APPLIED_HRS , 'timeUpHours' : TIME_UP_HRS , 'totalDownHours' : DOWN_TOTAL_HRS , 'scheduledDownHours' : SCHED_DOWN_HRS , 'unscheduledDownHours' : UNSCHED_DOWN_HRS , 'gapHours' : GAP_HRS , 'unavailableHours' : UNAVAIL_HRS , 'goodParts' : GOOD_PARTS , 'badParts' : BAD_PARTS , 'targetParts' : TARGET_PARTS , 'availabilityPercentage' : AVAILABILITY_PCT , 'performancePercentage' : PERFORMANCE_PCT , 'qualityPercentage' : QUALITY_PCT , 'runHours' : RUN_HRS , 'setupHours' : SETUP_HRS , 'appliedScheduledDownHours' : APPL_SDWN_HRS , 'appliedUnscheduledDownHours' : APPL_UDWN_HRS ) AS WC_OBJ FROM WC_CALC ) , -- Build dept JSON with pre-aggregated totals DEPT_JSON AS ( SELECT W . RANGE_TYPE , W . PLANT , W . DEPT , JSON_OBJECT ( 'departmentName' : W . DEPT , 'oeePercentage' : INT ( ( COALESCE ( ( DECFLOAT ( T . UP_SEC - T . UDWN_SEC - T . GAP_SEC ) / NULLIF ( DECFLOAT ( T . UP_SEC ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) / NULLIF ( DECFLOAT ( T . TARGET_QTY ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY ) / NULLIF ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) , 'elapsedHours' : DEC ( DEC ( T . UP_SEC + T . SDWN_SEC + T . UDWN_SEC + T . GAP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) , 'timeUpHours' : DEC ( DEC ( T . UP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) , 'goodParts' : DEC ( T . GOOD_QTY , 31 , 2 ) , 'targetParts' : DEC ( T . TARGET_QTY , 31 , 2 ) , 'workcenters' : JSON_ARRAYAGG ( W . WC_OBJ FORMAT JSON ORDER BY W . DEPT ) ) AS DEPT_OBJ FROM WC_JSON W JOIN DEPT_TOTALS T ON W . RANGE_TYPE = T . RANGE_TYPE AND W . PLANT = T . PLANT AND W . DEPT = T . DEPT GROUP BY W . RANGE_TYPE , W . PLANT , W . DEPT , T . UP_SEC , T . SDWN_SEC , T . UDWN_SEC , T . GAP_SEC , T . GOOD_QTY , T . BAD_QTY , T . TARGET_QTY ) , -- Build plant JSON with pre-aggregated totals PLANT_JSON AS ( SELECT D . RANGE_TYPE , D . PLANT , JSON_OBJECT ( 'plantName' : D . PLANT , 'oeePercentage' : INT ( ( COALESCE ( ( DECFLOAT ( T . UP_SEC - T . UDWN_SEC - T . GAP_SEC ) / NULLIF ( DECFLOAT ( T . UP_SEC ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) / NULLIF ( DECFLOAT ( T . TARGET_QTY ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY ) / NULLIF ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) , 'elapsedHours' : DEC ( DEC ( T . UP_SEC + T . SDWN_SEC + T . UDWN_SEC + T . GAP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) , 'timeUpHours' : DEC ( DEC ( T . UP_SEC , 31 , 10 ) / 3600 , 13 , 2 ) , 'goodParts' : DEC ( T . GOOD_QTY , 31 , 2 ) , 'targetParts' : DEC ( T . TARGET_QTY , 31 , 2 ) , 'departments' : JSON_ARRAYAGG ( D . DEPT_OBJ FORMAT JSON ORDER BY D . DEPT ) ) AS PLANT_OBJ FROM DEPT_JSON D JOIN PLANT_TOTALS T ON D . RANGE_TYPE = T . RANGE_TYPE AND D . PLANT = T . PLANT GROUP BY D . RANGE_TYPE , D . PLANT , T . UP_SEC , T . SDWN_SEC , T . UDWN_SEC , T . GAP_SEC , T . GOOD_QTY , T . BAD_QTY , T . TARGET_QTY ) , GROUPED AS ( SELECT P . RANGE_TYPE , JSON_OBJECT ( 'rangeLabel' : CASE P . RANGE_TYPE WHEN 'M' THEN 'Monthly' WHEN 'W' THEN 'Weekly' WHEN 'D' THEN 'Daily' END , 'generatedAt' : TO_CHAR ( CURRENT_TIMESTAMP , 'YYYY-MM-DD HH24:MI:SS' ) , 'totalOeePercentage' : INT ( ( COALESCE ( ( DECFLOAT ( T . UP_SEC - T . UDWN_SEC - T . GAP_SEC ) / NULLIF ( DECFLOAT ( T . UP_SEC ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) / NULLIF ( DECFLOAT ( T . TARGET_QTY ) , 0 ) ) , 0 ) * COALESCE ( ( DECFLOAT ( T . GOOD_QTY ) / NULLIF ( DECFLOAT ( T . GOOD_QTY + T . BAD_QTY ) , 0 ) ) , 0 ) * 100 ) + 0.5 ) , 'plants' : JSON_ARRAYAGG ( P . PLANT_OBJ FORMAT JSON ORDER BY P . PLANT ) ) AS JSON_DATA FROM PLANT_JSON P JOIN GRAND_TOTALS T ON P . RANGE_TYPE = T . RANGE_TYPE GROUP BY P . RANGE_TYPE , T . UP_SEC , T . SDWN_SEC , T . UDWN_SEC , T . GAP_SEC , T . GOOD_QTY , T . BAD_QTY , T . TARGET_QTY ORDER BY CASE P . RANGE_TYPE WHEN 'D' THEN 3 WHEN 'W' THEN 2 ELSE 1 END ) SELECT JSON_OBJECT ( 'ranges' VALUE JSON_ARRAYAGG ( JSON_DATA FORMAT JSON ) ) FROM GROUPED ; END ; GRANT ALTER , EXECUTE ON SPECIFIC FUNCTION RESTAPI.SFP_GET_OEE_DETAILS TO AMAPICS WITH GRANT OPTION ; GRANT EXECUTE ON SPECIFIC FUNCTION RESTAPI.SFP_GET_OEE_DETAILS TO PUBLIC ;