Function Scheduling_Services(@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 : Scheduling_Services 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) 06/01/17 dmb Original programmer. - [EPIOI-8] ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert APP_INSERTS $insert SERVICE_SETUP $insert REACTOR_EQUATES $insert SCHED_DET_EQUATES $insert WO_LOG_EQUATES $insert COMPANY_EQUATES $insert PROD_VER_EQUATES $insert PRS_LAYER_EQU $insert SCHEDULE_EVENT_SUMMMARY_EQUATES $insert WO_SCHEDULE_EQUATES $insert SCHED_DET_KEY_IDS_EQUATES $insert WORK_ORDER_CHILD_KEY_IDS_EQUATES $insert SCHEDULER_EQUATES Equ NOTIFICATION_PERIOD$ to 12 Equ SECONDS_PER_DAY$ to 86400 Equ MIDNIGHT_AM$ to 0 ; // Midnight represented as the beginning of the day. Equ MIDNIGHT_PM$ to 86400 ; // Midnight represented as the end of the day. Equ SIX_AM$ to 21600 Equ NOON$ to 43200 Equ SIX_PM$ to 64800 Declare subroutine Error_Services, Scheduling_Services, Memory_Services, RList, Database_Services, SRP_JSON Declare function SRP_Array, Scheduling_Services, Memory_Services, Database_Services, SRP_Sort_Array, Work_Order_Services Declare function Epi_Part_Services, SRP_Math, SRP_Hash, obj_Prod_Spec, Reactor_Services, SRP_JSON GoToService else Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.') end Return Response else '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Services //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //---------------------------------------------------------------------------------------------------------------------- // GetScheduleEvents // // Returns a JSON formatted array of schedule event objects. Schedule events will be filtered according to the search // arguments passed in. //---------------------------------------------------------------------------------------------------------------------- Service GetScheduleEvents(StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut) Debug If Not(Num(StartDate)) then StartDate = Iconv(StartDate, 'D') If Not(Num(EndDate)) then EndDate = Iconv(EndDate, 'D') If BlockOut NE True$ then BlockOut = False$ ScheduleEvents = '' ServiceKeyID := '*' : StartDate : '*' : EndDate : '*' : ReactorNo : '*' : WorkOrderNo : '*' : BlockOut ServiceKeyID = SRP_Hash(ServiceKeyID, 'SHA-1') If (StartDate NE '') then If Memory_Services('IsValueCurrent', ServiceKeyID, 15, True$) then ScheduleEvents = Memory_Services('GetValue', ServiceKeyID) end else If SRP_JSON(objScheduleEvents, 'NEW', 'OBJECT') then If SRP_JSON(objScheduleEventArray, 'NEW', 'ARRAY') then SchedulerKeyIDs = Scheduling_Services('SearchScheduler', StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut) If Error_Services('NoError') then If SchedulerKeyIDs NE '' then SchedulerKeyIDs = SRP_Array('SortRows', SchedulerKeyIDs, 'AR1' : @FM : 'AR2' : @FM : 'AR3', 'LIST', @FM, '*') For Each SchedulerKeyID in SchedulerKeyIDs using @FM setting fPos ReactorNo = SchedulerKeyID[1, '*'] StartDate = SchedulerKeyID[Col2() + 1, '*'] EventID = SchedulerKeyID[Col2() + 1, '*'] SRP_Stopwatch('Start', 'GetScheduleEvent') ScheduleEvent = Scheduling_Services('GetScheduleEvent', ReactorNo, StartDate, EventID, False$) SRP_Stopwatch('Stop', 'GetScheduleEvent') If SRP_JSON(objScheduleEvent, 'PARSE', ScheduleEvent) EQ '' then SRP_JSON(objScheduleEventArray, 'ADD', objScheduleEvent) SRP_JSON(objScheduleEvent, 'RELEASE') end Next SchedDetKeyID SRP_JSON(objScheduleEvents, 'SET', 'ScheduleEvents', objScheduleEventArray) ScheduleEvents = SRP_JSON(objScheduleEvents, 'STRINGIFY', 'FAST') Memory_Services('SetValue', ServiceKeyID, ScheduleEvents) end end SRP_JSON(objScheduleEventArray, 'RELEASE') end else Error_Services('Add', 'Error creating objScheduleEventArray in the ' : Service : ' service.') end SRP_JSON(objScheduleEvents, 'RELEASE') end else Error_Services('Add', 'Error creating objScheduleEvents in the ' : Service : ' service.') end end end else Error_Services('Add', 'StartDate argument was missing from the ' : Service : ' service.') end Response = ScheduleEvents end service //---------------------------------------------------------------------------------------------------------------------- // SearchScheduler // // Returns an array of SCHEDULER Key IDs based on the search parameters provided. //---------------------------------------------------------------------------------------------------------------------- Service SearchScheduler(StartDate, EndDate, ReactorNo, WorkOrderNo, BlockOut) SchedulerKeyIDs = '' If Not(Num(StartDate)) then StartDate = Iconv(StartDate, 'D') If Not(Num(EndDate)) then EndDate = Iconv(EndDate, 'D') If BlockOut NE True$ then BlockOut = False$ ServiceKeyID := '*' : StartDate : '*' : EndDate : '*' : ReactorNo : '*' : WorkOrderNo : '*' : BlockOut ServiceKeyID = SRP_Hash(ServiceKeyID, 'SHA-1') If (StartDate NE '') then If EndDate NE '' then DateRange = Oconv(StartDate - 1, 'D4/') : '~' : Oconv(EndDate + 1, 'D4/') DateRangeMatch = Database_Services('SearchIndex', 'SCHEDULER', 'START_DATE', DateRange, True$) end else DateRangeMatch = Database_Services('SearchIndex', 'SCHEDULER', 'START_DATE', '>=' : StartDate, True$) end Transfer DateRangeMatch to SchedulerKeyIDs If ReactorNo NE '' then ReactorMatch = Database_Services('SearchIndex', 'SCHEDULER', 'REACTOR_NO', ReactorNo, True$) SchedulerKeyIDs = SRP_Array('Join', SchedulerKeyIDs, ReactorMatch, 'AND', @FM) end If WorkOrderNo NE '' then WOChildKeyIDs = Database_Services('ReadDataRow', 'WORK_ORDER_CHILD_KEY_IDS', WorkOrderNo, True$, 5) WorkOrderMatch = WOChildKeyIDs Convert @VM to @FM in WorkOrderMatch SchedulerKeyIDs = SRP_Array('Join', SchedulerKeyIDs, WorkOrderMatch, 'AND', @FM) end // Only filter out or return Block Out events if the Reactor No is specified. Otherwise, all Block Out // will be included. If ReactorNo NE '' then BlockOutMatch = Database_Services('SearchIndex', 'SCHEDULER', 'BLOCK_OUT', 'Yes', True$) If BlockOut then SchedulerKeyIDs = SRP_Array('Join', SchedulerKeyIDs, BlockOutMatch, 'AND', @FM) end else SchedulerKeyIDs = SRP_Array('Join', SchedulerKeyIDs, BlockOutMatch, 'NOT', @FM) end end end else Error_Services('Add', 'StartDate argument was missing from the ' : Service : ' service.') end Response = SchedulerKeyIDs end service //---------------------------------------------------------------------------------------------------------------------- // AdjustScheduleEvents // // Adjusts the schedule detail rows for the indicated reactor. This service will add or remove events for the indicated // work order based on the number of events specified. This means other events for other work orders will be shifted // as needed to allow the specified work order to have continuous events as well as eliminating gaps in the scheduler // calendar. Note: blocked dates will need to be respected. //---------------------------------------------------------------------------------------------------------------------- Service AdjustScheduleEvents(ReactorNo, WorkOrderNo, NumberOfDays, Date, Description) Debug NumberOfEvents = SRP_Math('CEILING', NumberOfDays, '', 0) If Date EQ '' then Date = Date() If Not(Num(Date)) then Date = Iconv(Date, 'D') AllWorkOrderEvents = '' PreviousEvents = '' ; // Used to track events that already exist on the schedule for this date that ; // should be left alone. RemoveEventKeyIDs = '' ; // List of Scheduler Key IDs that will be removed when the service is finished. AddEvents = '' ; // List of Scheduler Events (Appointments) that will be added when the service is finished. If (ReactorNo NE '') AND (WorkOrderNo NE '') AND (NumberOfEvents NE '') AND (NumberOfEvents NE 0) then // Get the block out events for this reactor. BlockOutEvents = Scheduling_Services('GetScheduleEvents', Date - 1, '', ReactorNo, '', True$) BlockOutKeys = SRP_Array('Rotate', BlockOutEvents, @FM, @VM) BlockOutKeys = BlockOutKeys<2> BlockOutDates = BlockOutKeys Convert @VM to @FM in BlockOutDates Convert '*' to @VM in BlockOutDates BlockOutDates = SRP_Array('Rotate', BlockOutDates, @FM, @VM) BlockOutDates = BlockOutDates<4> // Get the indicated work order events for this reactor. WorkOrderEvents = Scheduling_Services('GetScheduleEvents', Date - 1, '', ReactorNo, WorkOrderNo, False$) WorkOrderKeys = SRP_Array('Rotate', WorkOrderEvents, @FM, @VM) WorkOrderKeys = WorkOrderKeys<2> Convert @VM to @FM in WorkOrderKeys Convert '*' to @VM in WorkOrderKeys WorkOrderKeys = SRP_Array('Rotate', WorkOrderKeys, @FM, @VM) WorkOrderKeys = Delete(WorkOrderKeys, 1, 0, 0) ; // Remove the work orders WorkOrderKeys = Delete(WorkOrderKeys, 1, 0, 0) ; // Remove the EPI Part WorkOrderKeys = SRP_Array('Rotate', WorkOrderKeys, @FM, @VM) Convert @VM to '*' in WorkOrderKeys // Get the other work order events for this reactor. OtherEvents = Scheduling_Services('GetScheduleEvents', Date - 1, '', ReactorNo, '', False$) OtherEvents = SRP_Array('Join', OtherEvents, WorkOrderEvents, 'NOT', @FM) OtherKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) OtherKeys = OtherKeys<2> Convert @VM to @FM in OtherKeys Convert '*' to @VM in OtherKeys OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the work orders OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the EPI Part OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) Convert @VM to '*' in OtherKeys // Initialize variables. EventCount = 0 If OtherKeys NE '' then // There are other schedule events for this reactor. Check to see if any of them fall on the same day the date assigned to the // service (either the passed in date or the default date). If any exist, a determination needs to be made to see which one // (if any) is the tail end of an existing work order series. // Get all existing events for the previous and current dates. CompareEvents = Scheduling_Services('GetScheduleEvents', Date - 1, Date, ReactorNo, '', False$) If CompareEvents NE '' then // There are other events already scheduled for the start date. Loop through each event to see if it is the tail end of an // existing work order series. For Each CompareEvent in CompareEvents using @FM CompareSchedulerKeyID = CompareEvent<0, 2> CompareWorkOrder = CompareSchedulerKeyID[1, '*'] CompareScheduleKeyID = Field(CompareSchedulerKeyID, '*', 3, 3) // Check to see if this work order has an event in the past. Events = Scheduling_Services('GetScheduleEvents', Date - 1, Date - 1, ReactorNo, CompareWorkOrder, False$) If Events NE '' then PreviousEvents := Events : @FM Next ScheduleEvent PreviousEvents[-1, 1] = '' If PreviousEvents NE '' then // Since there is one or more work orders already on the schedule that should not be adjusted, find the one with the date furthest // out into the future. If its date is after the proposed start date, adjust the start date to occur on the same date. LatestDate = '' For Each PreviousEvent in PreviousEvents using @FM PreviousSchedulerKeyID = PreviousEvent<0, 2> PreviousWorkOrder = PreviousSchedulerKeyID[1, '*'] Events = Scheduling_Services('GetScheduleEvents', Date, '', ReactorNo, PreviousWorkOrder, False$) If Events NE '' then LastEvent = Events[-1, 'B' : @FM] LastSchedulerKeyID = LastEvent<0, 2> LastDate = Field(LastSchedulerKeyID, '*', 4, 1) If LastDate GT LatestDate then Transfer LastDate to LatestDate end Next PreviousEvent If LatestDate GT Date then Transfer LatestDate to Date end end end If NumberOfEvents LT 0 then // The number of events is being reduced. Remove these from the end for this work order. Then find all other // work orders in the sfame reactor and shift them back by the number of events indicated. NumberOfEvents = Neg(NumberOfEvents) // Reverse sort the keys and events. Always keep the keys and events in sync. WorkOrderKeys = SRP_Array('SortRows', WorkOrderKeys, 'DR2', 'LIST', @FM, '*') WorkOrderEvents = SRP_Array('SortRows', WorkOrderEvents, 'DR3', 'LIST', @FM, @VM) // Remove the extra work order events. Loop WorkOrderKey = WorkOrderKeys<1> WorkOrderEvent = WorkOrderEvents<1> WorkOrderKeys = Delete(WorkOrderKeys, 1, 0, 0) WorkOrderEvents = Delete(WorkOrderEvents, 1, 0, 0) EventCount += 1 // Delete Schedule Detail record. RemoveEventKeyIDs := WorkOrderEvent<0, 2> : @FM Scheduling_Services('DeleteScheduleDetail', WorkOrderKey) Until (EventCount GE NumberOfEvents) Repeat // Now that the extra work order events have been removed, check to see if there are any more // work order keys. If so, then use the last schedule date for the work order to be the // starting schedule date. Otherwise, go back to the previous schedule date. If WorkOrderKeys NE '' then ScheduleDate = Field(WorkOrderKeys[1, @FM], '*', 2, 1) end else // All of the work order events were removed so use the date of the first event and then // subtract 1 to be safe in case the tip was not stacked with the tail of a previous // list of work orders. ScheduleDate = Field(WorkOrderKey, '*', 2, 1) - 1 // However, if the previous list of work orders is extended into the future already, then // use the latest date from that list instead since this is definitely the correct starting date. If ScheduleDate LT Date then ScheduleDate = Date end end else // The number of events is being increased (or added if this is a new work order). Increased work order events will be added to // the end of the existing eents. New work order events will be added to the indicated date unless a series of work orders // was already on the schedule. In this case the end of this work order series will be used as the start date. All other work order // events in the same reactor will be shifted forward. If WorkOrderKeys NE '' then // An already scheduled work order is being adjusted. Get the last scheduled event and read the schedule detail record to use // as a template for new events that will be added. ScheduleKeyID = WorkOrderKeys[-1, 'B' : @FM] ScheduleEvent = WorkOrderEvents[-1, 'B' : @FM] ScheduleDate = Field(ScheduleKeyID, '*', 2, 1) * ScheduleDetRow = Scheduling_Services('GetScheduleDetail', ScheduleKeyID) ScheduleDetRow = Database_Services('ReadDataRow', 'SCHED_DET', ScheduleKeyID) ScheduleDetRow = '' ; // New schedule detail rows will not have any description. end else // There are no events for the indicated work order. This implies that a new work order is being added to the scheduler. // In order to work with the adjust logic, a pseudo-event needs to be created. This will also provide a template for the // new work order events being added to the schedule. Note, a sequence of 99 for the ScheduleKeyID is used to avoid any // possible conflicts with real schedule Key IDs. ScheduleKeyID = ReactorNo : '*' : Date : '*' : 99 WOLogRow = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo) NoteFlag = Description NE '' CustNo = WOLogRow EpiPartNo = WOLogRow WOClosedFlag = False$ HotLotFlag = WOLogRow NE '' If CustNo NE '' then CompanyRow = Database_Services('ReadDataRow', 'COMPANY', CustNo) CustName = CompanyRow end else CustName = '' end Begin Case Case CustName _EQC 'International Rectifier' ; CustName = 'IR' Case CustName _EQC 'IRNewport' ; CustName = 'Newport' Case CustName _EQC 'IFX (Kulim)' ; CustName = 'Kulim' Case CustName _EQC 'IFX Austria AG' ; CustName = 'Austria' Case CustName _EQC 'Tower Semiconductor' ; CustName = 'Tower' End Case StartDTM = (Date - 1) : '.0' EndDTM = Date : '.0' CurrentFlag = True$ Begin Case Case HotLotFlag BackColor = 'LightCoral' Case Otherwise$ BackColor = 'LightSteelBlue' End Case ForeColor = 'Black' Title = WorkOrderNo : ' (' : CustName : ' ' : EpiPartNo : ')' ScheduleEvent = ReactorNo : @VM : WorkOrderNo : '*' : EpiPartNo : '*' : ScheduleKeyID : @VM : StartDTM : @VM : EndDTM : @VM : BackColor : @VM : ForeColor : @VM : Title ScheduleEvent := @VM : Description : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : HotLotFlag : @SVM : NoteFlag : @SVM : WOClosedFlag : @SVM : CurrentFlag // Set the schedule date to be one day before the intended start date. This will allow the adjust logic to use the actual schedule start date. ScheduleDate = Date - 1 // Create the schedule detail database row. ScheduleDetRow = '' ScheduleDetRow = WorkOrderNo ScheduleDetRow = Description end // Create the new work order events. ScheduleDate += 1 DayRemain = NumberOfDays Loop Locate ScheduleDate in BlockOutDates using @VM setting vPos then // Do not schedule on the same date as a block out event. end else Sequence = 1 KeyFound = False$ ; // Assume key not found for now. Loop NewScheduleKeyID = ReactorNo : '*' : ScheduleDate : '*' : Sequence Locate NewScheduleKeyID in OtherKeys using @FM setting fPos then Sequence += 1 end else // Increase the event count to determine if enough events have been created. EventCount += 1 KeyFound = True$ If WorkOrderKeys EQ '' then WorkOrderKeys = NewScheduleKeyID end else WorkOrderKeys := @FM : NewScheduleKeyID end SchedulerKey = ScheduleEvent<0, 2> SchedulerKey = Field(SchedulerKey, '*', 1, 2) : '*' : NewScheduleKeyID ScheduleEvent<0, 2> = SchedulerKey If @UserName EQ 'DANIEL_ST' then If EventCount LT NumberOfEvents then // Assume this schedule detail will be a full day. DayLengthCode = 4 end else // Calculate the portion of the day using the remaining time available. DayLengthCode = DayRemain / .25 end ScheduleDetRow = DayLengthCode DayLength = DayLengthCode * .25 Swap '0.' with '.' in DayLength Begin Case Case (EventCount EQ 1) OR (EventCount EQ NumberOfEvents) // This event must be the Head or Tail of the event series. Assume it will start // at the beginning of the day. StartDTM = ScheduleDate : '.0' If DayLength EQ 1 then // This event is a full day. EndDTM = ScheduleDate + 1 : '.0' end else EndDTM = ScheduleDate : DayLength end Case Otherwise$ // This event must be a standalone event or the middle of the event series. StartDTM = ScheduleDate : '.0' EndDTM = ScheduleDate + 1 : '.0' End Case end else StartDTM = ScheduleDate : '.0' EndDTM = ScheduleDate + 1 : '.0' end ScheduleEvent<0, 3> = StartDTM ScheduleEvent<0, 4> = EndDTM If WorkOrderEvents EQ '' then WorkOrderEvents = ScheduleEvent end else WorkOrderEvents := @FM : ScheduleEvent end // Deduct from the number of days needed to create events for. DayRemain -= 1 // Create a new Schedule Event AddEvents := ScheduleEvent : @FM Scheduling_Services('SetScheduleDetail', NewScheduleKeyID, ScheduleDetRow) end Until KeyFound Repeat end ScheduleDate += 1 Until (EventCount GE NumberOfEvents) Repeat // This starts the other schedule events on the same date as the last new work order event. ScheduleDate = Field(NewScheduleKeyID, '*', 2, 1) end // Now that the current work order list has been adjusted whether it is being reduced or increased, call the logic // to adjust other work order events. GoSub AdjustOtherWorkOrderEvents end else Error_Services('Add', 'ReactorNo, WorkOrderNo, or NumberOfEvents argument was missing from the ' : Service : ' service.') end // This service does not return a response to complete it's purpose, but for analysis and reporting purposes the adjusted // events will be returned to the caller if requested. RemoveEventKeyIDs[-1, 1] = '' AddEvents[-1, 1] = '' Response = RemoveEventKeyIDs : @RM : AddEvents end service //---------------------------------------------------------------------------------------------------------------------- // AddBlockOutEvents // // Adds one or more block out events to the schedule. //---------------------------------------------------------------------------------------------------------------------- Service AddBlockOutEvents(ReactorNo, StartDate, EndDate, Description) // Always have an end date. No end date means this block out event will only be for one day. If EndDate EQ '' then EndDate = StartDate AllWorkOrderEvents = '' PreviousEvents = '' ; // Used to track events that already exist on the schedule for this date that ; // should be left alone. WorkOrderKeys = '' WorkOrderEvents = '' RemoveEventKeyIDs = '' AddEvents = '' If (ReactorNo NE '') AND (StartDate NE '') then // Get the block out events for this reactor. BlockOutEvents = Scheduling_Services('GetScheduleEvents', StartDate - 1, '', ReactorNo, '', True$) // Get the other work order events for this reactor. OtherEvents = Scheduling_Services('GetScheduleEvents', StartDate - 1, '', ReactorNo, '', False$) OtherEvents = SRP_Array('Join', OtherEvents, BlockOutEvents, 'NOT', @FM) OtherKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) OtherKeys = OtherKeys<2> Convert @VM to @FM in OtherKeys Convert '*' to @VM in OtherKeys OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the work orders OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the EPI Part OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) Convert @VM to '*' in OtherKeys // In order to work with the adjust logic, a pseudo-event needs to be created. This will also provide a template for the // new work order events being added to the schedule. Note, a sequence of 99 for the ScheduleKeyID is used to avoid any // possible conflicts with real schedule Key IDs. ScheduleKeyID = ReactorNo : '*' : StartDate : '*' : 99 NoteFlag = Description NE '' WorkOrderNo = '' CustNo = '' EpiPartNo = '' WOClosedFlag = False$ HotLotFlag = False$ CustName = '' StartDTM = StartDate : '.0' EndDTM = (StartDate + 1) : '.0' CurrentFlag = False$ BackColor = 'Plum' ForeColor = 'Black' BlockOutType = '' ; // Future enhancement. Title = 'Block Out' : ' (' : BlockOutType : ')' ScheduleEvent = ReactorNo : @VM : WorkOrderNo : '*' : EpiPartNo : '*' : ScheduleKeyID : @VM : StartDTM : @VM : EndDTM : @VM : BackColor : @VM : ForeColor : @VM : Title ScheduleEvent := @VM : Description : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : @VM : HotLotFlag : @SVM : NoteFlag : @SVM : WOClosedFlag : @SVM : CurrentFlag // Set the schedule date to the intended start date. ScheduleDate = StartDate // Create the schedule detail database row. ScheduleDetRow = '' ScheduleDetRow = Description ScheduleDetRow = True$ ScheduleDetRow = '' ; // Future enhancement. // Create the new block out events. For ScheduleDate = StartDate to EndDate Sequence = 1 KeyFound = False$ ; // Assume key not found for now. Loop NewScheduleKeyID = ReactorNo : '*' : ScheduleDate : '*' : Sequence Locate NewScheduleKeyID in OtherKeys using @FM setting fPos then Sequence += 1 end else KeyFound = True$ SchedulerKey = ScheduleEvent<0, 2> SchedulerKey = Field(SchedulerKey, '*', 1, 2) : '*' : NewScheduleKeyID ScheduleEvent<0, 2> = SchedulerKey ScheduleEvent<0, 3> = ScheduleDate : '.0' ScheduleEvent<0, 4> = ScheduleDate + 1 : '.0' // Create a new Schedule Event AddEvents := ScheduleEvent : @FM Scheduling_Services('SetScheduleDetail', NewScheduleKeyID, ScheduleDetRow) end Until KeyFound Repeat Next ScheduleDate // Get an updated list of the block out events for this reactor. This will include the block outs // we just added. BlockOutEvents = Scheduling_Services('GetScheduleEvents', StartDate, '', ReactorNo, '', True$) BlockOutKeys = SRP_Array('Rotate', BlockOutEvents, @FM, @VM) BlockOutKeys = BlockOutKeys<2> BlockOutDates = BlockOutKeys Convert @VM to @FM in BlockOutDates Convert '*' to @VM in BlockOutDates BlockOutDates = SRP_Array('Rotate', BlockOutDates, @FM, @VM) BlockOutDates = BlockOutDates<4> // Get the other work order events for this reactor. OtherEvents = Scheduling_Services('GetScheduleEvents', StartDate, '', ReactorNo, '', False$) OtherKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) OtherKeys = OtherKeys<2> Convert @VM to @FM in OtherKeys Convert '*' to @VM in OtherKeys OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the work orders OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the EPI Part OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) Convert @VM to '*' in OtherKeys // Now that the block outs have been added, see if any work order events need to be adjusted. GoSub AdjustOtherWorkOrderEvents end else Error_Services('Add', 'ReactorNo or StartDate argument was missing from the ' : Service : ' service.') end // Return to the caller any events that were removed and added so the visual display can be updated if needed. RemoveEventKeyIDs[-1, 1] = '' AddEvents[-1, 1] = '' Response = RemoveEventKeyIDs : @RM : AddEvents end service //---------------------------------------------------------------------------------------------------------------------- // CancelBlockOutEvent // // Removes a block out event from the schedule. //---------------------------------------------------------------------------------------------------------------------- Service CancelBlockOutEvent(BlockOutEventKeyID) AllWorkOrderEvents = '' PreviousEvents = '' ; // Used to track events that already exist on the schedule for this date that ; // should be left alone. WorkOrderKeys = '' WorkOrderEvents = '' RemoveEventKeyIDs = '' AddEvents = '' If BlockOutEventKeyID NE '' then ScheduleKeyID = Field(BlockOutEventKeyID, '*', 3, 3) ReactorNo = ScheduleKeyID[1, '*'] Date = ScheduleKeyID[Col2() + 1, '*'] Sequence = ScheduleKeyID[Col2() + 1, '*'] RemoveEventKeyIDs := BlockOutEventKeyID : @FM Scheduling_Services('DeleteScheduleDetail', ScheduleKeyID) // Set the schedule date to the block out date. ScheduleDate = Date // Get an updated list of the block out events for this reactor. This will include the block outs // we just added. BlockOutEvents = Scheduling_Services('GetScheduleEvents', Date, '', ReactorNo, '', True$) BlockOutKeys = SRP_Array('Rotate', BlockOutEvents, @FM, @VM) BlockOutKeys = BlockOutKeys<2> BlockOutDates = BlockOutKeys Convert @VM to @FM in BlockOutDates Convert '*' to @VM in BlockOutDates BlockOutDates = SRP_Array('Rotate', BlockOutDates, @FM, @VM) BlockOutDates = BlockOutDates<4> // Get the other work order events for this reactor. OtherEvents = Scheduling_Services('GetScheduleEvents', Date, '', ReactorNo, '', False$) OtherKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) OtherKeys = OtherKeys<2> Convert @VM to @FM in OtherKeys Convert '*' to @VM in OtherKeys OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the work orders OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the EPI Part OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) Convert @VM to '*' in OtherKeys // Now that the block outs have been added, see if any work order events need to be adjusted. GoSub AdjustOtherWorkOrderEvents end else Error_Services('Add', 'BlockOutEventKeyID argument was missing from the ' : Service : ' service.') end // Return to the caller any events that were removed and added so the visual display can be updated if needed. RemoveEventKeyIDs[-1, 1] = '' AddEvents[-1, 1] = '' Response = RemoveEventKeyIDs : @RM : AddEvents end service //---------------------------------------------------------------------------------------------------------------------- // GetScheduleEvent // // Returns a JSON formatted object of information for a specific schedule event. //---------------------------------------------------------------------------------------------------------------------- Service GetScheduleEvent(ReactorNo, StartDate, EventID) SRP_Stopwatch('Start', Service) If Not(Num(StartDate)) then StartDate = Iconv(StartDate, 'D') ServiceKeyID := '*' : ReactorNo : '*' : StartDate : '*' : EventID ScheduleEvent = '' If (ReactorNo NE '') AND (StartDate NE '') AND (EventID NE '') then If SRP_JSON(objScheduleEvent, 'NEW', 'OBJECT') then SchedulerKeyID = ReactorNo : '*' : StartDate : '*' : EventID SRP_JSON(objScheduleEvent, 'SETVALUE', 'EventID', EventID, 'STRING') SchedulerRow = Database_Services('ReadDataRow', 'SCHEDULER', SchedulerKeyID, True$, 15) SRP_JSON(objScheduleEvent, 'SETVALUE', 'StartDate', Oconv(StartDate, 'D4/'), 'STRING') EndDate = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'EndDate', Oconv(EndDate, 'D4/'), 'STRING') SRP_JSON(objScheduleEvent, 'SETVALUE', 'ModifiedDTM', Oconv(SchedulerRow, 'DT/4^HS'), 'STRING') Note = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'Note', Note, 'STRING') BlockOut = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'BlockOut', Oconv(BlockOut, 'BYes,No'), 'STRING') BlockOutType = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'BlockOutType', BlockOutType, 'STRING') BackColor = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'BackColor', BackColor, 'STRING') ForeColor = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'ForeColor', ForeColor, 'STRING') StartTime = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'StartTime', Oconv(StartTime, 'MTH'), 'STRING') EndTime = SchedulerRow SRP_JSON(objScheduleEvent, 'SETVALUE', 'EndTime', Oconv(EndTime, 'MTH'), 'STRING') Reactor = Reactor_Services('GetReactor', ReactorNo) If SRP_JSON(objReactor, 'PARSE', Reactor) EQ '' then SRP_JSON(objScheduleEvent, 'SET', 'Reactor', objReactor) SRP_JSON(objReactor, 'RELEASE') end WorkOrderNo = EventID SRP_JSON(objScheduleEvent, 'SETVALUE', 'WorkOrderStep', SchedulerRow, 'STRING') WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, False$) If SRP_JSON(objWorkOrder, 'PARSE', WorkOrder) EQ '' then SRP_JSON(objScheduleEvent, 'SET', 'WorkOrder', objWorkOrder) WorkOrderNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'WorkOrderNumber') EpiPartNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'EpiPartNumber') CustNameShort = SRP_JSON(objWorkOrder, 'GETVALUE', 'Company.NameShort') SRP_JSON(objWorkOrder, 'RELEASE') end else WorkOrderNo = '' EpiPartNo = '' CustNameShort = '' end StartDateTime = StartDate + (StartTime / SECONDS_PER_DAY$) SRP_JSON(objScheduleEvent, 'SETVALUE', 'StartDateTime', Oconv(StartDateTime, 'DT2/^H'), 'STRING') EndDateTime = EndDate + (EndTime / SECONDS_PER_DAY$) SRP_JSON(objScheduleEvent, 'SETVALUE', 'EndDateTime', Oconv(EndDateTime, 'DT2/^H'), 'STRING') TotalScheduledDays = EndDateTime - StartDateTime TotalScheduledDays = SRP_Math('CEILING', TotalScheduledDays, '', 2) SRP_JSON(objScheduleEvent, 'SETVALUE', 'TotalScheduledDays', TotalScheduledDays, 'STRING') ScheduleEvent = SRP_JSON(objScheduleEvent, 'STRINGIFY', 'FAST') SRP_JSON(objScheduleEvent, 'RELEASE') end else Error_Services('Add', 'Error creating objScheduleEvent in the ' : Service : ' service.') end end else Error_Services('Add', 'ReactorNo, StartDate, or EventID argument was missing from the ' : Service : ' service.') end Response = ScheduleEvent SRP_Stopwatch('Start', Service) end service //---------------------------------------------------------------------------------------------------------------------- // SetScheduleDetail // // Creates or updates the SCHED_DET row for the indicated Key ID. //---------------------------------------------------------------------------------------------------------------------- Service SetScheduleDetail(ScheduleKeyID, ScheduleDetail) If (ScheduleKeyID NE '') AND (ScheduleDetail NE '') then TableName = 'SCHED_DET' Database_Services('WriteDataRow', Tablename, ScheduleKeyID, ScheduleDetail, True$) end else Error_Services('Add', 'ScheduleKeyID or ScheduleDetail argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // AddScheduleEvents // // Adds schedule events for the indicated work order on the indicated reactor starting on the indicated start date for // the indicated number of days. All future schedule events will automatically be adjusted future into the future // accordingly. The exception to this are schedule events which begin on or before the indicated start date. In this // case, the new schedule events will be placed after the current schedule event. //---------------------------------------------------------------------------------------------------------------------- Service AddScheduleEvents(WorkOrderNo, ReactorNo, RequestedStartDate, NumberOfDays, Description) If Not(Num(RequestedStartDate)) then RequestedStartDate = Iconv(RequestedStartDate, 'D') If NumberOfDays EQ '' then NumberOfDays = Scheduling_Services('GetAdjustedDays', WorkOrderNo) end CancelledSchedDetKeyIDs = '' AddedSchedDetKeyIDs = '' Debug If (WorkOrderNo NE '') AND (ReactorNo NE '') AND (RequestedStartDate NE '') AND (NumberOfDays GT 0) then ActualStartDate = '' ; // This will be used to store the actual start date the system is able to find. RemainingSeconds = NumberOfDays * SECONDS_PER_DAY$ ; // Convert the total days of the new schedule events into seconds. Loop // Get the SCHED_DET Key IDs already scheduled for this reactor and schedule date. The basic rule is that no // schedule event already engaged (i.e., started on or before the requested start date) will be moved. The new // schedule event must begin in the first available time slot on or after the requested start date. All // schedule events that start after the requested start date will be moved into the future to make room for // new schedule events. * SchedDetKeyIDsRow = Database_Services('ReadDataRow', 'SCHED_DET_KEY_IDS', ReactorNo : '*' : RequestedStartDate) WOSchedDetKeyIDs = SchedDetKeyIDsRow BOSchedDetKeyIDs = SchedDetKeyIDsRow SchedDetKeyIDs = SRP_Array('Clean', WOSchedDetKeyIDs : @VM : BOSchedDetKeyIDs, 'TrimAndMakeUnique', @VM) If SchedDetKeyIDs EQ '' then // Nothing is already scheduled. Use this date and set the start time (midnight). Calculate the end time // based on whether there are more seconds in the schedule than are in a full day. ActualStartDate = RequestedStartDate ActualStartTime = MIDNIGHT_AM$ AvailableSeconds = SECONDS_PER_DAY$ If RemainingSeconds GE AvailableSeconds then ActualStopTime = MIDNIGHT_PM$ end else ActualStopTime = RemainingSeconds end end else // There are existing schedule events. Check to see if there is any remaining portion of the day that // can be used to start the new schedule event. GoSub GetLastEndTime ; // Assumes SchedDetKeyIDs is populated. Returns LastEndTime variable. If LastEndTime LT MIDNIGHT_PM$ then // The last schedule event on this date ends before midnight. Use this date and set the // start time to be the end time of the existing schedule event. ActualStartDate = RequestedStartDate ActualStartTime = LastEndTime AvailableSeconds = SECONDS_PER_DAY$ - ActualStartTime If RemainingSeconds GE AvailableSeconds then ActualStopTime = MIDNIGHT_PM$ end else ActualStopTime = RemainingSeconds end end end Until ActualStartDate NE '' RequestedStartDate += 1 Repeat // To simplify the process of adding schedule events, remove all future schedule events related to work orders. // Save all future SCHED_DET rows into cache so they can be added back later. If ActualStopTime EQ MIDNIGHT_PM$ then // Start the search on the next date since the new schedule event will end at midnight. SearchDate = ActualStartDate + 1 end else // Start the search on the actual start date since the new schedule event will end before // midnight. SearchDate = ActualStartDate end SchedDetKeyIDs = Scheduling_Services('SearchSchedDet', SearchDate, '', ReactorNo, '', False$) If SchedDetKeyIDs NE '' then For Each SchedDetKeyID in SchedDetKeyIDs using @FM MoveScheduleEvent = False$ ; // Assume false for now. FutureSchedDetRow = Database_Services('ReadDataRow', 'SCHED_DET', SchedDetKeyID) FutureReactorNo = SchedDetKeyID[1, '*'] FutureScheduleDate = SchedDetKeyID[Col2() + 1, '*'] If ActualStartDate EQ FutureScheduleDate then // Make sure this schedule event occurs after the new schedule event before nominating it to be // moved. FutureStartTime = FutureSchedDetRow If FutureStartTime GT ActualStartTime then MoveScheduleEvent = True$ end else MoveScheduleEvent = True$ end If MoveScheduleEvent EQ True$ then Memory_Services('SetValue', Service : '*' : SchedDetKeyID, FutureSchedDetRow) * Database_Services('DeleteDataRow', 'SCHED_DET', SchedDetKeyID, True$) end Next SchedDetKeyID end // Create the SCHED_DET row base. SchedDetRow = '' SchedDetRow = WorkOrderNo SchedDetRow = Description SchedDetRow = Iconv(Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTH'), 'DTM') // Now add the new schedule events. Stop when the number of seconds remaining to be scheduled reaches 0. // Schedule around block out events if they exist. Transfer ActualStartDate to ScheduleDate ; // Begin with the actual start date. Transfer ActualStartTime to StartTime ; // Begin with the actual start time. Loop Until RemainingSeconds LE 0 SchedDetKeyIDsRow = Database_Services('ReadDataRow', 'SCHED_DET_KEY_IDS', ReactorNo : '*' : ScheduleDate) SchedDetKeyIDs = SchedDetKeyIDsRow If SchedDetKeyIDs NE '' then // There are existing block out schedule events. Check to see if there is any remaining portion of the // day that can be used to start the new schedule event. GoSub GetLastEndTime ; // Assumes SchedDetKeyIDs is populated. Returns LastEndTime variable. If LastEndTime LT MIDNIGHT_PM$ then // The last schedule event on this date ends before midnight. Use this date and set the // start time to be the end time of the existing schedule event. StartTime = LastEndTime end end SchedDetRow = StartTime // Calculate the portion of the day remaining in the current day. Most of the time this will be the whole // day (i.e., DayPortion = 1), but if this is the first day of the schedule event, it might be starting // late in the day. AvailableSeconds = SECONDS_PER_DAY$ - StartTime If RemainingSeconds GE AvailableSeconds then StopTime = MIDNIGHT_PM$ end else StopTime = RemainingSeconds end RemainingSeconds -= AvailableSeconds SchedDetRow = StopTime // Find the next available SchedDetKeyID for the curent reactor and schedule date and create the // SCHED_DET row. SchedDetKeyIDsRow = Database_Services('ReadDataRow', 'SCHED_DET_KEY_IDS', ReactorNo : '*' : ScheduleDate) SchedDetKeyIDs = SchedDetKeyIDsRow SchedDetKeyIDs = SRP_Array('SortRows', SchedDetKeyIDs, 'DR3', 'LIST', @VM, '*') // Get the last sequence number used and increase by one to create a unique SchedDetKeyID. Sequence = Field(SchedDetKeyIDs<0, 1>, '*', 3, 1) + 1 SchedDetKeyID = ReactorNo : '*' : ScheduleDate : '*' : Sequence Database_Services('WriteDataRow', 'SCHED_DET', SchedDetKeyID, SchedDetRow, True$) If Error_Services('NoError') then // SCHED_DET row was successfully created. Add the Key ID to the list. AddedSchedDetKeyIDs := SchedDetKeyID : @VM end // Increment the schedule date and reset the start time. ScheduleDate += 1 StartTime = MIDNIGHT_AM$ Repeat AddedSchedDetKeyIDs[-1, 1] = '' end else Error_Services('Add', 'WorkOrderNo, ReactorNo, RequestedStartDate, or NumberOfDays argument was missing from the ' : Service : ' service.') end Response = CancelledSchedDetKeyIDs end service //---------------------------------------------------------------------------------------------------------------------- // CancelScheduleEvents // // Cancels all schedule events for the indicated work order. If a reactor number is specified, then only events on that // reactor are cancelled. The adjust future events flag indicates whether events in the future should be automatically // adjusted, meaning that an attempt will be made to relocate these events to fill in the gap left behind by the // cancelled events. //---------------------------------------------------------------------------------------------------------------------- Service CancelScheduleEvents(WorkOrderNo, ReactorNo, AdjustFutureEvents) CancelledSchedDetKeyIDs = '' If WorkOrderNo NE '' then WOScheduleRow = Database_Services('ReadDataRow', 'WO_SCHEDULE', WorkOrderNo) SchedDetKeyIDs = WOScheduleRow If SchedDetKeyIDs NE '' then For Each SchedDetKeyID in SchedDetKeyIDs using @VM ScheduleReactorNo = SchedDetKeyID[1, '*'] If (ReactorNo EQ '') OR (ReactorNo EQ ScheduleReactorNo) then Database_Services('DeleteDataRow', 'SCHED_DET', SchedDetKeyID, True$) If Error_Services('NoError') then CancelledSchedDetKeyIDs := SchedDetKeyID : @FM end end Next SchedDetKeyID end CancelledSchedDetKeyIDs[-1, 1] = '' end else Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.') end Response = CancelledSchedDetKeyIDs end service //---------------------------------------------------------------------------------------------------------------------- // DeleteScheduleDetail // // Deletes the SCHED_DET row for the indicated Key IDs. //---------------------------------------------------------------------------------------------------------------------- Service DeleteScheduleDetail(ScheduleKeyIDs) If ScheduleKeyIDs NE '' then TableName = 'SCHED_DET' For Each ScheduleKeyID in ScheduleKeyIDs using @FM Database_Services('DeleteDataRow', Tablename, ScheduleKeyID, True$) Next ScheduleKeyID end else Error_Services('Add', 'ScheduleKeyID argument was missing from the ' : Service : ' service.') end end service //---------------------------------------------------------------------------------------------------------------------- // GetAdjustedDays // // Returns the calculated adjusted number of days needed to process the remaining wafers for the indicated work order. // Note: this can return a negative value if there are more scheduled events needed to complete the remaining wafers. //---------------------------------------------------------------------------------------------------------------------- Service GetAdjustedDays(WorkOrderNo) ServiceKeyID := '*' : WorkOrderNo AdjustedDays = '' If Memory_Services('IsValueCurrent', ServiceKeyID, 5, True$) then AdjustedDays = Memory_Services('GetValue', ServiceKeyID) end else If WorkOrderNo NE '' then // Get the list of existing SCHED_DET Key IDs sorted by Schedule Date. WOScheduleRow = Database_Services('ReadDataRow', 'WO_SCHEDULE', WorkOrderNo, True$, 15) SchedDetKeyIDs = WOScheduleRow SchedDetKeyIDs = SRP_Array('SortRows', SchedDetKeyIDs, 'AR2', 'LIST', @VM, '*') // Get the first SCHED_DET Key ID. Use this to get a schedule object, which will also // contain the total scheduled days. TotalScheduledDays = 0 Date = Date() If SchedDetKeyIDs NE '' then FirstSchedDetKeyID = SchedDetKeyIDs<0, 1> For Each SchedDetKeyID in SchedDetKeyIDs using @VM ReactorNo = SchedDetKeyID[1, '*'] ScheduleDate = SchedDetKeyID[Col2() + 1, '*'] Sequence = SchedDetKeyID[Col2() + 1, '*'] // If the schedule date for the event is on the same date being referenced or later, // consider this to be a future event and add its scheduled time to the total scheduled // days. This will be used to determine if any adjustment is necessary. If ScheduleDate GE Date then ScheduleEvent = Scheduling_Services('GetScheduleEvent', ReactorNo, ScheduleDate, Sequence, True$) GoSub ParseScheduleEvent If DayLengthCode EQ '' then DayLengthCode = 4 TotalScheduledDays += DayLengthCode * .25 end Next SchedDetKeyID end else // This work order has not yet ben scheduled. The adjusted days should indicate the actual number // of days needed to process this order from beginning to end. WorkOrder = Work_Order_Services('GetWorkOrder', WorkOrderNo, True$) GoSub ParseWorkOrder FirstScheduleDate = '' end // Make sure the Reactor Type assigned to this work order is formatted to the newer type. Begin Case Case WOReactorType _EQC 'ASM+' ; WOReactorType = 'ASM+' Case WOReactorType _EQC 'EPP' ; WOReactorType = 'EpiPro' Case WOReactorType _EQC 'EPS' ; WOReactorType = 'ASM' Case WOReactorType _EQC 'HTR' ; WOReactorType = 'HTR' Case WOReactorType _EQC 'GAN' ; WOReactorType = 'GaN' Case WOReactorType _EQC '' ; WOReactorType = '***' End Case // Get the wafers per day that can be processed. This takes into consideration the current reactor // utilization value. Thus, this is a realistic number rather than an ideal number. WafersPerDay = Epi_Part_Services('GetAdjustedWafersPerDayScheduler', EpiPartNo, WOReactorType) If Error_Services('NoError') then DaysNeeded = SRP_Math('ROUND', (WafersRemaining / WafersPerDay) * 4, '', 0) / 4 AdjustedDays = DaysNeeded - TotalScheduledDays If (WafersRemaining EQ 0) OR ((AdjustedDays LT 0) AND (FirstScheduleDate EQ Date)) then // This implies that the work order being analyzed is already finished. However, this is // due to the current work order event finishing the job so this specific event needs to // be kept in the list. The adjusted days counter needs to be increased by 1 to allow // this to happen. AdjustedDays += 1 end Memory_Services('SetValue', ServiceKeyID, AdjustedDays) end end else Error_Services('Add', 'WorkOrderNo argument was missing from the ' : Service : ' service.') end end Response = AdjustedDays end service //---------------------------------------------------------------------------------------------------------------------- // AutoScheduler // // Analyzes all work orders events for the current date for all reactors. Determines the number of days required for // each work order to complete the remaining number of wafers. Each work order is adjusted automatically based on the // adjuted number of days calculated. //---------------------------------------------------------------------------------------------------------------------- Service AutoScheduler() SRP_Stopwatch('Reset') SRP_Stopwatch('Start', Service) Log = '' Reactors = Reactor_Services('GetReactors') Reactors = Reactors[1, @RM] ; // Remove the meta data from the end. Reactors = SRP_Array('Rotate', Reactors, @FM, @VM) ReactorNos = Reactors<3> For Each ReactorNo in ReactorNos using @VM ScheduleEvents = Scheduling_Services('GetScheduleEvents', Date(), '', ReactorNo, '', False$) WorkOrderNos = SRP_Array('Rotate', ScheduleEvents, @FM, @VM) WorkOrderNos = WorkOrderNos<2> Convert @VM to @FM in WorkOrderNos Convert '*' to @VM in WorkOrderNos WorkOrderNos = SRP_Array('Rotate', WorkOrderNos, @FM, @VM) WorkOrderNos = WorkOrderNos<1> WorkOrderNos = SRP_Array('Clean', WorkOrderNos, 'TrimAndMakeUnique', @VM) If WorkOrderNos NE '' then For Each WorkOrderNo in WorkOrderNos using @VM // Determine how many days has this Work order has been scheduled for starting from today AdjustedDays = Scheduling_Services('GetAdjustedDays', WorkOrderNo) Log := 'Reactor: ' : ReactorNo : ' - WorkOrder: ' : WorkOrderNo : ' - Number of days: ' : AdjustedDays : @FM Log := ReactorNo : ',' : WorkOrderNo : ',' : AdjustedDays : @FM If Error_Services('NoError') then If AdjustedDays NE '' AND AdjustedDays NE 0 then AdjustedEvents = Scheduling_Services('AdjustScheduleEvents', ReactorNo, WorkOrderNo, AdjustedDays, Date()) If Error_Services('NoError') then Log := AdjustedEvents : @FM end else Log := Error_Services('GetMessage') : @FM end end end else Message = Error_Services('GetMessage') Message := ' - Reactor No: ' : ReactorNo Error_Services('Set', Message) Log := Error_Services('GetMessage') : @FM end Next WorkOrderNo end Next ReactorNo SRP_Stopwatch('Stop', Service) If @UserName EQ 'DANIEL_ST' then Open 'SYSLISTS' to hsyslists then Log = SRP_Array('SortRows', Log, 'AL1', 'LIST', @FM, @VM) Write Log to hSysLists, 'SCHEDULERENGINE' else Debug end SRP_Stopwatch('ShowAll') end end service //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Internal GoSubs //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //---------------------------------------------------------------------------------------------------------------------- // AdjustOtherWorkOrderEvents // // Logic dedicated to adjusting all other work orders for a specific reactor. This is called by the AdjustScheduleEvents // service. //---------------------------------------------------------------------------------------------------------------------- AdjustOtherWorkOrderEvents: PreviousKeys = '' ; // List of previous schedule detail keys that should be referenced later on. If PreviousEvents NE '' then // This means one or more work orders has been identified as already being scheduled in the past. All events // related to them shoud be removed from the OtherKeys and OtherEvents variables. This will prevent them from being // adjusted. For Each PreviousEvent in PreviousEvents using @FM PreviousSchedulerKeyID = PreviousEvent<0, 2> PreviousWorkOrder = PreviousSchedulerKeyID[1, '*'] NumOtherEvents = DCount(OtherEvents, @FM) For OtherEventCnt = NumOtherEvents to 1 Step -1 OtherEvent = OtherEvents OtherSchedulerKeyID = OtherEvent<0, 2> OtherWorkOrder = OtherSchedulerKeyID[1, '*'] If OtherWorkOrder EQ PreviousWorkOrder then PreviousKeys := OtherKeys : @FM OtherEvents = Delete(OtherEvents, OtherEventCnt, 0, 0) OtherKeys = Delete(OtherKeys, OtherEventCnt, 0, 0) end Next OtherEventCnt Next PreviousEvent PreviousKeys[-1, 1] = '' end // Move the work order events and keys into the "All" work order event and key lists. This will be the master list // that will be adjusted and eventually returned by this service. Transfer WorkOrderKeys to AllWorkOrderKeys Transfer WorkOrderEvents to AllWorkOrderEvents // If there were previous events that were removed from the Other events and keys, there might not be any // other events that need to be adjuted. Check to see if there are other events before proceeding. If OtherEvents NE '' then If AllWorkOrderKeys NE '' then AllWorkOrderKeys := @FM : OtherKeys AllWorkOrderEvents := @FM : OtherEvents end else AllWorkOrderKeys = OtherKeys AllWorkOrderEvents = OtherEvents end AllWorkOrderKeys = SRP_Array('SortRows', AllWorkOrderKeys, 'AR2', 'LIST', @FM, '*') AllWorkOrderKeys = SRP_Array('Clean', AllWorkOrderKeys, 'TrimAndMakeUnique', @FM) AllWorkOrderEvents = SRP_Array('SortRows', AllWorkOrderEvents, 'AR3', 'LIST', @FM, @VM) AllWorkOrderEvents = SRP_Array('Clean', AllWorkOrderEvents, 'TrimAndMakeUnique', @FM) // The original OtherEvents and OtherKeys lists were sorted by reactor number and then by // work order in order to keep the like work orders together. However, these now need to be // resorted in strict chronological order in order to maintain priority of work orders that // are scheduled in the future. Otherwise, work orders intended to be scheduled further // out in the calendar might get adjusted to occur prior to a higher priority work // order. OtherEvents = SRP_Array('SortRows', OtherEvents, 'AR1' : @FM : 'AR3', 'LIST', @FM, @VM) MatchKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) MatchKeys = MatchKeys<2> Convert @VM to @FM in MatchKeys Convert '*' to @VM in MatchKeys // These keys are sorted by work order and then by date. This puts the oldest date first // so priorty can be established. MatchKeys = SRP_Array('SortRows', MatchKeys, 'AR1' : @FM : 'AR4', 'LIST', @FM, @VM) MatchKeys = SRP_Array('Rotate', MatchKeys, @FM, @VM) NewOtherEvents = '' // Create a new list of other events but pre-pend the oldest date related to each work order. // This will create a temporary priority sorting column. If OtherEvents NE '' then For Each OtherEvent in OtherEvents using @FM SchedulerKeyID = OtherEvent<0, 2> WorkOrderNo = SchedulerKeyID[1, '*'] Locate WorkOrderNo in MatchKeys<1> using @VM setting vPos then FirstDate = MatchKeys<4, vPos> NewOtherEvents := FirstDate : @VM : OtherEvent : @FM end Next OtherEvent NewOtherEvents[-1, 1] = '' Transfer NewOtherEvents to OtherEvents end // Now sort the other events by the temporary column. OtherEvents = SRP_Array('SortRows', OtherEvents, 'AR1', 'LIST', @FM, @VM) OtherEvents = SRP_Array('Rotate', OtherEvents, @FM, @VM) // Remove the temporary column and restore other events to its normal format. OtherEvents = Delete(OtherEvents, 1, 0, 0) OtherEvents = SRP_Array('Rotate', OtherEvents, @FM, @VM) // Since other events has been sorted, the other keys needs to be rebuilt. OtherKeys = SRP_Array('Rotate', OtherEvents, @FM, @VM) OtherKeys = OtherKeys<2> Convert @VM to @FM in OtherKeys Convert '*' to @VM in OtherKeys OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the work orders OtherKeys = Delete(OtherKeys, 1, 0, 0) ; // Remove the EPI Part OtherKeys = SRP_Array('Rotate', OtherKeys, @FM, @VM) Convert @VM to '*' in OtherKeys // Relocate the pre-existing work order events. PreviousScheduleDate = '' PreviousWorkOrder = '' Loop Until (OtherKeys EQ '') Locate ScheduleDate in BlockOutDates using @VM setting vPos then // Do not schedule on the same date as a block out event. end else // Get the next schedule key that needs to be adjusted. It will always be the first // item in the list of keys. OtherKey = OtherKeys<1> // Look for this schedule in the combined list of all work order keys (which should // always be found). Then use the index position to extract the schedule detail information. // Remove the schedule key and event from the master lists so they don't get processed again. Locate OtherKey in AllWorkOrderKeys using @FM setting fPos then OtherEvent = AllWorkOrderEvents AllWorkOrderKeys = Delete(AllWorkOrderKeys, fPos, 0, 0) AllWorkOrderEvents = Delete(AllWorkOrderEvents, fPos, 0, 0) end else // This condition should never occur. OtherEvent = '' end // Get the current work order for the event being adjusted. Compare it to the previous // work order already adjusted (if applicable). If the work order is different, then use // don't advance the schedule date. Just increase the sequence so it can be added to the // same date. Otherwise, advance the schedule date and reset the sequence to 1. CurrentWorkOrder = OtherEvent<0, 2>[1, '*'] OtherKeys = Delete(OtherKeys, 1, 0, 0) OtherEvents = Delete(OtherEvents, 1, 0, 0) Begin Case Case PreviousWorkOrder EQ '' // Use the current schedule date as already assigned above. Sequence = 1 Case CurrentWorkOrder EQ PreviousWorkOrder // Use the newly advanced schedule date. Sequence to 1. Sequence = 1 Case CurrentWorkOrder NE PreviousWorkOrder // Current work order is different from previous work order. // Use the previous schedule date but increase the sequence. ScheduleDate = PreviousScheduleDate Sequence += 1 End Case // Using the starting values from above, look for the next available schedule key that // the current event can use. KeyFound = False$ ; // Assume key not found for now. Loop // Get the next "new" schedule detail key that is available. NewScheduleKeyID = ReactorNo : '*' : ScheduleDate : '*' : Sequence Locate NewScheduleKeyID in PreviousKeys : @FM : AllWorkOrderKeys using @FM setting fPos then // The new schedule key already exists. Just increase the sequence counter to create // another new schedule key to compare with. Sequence += 1 end else // We now have a "new" schedule detail key. Update the database using the new // schedule detail key and then delete the original key. KeyFound = True$ // The event detail contains information from the original schedule date. This needs to be // adjusted to reflect the new schedule date. OrigOtherEvent = OtherEvent ; // Used to append to the RemoveEventKeyIDs list below. SchedulerKey = OtherEvent<0, 2> SchedulerKey = Field(SchedulerKey, '*', 1, 2) : '*' : NewScheduleKeyID OtherEvent<0, 2> = SchedulerKey OtherEvent<0, 3> = ScheduleDate : '.0' OtherEvent<0, 4> = ScheduleDate + 1 : '.0' // For reporting purposes, add the updated event to the list of all work order events. AllWorkOrderKeys := @FM : NewScheduleKeyID AllWorkOrderEvents := @FM : OtherEvent // OtherKey is the original key being adjusted. Delete it first before adding the // new scheduler key. * ScheduleDetRow = Scheduling_Services('GetScheduleDetail', OtherKey) ScheduleDetRow = Database_Services('ReadDataRow', 'SCHED_DET', OtherKey) If Error_Services('NoError') then RemoveEventKeyIDs := OrigOtherEvent<0, 2> : @FM Scheduling_Services('DeleteScheduleDetail', OtherKey) If Error_Services('NoError') then AddEvents := OtherEvent : @FM Scheduling_Services('SetScheduleDetail', NewScheduleKeyID, ScheduleDetRow) end end end Until KeyFound Repeat // Store the current schedule date in the previous schedule date variable so it can be used to compare // with in the next loop. PreviousScheduleDate = ScheduleDate // Store the current work order in the previous work order variable so it can be used to compare with // in the next loop. PreviousWorkOrder = CurrentWorkOrder end // Advanced to the next schedule date whether a new schedule event was created or if a block out date was encountered. ScheduleDate += 1 Repeat end return ParseScheduleEvent: If Assigned(objScheduleEvent) else objScheduleEvent = 0 If objScheduleEvent LE 0 then SRP_JSON(objScheduleEvent, 'PARSE', ScheduleEvent) end If objScheduleEvent GT 0 then ReactorNo = SRP_JSON(objScheduleEvent, 'GETVALUE', 'Reactor.ReactorNumber') ReactorType = SRP_JSON(objScheduleEvent, 'GETVALUE', 'Reactor.Type') SusceptorSize = SRP_JSON(objScheduleEvent, 'GETVALUE', 'Reactor.SusceptorSize') ScheduleDate = SRP_JSON(objScheduleEvent, 'GETVALUE', 'ScheduleDate') ScheduleDate = Iconv(ScheduleDate, 'D') Sequence = SRP_JSON(objScheduleEvent, 'GETVALUE', 'Sequence') BackColor = SRP_JSON(objScheduleEvent, 'GETVALUE', 'BackColor') ForeColor = SRP_JSON(objScheduleEvent, 'GETVALUE', 'ForeColor') ModifiedDTM = Iconv(SRP_JSON(objScheduleEvent, 'GETVALUE', 'ModifiedDTM'), 'DT') BlockOut = Iconv(SRP_JSON(objScheduleEvent, 'GETVALUE', 'BlockOut'), 'BYes,No') BlockOutType = SRP_JSON(objScheduleEvent, 'GETVALUE', 'BlockOutType') Description = SRP_JSON(objScheduleEvent, 'GETVALUE', 'Note') DayLengthCode = SRP_JSON(objScheduleEvent, 'GETVALUE', 'DayLengthCode') StartTime = Iconv(SRP_JSON(objScheduleEvent, 'GETVALUE', 'StartTime'), 'MT') EndTime = Iconv(SRP_JSON(objScheduleEvent, 'GETVALUE', 'EndTime'), 'MT') If EndTime EQ MIDNIGHT_AM$ then EndTime = MIDNIGHT_PM$ objWorkOrder = SRP_JSON(objScheduleEvent, 'GET', 'WorkOrder') GoSub ParseWorkOrder If BackColor EQ '' then Begin Case Case BackColor NE '' // Set in the schedule detail record. Use current color. Case HotLot BackColor = 'LightCoral' Case Closed BackColor = 'LightGray' Case BlockOut BackColor = 'Plum' Case Otherwise$ BackColor = 'LightSteelBlue' End Case end Current = False$ ; // Assume false for now. If (ScheduleDate GE Date()) then If (ModifiedDTM NE '') then CurrentDTM = Iconv(Oconv(Date(), 'D4/') : ' ' : Oconv(Time(), 'MTH'), 'DTM') ElapseTime = (CurrentDTM - ModifiedDTM) * 24 IF (ElapseTime LE 12) then CurrentFlag = True$ end end end If ForeColor EQ '' then ForeColor = 'Black' FirstSchedDetKeyID = SRP_JSON(objScheduleEvent, 'GETVALUE', 'FirstSchedDetKeyID') FirstReactorNo = FirstSchedDetKeyID[1, '*'] FirstScheduleDate = Oconv(FirstSchedDetKeyID[Col2() + 1, '*'], 'D4/') LastSchedDetKeyID = SRP_JSON(objScheduleEvent, 'GETVALUE', 'LastSchedDetKeyID') LastReactorNo = LastSchedDetKeyID[1, '*'] LastScheduleDate = Oconv(LastSchedDetKeyID[Col2() + 1, '*'], 'D4/') TotalScheduledDays = SRP_JSON(objScheduleEvent, 'GETVALUE', 'TotalScheduledDays') ScheduleEvent = SRP_JSON(objScheduleEvent, 'STRINGIFY', 'FAST') SRP_JSON(objScheduleEvent, 'RELEASE') end else end return ParseWorkOrder: If Assigned(objWorkOrder) else objWorkOrder = 0 If objWorkOrder LE 0 then SRP_JSON(objWorkOrder, 'PARSE', WorkOrder) end If objWorkOrder GT 0 then WorkOrderNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'WorkOrderNumber') EpiPartNo = SRP_JSON(objWorkOrder, 'GETVALUE', 'EpiPartNumber') WOReactorType = SRP_JSON(objWorkOrder, 'GETVALUE', 'ReactorType') PSN = SRP_JSON(objWorkOrder, 'GETVALUE', 'PSN') Recipe = SRP_JSON(objWorkOrder, 'GETVALUE', 'Recipe') HotLot = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'HotLot'), 'BYes,No') Closed = Iconv(SRP_JSON(objWorkOrder, 'GETVALUE', 'Closed'), 'BYes,No') TotalWafers = SRP_JSON(objWorkOrder, 'GETVALUE', 'TotalWafers') WafersRemaining = SRP_JSON(objWorkOrder, 'GETVALUE', 'WafersRemaining') PercentComplete = SRP_JSON(objWorkOrder, 'GETVALUE', 'PercentComplete') CustNameShort = SRP_JSON(objWorkOrder, 'GETVALUE', 'Company.NameShort') SRP_JSON(objWorkOrder, 'RELEASE') end else end return GetLastEndTime: EndTimes = '' For Each SchedDetKeyID in SchedDetKeyIDs using @VM SchedDetRow = Database_Services('ReadDataRow', 'SCHED_DET', SchedDetKeyID) EndTimes := SchedDetRow : @FM Next SchedDetKeyID EndTimes[-1, 1] = '' // Sort the list of end times so the latest time is at the top. EndTimes = SRP_Array('SortRows', EndTimes, 'DR1', 'ARRAY', @FM, @VM) LastEndTime = EndTimes<1> end