open-insight/LSL2/STPROC/SCHEDULING_SERVICES.txt
2024-05-22 14:06:46 -07:00

1427 lines
79 KiB
Plaintext

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<WORK_ORDER_CHILD_KEY_IDS.SCHEDULER_KEY_IDS$>
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<SCHED_DET_DESC$> = '' ; // 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<WO_LOG_CUST_NO$>
EpiPartNo = WOLogRow<WO_LOG_EPI_PART_NO$>
WOClosedFlag = False$
HotLotFlag = WOLogRow<WO_LOG_WO_MAT_KEY_HOTLOT$> NE ''
If CustNo NE '' then
CompanyRow = Database_Services('ReadDataRow', 'COMPANY', CustNo)
CustName = CompanyRow<COMPANY_ABBREV$>
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<SCHED_DET_WO_NO$> = WorkOrderNo
ScheduleDetRow<SCHED_DET_DESC$> = 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<SCHED_DET_DAY_LENGTH_CODE$> = 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<SCHED_DET_DESC$> = Description
ScheduleDetRow<SCHED_DET_BLOCK_OUT$> = True$
ScheduleDetRow<SCHED_DET_BLOCK_OUT_TYPE$> = '' ; // 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<SCHEDULER.END_DATE$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'EndDate', Oconv(EndDate, 'D4/'), 'STRING')
SRP_JSON(objScheduleEvent, 'SETVALUE', 'ModifiedDTM', Oconv(SchedulerRow<SCHEDULER.MODIFIED$>, 'DT/4^HS'), 'STRING')
Note = SchedulerRow<SCHEDULER.DESCRIPTION$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'Note', Note, 'STRING')
BlockOut = SchedulerRow<SCHEDULER.BLOCK_OUT$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'BlockOut', Oconv(BlockOut, 'BYes,No'), 'STRING')
BlockOutType = SchedulerRow<SCHEDULER.BLOCK_OUT_TYPE$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'BlockOutType', BlockOutType, 'STRING')
BackColor = SchedulerRow<SCHEDULER.BACKCOLOR$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'BackColor', BackColor, 'STRING')
ForeColor = SchedulerRow<SCHEDULER.FORECOLOR$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'ForeColor', ForeColor, 'STRING')
StartTime = SchedulerRow<SCHEDULER.START_TIME$>
SRP_JSON(objScheduleEvent, 'SETVALUE', 'StartTime', Oconv(StartTime, 'MTH'), 'STRING')
EndTime = SchedulerRow<SCHEDULER.END_TIME$>
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<SCHEDULER.WO_STEP$>, '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<SCHED_DET_KEY_IDS_WORK_ORDER_SCHED_DET_KEY_IDS$>
BOSchedDetKeyIDs = SchedDetKeyIDsRow<SCHED_DET_KEY_IDS_BLOCK_OUT_SCHED_DET_KEY_IDS$>
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<SCHED_DET_START_TIME$>
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<SCHED_DET_WO_NO$> = WorkOrderNo
SchedDetRow<SCHED_DET_DESC$> = Description
SchedDetRow<SCHED_DET_MODIFIED$> = 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<SCHED_DET_KEY_IDS_BLOCK_OUT_SCHED_DET_KEY_IDS$>
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<SCHED_DET_START_TIME$> = 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<SCHED_DET_END_TIME$> = 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<SCHED_DET_KEY_IDS_WORK_ORDER_SCHED_DET_KEY_IDS$>
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<WO_SCHEDULE_SCHED_DET_KEY_IDS$>
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<WO_SCHEDULE_SCHED_DET_KEY_IDS$>
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<OtherEventCnt>
OtherSchedulerKeyID = OtherEvent<0, 2>
OtherWorkOrder = OtherSchedulerKeyID[1, '*']
If OtherWorkOrder EQ PreviousWorkOrder then
PreviousKeys := OtherKeys<OtherEventCnt> : @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<fPos>
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<SCHED_DET_END_TIME$> : @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