Function REACTOR_SERVICES_Dev(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Reactor_Services_Dev

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    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)
        08/10/17    dmb     Original programmer. Adapted from Scheduling_Services. - [IREPIOI-8]
        01/08/18    dmb     Update the CreatePerformanceTrackersDev service so the CreateWeeklyPerformanceReportDev is called
                            on Friday mornings for the upcoming week rather than Monday mornings for the current week.
                            - [IREPIOI-14]
        02/17/18    dmb     Add logging to track reactor performance activities. - [IREPIOI-14]
        05/30/18    dmb     Update various services to allow newer reactors to be used for reporting. - [IREPIOI-17]
        06/21/18    dmb     Update the path where templates and reports are stored. - [IREPIOI-33]
        06/26/18    dmb     Add logging to track when this service is being called. - [IREPIOI-56]
        06/26/18    dmb     Update logic on how reactors are selected for reporting. - [IREPIOI-56]
        04/20/21    djs     Added PickPlace to objReactor JSON object in GetReactorDev service.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert APP_INSERTS
$insert SERVICE_SETUP
$insert REACTOR_EQUATES
$insert RDS_EQUATES
$insert RLIST_EQUATES
$insert SCHED_DET_NG_EQUATES

Declare subroutine  Error_Services, Reactor_Services_Dev, Memory_Services, RList, Database_Services, SRP_JSON
Declare subroutine  Excel_Services, Schedule_Services, Logging_Services, Set_Status, Schedule_Services
Declare function    SRP_Array, Reactor_Services_Dev, Memory_Services, Database_Services, SRP_Sort_Array, Excel_Services
Declare function    SRP_Math, SRP_Hash, SRP_JSON, Epi_Part_Services, Schedule_Services, Date_Services
Declare function    Logging_Services, GetCommandLine, Environment_Services, Schedule_Services

// Report paths for various performance report services.
TemplatesFolder         = Environment_Services('GetApplicationRootPath') : '\Reports\SchedulerPro\Templates\'
DailyReportsFolder      = Environment_Services('GetApplicationRootPath') : '\Reports\SchedulerPro\Daily\'
WeeklyReportsFolder     = Environment_Services('GetApplicationRootPath') : '\Reports\SchedulerPro\Weekly\'

CommandLine     = GetCommandLine()
Machine         = Environment_Services('GetServer')
LogPath         = Environment_Services('GetApplicationRootPath') : '\LogFiles\PerformanceReportsPro'
LogDate         = Oconv(Date(), 'D4/')
LogTime         = Oconv(Time(), 'MTS')
LogFileName     = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Performance Reports Pro.csv'
Headers         = 'Logging DTM' : @FM : 'Machine' : @FM : 'Command Line' : @FM : 'Service Detail'
ColumnWidths    = 20 : @FM : 15 : @FM : 60 : @FM : 300
objLog          = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, ' ', Headers, ColumnWidths, False$, False$)
LoggingDTM      = LogDate : ' ' : LogTime   ; // Logging DTM

GoToService else
    Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Error_Services('GetMessage')
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)
end

Return Response else ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// GetReactorsDev
//
// Returns a JSON formatted array of reactor objects. Reactor number 0 will be ommitted. If the MatchType or MatchSize
// arguments are populated, the array will be filterd accordingly.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorsDev(MatchType, MatchSize)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
*    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    ServiceKeyID   := '*' : MatchType : '*' : MatchSize
    Reactors        = ''

    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        Reactors    = Memory_Services('GetValue', ServiceKeyID)
    end else
        objReactors = ''
        If SRP_JSON(objReactors, 'NEW', 'OBJECT') then
            objReactorArray = ''
            If SRP_JSON(objReactorArray, 'NEW', 'ARRAY') then
                hReactors   = Database_Services('GetTableHandle', 'REACTOR')
                If Error_Services('NoError') then
                    rv = Set_Status(0)
                    RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                    Done        = False$
                    Reactors    = ''
                    Loop
                        Readnext ReactorNo else Done = True$
                    Until Done
                        Reactor    = Reactor_Services_Dev('GetReactorDev', ReactorNo)
                        objReactor = ''
                        If SRP_JSON(objReactor, 'PARSE', Reactor) EQ '' then
                            ReactorType = SRP_JSON(objReactor, 'GETVALUE', 'Type')
                            ReactorSize = SRP_JSON(objReactor, 'GETVALUE', 'SusceptorSize')
                            IncludeReactor  = True$ ; // Assume reactor will be included in the response.
                            Begin Case
                                Case ReactorNo EQ 0
                                    // Reactor number 0 is not a real reactor.
                                    IncludeReactor  = False$
                                Case MatchType NE ''
                                    Begin Case
                                        Case MatchType[1, 3] EQ 'ASM' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EPS' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EPP' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                            IncludeReactor  = False$
                                        Case MatchType EQ 'EpiPro' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                            IncludeReactor  = False$
                                        Case MatchType _EQC 'GAN' AND Not(ReactorType _EQC 'GAN')
                                            IncludeReactor  = False$
                                        Case MatchType NE ReactorType
                                            IncludeReactor  = False$
                                    End Case
                                Case MatchSize NE ''
                                    If MatchSize NE ReactorSize then IncludeReactor  = False$
                            End Case
                            If IncludeReactor then
                                SRP_JSON(objReactorArray, 'ADD', objReactor)
                            end
                            SRP_JSON(objReactor, 'RELEASE')
                        end
                    Repeat
                    SRP_JSON(objReactors, 'SET', 'Reactors', objReactorArray)
                    Reactors    = SRP_JSON(objReactors, 'STRINGIFY', 'FAST')
                    Memory_Services('SetValue', ServiceKeyID, Reactors)
                end
                SRP_JSON(objReactorArray, 'RELEASE')
            end else
                Error_Services('Add', 'Error creating objReactorArray in the ' : Service : ' service.')
            end
            SRP_JSON(objReactors, 'RELEASE')
        end else
            Error_Services('Add', 'Error creating objReactors in the ' : Service : ' service.')
        end
    end

    Response    = Reactors

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
*    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactorNumbersDev
//
// Returns an @FM delimited list of Reactor Numbers.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorNumbersDev(MatchType, MatchSize)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    ServiceKeyID   := '*' : MatchType : '*' : MatchSize
    ReactorNumbers  = ''

    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        ReactorNumbers  = Memory_Services('GetValue', ServiceKeyID)
    end else
        hReactors       = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            rv = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            Reactors    = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                Reactor     = Reactor_Services_Dev('GetReactorDev', ReactorNo)
                If SRP_JSON(objReactor, 'PARSE', Reactor) EQ '' then
                    ReactorType = SRP_JSON(objReactor, 'GETVALUE', 'Type')
                    ReactorSize = SRP_JSON(objReactor, 'GETVALUE', 'SusceptorSize')
                    IncludeReactor  = True$ ; // Assume reactor will be included in the response.
                    Begin Case
                        Case ReactorNo EQ 0
                            // Reactor number 0 is not a real reactor.
                            IncludeReactor  = False$
                        Case MatchType NE ''
                            Begin Case
                                Case MatchType[1, 3] EQ 'ASM' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EPS' AND Not((ReactorType[1, 3] EQ 'ASM' OR ReactorType EQ 'EPS'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EPP' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                    IncludeReactor  = False$
                                Case MatchType EQ 'EpiPro' AND Not((ReactorType EQ 'EPP' OR ReactorType EQ 'EpiPro'))
                                    IncludeReactor  = False$
                                Case MatchType _EQC 'GAN' AND Not(ReactorType _EQC 'GAN')
                                    IncludeReactor  = False$
                                Case MatchType NE ReactorType
                                    IncludeReactor  = False$
                            End Case
                        Case MatchSize NE ''
                            If MatchSize NE ReactorSize then IncludeReactor  = False$
                    End Case
                    If IncludeReactor then
                        ReactorNumbers := ReactorNo : @FM
                    end
                end
            Repeat
            ReactorNumbers[-1, 1]   = ''
            Memory_Services('SetValue', ServiceKeyID, ReactorNumbers)
        end
    end

    Response    = ReactorNumbers

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReactorDev
//
// Returns a JSON formatted object of information related to the indicated reactor number.
//----------------------------------------------------------------------------------------------------------------------
Service GetReactorDev(ReactorNo)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
*    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    ServiceKeyID   := '*' : ReactorNo
    Reactor         = ''

    If Memory_Services('IsValueCurrent', ServiceKeyID, 3600, True$) then
        Reactor     = Memory_Services('GetValue', ServiceKeyID)
    end else
        If (ReactorNo NE '') AND (ReactorNo NE 0) then
            ReactorRow  = Database_Services('ReadDataRow', 'REACTOR', ReactorNo)
            If SRP_JSON(objReactor, 'NEW', 'OBJECT') then
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SRP_JSON(objReactor, 'SETVALUE', 'ReactorNumber', ReactorNo, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'Type', ReactorType, 'STRING')
                Begin Case
                    Case ReactorType _EQC 'ASM' ; ReactorTypeVerbose = 'ASM'
                    Case ReactorType _EQC 'ASM+'; ReactorTypeVerbose = 'ASM+'
                    Case ReactorType _EQC 'EPP' ; ReactorTypeVerbose = 'EpiPro'
                    Case ReactorType _EQC 'EPS' ; ReactorTypeVerbose = 'ASM'
                    Case ReactorType _EQC 'HTR' ; ReactorTypeVerbose = 'HTR'
                    Case ReactorType _EQC 'GAN' ; ReactorTypeVerbose = 'GaN'
                    Case ReactorType _EQC ''	; ReactorTypeVerbose = '***'
                End Case
                SRP_JSON(objReactor, 'SETVALUE', 'TypeVerbose', ReactorTypeVerbose, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'AssignmentCode', ReactorRow<REACTOR_REACT_ASSIGNMENT$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'AssignmentDescription', Oconv(ReactorRow<REACTOR_REACT_ASSIGNMENT$>, '[REACT_ASSIGN_CONV]'), 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'Location', ReactorRow<REACTOR_LOCATIONX$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'SusceptorSize', ReactorRow<REACTOR_SUSC_POCKET_SIZE$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'SecondChamber', ReactorRow<REACTOR_SECOND_CHAMBER$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'PickPlace', ReactorRow<REACTOR_PICK_PLACE$>, 'STRING')
                SRP_JSON(objReactor, 'SETVALUE', 'NotRepairable', ReactorRow<REACTOR_NOT_REPAIRABLE$>, 'STRING')
                Reactor     = SRP_JSON(objReactor, 'STRINGIFY', 'FAST')
                Memory_Services('SetValue', ServiceKeyID, Reactor)
                SRP_JSON(objReactor, 'RELEASE')
            end else
                Error_Services('Add', 'Error creating objReactor in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'ReactorNo argument was missing from the ' : Service : ' service.')
        end
    end

    Response    = Reactor

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
*    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// CreatePerformanceTrackersDev
//
// Creates performance tracking database rows in the REACTOR_PERFORMANCE_NG table. This creates a unique tracker for each
// Reactor/Part pair based on the indicated dates scheduled work orders.
//----------------------------------------------------------------------------------------------------------------------
Service CreatePerformanceTrackersDev(ScheduleDate, CreateReports)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    // Force the auto scheduler to run to make sure that all adjustments are being made to the scheduler.
*    Schedule_Services('AutoScheduler')

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    If CreateReports NE False$ then CreateReports = True$

    // There will be two procedures. The first is a pass through the scheduled events to create the Reactor Performance
    // rows. The second is a pass through the recently created Reactor Performance records to adjust the expected
    // wafer count based part changes.

    // Get a list of all the scheduled events for the indicated schedule date. Note: this service expects to be ran
    // shortly after midnight for the indicated schedule date.
    ScheduleEvents  = Schedule_Services('GetScheduleEventsNew', ScheduleDate, ScheduleDate, '', '', False$)
    If ScheduleEvents NE '' then
        PerfKeyIDs  = ''
        For Each ScheduleEvent in ScheduleEvents using @FM setting fPos
            KeyID        = ScheduleEvent<0, 2>
            EventRec     = Database_Services('ReadDataRow', 'SCHED_DET_NG', KeyID)
            WorkOrderNo  = EventRec<SCHED_DET_NG.WO_NO$>
            EpiPartNo    = Xlate('WO_LOG', WorkOrderNo, 'EPI_PART_NO', 'X')
            ReactorNo    = EventRec<SCHED_DET_NG.REACT_NO$>
*            ScheduleDate = Field(EventRec<SCHED_DET_NG.START_DTM$>, '.', 1, 1)
            If EpiPartNo NE '' then
                PartUsedKeyID   = Service : '*' : ReactorNo : '*' : ScheduleDate : '*' : EpiPartNo
                If Memory_Services('KeyExists', PartUsedKeyID) EQ False$ then
                    PartCountKeyID  = Service : '*' : ReactorNo : '*' : ScheduleDate
                    NumParts        = Memory_Services('GetValue', PartCountKeyID)
                    NumParts       += 1
                    Memory_Services('SetValue', PartCountKeyID, NumParts)
                    Memory_Services('SetValue', PartUsedKeyID, True$)
                end
                // This is a work order event rather than a block out event.
                ScheduleEventSummary    = Schedule_Services('GetScheduleEventSummary', KeyID, True$)
                StartDate               = Iconv(ScheduleEventSummary<6>, 'D')
                ReactorType             = ScheduleEventSummary<1>
                EndDate                 = Iconv(ScheduleEventSummary<7>, 'D')
                TotalWafers             = ScheduleEventSummary<9>
                Begin Case
                    Case ReactorType _eqc 'ASM+'    ; ReactorType = 'ASM+'
                    Case ReactorType _eqc 'EPP'     ; ReactorType = 'EpiPro'
                    Case ReactorType _eqc 'EPS'     ; ReactorType = 'ASM'
                    Case ReactorType _eqc 'HTR'     ; ReactorType = 'HTR'
                    Case ReactorType _eqc 'GAN'     ; ReactorType = 'GaN'
                    Case ReactorType _eqc ''	    ; ReactorType = '***'
                End Case
                MinutesPerWafer = Epi_Part_Services('GetMinutesPerWaferScheduler', EpiPartNo, ReactorType)
                WafersPerDay    = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, ReactorType, MinutesPerWafer)
                If ScheduleDate LT EndDate then
                    // The end date of this schedule event is still in the future. Therefore, the current event is expected
                    // to run the whole day. Use the calculated wafers per day.
                    WafersExpected  = WafersPerDay
                end else
                    // This is the end of the schedule event. Wafers expected will be the remaining total wafers after
                    // all previous wafers per day have been deducted.
                    TotalPreviousDays   = EndDate - StartDate
                    TotalPreviousWafers = TotalPreviousDays * WafersPerDay
                    WafersExpected      = TotalWafers - TotalPreviousWafers
                    If WafersExpected GT WafersPerDay then WafersExpected = WafersPerDay    ; // Cap to the wafers per day.
                end
                If WafersExpected LT 0 then WafersExpected = 0
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                WeekDate   = Date_Services('GetISOWeekDate', ScheduleDate)
                WeekYear   = WeekDate[1, '-']
                WeekNo     = WeekDate[Col2() + 1, '-']
                PerfKeyID  = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
                PerfRow    = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                PerfRow<2> = PerfRow<2> + Iconv(WafersExpected, 'MD2')
                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, PerfRow, True$, False$, True$)
                Locate PerfKeyID in PerfKeyIDs using @FM setting fPos else PerfKeyIDs := PerfKeyID : @FM
            end
        Next ScheduleEvent

        // Pass through each Reactor Performance row. For every part being run on a given reactor, the following
        // adjustment is required:
        //
        //   1. Total expected amount per part should be prorated by the number of parts. This accounts for the fact
        //      that one reactor will be splitting its time for each part. For example, if Part A is normally estimated
        //      to run 200 parts and Part B is normally estimated to run 300 parts, they will each be prorated by .5.
        //      Thus, Part A will now be estimated to run 100 parts and Part B will be estimated to run 150 parts.
        //
        //   2. Since each part change requires an average of 6 hours of down time to prepare the reactor, the amount
        //      per part should be prorated by the remaining hours in the day. For example, if there are two parts being
        //      ran on the reactor, this only allows 18 hours (out of 24) to run parts, which is a prorated value of
        //      .75. Using the above example, Part A would then have 100 parts prorated to 75 (100 * .75) and Part would
        //      then have 150 parts prorated to 112.50 (150 * .75).
        If PerfKeyIDs NE '' then
            For Each PerfKeyID in PerfKeyIDs using @FM
                ReactorNo      = PerfKeyID[1, '*']
                EpiPartNo      = PerfKeyID[Col2() + 1, '*']
                WeekYear       = PerfKeyID[Col2() + 1, '*']
                WeekNo         = PerfKeyID[Col2() + 1, '*']
                ScheduleDate   = PerfKeyID[Col2() + 1, '*']
                PartCountKeyID = Service : '*' : ReactorNo : '*' : ScheduleDate
                NumParts       = Memory_Services('GetValue', PartCountKeyID)
                If NumParts GT 1 then
                    // Only prorate if there is more then one part.
                    Prorate1    = 1 / NumParts                          ; // Adjust for the number of parts.
                    Prorate2    = ((24 - ((NumParts - 1) * 6) ) / 24)   ; // Adjust for the number of hours remaining in the day.
                    Prorate     = Prorate1 * Prorate2
                    PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                    PerfRow<2>  = PerfRow<2> * ProRate
                    Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, PerfRow, True$, False$, True$)
                end
            Next PerfKeyID

            // Clear out all cached data.
            Memory_Services('ReleaseHashTable')
        end
    end

    If CreateReports then
        Reactor_Services_Dev('CreateDailyPerformanceReportDev', ScheduleDate)
        If Mod(ScheduleDate, 7) EQ 5 then
            // This is early Friday. Create the weekly performance report for the upcoming week. First, the
            // CreatePerformanceTrackersDev must be called for all days in the upcoming week.
            NextMonday  = ScheduleDate + 3
            NextSunday  = ScheduleDate + 9
            For ScheduleDate = NextMonday to NextSunday
                Reactor_Services_Dev('CreatePerformanceTrackersDev', ScheduleDate, False$)
            Next ScheduleDate
            // Create the weekly performance report based on the daily performance tracking data.
            YearWeekNo = Date_Services('GetISOWeekDate', NextMonday)
            YearWeekNo = Field(YearWeekNo, '-', 1, 2)
            Reactor_Services_Dev('CreateWeeklyPerformanceReportDev', YearWeekNo)
            // Clear the daily performance tracking data for the upcoming week since these will be recreated
            // automatically on the morning of each date.
            For ScheduleDate = NextMonday to NextSunday
                Reactor_Services_Dev('ClearWafersExpectedDev', ScheduleDate)
            Next ScheduleDate
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// UpdatePerformanceTrackersDev
//
// Updates the wafers processed column in the performance tracking database rows.
//----------------------------------------------------------------------------------------------------------------------
Service UpdatePerformanceTrackersDev(ScheduleDate, CreateReports)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()
    If CreateReports NE False$ then CreateReports = True$

    rv          = Set_Status(0)
    Sentence    = 'SELECT RDS WITH DATE_OUT EQ ' : Quote(Oconv(ScheduleDate, 'D4/'))
    RList(Sentence, 5, '', '', '')
    EOF         = False$
    Loop
        Readnext RDSID else EOF = True$
    Until EOF
        RDSRow  = Database_Services('ReadDataRow', 'RDS', RDSID)
        If Error_Services('NoError') then
            WeekDate    = Date_Services('GetISOWeekDate', ScheduleDate)
            WeekYear    = WeekDate[1, '-']
            WeekNo      = WeekDate[Col2() + 1, '-']
            ReactorNo   = RDSRow<RDS_REACTOR$>
            ReactorRow  = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 60, False$)
            ReactorType = ReactorRow<REACTOR_REACT_TYPE$>
            If ReactorType EQ 'EPP' then
                // This is an EpiPro reactor. Check to see if has a second chamber. If so, then this is the primary
                // reactor. If not, then check the previous reactor to see if it references this current reactor as its
                // second chamber. If so then use the previous reactor to track the wafers processed.
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                If SecondChamber EQ '' then
                    CompareReactorNo        = ReactorNo - 2
                    CompareReactorRow       = Database_Services('ReadDataRow', 'REACTOR', CompareReactorNo, True$, 60, False$)
                    CompareSecondChamber    = CompareReactorRow<REACTOR_SECOND_CHAMBER$>
                    If CompareSecondChamber EQ ReactorNo then Transfer CompareReactorNo to ReactorNo
                end
            end

*            EpiPartNo   = RDSRow<RDS_PART_NUM$>
            EpiPartNo   = Xlate('RDS', RDSID, 'EPI_PART_NO', 'X')
            // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
            PerfKeyID   = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
            PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
            If Error_Services('NoError') then
                WafersProcessed = RDSRow<RDS_WAFERS_IN$>
                PerfRow<3>      = PerfRow<3> + Iconv(WafersProcessed, 'MD2')
                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, PerfRow, True$, False$, True$)
            end
        end
    Repeat
    
    If CreateReports then
        Reactor_Services_Dev('UpdateDailyPerformanceReportDev', ScheduleDate)
        If Mod(ScheduleDate, 7) EQ 0 then
            // This is a Sunday, which means the last day of a work week has been updated. Update the weekly performance
            // report but set the date to the previous Monday.
            YearWeekNo = Date_Services('GetISOWeekDate', ScheduleDate - 6)
            YearWeekNo = Field(YearWeekNo, '-', 1, 2)
            Reactor_Services_Dev('UpdateWeeklyPerformanceReportDev', ScheduleDate - 6)
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// ClearWafersExpectedDev
//
// Clears the expected wafers for all database rows the REACTOR_PERFORMANCE_NG table based on the indicated schedule date.
//----------------------------------------------------------------------------------------------------------------------
Service ClearWafersExpectedDev(ScheduleDate)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()

    hReactors   = Database_Services('GetTableHandle', 'REACTOR')
    If Error_Services('NoError') then
        If Error_Services('NoError') then
            rv          = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            ReportData  = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
                If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
*                If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                    @ID     = ReactorNo : '****' : ScheduleDate
                    @RECORD = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)
                    If Error_Services('NoError') then
                        DetailPerfKeyIDs    = {ALL_DETAIL_CHILD_KEY_IDS}
                        If DetailPerfKeyIDs NE '' then
                            For Each PerfKeyID in DetailPerfKeyIDs using @VM
                                PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                                PerfRow<2>  = ''
                                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, PerfRow, True$, False$, True$)
                            Next DetailPerfKeyID
                        end
                    end
                end
            Repeat
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// ClearWafersProcessedDev
//
// Clears the processed wafers for all database rows the REACTOR_PERFORMANCE_NG table based on the indicated schedule date.
//----------------------------------------------------------------------------------------------------------------------
Service ClearWafersProcessedDev(ScheduleDate)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()

    hReactors   = Database_Services('GetTableHandle', 'REACTOR')
    If Error_Services('NoError') then
        If Error_Services('NoError') then
            rv          = Set_Status(0)
            RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
            Done        = False$
            ReportData  = ''
            Loop
                Readnext ReactorNo else Done = True$
            Until Done
                ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
                If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
*                If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                    @ID     = ReactorNo : '****' : ScheduleDate
                    @RECORD = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)
                    If Error_Services('NoError') then
                        DetailPerfKeyIDs    = {ALL_DETAIL_CHILD_KEY_IDS}
                        If DetailPerfKeyIDs NE '' then
                            For Each PerfKeyID in DetailPerfKeyIDs using @VM
                                PerfRow     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                                PerfRow<3>  = ''
                                Database_Services('WriteDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, PerfRow, True$, False$, True$)
                            Next DetailPerfKeyID
                        end
                    end
                end
            Repeat
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service

//----------------------------------------------------------------------------------------------------------------------
// CreateDailyPerformanceReportDev
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE_NG table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service CreateDailyPerformanceReportDev(ScheduleDate)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()

    DisplayDate     = Oconv(ScheduleDate, 'D4/')
    XLSXTemplate    = 'Fab Performance Daily Template.xlsx'
    objDocument     = Excel_Services('OpenDocument', TemplatesFolder : XLSXTemplate)
    If Error_Services('NoError') then
        hReactors   = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                rv          = Set_Status(0)
                RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                Done        = False$
                ReportData  = ''
                Loop
                    Readnext ReactorNo else Done = True$
                Until Done
                    ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                    ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                    SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                    @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
                    If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
*                    If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                        PerfKeyID           = ReactorNo : '****' : ScheduleDate
                        PerfRow             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                        DetailPerfKeyIDs    = PerfRow<1>
                        If DetailPerfKeyIDs NE '' then
                            For Each @ID in DetailPerfKeyIDs using @VM
                                @RECORD                 = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)
                                ReactorName             = {REACTOR_NAME}
                                EpiPartNo               = {EPI_PART_NO}
                                WafersExpected          = Oconv({TOTAL_WAFERS_EXPECTED}, 'MD2')
                                WafersExpectedRounded   = Oconv({TOTAL_WAFERS_EXPECTED_ROUNDED}, 'MD2')
                                ReportData             := ReactorType : @VM : ReactorName : @VM : DisplayDate : @VM : EpiPartNo : @VM : WafersExpected : @VM : WafersExpectedRounded : @VM : ReactorNo : @FM
                            Next DetailPerfKeyID
                        end else
                            ReportData := ReactorType : @VM : ReactorNo : @VM : DisplayDate : @VM : '' : @VM : '' : @VM : '' : @VM : ReactorNo : @FM
                        end
                    end
                Repeat
                ReportData[-1, 1]   = ''    ; // Strip off the last @FM
                ReportData          = SRP_Array('SortRows', ReportData, 'AR7' : @VM : 'AL4', 'LIST', @FM, @VM)
                CellRow             = 1     ; // Initialize the cell row to start entering data into.

                If ReportData NE '' then
                    For Each ReportRow in ReportData using @FM
                        ReactorType             = ReportRow[1, @VM]
                        ReactorName             = ReportRow[Col2() + 1, @VM]
                        DisplayDate             = ReportRow[Col2() + 1, @VM]
                        EpiPartNo               = ReportRow[Col2() + 1, @VM]
                        WafersExpected          = ReportRow[Col2() + 1, @VM]
                        WafersExpectedRounded   = ReportRow[Col2() + 1, @VM]
                        CellRow                += 1
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'A', CellRow, ReactorType)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'B', CellRow, ReactorName)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'E', CellRow, DisplayDate)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'F', CellRow, EpiPartNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'G', CellRow, WafersExpected)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'H', CellRow, WafersExpectedRounded)
                    Next ReportRow
                end
            end
            NewDocument = DisplayDate[7, 4] : '-' : DisplayDate[1, 2] : '-' : DisplayDate[4, 2] : ' Fab Performance.xlsx'
            Excel_Services('SaveDocument', objDocument, DailyReportsFolder : NewDocument)
            If Error_Services('NoError') then
*                Call Send_SRP_Mail(DailyReportsFolder : NewDocument)
                rv  = Set_Status(0)
                OSRead PerformanceReport from DailyReportsFolder : NewDocument then
                    OSWrite PerformanceReport to DailyReportsFolder : NewDocument
                    If Status() then
                        Error_Services('Add', 'Error writing ' : DailyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : DailyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateDailyPerformanceReportDev
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE_NG table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service UpdateDailyPerformanceReportDev(ScheduleDate)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    If Num(ScheduleDate) else ScheduleDate = Iconv(ScheduleDate, 'D')
    If ScheduleDate EQ '' then ScheduleDate = Date()

    DisplayDate     = Oconv(ScheduleDate, 'D4/')
    Document        = DisplayDate[7, 4] : '-' : DisplayDate[1, 2] : '-' : DisplayDate[4, 2] : ' Fab Performance.xlsx'
    If Excel_Services('DocumentExists', DailyReportsFolder : Document) then
        objDocument     = Excel_Services('OpenDocument', DailyReportsFolder : Document)
    end else
        Error_Services('Add', 'Unable to find ' : Document : ' in the ' : Service : ' service.')
    end
    If Error_Services('NoError') then
        @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
        If Error_Services('NoError') then
            CellRow             = 1     ; // Initialize the cell row to start entering data into.
            Loop
                CellRow    += 1
                Date        = Excel_Services('GetCellValue', objDocument, 'SSR', 'E', CellRow)
            Until Date EQ ''
                ReactorName         = Excel_Services('GetCellValue', objDocument, 'SSR', 'B', CellRow)
                ReactorNo           = ReactorName[1, 'F/']
                EpiPartNo           = Excel_Services('GetCellValue', objDocument, 'SSR', 'F', CellRow)
                WeekDate            = Date_Services('GetISOWeekDate', ScheduleDate)
                WeekYear            = WeekDate[1, '-']
                WeekNo              = WeekDate[Col2() + 1, '-']
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                @ID                 = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*' : ScheduleDate
                @RECORD             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)

                WafersProcessed     = Oconv({TOTAL_WAFERS_PROCESSED}, 'MD2')
                WafersDelta         = Oconv({TOTAL_WAFERS_DELTA}, 'MD2')
                WafersDeltaRounded  = Oconv({TOTAL_WAFERS_DELTA_ROUNDED}, 'MD2')
                Excel_Services('SetCellValue', objDocument, 'SSR', 'I', CellRow, WafersProcessed)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'K', CellRow, WafersDelta)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'L', CellRow, WafersDeltaRounded)
            Repeat
            Excel_Services('SaveDocument', objDocument)
            If Error_Services('NoError') then
*                Call Send_SRP_Mail(DailyReportsFolder : Document)
                rv  = Set_Status(0)
                OSRead PerformanceReport from DailyReportsFolder : Document then
                    OSWrite PerformanceReport to DailyReportsFolder : Document
                    If Status() then
                        Error_Services('Add', 'Error writing ' : DailyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : DailyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end else
                ErrorMessage = Error_Services('GetMessage')
            end
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// CreateWeeklyPerformanceReportDev
//
// Creates weekly performance tracking report using the database rows in the REACTOR_PERFORMANCE_NG table. This creates an
// Excel spreadsheet using the "Fab Performance Weekly Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service CreateWeeklyPerformanceReportDev(YearWeekNo)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    // YearWeekNo looks like 2017-W44. For testing purposes it might be easier to pass in a date and the YearWeekNo
    // will be calculated.
    If (Num(Iconv(YearWeekNo, 'D')) AND (Iconv(YearWeekNo, 'D') NE '')) OR Num(YearWeekNo) then
        // A internal or external formatted date was passed in.
        YearWeekNo = Date_Services('GetISOWeekDate', YearWeekNo)
        YearWeekNo = Field(YearWeekNo, '-', 1, 2)
    end

    WeekYear        = YearWeekNo[1, '-']
    WeekNo          = YearWeekNo[Col2() + 1, '-']
    XLSXTemplate    = 'Fab Performance Weekly Template.xlsx'
    objDocument     = Excel_Services('OpenDocument', TemplatesFolder : XLSXTemplate)
    If Error_Services('NoError') then
        hReactors   = Database_Services('GetTableHandle', 'REACTOR')
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                rv          = Set_Status(0)
                RList('SELECT REACTOR BY REACT_NO', 5, '', '', '')
                Done        = False$
                ReportData  = ''
                Loop
                    Readnext ReactorNo else Done = True$
                Until Done
                    ReactorRow      = Database_Services('ReadDataRow', 'REACTOR', ReactorNo, True$, 0)
                    ReactorType     = ReactorRow<REACTOR_REACT_TYPE$>
                    SecondChamber   = ReactorRow<REACTOR_SECOND_CHAMBER$>
                    @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
                    If (((ReactorNo GT 0) AND (ReactorNo LE 79))) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) then
*                    If (ReactorNo NE 0) AND (ReactorNo LE 74) AND ((ReactorType EQ 'EPP' AND SecondChamber NE '') OR (ReactorType NE 'EPP')) AND (ReactorType NE 'GAN') then
                        PerfKeyID           = ReactorNo : '**' : WeekYear : '*' : WeekNo : '*'
                        PerfRow             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', PerfKeyID, True$, 0)
                        DetailPerfKeyIDs    = PerfRow<1>
                        If DetailPerfKeyIDs NE '' then
                            DetailPerfKeyIDs            = SRP_Array('SortRows', DetailPerfKeyIDs, 'AL2', 'LIST', @VM, '*')
                            PrevEpiPartNo               = ''
                            TotalWafersExpected         = 0
                            TotalWafersExpectedRounded  = 0
                            For Each @ID in DetailPerfKeyIDs using @VM
                                @RECORD                     = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)
                                ReactorName                 = {REACTOR_NAME}
                                EpiPartNo                   = {EPI_PART_NO}
                                If (EpiPartNo NE PrevEpiPartNo) AND (PrevepiPartNo NE '') then
                                    // Epi Part No has changed. Add the current totals for the previous Epi Part No to
                                    // the report and reset the variables.
                                    ReportData                 := ReactorType : @VM : ReactorName : @VM : YearWeekNo : @VM : PrevEpiPartNo : @VM : TotalWafersExpected : @VM : TotalWafersExpectedRounded : @VM : ReactorNo : @FM
                                    TotalWafersExpected         = 0
                                    TotalWafersExpectedRounded  = 0
                                end
                                PrevEpiPartNo               = EpiPartNo
                                TotalWafersExpected        += Oconv({TOTAL_WAFERS_EXPECTED}, 'MD2')
                                TotalWafersExpectedRounded += Oconv({TOTAL_WAFERS_EXPECTED_ROUNDED}, 'MD2')
                            Next DetailPerfKeyID
                            // Add the final total values to the report.
                            ReportData := ReactorType : @VM : ReactorName : @VM : YearWeekNo : @VM : PrevEpiPartNo : @VM : TotalWafersExpected : @VM : TotalWafersExpectedRounded : @VM : ReactorNo : @FM
                        end else
                            ReportData := ReactorType : @VM : ReactorNo : @VM : YearWeekNo : @VM : '' : @VM : '' : @VM : '' : @VM : ReactorNo : @FM
                        end
                    end
                Repeat
                ReportData[-1, 1]   = ''    ; // Strip off the last @FM
                ReportData          = SRP_Array('SortRows', ReportData, 'AR7' : @VM : 'AL4', 'LIST', @FM, @VM)
                CellRow             = 1     ; // Initialize the cell row to start entering data into.

                If ReportData NE '' then
                    For Each ReportRow in ReportData using @FM
                        ReactorType             = ReportRow[1, @VM]
                        ReactorName             = ReportRow[Col2() + 1, @VM]
                        YearWeekNo              = ReportRow[Col2() + 1, @VM]
                        EpiPartNo               = ReportRow[Col2() + 1, @VM]
                        WafersExpected          = ReportRow[Col2() + 1, @VM]
                        WafersExpectedRounded   = ReportRow[Col2() + 1, @VM]
                        CellRow                += 1
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'A', CellRow, ReactorType)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'B', CellRow, ReactorName)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'E', CellRow, YearWeekNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'F', CellRow, EpiPartNo)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'G', CellRow, WafersExpected)
                        Excel_Services('SetCellValue', objDocument, 'SSR', 'H', CellRow, WafersExpectedRounded)
                    Next ReportRow
                end
            end
            NewDocument = YearWeekNo : ' Fab Performance.xlsx'
            Excel_Services('SaveDocument', objDocument, WeeklyReportsFolder : NewDocument)
            If Error_Services('NoError') then
*                Call Send_SRP_Mail(WeeklyReportsFolder : NewDocument)
                rv  = Set_Status(0)
                OSRead PerformanceReport from WeeklyReportsFolder : NewDocument then
                    OSWrite PerformanceReport to WeeklyReportsFolder : NewDocument
                    If Status() then
                        Error_Services('Add', 'Error writing ' : WeeklyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : WeeklyReportsFolder : NewDocument : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


//----------------------------------------------------------------------------------------------------------------------
// UpdateWeeklyPerformanceReportDev
//
// Creates daily performance tracking report using the database rows in the REACTOR_PERFORMANCE_NG table. This creates an
// Excel spreadsheet using the "Fab Performance Daily Template.xlsx" file as a template.
//----------------------------------------------------------------------------------------------------------------------
Service UpdateWeeklyPerformanceReportDev(YearWeekNo)

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' begins.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

    // YearWeekNo looks like 2017-W44. For testing purposes it might be easier to pass in a date and the YearWeekNo
    // will be calculated.
    If (Num(Iconv(YearWeekNo, 'D')) AND (Iconv(YearWeekNo, 'D') NE '')) OR Num(YearWeekNo) then
        // A internal or external formatted date was passed in.
        YearWeekNo = Date_Services('GetISOWeekDate', YearWeekNo)
        YearWeekNo = Field(YearWeekNo, '-', 1, 2)
    end

    WeekYear        = YearWeekNo[1, '-']
    WeekNo          = YearWeekNo[Col2() + 1, '-']
    Document        = YearWeekNo : ' Fab Performance.xlsx'
    If Excel_Services('DocumentExists', WeeklyReportsFolder : Document) then
        objDocument     = Excel_Services('OpenDocument', WeeklyReportsFolder : Document)
    end else
        Error_Services('Add', 'Unable to find ' : Document : ' in the ' : Service : ' service.')
    end
    If Error_Services('NoError') then
        @DICT           = Database_Services('GetTableHandle', 'DICT.REACTOR_PERFORMANCE_NG')
        If Error_Services('NoError') then
            CellRow             = 1     ; // Initialize the cell row to start entering data into.
            Loop
                CellRow    += 1
                Date        = Excel_Services('GetCellValue', objDocument, 'SSR', 'E', CellRow)
            Until Date EQ ''
                ReactorName         = Excel_Services('GetCellValue', objDocument, 'SSR', 'B', CellRow)
                ReactorNo           = ReactorName[1, 'F/']
                EpiPartNo           = Excel_Services('GetCellValue', objDocument, 'SSR', 'F', CellRow)
                // Full KeyID = {REACTOR_NO} * {EPI_PART_NO} * {WEEK_YEAR} * {WEEK_NO} * {SCHEDULE_DATE}
                @ID                 = ReactorNo : '*' : EpiPartNo : '*' : WeekYear : '*' : WeekNo : '*'
                @RECORD             = Database_Services('ReadDataRow', 'REACTOR_PERFORMANCE_NG', @ID, True$, 0)
                WafersProcessed     = Oconv({TOTAL_WAFERS_PROCESSED}, 'MD2')
                WafersDelta         = Oconv({TOTAL_WAFERS_DELTA}, 'MD2')
                WafersDeltaRounded  = Oconv({TOTAL_WAFERS_DELTA_ROUNDED}, 'MD2')

                Excel_Services('SetCellValue', objDocument, 'SSR', 'J', CellRow, WafersDelta)
                Excel_Services('SetCellValue', objDocument, 'SSR', 'K', CellRow, WafersDeltaRounded)
            Repeat
            Excel_Services('SaveDocument', objDocument)
            If Error_Services('NoError') then
*                Call Send_SRP_Mail(WeeklyReportsFolder : Document)
                rv  = Set_Status(0)
                OSRead PerformanceReport from WeeklyReportsFolder : Document then
                    OSWrite PerformanceReport to WeeklyReportsFolder : Document
                    If Status() then
                        Error_Services('Add', 'Error writing ' : WeeklyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                    end
                end else
                    Error_Services('Add', 'Error reading ' : WeeklyReportsFolder : Document : ' in the ' : Service : ' service. Status: ' : Status())
                end
            end
        end
    end

    LogData = LoggingDTM : @FM : Machine : @FM : CommandLine : @FM : Service : ' ends.'
    Logging_Services('AppendLog', objLog, LogData, @RM, @FM, False$)

end service


Service GetReactModeHistoryDev(ReactNo, DaysToReport)
    
    Response = ''
    If ( (ReactNo NE '') and (DaysToReport NE '') ) then
        Open 'REACT_MODE' to ReactModeTable then
            OPEN 'DICT.REACT_MODE' TO @DICT then
                StartDt = Date() - DaysToReport
                StartDt = OCONV(StartDt,'D4/')
                SelectSent = 'SELECT REACT_MODE WITH REACT_NO = ':QUOTE(ReactNo):' AND WITH START_DT > ':QUOTE(StartDt):'  BY-DSND START_DTM '
                Set_Status(0)
                errCode = ''
                RList(SelectSent, TARGET_ACTIVELIST$, "", "", "")
                If Not(Get_Status(errCode)) then
                    @RecCount = 0
                    EOF       = False$
                    Loop
                        Readnext @ID else EOF = True$
                    While EOF EQ False$    
                        READO @RECORD FROM ReactModeTable,@ID then
                            @RecCount    += 1
                            StartDTM      = OCONV({START_DTM},'DT2/^HS')
                            StopDTM       = OCONV({STOP_DTM},'DT2/^HS')
                            ElapsedHrs    = OCONV({ELAP_HRS},'MD2,')
                            Mode          = OCONV({MODE},'[REACT_MODE_CONV]')
                            StartRLID     = {START_RL_ID}
                            ProbCatIDs    = {PROB_CAT_ID}
                            ProbCatDescs  = {PROB_CAT_DESC}
                            ServIDs       = {SERV_ID}
                            ServDescs     = {SERV_DESC}
                            StartUser     = {START_USER}
                            StopUser      = {STOP_USER}
                            Notes         = {START_NOTES}

*                            SWAP ' ' WITH CRLF$ IN StartDTM
*                            SWAP ' ' WITH CRLF$ IN StopDTM

                            FOR I = 1 TO COUNT(ProbCatIDs,@VM) + (ProbCatIDs NE '')
                                ProbCatDescs<1,I> = ProbCatIDs<1,I>:' - ':ProbCatDescs<1,I>
                            NEXT I

                            FOR I = 1 TO COUNT(ServIDs,@VM) + (ServIDs NE '')
                                ServDescs<1,I> = ServIDs<1,I>:' - ':ServDescs<1,I>
                            NEXT I

                            SWAP @VM WITH CRLF$ IN ProbCatDescs
                            SWAP @VM WITH CRLF$ IN ServDescs

                            Response<@RecCount, 1> = StartDTM                        
                            Response<@RecCount, 2> = ElapsedHrs
                            Response<@RecCount, 3> = Mode
                            Response<@RecCount, 4> = Notes
                            Response<@RecCount, 5> = StartRLID
                            Response<@RecCount, 6> = ProbCatDescs
                            Response<@RecCount, 7> = ServDescs
                            Response<@RecCount, 8> = StartUser
                        end
                    Repeat
                end else
                    ErrMsg = 'Error in service ':Service:'. Error code: ':errCode:'.'
                    Error_Services('Add', ErrorMsg)          
                end
            end else
                ErrorMsg = 'Unable to Open "DICT.REACT_MODE" table!'
                Error_Services('Add', ErrorMsg)            
            end
        end else
            ErrorMsg = 'Unable to Open "REACT_MODE" table!'
            Error_Services('Add', ErrorMsg)
        end
    end
    
end service


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



