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 ; 

