Compile function Report_Services(@Service, @Params)
/***********************************************************************************************************************

    Name        :   Report_Services

    Description :   Handler program for all Report services.

    Notes       :   Application errors should be logged using the Error Services module. There are a few methodological
                    assumptions built into way errors are managed which are important to understand in order to properly
                    work with Error Services:

                    - The term 'top' refers to the originating procedure of a call stack and the term 'bottom' refers to
                      the last routine (or the current routine) within a call stack. Within the OpenInsight Debugger
                      this will appear backwards since the originating procedure always appears at the bottom of the
                      list and the current routine appears at the top of the list. We are using this orientation because
                      it is common to refer to the process of calling other procedures as 'drilling down'.

                    - The reason for defining the orientation of the call stack is because Error_Services allows for
                      multiple error conditions to be appended to an original error. In most cases this will happen when
                      a procedure at the bottom of the stack generates an error condition and then returns to its
                      calling procedure. This higher level procedure can optionally add more information relevant to
                      itself. This continues as the call stack 'bubbles' its way back to the top to where the
                      originating procedure is waiting.

                    - Native OpenInsight commands that handle errors (e.g., Set_Status, Set_FSError, Set_EventStatus)
                      preserve their error state until explicitly cleared. This can hinder the normal execution of code
                      since subsequent procedures (usually SSPs) will fail if a pre-existing error condition exists.
                      Our philosophy is that error conditions should automatically be cleared before a new procedure
                      is executed to avoid this problem. However, the nature of Basic+ does not make this easy to
                      automate for any given stored procedure. Therefore, if a stored procedure wants to conform to our
                      philosophy then it should include a call into the 'Clear' service request at the top of the
                      program. Alternatively this can be done through a common insert (see SERVICE_SETUP for example.)

                    - Service modules will use the SERVICE_SETUP insert and therefore automatically clear out any
                      error conditions that were set before.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    Metadata    :

    History     :   (Date, Initials, Notes)
        10/30/20    djs     Original programmer.
														

***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler

$Insert SERVICE_SETUP
$Insert LOGICAL
$Insert OIPRINT_EQUATES
$Insert SCHED_DET_EQUATES
$Insert WO_LOG_EQUATES
$Insert APPCOLORS
$Insert MSG_EQUATES
$Insert SCHEDULE_EQU
$Insert LOCATION_EQUATES
$Insert RLIST_EQUATES
$Insert DICT_EQUATES
$Insert DAILY_METRICS_EQUATES
$Insert RDS_EQUATES
$Insert DAILY_PERFORMANCE_EQUATES
$Insert DAILY_PERFORMANCE_REPORT_EQUATES
$Insert SCHED_DET_NG_EQUATES
$Insert MAKEUP_WAFERS_EQUATES
$Insert REACTOR_EQUATES
$Insert WAFER_COUNTER_EQUATES
$Insert REACTOR_DAILY_UPTIME_EQUATES

// Reduce modes (for Select statement)
Equ NEW_EXIST$ To 0 
Equ NEXT_CUR$  To 1
Equ ADD_EXIST$ To 2  

Declare subroutine Utility, ErrMsg, Set_Status, Set_Printer, RList, SRP_Stopwatch, Btree.Extract, Error_Services, V119
Declare subroutine Push.Select, Pop.Select, Database_Services, SRP_JSON, Logging_Services, Reduce, FSMsg
Declare subroutine Report_Services, Make.List
Declare function   Set_Printer, Get_Printer, Msg, Get_Status, Printer_Select, obj_Install, Dialog_Box, obj_Location
Declare function   Schedule_Services, Error_Services, Location_Services, SRP_Array, Signature_Services, obj_RDS_Test
Declare function   Epi_Part_Services, SRP_Math, Database_Services, SRP_JSON, Reactor_Services, SRP_Datetime
Declare function   SRP_Array, Environment_Services, Logging_Services, Datetime, Select_Into, Wafer_Counter_Services
Declare function    Lot_Services, Test_Run_Services

Equ TAB   TO \09\

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\RDS'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName  = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' WAFERS_IN.csv'
Headers      = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'WAFERS_IN'
objWafersLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LogPath       = Environment_Services('GetApplicationRootPath') : '\LogFiles\Report_Services'
LogFileName   = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' CalcDailyPerformanceData.csv'
Headers       = 'Logging DTM' : @FM : 'Service Step' : @FM : 'Service Notes'
objReportsLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ',', Headers, '', False$, False$)

LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

GoToService

Return Response or ""

//-----------------------------------------------------------------------------
// SERVICES
//-----------------------------------------------------------------------------

Service GetMakeupInventoryReportJSON()
	
	clearselect
    Statement1 = 'LIST MAKEUP_WAFERS WO_NO CASS_NO CUST_NO CUST_PART_NO EPI_PART_NO PROD_ORD_NO PROD_VER_NO PS_NO RDS_NO REACT_TYPE SAP_BATCH_NO UNLOAD_DTM WAFER_SIZE WM_OUT_NO WFR_QTY EXPIRED BY WAFER_SIZE BY PS_NO'
    MUInv = Select_Into(Statement1, 'EDT')
    
    If Error_Services('NoError') then
        MUInvJSON = ''
        objJSON   = ''
        If SRP_JSON(objJSON, 'New', 'Object') then
        	objReactArray = ''
            If SRP_JSON(objReactArray, 'New', 'Array') then
                For each Row in MUInv using @FM setting fPos
                	objRow = ''
					If SRP_JSON(objRow, 'New', 'Object') then
						SRP_JSON(objRow, 'SetValue', 'workOrderNo', Row<0, 2>, 'number')
						SRP_JSON(objRow, 'SetValue', 'cassNo', Row<0, 3>, 'number')
						SRP_JSON(objRow, 'SetValue', 'custNo', Row<0, 4>, 'number')
						SRP_JSON(objRow, 'SetValue', 'custPartNo', Row<0, 5>)
						SRP_JSON(objRow, 'SetValue', 'epiPartNo', Row<0, 6>)
						SRP_JSON(objRow, 'SetValue', 'prodOrderNo', Row<0, 7>)
						SRP_JSON(objRow, 'SetValue', 'prodVerNo', Row<0, 8>, 'number')
						SRP_JSON(objRow, 'SetValue', 'prodSpecNo', Row<0, 9>, 'number')
						SRP_JSON(objRow, 'SetValue', 'rdsNo', Row<0, 10>, 'number')
						SRP_JSON(objRow, 'SetValue', 'reactType', Row<0, 11>)
						SRP_JSON(objRow, 'SetValue', 'sapBatchNo', Row<0, 12>)
						SRP_JSON(objRow, 'SetValue', 'unloadDtm', Row<0, 13>)
						SRP_JSON(objRow, 'SetValue', 'wfrSize', Row<0, 14>)
						SRP_JSON(objRow, 'SetValue', 'wmOutNo', Row<0, 15>)
						SRP_JSON(objRow, 'SetValue', 'wfrQty', Row<0, 16>, 'number')
						
						ReactType = Xlate('WO_MAT', Row<0, 2>:'*':Row<0, 3>, 'REACTOR_TYPE', 'X')
						If ReactType NE 'EPP' then
							LotID = Row<0, 10>
						end else
							LotID = Row<0, 2>:'*1*':Row<0, 3>
						end
						WcRec = Wafer_Counter_Services('GetLastScan', LotID, 'MU')
						WcQty = WcRec<WAFER_COUNTER.SCAN_QTY$>
						WcDtm = WcRec<WAFER_COUNTER.SCAN_DTM$>
						
						SRP_JSON(objRow, 'SetValue', 'muWaferCounterQty', WcQty)
						SRP_JSON(objRow, 'SetValue', 'muWaferCounterDtm', WcDtm)
						
						SRP_JSON(objRow, 'SetValue', 'expired', IConv(Row<0, 17>, 'B'), 'Boolean')
						
						SRP_JSON(objReactArray, 'Add', objRow)
						SRP_JSON(objRow, 'Release')
					end
                Next Row
                SRP_JSON(objJSON, 'Set', 'MUInvReport', objReactArray)
                SRP_JSON(objReactArray, 'Release')
            end
            Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
            SRP_JSON(objJSON, 'Release')
        end
        
    end
    
end service


Service GenerateEPPMetReport(DateFrom, DateTo, EpiPartNo, ExportDir)
    
    ReportData = ''
    ErrorMsg   = ''
    If ( (DateFrom NE '') and (EpiPartNo NE '') and (ExportDir NE '') ) then
        If DateTo EQ '' then
            DateTo = Date()
        end else
            If Not(Num(DateTo)) then DateTo = IConv(DateTo, 'D')
        end
        If Not(Num(DateFrom)) then DateFrom = IConv(DateFrom, 'D')
        // Select WM_OUT keys
        WMOList = ''
        Query   = 'SELECT WM_OUT WITH PART_NO EQ ':Quote(EpiPartNo)
        GoSub ClearCursors
        Set_Status(0)
        RList(Query, TARGET_ACTIVELIST$, '', '', '')
        If Get_Status(ErrCode) then
            ErrorMsg = 'Error in ':Service:' service. RList failed! Error code: ':ErrCode:'.'
        end
        If ErrorMsg EQ '' then
            Def              = ""
            Def<MCAPTION$>   = "Selecting WM_OUT records..."
            Def<MTYPE$>      = "GC"
            Def<MEXTENT$>    = @RecCount
            Def<MTEXTWIDTH$> = 600
            MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
            Counter          = 0
            EOF              = False$
            Loop
                Counter += 1
                Running     = Msg(@WINDOW, MsgUp, Counter, MSGINSTUPDATE$) ;* Update message
                ReadNext WMOKey else EOF = True$
            Until EOF
                WMOList<-1> = WMOKey
            Repeat
            Msg(@WINDOW,MsgUp)
            
            // Filter on RDS {DATE_OUT}
            RDSList       = ''
            FilterWMOList = ''
            NumWMO        = DCount(WMOList, @FM)
            
            Def              = ""
            Def<MCAPTION$>   = "Filtering on dates..."
            Def<MTYPE$>      = "GC"
            Def<MEXTENT$>    = NumWMO
            Def<MTEXTWIDTH$> = 600
            MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
            Counter          = 0
            
            For WMOIndex = 1 to NumWMO
                Running     = Msg(@WINDOW, MsgUp, WMOIndex, MSGINSTUPDATE$) ;* Update message
                Done        = False$
                WMOKey      = WMOList<WMOIndex>
                ThisRDSList = Xlate('WM_OUT', WMOKey, 'RDS', 'X')
                ThisRDSList = SRP_Array('Clean', ThisRDSList, 'TrimAndMakeUnique', @VM)
                For each RDSNo in ThisRDSList using @VM
                    DateOut = Xlate('RDS', RDSNo, 'DATE_OUT', 'X')
                    If ( (DateOut GE DateFrom) and (DateOut LE DateTo) ) then
                        FilterWMOList<-1> = WMOKey
                        Done              = True$
                    end
                Until Done
                Next RDSNo
            Next WMOIndex
            Msg(@WINDOW,MsgUp)
            
            // Build the report
            ReportData    = ''
            Headers       = Database_Services('ReadDataRow', 'SYSLISTS', 'EPP_METROLOGY_HEADERS')
            Columns       = Xlate('EXPORTS', 'EPIPRO_METOROLGY', 'COLUMNS', 'X')
            Swap @FM with @VM in Headers
            ReportData<1> = Headers
            
            RDSFields  = Database_Services('ReadDataRow', 'DICT.RDS', '%FIELDS%')
            RDSColumns = RDSFields<DICT_DISPLAY$>
            RDSConvs   = RDSFields<DICT_GENERIC_TYPE$>
            NumWMO     = DCount(FilterWMOList, @FM)

            Def              = ""
            Def<MCAPTION$>   = "Building report..."
            Def<MTYPE$>      = "GC"
            Def<MEXTENT$>    = NumWMO
            Def<MTEXTWIDTH$> = 600
            MsgUp            = Msg(@WINDOW, Def) ;* Start gas guage message
            
            If NumWMO GT 0 then
                For each WMOKey in FilterWMOList using @FM setting fPos
                    Running     = Msg(@WINDOW, MsgUp, fPos, MSGINSTUPDATE$) ;* Update message
                    RowIndex    = fPos + 1
                    ThisRDSList = Xlate('WM_OUT', WMOKey, 'RDS', 'X')
                    Zones       = Xlate('WM_OUT', WMOKey, 'ZONE', 'X')
                    Pockets     = Xlate('WM_OUT', WMOKey, 'POCKET', 'X')
                    SAPBatchNo  = Xlate('WM_OUT', WMOKey, 'SAP_BATCH_NO', 'X')
                    InCassNos   = Xlate('WM_OUT', WMOKey, 'IN_CASS_NO', 'X')
                    WONo        = Field(WMOKey, '*', 1)
                    PrevRDSNo   = ''
                    For each RDSNo in ThisRDSList using @VM setting SlotNo
                        If RDSNo NE '' then
                            If RDSNo NE PrevRDSNo then Database_Services('ActivateRecord', 'RDS', RDSNo)
                            ReportRow = ''
                            For each Column in Columns using @VM setting cPos
                                Locate Column in RDSColumns using @VM setting ConvPos then
                                    Conv = RDSConvs<0, ConvPos>
                                    Begin Case
                                        Case Column EQ 'WO'
                                            ReportRow<0, cPos> = Field(WMOKey, '*', 1)
                                        Case Column EQ 'SEQ'
                                            ReportRow<0, cPos> = RDSNo
                                        Case Column EQ 'ZONE'
                                            ReportRow<0, cPos> = Zones<0, SlotNo>
                                        Case Column EQ 'POCKET'
                                            ReportRow<0, cPos> = Pockets<0, SlotNo>
                                        Case Column EQ 'WM_OUT_KEY'
                                            ReportRow<0, cPos> = WMOKey
                                        Case Column EQ 'OUT_CASS_NO'
                                            ReportRow<0, cPos> = Field(WMOKey, '*', 3)
                                        Case Column EQ 'OUT_SLOT_NO'
                                            ReportRow<0, cPos> = SlotNo
                                        Case Column EQ 'SAP_BATCH_NO'
                                            ReportRow<0, cPos> = SAPBatchNo
                                        Case Column EQ 'LOT_NUM'
                                            ReportRow<0, cPos> = Xlate('WO_MAT', WONo:'*':InCassNos<0, SlotNo>, 'LOT_NO', 'X')
                                        Case Otherwise$
                                            If Conv EQ '' then
                                                ReportRow<0, cPos> = Calculate(Column)
                                            end else
                                                ReportRow<0, cPos> = OConv(Calculate(Column), Conv)
                                            end
                                    End Case
                                end
                            Next Column
                            PrevRDSNo      = RDSNo
                            ReportData<-1> = ReportRow
                        end
                    Next RDSNo
                    
                Next WMOKey
            end
            Msg(@WINDOW,MsgUp)
            Swap @VM with ',' in ReportData
            Swap @FM with CRLF$ in ReportData
            Set_Status(0)
            OSWrite ReportData on ExportDir
            ErrCode = ''
            If Get_Status(ErrCode) then
                ErrorMsg = 'Error in ':Service:' service. OSWrite failed! Error code: ':ErrCode:'.'
            end
        end
    end else
        ErrorMsg = 'Error in ':Service:' service. Null DateFrom or EpiPartNo passed in!'
    end
    If ErrorMsg EQ '' then
        Response = ReportData
    end else
        Error_Services('Add', ErrorMsg)
    end
    
end service


Service GetMaterialTrackReport(RptColumns, LocationFilter)
    
    // All Possible Report Columns
    AllRptColumns        = ''
    AllRptColumns<1, 1>  = 'React No'
    AllRptColumns<1, 2>  = 'React Type'
    AllRptColumns<1, 3>  = 'WO No'
    AllRptColumns<1, 4>  = 'SAP Prod No'
    AllRptColumns<1, 5>  = 'Sub Part No'
    AllRptColumns<1, 6>  = 'Epi Part No'
    AllRptColumns<1, 7>  = 'WO Qty'
    AllRptColumns<1, 8>  = 'RX Qty'
    AllRptColumns<1, 9>  = 'UnRel Qty'
    AllRptColumns<1, 10> = 'Kit Location'
    AllRptColumns<1, 11> = 'Kit Qty'
    AllRptColumns<1, 12> = '+/-'
    AllRptColumns<1, 13> = 'Kit RO'
    AllRptColumns<1, 14> = 'PTI RO'
    AllRptColumns<1, 15> = 'Load'
    AllRptColumns<1, 16> = 'Comments'
    
    If Index(RptColumns, 'Comments', 1) then
        AllColFmt        = ''
        AllColFmt<1, 1>  = '+^720'
        AllColFmt<1, 2>  = '+<720'
        AllColFmt<1, 3>  = '+^1000'
        AllColFmt<1, 4>  = '+^1100'
        AllColFmt<1, 5>  = '+^1300'
        AllColFmt<1, 6>  = '+^1200'
        AllColFmt<1, 7>  = '+>720'
        AllColFmt<1, 8>  = '+>720'
        AllColFmt<1, 9>  = '+>720'
        AllColFmt<1, 10> = '+^1260'
        AllColFmt<1, 11> = '+^720'
        AllColFmt<1, 12> = '+^500'
        AllColFmt<1, 13> = '+^1300'
        AllColFmt<1, 14> = '+^1300'
        AllColFmt<1, 15> = '+^700'
        AllColFmt<1, 16> = '+^1300'    
    end else
        AllColFmt        = ''
        AllColFmt<1, 1>  = '+^720'
        AllColFmt<1, 2>  = '+<720'
        AllColFmt<1, 3>  = '+^1000'
        AllColFmt<1, 4>  = '+^1100'
        AllColFmt<1, 5>  = '+^1300'
        AllColFmt<1, 6>  = '+^1200'
        AllColFmt<1, 7>  = '+>720'
        AllColFmt<1, 8>  = '+>720'
        AllColFmt<1, 9>  = '+>720'
        AllColFmt<1, 10> = '+^1260'
        AllColFmt<1, 11> = '+^720'
        AllColFmt<1, 12> = '+^500'
        AllColFmt<1, 13> = '+^1800'
        AllColFmt<1, 14> = '+^1800'
        AllColFmt<1, 15> = '+^1100'
        AllColFmt<1, 16> = ''
    end
    
    ErrorTitle  =  'Error in service ':Service
    ErrCode		= ''
    ErrorMsg	= ''
    
    OPEN 'SCHED_DET' TO SchedDetTable then
        OPEN 'DICT.SCHED_DET' TO @DICT then
            Today   = OCONV(Date(),'D4/')
            EndDate	= OCONV(Date()+13,'D4/')
            
            ReactList 		= ''
            WOList			= ''
            SchedDetKeys	= ''
            PrevReactNo     = ''
            PrevWO          = ''
            
            SelectSent  = 'SELECT SCHED_DET WITH SCHED_DT GE ':QUOTE(Today):' BY REACT_NO'
            
            *            Push.Select(f1, f2, f3, f4)
            RList(SelectSent,TARGET_ACTIVELIST$,'','','')
            *            Pop.Select(f1, f2, f3, f4)
            IF Get_Status(errCode) THEN
                ErrMsg(errCode)
                RETURN
            END
            
            Done = 0
            @ID  = ''
            LOOP
                PrevSchedDetKey = @ID
                READNEXT @ID ELSE Done = 1
            UNTIL Done
                ReactNo = @ID[1,'*']
                SchedDt	= @ID[COL2()+1,'*']
                SeqNo	= @ID[COL2()+1,'*']
                
                READ SchedDetRec FROM SchedDetTable,@ID THEN
                    
                    WONo = SchedDetRec<SCHED_DET_WO_NO$>
                    
                    LOCATE ReactNo IN ReactList BY 'AR' USING @FM  SETTING Pos ELSE
                        ReactList = INSERT(ReactList,Pos,0,0,ReactNo)
                    END
                    
                    PrevReactNo     = Field(PrevSchedDetKey, '*', 1)
                    PrevSchedDt     = Field(PrevSchedDetKey, '*', 2)
                    
                    LOCATE WONo IN WOList<Pos> USING @VM SETTING WPos ELSE	
                        WOList 			= INSERT(WOList,Pos,-1,0,WONo)
                        // Patch added on 10/24/18 to resolve scheduling conflicts on the same day for
                        // the material track report. - djs                         
                        If (PrevReactNo EQ ReactNo) and (PrevSchedDt EQ SchedDt) then
                            PrevSeqNo   = Field(PrevSchedDetKey, '*', 3)
                            NumWO       = DCount(WOList<Pos>, @VM)
                            PrevWONo    = WOList<Pos, NumWO - 1>
                            SchedEvents = Schedule_Services('GetScheduleEventSummary', ReactNo, WONo, SchedDt, SeqNo, True$)
                            StartDt     = IConv(SchedEvents<SCHEDULE_ENTRY_DATE$>, 'D')
                            PrevSchedEvents = Schedule_Services('GetScheduleEventSummary', ReactNo, PrevWONo, PrevSchedDt, PrevSeqNo, True$)
                            PrevStartDt = IConv(PrevSchedEvents<SCHEDULE_ENTRY_DATE$>, 'D')
                            If StartDt GT PrevStartDt then
                                // This is the common case
                                SchedDetKeys = INSERT(SchedDetKeys,-1,0,0,@ID)
                            end else
                                // Scheduling irregularity - Insert this key in the second to last position.
                                NumKeys      = DCount(SchedDetKeys, @FM)
                                InsertPos    = NumKeys
                                SchedDetKeys = INSERT(SchedDetKeys,InsertPos,0,0,@ID)
                            end
                        end else
                            SchedDetKeys = INSERT(SchedDetKeys,-1,0,0,@ID)
                        end
                    END
                END
                
            REPEAT
            
            CALL Make.List(0,SchedDetKeys,SchedDetTable,@DICT)
            
            Header		= "'D'":@VM:obj_Install('Get_Prop','CompTitle'):' Material Tracking by Reactor':@VM:"Page  'P'"
            MinDemand	= 0
            Set_Status(0)
            
            * Start of printing process
            FileName = 'Print Material Demand'
            Title    = 'Printing Material Demand'
            
            TopMargin		= 1.0
            BottomMargin	= 0.75
            LeftMargin		= 0.25
            RightMargin		= 0.25
            
            Margins = LeftMargin:@FM:TopMargin:@FM:RightMargin:@FM:BottomMargin
            
            PageSetup       = '1'		;* LandScape
            PrintSetup      = '2'		;* Preview Normal
            PrintSetup<1,2> = '0'	;* All buttons
            PrintSetup<1,5> = '1'	;* Page Range
            PrintSetup<1,6> = 7		;* full mouse and keyboard support
            
            PrintPath = ''
            stat      = Set_Printer('INIT',FileName,Title,Margins,PageSetup,PrintSetup,PrintPath)
            
            IF stat < 0 THEN
                ErrorMsg	= 'Stat = ' : Stat : ', PrintPath = ' : PrintPath
                GOTO OIPrintErr
            end
            DateRange = 'Effective ':OCONV(Date(),'D4')
            
            Header<-1> = "'T'"
            Header<-1> = ''																;* Blank line following heading
            
            font	= 'Arial'
            font<2>	= '10'
            font<4>	= '0'		;* Bold
            
            stat = Set_Printer('FONTHEADFOOT',font)	; IF stat < 0 THEN GOTO OIPrintErr
            stat = Set_Printer('HEADER',Header)		; IF stat < 0 THEN GOTO OIPrintErr
            
            Footer = " 'D' 'T'":@VM:@VM:"Page:  'P'"
            stat = Set_Printer('FOOTER',Footer)		; IF stat < 0 THEN GOTO OIPrintErr
            
            @RECCOUNT	= 0
            FirstPass	= 1
            LastRecord	= 0
            FirstLine 	= 1
            fontSpacing	= 100
            
            * Make Column Heading
            ColHead = ''
            ColFmt  = ''
            
            For each Column in RptColumns using @VM setting rcPos
                Locate Column in AllRptColumns using @VM setting dataPos then
                    ColHead<0, -1> = Column
                    ColFmt<0, -1>  = AllColFmt<0, dataPos>
                end
            Next Column
            
            * Zero Accumulators For Each Break
            
            Prev.ReactNo       = ''
            Last.ReactNo.Break = 1
            
            GoTo ReadRecord
            
        end else
            ErrorMsg = 'Unable to open "SCHED_DET" table.'
            ErrMsg(ErrorTitle:@SVM:ErrorMsg)
        end
    end else
        ErrorMsg  =  'Unable to open "SCHED_DET" table.'
        ErrMsg(ErrorTitle:@SVM:ErrorMsg)       
    end
    
End Service


Service UpdateDailyMetrics(ReportDate)

    If ReportDate NE '' then
        If Not(Num(ReportDate)) then ReportDate = IConv(ReportDate, 'D')
        // Get daily target
        CurrDate     = Date()
        DailyTarget  = 0
        Query        = 'SELECT FISCAL_QTR WITH FISCAL_YR EQ 2022 AND WITH START_DT LT ':Quote(OConv(ReportDate + 1, 'D4/'))
        Query       := ' AND WITH END_DT GT ':Quote(OConv(ReportDate - 1, 'D4/'))
        RList(Query, TARGET_ACTIVELIST$, '', '', '')
        If Not(Get_Status(ErrCode)) then
            ReadNext FiscalQtrKey then
                WorkingDays = Xlate('FISCAL_QTR', FiscalQtrKey, 'PLAN_WORKING_DAYS', 'X')
                Targets     = Xlate('FISCAL_QTR', FiscalQtrKey, 'THRU_TARGET', 'X')
                Qtys        = Xlate('FISCAL_QTR', FiscalQtrKey, 'THRU_QTY', 'X')
                Locate 'Yield_Outs' in Targets using @VM setting vPos then
                    QtrTarget   = Qtys<0, vPos>
                    DailyTarget = QtrTarget / WorkingDays
                    DailyTarget = OConv(IConv(DailyTarget, 'MD0'), 'MD0')
                end
            end
        end
        
        GoSub ClearCursors
        
        // Calculate the metrics for the ReportDate
        OPEN 'DICT.RDS' TO @DICT then
            OPEN 'RDS' TO RDSTable then
                
                TotalThickness24 = 0
                TotalYield24     = 0
                
                
                Dates            = ''
                CurrTime         = Time()
                If CurrTime LT 3600 then
                    // 3600 is 1:00 am -> update yesterday's metrics as well
                    Dates<-1> = ReportDate - 1
                end
                Dates<-1> = ReportDate
                For each Date in Dates using @FM
                    
                    TotalThickness  = 0
                    WafersOutTotal  = 0
                    YieldTotal      = 0
                    ScrapTotal      = 0
                    ProdTestTotal   = 0
                    ProjectedYield  = 0
                    YieldPercent    = '0.00'
                    ThickWaferRatio = 0
                    
                    SearchString   = 'DATE_OUT':@VM:OConv(Date, 'D4/')
                    RDSKeys        = ''
                    Option         = ''
                    Flag           = ''
                    Set_Status(0)
                    Btree.Extract(SearchString, 'RDS', @DICT, RDSKeys, Option, Flag)
                    IF Not(Get_Status(errCode)) then
                        IF RDSKeys NE '' THEN
                            RDSCnt  = COUNT(RDSKeys,@VM) + (RDSKeys NE '')
                            LOOP
                                @ID = RDSKeys[1,@VM]
                                RDSKeys[1,COL2()] = ''
                                READ @RECORD FROM RDSTable, @ID THEN
                                    ReactorsOut     = {WAFERS_IN}
                                    If Not(Num(ReactorsOut)) then
                                        // Log the WafersIn value
                                        LogData     = ''
                                        LogData<1>  = LoggingDTM
                                        LogData<2>  = @ID
                                        LogData<3>  = ReactorsOut
                                        Logging_Services('AppendLog', objWafersLog, LogData, @RM, @FM)
                                        ReactorsOut = ReactorsOut<1, 1, 1>
                                    end
                                    
                                    CustScrap       = {CUST_TOT_REJ}
                                    IFXEpiScrap     = {LSL_TOT_REJ}
                                    TestKeys        = XLATE('RDS_LAYER', @RECORD<RDS_RDS_LAYER_KEYS$>, 3, 'X')
                                    TWProd          = obj_RDS_Test('ProdTestCount',TestKeys)
                                    Reactor         = @RECORD<RDS_REACTOR$>
                                    ReactorType     = XLATE('REACTOR', Reactor, REACTOR_REACT_TYPE$, 'X')
                                    Scrap           = CustScrap + IFXEpiScrap
                                    Yield           = ReactorsOut - Scrap - TWProd
                                    WafersOutTotal += ReactorsOut
                                    YieldTotal     += Yield
                                    ScrapTotal     += Scrap
                                    ProdTestTotal  += TWProd
                                    ThickTarget     = OConv({THICK_TARGET_TOT}, 'MD3')
                                    TotalThickness += (ReactorsOut * ThickTarget)
                                end
                            UNTIL RDSKeys = ''
                            REPEAT
                            TotalThickness = TotalThickness[1, 'F.']
                        end
                    end

                    If Date EQ CurrDate then
                        // Calculate projected yield and projected thickness
                        Date             = Date()
                        CurrTime         = Time()
                        SearchString     = 'DATE_OUT':@VM:OConv((Date - 1), 'D4/'):@FM:'TIME_OUT':@VM:'>':CurrTime
                        YesterdayRDSKeys = ''
                        Option           = ''
                        Flag             = ''
                        Set_Status(0)
                        Btree.Extract(SearchString, 'RDS', @DICT, YesterdayRDSKeys, Option, Flag)
                        If Not(Get_Status(errCode)) then
                            SearchString = 'DATE_OUT':@VM:Date
                            TodayRDSKeys = ''
                            Option       = ''
                            Flag         = ''
                            Set_Status(0)
                            Btree.Extract(SearchString, 'RDS', @DICT, TodayRDSKeys, Option, Flag)
                            If Not(Get_Status(errCode)) then
                                TotalRDSKeys     = SRP_Array('Join', YesterdayRDSKeys, TodayRDSKeys, 'OR', @VM)
                                For each RDS in TotalRDSKeys using @VM
                                    Database_Services('ActivateRecord', 'RDS', RDS)
                                    ReactorsOut       = {WAFERS_IN}
                                    If Not(Num(ReactorsOut)) then
                                        // Log the WafersIn value
                                        LogData     = ''
                                        LogData<1>  = LoggingDTM
                                        LogData<2>  = RDS
                                        LogData<3>  = ReactorsOut
                                        Logging_Services('AppendLog', objWafersLog, LogData, @RM, @FM)
                                        ReactorsOut = ReactorsOut<1, 1, 1>
                                    end
                                    CustScrap         = {CUST_TOT_REJ}
                                    IFXEpiScrap       = {LSL_TOT_REJ}
                                    TestKeys          = XLATE('RDS_LAYER', @RECORD<RDS_RDS_LAYER_KEYS$>, 3, 'X')
                                    TWProd            = obj_RDS_Test('ProdTestCount',TestKeys)
                                    Reactor           = @RECORD<RDS_REACTOR$>
                                    ReactorType       = XLATE('REACTOR', Reactor, REACTOR_REACT_TYPE$, 'X')
                                    Scrap             = CustScrap + IFXEpiScrap
                                    Yield             = ReactorsOut - Scrap - TWProd
                                    TotalYield24     += Yield
                                    TargetThick       = OConv({THICK_TARGET_TOT}, 'MD3')
                                    TotalThickness24 += TargetThick * ReactorsOut
                                Next RDS
                                TotalThickness24 = TotalThickness24[1, 'F.']
                            end
                        end
                    end
                    
                    If WafersOutTotal GT 0 then
                        ThickWaferRatio = TotalThickness / WafersOutTotal
                        // Round to one decimal 
                        ThickWaferRatio = OConv(IConv(ThickWaferRatio, 'MD1'), 'MD1')
                        // Round to two decimals
                        YieldPercent    = (YieldTotal / WafersOutTotal) * 100
                        YieldPercent    = OConv(IConv(YieldPercent, 'MD2'), 'MD2')
                    end
                    
                    If WafersOutTotal   EQ 0 then WafersOutTotal   = ''
                    If YieldTotal       EQ 0 then YieldTotal       = ''
                    If TotalYield24     EQ 0 then TotalYield24     = ''
                    If TotalThickness   EQ 0 then TotalThickness   = ''
                    If TotalThickness24 EQ 0 then TotalThickness24 = ''
                    
                    MetricsRec                                       = ''
                    MetricsRec<DAILY_METRICS.WAFERS_OUT$>            = WafersOutTotal
                    MetricsRec<DAILY_METRICS.YIELD$>                 = YieldTotal
                    MetricsRec<DAILY_METRICS.PROJECTED$>             = TotalYield24
                    MetricsRec<DAILY_METRICS.SCRAP$>                 = ScrapTotal
                    MetricsRec<DAILY_METRICS.PROD_TEST$>             = ProdTestTotal
                    MetricsRec<DAILY_METRICS.TARGET$>                = DailyTarget
                    MetricsRec<DAILY_METRICS.TOTAL_THICKNESS$>       = TotalThickness
                    MetricsRec<DAILY_METRICS.PROJECTED_THICKNESS$>   = TotalThickness24
                    MetricsRec<DAILY_METRICS.THICKNESS_WAFER_RATIO$> = ThickWaferRatio
                    MetricsRec<DAILY_METRICS.YIELD_PERCENT$>         = YieldPercent
                    Database_Services('WriteDataRow', 'DAILY_METRICS', Date, MetricsRec)
                    
                Next Date
            end
        end
    end
    
end service


Service GetMetricsJSON(Date)
    
    // Get a week's worth of metrics and return them as JSON
    StartDate    = Date() - 6
    EndDate      = Date()
    
    JSON           = ''
    hMetricsObj    = ''
    hMetricsArray  = ''
    hDayMetricsObj = ''
    hStatusObj     = ''
    If SRP_JSON(hMetricsObj, 'NEW', 'OBJECT') then
        If SRP_JSON(hMetricsArray, 'NEW', 'ARRAY') then
            DateIndex = 1
            For Date = StartDate to EndDate
                MetricsRec = Database_Services('ReadDataRow', 'DAILY_METRICS', Date)
                // Create new date object
                SRP_JSON(hDayMetricsObj, 'NEW', 'OBJECT')
                // Add values
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'date', OConv(Date, 'D4/'))
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'target', MetricsRec<DAILY_METRICS.TARGET$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'reactorOut', MetricsRec<DAILY_METRICS.WAFERS_OUT$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'yieldOut', MetricsRec<DAILY_METRICS.YIELD$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'projected', MetricsRec<DAILY_METRICS.PROJECTED$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'yieldPercent', MetricsRec<DAILY_METRICS.YIELD_PERCENT$>, 'String')
                // Add object to metrics array
                SRP_JSON(hMetricsArray, 'ADD', hDayMetricsObj)
                SRP_JSON(hDayMetricsObj, 'RELEASE')
            Next Date
        end
        SRP_JSON(hMetricsObj, 'SET', 'dailyMetrics', hMetricsArray)
        JSON    = SRP_JSON(hMetricsObj, 'STRINGIFY', 'FAST')
        SRP_JSON(hMetricsArray, 'RELEASE')
        SRP_JSON(hStatusObj, 'RELEASE')
    end else
        Error_Services('Add', 'Error creating the metrics JSON object.')
    end

    Response = JSON
    
end service


Service GetThickMetricsJSON(Date)
    
    // Get a week's worth of metrics and return them as JSON
    StartDate    = Date() - 6
    EndDate      = Date()
    
    JSON           = ''
    hMetricsObj    = ''
    hMetricsArray  = ''
    hDayMetricsObj = ''
    If SRP_JSON(hMetricsObj, 'NEW', 'OBJECT') then
        If SRP_JSON(hMetricsArray, 'NEW', 'ARRAY') then
            DateIndex = 1
            For Date = StartDate to EndDate
                MetricsRec = Database_Services('ReadDataRow', 'DAILY_METRICS', Date)
                // Create new date object
                SRP_JSON(hDayMetricsObj, 'NEW', 'OBJECT')
                // Add values
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'date', OConv(Date, 'D4/'))
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'thickness', MetricsRec<DAILY_METRICS.TOTAL_THICKNESS$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'projectedThickness', MetricsRec<DAILY_METRICS.PROJECTED_THICKNESS$>)
                SRP_JSON(hDayMetricsObj, 'SETVALUE', 'thicknessWaferRatio', MetricsRec<DAILY_METRICS.THICKNESS_WAFER_RATIO$>)
                // Add object to metrics array
                SRP_JSON(hMetricsArray, 'ADD', hDayMetricsObj)
                SRP_JSON(hDayMetricsObj, 'RELEASE')
            Next Date
        end
        SRP_JSON(hMetricsObj, 'SET', 'dailyMetrics', hMetricsArray)
        JSON    = SRP_JSON(hMetricsObj, 'STRINGIFY', 'FAST')
        SRP_JSON(hMetricsArray, 'RELEASE')
        SRP_JSON(hStatusObj, 'RELEASE')
    end else
        Error_Services('Add', 'Error creating the metrics JSON object.')
    end

    Response = JSON
    
end service


Service GetMaterialTrackJSON()
    
    MatRep = Database_Services('ReadDataRow', 'SYSLISTS', 'MAT_REPORT')
    If Error_Services('NoError') then
        MatRepJSON = ''
        If SRP_JSON(objJSON, 'New', 'Object') then
            If SRP_JSON(objReactArray, 'New', 'Array') then
                For each Row in MatRep using @FM setting fPos
                    KitRunOrder  = Row<0, 13>
                    PTIRunOrder  = Row<0, 14>
                    LoadRunOrder = Row<0, 15>
                    If KitRunOrder NE '' or PTIRunOrder NE '' or LoadRunOrder NE '' then
                        If SRP_JSON(objRow, 'New', 'Object') then
                            SRP_JSON(objRow, 'SetValue', 'reactorNo', Row<0, 1>)
                            SRP_JSON(objRow, 'SetValue', 'reactorType', Row<0, 2>)
                            SRP_JSON(objRow, 'SetValue', 'workOrderNo', Row<0, 3>)
                            SRP_JSON(objRow, 'SetValue', 'sapProdNo', Row<0, 4>)
                            SRP_JSON(objRow, 'SetValue', 'subPartNo', Row<0, 5>)
                            SRP_JSON(objRow, 'SetValue', 'epiPartNo', Row<0, 6>)
                            WOQty        = Row<0, 7>
                            Swap ',' with '' in WOQty
                            SRP_JSON(objRow, 'SetValue', 'woQty', WOQty)
                            RxQty        = Row<0, 8>
                            Swap ',' with '' in RxQty
                            SRP_JSON(objRow, 'SetValue', 'rxQty', RxQty)
                            UnRelQty     = Row<0, 9>
                            Swap ',' with '' in UnRelQty
                            SRP_JSON(objRow, 'SetValue', 'unRelQty', UnRelQty)
                            SRP_JSON(objRow, 'SetValue', 'kitLoc', Row<0, 10>)
                            KitQty       = Row<0, 11>
                            Swap ',' with '' in KitQty
                            SRP_JSON(objRow, 'SetValue', 'kitQty', KitQty)
                            SRP_JSON(objRow, 'SetValue', 'cassDelta', Row<0, 12>)
                            Swap ',' with ', ' in KitRunOrder
                            SRP_JSON(objRow, 'SetValue', 'kitRunOrder', KitRunOrder)
                            Swap ',' with ', ' in PTIRunOrder
                            SRP_JSON(objRow, 'SetValue', 'ptiRunOrder', PTIRunOrder)
                            Swap ',' with ', ' in LoadRunOrder
                            SRP_JSON(objRow, 'SetValue', 'loadRunOrder', LoadRunOrder)
                            SRP_JSON(objRow, 'SetValue', 'comments', Row<0, 16>)
                            SRP_JSON(objReactArray, 'Add', objRow)
                            SRP_JSON(objRow, 'Release')
                        end
                    end
                Next Row
                SRP_JSON(objJSON, 'Set', 'matReport', objReactArray)
                SRP_JSON(objReactArray, 'Release')
            end
            Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
            SRP_JSON(objJSON, 'Release')
        end       
    end
    
end service


Service CalcDailyPerformanceData(DateOut)

    hSysLists  = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
	    LogData = ''
	    LogData<1> = LoggingDtm
        LogData<2> = '1'
        LogData<3> = 'Begin service.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
	    
        Response   = ''
        CurrDate   = Date()
        CurrDTM    = Datetime()
        ReportDate = IConv(DateOut, 'D')
        ReportData = ''
        
        // Clear out previous daily_performance metrics
        table_name = "DAILY_PERFORMANCE"
        flag       = ""
        done       = False$
        CursorVar  = ""

        For counter = 0 To 8
            ClearSelect counter
        Next counter

        sort_list     = "DATE_OUT"
        Reduce_Script = "WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
        mode          = NEXT_CUR$
        DeleteKeyList = ''
        LogData = ''
        LogData<1>    = LoggingDtm
        LogData<2> = '2'
        LogData<3> = 'Calling Reduce with query "':Reduce_Script:'" using next available cursor.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
        
        Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
        If flag then
            Select table_name By sort_list Using Cursorvar then
                Open table_name To file_var then
                    Loop
                        ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
                    Until done
                        DeleteKeyList<-1> = KeyID
                    Repeat
                End
            end
        End
        LogData = ''
        LogData<1> = LoggingDtm
        LogData<2> = '3'
        LogData<3> = 'Deleting selected records.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
        
        If DeleteKeyList NE '' then
            For each KeyID in DeleteKeyList using @FM
                Database_Services('DeleteDataRow', 'DAILY_PERFORMANCE', KeyID, True$, False$)
            Next KeyID
        end
        
        // Begin by building DAILY_PERFORMANCE data from RDS table. We will need to supplement this below
        // for work orders / parts that do not have any cassettes unloaded yet.
        Query      = 'SELECT RDS WITH DATE_OUT EQ ':Quote(OConv(ReportDate, 'D4/')): ' BY REACTOR'
        EOF        = False$
        ErrCode    = ''
        ReportData = ''
        RawData    = ''
        
        // Clear cursors for good measure
        For counter = 0 To 8
            ClearSelect counter
        Next counter
        LogData = ''
        LogData<1> = LoggingDtm
        LogData<2> = '4'
        LogData<3> = 'Selecting RDS records using query "':Query:'".'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
        
        RList(Query, TARGET_ACTIVELIST$, '', '', '')
        RowIndex   = 0
        If Not(Get_Status(ErrCode)) then
            Loop
                ReadNext KeyID else EOF = True$
            Until EOF
                RowIndex += 1
                Database_Services('ActivateRecord', 'RDS', KeyID)
                If Error_Services('NoError') then
                    Reactor                                   = {REACTOR}
                    PSN                                       = {PROD_SPEC_ID}
                    DateOut                                   = {DATE_OUT}
                    PartNo                                    = {PART_NUM}
                    WafersOut                                 = {WAFERS_IN}
                    DtmIn                                     = {DTM_IN}
                    DtmOut                                    = {DTM_OUT}
                    ReportKey                                 = DateOut:'*':Reactor:'*':PSN:'*':PartNo
                    ReportRow                                 = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE', ReportKey)
                    RepWafersOut                              = ReportRow<DAILY_PERFORMANCE.WAFERS_OUT$>
                    RepWafersOut                             += WafersOut
                    ReportRow<DAILY_PERFORMANCE.WAFERS_OUT$>  = RepWafersOut
                    Database_Services('WriteDataRow', 'DAILY_PERFORMANCE', ReportKey, ReportRow)
                end
            Repeat
        end
       
        Reactors = Reactor_Services('GetReactorNumbers')
        RowIndex = 0
        For each ReactorNo in Reactors using @FM setting RIndex
            ReactorAssign = Xlate('REACTOR', ReactorNo, 'REACT_ASSIGNMENT', 'X')
            // Only report reactors that are not out of service.
            If ReactorAssign NE 'O' then
                KeyList          = ''
                ReportRow        = ''
                ReportRow<0, 1>  = ReactorNo
                
                // Supplement DAILY_PERFORMANCE data for work orders / parts that do not have any cassettes unloaded yet.
                // Get the scheduled PSN, PartNo, and ReactType
                SchedWONos             = ''
                SchedPSNs              = ''
                SchedEpiParts          = ''
                SchedDetKeyIDs         = ''
                // The below EpiPro reactors are scheduled via a "sister" reactor (e.g. 44 for 46)
                If ( (ReactorNo EQ 42) or (ReactorNo EQ 46) or (ReactorNo EQ 50) or (ReactorNo EQ 54) ) then
                    QueryReactorNo = ReactorNo - 2
                end else
                    QueryReactorNo = ReactorNo
                end
                Query = "SELECT SCHED_DET_NG WITH REACT_NO EQ ":QueryReactorNo:" AND WITH BLOCK_OUT NE 1 AND WITH START_DTM LT '":OConv(ReportDate + 1, 'DT2/^H'):"' AND WITH STOP_DTM GT '":OConv(ReportDate, 'DT2/^H'):"'"
                LogData = ''
                LogData<1> = LoggingDtm
                LogData<2> = '5.':RIndex:'.1'
                LogData<3> = 'Selecting SCHED_DET_NG records for reactor ':ReactorNo:' using query "':Query:'".'
                Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)
                
                // Clear cursors for good measure.
                For counter = 0 To 8
                    ClearSelect counter
                Next counter
                
                RList(Query, TARGET_ACTIVELIST$, '', '', '')
                If Not(Get_Status(ErrCode)) then
                    EOF = False$
                    Loop
                        ReadNext SchedKeyID else EOF = True$
                    Until EOF
                        SchedDetKeyIDs<0, -1> = SchedKeyID
                    Repeat
                end
                LogData = ''
                LogData<1> = LoggingDtm
                LogData<2> = '5.':RIndex:'.2'
                LogData<3> = 'Reading SCHED_DET_NG records for reactor ':ReactorNo:'.'
                Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)                
                
                If SchedDetKeyIDs NE '' then
                    If Error_Services('NoError') then
                        HoursUp   = ''
                        SchedKeys = ''
                        For Each SchedDetKeyID in SchedDetKeyIDs using @VM setting vPos
                            SchedDetRec      = Database_Services('ReadDataRow', 'SCHED_DET_NG', SchedDetKeyID)
                            SchedWONo        = SchedDetRec<SCHED_DET_NG.WO_NO$>
                            SchedStartDtm    = SchedDetRec<SCHED_DET_NG.START_DTM$>
                            SchedStopDtm     = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
                            SchedStartDt     = SchedStartDTM[1, 'F.']
                            SchedStopDt      = SchedStopDTM[1, 'F.']
                            Begin Case
                                Case SchedStopDt EQ ReportDate
                                    // This scheduled part is ending
                                    If SchedStopDtm GT CurrDtm then
                                        HoursUp<0, vPos> = (CurrDtm - ReportDate) * 24
                                    end else
                                        HoursUp<0, vPos> = (SchedStopDtm - ReportDate) * 24
                                    end
                                Case SchedStartDt EQ ReportDate
                                    // This scheduled part is starting on the day of the report
                                    Begin Case
                                        Case SchedStartDtm GT CurrDtm 
                                            TempHours = 0
                                        Case ReportDate EQ CurrDate
                                            TempHours = ( ( CurrDtm - SchedStartDtm) * 24 )
                                        Case Otherwise$
                                            TempHours = ( ( (ReportDate + 1) - SchedStartDtm) * 24 )
                                    End Case
                                    If TempHours LT 0 then TempHours = 0
                                    HoursUp<0, vPos> = TempHours
                                Case ReportDate EQ CurrDate
                                    // Part is still running. Calculate how long it has been up today.
                                    HoursUp<0, vPos> = (CurrDtm - ReportDate) * 24
                                Case Otherwise$
                                    HoursUp<0, vPos> = 24
                            End Case
                            SchedWONos<0, -1>    = SchedWONo
                            SchedPSN             = Xlate('WO_LOG', SchedWONo, 'PS_NO', 'X')
                            SchedPSNs<0, -1>     = SchedPSN
                            SchedEpiParts<0, -1> = Xlate('WO_LOG', SchedWONo, 'EPI_PART_NO', 'X')
                            
                            SchedKeys<0, -1>     = SchedDetKeyID
                        Next SchedDetKeyID
                    end
                end
                LogData = ''
                LogData<1> = LoggingDtm
                LogData<2> = '5.':RIndex:'.3'
                LogData<3> = 'Writing new DAILY_PERFORMANCE records reactor ':ReactorNo:'.'
                Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)    
                
                RepEpiParts = SRP_Array('Clean', SchedEpiParts, 'TrimAndMakeUnique', @VM)
                If RepEpiParts NE '' then
                    For each SchedEpiPart in RepEpiParts using @VM setting vPos
                        If SchedEpiPart NE '' then
                            Locate SchedEpiPart in SchedEpiParts using @VM setting sPos then
                                SchedPSN = SchedPSNs<0, sPos>
                            end
                        end else
                            SchedPSN = ''
                        end
                        PSN                                           = SchedPSN
                        PartNo                                        = SchedEpiPart
                        PerfKey                                       = DateOut:'*':ReactorNo:'*':PSN:'*':PartNo
                        PerfRow                                       = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE', PerfKey)
                        RepWafersOut                                  = PerfRow<DAILY_PERFORMANCE.WAFERS_OUT$>
                        If RepWafersOut EQ '' then RepWafersOut  = 0
                        PerfRow<DAILY_PERFORMANCE.WAFERS_OUT$>        = RepWafersOut
                        PerfRow<DAILY_PERFORMANCE.HOURS_ON_PART$>     = OConv(IConv(HoursUp<0, vPos>, 'MD2'), 'MD2')
                        PerfRow<DAILY_PERFORMANCE.SCHED_DET_KEY_IDS$> = SchedKeys<0, vPos>
                        Database_Services('WriteDataRow', 'DAILY_PERFORMANCE', PerfKey, PerfRow)
                    Next SchedEpiPart
                end
                // Merge in Changeover data and build the report row from the DAILY_PERFORMANCE data generated above.

                table_name = "DAILY_PERFORMANCE"
                flag       = ""
                done       = False$
                CursorVar  = ""

                * Clears all cursors
                For counter = 0 To 8
                    ClearSelect counter
                Next counter

                sort_list     = "DATE_OUT"
                Reduce_Script = "WITH {REACTOR} EQ '":ReactorNo:"' AND WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
                mode          = NEXT_CUR$
				LogData = ''
                LogData<1> = LoggingDtm
                LogData<2> = '5.':RIndex:'.4'
                LogData<3> = 'Selecting new DAILY_PERFORMANCE records for reactor ':ReactorNo:' using query "':Reduce_Script:'" via Reduce.'
                Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 

                Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
                If flag then
                    Select table_name By sort_list Using Cursorvar then
                        Open table_name To file_var then
                            Loop
                                ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
                            Until done
                                KeyList<-1> = KeyID
                            Repeat
                        End
                    end
                End
                LogData = ''
                LogData<1> = LoggingDtm
                LogData<2> = '5.':RIndex:'.5'
                LogData<3> = 'Updating new DAILY_PERFORMANCE records for reactor ':ReactorNo:'.'
                Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)                 
                
                If KeyList NE '' then
                    // Build the final report row with data compiled above.
                    For each KeyID in KeyList using @FM setting fPos
                        WafersOut           = Xlate('DAILY_PERFORMANCE', KeyID, 'WAFERS_OUT', 'X')
                        PSN                 = Field(KeyID, '*', 3)
                        PartNo              = Field(KeyID, '*', 4)
                        ReactType           = Xlate('REACTOR', ReactorNo, 'REACT_TYPE', 'X')
                        If ReactType EQ 'EPP' then ReactType = 'EPIPRO'
                        ReportRow<0, 2, -1> = PSN
                        ReportRow<0, 3, -1> = PartNo
                        HoursOnPart         = Xlate('DAILY_PERFORMANCE', KeyID, 'HOURS_ON_PART', 'X')
                        ReportRow<0, 4, -1> = OConv(IConv(HoursOnPart, 'MD2'), 'MD2')
                        ReportRow<0, 5, -1> = WafersOut           ; // ActualOuts
                        ReportRow<0, 6, -1> = XLATE('DAILY_PERFORMANCE', KeyID, DAILY_PERFORMANCE.SCHED_DET_KEY_IDS$, 'X')
                        ReportRow<0, 7>     = ReactorNo
                    Next KeyID
                    ReportData<-1> = ReportRow
                end
            end
            
        Next ReactorNo
        LogData = ''
        LogData<1> = LoggingDtm
        LogData<2> = '6'
        LogData<3> = 'Begin building DAILY_PERFORMANCE_REPORT rows.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM)         
        
        ReportKeys = ''
        For each Row in ReportData using @FM setting fPos
            LogData = ''
			LogData<1> = LoggingDtm
            LogData<2> = '6.':fPos
            LogData<3> = 'Building DAILY_PERFORMANCE_REPORT row ':fPos:'.'
            Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
            
            PSNs    = Row<0, 2>
            NumPSNs = DCount(Row<0, 2>, @SVM)
            If NumPSNs GT 0 then
                For PSNIndex = 1 to NumPSNs
                    KeyID             = ReportDate:'*':Row<0, 1>:'*':Row<0, 2, PSNIndex>:'*':Row<0, 3, PSNIndex>
                    ReportKeys<0, -1> = KeyID
                    If RowExists('DAILY_PERFORMANCE_REPORT', KeyID) then
                        Rec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
                    end else
                        Rec = ''
                    end
                    Rec<DAILY_PERFORMANCE_REPORT.HOURS_ON_PART$> = Row<0, 4, PSNIndex>
                    Rec<DAILY_PERFORMANCE_REPORT.ACTUAL_OUT$>    = Row<0, 5, PSNIndex>
                    ActualStartDTM                               = ''
                    ActualStopDTM                                = ''
                    SchedDetRec                                  = Database_Services('ReadDataRow', 'SCHED_DET_NG', Row<0, 6, PSNIndex>)
                    SchedDetStartDTM                             = SchedDetRec<SCHED_DET_NG.START_DTM$>
                    SchedDetStopDTM                              = SchedDetRec<SCHED_DET_NG.STOP_DTM$>
                    If SchedDetStartDTM LT ReportDate then
                        ActualStartDTM = ReportDate
                    end else
                        ActualStartDTM = SchedDetStartDTM
                    end
                    If SchedDetStopDTM GT ReportDate + 1 then
                        ActualStopDTM = ReportDate + 1
                    end else
                        ActualStopDTM = SchedDetStopDTM
                    end
                    Rec<DAILY_PERFORMANCE_REPORT.START_DTM$>     = ActualStartDTM
                    Rec<DAILY_PERFORMANCE_REPORT.STOP_DTM$>      = ActualStopDTM
                    
                    //Get Mode Metric here
                    EQU PRODUCTIVE$                                      To 1
                    EQU UNSCHED$                                         to 2
                    EQU SCHED$                                           to 3
                    TotalReactorData                                      = Reactor_Services('GetReactorUptimeMetricsByTimeSpan', Row<0, 7> , ActualStartDTM, ActualStopDTM)
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_PROD_PERCENT$>     = TotalReactorData<1, PRODUCTIVE$>
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_PROD_MIN$>         = TotalReactorData<2, PRODUCTIVE$>
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_PERCENT$>  = TotalReactorData<1, UNSCHED$>
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_MIN$>      = TotalReactorData<2, UNSCHED$>
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_MIN$>        = TotalReactorData<2, SCHED$>
                    Rec<DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_PERCENT$>    = TotalReactorData<1, SCHED$>
                    
                    Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID, Rec)
                Next PSNIndex
            end else
                // Nothing ran or is scheduled on this reactor
                KeyID             = ReportDate:'*':Row<0, 1>:'*':Row<0, 2, PSNIndex>:'*':Row<0, 3, PSNIndex>
                ReportKeys<0, -1> = KeyID
                If RowExists('DAILY_PERFORMANCE_REPORT', KeyID) then
                    Rec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
                end else
                    Rec = ''
                end
                Rec<DAILY_PERFORMANCE_REPORT.HOURS_ON_PART$> = 0
                Rec<DAILY_PERFORMANCE_REPORT.ACTUAL_OUT$>    = 0
                Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID, Rec)
            end
        Next Row 
        
        // Clean out any rows that weren't just generated.
        table_name = "DAILY_PERFORMANCE_REPORT"
        flag      = ""
        done      = False$
        CursorVar = ""
        * Clears all cursors
        For counter = 0 To 8
            ClearSelect counter
        Next counter
        sort_list     = "DATE_OUT"
        Reduce_Script = "WITH {DATE_OUT} EQ '":OConv(ReportDate, 'D4/'):"'"
        mode          = NEXT_CUR$
        LogData = ''
        LogData<1> = LoggingDtm
        LogData<2> = '7'
        LogData<3> = 'Selecing DAILY_PERFORMANCE_REPORT rows to delete using query "':Reduce_Script:'" via Reduce.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
        
        Reduce(reduce_script, sort_list, mode, table_name, Cursorvar, flag)
        If flag then
            Select table_name By sort_list Using Cursorvar then
                Open table_name To file_var then
                    Loop
                        ReadNext KeyID Using Cursorvar By AT Else done = TRUE$
                    Until done
                        Locate KeyID in ReportKeys using @VM setting vPos else
                            Database_Services('DeleteDataRow', 'DAILY_PERFORMANCE_REPORT', KeyID)
                        end
                    Repeat
                End
            end
        end
        LogData = ''
        LogData<1> = LoggingDtm
        LogData<2> = '8'
        LogData<3> = 'End service.'
        Logging_Services('AppendLog', objReportsLog, LogData, @RM, @FM) 
        
        Unlock hSysLists, ServiceKeyID else Null
    end
    
end service

Service GenerateOrUpdateDailyReactorUptimeData()
	//Generates new or updates existing Reactor uptime data for all reactors in the current day.
	hSysLists  = Database_Services('GetTableHandle', 'SYSLISTS')
	Lock hSysLists, ServiceKeyID then
		Today = Date()
		DataArray = ''
		//ASM Reactors
		ASMRecordKey = 'ASM*': Today
		ASMUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'ASM')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', ASMRecordKey, ASMUptimeRec, True$, False$, True$)
		//HTR Reactors
		HTRRecordKey = 'HTR*': Today
		HTRUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'HTR')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', HTRRecordKey, HTRUptimeRec, True$, False$, True$)
		//EPP Reactors
		EPPRecordKey = 'EPP*': Today
		EPPUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'EPP')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', EPPRecordKey, EPPUptimeRec, True$, False$, True$)
		//All Reactors
		ALLRecordKey = 'ALL*': Today
		ALLUptimeRec = Reactor_Services('GetReactorUptimeMetricsByTypeAndTimeSpan', Today, Today + 1, 'ALL')
		Database_Services('WriteDataRow', 'REACTOR_DAILY_UPTIME', ALLRecordKey, ALLUptimeRec, True$, False$, True$)
		Unlock hSysLists, ServiceKeyID else Null
	end
end service

Service GetDailyReactorUptimeDataJSON(ReportStartDt, ReportEndDt)
	Response = ''
	Begin Case
		Case ReportStartDt EQ ''
			Error_Services('Add', 'Start Date was not provided.')
		Case ReportEndDt EQ ''
			Error_Services('Add', 'End Date was not provided.')
	End Case
	ReportStartDT = IConv(ReportStartDt, 'D4/H')
	ReportEndDt = IConv(ReportEndDt, 'D4/H')
	//Calculate Days to include
	FirstDay = SRP_Datetime('Date', ReportStartDt)
	LastDay = SRP_Datetime('Date', ReportEndDt) - 1
	DataArray = ''
	ASMPeriodUptimeMinutes = 0
	ASMPeriodAvailMinutes = 0
	HTRPeriodUptimeMinutes = 0
	HTRPeriodAvailMinutes = 0
	EPPPeriodUptimeMinutes = 0
	EPPPeriodAvailMinutes = 0
	AllPeriodUptimeMinutes = 0
	AllPeriodAvailMinutes = 0
	ReactorUptimeJson = ''
	//Create Root Object
	If SRP_Json(ReactorUptimeJson, 'New') then
		//Create an Array of days
		ReactorUptimeDaysJson = ''
		If SRP_Json(ReactorUptimeDaysJson, 'New', 'Array') then
			//For each day create a daily uptime object
			for day = FirstDay to LastDay
				//Single Day Object
				ASMUptimeKey = 'ASM*':day
				HTRUptimeKey = 'HTR*':day
				EPPUptimeKey = 'EPP*':day
				AllUptimeKey = 'ALL*':day
				ReactorUptimeDayJson = ''
				If SRP_Json(ReactorUptimeDayJson, 'New') then
					ASMUptime = 0
					HTRUptime = 0
					EPPUptime = 0
					AllUptime = 0
					If RowExists('REACTOR_DAILY_UPTIME', ASMUptimeKey) then
						ASMUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', ASMUptimeKey, True$, 0)
						ASMUptime = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						ASMUptime = SRP_Math('ROUND', ASMUptime * 100, 2)
						ASMUptimeMinutes = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						ASMAvailMinutes = ASMUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						ASMPeriodUptimeMinutes += ASMUptimeMinutes
						ASMPeriodAvailMinutes += ASMAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', HTRUptimeKey) then
						HTRUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', HTRUptimeKey, True$, 0)
						HTRUptime = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						HTRUptime = SRP_Math('ROUND', HTRUptime * 100, 2)
						HTRUptimeMinutes = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						HTRAvailMinutes = HTRUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						HTRPeriodUptimeMinutes += HTRUptimeMinutes
						HTRPeriodAvailMinutes += HTRAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', EPPUptimeKey) then
						EPPUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', EPPUptimeKey, True$, 0)
						EPPUptime = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						EPPUptime = SRP_Math('ROUND', EPPUptime * 100, 2)
						EPPUptimeMinutes = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						EPPAvailMinutes = EPPUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						EPPPeriodUptimeMinutes += EPPUptimeMinutes
						EPPPeriodAvailMinutes +=	EPPAvailMinutes
					end
					If RowExists('REACTOR_DAILY_UPTIME', AllUptimeKey) then
						AllUptimeRec = Database_Services('ReadDataRow', 'REACTOR_DAILY_UPTIME', AllUptimeKey, True$, 0)
						AllUptime = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_PERCENTAGE$>
						AllUptime = SRP_Math('ROUND', AllUptime * 100, 2)
						AllUptimeMinutes = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_UPTIME_MINUTES$>
						AllAvailMinutes = AllUptimeRec<REACTOR_DAILY_UPTIME_TOTAL_AVAIL_MINUTES$>
						AllPeriodUptimeMinutes += AllUptimeMinutes
						AllPeriodAvailMinutes += AllAvailMinutes
					end
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ReportDate", Oconv(day, 'DT4/H'))
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMUptime", ASMUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMUptimeMinutes", ASMUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "ASMAvailMinutes", ASMAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRUptime", HTRUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRUptimeMinutes", HTRUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "HTRAvailMinutes", HTRAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPUptime", EPPUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPUptimeMinutes", EPPUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "EPPAvailMinutes", EPPAvailMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllUptime", AllUptime)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllUptimeMinutes", ALLUptimeMinutes)
					SRP_Json(ReactorUptimeDayJson, "SetValue", "AllAvailMinutes", ALLAvailMinutes)
					SRP_Json(ReactorUptimeDaysJson, "Add", ReactorUptimeDayJson)
					//Release it
					SRP_Json(ReactorUptimeDayJson, "Release")
					

				end
			Next day
			//Add array to root object
			SRP_Json(ReactorUptimeJson, 'Set', 'Days', ReactorUptimeDaysJson)
			//Close out array
			SRP_Json(ReactorUptimeDaysJson, 'Release')

		end
		ReactorUptimeWTDJson = ''
		If SRP_Json(ReactorUptimeWTDJson, 'New') then	
			If ASMPeriodAvailMinutes GT 0 then
				ASMWTDUptimePercent = SRP_Math('ROUND', (ASMPeriodUptimeMinutes / ASMPeriodAvailMinutes) * 100, 2)
			end else
				ASMWTDUptimePercent = 0
			end
			If HTRPeriodAvailMinutes GT 0 then
				HTRWTDUptimePercent = SRP_Math('ROUND', (HTRPeriodUptimeMinutes / HTRPeriodAvailMinutes) * 100, 2)
			end else
				HTRWTDUptimePercent = 0
			end
			If EPPPeriodAvailMinutes GT 0 then
				EPPWTDUptimePercent = SRP_Math('ROUND', (EPPPeriodUptimeMinutes / EPPPeriodAvailMinutes) * 100, 2)
			end else
				EPPWTDUptimePercent = 0
			end
			If ALLPeriodAvailMinutes GT 0 then
				ALLWTDUptimePercent = SRP_Math('ROUND', (ALLPeriodUptimeMinutes / ALLPeriodAvailMinutes) * 100, 2)
			end else
				ALLWTDUptimePercent = 0
			end
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWtdUptimeMinutes", ASMPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWTDAvailMinutes", ASMPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ASMWTDUptimePercent", ASMWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDUptimeMinutes", HTRPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDAvailMinutes", HTRPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "HTRWTDUptimePercent", HTRWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDUptimeMinutes", EPPPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDAvailMinutes", EPPPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "EPPWTDUptimePercent", EPPWTDUptimePercent)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDUptimeMinutes", ALLPeriodUptimeMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDAvailMinutes", ALLPeriodAvailMinutes)
			SRP_Json(ReactorUptimeWTDJson, "SetValue", "ALLWTDUptimePercent", ALLWTDUptimePercent)
			SRP_Json(ReactorUptimeJson, 'Set', 'WTDMetrics', ReactorUptimeWTDJson)
			SRP_Json(ReactorUptimeWTDJson, 'Release')
		end
		// Now get the actual JSON
	   ResponseJson = SRP_Json(ReactorUptimeJson, "Stringify", "STYLED")
		
	   // All done with the root object
	   SRP_Json(ReactorUptimeJson, "Release")
		Response = ResponseJson
	end
end service

Service GetDailyPerformanceDataJSON(DateOut)

    Response   = ''
    ReportDate = IConv(DateOut, 'D')
    ReportData = ''
   
    Query = "SELECT DAILY_PERFORMANCE_REPORT WITH DATE_OUT EQ '":DateOut:"' BY REACTOR"
    RList(Query, TARGET_ACTIVELIST$, '', '', '')
    If Not(Get_Status(ErrCode)) then
        EOF = False$
        If SRP_JSON(objJSON, 'New', 'Object') then
            If SRP_JSON(objReactArray, 'New', 'Array') then
                Loop
                    ReadNext KeyID else EOF = True$
                Until EOF
                    Database_Services('ActivateRecord', 'DAILY_PERFORMANCE_REPORT', KeyID)
                    If SRP_JSON(objRow, 'New', 'Object') then
                        
                        ReactorNo    = Field(KeyID, '*', 2)
                        SRP_JSON(objRow, 'SetValue', 'reactorNo', ReactorNo)
                        CurrModeKeys = Xlate('REACTOR_CHILD_KEY_IDS_NG', ReactorNo, 'REACT_MODE_KEY_IDS', 'X')
                        CurrModeKey  = CurrModeKeys<0, 1>
                        CurrMode     = Xlate('REACT_MODE_NG', CurrModeKey, 'MODE', 'X')
                        SRP_JSON(objRow, 'SetValue', 'reactorType', {REACTOR_TYPE})
                        SRP_JSON(objRow, 'SetValue', 'currMode', CurrMode)
                        SRP_JSON(objRow, 'SetValue', 'psn', Field(KeyID, '*', 3))
                        SRP_JSON(objRow, 'SetValue', 'partNo', Field(KeyID, '*', 4))
                        SRP_JSON(objRow, 'SetValue', 'upHours', {HOURS_ON_PART})
                        SRP_JSON(objRow, 'SetValue', 'uptimePercent', {PERCENT_ON_PART})
                        SRP_JSON(objRow, 'SetValue', 'minutesPerWafer', {MIN_PER_WAFER})
                        SRP_JSON(objRow, 'SetValue', 'wafersPerDay', {WAFERS_PER_DAY})
                        SRP_JSON(objRow, 'SetValue', 'oee', {OEE})
                        SRP_JSON(objRow, 'SetValue', 'projOut', {PROJECTED_OUT})
                        SRP_JSON(objRow, 'SetValue', 'oeeCalculation', {OEE_CALCULATION})
                        SRP_JSON(objRow, 'SetValue', 'timeAdjProjOut', {TIME_ADJ_PROJECTED_OUT})
                        SRP_JSON(objRow, 'SetValue', 'actualOut', {ACTUAL_OUT}, 'Number')
                        SRP_JSON(objRow, 'SetValue', 'totalDelta', {TOTAL_DELTA})
                        SRP_JSON(objRow, 'SetValue', 'totalDeltaAlt', {TOTAL_DELTA_ALT})
                        SRP_JSON(objRow, 'SetValue', 'comments', {COMMENTS})
                        SRP_JSON(objRow, 'SetValue', 'projOutAlt', {PROJECTED_OUT_ALT})
                        SRP_JSON(objRow, 'SetValue', 'toolProdPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_PROD_PERCENT$, 'X'), 2))
                        SRP_JSON(objRow, 'SetValue', 'toolUnschedPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_PERCENT$, 'X'), 2))
                        SRP_JSON(objRow, 'SetValue', 'toolSchedPecent', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_PERCENT$, 'X'), 2))
                        SRP_JSON(objRow, 'SetValue', 'toolProdMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_PROD_MIN$, 'X'), 2))
                        SRP_JSON(objRow, 'SetValue', 'toolUnschedMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_UNSCHED_MIN$, 'X'), 2))
                        SRP_JSON(objRow, 'SetValue', 'toolSchedMin', SRP_MATH('ROUND',Xlate('DAILY_PERFORMANCE_REPORT', @ID, DAILY_PERFORMANCE_REPORT.TOTAL_SCHED_MIN$, 'X'), 2))
                        SRP_JSON(objReactArray, 'Add', objRow)
                        SRP_JSON(objRow, 'Release')
                    end
                Repeat
                SRP_JSON(objJSON, 'Set', 'performanceReport', objReactArray)
                SRP_JSON(objReactArray, 'Release')
            end
        end
        Response = SRP_JSON(objJSON, 'Stringify', 'Styled')
        SRP_JSON(objJSON, 'Release')
    end

end service


Service UpdateDailyPerformanceData(RepDate, RepJSON)
    
    If ( (RepDate NE '') and (RepJSON NE '') ) then
        If (SRP_JSON(objJSON, 'Parse', RepJSON) EQ '') then
            objArray   = SRP_JSON(objJSON, 'Get', 'performanceReport')
            ArrayCount = SRP_JSON(objArray, 'GetCount')
            For ArrayIndex = 1 to ArrayCount
                objRow    = SRP_JSON(objArray, 'Get', '[':ArrayIndex:']')
                ReactorNo = SRP_JSON(objRow, 'GetValue', 'reactorNo')
                PSNo      = SRP_JSON(objRow, 'GetValue', 'psn')
                PartNo    = SRP_JSON(objRow, 'GetValue', 'partNo')
                Comments  = SRP_JSON(objRow, 'GetValue', 'comments', '')
                RepKey = IConv(RepDate, 'D'):'*':ReactorNo:'*':PSNo:'*':PartNo
                RepRec = Database_Services('ReadDataRow', 'DAILY_PERFORMANCE_REPORT', RepKey)
                If Error_Services('NoError') then
                    RepRec<DAILY_PERFORMANCE_REPORT.COMMENTS$> = Comments
                    Database_Services('WriteDataRow', 'DAILY_PERFORMANCE_REPORT', RepKey, RepRec)
                end
                SRP_JSON(objRow, 'Release')
            Next ArrayIndex
            SRP_JSON(objArray, 'Release')
            SRP_JSON(objJSON, 'Release')
        end
    end
    
end service


// Calculate the last month's worth of performance data one day at a time.
Service CalcInProcessDailyPerformanceData()
    
    ReportDate  = Database_Services('ReadDataRow', 'APP_INFO', 'PERFORMANCE_REPORT_DATE')
    Report_Services('CalcDailyPerformanceData', ReportDate)
    ReportDate  = IConv(ReportDate, 'D')
    ReportDate -= 1
    If ReportDate LT Date() - 2 then ReportDate = Date()
    ReportDate  = OConv(ReportDate, 'D2/')
    Database_Services('WriteDataRow', 'APP_INFO', 'PERFORMANCE_REPORT_DATE', ReportDate)
    
end service

Service GetOpenTestWaferLotWIPReportJson()
    ErrorMessage    = ''
    JSONCollection  = ''
    TestWaferLots   = Test_Run_Services('GetTestWaferLots', True$)
    hJSONCollection = ''
    If SRP_JSON(hJSONCollection, 'New', 'Object') then
    	hTWLotArray = ''
        If SRP_JSON(hTWLotArray, 'New', 'Array') then
            Abort = False$
            for each TestWaferLotId in TestWaferLots using @VM
                ThisLotJson = Lot_Services('ConvertLotRecordToJson', TestWaferLotId, '', '', False$)
                If Error_Services('NoError') then
                	hTWLot = ''
                    If (SRP_JSON(hTWLot, 'Parse', ThisLotJson) EQ '') then
                        SRP_JSON(hTWLotArray, 'Add', hTWLot)
                        SRP_JSON(hTWLot, 'Release')
                    end
                end else
                    Abort   = True$
                end
            Until Abort
            Next TestWaferLotId
            If Abort EQ False$ then
                SRP_JSON(hJSONCollection, 'Set', 'Lots', hTWLotArray)
            end
            SRP_JSON(hTWLotArray, 'Release')
        end else
            ErrorMessage = 'Error creating test wafer Json array.'
        end
        JSONCollection = SRP_JSON(hJSONCollection, 'Stringify', 'Styled')
        SRP_JSON(hJSONCollection, 'Release')
    end else
        ErrorMessage = 'Error creating test wafer Json Collection'
    end
    If ErrorMessage NE '' then
        Error_Services('Add', ErrorMessage)
    end
    Response = JSONCollection
end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

ReadRecord:
    
    * Zero Break Flags To False
    
    ReactNo.Break=0
    
    READNEXT @ID, Which.Value ELSE
        LastRecord    = 1
        ReactNo.Break = 1
        ReactNo       = Prev.ReactNo
    END
    
    S.ATID = @ID
    
    IF FirstPass AND LastRecord THEN
        GOTO Bail
    END
    
    IF LastRecord THEN GOTO BREAKS
    
    READO @RECORD FROM SchedDetTable,@ID ELSE
        GOTO ReadRecord
    END
    
    @RECCOUNT += 1
    
    * Calculate Value(s) For Column(s)
    
    S.ATID			= {@ID}
    I.ATID			= S.ATID
    S.ReactNo		= {REACT_NO}
    I.ReactNo		= S.ReactNo
    S.WoNo			= {WO_NO}
    I.WoNo			= S.WoNo
    S.SubPartNo		= XLATE('WO_LOG',S.WoNo,'ORD_SUB_PART_NO','X')
    I.SubPartNo		= S.SubPartNo
    S.EpiPartNo		= XLATE('WO_LOG',S.WoNo,WO_LOG_EPI_PART_NO$,'X')
    I.EpiPartNo		= S.EpiPartNo
    S.WO_Qty		= {WO_QTY}
    I.WO_Qty		= S.WO_Qty
    S.WO_RX_Qty 	= {WO_RX_QTY}
    I.WO_RX_Qty 	= S.WO_RX_Qty
    S.WO_UnRel_QTY	= {WO_UNREL_QTY}
    I.WO_UnRel_QTY	= S.WO_UnRel_QTY
    S.Sched_DT		= {SCHED_DT}
    I.Sched_DT		= S.Sched_DT
    S.REACT_TYPE    = {REACTOR_TYPE}
    S.ProdOrdNo		= {PROD_ORD_NO}
    
    Locate 'SR*KTR]' in LocationFilter using @VM setting vPos then
        KitData = Location_Services('GetLocationCassInfo', S.WONo, 'SR*KTR]')    
        SWAP CRLF$ WITH @VM IN KitData
    end else
        KitData = ''
    end
    
    S.KitLocation = KitData<1>
    I.KitLocation = S.KitLocation
    
    S.KitQty		= OCONV(SUM(KitData<2>) * 25,'MD0,Z')
    I.KitQty		= S.KitQty
    
    S.KitCassCnt	= COUNT(KitData<3>,',') + (KitData<3> NE '')
    I.KitCassCnt	= S.KitCassCnt
    
    KitCassList = KitData<3>
    
    Locate '1K*PTI' in LocationFilter using @VM setting vPos then
        PTIData = Location_Services('GetLocationCassInfo', S.WONo, '1K*PTI')
        SWAP CRLF$ WITH @VM IN PTIData    
    end else
        PTIData = ''
    end
    PTICassList = PTIData<3>
    
    CRLocs        = ''
    CRLocs<0, -1> = 'CR*BE'
    CRLocs<0, -1> = 'CR*BO'
    CRLocs<0, -1> = 'CR*TUN'
    CRLocs<0, -1> = 'CR*EPR'
    CRLocs<0, -1> = 'CR*FE'
    CRLocs<0, -1> = 'CR*FEH'
    CRLocs<0, -1> = 'CR*FO'
    CRLocs<0, -1> = 'CR*FOH'
    
    LoadedData     = ''
    LocQueryList   = ''
    For each Loc in CRLocs using @VM setting crPos
        Locate Loc in LocationFilter using @VM setting vPos then
            LocQueryList<0, -1> = Loc    
        end
    Next Loc
    
    LoadedCassList = ''
    ReactType      = Xlate('WO_LOG', S.WONo, 'REACT_TYPE', 'X')
    If ReactType NE 'EPP' then
        If LocQueryList NE '' then
            LoadedData     = Location_Services('GetLocationCassInfo', S.WONo, LocQueryList)
            Swap CRLF$ with @VM in LoadedData
            CassList       = LoadedData<3>
            For each CassNo in CassList using ','
                If ( (S.WONo NE '') and (CassNo NE '') ) then
                    WOMatKey      = S.WONo:'*':CassNo
                    UnloadSigComp = Signature_Services('CheckSignature', WOMatKey, 'UNLOAD')
                    If UnloadSigComp then
                        // Add this cassette to the PTI list instead of the Load list
*                        PTICassList<0, -1> = CassNo
                    end else
                        LoadedCassList<0, -1> = CassNo
                    end
                end
            Next CassNo
        end
    end
    
    * TEST FOR CONTROL BREAK(S)
    
    IF (S.ReactNo NE Prev.ReactNo) OR ReactNo.Break THEN
        ReactNo			= Prev.ReactNo
        Prev.ReactNo	= S.ReactNo
        ReactNo.Break  += 1
    END
    
    IF FirstPass THEN
        FirstPass=0
        GOTO DETAIL
    END
    
    
BREAKS:
    
    * Print Break Total(s) And Accumulate Total(s)    
    IF ReactNo.Break THEN stat = Set_Printer('TEXT')      
    
    * Perform Last Record Output If Done  
    IF LastRecord THEN        
        colData = ''
        GOTO Bail        
    END
    
    
DETAIL:
    
    * Do Conversion If Any
    
    IF S.REACTNO NE "" 		THEN S.REACTNO		= OCONV(S.REACTNO,"MD0,")
    IF S.WONO NE "" 		THEN S.WONO			= OCONV(S.WONO,"MD0")
    IF S.WO_QTY NE "" 		THEN S.WO_QTY		= OCONV(S.WO_QTY,"MD0,")
    IF S.WO_RX_QTY NE ""	THEN S.WO_RX_QTY	= OCONV(S.WO_RX_QTY,"MD0,")
    IF S.WO_UNREL_QTY NE "" THEN S.WO_UNREL_QTY	= OCONV(S.WO_UNREL_QTY,"MD0,")
    IF S.SCHED_DT NE "" 	THEN S.SCHED_DT		= OCONV(S.SCHED_DT,"D4/")
    
    * PRINT DETAIL LINE
    
    COLDATA        = ''
    RowNumber      = 0
    
    /*  ascending sort  */
    Convert @VM to ',' in KitCassList
    Convert @VM to ',' in PTICassList
    Convert @VM to ',' in LoadedCassList
    KitCassList    = SRP_Array('SortSimpleList', KitCassList, 'AscendingNumbers', ',')
    PTICassList    = SRP_Array('SortSimpleList', PTICassList, 'AscendingNumbers', ',')
    LoadedCassList = SRP_Array('SortSimpleList', LoadedCassList, 'AscendingNumbers', ',')
    
    Convert ',' to @VM in KitCassList
    Convert ',' to @VM in PTICassList
    Convert ',' to @VM in LoadedCassList
    
    NumKitCass = DCount(KitCassList, @VM)
    If NumKitCass GT 4 then
        For CassIndex = 4 to NumKitCass Step 4
            CassNo                     = KitCassList<0, CassIndex>
            CassNo                    := CRLF$
            KitCassList<0, CassIndex>  = CassNo
        Next CassIndex
    end
    NumPTICass = DCount(PTICassList, @VM)
    If NumPTICass GT 4 then
        For CassIndex = 4 to NumPTICass Step 4
            CassNo                     = PTICassList<0, CassIndex>
            CassNo                    := CRLF$
            PTICassList<0, CassIndex>  = CassNo
        Next CassIndex
    end
    NumLoadedCass = DCount(LoadedCassList, @VM)
    If NumLoadedCass GT 2 then
        For CassIndex = 2 to NumLoadedCass Step 2
            CassNo                        = LoadedCassList<0, CassIndex>
            CassNo                       := CRLF$
            LoadedCassList<0, CassIndex>  = CassNo
        Next CassIndex
    end
    Convert @VM to ',' in KitCassList
    Convert @VM to ',' in PTICassList
    Convert @VM to ',' in LoadedCassList
    
    CassNeeded = ''
    EpiPartNo  = Xlate('WO_LOG', S.WONo, 'EPI_PART_NO', 'X')
    If EpiPartNo NE '' then
        WPDAdjusted = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, S.React_Type)
        If WPDAdjusted NE '' then
            CPDAdjusted = SRP_Math('CEILING', (WPDAdjusted/25) )
            CassInCR    = DCount(KitCassList, ',') + DCount(PTICassList, ',') + DCount(LoadedCassList, ',')
            CassNeeded  = CassInCR - CPDAdjusted
        end
    end
    
    Swap '.1' with '' in S.ProdOrdNo
    AllReportData     = ''
    AllReportData<1>  = S.ReactNo
    AllReportData<2>  = S.React_Type
    AllReportData<3>  = S.WONo
    AllReportData<4>  = S.ProdOrdNo
    AllReportData<5>  = S.SubPartNo
    AllReportData<6>  = S.EpiPartNo
    AllReportData<7>  = S.WO_Qty
    AllReportData<8>  = S.WO_RX_Qty
    AllReportData<9>  = S.WO_UnRel_Qty
    AllReportData<10> = S.KitLocation
    AllReportData<11> = S.KitQty
    AllReportData<12> = CassNeeded					
    AllReportData<13> = KitCassList
    AllReportData<14> = PTICassList
    AllReportData<15> = LoadedCassList
    AllReportData<16> = ''
    
    If ( (KitCassList NE '') or (PTICassList NE '') or (LoadedCassList NE '') ) then
        RowNumber += 1              
        For each RptColumn in RptColumns using @VM setting rcPos
            Locate RptColumn in AllRptColumns using @VM setting dataPos then
                COLDATA<RowNumber,rcPos> = AllReportData<dataPos>
            end  
        Next RptColumn
        If COLDATA NE '' then GoSub PrintTable
    END
    
    GOTO ReadRecord   
    
Bail:
    
    stat = Set_Printer('TERM',1)
    
RETURN


OIPrintErr:
    
    ErrMsg(ErrorTitle:@SVM:ErrorMsg)
    ErrMsg(ErrorTitle:@SVM:'Set_Printer returned errorcode ':stat)
    stat = Set_Printer('TERM',1)
    
RETURN


PrintTable:
    
    PageHeight		= Get_Printer('PAGESIZE')<2>
    PrintableHeight = PageHeight - TopMargin - BottomMargin
    PrinterHeight   = Get_Printer('POS')<2>   
    stat			= Set_Printer('CALCTABLE',ColFmt:@FM:ColData)
    TableSize		= Get_Printer('CALCTABLE')  
    TableHeight		= TableSize<2>   
    fontSpacing			= 120
    
    IF ( TableHeight + PrinterHeight >= PrintableHeight ) OR FirstLine THEN
        IF NOT(FirstLine) THEN
            stat = Set_Printer('PAGEBREAK')
        END
        FirstLine = 0
        font<2> = 10
        font<4> = 1		;* Bold
        stat = Set_Printer('FONT',font,'100')       
        stat = Set_Printer('ADDTABLE',colFmt,colHead,'',LTGREY$,'',0,TB_ALL)                
        font<4> = 0
        stat = Set_Printer('FONT',font,fontSpacing)      
        stat = Set_Printer('ADDTABLE',colFmt,'',colData,LTGREY$,'',0,7)        
    END ELSE
        font<2> = 10
        font<4> = 0
        stat = Set_Printer('FONT',font,fontSpacing)
        stat = Set_Printer('ADDTABLE',colFmt,'',colData,LTGREY$,'',1,TB_ALL)       
    END
    
RETURN


ClearCursors:
    
    For counter = 0 to 8
        ClearSelect counter
    Next counter
    
return


