Function WO_MAT_QA_Actions(Action, CalcColName, FSList, Handle, Name, FMC, Record, Status, OrigRecord, Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, Param9, Param10) /*********************************************************************************************************************** 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 Infineon. Name : WO_MAT_QA_Actions Description : Handles calculated columns and MFS calls for the current table. Notes : This function uses @ID, @RECORD, and @DICT to make sure {ColumnName} references work correctly. If called from outside of a calculated column these will need to be set and restored. Parameters : Action [in] -- Name of the action to be taken CalcColName [in] -- Name of the calculated column that needs to be processed. Normally this should only be populated when the CalcField action is being used. FSList [in] -- The list of MFSs and the BFS name for the current file or volume. This is an @SVM delimited array, with the current MFS name as the first value in the array, and the BFS name as the last value. Normally set by a calling MFS. Handle [in] -- The file handle of the file or media map being accessed. Note, this does contain the entire handle structure that the Basic+ Open statement would provide. Normally set by a calling MFS. Name [in] -- The name (key) of the record or file being accessed. Normally set by a calling MFS. FMC [in] -- Various functions. Normally set by a calling MFS. Record [in] -- The entire record (for record-oriented functions) or a newly-created handle (for "get handle" functions). Normally set by a calling MFS. Status [in/out] -- Indicator of the success or failure of an action. Normally set by the calling MFS but for some actions can be set by the action handler to indicate failure. OrigRecord [in] -- Original content of the record being processed by the current action. This is automatically being assigned by the WRITE_RECORD and DELETE_RECORD actions within BASE_MFS. Param1-10 [in/out] -- Additional request parameter holders ActionFlow [out] -- Used to control the action chain (see the ACTION_SETUP insert for more information.) Can also be used to return a special value, such as the results of the CalcField method. History : (Date, Initials, Notes) 10/22/18 djs Original programmer. ***********************************************************************************************************************/ #pragma precomp SRP_PreCompiler $insert LOGICAL $insert FILE.SYSTEM.EQUATES $insert ACTION_SETUP $insert RDS_TEST_EQUATES $insert RDS_EQUATES $insert WO_LOG_EQUATES $insert WM_OUT_EQUATES $insert WM_IN_EQUATES $insert WO_MAT_EQUATES $insert WO_MAT_QA_EQUATES $insert PRS_STAGE_EQUATES $insert TOOL_CLASS_EQUATES Declare function Database_Services, obj_NCR, obj_SAP, Qa_Services, SRP_Trim, Environment_Services, Logging_Services Declare subroutine Error_Services, Database_Services, obj_NCR, obj_SAP, Post_Metrology_Manual_Data_Entry_Log Declare subroutine Logging_Services, obj_Notes Equ CRLF$ to \0D0A\ LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\WO_MAT_QA' LogDate = Oconv(Date(), 'D4/') LogTime = Oconv(Time(), 'MTS') LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' WO_MAT_QA Log.csv' Headers = 'Logging DTM' : @FM : 'User' : @FM : 'Key ID' : @FM : 'Call Stack' : @FM : 'Record' objLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) LoggingDTM = LogDate : ' ' : LogTime ; // Logging DTM LogPath = Environment_Services('GetApplicationRootPath') : '\LogFiles\FQABlocked' LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' FQA Log.csv' Headers = 'Logging DTM' : @FM : 'RDS Key ID' : @FM : 'Table' : @FM : 'Key' : @FM : 'Notes' FQAobjLog = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$) If KeyID then GoSub Initialize_System_Variables Begin Case Case Action _EQC 'CalcField' ; GoSub Calc_Field Case Action _EQC 'READ_RECORD_PRE' ; GoSub READ_RECORD_PRE Case Action _EQC 'READ_RECORD' ; GoSub READ_RECORD Case Action _EQC 'READONLY_RECORD_PRE' ; GoSub READONLY_RECORD_PRE Case Action _EQC 'READONLY_RECORD' ; GoSub READONLY_RECORD Case Action _EQC 'WRITE_RECORD_PRE' ; GoSub WRITE_RECORD_PRE Case Action _EQC 'WRITE_RECORD' ; GoSub WRITE_RECORD Case Action _EQC 'DELETE_RECORD_PRE' ; GoSub DELETE_RECORD_PRE Case Action _EQC 'DELETE_RECORD' ; GoSub DELETE_RECORD Case Otherwise$ ; Status = 'Invalid Action' End Case If KeyID then GoSub Restore_System_Variables If Assigned(ActionFlow) else ActionFlow = ACTION_CONTINUE$ Return ActionFlow // ----- Calculated Columns -------------------------------------------------------------------------------------------- // // The typical structure of a calculated column will look like this: // // Declare function TableName_Actions // // A = {COL1} ; * Reference as many data columns in this way to ensure the dictionary dependency is generated. // // @ANS = TableName_Actions('CalcField', 'CalcColName') // // --------------------------------------------------------------------------------------------------------------------- Calc_Field: // Make sure the ActionFlow return variable is cleared in case nothing is calculated. ActionFlow = '' return // ----- MFS calls ----------------------------------------------------------------------------------------------------- READ_RECORD_PRE: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' // ActionFlow = ACTION_STOP$ return READ_RECORD: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' return READONLY_RECORD_PRE: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' // ActionFlow = ACTION_STOP$ return READONLY_RECORD: // In order to stop a record from being read in this action these lines of code must be used: // // OrigFileError = 100 : @FM : KeyID // Status = 0 // Record = '' return WRITE_RECORD_PRE: WorkOrderNo = Field(Name, '*', 1, 1) CassNo = Field(Name, '*', 2, 1) WOMatKey = WorkOrderNo : '*' : CassNo FinalQA = False$ WorkOrderRow = Database_Services('ReadDataRow', 'WO_LOG', WorkOrderNo) ReactType = WorkOrderRow // Check to see if the Final QA signature is in place. If ReactType EQ 'EPP' then WMOutKey = WorkOrderNo : '*1*' : CassNo WMOutRow = Database_Services('ReadDataRow', 'WM_OUT', WMOutKey) If WMOutRow NE '' then FinalQA = True$ end end else RDSNo = Xlate('WO_MAT', WOMatKey, 'RDS_NO', 'X') RDSRow = Database_Services('ReadDataRow', 'RDS', RDSNo) If RDSRow NE '' then FinalQA = True$ end end Begin Case Case Record EQ '' // Unscheduled record write attempt. Block the write and log the event. ErrorMessage = 'Empty WO_MAT_QA profile. Unscheduled record write attempt!' Error_Services('Add', 'FS104: ':ErrorMessage) OrigFileError = 104 : @FM : ErrorMessage Stack = RetStack() Swap @FM with ' || ' in Stack RecordSnapshot = Record Swap @FM with CRLF$ in RecordSnapshot Swap @VM with ' @VM ' in RecordSnapshot Swap @SVM with ' @SVM ' in RecordSnapshot Swap @TM with ' @TM ' in RecordSnapshot Swap @STM with ' @STM ' in RecordSnapshot LogData = '' LogData<1> = LoggingDTM LogData<2> = @User4 LogData<3> = Name LogData<4> = Stack LogData<5> = CRLF$:RecordSnapshot:CRLF$ LogData<6> = 'FS104: ':ErrorMessage // Sending a null message will send the LogData instead Logging_Services('AppendLog', objLog, LogData, @RM, @FM, '', '', '') // Send an internal OI message to OI admins Recipients = Xlate('SEC_GROUPS', 'OI_ADMIN', 'USER', 'X') SentFrom = 'SYSTEM' Subject = 'Unscheduled WO_MAT_QA record write attempt.' Message = ErrorMessage AttachWindow = '' AttachKey = '' SendToGroup = '' Parms = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup obj_Notes('Create',Parms) Status = 0 Record = '' ActionFlow = ACTION_STOP$ Case FinalQA EQ True$ // User is attempting to modify this row. Set the FS104 error and log the event. OrigFileError = 104 : @FM : 'FQA has already been signed. WO_MAT_QA record cannot be updated!' LogData = '' LogData<1> = LoggingDTM LogData<2> = @User4 LogData<3> = WOMatKey LogData<4> = 'WO_MAT_QA' LogData<5> = Name LogData<6> = 'FS104:FQA has already been signed. WO_MAT_QA record cannot be updated!' Logging_Services('AppendLog', FQAobjLog, LogData, @RM, @FM) Status = 0 Record = '' ActionFlow = ACTION_STOP$ Error_Services('Add', 'FS104:FQA has already been signed. WO_MAT_QA record cannot be updated!') Case Otherwise$ // Validate metrology data // Current values in record OrigDataPoints = OrigRecord OrigProfiles = OrigRecord OrigResult = OrigRecord OrigSigs = OrigRecord OrigSpecMin = OrigRecord OrigSpecMax = OrigRecord // New values to be saved DataPoints = Record Profiles = Record ; // THICK_ONLY, CRES Sigs = Record SigDTMS = Record Result = Record ; // Average of Data Points MinResult = Record ; // Min of Data Points MaxResult = Record ; // Max of Data Points RangePctResult = Record ; // Range % of Data Points EdgeMeanResult = Record ; // Edge Mean Delta of Data Points SpecMin = Record SpecMax = Record SpecPhaseMin = Record OutOfSpec = Record FailReasons = Record ScanRecipes = Record Stages = Record PSN = {PS_NO} RawThicknessData = '' RawHgCVData = '' RawPhaseData = '' // Only enter validation code below if relevant data has changed. If ( (OrigDataPoints NE DataPoints) or (OrigProfiles NE Profiles) or (OrigResult NE Result) | or (OrigSigs NE Sigs) or (OrigSpecMin NE SpecMin) or (OrigSpecMax NE SpecMax) ) then Profile = '' For each Profile in Profiles using @VM setting ProfileIndex Stage = Stages<1, ProfileIndex> PRSStageKey = PSN:'*':Stage PRSStageRec = Database_Services('ReadDataRow', 'PRS_STAGE', PRSStageKey) SpecRecipes = PRSStageRec SpecProfiles = PRSStageRec Begin Case Case ( (Profile EQ '1THICK_ONLY') and (Stage EQ 'UNLOAD') ) FailReason = '' ThickOutOfSpec = False$ ToolClassID = Record ToolClassRec = Database_Services('ReadDataRow', 'TOOL_CLASS', ToolClassID) RecipePattern = Record RequiredDataPoints = '' Locate RecipePattern in ToolClassRec setting rPos then RequiredDataPoints = ToolClassRec end ScanProfile = Profile ScanProfile[1,1] = '' Locate ScanProfile in SpecProfiles using @VM setting vPos then SpecRecipe = SpecRecipes<1, vPos> ScanRecipe = ScanRecipes<1, ProfileIndex> If ScanRecipe NE SpecRecipe then ThickOutOfSpec = True$ FailReason = 'Scanned THICK_ONLY UNLOAD recipe, ':ScanRecipe:', does not match spec ' | : 'recipe, ':SpecRecipe:'.' end end SaveList = '' ThickSig = Sigs<1, ProfileIndex> ThickSigDTM = SigDTMS<1, ProfileIndex> ThickSpecMin = SpecMin<1, ProfileIndex> ThickSpecMax = SpecMax<1, ProfileIndex> ThicknessData = DataPoints<1, ProfileIndex> ResAvg = Result<1, ProfileIndex> ThicknessDataCount = 0 For each DataPoint in ThicknessData using @SVM setting ThickIndex Until DataPoint EQ '' Begin Case Case ThickIndex GT 10 Null Case (DataPoint EQ '') ThickOutOfSpec = True$ FailReason = 'Thickness data point ':ThickIndex:' is missing.' Case (DataPoint GT ThickSpecMax) OR (DataPoint LT ThickSpecMin) ThickOutOfSpec = True$ FailReason = 'Thickness data point ':ThickIndex:' is out of bounds.' End Case SaveList<1, 1, ThickIndex> = DataPoint RawThicknessData<1, ThickIndex> = DataPoint ThicknessDataCount += 1 Next DataPoint DataPoints<1, ProfileIndex> = SaveList If ThicknessDataCount LT RequiredDataPoints then ThickOutOfSpec = True$ FailReason = 'Number of data points is less than the required amount of ' : RequiredDataPoints : '!' end If RawThicknessData NE '' then Response = QA_Services('CalculateThickData', RawThicknessData) ThickAvg = Response<1,1> ThickMin = Response<1,2> ThickMax = Response<1,3> Result<1, ProfileIndex> = ThickAvg MinResult<1, ProfileIndex> = ThickMin MaxResult<1, ProfileIndex> = ThickMax end else // Lead/Supervisor/Engineer is overriding the typical workflow and is // entering the Thickness average only. ThickAvg = ResAvg Begin Case Case (ThickAvg EQ '') ThickOutOfSpec = True$ FailReason = 'Thickness average is missing.' Case (ThickAvg GT ThickSpecMax) OR (ThickAvg LT ThickSpecMin) ThickOutOfSpec = True$ FailReason = 'Thickness average is out of bounds.' End Case end If ThickOutOfSpec EQ False$ then // Final failure check. Only check if a previous failure reason has not been set as to not // ovewrite the previous failure reason. If (ThickSig EQ '') or (ThickSigDTM EQ '') then ThickOutOfSpec = True$ FailReason = 'THICK_ONLY UNLOAD product measurement test has not been signed.' end end OutOfSpec<1, ProfileIndex> = ThickOutOfSpec FailReasons<1, ProfileIndex> = FailReason Case ( (Profile EQ '1CRES') and (Stage EQ 'UNLOAD') ) CresOutOfSpec = False$ FailReason = '' ToolClassID = Record ToolClassRec = Database_Services('ReadDataRow', 'TOOL_CLASS', ToolClassID) RecipePattern = Record RequiredDataPoints = '' Locate RecipePattern in ToolClassRec setting rPos then RequiredDataPoints = ToolClassRec end ScanProfile = Profile ScanProfile[1,1] = '' Locate ScanProfile in SpecProfiles using @VM setting vPos then SpecRecipe = SpecRecipes<1, vPos> ScanRecipe = ScanRecipes<1, ProfileIndex> If ScanRecipe NE SpecRecipe then CresOutOfSpec = True$ FailReason = 'Scanned CRES UNLOAD recipe, ':ScanRecipe:', does not match spec ' | : 'recipe, ':SpecRecipe:'.' end end SaveList = '' CriticalPoints = '1,2,5,6,9' CRESSig = Sigs<1, ProfileIndex> CRESSigDTM = SigDTMS<1, ProfileIndex> HgCVSpecMin = SpecMin<1, ProfileIndex> HgCVSpecMax = SpecMax<1, ProfileIndex> ThisPhaseMin = SpecPhaseMin<1, ProfileIndex> HgCVData = DataPoints<1, ProfileIndex> // Adding Result Average here in the case that a lead manually only enters the averages. ResAvg = Result<1, ProfileIndex> If ThisPhaseMin EQ '' then // Spec Phase Min was not added when the record initially created, so add it here. PSN = Xlate('RDS', RDSNo, 'PROD_SPEC_ID', 'X') If PSN NE '' then PRSStageKey = PSN:'*UNLOAD' PRSStageRec = Database_Services('ReadDataRow', 'PRS_STAGE', PRSStageKey) MetProps = PRSStageRec Locate 'CRES' in MetProps using @VM setting PropPos then SpecPhaseMinCol = PRSStageRec ThisPhaseMin = SpecPhaseMinCol<1, PropPos> SpecPhaseMin<1, ProfileIndex> = ThisPhaseMin Record = SpecPhaseMin end end end ResDataCount = 0 For each DataPoint in HgCVData using @SVM setting CresIndex Until DataPoint EQ '' HgCVDataPoint = DataPoint[1, 'F':@TM] PhaseDataPoint = DataPoint[-1, 'B':@TM] CriticalPoint = Index(CriticalPoints, CresIndex, 1) Begin Case Case (HgCVDataPoint EQ '') and CriticalPoint CresOutOfSpec = True$ FailReason = 'HgCV data point: ':CresIndex:' is missing data.' Case (PhaseDataPoint EQ '') and CriticalPoint CresOutOfSpec = True$ FailReason = 'Phase data point: ':CresIndex:' is missing data.' Case (HgCVDataPoint GT HgCVSpecMax) OR (HgCVDataPoint LT HgCVSpecMin) and CriticalPoint CresOutOfSpec = True$ FailReason = 'HgCV data point: ':CresIndex:' is out of bounds.' Case (PhaseDataPoint LT ThisPhaseMin) and CriticalPoint CresOutOfSpec = True$ FailReason = 'Phase data point: ':CresIndex:' is out of bounds.' End Case RawHgCVData<1, CresIndex> = HgCVDataPoint RawPhaseData<1, CresIndex> = PhaseDataPoint ResDataCount += 1 Next DataPoint If ResDataCount LT RequiredDataPoints then CresOutOfSpec = True$ FailReason = 'Number of data points is less than the required amount of ' : RequiredDataPoints : '!' end If (RawHgCVData NE '') AND (RawPhaseData NE '') then Response = QA_Services('CalculateHgCVData', RawHgCVData) HgCVAvg = Response<1,1> HgCVMin = Response<1,2> HgCVMax = Response<1,3> HgCVEdgeMean = Response<1,4> HgCVRangePct = Response<1,5> Response = QA_Services('CalculateHgCVData', RawPhaseData) PhaseAvg = Response<1,1> PhaseMin = Response<1,2> PhaseMax = Response<1,3> PhaseEdgeMean = Response<1,4> PhaseRangePct = Response<1,5> DataPoints<1, ProfileIndex> = HgCVData Result<1, ProfileIndex> = HgCVAvg : @SVM : PhaseAvg MinResult<1, ProfileIndex> = HgCVMin : @SVM : PhaseMin MaxResult<1, ProfileIndex> = HgCVMax : @SVM : PhaseMax EdgeMeanResult<1, ProfileIndex> = HgCVEdgeMean : @SVM : PhaseEdgeMean RangePctResult<1, ProfileIndex> = HgCVRangePct : @SVM : PhaseRangePct end else // Lead/Supervisor/Engineer is overriding the typical workflow and is // entering the HgCV average and Phase average only. HgCVAvg = ResAvg<1, 1, 1> PhaseAvg = ResAvg<1, 1, 2> Begin Case Case (HgCVAvg EQ '') CresOutOfSpec = True$ FailReason = 'HgCV average is missing.' Case (PhaseAvg EQ '') CresOutOfSpec = True$ FailReason = 'Phase average is missing.' Case (HgCVAvg GT HgCVSpecMax) OR (HgCVAvg LT HgCVSpecMin) CresOutOfSpec = True$ FailReason = 'HgCV is out of bounds.' Case (PhaseAvg LT ThisPhaseMin) CresOutOfSpec = True$ FailReason = 'Phase is out of bounds.' End Case end If CresOutOfSpec EQ False$ then // Final failure check. Only check if a previous failure reason has not been set as to not // overwrite the previous failure reason. If (CRESSig EQ '') or (CRESSigDTM EQ '') then CresOutofSpec = True$ FailReason = 'CRES UNLOAD product measurement test has not been signed.' end end OutOfSpec<1, ProfileIndex> = CresOutOfSpec FailReasons<1, ProfileIndex> = FailReason OrigHgCVAvg = OrigRecord If HgCVAvg NE OrigHgCVAvg then Post_Metrology_Manual_Data_Entry_Log(@USER4, 'HgCRes', RDSNo : ' / ' : WorkOrderNo : '*' : CassNo) end Case ( (Profile EQ '1LW_RHO') and (Stage EQ 'UNLOAD') ) ThisOutOfSpec = False$ FailReason = '' ThisSig = Sigs<1, ProfileIndex> ThisSigDTM = SigDTMS<1, ProfileIndex> ThisSpecMin = SpecMin<1, ProfileIndex> ThisSpecMax = SpecMax<1, ProfileIndex> ThisResult = Result<1, ProfileIndex> ThisSpecTest = SpecProfiles<1, ProfileIndex> If ( (ThisResult LT ThisSpecMin) or (ThisResult GT ThisSpecMax) ) then ThisOutOfSpec = True$ FailReason = 'RHO is out of bounds.' end If ThisOutOfSpec EQ False$ then If (ThisSig EQ '') or (ThisSigDTM EQ '') then ThisOutOfSpec = True$ FailReason = Stage:' ':ThisSpecTest:' product measurement test has not been signed.' end end OutOfSpec<1, ProfileIndex> = ThisOutOfSpec FailReasons<1, ProfileIndex> = FailReason Case Otherwise$ // This profile & stage do not currently have any specific validation code associated with them. // Ensure 'out of spec' flag and 'fail reason' field for this profile are clear. OutOfSpec<1, ProfileIndex> = False$ FailReasons<1, ProfileIndex> = '' End Case Next Profile Record = DataPoints Record = Result Record = MinResult Record = MaxResult Record = EdgeMeanResult Record = RangePctResult Record = OutOfSpec Record = FailReasons SaveRecord = Record end End Case return WRITE_RECORD: return DELETE_RECORD_PRE: return DELETE_RECORD: return // ----- Internal Methods ---------------------------------------------------------------------------------------------- Initialize_System_Variables: // Save these for restoration later SaveDict = @DICT SaveID = @ID SaveRecord = @RECORD OrigFileError = @FILE.ERROR // Now make sure @DICT, ID, and @RECORD are populated CurrentDictName = '' If @DICT then DictHandle = @DICT<1, 2> Locate DictHandle in @TABLES(5) Using @FM Setting fPos then CurrentDictName = Field(@TABLES(0), @FM, fPos, 1) end end If CurrentDictName NE DictName then Open DictName to @DICT else Status = 'Unable to initialize @DICT' end @ID = KeyID If Record else // Record might not have been passed in. Read the record from the database table just to make sure. @FILE.ERROR = '' Open TableName to hTable then FullFSList = hTable[1, 'F' : @VM] BFS = FullFSList[-1, 'B' : @SVM] LastHandle = hTable[-1, 'B' : \0D\] FileHandle = \0D\ : LastHandle[1, @VM] Call @BFS(READO.RECORD, BFS, FileHandle, KeyID, FMC, Record, ReadOStatus) end end @RECORD = Record return Restore_System_Variables: Transfer SaveDict to @DICT Transfer SaveID to @ID Transfer SaveRecord to @RECORD @FILE.ERROR = OrigFileError return