!{{{}}} ! (c)quickSOFT 1994,1995. All rights reserved. MEMBER('XP.clw') !{{{ history ! !{{{ 1995 ! 16/4/95 DCN Re-jig to allow forwards as well as backwards scheduling ! 17/4/95 DCN Added xpGetStrategy ! 27/5/95 DCN Propagate ParentXPW to the GrandParentXPW when create a ! new XPW ! 1/6/95 DCN Various tweaks to allow for multiple schedules per owner ! 2/6/95 DCN Change name of xpGetStrategy to xpGetDefaultStrategy ! 4/6/95 DCN Add State param to xpUnSchedule ! 8/7/95 CRJ Added stub for xpCreateNullSchedule ! 18/7/95 DCN Removed TRX from xpPrepareSchedules ! 21/9/95 CRJ Added xpTentativeSchedule. ! 2/10/95 CRJ Filled in xpCreateNullSchedule !}}} !{{{ 1996 ! 3/6/96 CRJ xpCreateSchedule ! 26/3/96 DCN xpSchedule ! 29/3/96 DCN xpCreateSummary ! 3/4/96 DCN xpPrepareSchedules ! 17/4/96 CRJ WO becomes WO+XP. ! 19/5/96 DCN xpCreateSchedule and xpMakeWorkSheet and xpCreateSummary ! Added xpFirstConsiderDate ! 23/5/96 CRJ xpCreateSchedule. ! 3/6/96 DCN xpSchedule ! 21/6/96 CRJ xpMakeWorksheet, xpCreateType, xpUnSchedule ! 21/6/96 CRJ Remove xpCreateNullSchedule. ! 12/7/96 DCN xpCreateSummary ! 13/7/96 DCN xpCreateSummary ! 14/7/96 DCN xpCreateSummary ! 31/7/96 DCN xpCreateSummary ! 1/8/95 CRJ All functions ! 12/8/96 CRJ xpSchedule ! 1/9/96 CRJ xpSchedule ! 3/9/96 CRJ xpSchedule ! 13/9/96 CRJ xpCreateSchedule,xpSChedule ! 13/9/96 CRJ Added xpReScheduleDemand ! 16/9/96 CRJ xpCreateSchedule ! 19/9/96 CRJ xpReScheduleDemand ! 20/9/96 CRJ xpReScheduleDemand ! 23/9/96 CRJ xpReScheduleDemand ! 25/9/96 CRJ xpReScheduleDemand ! 15/11/96 CRJ xpCreateSchedule ! 16/11/96 CRJ xpCreateSchedule ! 20/11/96 CRJ xpCreateSummary ! 10/12/96 CRJ xpRescheduleDemand ! 29/12/96 CRJ xpSchedule ! 30/12/96 CRJ xpRescheduleMoves !}}} !{{{ 1997 ! 6/1/97 CRJ xpCreateSummary ! 10/1/97 CRJ xpSchedule ! 14/1/97 DCN xpCreateSummary ! 25/1/97 CRJ xpCreateSchedule ! 26/1/97 CRJ xpSchedule ! 30/1/97 CRJ xpCreateSchedule ! 31/1/97 DCN xpCreateSchedule, xpSchedule, xpRescheduleMoves ! 31/1/97 CRJ xpSchedule, xpCreateSchedule ! 4/2/97 CRJ xpCreateSchedule ! 12/2/97 CRJ xpCreateSummary ! 15/2/97 CRJ xpRescheduleMoves ! 16/3/97 CRJ xpSchedule ! 27/3/97 CRJ xpRescheduleMoves ! 5/4/97 DCN xpSchedule ! 10/4/97 CRJ xpCreateSchedule ! 11/4/97 CRJ xpCreateSchedule ! 29/5/97 CRJ xpCreateSummary ! 28/6/97 DCN Added xpScheduleOne ! 2/7/97 CRJ xpScheduleOne ! 7/7/97 CRJ xpSchedule ! 7/7/97 CRJ xpScheduleOne ! 8/7/97 CRJ xpCreateSchedule ! 8/7/97 CRJ xpSchedule ! 16/7/97 CRJ xpSchedule ! 17/7/97 CRJ xpCreateSchedule ! 22/7/97 DCN xpCreateSchedule, xpCreateSummary, xpScheduleOne ! 7/8/97 DCN xpSchedule ! 14/8/97 CRJ xpScheduleOne ! 15/8/97 DCN xpSchedule, xpScheduleOne ! 20/8/97 DCN xpCreateSummary ! 21/8/97 DCN xpCreateSchedule, xpCreateSummary ! 21/8/97 CRJ xpCreateSchedule ! 4/9/97 CRJ Move xpCreateSummary to xp_make ! 11/9/97 CRJ xpScheduleOne ! 11/9/97 CRJ xpCreateSchedule ! 24/9/97 CRJ Add xpCreateScheduleHelper ! 29/9/97 CRJ xpSchedule ! 1/10/97 CRJ xpSchedule ! 3/10/97 CRJ xpCreateSchedule,xpCreateScheduleHelper,xpSchedule ! 10/10/97 CRJ xpCreateSchedule, xpCreateSCheduleHelper ! 14/10/97 CRJ xpCreateScheduleHelper ! 15/10/97 CRJ xpScheduleOne ! 31/10/97 DCN xpSchedule ! 3/11/97 CRJ xpCreateSCheduleHelper ! 3/11/97 CRJ xpCreateSchedule ! 25/11/97 DCN xpCreateScheduleHelper ! 27/11/97 DCN xpCreateScheduleHelper ! 4/12/97 CRJ xpSchedule ! 19/12/97 CRJ xpCreateScheduleHelper ! 21/12/97 DCN xpCreateScheduleHelper !}}} !{{{ 1998 ! 1/1/98 DCN xpSchedule, xpCreateScheduleHelper ! 12/1/98 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 15/1/98 CRJ xpCreateScheduleHelper ! 21/1/98 CRJ xpCreateScheduleHelper ! 16/2/98 DCN xpCreateScheduleHelper ! 24/2/98 CRJ xpCreateScheduleHelper ! 4/3/98 CRJ xpCreateScheduleHelper ! 8/3/98 CRJ xpCreateScheduleHelper ! 17/3/98 DCN xpSchedule ! 18/3/98 CRJ xpCreateScheduleHelper ! 19/3/98 CRJ xpCreateScheduleHelper ! 23/3/98 CRJ xpCreateScheduleHelper ! 24/3/98 CRJ xpCReateSCheduleHelper ! 31/3/98 CRJ xpSchedule ! 31/3/98 CRJ xpSchedule ! 7/4/98 CRJ xpCreateScheduleHelper ! 19/5/98 DCN xpSchedule ! 21/5/98 CRJ xpSchedule ! 2/6/98 DCN xpSchedule ! 20/6/98 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 22/6/98 DCN xpCreateSchedule, xpCreateScheduleHelper ! 23/6/98 DCN xpCreateScheduleHelper, xpCreateSchedule ! 9/7/98 CRJ xpCreateScheduleHelper ! 17/7/98 DCN xpScheduleOne ! 21/7/98 DCN xpCreateScheduleHelper, xpCreateSchedule ! 23/7/98 DCN xpSchedule ! 4/8/98 CRJ xpCreateScheduleHelper ! 15/8/98 DCN xpScheduleOne ! 18/8/98 DCN xpCreateScheduleHelper ! 27/8/98 DCN xpSchedule ! 31/8/98 DCN xpScheduleOne, xpCreateScheduleHelper ! 8/9/98 DCN xpCreateSchedule, xpCreateScheduleHelper ! 11/9/98 DCN xpCreateScheduleHelper ! 17/9/98 DCN xpCreateScheduleHelper ! 8/10/98 DCN xpCreateSchedule ! 8/10/98 CRJ xpSchedule ! 10/10/98 DCN xpCreateScheduleHelper ! 29/10/98 DCN xpSchedule, xpCreateScheduleHelper ! 30/10/98 DCN xpCreateScheduleHelper ! 25/11/98 DCN Use a 'failure' on the log trace where appropriate ! 26/11/98 DCN xpCreateScheduleHelper ! 7/12/98 DCN xpCreateScheduleHelper ! 24/12/98 DCN xpCreateScheduleHelper, xpSchedule !}}} !{{{ 1999 ! 11/1/99 CRJ xpSchedule ! 13/1/99 CRJ xpCreateScheduleHelper ! 14/1/99 CRJ xpCreateScheduleHelper ! 18/1/99 DCN xpCreateScheduleHelper, xpSchedule ! 19/1/99 DCN xpCreateScheduleHelper ! 20/1/99 DCN xpCreateScheduleHelper ! 24/1/99 DCN xpCreateScheduleHelper, xpSchedule ! 27/1/99 DCN xpCreateScheduleHelper ! 29/1/99 DCN xpCreateScheduleHelper ! 2/2/99 DCN xpCreateScheduleHelper ! 8/2/99 DCN xpCreateScheduleHelper ! 9/2/99 DCN xpCreateScheduleHelper ! 10/2/99 DCN xpCreateScheduleHelper ! 1/3/99 DCN xpCreateScheduleHelper ! 23/3/99 DCN xpCreateScheduleHelper ! 17/4/99 DCN xpCreateScheduleHelper ! 27/5/99 DCN xpCreateScheduleHelper ! 30/6/99 DCN xpCreateScheduleHelper ! 6/7/99 DCN xpCreateScheduleHelper ! 4/8/99 DCN xpSchedule ! 6/8/99 DCN xpCreateScheduleHelper ! 14/8/99 DCN xpCreateScheduleHelper ! 17/11/99 CRJ xpSchedule, xpCreateSchedule ! 18/11/99 DCN xpCreateScheduleHelper ! 14/12/99 DCN xpSchedule ! 16/12/99 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 23/12/99 DCN xpCreateSchedule, xpCreateScheduleHelper ! 28/12/99 DCN xpCreateSchedule, xpCreateScheduleHelper !}}} !{{{ 2000 ! 26/1/100 CRJ xpCreateSchedulerHelper ! 8/2/100 CRJ xpSchedule ! 10/02/00 DCN xpCreateScheduleHelper ! 20/03/00 CRJ xpSchedule ! 07/04/00 DCN xpSchedule, xpCreateScheduleHelper ! 08/04/00 DCN xpSchedule ! 21/06/00 CRJ xpSchedule ! 22/07/00 DCN xpSchedule ! 29/07/00 DCN xpCreateScheduleHelper ! 31/07/00 DCN xpCreateSchedule ! 03/08/00 DCN xpCreateScheduleHelper ! 05/08/00 DCN xpSchedule ! 08/08/00 DCN xpCreateScheduleHelper ! 24/08/00 DCN xpCreateScheduleHelper ! 26/08/00 DCN xpCreateScheduleHelper ! 01/09/00 DCN xpCreateScheduleHelper ! 14/09/00 DCN xpCreateScheduleHelper ! 18/09/00 DCN xpDestroySummaryWS, xpUnSchedule ! 19/09/00 DCN xpCreateScheduleHelper ! 22/09/00 DCN xpSchedule ! 05/10/00 CRJ xpSchedule ! 05/11/00 DCN xpSchedule ! 07/11/00 DCN xpSchedule ! 30/11/00 DCN xpCreateScheduleHelper ! 19/12/00 CRJ xpCreateSchedule !}}} !{{{ 2001 ! 16/03/01 DCN xpCreateScheduleHelper ! 22/03/01 DCN xpCreateScheduleHelper ! 08/04/01 DCN xpCreateScheduleHelper ! 20/05/01 DCN xpSchedule, xpCreateScheduleHelper ! 28/05/01 DCN xpSchedule ! 29/05/01 DCN xpCreateScheduleHelper ! 06/06/01 CRJ xpSchedule ! 30/06/01 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 02/07/01 DCN xpSchedule ! 02/08/01 CRJ xpCreateSchedule ! 22/08/01 CRJ xpCreateSchedule ! 02/10/01 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper, xpDestroySummaryWS ! 08/10/01 DCN xpCreateScheduleHelper, xpCreateSchedule ! 10/10/01 DCN xpSchedule ! 11/10/01 DCN xpCreateSchedule ! 12/10/01 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 13/10/01 DCN xpCreateScheduleHelper, xpCreateSchedule ! 14/10/01 DCN xpSchedule ! 30/11/01 DCN xpCreateScheduleHelper ! 11/12/01 DCN xpCreateSchedule, xpSchedule ! 19/12/01 DCN xpSchedule ! 31/12/01 DCN xpCreateSchedule, xpCreateScheduleHelper !}}} !{{{ 2002 ! 02/01/02 DCN xpSchedule ! 05/01/02 DCN xpSchedule ! 11/01/02 DCN xpCreateScheduleHelper ! 26/01/02 DCN xpCreateScheduleHelper, xpCreateSchedule ! 28/01/02 DCN xpCreateScheduleHelper ! 29/01/02 DCN xpSchedule ! 30/01/02 DCN xpCreateSchedule, xpCreateScheduleHelper, xpSchedule ! 31/01/02 DCN xpCreateSchedule, xpCreateScheduleHelper, xpSchedule ! 15/02/02 DCN xpCreateScheduleHelper ! 01/03/02 DCN xpCreateSchedule, xpSchedule ! 23/03/02 DCN xpCreateScheduleHelper ! 25/03/02 DCN xpSchedule ! 28/03/02 DCN xpSchedule ! 16/04/02 DCN xpCreateScheduleHelper ! 02/05/02 DCN xpSchedule, xpCreateScheduleHelper ! 31/05/02 DCN xpSchedule ! 06/06/02 DCN xpCreateScheduleHelper ! 07/06/02 DCN xpSchedule ! 13/06/02 DCN xpCreateScheduleHelper ! 05/07/02 DCN xpCreateScheduleHelper ! 27/07/02 DCN xpSchedule ! 02/08/02 DCN xpSchedule ! 07/08/02 DCN xpSchedule ! 10/08/02 DCN xpSchedule ! 04/10/02 DCN xpSchedule ! 05/10/02 DCN xpSchedule ! 07/10/02 DCN xpSchedule ! 11/10/02 DCN xpSchedule ! 17/10/02 DCN xpCreateScheduleHelper ! 21/10/02 DCN xpUnSchedule ! 03/11/02 DCN xpCreateScheduleHelper, xpSchedule, xpCreateSchedule !}}} !{{{ 2003 ! 01/02/03 DCN xpCreateScheduleHelper ! 13/02/03 DCN xpCreateScheduleHelper ! 14/02/03 DCN xpCreateSchedule ! 15/02/03 DCN xpDestroySummaryWS, xpSchedule, xpCreateScheduleHelper ! 16/02/03 DCN xpCreateSchedule ! 15/03/03 DCN xpSchedule ! 15/03/03 DCN xpCreateScheduleHelper ! 22/03/03 DCN xpSchedule ! 22/04/03 DCN xpCreateScheduleHelper ! 04/05/03 DCN xpSchedule ! 05/06/03 DCN xpSchedule ! 13/06/03 DCN xpCreateScheduleHelper ! 14/06/03 DCN xpUnSchedule ! 15/06/03 DCN xpSchedule ! 16/06/03 DCN xpSchedule ! 08/07/03 DCN xpCreateScheduleHelper ! 21/07/03 DCN xpCreateScheduleHelper ! 11/08/03 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 14/08/03 DCN xpCreateScheduleHelper, xpSchedule ! 17/08/03 DCN xpSchedule ! 27/08/03 DCN xpCreateSchedule ! 07/11/03 DCN xpCreateSchedule ! 16/12/03 CRJ xpCreateScheduleHelper ! 17/12/03 CRJ xpCreateScheduleHelper !}}} !{{{ 2004 ! 07/01/04 DCN xpCreateSchedule ! 01/03/04 DCN xpCreateScheduleHelper ! 12/03/04 DCN xpCreateSchedule, xpCreateScheduleHelper ! 15/03/04 DCN xpCreateSchedule, xpCreateScheduleHelper ! 17/03/04 DCN xpCreateScheduleHelper ! 18/03/04 DCN xpCreateSchedule ! 22/03/04 DCN xpCreateScheduleHelper, xpCreateSchedule, xpDestroySummaryWS ! Add xpMakeContainer ! 05/04/04 DCN xpSchedule ! 06/04/04 DCN xpUnSchedule ! 08/04/04 DCN xpSchedule ! 11/04/04 DCN xpCreateScheduleHelper ! 13/04/04 DCN xpCreateScheduleHelper ! 06/05/04 CRJ xpCreateScheduleHelper ! 07/05/04 DCN xpCreateSchedule ! 08/07/04 DCN xpSchedule ! 09/07/04 DCN xpCreateScheduleHelper, xpSchedule, xpCreateSchedule ! 12/07/04 DCN xpCreateScheduleHelper ! 05/08/04 DCN xpMakeContainer ! 12/08/04 DCN xpCreateScheduleHelper ! 23/08/04 DCN xpCreateScheduleHelper ! 15/09/04 DCN xpMakeContainer, xpCreateScheduleHelper ! 04/10/04 DCN xpCreateSchedule ! 29/10/04 DCN xpCreateScheduleHelper ! 30/12/04 DCN xpSchedule !}}} !{{{ 2005 ! 24/02/05 DCN xpCreateScheduleHelper ! 04/03/05 CRJ xpCreateSchedule ! 11/03/05 DCN xpCreateScheduleHelper, xpMakeContainer ! 26/04/05 DCN xpCreateScheduleHelper, xpMakeContainer ! 27/05/05 DCN xpCreateScheduleHelper ! 08/06/05 DCN xpCreateScheduleHelper ! 14/06/05 DCN xpMakeContainer ! 22/06/05 DCN xpCreateScheduleHelper ! 20/07/05 CRJ xpCreateScheduleHelper ! 14/10/05 CRJ xpSchedule !}}} !{{{ 2006 ! 19/01/06 DCN xpSchedule ! 07/06/06 CRJ xpCreateScheduleHelper ! 10/07/06 DCN xpMakeContainer, xpCreateScheduleHelper !}}} !{{{ 2007 ! 12/02/07 DCN xpCreateScheduleHelper ! 29/03/07 DCN xpCreateScheduleHelper ! 30/05/07 DCN xpSchedule ! 24/10/07 CRJ xpCreateSCheduleHelper !}}} !{{{ 2008 ! 29/02/08 DCN xpSchedule ! 12/03/08 DCN xpMakeContainer, xpCreateScheduleHelper ! 15/04/08 DCN xpSchedule ! 29/04/08 DCN xpCreateScheduleHelper ! 30/04/08 DCN xpSchedule ! 21/11/08 DCN xpSchedule !}}} !{{{ 2009 ! 28/03/09 DCN xpCreateScheduleHelper ! 24/04/09 DCN xpSchedule, xpCreateScheduleHelper ! 27/04/09 DCN xpCreateScheduleHelper ! 03/07/09 DCN xpSchedule !}}} !{{{ 2010 ! 28/05/10 DCN xpSchedule ! 25/10/10 DCN xpCreateScheduleHelper ! 28/10/10 CRJ xpCreateSchedulerHelper ! 28/10/10 CRJ xpMakeContainer !}}} !{{{ 2011 ! 16/03/11 DCN xpCreateSchedule ! 05/04/11 DCN xpCreateScheduleHelper ! 20/04/11 DCN xpSchedule ! 24/06/11 DCN xpCreateScheduleHelper ! 27/06/11 DCN xpCreateScheduleHelper ! 28/06/11 DCN xpCreateScheduleHelper ! 29/06/11 DCN xpCreateScheduleHelper, xpSchedule ! 30/06/11 DCN xpSchedule ! 01/07/11 DCN xpCreateSchedule, xpCreateScheduleHelper ! 05/07/11 DCN xpSchedule, xpCreateSchedule ! 06/07/11 DCN xpSchedule ! 07/07/11 DCN xpCreateScheduleHelper ! 09/07/11 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 14/07/11 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 27/07/11 DCN xpCreateScheduleHelper, xpSchedule ! 28/07/11 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 02/08/11 DCN xpCreateSchedule, xpSchedule, xpCreateScheduleHelper ! 03/08/11 DCN xpCreateScheduleHelper, xpSchedule ! 04/08/11 DCN xpCreateScheduleHelper ! 07/08/11 DCN xpCreateScheduleHelper ! 10/08/11 DCN xpSchedule, xpCreateScheduleHelper ! 11/08/11 DCN xpSchedule ! 12/08/11 DCN xpSchedule ! 13/08/11 DCN xpUnSchedule ! 15/08/11 DCN xpCreateScheduleHelper ! 18/08/11 DCN xpCreateScheduleHelper, xpSchedule, xpCreateSchedule, xpMakeContainer ! 19/08/11 DCN xpSchedule ! 21/08/11 DCN xpUnSchedule ! 23/08/11 DCN Remove xpPurgeSchedules (not used) ! xpCreateScheduleHelper ! 26/08/11 DCN xpUnSchedule ! 30/08/11 DCN xpCreateScheduleHelper ! 02/09/11 DCN xpSchedule ! 03/09/11 DCN xpCreateScheduleHelper, xpSchedule, xpUnSchedule ! 06/09/11 DCN xpSchedule ! 07/09/11 DCN xpCreateScheduleHelper ! 15/09/11 DCN xpCreateScheduleHelper ! 16/09/11 DCN xpCreateScheduleHelper ! 21/09/11 DCN xpSchedule ! 26/09/11 DCN xpSchedule, xpCreateScheduleHelper ! 03/10/11 DCN xpCreateScheduleHelper ! 05/10/11 DCN xpSchedule ! 12/10/11 DCN xpCreateScheduleHelper ! 13/10/11 DCN xpSchedule ! 18/10/11 DCN xpSchedule ! 21/10/11 DCN xpSchedule ! 24/10/11 DCN xpCreateScheduleHelper ! Add xpZapSchedule ! 27/10/11 DCN xpSchedule ! 28/10/11 DCN xpSchedule ! 31/10/11 DCN xpCreateScheduleHelper ! 03/11/11 DCN xpCreateScheduleHelper ! 07/11/11 DCN xpCreateScheduleHelper ! 09/11/11 DCN xpCreateSchedule, xpSchedule ! 10/11/11 DCN xpSchedule, xpZapSchedule, xpUnSchedule, xpCreateSchedule, xpCreateScheduleHelper ! Add xpGetPeers ! 11/11/11 DCN xpSchedule ! 14/11/11 DCN xpCreateSchedule, xpSchedule ! 16/11/11 DCN xpSchedule, xpCreateSchedule ! Add xpGetGranularity ! 17/11/11 DCN xpSchedule, xpCreateSchedule ! 18/11/11 DCN xpCreateScheduleHelper, xpGetGranularity, xpCreateSchedule ! Add xpGetChildren ! 23/11/11 DCN xpCreateScheduleHelper, xpMakeContainer ! 25/11/11 DCN xpUnSchedule ! 30/11/11 DCN xpCreateScheduleHelper, xpSchedule ! 07/12/11 DCN xpSchedule, xpCreateSchedule, xpCreateScheduleHelper ! 12/12/11 DCN xpCreateScheduleHelper ! 15/12/11 DCN xpSchedule ! 16/12/11 DCN xpCreateScheduleHelper ! 21/12/11 DCN xpSchedule ! 22/12/11 DCN xpZapSchedule, xpCreateSchedule ! 30/12/11 DCN xpCreateScheduleHelper !}}} !{{{ 2012 ! 02/01/12 DCN xpGetGranularity ! 03/01/12 DCN xpSchedule ! 06/01/12 DCN xpSchedule, xpCreateSchedule ! 13/01/12 DCN xpCreateSchedule ! 01/02/12 DCN xpSchedule ! 02/02/12 DCN xpCreateScheduleHelper ! 03/02/12 DCN xpCreateScheduleHelper ! 10/02/12 DCN xpSchedule ! 11/02/12 DCN xpCreateScheduleHelper ! 13/02/12 DCN xpSchedule, xpCreateSchedule, xpMakeContainer ! 15/02/12 DCN xpGetChildren ! 17/02/12 DCN xpUnSchedule, xpZapSchedule, xpSchedule ! 20/02/12 DCN xpCreateScheduleHelper, xpCreateSchedule, xpMakeContainer ! Add xpIsRaceWinByDate ! 21/02/12 DCN xpZapSchedule ! 22/02/12 DCN xpIsRaceWinByDate, xpSchedule, xpCreateScheduleHelper, xpCreateSchedule ! 23/02/12 DCN xpCreateScheduleHelper, xpSchedule ! 24/02/12 DCN xpGetChildren, xpCreateScheduleHelper, xpIsRaceWinByDate ! 04/03/12 DCN xpSchedule ! 05/03/12 DCN xpCreateSchedule, xpSchedule ! 07/03/12 DCN xpZapSchedule ! Add xpZapSchedule:SetNewWONum, xpZapSchedule:Promoted ! 08/03/12 DCN xpUnSchedule ! Add xpUnSchedule:DemoteSummary ! 09/03/12 DCN xpCreateScheduleHelper ! 23/03/12 DCN xpSchedule ! 26/03/12 DCN xpSchedule ! 30/05/12 DCN xpCreateSchedule ! 18/07/12 DCN xpSchedule ! 17/12/12 DCN xpZapSchedule ! 18/12/12 DCN xpZapSchedule !}}} !{{{ 2013 ! 18/01/13 DCN xpCreateScheduleHelper, xpIsRaceCandidate, xpIsRaceWinByDate ! 06/02/13 DCN xpSchedule ! 27/02/13 DCN xpMakeContainer ! 28/02/13 DCN xpZapSchedule, xpUnSchedule, xpGetChildren ! 01/03/13 DCN xpDestroySummaryWS, xpMakeContainer ! Add xpGetContainerQty ! 07/03/13 DCN xpCreateScheduleHelper ! 14/03/13 DCN xpZapSchedule, xpUnSchedule ! 15/03/13 DCN xpIsRaceWinByDate, xpCreateScheduleHelper ! 18/03/13 DCN xpDestroySummaryWS ! 19/03/13 DCN xpSchedule ! 21/03/13 DCN xpZapSchedule ! 17/05/13 DCN xpSchedule ! 30/05/13 DCN xpSchedule, xpCreateScheduleHelper ! 17/06/13 DCN Add xpFinishSummary ! xpCreateScheduleHelper ! 02/07/13 DCN xpSchedule !}}} !{{{ 2014 ! 26/02/14 DCN xpSchedule ! 27/02/14 DCN xpSchedule ! 03/03/14 DCN xpSchedule ! 07/10/14 DCN xpSchedule ! 24/10/14 DCN xpSchedule ! 27/11/14 DCN xpCreateSchedule !}}} !{{{ 2015 ! 26/03/15 DCN xpIsRaceWinByDate ! 29/06/15 DCN xpCreateSchedule ! 27/10/15 DCN xpCreateScheduleHelper !}}} !{{{ 2017 ! 01/03/17 DCN xpCreateSchedule ! 23/03/17 DCN xpSchedule, xpCreateSchedule ! 24/05/17 DCN Move xpGetChildren to xp_merge.clw ! 15/08/17 DCN xpGetContainerQty ! 04/10/17 DCN xpCreateScheduleHelper ! 05/10/17 DCN xpCreateScheduleHelper ! 10/10/17 DCN xpGetGranularity !}}} !{{{ 2018 ! 01/03/18 DCN xpCreateScheduleHelper ! Add xpSetOrderQty ! 03/03/18 DCN xpGetGranularity ! 20/05/18 DCN xpIsRaceCandidate, xpCreateScheduleHelper, xpIsRaceWinByDate ! 26/07/18 DCN xpCreateSchedule !}}} !{{{ 2019 ! 12/07/19 DCN xpSchedule ! 16/12/19 DCN xpCreateScheduleHelper ! 17/12/19 DCN xpCreateScheduleHelper !}}} !{{{ 2020 ! 30/12/20 DCN xpFinishSummary !}}} !{{{ 2021 ! 24/05/21 DCN xpFinishSummary !}}} !{{{ 2022 ! 24/11/22 CRJ xpCreateScheduleHelper !}}} ! 20/09/23 DCN xpSchedule ! 22/09/23 DCN xpSchedule ! 24/09/23 DCN xpCreateScheduleHelper ! 25/09/23 DCN xpSchedule ! 05/10/23 DCN xpSchedule ! 09/10/23 DCN xpSchedule ! 12/10/23 DCN xpSchedule ! 22/11/23 DCN xpCreateScheduleHelper ! 23/11/23 DCN xpGetGranularity ! 24/11/23 DCN xpCreateScheduleHelper ! 27/11/23 DCN xpZapSchedule:Promoted, xpZapSchedule:SetNewWONum, xpSchedule, xpCreateSchedule, xpMakeContainer ! 06/12/23 DCN xpCreateScheduleHelper ! 09/12/23 DCN xpSchedule ! 10/12/23 DCN xpMakeContainer ! Add xpDeAllocateContainers ! 11/12/23 DCN xpCreateScheduleHelper, xpZapSchedule:SetNewWONum ! Add xpIsRaceWinByQty ! 04/01/24 DCN xpCreateScheduleHelper ! 05/01/24 DCN xpCreateScheduleHelper ! !}}} !{{{ module notes !}}} !{{{ module map MAP xpCreateScheduleHelper(LONG,*xpSchedStateType,LONG,*LONG),LONG xpMakeContainer(LONG,LONG,LONG,BYTE,STRING,LONG,LONG,*LONG,*LONG,*LONG,*LONG,*STRING,*STRING,*LONG),LONG xpGetGranularity(STRING,BYTE=0,*STRING,*STRING) xpSetOrderQty(LONG,*STRING,STRING) xpIsRaceWinByQty(STRING TotalQty,STRING ExistingQty,LONG WorkBackwards,STRING LogPrefix),LONG xpIsRaceCandidate(LONG=0,LONG,LONG,LONG,LONG,LONG,<*STRING>),LONG xpZapSchedule:Promoted(LONG,LONG,LONG=0),LONG xpZapSchedule:SetNewWONum(LONG,LONG,LONG,LONG),LONG xpUnSchedule:DemoteSummary(LONG),LONG,PROC END !}}} !{{{ module data !}}} !{{{ xpSchedule !{{{ history ! !{{{ 1995 ! 29/3/95 DCN Created ! 5/4/95 DCN Split main function away to xpCreateSchedule ! 14/4/95 DCN Clear the sticky error code before exit ! 6/4/95 DCN Open all files up front ! Add EndDate parameter !}}} !{{{ 1996 ! 6/3/96 CRJ Add mck,mco and rsk to opened file list ! 26/3/96 DCN Change MCK to MCP ! 3/6/96 DCN Remove open/close of the RSK ! 6/7/96 CRJ Calls to RAM stuff ! 12/8/96 CRJ Cache the RSW once only for this schedule ! 2/9/96 CRJ Integrate xpFullQuantityStrategy (the only other ! place we need to tweak is xpIsAchievable()) ! 3/9/96 CRJ Integrate xpFullQtyStartDays ! 13/9/96 CRJ Added BuyAndMake flag ! 16/11/96 CRJ Integrate MakeDate ! 29/12/96 CRJ Don't reload summary if aborted !}}} !{{{ 1997 ! 10/1/97 CRJ Add date absurdity checking ! 26/1/97 CRJ Change definition of FullQty strategy - I really must write a ! user note on the scheduler. ! 28/1/97 CRJ API changes for quantity solution ! 31/1/97 DCN Implement new philosophy wrt always go forwards first, then backwards if asked to do so ! 31/1/97 CRJ Tweak new philosophy a bit ! 17/2/97 CRJ Add MCG to file open list ! 26/2/97 CRJ Convert dates to absolute form on entry ! 16/3/97 CRJ Open RSC and RSU ! 4/4/97 CRJ Add top-level tracing ! 5/4/97 DCN Correct sense of qty test when gen'ing success/fail trace message ! 7/7/97 CRJ Separate allocated qtys/costs from make+buy+subby+stock costs ! 8/7/97 CRJ Fix total qty calculation ! 16/7/97 CRJ Improved trace messages (IMO) and tidied; was a mess. Fix bugs. ! 7/8/97 DCN Correct whoopsie in cost calc from old stock ! 15/8/97 DCN Remove xpFullQtyStartDays offset for start limit on JIT schedule ! 29/9/97 CRJ Remove schedule on abort ! 1/10/97 CRJ Take into accout spawned summaries parallel to SummaryWS for costing ! 1/10/97 CRJ Store the alloc and create qty/cost in the meta summary ! 31/10/97 DCN Include MCB in open/clear/close set ! 4/12/97 CRJ Handle MS API changes !}}} !{{{ 1998 ! 1/1/98 DCN Show dims in trace message ! 12/1/98 DCN Ignore max stock strategy when doing JIT ! 17/3/98 DCN Add more UM bracketing ! 31/3/98 CRJ Open the MSI/MSC/WOH as well ! 31/3/98 CRJ Fix dispatch date stuff ! 19/5/98 DCN Always succeed when scheduling a phantom service ! 21/5/98 CRJ Copy qualifiers from meta summary ! 2/6/98 DCN Update the WS when do a phantom service and log a trace message! ! 20/6/98 DCN xpCreateSchedule API change ! 23/7/98 DCN Gen title 'trigger' log message and adjust log trace depth ! 27/8/98 DCN Remove open/close of MCG,MCA,MCP,MCR ! 8/10/98 CRJ Dont ignore MAXSTK when JIT ! 29/10/98 DCN xpwC:OwnerFile is now a FileNo (SHORT), not a TLA (STRING(3)) ! 24/12/98 DCN Don't ignore reserved when looking for dispatch date !}}} !{{{ 1999 ! 11/1/99 CRJ Fix end limit when scheduling JIT ! 12/1/99 CRJ Calculate start/end limits rather then get given them ! 12/1/99 CRJ Don't start schedules until tomorrow if it's late ! 12/1/99 CRJ Don't start schedules until the next day we're open if it's late ! 18/1/99 DCN Remove looking for dispatch date (superceded by xpCreateSchedule:Helper ! double stock stab) ! 24/1/99 DCN Save corrected target date in the worksheet ! Skip JIT if target no better than first possible date ! 3/2/99 DCN Add EndLimit date to ASAP schedule start log message ! 4/8/99 DCN Remove init of xpwCNextWO (its done globally) ! 17/11/99 CRJ dont assign quals ! 14/12/99 DCN Make JIT to ASAP transition log message 'cos target date too early an alert (i.e. -1) ! 16/12/99 DCN When JIT fails, try ASAP from the earliest date JIT found, not xpFirstScheduleDate() ! 28/12/99 DCN xpCreateSchedule API !}}} !{{{ 2000 ! 8/2/100 CRJ Add resource efficiency warnings ! 20/03/00 CRJ Reset xpRRSchedule JIC ! 07/04/00 DCN xpCheck API ! 08/04/00 DCN Remove xpRRSchedule (no longer exists) ! 21/06/00 CRJ Only create out of thin air if a phantom sell service ! 22/07/00 DCN Don't make ASAP target date absolute ! 05/08/00 DCN xpAnalyseSchedules function created ! 05/08/00 DCN Get initial start limit from worksheet ! 18/09/00 DCN Purge all types when call xpPurgeTypes ! 22/09/00 DCN Put all log messages on the meta summary so they don't become backtracks ! 05/10/00 CRJ Integrate xpStockOnly Strategy bit ! 05/11/00 DCN Remove stock allocation (but *not* the stock record) for sold services ! when done (to release for subsequent use in this scheduling 'session'). ! 07/11/00 DCN Use xpHideAlloc not xpRemoveAlloc to release sold services !}}} !{{{ 2001 ! 20/05/01 DCN Allow for SOP ! 28/05/01 DCN Only open the MCS and MCB (rest are done by access primitives) ! 05/06/01 DCN Don't hide contract demand ! 30/06/01 DCN Don't load WS after create it (its already loaded) ! xpStockRecord renamed xpStockUseRecord ! 02/07/01 DCN Don't call rsCacheWeeks (its auto by RS) ! 02/10/01 DCN xpPurgeTypes API ! Disallow scheduling a container unless its a WO ! 10/10/01 DCN Allow containers ! 12/10/01 DCN Rename xpOutputRecord to xpMadeRecord ! 14/10/01 DCN Allow for relative end date when checking for JIT failure ! 11/12/01 DCN Consider OutputRecord types and *all* StockUseRecord types in final value too ! 19/12/01 DCN Only consider stock use records that are not sourced from a top level make/buy in final total value ! Call xpFocusOnWOrder when approp ! 21/12/01 DCN xpFocusOnWOrder API ! Call xpFocusOnPOrder and xpFocusOnStock too. !}}} !{{{ 2002 ! 02/01/02 DCN Don't clear file buffers on open ! 05/01/02 DCN Don't focus on owner when doing a costing or test schedule or the NoFocus param is set ! 29/01/02 DCN Drop order date param ! 30/01/02 DCN Set target date to the first consider date in the scheduled summaries ! 31/01/02 DCN xpCreateSchedule API ! 01/03/02 DCN Warn if doing a consumable ! 25/03/02 DCN Show ASAP OK as an alert if JIT failed ! 28/03/02 DCN Use xpSetSelf if don't use xpFocusOnStock ! 02/05/02 DCN Extend date info in log messages ! 31/05/02 DCN Always do ASAP from now when abandon JIT ! 07/06/02 DCN Undo the above (the problem to be fixed is to do with mutually dependent returns and this doesn't do it) ! 27/07/02 DCN Extend efficiency warning message to show effect ! 02/08/02 DCN Always show ASAP target dates achieved as an alert when done ! 07/08/02 DCN Create alerts for stock, suppliers and resources with undefined costs ! 10/08/02 DCN Don't alert swapping from JIT to ASAP if the target date is ASAP ! 04/10/02 DCN Open/close the RSU too ! 05/10/02 DCN rsGetInstances has become rsGetUnitCost ! 07/10/02 DCN Don't delete the log when abort (so can view it for clues) ! 11/10/02 DCN rsGetUnitCost API ! 03/11/02 DCN Return error code not T/F !}}} !{{{ 2003 ! 15/02/03 DCN Remove guards from xpDestroySummaryWS, its done internally now ! 15/03/03 DCN Fix missing param fom JIT late trace message ! 22/03/03 DCN Set a null target time when adjust date ! Set end limit date for ASAP based on start limit not the target ! 04/05/03 DCN Implement use overtime if late ! 05/06/03 DCN Don't hide volatile sold services (they're not available to others) ! 15/06/03 DCN Put the material in level 1 alerts ! 16/06/03 DCN Treat a TBD date as don't schedule ! 11/08/03 DCN xpCreateSchedule API ! 17/08/03 DCN Reset start/end date when do an ASAP to clear junk left by JIT !}}} !{{{ 2004 ! 05/04/04 DCN Re-instate hiding of contract created stock ! 08/04/04 DCN Don't hide contract stock! (see discussion in the code) ! 08/07/04 DCN Remove redundant gaurd from phantom service detection ! Use mcGetCost for phantoms not just set to 0 ! 09/07/04 DCN When calculating total costs do not go below the allocated costs ! Allow for invented stock ! 30/12/04 DCN Init common strategy masks (optimisation to minimise calls to gxBitInMask) !}}} !{{{ 2005 ! 14/10/05 CRJ xpSetSelf and xpFocusOnStock APi changes !}}} !{{{ 2006 ! 19/01/06 DCN [10919] Ignore output records when accumulating costs (they do not contribute to costs) !}}} !{{{ 2007 ! 30/05/07 DCN Call the xpFocus<...> functions in all cases to clear caches!! !}}} !{{{ 2008 ! 29/02/08 DCN Add a level 1 alert if shifts are being ignored ! 15/04/08 DCN Don't use gxFormat for monitor start message (too slow) ! 29/04/08 DCN Use schedule mode in calls to xpFirstScheduleDate() ! 30/04/08 DCN xpFirstConsiderDate API ! Force back-flush schedule to be JIT and only JIT ! 21/11/08 DCN Be silent about undefineds in rsGetUnitCost !}}} !{{{ 2009 ! 24/04/09 DCN mcGetCost API ! 03/07/09 DCN Differentiate between consumable and volatile in warning message !}}} !{{{ 2010 ! 28/05/10 DCN Use xpDecodeOvertime() not DIY !}}} !{{{ 2011 ! 20/04/11 DCN Ignore TOOL costs when accumulating overall cost ! 29/06/11 DCN Don't use JIT fail start date as start limit for ASAP ! 30/06/11 DCN Remove xpUseUnApprovedCapacityMask (no longer a strategy bit) ! 05/07/11 DCN Remove redundant xpRead/Check calls ! Implement a MAXTIME algorithm for JIT ! 06/07/11 DCN Use delivery day as day limits not ordering day ! 09/07/11 DCN Don't adjust the target date in WS (done later now) ! 14/07/11 DCN Set xpwC:UsedDate/Time in the meta-summary to reflect the true EndDate/Time of the schedule ! 27/07/11 DCN Remove start limit if JIT fails ! 28/07/11 DCN Use xpDecode/EncodeDirection, not DIY ! Use xpIsLate not DIY ! Do JIT overtime before ASAP overtime if JIT fails ! Implement ASAP:JIT mode ! Use xpIsAchievable not DIY ! 02/08/11 DCN Fix GetTotals handling ! 03/08/11 DCN Introduce notion of a lower limit for JIT:SPLIT qty ! 10/08/11 DCN Re-implement ASAP:JIT to do binary chop on start date ! 11/08/11 DCN If JIT fails, fall back to ASAP/JIT not ASAP ! 12/08/11 DCN If ASAP/JIT fails only try JIT/SPLIT if appropriate, else plain ASAP ! 18/08/11 DCN Add xpwC:Start/Raise/EndTime stuff ! Remove return params (use xpIsAchievable to get them) ! xpIsAchievable API ! 19/08/11 DCN Use StartDate/Time as the start point for ASAP schedules not StartLimitDate ! 02/09/11 DCN Call xpReAllocateStock() to attempt stock re-allocation whenever commit to a schedule ! Use BatchValue not UsedValue in stock use records ! 03/09/11 DCN Use xpOlder not gxOlder ! 06/09/11 DCN Remove xpMerge::id as an owner, doesn't happen ! 20/09/11 DCN Use xpSchFlgAnyStockCheck not xpSchFlgJustStockCheck when probing for stock ! 21/09/11 DCN Consider xpDemand::Id ! 26/09/11 DCN Honour the make unit size when calc'ing the jit/split quanta ! 05/10/11 DCN Ignore returns and MSI's when scheduling merged demands ! 13/10/11 DCN Don't allow merged demands to race (disabled) ! 14/10/11 DCN Don't ignore MSI's when scheduling merged demands ! 18/10/11 DCN Don't do JIT if target is 'today' or earlier ! Move issues to actual required date when done ! 21/10/11 DCN Re-do ASAP as JIT ! 27/10/11 DCN Simplify the direction change options (remove implicit ASAP/JIT) ! Simplify what ASAP/JIT means ! 28/10/11 DCN Re-instate MinTryQty for JIT/SPLIT ! Allow for split batches in GetTotal ! 09/11/11 DCN Don't destroy the meta-summary when get an error (so can diagnose it via the trace) ! 10/11/11 DCN Use xpwC:Sibling not xpwC:Ancester ! Don't JIT/SPLIT unless scope for at least 1 quanta to split ! 11/11/11 DCN Don't JIT/SPLIT if doing NoMix ('cos run risk of creating N batches that higher level can't use) ! Don't JIT/SPLIT if residue goes below the re-order qty ! Don't JIT/SPLIT if residue goes below min batch size ! 14/11/11 DCN Don't set IsSplitBatch (only allowed via xpMakeMaterial) ! 16/11/11 DCN Allow for visiting the same summary in GetTotals ! Chuck siblings too when dump a failed schedule ! Update required qty when get stuff from the stock probe ! Use xpGetGranularity not DIY ! 17/11/11 DCN Don't do initial stock check when doing NOMIX ! Don't allow JIT/SPLIT when doing MAXSTOCK (pointless) ! 30/11/11 DCN Don't fiddle with date when go from ASAP to JIT:LAST ! 07/12/11 DCN Implement 'allocate' and 'release' schedules ! 15/12/11 DCN Adjust the OrderQty when get stuff from stock too ! 21/12/11 DCN Call xpFlushFloaters when done !}}} !{{{ 2012 ! 03/01/12 DCN Don't ignore returns when doing demands ! 06/01/12 DCN Allow for OrderQty not being in same units as RequiredQty ! 01/02/12 DCN Correct xpAdjustKitIssueDates call! ! 10/02/12 DCN Honour xpMergeOnlyShortages ! 13/02/12 DCN Allocate a new WO# when clone the meta summary ! 17/02/12 DCN Allow for ASAP beating JIT when JIT was late (due to diff WIP race context) ! 20/02/12 DCN xpCreateSchedule API ! 22/02/12 DCN Ignore late WIP when do the final JIT:LAST schedule ('cos already been raced) ! ASAP beating JIT is no longer an issue, 'cos JIT:LAST will beat JIT too ! When JIT fails, re-do JIT at a later date rather than do it ASAP ! 23/02/12 DCN Do an acceleration algorithm when probing JIT after an abandon ! 04/03/12 DCN Honour xpTraceMaterial ! 05/03/12 DCN Don't do post ASAP JIT if MAXSTOCK split the batch ! 23/03/12 DCN Clear xpIgnoreLateWIPStrategy when doing ASAP ! 26/03/12 DCN Honour xpTimeAlignKitSchedules ! 18/07/12 DCN Honour xpNoJITafterASAP ! 03/08/12 DCN Try overtime even if JIT was abandonned due to lost WIP race, see #37278 for why !}}} !{{{ 2013 ! 06/02/13 DCN Use umIsBiggerReal and umIsSamllerReal not DIY ! 19/03/13 DCN Don't log against SummaryWS after its been deleted! ! 17/05/13 DCN Preserve alerts when dump a schedule ! 30/05/13 DCN Use xpCacheThread not xpCacheDepth ! 02/07/13 DCN Add more cost calculation logging !}}} !{{{ 2014 ! 26/02/14 DCN Be lazy about dumping schedule logs ! 27/02/14 DCN Use xpGetOwnerInfo not DIY to get EntryID ! 03/03/14 DCN Don't treat JIT abandonned to use WIP as an alert (already alerted at a lower level) ! 07/10/14 DCN Honour xpSession:NoJITafterASAP ! 24/10/14 DCN Honour xpSession:JITSplitAsJIT !}}} ! 23/03/17 DCN Add more logging for why JIT:LAST is not done ! 12/07/19 DCN [58952]When accumulating qty's for final cost normalise to the MCH frame ! 20/09/23 DCN xpDecodeDirection, xpEncodeDirection API ! 22/09/23 DCN Use xpGetEndOfDeliveryDay(), xpGetStartOfDeliveryDay() not DIY ! 25/09/23 DCN Remove xpTimeAlignKitSchedules guard (pointless when got xpNoJITafterASAP) ! 05/10/23 DCN Remove reference to xpSchedQ.EntryID!! ! Do JIT:LAST to later of end date or target date ! 09/10/23 DCN Only look at xpSession:NoJITafterASAP session option not the default ! 12/10/23 DCN After a JIT fail, do ASAP then then JIT:LAST rather than JIT probing (more accurate) ! 27/11/23 DCN Use xpAllocateWONum() not DIY ! 09/12/23 DCN Add better logging ! 11/12/23 DCN Don't delete the log for a failed stock probe ! Nor when do a direction change JIT-->ASAP keep the JIT failure ! !}}} !{{{ description ! ! Schedule a quantity of material as specified by the supplied ! top-level summary WS. Results can be accessed via xpIsAchievable. ! ! xpSchedule(LONG,LONG=0),LONG,PROC ! ! ! !ErrorCode ! ! !TRUE to not focus on the owner ! !Handle - top level summary WS (requirement within here) ! ! NB: Containers are not schedulable at this level. ! !{{{ direction strategies !There are four direction strategies that dictate what to do in an !attempt to achieve a date. If any of the attempts fail, then another !strategy is attempted until one succeeds or we run out of options. !The term 'fail' above is referring to missing the target date/time. !The sequence for each of these directions is: ! ASAP --> JIT/LAST --> ASAP/LAST (1) ! JIT --> ASAP --> JIT/LAST --> ASAP/LAST (2) ! JIT/SPLIT* --> ASAP --> JIT/LAST --> ASAP/LAST (3) ! ASAP/JIT --> JIT --> ASAP --> JIT/LAST --> ASAP/LAST (4) ! (1) do ASAP from set start date ! (2) do JIT from set target date, then ASAP from 'now' ! (3) do JIT, then attempt to break up the qty to achieve JIT, ! then ASAP on the residue, the break-up loop is performed ! multiple times until no more JIT is achievable ! (4) do ASAP from set start date, then JIT from set target date, ! then ASAP from 'now' !All ASAP schedules finish with a JIT from the ASAP end date. The !rationale for this is explained in note 73509 in [32545]. It needs !to always be done after an ASAP even if the ASAP schedule is not late. !This final JIT pass is referred to in the code as JIT:LAST. !If a JIT/LAST schedule fails (it shouldn't!), then ASAP is done again. !}}} !}}} xpSchedule FUNCTION(MetaSummaryWS,NoFocus) !{{{ data OrigTraceDetail LIKE(xpMaxTraceDetail:Value) OrigDebugDetail LIKE(xpDebugDetail:Value) LogDepth LONG(0) !{{{ stuff needed by JIT:LAST OwnerQ QUEUE(gxRecNoQType). OrigOwnerStrategy LIKE(xpwC:AllocationStrategy) !the original strategy of the schedule owner (pre-merging tweaks) ownerASAP BYTE ownerASAP:JIT BYTE ownerJIT BYTE ownerJIT:SPLIT BYTE ownerDEFAULT:DIR BYTE ownerIsJIT BYTE !}}} !{{{ schedule results AllocCost LIKE(xpwC:UsedValue) AllocQty LIKE(xpwC:UsedQuantity) TotalCost LIKE(xpwC:CreateValue) TotalQty LIKE(xpwC:CreateQuantity) !}}} !{{{ file states mcb::Open BYTE mcs::Open BYTE rsu::Open BYTE !}}} !Direction strategy options Direction GROUP ASAP BYTE JIT BYTE ASAP:JIT BYTE JIT:SPLIT BYTE DEFAULT BYTE !this is only here for the API, its never used JIT:LAST BYTE !internal state - used after an ASAP to do JIT from its end date JIT:FAIL BYTE !internal state - used to tell ASAP that a JIT was attempted and failed END UseOvertime BYTE UseOvertimeWhenLate BYTE AlertResult BYTE ProbeStock BYTE !{{{ JIT probe control LoopsDone LONG !incremented for every iteration around the JIT loop LoopsStartedAt LONG !when the JIT probing started Interval REAL !the JIT time-line increment !}}} OwnerFile LIKE(xpwC:OwnerFile) Material LIKE(xpwC:Material) Strategy LIKE(xpwC:AllocationStrategy) !the strategy to use in cloned summaries Flags LONG !{{{ allocate/release schedule stuff ScheduleMode LIKE(xpwC:ScheduleMode) EntryID LONG AllocFile SHORT AllocRec LONG AllocInstance LONG AllocWS LIKE(xpwC:Handle) !}}} !{{{ finding JIT:SPLIT and ASAP:JIT limit stuff RequiredQty LIKE(xpwC:RequiredQty) !What we want OrderQty LIKE(xpwC:OrderQty) !.. QuantiseQty LIKE(xpwC:RefQty) !Min possible MinTryQty LIKE(xpwC:PartQty) !The min try limit being imposed from above or context MaxTryQty LIKE(xpwC:RequiredQty) !The max try limit for the current iteration TryQty LIKE(xpwC:RequiredQty) !Current JIT probe TryDate LIKE(xpwC:StartDate) !Current ASAP probe TryTime LIKE(xpwC:StartTime) !.. !Result of last probe ThisState GROUP(xpSchedStateType) . ThisStartDate EQUATE(ThisState.StartDate) ThisStartTime EQUATE(ThisState.StartTime) ThisEndDate EQUATE(ThisState.EndDate) ThisEndTime EQUATE(ThisState.EndTime) ThisRaiseDate EQUATE(ThisState.RaiseDate) ThisRaiseTime EQUATE(ThisState.RaiseTime) ThisAllocQty EQUATE(ThisState.AllocQty) ThisAllocCost EQUATE(ThisState.AllocCost) !Result of best probe BestState GROUP(xpSchedStateType) . BestStartDate EQUATE(BestState.StartDate) BestStartTime EQUATE(BestState.StartTime) BestEndDate EQUATE(BestState.EndDate) BestEndTime EQUATE(BestState.EndTime) BestRaiseDate EQUATE(BestState.RaiseDate) BestRaiseTime EQUATE(BestState.RaiseTime) BestAllocQty EQUATE(BestState.AllocQty) BestAllocCost EQUATE(BestState.AllocCost) !Extras for JIT/SPLIT probing BestTotalQty LIKE(xpwC:RequiredQty) BestTryDate LIKE(xpwC:StartDate) BestTryTime LIKE(xpwC:StartTime) !}}} Achieved GROUP(xpAchievedType) . SummaryWS LONG PendingLogWS LONG !if set, its a schedule log that should be deleted if a subsequent schedule is dumped !{{{ our dates EarliestStartDate LONG !can't go below this EarliestStartTime LONG !.. StartDate LONG !start date to be used by ASAP StartTime LONG !.. EndDate LONG !start date to be used by JIT EndTime LONG !.. TargetDate LONG !what we're aiming at - the 'lateness' threshold TargetTime LONG !.. !}}} !This Q contains cost/qty contributors to the total value/qty of the !schedule. This may be more than the allocated value. Its a mixture !of stock creators (make/buy, etc) and stock allocations (use). The !allocations are only those that are not also a creator. CostQ QUEUE,PRE(CostQ) Handle LIKE(xpwC:Handle) !(+) UsedQuantity LIKE(xpwC:UsedQuantity) UsedValue LIKE(xpwC:UsedValue) END !Used to alert undefined costs NoCostQ QUEUE,PRE(NoCostQ) FileNo SHORT !(+) RecNo LONG !(+) END !Used for monitor start message sQty STRING(24) sMat STRING(64) sDat STRING(8) !Used to dump/cost schedules PeerQ QUEUE(gxRecNoQType). !}}} CODE DO ProcedureEntry !{{{ get the meta WS and the stuff we need from it Err#=xpLoadWS(MetaSummaryWS,1212) IF Err# THEN DO ProcedureExit. Material = xpwC:Material !for log messages OwnerFile = xpwC:OwnerFile !.. Strategy = xpwC:AllocationStrategy !the strategy to use in cloned summaries RequiredQty = xpwC:RequiredQty !this is the ultimate demand OrderQty = mcNormaliseTo(xpwC:Material, | xpwC:OrderQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:Length,xpwC:Width,xpwC:Height) !reduced by every successful allocation ScheduleMode = xpwC:ScheduleMode !{{{ get stuff needed by the JIT:SPLIT and ASAP:JIT probing logic xpGetGranularity(xpwC:MinJitSplitQty,,QuantiseQty,MinTryQty) xpFirstConsiderDate(StartDate,StartTime,FALSE) !ASAP start xpFirstConsiderDate(EndDate ,EndTime ,TRUE ) !JIT start TargetDate = EndDate !What we're aiming at TargetTime = EndTime !.. !}}} !{{{ get stuff needed by 'release' and JIT:LAST !EntryID is needed by 'release' !ownerIsJIT is needed by JIT:LAST xpGetOwnerInfo(xpwC:OwnerFile,xpwC:OwnerRec,xpwC:OwnerInstance,,,,EntryID,OrigOwnerStrategy) ownerIsJIT = TRUE !this means JIT:LAST will target the required date if its later than achieved IF xpwC:OwnerFile AND xpwc:OwnerFile <> xpDemand::Id !This is a top level, so honour its JIT setting in JIT:LAST xpDecodeDirection(OrigOwnerStrategy,ownerASAP,ownerASAP:JIT,ownerJIT,ownerJIT:SPLIT,ownerDEFAULT:DIR) IF ~(ownerJIT OR ownerJIT:SPLIT) ownerIsJIT = FALSE !this means JIT:LAST will target the achieved date, regardless END END !}}} EarliestStartDate = xpFirstScheduleDate(ScheduleMode,EarliestStartTime) IF gxBitInMask(xpStockOnlyStrategy,xpwC:AllocationStrategy) Flags = xpSchFlgJustStockCheck !Overtime irrelevant UseOverTime = FALSE UseOverTimeWhenLate = FALSE ELSE Flags = xpSchFlgNone !Honour overtime options xpDecodeOverTime(xpwC:AllocationStrategy,UseOverTime,UseOverTimeWhenLate) END AlertResult = FALSE !}}} !{{{ special case: 'release' ! ! A 'release' schedule means find the associated 'allocate' and ! un-schedule it. This releases stock pre-allocated by the merging ! system for use by the upcoming real schedule. The merging system ! is responsible for placing 'release' requests in the appropriate ! place in the schedule list. We just do as we're told. ! IF ScheduleMode = xpScheduleMode:Release !We find the associated 'allocate' via the EntryID in our entry. !The EntryID for the 'allocate' is +ve, for the associated 'release' !its the same ID but -ve. !Get the EntryID to look for IF ~EntryID xpWriteLog(MetaSummaryWS,,-1,'Cannot find EntryID for RELEASE schedule @',| zvMaterial,xpwC:Material) Err# = xpDisplayError(,'xpSchedule: Cannot find EntryID for RELEASE schedule',gxInternalErr) DO ProcedureExit ELSE !the allocate ID is the -ve of the release ID EntryID = 0-EntryID END !Find the allocate owner IF ~xpGetOwnerByID(EntryID,AllocFile,AllocRec,AllocInstance) xpWriteLog(MetaSummaryWS,,-1,'Cannot find owner for ALLOCATE schedule @',| zvInt,EntryID) Err# = xpDisplayError(,'xpSchedule: Cannot find owner for ALLOCATE schedule ' & EntryID,gxInternalErr) DO ProcedureExit END xpWriteLog(MetaSummaryWS,,1,'Releasing @ @ for @ @',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,| zvDate,xpwC:TargetDate,zvClock,xpwC:TargetTime) !Un-schedule it AllocWS = xpIsScheduled(AllocFile,AllocRec,AllocInstance) IF ~AllocWS xpWriteLog(MetaSummaryWS,,-1,'Cannot find worksheet for ALLOCATE schedule @',| zvInt,EntryID) Err# = xpDisplayError(,'xpSchedule: Cannot find worksheet for ALLOCATE schedule',gxInternalErr) DO ProcedureExit END Err# = xpDestroySummaryWS(AllocWS,,TRUE) IF Err# THEN DO ProcedureExit. !{{{ mark the worksheet as succeeded xpwC:StartDate = xpwC:TargetDate xpwC:StartTime = xpwC:TargetTime xpwC:EndDate = xpwC:StartDate xpwC:EndTime = xpwC:StartTime xpwC:UsedDate = xpwC:StartDate xpwC:UsedTime = xpwC:StartTime xpwC:CreateQuantity = xpwC:RequiredQty !NB: Ref frame is xpwC:Length/Width/Height xpwC:CreateValue = umMake(umUnitLess:Value,umInfinity) xpwC:UsedQuantity = xpwC:CreateQuantity xpwC:UsedValue = xpwC:CreateValue Err#=xpPutWS() IF Err# THEN DO ProcedureExit. !}}} DO ProcedureExit END !}}} !{{{ special case: target date is TBD ! ! A target date of TBD means do nothing now at an undefined cost ! IF TargetDate = gxDateTBD !Note the case for the user xpWriteLog(MetaSummaryWS,,-1,'TBD demand for @ not scheduled',zvMaterial,xpwC:Material) !Mark the worksheet as succeeded xpwC:StartDate = EarliestStartDate xpwC:StartTime = EarliestStartTime xpwC:EndDate = xpwC:StartDate xpwC:EndTime = xpwC:StartTime xpwC:UsedDate = xpwC:StartDate xpwC:UsedTime = xpwC:StartTime xpwC:CreateQuantity = xpwC:RequiredQty !NB: Ref frame is xpwC:Length/Width/Height xpwC:CreateValue = umMake(umUnitLess:Value,umInfinity) xpwC:UsedQuantity = xpwC:CreateQuantity xpwC:UsedValue = xpwC:CreateValue Err#=xpPutWS() IF Err# THEN DO ProcedureExit. DO ProcedureExit END !}}} !{{{ special case: phantom service ! ! A phantom service is a service with no acquisition method. ! These appear out of thin air as necessary. ! IF mcIsService(xpwC:Material,TRUE) AllocQty = xpwC:RequiredQty AllocCost = mcGetCost(xpwC:Material,,xpwC:RequiredQty,xpwC:Length,xpwC:Width,xpwC:Height,,EndDate,mcGetCost:NeverRefresh) BestStartDate = EarliestStartDate BestStartTime = EarliestStartTime xpSetLatest(EndDate,EndTime,BestStartDate,BestStartTime) !Note the case for the user IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Scheduled phantom service of @ for @ @ [at @]', | zvMeasure,AllocQty,zvDate,EndDate,zvClock,EndTime,| zvMoney,AllocCost ) END !Mark the worksheet as succeeded xpwC:StartDate = EndDate xpwC:StartTime = EndTime xpwC:EndDate = EndDate xpwC:EndTime = EndTime xpwC:CreateQuantity = AllocQty !NB: Ref frame is xpwC:Length/Width/Height xpwC:CreateValue = AllocCost xpwC:UsedQuantity = AllocQty xpwC:UsedValue = AllocCost xpwC:UsedDate = EndDate xpwC:UsedTime = EndTime Err#=xpPutWS() IF Err# THEN DO ProcedureExit. DO ProcedureExit END !}}} !{{{ warn about resource efficiency/capacity defaults IF umIsSmallerReal(xpSetupEfficiency:Value,1.0) xpWriteLog(MetaSummaryWS,,-1,'WARNING! setup efficiency below 100% (set to @)',| zvReal,xpSetupEfficiency:Value) ELSIF umIsBiggerReal(xpSetupEfficiency:Value,1.0) xpWriteLog(MetaSummaryWS,,-1,'WARNING! setup efficiency above 100% (set to @)',| zvReal,xpSetupEfficiency:Value) END IF umIsSmallerReal(xpCycleEfficiency:Value,1.0) xpWriteLog(MetaSummaryWS,,-1,'WARNING! cycle efficiency below 100% (set to @)',| zvReal,xpCycleEfficiency:Value) ELSIF umIsBiggerReal(xpCycleEfficiency:Value,1.0) xpWriteLog(MetaSummaryWS,,-1,'WARNING! cycle efficiency above 100% (set to @)',| zvReal,xpCycleEfficiency:Value) END IF xpIgnoreShifts:Value xpWriteLog(MetaSummaryWS,,-1,'WARNING! Shifts are being ignored (this will reduce capacity)') END !}}} !{{{ warn if doing a consumable or a volatile IF xpwC:Material AND mcIsConsumable(xpwC:Material,,TRUE) xpWriteLog(MetaSummaryWS,,-1,'WARNING: scheduled material @ is a consumable',| zvMaterial,xpwC:Material) END IF xpwC:Material AND mcIsConsumable(xpwC:Material,TRUE) xpWriteLog(MetaSummaryWS,,-1,'WARNING: scheduled material @ is a volatile',| zvMaterial,xpwC:Material) END !}}} !{{{ determine direction xpDecodeDirection(xpwC:AllocationStrategy,Direction.ASAP, | Direction.ASAP:JIT, | Direction.JIT, | Direction.JIT:SPLIT,| Direction.DEFAULT, | !this should never get set due to the ASAP default below xpEncodeASAP() ) !use ASAP if no default IF xpIsSessionOption(xpSession:JITSplitAsJIT) IF Direction.JIT:SPLIT !Change it to JIT - this is typically done for approve time schedules, !'cos if they split it causes mass user confusion Direction.JIT = TRUE Direction.JIT:SPLIT = FALSE END END IF Direction.JIT OR Direction.JIT:SPLIT Direction.JIT:LAST = TRUE !allow it here for the case where we try ASAP after a JIT failure ELSIF xpIsSessionOption(xpSession:NoJITafterASAP) Direction.JIT:LAST = FALSE !being told not to for this session ELSE Direction.JIT:LAST = TRUE !all routes can end up doing this, but only once END Direction.JIT:FAIL = FALSE !note haven't had a JIT failure yet IF ~Direction.JIT AND ScheduleMode = xpScheduleMode:BackFlush !back-flush is always and only JIT !Tell user we swapped strategies xpWriteLog(MetaSummaryWS,,-1,'Back-flush schedule cannot be ASAP. Doing @ JIT',| zvMaterial,xpwC:Material) Direction.JIT = TRUE Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE END IF Direction.JIT:SPLIT AND gxBitInMask(xpExactBatchSizeStrategy,xpwC:AllocationStrategy) !Can't do JIT/SPLIT with EXACT xpWriteLog(MetaSummaryWS,,-1,'JIT/SPLIT cannot be used with EXACT. Doing @ JIT',| zvMaterial,xpwC:Material) Direction.JIT:SPLIT = FALSE Direction.JIT = TRUE END IF Direction.JIT:SPLIT AND gxBitInMask(xpUniqStockStrategy,xpwC:AllocationStrategy) !Can't do JIT/SPLIT with NoMix 'cos could create N batches that a higher !level then cannot use 'cos its not allowed to have more than 1. xpWriteLog(MetaSummaryWS,,-1,'JIT/SPLIT cannot be used with NOMIX. Doing @ JIT',| zvMaterial,xpwC:Material) Direction.JIT:SPLIT = FALSE Direction.JIT = TRUE END IF Direction.JIT:SPLIT AND gxBitInMask(xpBatchingStrategy,xpwC:AllocationStrategy) !Don't do JIT/SPLIT with MaxStock. Pointless 'cos xpCreateSchedule will split it anyway xpWriteLog(MetaSummaryWS,,-1,'JIT/SPLIT cannot be used with MAXSTOCK. Doing @ JIT',| zvMaterial,xpwC:Material) Direction.JIT:SPLIT = FALSE Direction.JIT = TRUE END IF Direction.JIT OR Direction.JIT:SPLIT IF ScheduleMode = xpScheduleMode:BackFlush !Do not check target date when backflushing ELSIF TargetDate = gxDateASAP !Can't do JIT when date is ASAP IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(MetaSummaryWS,,2,'Cannot do JIT when date is ASAP. Doing @ ASAP',| zvMaterial,xpwC:Material). Direction.JIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE Direction.JIT:SPLIT = FALSE ELSIF ~xpOlder(EarliestStartDate,,TargetDate) !NB: Ignore time-in-day !Pointless doing JIT this fast IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(MetaSummaryWS,,2,'Cannot do JIT for @ when ''now'' is @. Doing @ ASAP',| zvDate,TargetDate,zvDate,EarliestStartDate,zvMaterial,xpwC:Material). Direction.JIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE Direction.JIT:SPLIT = FALSE END END IF Direction.ASAP:JIT THEN Direction.ASAP = TRUE. !do ASAP first IF Direction.JIT:SPLIT THEN Direction.JIT = TRUE. !do JIT first !}}} !{{{ start the monitor zvFormat(zvMeasure ,xpwC:RequiredQty,sQty,TRUE) zvFormat(zvMaterial,xpwC:Material ,sMat,TRUE) zvFormat(zvDate , TargetDate ,sDat,TRUE) IF Direction.JIT OR Direction.JIT:SPLIT IF ~gxMonitorStart('JIT schedule for ' & Clip(sQty) & ' of ' & Clip(sMat) & ' for ' & Clip(sDat)) Err# = gxUnspecifiedErr DO ProcedureExit END ELSE IF ~gxMonitorStart('ASAP schedule for ' & Clip(sQty) & ' of ' & Clip(sMat) & ' for ' & Clip(sDat)) Err# = gxUnspecifiedErr DO ProcedureExit END END MonStarted# = TRUE !}}} !{{{ focus on the owner !If we're re-scheduling a WO or PO, setup such that user resource overrides !are honoured. Ditto kit lists and suppliers. IF TRUE !25/11/14 DCN See [#46637] for reason to unilaterally not focus-->NoFocus !Caller is telling us not to do it (means doing a full re-schedule) xpFocusOnWOrder(0,0) !Needed to clear caches xpFocusOnPOrder(0,0) !.. xpFocusOnStock (0,0,0) !.... xpSetSelf(xpwC:OwnerFile,xpwC:OwnerRec,xpwC:OwnerInstance) !Needed for lock instances handling ELSIF gxBitInMask(xpIgnoreStockStrategy ,xpwC:AllocationStrategy) | OR gxBitInMask(xpUseAllCapacityStrategy,xpwC:AllocationStrategy) THEN !Don't focus on owner, user is doing a cost or test schedule xpFocusOnWOrder(0,0) !Needed to clear caches xpFocusOnPOrder(0,0) !.. xpFocusOnStock (0,0,0) !.... xpSetSelf(xpwC:OwnerFile,xpwC:OwnerRec,xpwC:OwnerInstance) ELSE !Do it (means doing a partial re-schedule, so want things to be sticky) xpFocusOnWOrder(xpwC:OwnerFile,xpwC:OwnerRec) xpFocusOnPOrder(xpwC:OwnerFile,xpwC:OwnerRec) xpFocusOnStock (xpwC:OwnerFile,xpwC:OwnerRec,xpwC:OwnerInstance) !NB: Also sets self END !}}} !{{{ determine if we're allocatable/releasable !These flags control the pre and post stock checks done by xpCreateScheduleHelper. !They are transparent to xpCreateSchedule (it just passes them on). !The xpSchFlgNoAllocate flag means: "I don't care what the stock position is, I want this made". !This inhibits the initial stock check. !The xpSchFlgReleaseStock flag means: "I don't want what I just made, make it available to others". !This inhibits the final stock check. IF ScheduleMode = xpScheduleMode:Allocate !'allocate' schedule - always allocate and keep it ELSE !normal schedule - depends on the object CASE xpwC:OwnerFile OF sop::Id !contract demand !always allocate to a SOP and keep it OF sol::Id !sales demand !always allocate to a SOL and keep it !(if its to a service, the final allocation is hidden - see later) OF msc::Id !committed works order kit shortage !always allocate to a MSC and keep it (stock allocation shortage) OF enq::Id !enquiry !always allocate to a ENQ and keep it OF mch::Id !min-stock !always allocate to a MCH and keep it OF xpDemand::Id !merged demand !What we're doing here is pre-empting shortages detected by the merging system. !If you fidddle here, make a corresponding fiddle in xpAnalyseSchedules IF xpMergeOnlyShortages:Value !Treat similar to an ad-hoc !11/02/12 DCN HACK Do normal stock check else re-order qty's muck it up-->Flags += xpSchFlgNoAllocate ! Its safe 'cos stuff we're not allowed to have will be pre-allocated and not visible Flags += xpSchFlgReleaseStock !make what we do available to others Flags += xpSchFlgIgnoreReturns !merging ignores them, so we must too ELSE !always allocate to a merged demand and keep it !(allocation includes hidden and becomes hidden - see xpAllocateStock and xpUpdateStock) !Leave as is END ELSE !OF asu::Id OROF woh::Id OROF pol::Id !ad-hoc demand !don't allocate or keep ad-hoc demand Flags += xpSchFlgNoAllocate Flags += xpSchFlgReleaseStock END END !}}} !{{{ determine if we're allowed to do an initial stock probe ProbeStock = TRUE IF ScheduleMode = xpScheduleMode:Allocate !'allocate' schedule - always probe ELSE !depends on context IF ProbeStock AND BAND(Flags,xpSchFlgJustStockCheck+xpSchFlgNoAllocate+xpSchFlgReleaseStock) !Don't want stock - so do nothing IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Skipping initial stock check (not consistent with flags:@)',| zvHex,Flags) END ProbeStock = FALSE END !17/11/11 DCN Another gotcha: !When doing MAXSTOCK+NOMIX, the stock allocated must be in multiples of the QuantiseQty else !we may end up with a residue that is not the QuantiseQty and that'll get ignored and create !an orphan. We take a pragmatic approach and do not do an initial stock probe here and defer !the issue to xpCreateSchedule later. IF ProbeStock AND gxBitInMask(xpUniqStockStrategy,xpwC:AllocationStrategy) THEN IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Skipping initial stock check (not consistent with NOMIX)') END ProbeStock = FALSE END END !}}} !{{{ get what we can from stock first IF ProbeStock !Want stock !13/09/11 DCN !What we're trying to do here is determine what the shortage is so we know !what to increase stock by in the following JIT/ASAP probes. There are several !contexts to consider: ! 1. We got it all from stock - easy case ! 2. There is none in stock at any date - another easy case ! 3. There is some in stock and its all on-time - also an easy case ! 4. There is some in stock but its too late - now it gets tricky ! 4.1 When racing WIP is allowed - OK to carry on and try to beat it ! 4.2 When not racing WIP - it gets trickier ! 4.2.1 When doing ASAP, xpCreateScheduleHelper snaps to the late stock and carries on - OK ! 4.2.2 When doing JIT, xpCreateScheduleHelper abandons JIT - now it goes pear-shaped here! ! It carries on here and tries to *increase* stock - ignoring the WIP - *wrong*. ! What it should do is also snap to the WIP by *moving* the JIT target to meet it. ! xpCreateScheduleHelper can safely do that at depth 1. Then the logic here is correct. !{{{ clone the meta summary WS DO CloneMetaSummary !}}} !{{{ set target xpwC:TargetDate = TargetDate xpwC:TargetTime = TargetTime Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !}}} !{{{ do the stock schedule IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'STOCK PROBE: @ @[of @][x @]for @ @',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,TargetDate,zvClock,TargetTime) END xpSetLogDepth(+1); LogDepth +=1 Err# = xpCreateSchedule(SummaryWS,BestState,BOR(Flags,xpSchFlgAnyStockCheck)) !NB: Late WIP implicitly ignored xpSetLogDepth(-1); LogDepth -=1 IF Err# xpWriteLog(MetaSummaryWS,,-1,'Schedule (STOCK PROBE) abandonned due to an error') DO ProcedureExit END !If we're doing JIT with NoWIP/PlanRace this stock probe may have found later stuff !and 'snapped' to it. In that case we need to move our target to meet it. IF umIsPositive(BestAllocQty) !We found some, move the target. Do it unilaterally for all cases. !NB: xpCreateScheduleHelper would've generated an alert if it delayed, ! so no need to create another one now. xpSetLatest(EndDate,EndTime,BestEndDate,BestEndTime) DO IntegrateBest !we're gonna keep this END !}}} IF umIsPositive(AllocQty) IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Got @ of @ required from stock',| zvMeasure,AllocQty,zvMeasure,RequiredQty) END !{{{ adjust and calc residue RequiredQty = umSubtract(RequiredQty,AllocQty) OrderQty = umSubtract(OrderQty ,AllocQty) !Update the qty in the summary to reflect what we actually achieved from stock. Err# = xpLoadWS(SummaryWS,01161111) IF Err# THEN DO ProcedureExit. xpwC:RequiredQty = AllocQty Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !}}} ELSE !Chuck the duff schedule DO DumpFailedAttempt PendingLogWS = 0 !preserve the log IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Got none of @ required from stock',| zvMeasure,RequiredQty) END END IF umIsPositive(RequiredQty) !{{{ there is still some left to do !If we're doing an allocate this is an alertable condition IF ScheduleMode = xpScheduleMode:Allocate xpLoadWS(MetaSummaryWS,01071211) xpWriteLog(MetaSummaryWS,,-1,'ALLOCATE stock shortage: found ^@, residue is @ of @',| zvMeasure,AllocQty,zvMeasure,RequiredQty,zvMaterial,Material) !Not allowed to do any more Direction.JIT = FALSE Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE END !}}} ELSE !We got the lot, so that's it - do nothing more Direction.JIT = FALSE Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE END END !}}} LOOP WHILE Direction.JIT OR Direction.ASAP OR Direction.ASAP:JIT OR Direction.JIT:SPLIT !{{{ JIT schedule if required IF Direction.JIT !NB: Many attempts may be done here. ! While JIT is abandonned due to late WIP, it'll keep trying ! with later and later dates until it succeeds or there is no ! more WIP to be found. ! After that, there may be a further attempt with overtime on. ! If all that fails, it'll give up and try ASAP. After that ! we may get here again with the ASAP achieved date to close ! up the kit. But in that context late WIP is ignored, so the ! JIT will not be abandonned for that reason. !12/10/23 DCN No longer does JIT probes when JIT fails. LoopsDone = 0 LoopsStartedAt = Clock() !'stuck' detector LOOP LoopsDone += 1 !{{{ clone the meta summary WS DO CloneMetaSummary !}}} !{{{ set limits in the scheduled summary xpEncodeDirection(xpwC:AllocationStrategy,FALSE,FALSE,TRUE,FALSE,FALSE) !set JIT (NB: Never JIT/SPLIT) IF UseOvertime AND UseOvertimeWhenLate !This means a non-overtime JIT failed and we're now doing the overtime attempt xpwC:AllocationStrategy = gxSetBitInMask (xpSysUseOvertimeStrategy ,xpwC:AllocationStrategy) xpwC:AllocationStrategy = gxClearBitInMask(xpSysUseOvertimeWhenLateStrategy,xpwC:AllocationStrategy) END xpwC:EndDate = 0 xpwC:EndTime = 0 xpwC:StartDate = 0 xpwC:StartTime = 0 xpwC:RaiseDate = 0 xpwC:RaiseTime = 0 xpwC:TargetDate = EndDate !set JIT (modified) start position xpwC:TargetTime = xpQuantiseTime(EndTime,xpQuantiseTime:ToEnd) !.. xpwC:RequiredQty = RequiredQty !set the (remaining) qty we want !NB: If we've done a successful JIT probe, ! this will be less than the original requirement. xpwC:OrderQty = OrderQty !ditto xpwC:PartQty = umZeroVal !clear (possible) min JIT try qty Err#=xpPutWS() IF Err# THEN DO ProcedureExit. !}}} !{{{ do the schedule IF ScheduleMode = xpScheduleMode:BackFlush Mode" = '(BF)' ELSE Mode" = '' END IF xpMaxTraceDetail:Value > 0 IF ~BAND(Flags,xpSchFlgJustStockCheck) IF UseOvertime xpWriteLog(MetaSummaryWS,,1,'JIT' & Clip(Mode") & ': @ @[of @][x @]for @ @ using overtime',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,xpwC:TargetDate,zvClock,xpwC:TargetTime) ELSE xpWriteLog(MetaSummaryWS,,1,'JIT' & Clip(Mode") & ': @ @[of @][x @]for @ @',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,xpwC:TargetDate,zvClock,xpwC:TargetTime) END ELSE xpWriteLog(MetaSummaryWS,,1,'JIT' & Clip(Mode") & ' stock only: @ @[of @][x @]for @ @',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,xpwC:TargetDate,zvClock,xpwC:TargetTime) END END xpSetLogDepth(+1); LogDepth +=1 Err# = xpCreateSchedule(SummaryWS,BestState,Flags) xpSetLogDepth(-1); LogDepth -=1 IF Err# xpWriteLog(MetaSummaryWS,,-1,'Schedule (JIT) abandonned due to an error') DO ProcedureExit END xpIsAchievable(xpwC::Id,MetaSummaryWS,,Achieved) !NB: Loads the summary WS IF xpMaxTraceDetail:Value > 0 THEN DO LogResult. !}}} !{{{ check it IF ScheduleMode = xpScheduleMode:BackFlush !back-flush !{{{ check if OK IF Achieved.QtyFail !Set the fail condition xpwC:CreateQuantity = umLike(xpwC:RequiredQty) !Length/Width/Height irrelevant xpwC:EndDate = 0 xpwC:EndTime = 0 xpwC:StartDate = 0 xpwC:StartTime = 0 Err#=xpPutWS() IF Err# THEN DO ProcedureExit. !Chuck the duff schedule DO DumpFailedAttempt xpWriteLog(MetaSummaryWS,,-1,| 'Backflush JIT schedule of @ for @@ failed',| zvMaterial,Material,zvDate,TargetDate,zvClock,TargetTime) ELSE DO IntegrateBest IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Backflush JIT schedule of @ OK for @ by @@',| zvMaterial,Material,zvMeasure,BestAllocQty,| zvDate,BestEndDate,zvClock,BestEndTime) END END !Don't do any more Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE !}}} ELSE !normal or RR !{{{ check if OK IF Achieved.QtyFail !Chuck the duff schedule DO DumpFailedAttempt !Show next OK result as an alert so user can see it in the log AlertResult = TRUE IF ~UseOvertime AND UseOvertimeWhenLate !Try again using overtime (NB: Will be left on for subsequent JIT:SPLIT and ASAP) !{{{ log it xpWriteLog(MetaSummaryWS,,-1, | 'JIT of @ for @ @ failed [at @]@; trying again using overtime',| zvMaterial,Material,zvDate,TargetDate,zvClock,TargetTime, | zvDate,BestStartDate,zvClock,BestStartTime ) !}}} UseOvertime = TRUE CYCLE END !{{{ check for abandonned JIT re-try IF FALSE !12/10/23 DCN Don't do this, it overshoots too much, do ASAP then JIT:LAST instead-->Achieved.NextJITdate !Try again with a new later date. !How much later depends on how many times we've been round the loop !The NextJITdate/time is based on how far 'away' some WIP was deep in !the schedule we've just done. But constraints on higher levels may !mean it keeps getting back to the same time-line when it gets back !down to the late WIP level. The upshot is we may be asked to iterate !by quite small time increments which may take some time to get to the !point where the lower level WIP is reachable. To mitigate this we try !to detect excessive iterations and accelerate the time increments. !This may result in an overshoot - tough. The acceleration we use is !X*(loop_count-1)*interval+interval, X=0 is no acceleration. IF gxElapsedTicks(LoopsStartedAt) > xpJITprobeTimeLimit:Value !Been doing it too long - give up !{{{ log it xpWriteLog(MetaSummaryWS,,-1,'JIT probe time limit exceeded on @@ for @@, doing ASAP',| zvMaterial,Material,zvMeasure,BestAllocQty,| zvDate,BestEndDate,zvClock,BestEndTime) !}}} ELSE !We've still got time - have another go Interval = xpDate2ElapsedTime(EndDate,EndTime,Achieved.NextJITdate,Achieved.NextJITtime) IF Interval < (xpJITprobeMinInterval:Value/100) !Interval below min - jack it up Interval = xpJITprobeMinInterval:Value/100 END IF xpJITprobeMaxInterval:Value AND Interval > (xpJITprobeMaxInterval:Value/100) !Already over the top - leave as is ELSIF LoopsDone < (1+1) OR LoopsDone < (xpJITprobeLoopThreshold:Value+1) !Not done enough to start accelerating yet - just use what we got !This threshold should reflect a typical structure depth ELSIF xpJITprobeSpeedupRate:Value > umNoise:Value !Time to accelerate Interval += Interval*(LoopsDone-xpJITprobeLoopThreshold:Value)*xpJITprobeSpeedupRate:Value IF xpJITprobeMaxInterval:Value AND Interval > (xpJITprobeMaxInterval:Value/100) !Interval above max - bring it down Interval = xpJITprobeMaxInterval:Value/100 END ELSE !Rate is 0 so do nothing END !Set the new target date xpElapsedTime2Date(EndDate,EndTime,Interval,FALSE,EndDate,EndTime) !{{{ log it IF xpMaxTraceDetail:Value xpWriteLog(MetaSummaryWS,,1, | 'JIT of@for@@abandonned to use WIP; trying again for@@', | zvMaterial,Material,zvDate,TargetDate,zvClock,TargetTime,| zvDate,EndDate,zvClock,EndTime ) END IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Attempt number @, delay interval was @ seconds',| zvInt,LoopsDone,zvReal,Interval ) END !}}} TargetDate = EndDate !for subsequent log messages TargetTime = EndTime !.. CYCLE !go have another go at JIT END END !}}} IF ~Direction.JIT:LAST !{{{ re-do ASAP !This means we've tried JIT after a late ASAP and the JIT has failed, re-do ASAP !{{{ log it xpWriteLog(MetaSummaryWS,,-1,'JIT schedule post late ASAP failed! Re-doing ASAP') !}}} Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE Direction:JIT = FALSE Direction:JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE AlertResult = FALSE !we've already done it once PendingLogWS = 0 !preserve the last JIT log !}}} ELSIF ~Direction.JIT:SPLIT !{{{ do ASAP next !{{{ log it xpWriteLog(MetaSummaryWS,,-1, | 'JIT of @ for @ @ failed [at @]@; doing ASAP from @ @', | zvMaterial,Material,zvDate,TargetDate,zvClock,TargetTime,| zvDate,BestStartDate,zvClock,BestStartTime, | zvDate,EarliestStartDate,zvClock,EarliestStartTime ) !}}} Direction:JIT = FALSE Direction:JIT:SPLIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE Direction.JIT:FAIL = TRUE !tell ASAP that a JIT attempt failed !Set to do it from 'now' StartDate = EarliestStartDate StartTime = EarliestStartTime PendingLogWS = 0 !preserve the last JIT log !}}} ELSE !We're gonna try JIT:SPLIT next !{{{ log it xpWriteLog(MetaSummaryWS,,-1, | 'JIT of @ for @ @ failed [at @]@; trying JIT/SPLIT', | zvMaterial,Material,zvDate,TargetDate,zvClock,TargetTime,| zvDate,BestStartDate,zvClock,BestStartTime ) !}}} Direction:JIT = FALSE Direction:JIT:SPLIT = TRUE Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE END ELSE DO IntegrateBest IF AlertResult xpWriteLog(MetaSummaryWS,,-1,'JIT schedule of @ OK for @ by @ @ (O/T:@)',| zvMaterial,Material,zvMeasure,BestAllocQty,| zvDate,BestEndDate,zvClock,BestEndTime,zvFlag,UseOvertime) ELSE IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'JIT schedule of @ OK for @ by @ @ (O/T:@)',| zvMaterial,Material,zvMeasure,BestAllocQty,| zvDate,BestEndDate,zvClock,BestEndTime,zvFlag,UseOvertime) END END !Don't do any more Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE END !}}} END !}}} BREAK END END !}}} !{{{ JIT:SPLIT schedule if required IF Direction.JIT:SPLIT !NB: To get here we've already tried JIT and it failed. ! That failure would've moved the end date out to ensure ! we find all relevant WIP. IF BAND(Flags,xpSchFlgJustStockCheck) IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Demoting JIT/SPLIT to ASAP - doing stock-only schedule') END !Do ASAP instead Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE ELSIF umIsSmallerByValue(umSubtract(RequiredQty,QuantiseQty),MinTryQty) !No scope for probing IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Demoting to ASAP - cannot JIT/SPLIT (min @, max @, quanta @)',| zvMeasure,MinTryQty,zvMeasure,RequiredQty,zvMeasure,QuantiseQty) END !Do ASAP instead Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE StartDate = EarliestStartDate !start from 'now' StartTime = EarliestStartTime !.. ELSE DO FindMaxInTime END END !}}} !{{{ ASAP schedule if required IF Direction.ASAP !{{{ clone the meta summary WS DO CloneMetaSummary !}}} !{{{ tweak limits and strategy xpEncodeDirection(xpwC:AllocationStrategy,TRUE,FALSE,FALSE,FALSE,FALSE) !set ASAP (NB: Never ASAP/JIT) IF UseOvertime !NB: This may have been adjusted if a JIT failed xpwC:AllocationStrategy = gxSetBitInMask (xpSysUseOvertimeStrategy ,xpwC:AllocationStrategy) xpwC:AllocationStrategy = gxClearBitInMask(xpSysUseOvertimeWhenLateStrategy,xpwC:AllocationStrategy) END !Never do this for ASAP xpwC:AllocationStrategy = gxClearBitInMask(xpIgnoreLateWIPStrategy,xpwC:AllocationStrategy) !Clear the hints set by xpMakeWorksheet xpwC:EndDate = 0 xpwC:EndTime = 0 xpwC:StartDate = 0 xpwC:StartTime = 0 xpwC:RaiseDate = 0 xpwC:RaiseTime = 0 !Set our real start position xpwC:TargetDate = StartDate !NB: This may have been adjusted if a JIT failed xpwC:TargetTime = xpQuantiseTime(StartTime,xpQuantiseTime:ToStart) !.. xpwC:RequiredQty = RequiredQty !set the (remaining) qty we want !NB: If we've done a successful JIT probe, ! this will be less than the original requirement. xpwC:OrderQty = OrderQty !ditto xpwC:PartQty = umZeroVal !clear (possible) min JIT try qty Err#=xpPutWS() IF Err# THEN DO ProcedureExit. !}}} !{{{ do the schedule IF xpMaxTraceDetail:Value > 0 IF ~BAND(Flags,xpSchFlgJustStockCheck) IF UseOvertime xpWriteLog(MetaSummaryWS,,1,'ASAP: @ @[of @][x @]for @ @ (between @ and @) using overtime',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,TargetDate,zvClock,TargetTime,zvDate,xpwC:StartLimitDate,zvDate,xpwC:EndLimitDate) ELSE xpWriteLog(MetaSummaryWS,,1,'ASAP: @ @[of @][x @]for @ @ (between @ and @)',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,TargetDate,zvClock,TargetTime,zvDate,xpwC:StartLimitDate,zvDate,xpwC:EndLimitDate) END ELSE xpWriteLog(MetaSummaryWS,,1,'ASAP stock only: @ @[of @][x @]for @ @ (between @ and @)',| zvMaterial,xpwC:Material,zvMeasure,xpwC:RequiredQty,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvDate,TargetDate,zvClock,TargetTime,zvDate,xpwC:StartLimitDate,zvDate,xpwC:EndLimitDate) END END xpSetLogDepth(+1); LogDepth +=1 Err# = xpCreateSchedule(SummaryWS,BestState,Flags) xpSetLogDepth(-1); LogDepth -=1 IF Err# xpWriteLog(MetaSummaryWS,,-1,'Schedule (ASAP) abandonned due to an error') DO ProcedureExit END xpIsAchievable(xpwC::Id,MetaSummaryWS,,Achieved) !NB: Loads the summary WS IF xpMaxTraceDetail:Value > 0 THEN DO LogResult. !}}} !{{{ check it IF Achieved.QtyFail !{{{ oops - no can do !19/08/11 DCN Hmmm... it might have failed by hitting the ASAP cut-off date with a start limit - should try again with no limit !Chuck the duff schedule DO DumpFailedAttempt !Don't do any more Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE xpWriteLog(MetaSummaryWS,,-1,'ASAP schedule of @ failed; cannot do @',| zvMaterial,Material,zvMeasure,RequiredQty) !}}} ELSE !{{{ check for date fail conditions IF Achieved.DateFail AND Direction.ASAP:JIT !See if scope for probing IF ~BAND(Flags,xpSchFlgJustStockCheck) AND xpOlder(EarliestStartDate,EarliestStartTime,StartDate,StartTime) !Yes - let it probe ELSE !No - turn it off Direction.ASAP:JIT = FALSE !{{{ log it IF xpMaxTraceDetail:Value > 0 IF BAND(Flags,xpSchFlgJustStockCheck) xpWriteLog(MetaSummaryWS,,1,'Skipping ASAP:JIT after ASAP - doing stock-only schedule') ELSE xpWriteLog(MetaSummaryWS,,1,'Skipping ASAP:JIT after ASAP - start limit is ''now''') END END !}}} END ELSIF FALSE !22/02/12 DCN JIT:LAST now ignores WIP so not an issue anymore-->Direction.JIT:LAST AND Direction.JIT:FAIL AND ~xpOlder(EndDate,EndTime,BestEndDate,BestEndTime) !The end date is now earlier than the original JIT target, it means we !raced something and won and the JIT case didn't. So there is no point !in doing the JIT again. Direction.JIT:LAST = FALSE !{{{ log it IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,-1,'Skipping JIT:LAST after ASAP - JIT cannot achieve @@',| zvDate,BestEndDate,zvClock,BestEndTime) END !}}} END IF Achieved.DateFail AND Direction.ASAP:JIT !{{{ re-do as JIT from the target !Chuck the duff schedule DO DumpFailedAttempt !Can't do ASAP so try JIT instead Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE !don't let it do this again Direction.JIT = TRUE Direction.JIT:SPLIT = FALSE xpWriteLog(MetaSummaryWS,,-1,'ASAP schedule of @ from @ LATE by @ days; trying JIT',| zvMaterial,Material,zvDate,StartDate,zvInt,Achieved.DateFail) !}}} ELSIF Direction.JIT:LAST IF BAND(BestState.ExistingStatus,xpStkFlgMaxStockSplit) !don't go JIT if MAXSTOCK caused a split (see [35089] for why) !{{{ log it IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Skipping JIT:LAST after ASAP - MAXSTOCK caused a split') END !}}} DO DoneAfterASAP ELSE !{{{ re-do as JIT from the end date !Chuck the duff schedule DO DumpFailedAttempt !Log what happened DO LogASAP AlertResult = FALSE !now done it, so don't do it again !Have another go as JIT from the latest of the end date or the target date. !The purpose of this is to close the gap from early kit creation !and also to find existing stock that the ASAP may have by-passed. !See note 73509 in [32545] for the rationale. Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = TRUE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE !make sure we don't do this again !{{{ set the JIT target !If we're here as a result of previous JIT fail, we want to !set the target date to the later of what we got now and the !original target. !If we're here 'cos the initial ASAP completed, we want to !set the target to what we got here 'cos the user is asking !for ASAP and being early is allowed then. !If we're here 'cos merging did everything as ASAP but the user did not, !we want to re-do JIT from the later of the end date and the original !target date. This will prevent things being done too early. IF Direction.JIT:FAIL !We now have a success date that may be earlier than the !original JIT due to differing WIP race contexts. We don't !want to go earlier, so use the later of what we got now !and what we want. xpSetLatest(EndDate,EndTime,BestEndDate,BestEndTime) ELSE !We succeeded ASAP, but it may be early, if its early use the target date !else use what we actually achieved. !NB: With merging on, the target date is what it discovered after pass 1 ! and this will reflect the ASAP/JIT-ness of the original demand. So to ! close it up as JIT here is always valid. The only time its not is for ! final real demand. The ownerIsJIT prepare logic above makes this discrimination. IF FALSE !05/10/23 DCN does not work, makes things late-->ownerIsJIT !Done an ASAP that may be too early, re-do as JIT from the target date if its later IF xpOlder(BestEndDate,BestEndTime,TargetDate,TargetTime) !We're early and do not want to be, so use original target date for our JIT pass EndDate = TargetDate EndTime = TargetTime ELSE !We're late or on-time, go with the achieved date EndDate = BestEndDate EndTime = BestEndTime END ELSE !User wants ASAP, or JIT failed, so allowed to be early. EndDate = BestEndDate EndTime = BestEndTime END END OMIT('ENDOMIT') !30/11/11 DCN HACK don't do this? IF Achieved.DateFail !Go to the end of the day if we're late IF xpOlder(EndDate,EndTime,EndDate,xpGetEndOfdeliveryDay()) !Not beyond end of day, so go to that EndTime = xpGetEndOfDeliveryDay() ELSE !Beyond end of day - go to end of tomorrow EndDate += 1 EndTime = xpGetEndOfDeliveryDay() END ELSIF xpOlder(EarliestStartDate,EarliestStartTime,StartDate,StartTime) !Go exact if on time and we've got some leeway EndTime = BestEndTime ELSE !Go to the end of the day if we've got no elbow room IF xpOlder(EndDate,EndTime,EndDate,xpGetEndOfdeliveryDay()) !Not beyond end of day, so go to that EndTime = xpGetEndOfDeliveryDay() ELSE !Beyond end of day - go to end of tomorrow EndDate += 1 EndTime = xpGetEndOfDeliveryDay() END END !ENDOMIT !}}} Strategy = gxSetBitInMask(xpIgnoreLateWIPStrategy,Strategy) !the date we've got now already !includes the effect of WIP racing !so we don't want to race again else !we'll get in an endless loop chasing !WIP over the horizon xpWriteLog(MetaSummaryWS,,1,'ASAP schedule of @ being re-done as JIT from @@',| zvMaterial,Material,| zvDate,EndDate,zvClock,EndTime) !}}} END ELSE !We're done DO DoneAfterASAP END !}}} END !}}} END !}}} !{{{ ASAP:JIT schedule if required IF Direction.ASAP:JIT !NB: To get here we've already tried a plain ASAP and it failed. !27/10/11 DCN Can't get here anymore-->DO FindLatestStart END !}}} END !{{{ check all floaters got fixed !By the time we get here all floaters created must have been fixed. !Check they have. xpFlushFloaters() !}}} !{{{ adjust issue dates to meet target !Kit is initially allocated as soon as it is available. This may be !earlier than the actual demand date. So move our child summaries !to the demand date. xpSetLogDepth(+1); LogDepth +=1 xpAdjustKitIssueDates(MetaSummaryWS,EndDate,EndTime,| xpGetHandleMaterial(MetaSummaryWS),FALSE,TRUE) xpSetLogDepth(-1); LogDepth -=1 !}}} !{{{ calculate costs and release/adjust service/demand stock allocation !{{{ release services, accumulate costs !The schedule may have spawned some peers (xpCreateSchedule - parallel batching, !and here by JIT probing). So accumulate the created qty and cost over all our !child summaries. !Also: !Release the stock allocation to services and contracts (not the stock !record) so that the quantity becomes available for subsequent use in !this session. Non-volatile sold services are retained, so the stock is !not consumed. !NB: Consumable allocation *is* retained. They're not available to others. IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'Calculate costs') xpSetLogDepth(+1); LogDepth +=1 END DO PrepareTotals Err# = xpStartWSChildScan(MetaSummaryWS,MetaKey#) IF Err# DO ProcedureExit END LOOP Err# = xpNextWSChildScan(MetaSummaryWS,MetaKey#) IF Err# DO ProcedureExit END IF MetaKey# = 0 THEN BREAK. IF xpwC:RecType <> xpSummaryRecord THEN CYCLE. SummaryWS = xpwC:Handle !snaffle the handle we're about to process DO GetTotals !add TotalQty and TotalCost for this summary !{{{ scan this summary for services Err# = xpStartWSChildScan(SummaryWS,Key#) IF Err# DO ProcedureExit END LOOP Err# = xpNextWSChildScan(SummaryWS,Key#) IF Err# DO ProcedureExit END IF Key# = 0 THEN BREAK. IF xpwC:RecType <> xpStockUseRecord THEN CYCLE. CASE xpwC:OwnerFile OF xpDemand::Id !27/10/11 DCN Done on stock creation now !IF xpwC:OwnerRec > 0 ! !Hide demand stock (except from other demands) so its available for use by others ! xpwC:Hidden = TRUE ! xpPutWS() ! xpHideAlloc() !END OF sol::Id IF mcIsServiceSell(xpwC:Material) AND ~mcIsServiceSell(xpwC:Material,TRUE) !Hide sold services (so can be used, e.g. when sold service is a retained tool) !NB: This is hiding the allocation, i.e. the instance behaves as if the allocation was not made. ! This differs from removing the allocation in that the allocation linkage is still present. ! This is required to create the demand move to the sales line. xpwC:Hidden = TRUE xpPutWS() xpHideAlloc() END OF sop::Id !05/06/01 DCN Don't hide this otherwise demands steal from each other. ! Keeping the allocation is OK 'cos normally contracts are ! done after sales lines. !05/04/04 DCN When a contract is scheduled, the *whole* contracted quantity is ! now scheduled *before* sales lines, so its permissible and desirable ! to allow sales orders to steal from them, but they must not steal ! from each other. This is a stock allocation issue. So the allocation ! here *is* hidden so the contract stock becomes available to sales ! lines (only). !08/04/04 DCN DO NOT HIDE CONTRACT STOCK. The stock must be held and not be ! allowed to be used. This is why: ! The contract demand added to the schedule list is that for the ! contracted quantity that has not yet been drawn. Stock becomes ! 'drawn' as soon as a sales order is approved for it. This means ! stock for that amount is already being scheduled elsewhere, so ! we don't want the contract residue here to contribute to that. !IF xpwC:GrandParentHandle = xpwC:TopHandle ! !Release the stock allocation (not the stock record) so that the ! !quantity becomes available for subsequent use in this session. ! !Contracts are retained, so the stock is not consumed. ! xpwC:Hidden = TRUE ! xpPutWS() ! xpHideAlloc() !END END END !}}} END IF umIsBigger(AllocCost,TotalCost) !Alloc is more cost, so set that (this means a min target cost has been applied somewhere) TotalCost = AllocCost END IF xpDebugDetail:Value > 4 THEN xpSetLogDepth(-1); LogDepth -=1. !}}} Err# = xpLoadWS(MetaSummaryWS,3109732) IF Err# THEN DO ProcedureExit. !12/07/19 DCN TotalQty is now in the MCH frame. ! Re-cast to the demand frame for the result. xpwC:CreateQuantity = mcNormaliseTo(xpwC:Material,TotalQty,xpwC:RefLen,xpwC:RefWid,xpwC:RefHeight,| !from this TotalQty,xpwC:Length,xpwC:Width,xpwC:Height) !to this xpwC:CreateValue = TotalCost xpwC:UsedQuantity = AllocQty !NB: AllocQty is in the demand frame xpwC:UsedValue = AllocCost Err# = xpPutWS() IF Err# THEN DO ProcedureExit. IF umIsInfinity(TotalCost) xpWriteLog(MetaSummaryWS,,-1,'Scheduled cost for ^@[x @][x @] of @ is undefined',| zvMeasure,xpwC:CreateQuantity,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvMaterial,xpwC:Material) ELSE IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Scheduled cost for ^@[x @][x @] of @ is ^@',| zvMeasure,xpwC:CreateQuantity,zvMeasure,xpwC:Length,zvMeasure,xpwC:Width,| zvMaterial,xpwC:Material,zvMoney,TotalCost) IF xpMaxTraceDetail:Value > 3 xpWriteLog(MetaSummaryWS,,4,'Scheduled quantity was ^@[x @][x @]',| zvMeasure,TotalQty,zvMeasure,xpwc:RefLen,zvMeasure,xpwC:RefWid) END END END !}}} !{{{ detect and alert undefined costs ! ! We create alerts for all elements found with undefined costs. ! Free(NoCostQ) xpSetLogDepth(+1); LogDepth +=1 xpwC:Handle = MetaSummaryWS DO CheckCostsDefined xpSetLogDepth(-1); LogDepth -=1 Free(NoCostQ) !}}} DO ProcedureExit !{{{ ProcedureEntry ProcedureEntry ROUTINE OrigTraceDetail = xpMaxTraceDetail:Value OrigDebugDetail = xpDebugDetail:Value IF xpTraceMaterial:Value AND xpTraceMaterial:Value = xpwC:Material !Turn on logging (it'll get restored on exit) xpMaxTraceDetail:Value = 10 xpDebugDetail:Value = 10 END xpSetLogContext(MetaSummaryWS,MetaSummaryWS,xpLogOpt:Schedule) !IF xpMaxTraceDetail:Value ! xpWriteLog(MetaSummaryWS,,1,'Schedule: @',zvWS,MetaSummaryWS) ! xpSetLogDepth(+1); LogDepth +=1 !END !{{{ check cache depth IF xpCacheThread <> Thread() Err# = gxDisplayError(,'The XP cache has not been enabled on this thread - cannot schedule',gxInternalErr) DO ProcedureExit END !}}} !{{{ open all the method files Err# = zfOpen(mcb::Id); IF Err# THEN DO ProcedureExit. !Can't open the file mcb::Open = TRUE Err# = zfOpen(mcs::Id); IF Err# THEN DO ProcedureExit. !Can't open the file mcs::Open = TRUE Err# = zfOpen(rsu::Id); IF Err# THEN DO ProcedureExit. !Can't open the file rsu::Open = TRUE !}}} !{{{ setup common strategy masks xpWorkBackMask = gxBitToMask(xpWorkBackStrategy) xpSysNoReservedMask = gxBitToMask(xpSysNoReservedStrategy) xpUseAllCapacityMask = gxBitToMask(xpUseAllCapacityStrategy) !}}} AllocQty = umZeroVal !init the results accumulators AllocCost = umZeroVal !.. !}}} !{{{ ProcedureExit ProcedureExit ROUTINE IF Err# xpDestroySummaryWS(MetaSummaryWS,,TRUE) !only do children so meta-summary survives !this provides access to the trace for diagnosis END IF rsu::Open THEN zfClose(rsu::Id). IF mcb::Open THEN zfClose(mcb::Id). IF mcs::Open THEN zfClose(mcs::Id). gxSetErrorCode(0) !Clear the sticky error code IF MonStarted# THEN gxMonitorStop(). !Close the monitor LOOP LogDepth TIMES xpSetLogDepth(-1) END xpSetLogContext() xpMaxTraceDetail:Value = OrigTraceDetail xpDebugDetail:Value = OrigDebugDetail RETURN Err# !Return the result status !}}} !{{{ CloneMetaSummary ROUTINE CloneMetaSummary ROUTINE DATA Forced EQUATE(TRUE) CODE SummaryWS = xpCreateType(MetaSummaryWS,xpSummaryRecord) IF SummaryWS = 0 THEN Err# = gxUnspecifiedErr; DO ProcedureExit. xpwC:AllocationStrategy = Strategy !set (modified) strategy !Allocate the next tentative WO# (so it doesn't get muddled with other clones) xpAllocateWONum() !10/12/23 DCN HACK-->,4,'CloneMetaSummary') !}}} !{{{ CheckCostsDefined ROUTINE !Scan the summary in xpwC:Handle for undefined costs. CheckCostsDefined ROUTINE DATA ThisKey LONG ThisWS LONG CODE ThisWS = xpwC:Handle Err# = xpStartWSChildScan(ThisWS,ThisKey) IF Err# THEN DO ProcedureExit. LOOP Err# = xpNextWSChildScan(ThisWS,ThisKey) IF Err# THEN DO ProcedureExit. IF ThisKey = 0 THEN BREAK. CASE xpwC:RecType OF xpSummaryRecord DO CheckCostsDefined !recurse on this summary OF xpResourceRecord !{{{ check resource costs defined IF rsGetUnitCost(xpwC:Resource,SetupUnitCost$,CycleUnitCost$,,,TRUE) = 0 IF SetupUnitCost$ >= umInfinity OR CycleUnitCost$ >= umInfinity !Costs are undefined NoCostQ:FileNo = rst::Id NoCostQ:RecNo = xpwC:Resource Get(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) IF gxErrCode() !Not squeaked about this one yet xpWriteLog(xpwC:TopHandle,xpwC:ParentHandle,-1,'Resource @ used by @ has undefined cost',| zvResTyp,xpwC:Resource,zvMaterial,xpwC:Material) NoCostQ:FileNo = rst::Id NoCostQ:RecNo = xpwC:Resource Add(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) !Ignore errors END END END !}}} OF xpBuyRecord OROF xpSubContractRecord !{{{ check supplier costs defined IF umIsInfinity(xpwC:CreateValue) !Costs are undefined NoCostQ:FileNo = mcs::Id NoCostQ:RecNo = xpwC:mcsRef Get(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) IF gxErrCode() !Not squeaked about this one yet xpWriteLog(xpwC:TopHandle,xpwC:ParentHandle,-1,'Supplier for @ (^@) has undefined cost',| zvMaterial,xpwC:Material,zvSupPart,xpwC:mcsRef) NoCostQ:FileNo = mcs::Id NoCostQ:RecNo = xpwC:mcsRef Add(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) !Ignore errors END END !}}} OF xpStockUseRecord !{{{ check stock costs defined IF xpwC:InstanceFile = msi::Id !Got physical stock IF umIsInfinity(xpwC:UsedValue) NoCostQ:FileNo = xpwC:InstanceFile NoCostQ:RecNo = xpwC:InstanceRec Get(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) IF gxErrCode() !Not squeaked about this one yet xpWriteLog(xpwC:TopHandle,xpwC:ParentHandle,-1,'Stock batch @ used by @ has undefined cost',| zvMatBatch,xpwC:InstanceRec,zvMaterial,xpwC:Material) NoCostQ:FileNo = xpwC:InstanceFile NoCostQ:RecNo = xpwC:InstanceRec Add(NoCostQ,+NoCostQ:FileNo,+NoCostQ:RecNo) !Ignore errors END END END !}}} END END !}}} !{{{ DoneAfterASAP ROUTINE DoneAfterASAP ROUTINE DO IntegrateBest !Don't do any more Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE DO LogASAP !}}} !{{{ LogASAP ROUTINE !Log the result of an ASAP schedule LogASAP ROUTINE DATA MaxStockSplit BYTE CODE IF BAND(BestState,xpStkFlgMaxStockSplit) THEN MaxStockSplit = TRUE ELSE MaxStockSplit = FALSE. IF TargetDate = gxDateASAP Achieved.DateFail = gxElapsedDays(BestEndDate,TargetDate) IF OwnerFile <> mch::Id | !07/08/02 DCN always show non-stock ASAP targets as an alert AND OwnerFile <> sop::Id THEN !13/10/02 DCN Ditto for contracts !Show as an alert if non-stock ASAP (this is so it shows when doing what-if on an ASAP date) xpWriteLog(MetaSummaryWS,,-1,'ASAP schedule of@from@OK; can do@by@@(@days)(O/T:@)(MSS:@)',| zvMaterial,Material,zvDate,StartDate,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvInt,Achieved.DateFail,zvFlag,UseOvertime,zvFlag,MaxStockSplit) ELSE IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'ASAP schedule of@from@OK; can do@by@@(@days)(O/T:@)(MSS:@)',| zvMaterial,Material,zvDate,StartDate,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvInt,Achieved.DateFail,zvFlag,UseOvertime,zvFlag,MaxStockSplit) END END ELSIF Achieved.DateFail xpWriteLog(MetaSummaryWS,,-1,'ASAP sched of@from@LATE by@days; can do@by@@(O/T:@)(MSS:@)',| zvMaterial,Material,zvDate,StartDate,zvInt,Achieved.DateFail,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvFlag,UseOvertime,zvFlag,MaxStockSplit) ELSIF AlertResult !Show as an alert if JIT failed xpWriteLog(MetaSummaryWS,,-1,'ASAP schedule of@from@OK; can do@by@@(O/T:@)(MSS:@)',| zvMaterial,Material,zvDate,StartDate,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvFlag,UseOvertime,zvFlag,MaxStockSplit) ELSE IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'ASAP schedule of@from@OK; can do@by@@(O/T:@)(MSS:@)',| zvMaterial,Material,zvDate,StartDate,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvFlag,UseOvertime,zvFlag,MaxStockSplit) END END !}}} OMIT('ENDOMIT') !27/10/11 DCN No longer reachable !{{{ FindLatestStart ROUTINE !We get here if ASAP for the required date failed and ASAP:JIT is asserted !and we're not doing a stock-only schedule and we've got a start limit. !When the ASAP:JIT option is asserted, we probe for the latest start date !we *can* do in the available time (which may be none). It follows a binary-chop !algorithm in a very similar manner to the MAXSTOCK algorithm. !The limits of the probe are 'now' to the start date that failed. The failed !date is in StartDate. FindLatestStart ROUTINE DATA LimitDate LONG Lower LONG Upper LONG Next LONG NextOK BYTE CODE LimitDate = StartDate !note what we tried and failed !{{{ clone the meta summary WS DO CloneMetaSummary Err#=xpPutWS() IF Err# THEN DO ProcedureExit. !}}} IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Looking for latest start date between @ and @ to achieve @',| zvDate,EarliestStartDate,zvDate,LimitDate,zvDate,TargetDate) END xpSetLogDepth(+1); LogDepth +=1 !{{{ try the earliest IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'trying earliest of @',zvDate,EarliestStartDate) END TryDate = EarliestStartDate TryTime = EarliestStartTime DO ThisDateSchedule !try the earliest IF Achieved.DateFail OR Achieved.QtyFail !Can't do earliest, see if can with overtime on IF ~UseOvertime AND UseOvertimeWhenLate !Try again using overtime (NB: Will be left on for subsequent probes) UseOvertime = TRUE DO DumpSchedule DO ThisDateSchedule END END IF Achieved.DateFail OR Achieved.QtyFail !{{{ can't do the earliest, give up !We've just tried the earliest possible start date and failed. !What we do next depends on the initial strategy. If that had the !'on-fail' bit set, it means the user wants to do whatever it takes !to get a schedule done on time. So what we do next is JIT/SPLIT. !Otherwise, its plain ASAP from now. DO DumpFailedAttempt !dump the failed attempt xpSetLogDepth(-1); LogDepth -=1 IF Aggressive !Do JIT:SPLIT next IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Cannot do earliest start (@) in time for ASAP, trying JIT/SPLIT',| zvDate,EarliestStartDate) END !Set to try JIT:SPLIT next. Aggressive = FALSE !don't do this again Direction.JIT:SPLIT = TRUE !this'll now do plain ASAP on the residue Direction.JIT = FALSE Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE ELSE !Do plain ASAP from 'now' next IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Cannot do earliest start (@) in time for ASAP',| zvDate,EarliestStartDate) END !Set to do ASAP from 'now' next. Direction.JIT:SPLIT = FALSE Direction.JIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE StartDate = EarliestStartDate StartTime = EarliestStartTime END EXIT !}}} END !Set what we just did as the best so far DO SetAsBest !}}} !{{{ earliest OK, see what latest we can do Lower = TryDate+1 !we know we can do the earliest, so go +1 Upper = LimitDate-1 !we know we can't do the latest, so go -1 Next = 0 NextOK = FALSE IF Upper <= Lower !Earliest is the only choice IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Earliest OK, no other choice (using @)',| zvDate,TryDate) END ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Earliest OK, probing from @ to @ for latest',| zvDate,Lower,zvDate,Upper) END LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. gxNextChop(Lower,Upper,Next,NextOK) IF Next = 0 THEN BREAK. TryDate = Next TryTime = xpGetStartOfDeliveryDay() IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'trying @',zvDate,TryDate) END DO DumpSchedule !dump the earlier attempt DO ThisDateSchedule !..and try this IF Achieved.DateFail OR Achieved.QtyFail IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'@ failed, best so far is @',| zvDate,TryDate,zvDate,BestTryDate) END NextOK = FALSE ELSE NextOK = TRUE IF TryDate > BestTryDate DO SetAsBest END IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'@ was OK (spanning @ to @), best so far is @',| zvDate,TryDate,zvDate,ThisStartDate,zvDate,ThisEndDate,| zvDate,BestTryDate) END END END END !}}} IF BestTryDate <> TryDate !{{{ re-do the best IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Re-doing best of @',zvDate,BestTryDate) END DO DumpSchedule !chuck the last one we did TryDate = BestTryDate TryTime = bestTryTime DO ThisDateSchedule !..and re-do the best DO SetAsBest !....should be the same, but who knows... IF Achieved.DateFail OR Achieved.QtyFail !Eh? DO DumpFailedAttempt !chuck it xpSetLogDepth(-1); LogDepth -=1 xpWriteLog(MetaSummaryWS,,-1,'Latest ASAP start of @, failed! Will do all ASAP from @',| zvDate,BestTryDate,zvDate,EarliestStartDate) Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.ASAP:JIT = FALSE !don't try this again Direction.ASAP = TRUE !..do this instead StartDate = EarliestStartDate !....from here StartTime = EarliestStartTime !...... Aggressive = FALSE !stop ASAP failure trying JIT/SPLIT EXIT END !}}} END DO IntegrateBest !{{{ log it xpWriteLog(MetaSummaryWS,,-1,'ASAP schedule of @ from @ OK; can do @ by @ @ (O/T:@)',| zvMaterial,xpwC:Material,zvDate,BestTryDate,| zvMeasure,BestAllocQty,zvDate,BestEndDate,zvClock,BestEndTime,| zvFlag,UseOvertime) !}}} !We're done Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE xpSetLogDepth(-1); LogDepth -=1 !{{{ ThisDateSchedule !NB: The SummaryWS is loaded on exit from here. ThisDateSchedule ROUTINE DATA TheDate STRING(8) CODE zvFormat(zvDate,TryDate,TheDate) Err# = gxMonitorStep('Trying ASAP/JIT for ' & Clip(TheDate)) IF Err# THEN DO ProcedureExit. Err# = xpLoadWS(SummaryWS,01050711) IF Err# THEN DO ProcedureExit. xpEncodeDirection(xpwC:AllocationStrategy,FALSE,TRUE,FALSE,FALSE,FALSE) !set ASAP/JIT IF UseOvertime !NB: This may have been adjusted if we had a date failure xpwC:AllocationStrategy = gxSetBitInMask (xpSysUseOvertimeStrategy ,xpwC:AllocationStrategy) xpwC:AllocationStrategy = gxClearBitInMask(xpSysUseOvertimeWhenLateStrategy,xpwC:AllocationStrategy) END xpwC:TargetDate = TryDate !Set the start position xpwC:TargetTime = xpQuantiseTime(TryTime,xpQuantiseTime:ToStart) !.. xpwC:EndLimitDate = TargetDate + 1 !make it stop if we overshoot xpwC:StartDate = 0 !Reset so we can see what happens xpwC:StartTime = 0 !.. xpwC:EndDate = 0 !.... xpwC:EndTime = 0 !...... xpwC:RaiseDate = 0 !........ xpwC:RaiseTime = 0 !.......... xpwC:RequiredQty = RequiredQty !set the (remaining) qty we want !NB: If we've done a successful JIT probe, ! this will be less than the original requirement. xpwC:OrderQty = OrderQty !ditto xpwC:PartQty = umZeroVal !clear (possible) min JIT try qty Err# = xpPutWS() IF Err# THEN DO ProcedureExit. xpSetLogDepth(+1); LogDepth +=1 Err# = xpCreateSchedule(SummaryWS,ThisState,BOR(Flags,xpSchFlgNoAllocate)) xpSetLogDepth(-1); LogDepth -=1 IF Err# THEN DO ProcedureExit. xpIsAchievable(xpwC::Id,SummaryWS,,Achieved) !NB: also loads the worksheet !}}} !}}} !ENDOMIT !{{{ FindMaxInTime ROUTINE !We get here if JIT for the full quantity failed and JIT:SPLIT is asserted !and we're not doing a stock-only schedule and we're doing more than '1'. !When the JIT:SPLIT option is asserted, we probe for the maximum we *can* !do in the available time (which may be none). It follows a binary-chop !algorithm in a very similar manner to the MAXSTOCK algorithm. FindMaxInTime ROUTINE !{{{ data DATA Lower LONG Upper LONG Next LONG NextOK BYTE CODE !}}} MaxTryQty = umSubtract(RequiredQty,QuantiseQty) !set upper limit to the lot for now LOOP !{{{ clone the meta summary WS DO CloneMetaSummary xpwC:Sibling = MetaSummaryWS !note what we split from Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !}}} IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Looking for max can do as JIT between @ and @',| zvMeasure,MinTryQty,zvMeasure,MaxTryQty) END xpSetLogDepth(+1); LogDepth +=1 !{{{ try the min qty IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'trying min qty of @',zvMeasure,MinTryQty) END TryQty = MinTryQty DO ThisSplitSchedule !try the min IF Achieved.DateFail OR Achieved.QtyFail !{{{ can't do the min, give up DO DumpFailedAttempt !dump the failed attempt xpSetLogDepth(-1); LogDepth -=1 IF xpMaxTraceDetail:Value > 0 xpWriteLog(MetaSummaryWS,,1,'Cannot do minimum (@) in time for JIT, doing all ASAP',| zvMeasure,TryQty) END !Set to do ASAP from 'now' next. Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE StartDate = EarliestStartDate StartTime = EarliestStartTime EXIT !}}} END !Find out what the actual qty was we did, this may be more than the TryQty !if min batch sizes, etc, kicked in. This will minimise the range of further probes. DO PrepareTotals DO GetTotals TryQty = TotalQty DO SetAsBest !}}} !{{{ min OK, see what max we can do Lower = umValueOf(umCeiling(umDivide(TryQty,QuantiseQty))) Upper = umValueOf(umCeiling(umDivide(MaxTryQty,QuantiseQty))) Lower += 1 !we know we can do the min, so go +1 Next = 0 NextOK = FALSE IF xpMaxTraceDetail:Value > 1 TryQty = umScale(QuantiseQty,Lower) xpWriteLog(MetaSummaryWS,,2,'Min OK, probing from @ (@~) to @ (@~) for max',| zvMeasure,TryQty,zvInt,Lower,zvMeasure,MaxTryQty,zvInt,Upper) END LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. gxNextChop(Lower,Upper,Next,NextOK) IF Next = 0 THEN BREAK. TryQty = umScale(QuantiseQty,Next) IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'trying @',zvMeasure,TryQty) END DO DumpSchedule !dump the earlier attempt DO ThisSplitSchedule !..and try this IF Achieved.DateFail OR Achieved.QtyFail IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'@ failed, best so far is @',| zvMeasure,TryQty,zvMeasure,BestTotalQty) END NextOK = FALSE ELSE NextOK = TRUE DO PrepareTotals DO GetTotals IF umIsBigger(TotalQty,BestTotalQty) DO SetAsBest END IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'@ was OK, best so far is @',| zvMeasure,TryQty,zvMeasure,BestTotalQty) END END END !}}} DO PrepareTotals DO GetTotals IF ~umIsSame(BestTotalQty,TotalQty) !{{{ re-do the best IF xpMaxTraceDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Re-doing best of @',zvMeasure,BestAllocQty) END DO DumpSchedule !chuck the last one we did TryQty = BestTotalQty DO ThisSplitSchedule !..and re-do the best DO PrepareTotals DO GetTotals DO SetAsBest !....should be the same, but who knows... IF Achieved.DateFail OR Achieved.QtyFail !Eh? DO DumpFailedAttempt !chuck it xpSetLogDepth(-1); LogDepth -=1 xpWriteLog(MetaSummaryWS,,-1,'Max JIT qty of @, failed! Will do all ASAP from @ @',| zvMeasure,TryQty,zvDate,EarliestStartDate,zvClock,EarliestStartTime) Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE !don't try this again Direction.ASAP = TRUE !..do this instead Direction.ASAP:JIT = FALSE StartDate = EarliestStartDate StartTime = EarliestStartTime EXIT END !}}} END DO IntegrateBest xpSetLogDepth(-1); LogDepth -=1 !{{{ work out the residue RequiredQty = umSubtract(RequiredQty,BestTotalQty) !calc residue still to be done OrderQty = umSubtract(OrderQty ,BestTotalQty) !.. IF ~umIsPositive(RequiredQty) THEN RequiredQty = umZeroVal. IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. xpWriteLog(MetaSummaryWS,,-1,'JIT/SPLIT schedule of @ OK for ^@ by ^@ @ (residue is ^@)',| zvMaterial,Material,zvMeasure,BestTotalQty, | zvDate,BestEndDate,zvClock,BestEndTime, | zvMeasure,RequiredQty ) IF umIsPositive(RequiredQty) !There is a residue - go round the loop again on the residue !Leave direction as-is so it tries again with whats left ELSE !We've done the lot - this can happen if overtime got turned on !So that's it Direction.ASAP = FALSE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE Direction.JIT:LAST = FALSE EXIT END !}}} IF umIsSmaller(RequiredQty,BestTotalQty) | !gone below the best we can do OR umIsSmallerByValue(umSubtract(RequiredQty,QuantiseQty),MinTryQty) THEN !residue will be too small !{{{ set for ASAP from 'now' on the residue Direction.ASAP = TRUE Direction.ASAP:JIT = FALSE Direction.JIT = FALSE Direction.JIT:SPLIT = FALSE !don't do this again Direction.JIT:FAIL = TRUE !tell ASAP that a JIT failed StartDate = EarliestStartDate StartTime = EarliestStartTime !}}} EXIT END !To get here we know we can't do more than BestTotalQty and RequiredQty is still at least that. !So no point going round the outer loop again. Just keep going round here. MaxTryQty = BestTotalQty !no point probing for more than this END !{{{ ThisSplitSchedule !NB: The SummaryWS is loaded on exit from here. ThisSplitSchedule ROUTINE DATA TheQty STRING(24) CODE zvFormat(zvMeasure,TryQty,TheQty) Err# = gxMonitorStep('Trying JIT/SPLIT for ' & Clip(TheQty)) IF Err# THEN DO ProcedureExit. Err# = xpLoadWS(SummaryWS,01050711) IF Err# THEN DO ProcedureExit. xpEncodeDirection(xpwC:AllocationStrategy,FALSE,FALSE,FALSE,TRUE,FALSE) !set JIT/SPLIT IF UseOvertime !NB: This may have been adjusted if JIT failed xpwC:AllocationStrategy = gxSetBitInMask (xpSysUseOvertimeStrategy ,xpwC:AllocationStrategy) xpwC:AllocationStrategy = gxClearBitInMask(xpSysUseOvertimeWhenLateStrategy,xpwC:AllocationStrategy) END xpwC:StartLimitDate = xpFirstScheduleDate(ScheduleMode) !allow right back as far as poss for JIT xpwC:StartDate = 0 !Reset so we can see what happens xpwC:StartTime = 0 !.. xpwC:EndDate = 0 !.... xpwC:EndTime = 0 !...... xpwC:RaiseDate = 0 !........ xpwC:RaiseTime = 0 !.......... xpwC:TargetDate = EndDate !Set start position for JIT xpwC:TargetTime = xpQuantiseTime(EndTime,xpQuantiseTime:ToEnd) !.. xpwC:RequiredQty = TryQty !what to try xpwC:OrderQty = umZeroVal !..irrelevant here (29/07/11 DCN is it?) xpwC:PartQty = umZeroVal !don't let this propagate down Err# = xpPutWS() IF Err# THEN DO ProcedureExit. xpSetLogDepth(+1); LogDepth +=1 Err# = xpCreateSchedule(SummaryWS,ThisState,BOR(Flags,xpSchFlgNoAllocate)) xpSetLogDepth(-1); LogDepth -=1 IF Err# THEN DO ProcedureExit. xpIsAchievable(xpwC::Id,SummaryWS,,Achieved) !NB: also loads the worksheet !}}} !}}} !{{{ DumpSchedule !This dumps the current schedule along with any of its split batch peers. DumpSchedule ROUTINE IF PendingLogWS !Dump the log from the previous failure as this one now takes precedence xpDeleteLogFor(,,PendingLogWS) PendingLogWS = 0 END Err# = xpGetPeers(SummaryWS,PeerQ) IF Err# THEN DO ProcedureExit. LOOP WHILE Records(PeerQ) Get(PeerQ,1) Delete(PeerQ) Err# = xpDestroySummaryWS(PeerQ.RecNo) !do self too IF Err# THEN DO ProcedureExit. xpDeleteLogFor(PeerQ.RecNo) END Err# = xpDestroySummaryWS(SummaryWS,,TRUE) !only do children IF Err# THEN DO ProcedureExit. PendingLogWS = SummaryWS !set to dump our log if we get another failure xpBacktrackLog(SummaryWS) !but set it as backtracked now !}}} !{{{ DumpFailedAttempt !This dumps the current schedule and any of its siblings. !After this call its as if it never happenned. DumpFailedAttempt ROUTINE DO DumpSchedule !dump the failed attempt xpDestroySummaryWS(SummaryWS) !dump self too !}}} !{{{ SetAsBest !Set the result of the last probe as the best so far. SetAsBest ROUTINE BestStartDate = ThisStartDate BestStartTime = ThisStartTime BestEndDate = ThisEndDate BestEndTime = ThisEndTime BestRaiseDate = ThisRaiseDate BestRaiseTime = ThisRaiseTime BestAllocQty = ThisAllocQty BestAllocCost = ThisAllocCost BestTotalQty = TotalQty !For JIT/SPLIT BestTryDate = TryDate !For ASAP/JIT BestTryTime = TryTime !}}} !{{{ IntegrateBest ROUTINE !Integrate what we just did into the overall results. !This is called whenever we commit to a schedule (i.e. !its not going to be dumped). IntegrateBest ROUTINE AllocCost = umAdd(AllocCost,BestAllocCost) AllocQty = umAdd(AllocQty ,BestAllocQty ) xpReAllocateStock() !attempt re-allocation of excess stock !}}} !{{{ LogResult ROUTINE !Log the result of the last schedule LogResult ROUTINE IF ~xpMaxTraceDetail:Value THEN EXIT. IF umIsBigger(RequiredQty,BestAllocQty) IF umIsZero(BestAllocQty) IF umIsZero(RequiredQty) xpWriteLog(MetaSummaryWS,,1,'Failed. No quantity required!') ELSE xpWriteLog(MetaSummaryWS,,1,'Cannot achieve any of @ required',| zvMeasure,RequiredQty) END ELSE xpWriteLog(MetaSummaryWS,,1,'Can only achieve @ of @ required on @ @',| zvMeasure,BestAllocQty,zvMeasure,RequiredQty,| zvDate,BestEndDate,zvClock,BestEndTime) END ELSE IF Achieved.DateFail xpWriteLog(MetaSummaryWS,,1,'Achieved all of @ required on @ @ (LATE by @ days)',| zvMeasure,RequiredQty,zvDate,BestEndDate,zvClock,BestEndTime,zvDays,Achieved.DateFail) ELSE xpWriteLog(MetaSummaryWS,,1,'Achieved all of @ required on @ @ (OK)',| zvMeasure,RequiredQty,zvDate,BestEndDate,zvClock,BestEndTime) END IF xpDebugDetail:Value > 1 xpWriteLog(MetaSummaryWS,,2,'Start @ at @, end @ at @, raise order @ @',| zvDate,BestStartDate,zvClock,BestStartTime,| zvDate,BestEndDate,zvClock,BestEndTime,| zvDate,BestRaiseDate,zvClock,BestRaiseTime) END END !}}} !{{{ GetTotals ROUTINE ! ! This loop is calculating the total *scheduled* quantity and cost for ! the given summary. This may be more than that allocated due to batching ! constraints, etc. It does it by scanning for stock creators under SummaryWS. ! The scan allows for split batches by scanning all summaries with the same ! sibling. The siblings may be children of the given summary or peers. Peers ! are only possible on top-summaries. ! !On entry: SummaryWS is the top handle to scan. ! On exit: TotalQty and TotalCost contain the total scheduled quantity and cost ! for that summary. !12/07/19 DCN What reference frame is the TotalQty in? GetTotals ROUTINE DATA ThisWS LIKE(xpwC:Handle) i LONG CODE ThisWS = SummaryWS IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'GetTotals for @',zvWS,ThisWS) END !Do our peers first Err# = xpGetPeers(SummaryWS,PeerQ) IF Err# DO ProcedureExit END LOOP WHILE Records(PeerQ) Get(PeerQ,1) Delete(PeerQ) SummaryWS = PeerQ.RecNo DO FindContributors END !Now do self SummaryWS = ThisWS DO FindContributors !{{{ add them up TotalQty = umZeroVal TotalCost = umZeroVal LOOP i = 1 TO Records(CostQ) Get(CostQ,i) TotalQty = umAdd(TotalQty,CostQ:UsedQuantity) TotalCost = umAdd(TotalCost,CostQ:UsedValue) END !}}} !{{{ PrepareTotals !This must be called prior to a GetTotals iteration. PrepareTotals ROUTINE Free(CostQ) TotalQty = umZeroVal TotalCost = umZeroVal IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'PrepareTotals') END !}}} !{{{ FindContributors FindContributors ROUTINE DATA childKey LONG ThisMat LIKE(xpwC:Material) ThisWS LIKE(xpwC:Handle) ThisSibling LIKE(xpwC:Sibling) CreateQuantity LIKE(xpwC:CreateQuantity) CreateValue LIKE(xpwC:CreateValue) CODE ThisWS = SummaryWS !note what we're doing !See if done this summary already (can revisit same summary via sibling links) CostQ:Handle = ThisWS Get(CostQ,+CostQ:Handle) IF ~gxErrCode() THEN EXIT. !already done this one !Note we're doing this one CostQ:Handle = ThisWS CostQ:UsedQuantity = umZeroVal !so has no effect in accumulation loop CostQ:UsedValue = umZeroVal !.. Add(CostQ,+CostQ:Handle) IF gxErrCode() Err# = xpDisplayError(,'xpSchedule: Add(CostQ...',gxErrCode()) DO ProcedureExit END !Do this one now IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'FindContributors in @',zvWS,ThisWS) DO DumpCostQ END Err# = xpLoadWS(ThisWS,01020811) IF Err# DO ProcedureExit END ThisMat = xpwC:Material ThisSibling = ThisWS !{{{ find all the contributors xpStartWSChildScan(ThisWS,childKey) LOOP xpNextWSChildScan(ThisWS,childKey) IF childKey = 0 THEN BREAK. IF FALSE !28/10/11 DCN Rubbish-->xpwC:IsTool !Ignore these - they are 1-off costs and not relevant to the job in hand !28/10/11 DCN If we've scheduled to make a tool we do want to know about it. ! If its just a TOOL used in making what we scheduled then it'll ! get ignored anyway 'cos it won't be the material we're making. ELSE CASE xpwC:RecType OF xpMadeRecord OROF xpInventedRecord OROF xpBuyRecord OROF xpSubContractRecord !{{{ got a stock creator IF xpwC:Material <> ThisMat !We're looking at a side effect. !Side-effects are not part of the total qty. IF xpwC:NonStockedItem !Not a stocked thing, so its value *does* contribute CreateValue = xpwC:CreateValue CreateQuantity = umZeroVal !but its qty does not ELSE !Its a stocked thing, so side-effect irrelevant CYCLE END ELSE !Note qty and value of this ![58952]The qty must be normalised to the MCH frame to allow for (as an e.g.) a bought 1D/2D item ! that has a different size to the MCH ref. E.g. mch is "1 Bars of 1 Mtrs" but it can only ! be bought as "1 Bars of 3 Mtrs", in this context the result is "3 Bars of 1 Mtrs" from a ! costing perspective. CreateQuantity = mcNormalise(xpwC:Material,xpwC:CreateQuantity,xpwC:Length,xpwC:Width,xpwC:Height) CreateValue = xpwC:CreateValue END CostQ:Handle = xpwC:Handle Get(CostQ,+CostQ:Handle) IF gxErrCode() !Not there already, add it now CostQ:Handle = xpwC:Handle CostQ:UsedQuantity = CreateQuantity CostQ:UsedValue = CreateValue Add(CostQ,+CostQ:Handle) IF gxErrCode() Err# = xpDisplayError(,'xpSchedule: Add(CostQ...',gxErrCode()) DO ProcedureExit END IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'Total contribution is ^@ at ^@ from @',| zvMeasure,CostQ:UsedQuantity,zvMoney,CostQ:UsedValue,zvWS,CostQ:Handle) DO DumpCostQ END ELSE !Its there already, must be via a stock use, overwrite with this creator (which may be for a diff qty) IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'Total contribution ^@ at ^@ replacing ^@ at ^@ in @',| zvMeasure,CreateQuantity,zvMoney,CreateValue,| zvMeasure,CostQ:UsedQuantity,zvMoney,CostQ:UsedValue,zvWS,xpwC:Handle) END CostQ:Handle = xpwC:Handle CostQ:UsedQuantity = CreateQuantity CostQ:UsedValue = CreateValue Put(CostQ) IF gxErrCode() Err# = xpDisplayError(,'xpSchedule: Put(CostQ)',gxErrCode()) DO ProcedureExit END IF xpDebugDetail:Value > 4 DO DumpCostQ END END !}}} OF xpStockUseRecord !{{{ got a stock use !NB: If we get here via FindMaxInTime the stock use can only be from that ! we created 'cos FindMaxInTime only gets called on shortages. IF xpwC:OwnerFile = xpDemand::Id !These aren't really here - they are just place holders for merged demands !So ignore it CostQ:Handle = 0 ELSIF xpwC:Material <> ThisMat !Not interested in side-effect allocations CostQ:Handle = 0 ELSIF xpwC:InstanceFile = msi::Id !Its real stock, always include it CostQ:Handle = xpwC:Handle !NB: This'll never match an xpwC:InstanceRec below ! because virtual and real stock cannot be in ! the same worksheet. ELSE !Virtual stock, only include if no creator there already CostQ:Handle = xpwC:InstanceRec Get(CostQ,+CostQ:Handle) IF gxErrCode() !Its not already there, add it CostQ:Handle = xpwC:InstanceRec ELSE !Aleady there, don't add it again CostQ:Handle = 0 END END IF CostQ:Handle <> 0 !Not there already, add it now !CostQ:Handle already set CostQ:UsedQuantity = xpwC:UsedQuantity CostQ:UsedValue = xpwC:BatchValue !NB: If non-stocked, this is the batch cost not the move cost Add(CostQ,+CostQ:Handle) IF gxErrCode() Err# = xpDisplayError(,'xpSchedule: Add(CostQ...',gxErrCode()) DO ProcedureExit END IF xpDebugDetail:Value > 4 xpWriteLog(MetaSummaryWS,,5,'Total contribution is ^@ at ^@ from @',| zvMeasure,CostQ:UsedQuantity,zvMoney,CostQ:UsedValue,zvWS,CostQ:Handle) DO DumpCostQ END END !}}} OF xpOutputRecord !{{{ got a side-effect !19/01/06 DCN See [10919] ! These are side-effects of other work so do not contribute to our costs. ! Any value recovered from the output is still a direct cost here. !}}} OF xpSummaryRecord IF ThisSibling = xpwC:Sibling AND ThisMat = xpwC:Material !Scan this too, its a split batch of 'self' SummaryWS = xpwC:Handle DO FindContributors END END END END !}}} SummaryWS = ThisWS !restore SummaryWS (in case we recursed) !{{{ DumpCostQ ROUTINE DumpCostQ ROUTINE DATA i LONG CODE xpSetLogDepth(+1); LogDepth +=1 LOOP i = 1 TO Records(CostQ) Get(CostQ,i) xpWriteLog(MetaSummaryWS,,5,'CostQ:@=^@ at ^@ from @',| zvInt,i,zvMeasure,CostQ:UsedQuantity,zvMoney,CostQ:UsedValue,zvWS,CostQ:Handle) END xpSetLogDepth(-1); LogDepth -=1 !}}} !}}} !}}} !}}} !{{{ xpCreateSchedule !{{{ history ! !{{{ 1997 ! 24/9/97 CRJ Created ! 3/10/97 CRJ Fix existing under error ! 10/10/97 CRJ Extend for max physical stock usage etc ! 3/11/97 CRJ Add MAXSTK heuristic - try ref qty and if fails dont try bigger quantities - good when JIT is always late ! 6/11/97 CRJ Fix MAXSTK strategy - must look for top level friom committed stuff first. !}}} !{{{ 1998 ! 12/1/98 DCN Do level 1 trace of what we actually did ! 21/5/98 CRJ Assign qualifiers when cloning initial summary WS ! 20/6/98 DCN Remove MakeDate ! 22/6/98 DCN Re-instate it! (but call it RaiseDate) ! 23/6/98 DCN Always pass-through if got no material (==MCB process step) ! 21/7/98 DCN Dump the (probe) ref qty schedule if it succeeds when doing PAR ! Remove calls to mcGetMakeUnitQuantity 'cos they're redundant, ! use xpwC:RefQty instead (which was setup when the worksheet was created). ! 8/9/98 DCN Log and fail nil quantity ! 8/10/98 DCN Log and fail -ve qty as well !}}} !{{{ 1999 ! 17/11/99 CRJ dont assign quals ! 16/12/99 DCN Propagate any end date set in the meta summary when integrating schedules ! 23/12/99 DCN Use msCurrency() as the default currency not umCurrency() ! 28/12/99 DCN Add ExpiresOn param !}}} !{{{ 2000 ! 31/07/00 DCN Add NoFinalTrace param ! 19/12/00 CRJ Check for consumables !}}} !{{{ 2001 ! 30/06/01 DCN xpStockRecord renamed xpStockUseRecord ! 02/08/01 CRJ Schedule multiple outputs from here as peers ! 22/08/01 CRJ Tweak the calculation of how much container to schedule ! 02/10/01 DCN xpPurgeTypes API ! Force IsModule on containers ! Allow scheduling of consumables ! Get the step method for the *step* of the output in the container when doing MO ! Schedule container in its own context, not as a demand ! 08/10/01 DCN Set xpwC:MakeThis when schedule a container ! 11/10/01 DCN mcIsConsumable API ! Allow for delays in making the container ! Propagate JustStockCheck when do ThisSchedule ! Do normal MO schedule first, then look for container ! 12/10/01 DCN Rename xpOutputRecord to xpMadeRecord ! 13/10/01 DCN Don't allow start/end limits to shrink when integrating schedules ! When get an error, dump all cloned summaries too ! Don't force module boundary on containers ! Create 'proper' container WS using xpCreateSummary ! 11/12/01 DCN Only check stock when look for MO after making its container ! Allow for relative target date when schedule MO container ! 31/12/01 DCN Remove weight stuff !}}} !{{{ 2002 ! 26/01/02 DCN Add dims to log trace message instead of cost ! 30/01/02 DCN Remove xpwC:OrderDate ! Remove xpwP clone buffer (irrelevant) ! xpCreateSummary API ! 31/01/02 DCN Use xpSetLatest/Earliest not gxLatest/Earliest ! Add StartTime and EndTimeParams ! 01/03/02 DCN Remove consumable warning (now only done at the top) ! 03/11/02 DCN Return error code not T/F !}}} !{{{ 2003 ! 14/02/03 DCN When doing initial 'nromal' MO schedule, do a stock schedule, not a full one. ! 15/02/03 DCN Use xpDestroySummaryWS to dump failed MO and clone schedules not xpPurgeTypes ! Don't try to make MO or batching shortfall when just checking stock ! 16/02/03 DCN Honour conditional logging ! 11/08/03 DCN Extend JustStockCheck param to a set of flags ! 27/08/03 DCN Only look at xpTraceMaterial:Value when depth is 1 (i.e. its a top level demand) ! 07/11/03 DCN Remove batching strategy when doing residue normally in *all* cases [1422] ! Re-arrange NOMIX and PAR logic to be optimistic (ie. try full qty first, then 1, then iterate) ! Make dumping of 'test' schedules auto ! 16/12/03 CRJ Don't MakeBuySubCon when batching and scheduling an IsModule boundary [1422]. !}}} !{{{ 2004 ! 07/01/04 DCN Allow for no step material when finding the MO container material ! 12/03/04 DCN Allow for mcMethodUnMakeItem [1817] ! 15/03/04 DCN Add xpSetLogContext calls ! 18/03/04 DCN Setup the method type when make a container worksheet [1817] ! Pass the context to mcIsMultipleOutput ! 22/03/04 DCN Remove MO stuff from here (now done in xpCreateScheduleHelper) [1817] ! 07/05/04 DCN Ignore NOMIX and MAXSTOCK when EXACT is asserted ! 09/07/04 DCN Allow for invented records ! 04/10/04 DCN Test the correct qty when doing a ref qty check for MAXSTOCK+~NOMIC !}}} !{{{ 2005 ! 04/03/05 CRJ Don't schedule kit replacements for this material - the user has taken responsibility for it ! 09/03/05 CRJ Remove above - too simplistic. !}}} !{{{ 2008 ! 10/03/08 DCN Use xpCloneWS not DIY !}}} !{{{ 2011 ! 16/03/11 DCN Allow for xpwC:OrderQty when doing NOMIX and MAXSTOCK ! 01/07/11 DCN Logic simplification ! 05/07/11 DCN Use xpCreateType() not xpCloneWS() to create a cloned summary ! 09/07/11 DCN Use xpGetConsiderDate() not DIY ! 14/07/11 DCN Set xpwC:UsedDate/Time in the summary to reflect the true EndDate/Time of the schedule ! 28/07/11 DCN Don't update our meta-summary ! Remove NoFinalTrace param (no such thing anymore) ! 02/08/11 DCN Clone summaries as peers not children ! 19/08/11 DCN Add RaiseTime ! 09/11/11 DCN Initialise all output params ! Clear dump pending when integrate ! 10/11/11 DCN Set xpwC:Sibling in our clones ! Restore orig spec in the WS if we fail ! 14/11/11 DCN Don't set IsSplitBatch (only allowed via xpMakeMaterial) ! 16/11/11 DCN Detect and dump clones that don't get used ! Don't integrate result on final schedule if final schedule failed ! Use xpGetGranularity ! 17/11/11 DCN Optimise probing (don't do min if not smaller than required) ! Don't pick up bits of stock when doing NOMIX alone ! 18/11/11 DCN Don't allow NoMix+MaxStock to split a demand such that either is below the MinTryQty ! 28/11/11 DCN Reverse the meaning of xpTraceMaterial - turn on when its this, else leave alone ! 07/12/11 DCN Always pass-through an 'allocate' schedule ! 22/12/11 DCN Fix floaters when we're the top !}}} !{{{ 2012 ! 06/01/12 DCN Allow for OrderQty not being in same units as RequiredQty ! 13/01/12 DCN FixFloaters for all paths at top ! 13/02/12 DCN Allocate a new WO# when clone the meta summary ! 20/02/12 DCN Use xpSchedStateType for return params not separate params ! 22/02/12 DCN Clear xpwC:AbandonJIT when we're a top ! 05/03/12 DCN Set xpStkFlgMaxStockSplit as appropriate ! 30/05/12 DCN Don't allow obsolete parts, warn if not approved !}}} ! 27/11/14 DCN Detect not being given a material ! 29/06/15 DCN Show reason a part is not approved when warn ! 01/03/17 DCN [54269]Probe for stock when doing top level NOMIX ! 23/03/17 DCN [54438]Don't consider existing stock found as a NOMIX split ! 26/07/18 DCN [56892]Moan about undefined qty as well as 0 and -ve ! 27/11/23 DCN Use xpAllocateWONum not DIY ! !}}} !{{{ description ! ! This function manages parallel schedules at each summary level. We're passed ! in a summary record to try initially. In some circumstances if we fail to ! schedule the whole qty we can spawn off new summaries *along side* the one provided. ! This assumes the existence of a parent of the provided summary record; which can ! be the meta summary (in fact that's why the meta summay exists). ! ! This function assumes that there are not any top level returns. ! ! xpCreateSchedule(LONG,*xpSchedStateType,LONG=0),LONG ! ! ! ! !ErrorCode ! ! ! !Flags - see 'xp\xp_priv.equ'@xpSchFlg<...>@ ! ! !Schedule results, see 'xp\xp_priv.equ'@xpSchedStateType@ ! !Handle - the summary WS to schedule ! ! NB: Containers can be scheduled at this level as a result of referencing an MO. ! !}}} xpCreateSchedule FUNCTION(FirstSummaryWS,ss,Flags) !{{{ data !{{{ our result state param synonyms StartDate EQUATE(ss.StartDate) StartTime EQUATE(ss.StartTime) EndDate EQUATE(ss.EndDate) EndTime EQUATE(ss.EndTime) RaiseDate EQUATE(ss.RaiseDate) RaiseTime EQUATE(ss.RaiseTime) ExpiresOn EQUATE(ss.ExpiresOn) AllocCost EQUATE(ss.AllocCost) AllocQty EQUATE(ss.AllocQty) ExistingDate EQUATE(ss.ExistingDate) ExistingTime EQUATE(ss.ExistingTime) ExistingStatus EQUATE(ss.ExistingStatus) !}}} !{{{ intermediate schedule results ThisState GROUP(xpSchedStateType) . ThisStartDate EQUATE(ThisState.StartDate) ThisStartTime EQUATE(ThisState.StartTime) ThisEndDate EQUATE(ThisState.EndDate) ThisEndTime EQUATE(ThisState.EndTime) ThisRaiseDate EQUATE(ThisState.RaiseDate) ThisRaiseTime EQUATE(ThisState.RaiseTime) ThisExpiresOn EQUATE(ThisState.ExpiresOn) ThisAllocCost EQUATE(ThisState.AllocCost) ThisAllocQty EQUATE(ThisState.AllocQty) ThisExistingDate EQUATE(ThisState.ExistingDate) ThisExistingTime EQUATE(ThisState.ExistingTime) ThisStatus EQUATE(ThisState.ExistingStatus) !}}} !{{{ action codes for schedule logs ITEMIZE Action:NoMixTop EQUATE Action:NoMixMaxStockPhysicalMax EQUATE Action:NoMixMaxStockPhysicalMin EQUATE Action:NoMixMaxStockPhysicalProbe EQUATE Action:NoMixMaxStockPhysicalBest EQUATE Action:NoMixMaxStockMax EQUATE Action:NoMixMaxStockMin EQUATE Action:NoMixMaxStockProbe EQUATE Action:NoMixMaxStockBest EQUATE Action:NoMixMaxStockResidue EQUATE Action:MaxStockTop EQUATE Action:MaxStockPhysicalMax EQUATE Action:MaxStockPhysicalMin EQUATE Action:MaxStockPhysicalProbe EQUATE Action:MaxStockPhysicalBest EQUATE Action:MaxStockResidue EQUATE END Action LONG !one of the above !}}} !Decoded flags JustStockCheck BYTE SummaryWS LONG QuantiseQty STRING(12) MinTryQty STRING(12) OrderQty STRING(12) RequiredQty STRING(12) RequiredLen LIKE(xpwC:Length) RequiredWid LIKE(xpwC:Width) RequiredHght LIKE(xpwC:Height) ThisQty STRING(12) BestQty STRING(12) Next LONG Lower LONG Upper LONG Depth LONG NextOK BYTE DumpPending BYTE EmptyClone BYTE SetSty LONG SetOrderQty STRING(12) SetQty STRING(12) SetFlags LONG TopWS LONG MethodUsed LONG Material LONG Container LONG MethodRec LONG EndLimit LONG ContextSet BYTE LogDepth LONG(0) CloneQ QUEUE(gxRecNoQType),PRE(CloneQ);END OldMaxTraceDetail LIKE(xpMaxTraceDetail:Value) OldDebugDetail LIKE(xpDebugDetail:Value) OrigStrategy LIKE(xpwC:AllocationStrategy) OrigRequiredQty LIKE(xpwC:RequiredQty) OrigOrderQty LIKE(xpwC:OrderQty) Reason STRING(32) !}}} CODE DO ProcedureEntry !{{{ get info from first summary IF xpTraceMaterial:Value AND xpTraceMaterial:Value = xpwC:Material !Turn on logging (it'll get restored on exit) xpMaxTraceDetail:Value = 10 xpDebugDetail:Value = 10 END IF ~xpwC:material xpWriteLog(TopWS,SummaryWS,-1,'Cannot schedule - no part given!') DO ProcedureExit END Depth = xpwC:Depth TopWS = xpwC:TopHandle Material = xpwC:Material OrderQty = mcNormaliseTo(xpwC:Material, | xpwC:OrderQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:Length,xpwC:Width,xpwC:Height) RequiredQty = xpwC:RequiredQty RequiredLen = xpwC:Length !for MO RequiredWid = xpwC:Width RequiredHght = xpwC:Height IF umIsZero(RequiredQty) xpWriteLog(TopWS,SummaryWS,-1,'Cannot schedule - no quantity given!') DO ProcedureExit END IF umIsNegative(RequiredQty) xpWriteLog(TopWS,SummaryWS,-1,'Cannot schedule - negative quantity given!') DO ProcedureExit END IF umIsInfinity(RequiredQty) xpWriteLog(TopWS,SummaryWS,-1,'Cannot schedule - undefined quantity given!') DO ProcedureExit END IF mcIsObsolete(xpwC:material) xpWriteLog(TopWS,SummaryWS,-1,'Cannot schedule @ - part is obsolete!',zvMaterial,xpwC:Material) DO ProcedureExit END IF ~mcIsApproved(xpwC:material,Reason) xpWriteLog(TopWS,SummaryWS,-1,'Warning: @ is not approved (' & Clip(Reason) & ')',zvMaterial,xpwC:Material) END !}}} IF Depth = 1 AND xpwC:Material AND xpwC:ScheduleMode <> xpScheduleMode:Allocate !{{{ look at constraint options !The constraints at this level may result in a demand being !broken up into more than 1 batch. !{{{ calculate quantisation qty xpGetGranularity(xpwC:MinMaxStockQty,TRUE,QuantiseQty,MinTryQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Batching granularity is @, min limit is @',| zvMeasure,QuantiseQty,zvMeasure,MinTryQty ) END !}}} IF gxBitInMask(xpExactBatchSizeStrategy,OrigStrategy) !{{{ EXACT !This is the user telling us to do precisely the quantity asked and !ignore all other constraints. IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Constraints: EXACT') END Err# = xpCreateScheduleHelper(SummaryWS,ss,Flags,MethodUsed) IF Err# THEN DO ProcedureExit. IF umIsZero(AllocQty) !We failed ELSE DO FixFloaters END !}}} ELSIF gxBitInMask(xpUniqStockStrategy,OrigStrategy) IF gxBitInMask(xpBatchingStrategy,OrigStrategy) !{{{ NOMIX + MAXSTOCK !This mode will split a demand into N batches such that each N !satifies the NOMIX criteria and the MAXSTOCK criteria. !The term 'physical' here means: ! "will be physical when it comes time to do the work", !i.e. it may not be physical yet, but it will be by the time !we come to do it. !A gotcha: ! The smallest N must not be below that needed to create at ! least one of the thing above (all the way to the top). ! On schedule pass 1 this is not an issue 'cos the demands ! being scheduled at depth 1 are the top. But for merge passes ! 2+ that is not true. The merging logic helps out by setting ! the minimum demand qty for the part. So we limit the min ! batch to that. IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Constraints: NOMIX + MAXSTOCK') END DO ProbeForNoMixStock !{{{ physical (+MustBePhysical) LOOP !This pass finds all stock of the kit for the demand and creates !WO's that consume that kit. The key to this is the MustBePhysical !option. This prohibits make/buy of the *kit*, but allows make of !the demand. LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. !{{{ try whole lot IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Looking for kit stock of @ (max)',zvMeasure,RequiredQty). SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) !this prohibits make/buy of the kit SetQty = RequiredQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:NoMixMaxStockPhysicalMax !for log message DO ThisSchedule !}}} IF umIsSmaller(ThisAllocQty,RequiredQty) !{{{ see what we can do IF umIsSmaller(MinTryQty,RequiredQty) IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Not enough kit stock for @ (max)',zvMeasure,RequiredQty). !{{{ try minimum IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Looking for kit stock of @ (min)',zvMeasure,MinTryQty) END SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetQty = MinTryQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:NoMixMaxStockPhysicalMin !for log message DO ThisSchedule !}}} IF NOT umIsSmaller(ThisAllocQty,MinTryQty) !{{{ can do at least the min qty from stock - so probe to find the limit Lower = umValueOf(umFloor (umDivide(MinTryQty ,QuantiseQty))) Upper = umValueOf(umCeiling(umDivide(RequiredQty,QuantiseQty))) Next = 0 ThisQty = umZeroVal BestQty = MinTryQty NextOK = FALSE LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. gxNextChop(Lower,Upper,Next,NextOK) IF Next = 0 THEN BREAK. ThisQty = umScale(QuantiseQty,Next) SetQty = ThisQty SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Looking for kit stock of @',zvMeasure,ThisQty). Action = Action:NoMixMaxStockPhysicalProbe !for log message DO ThisSchedule IF ~umIsSmaller(ThisAllocQty,ThisQty) NextOK = TRUE IF umIsBigger(ThisAllocQty,BestQty) BestQty = ThisQty END IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Found kit stock of @, best is @',zvMeasure,ThisQty,zvMeasure,BestQty). ELSE IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Not enough kit stock for @, best is @',zvMeasure,ThisQty,zvMeasure,BestQty). NextOK = FALSE END END !}}} ELSE !{{{ can't do any from stock xpWriteLog(TopWS,SummaryWS,-2,'Not enough kit stock for @ (min)',zvMeasure,MinTryQty) BestQty = umZeroVal !}}} END ELSE !{{{ can't do any from stock xpWriteLog(TopWS,SummaryWS,-2,'Not enough kit stock for @ (min)',zvMeasure,MinTryQty) BestQty = umZeroVal !}}} END !}}} ELSE !{{{ we've done it all IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Found kit stock of @ (all of it)',zvMeasure,RequiredQty). BestQty = RequiredQty ThisQty = BestQty !}}} END IF umIsZero(BestQty) !{{{ can't do this req by NoMix xpWriteLog(TopWS,SummaryWS,-2,'No kit stock available for @',zvMeasure,RequiredQty) BREAK !}}} ELSE !{{{ redo best if necessary IF NOT umIsSame(BestQty,ThisQty) IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'redoing best of @ because last was @',zvMeasure,BestQty,zvMeasure,ThisQty). SetOrderQty = umZeroVal !no constraint while probing stock SetQty = BestQty SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetFlags = Flags Action = Action:NoMixMaxStockPhysicalBest !for log message DO ThisSchedule END DO IntegrateSchedule !}}} !{{{ any more to do? RequiredQty = umSubtract(RequiredQty,BestQty) !reduce demand IF umIsPositive(OrderQty) THEN OrderQty = umSubtract(OrderQty,BestQty). !reduce constraint IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. !constraint met IF ~umIsPositive(RequiredQty) IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'done required qty from kit stock'). BREAK END IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'^@ more to do',zvMeasure,RequiredQty). DO CloneFirstSummary ExistingStatus = BOR(ExistingStatus,xpStkFlgMaxStockSplit) !note we had to split !}}} END END !}}} !{{{ non physical (as is) LOOP !This pass allows a mix of physical kit and made/bought kit. !Its using up physical kit where it exists but allows missing !kit items to be made/bought to match the physical kit that is !present. IF umIsPositive(RequiredQty) LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. !{{{ try whole lot IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'trying full batch of @ from stock (inc WIP)',zvMeasure,RequiredQty). SetSty = OrigStrategy SetQty = RequiredQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:NoMixMaxStockMax !for log message DO ThisSchedule !}}} IF umIsSmaller(ThisAllocQty,RequiredQty) !{{{ done some - see what we can do IF umIsSmaller(MinTryQty,RequiredQty) IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'full batch of @ from stock (inc WIP) failed',zvMeasure,RequiredQty). !{{{ try minimum IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'trying min qty of @ from stock (inc WIP)',zvMeasure,MinTryQty) END SetSty = OrigStrategy SetQty = MinTryQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:NoMixMaxStockMin !for log message DO ThisSchedule !}}} IF NOT umIsSmaller(ThisAllocQty,MinTryQty) !{{{ we got the min qty - find the limit Lower = umValueOf(umFloor (umDivide(MinTryQty ,QuantiseQty))) Upper = umValueOf(umCeiling(umDivide(RequiredQty,QuantiseQty))) Next = 0 ThisQty = umZeroVal BestQty = MinTryQty NextOK = FALSE LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. gxNextChop(Lower,Upper,Next,NextOK) IF Next = 0 THEN BREAK. ThisQty = umScale(QuantiseQty,Next) SetQty = ThisQty SetSty = OrigStrategy SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'trying @ from stock (inc WIP)',zvMeasure,ThisQty). Action = Action:NoMixMaxStockProbe !for log message DO ThisSchedule IF ~umIsSmaller(ThisAllocQty,ThisQty) NextOK = TRUE IF umIsBigger(ThisAllocQty,BestQty) BestQty = ThisQty END IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'@ from stock (inc WIP) was OK, best is @',zvMeasure,ThisQty,zvMeasure,BestQty). ELSE xpWriteLog(TopWS,SummaryWS,-2,'@ from stock (inc WIP) failed, best is @',zvMeasure,ThisQty,zvMeasure,BestQty) NextOK = FALSE END END !}}} ELSE !{{{ can't do any from stock or R/C xpWriteLog(TopWS,SummaryWS,-2,'min qty of @ from stock (inc WIP) failed',zvMeasure,MinTryQty) BestQty = umZeroVal !}}} END ELSE !{{{ can't do any from stock or R/C xpWriteLog(TopWS,SummaryWS,-2,'min qty of @ from stock (inc WIP) failed',zvMeasure,MinTryQty) BestQty = umZeroVal !}}} END !}}} ELSE !{{{ we've done it all IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'full batch of @ from stock (inc WIP) was OK',zvMeasure,RequiredQty) END BestQty = RequiredQty ThisQty = BestQty !}}} END IF umIsZero(BestQty) !{{{ can't do this req by NoMix xpWriteLog(TopWS,SummaryWS,-2,'can''t do @ using stock (inc WIP)',zvMeasure,RequiredQty) BREAK !}}} ELSE !{{{ redo best if necessary IF NOT umIsSame(BestQty,ThisQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'re-doing best of @ because last was @',zvMeasure,BestQty,zvMeasure,ThisQty) END SetQty = BestQty SetSty = OrigStrategy SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:NoMixMaxStockBest !for log message DO ThisSchedule END DO IntegrateSchedule !}}} !{{{ any more to do? RequiredQty = umSubtract(RequiredQty,BestQty) !reduce demand IF umIsPositive(OrderQty) THEN OrderQty = umSubtract(OrderQty,BestQty). !reduce constraint IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. !constraint met IF ~umIsPositive(RequiredQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'done required qty from stock (inc WIP)') END BREAK END IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'^@ more to do',zvMeasure,RequiredQty) END DO CloneFirstSummary ExistingStatus = BOR(ExistingStatus,xpStkFlgMaxStockSplit) !note we had to split !}}} END END END !}}} DO MakeNoMixResidue !}}} ELSE !{{{ NOMIX + ~MAXSTOCK !This mode does not split the demand, but ensures the entire !quantity is made from single batches of kit. IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Constraints: NOMIX') END DO ProbeForNoMixStock DO MakeNoMixResidue !}}} END ELSE IF gxBitInMask(xpBatchingStrategy,OrigStrategy) !{{{ ~NOMIX + MAXSTOCK !This mode splits the demand into 3. One being satisifed from stock, one !where the kit is coming from physical stock and another for the residue. !The term 'physical' here means "will be physical when it comes time to do !the work", i.e. it may not be physical yet, but it will be by the time !we come to do it. !When doing MAXSTOCK alone there is no minimum constraint other than !the quantise qty (unlike MAXSTOCK+NOMIX). IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Constraints: MAXSTOCK') END !{{{ get what we can of top level from stock (+JustStockCheck) single ! ! This is essential to catch existing schedules, otherwise if there is ! loads of raw kit it just uses more and more on each reschedule! ! SetSty = OrigStrategy SetQty = RequiredQty SetOrderQty = umZeroVal !no constraint while probing stock IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Looking in stock (inc WIP) for @',zvMeasure,RequiredQty) END SetFlags = BOR(Flags,xpSchFlgJustStockCheck) Action = Action:MaxStockTop !for log message DO ThisSchedule IF umIsZero(ThisAllocQty) !Got none from stock IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'none in stock (inc WIP)') END DO DumpSchedule IF JustStockCheck THEN DO ProcedureExit. !go no further if just checking stock ELSIF umIsSmaller(ThisAllocQty,RequiredQty) !Got some from stock IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'@ in stock (inc WIP)',zvMeasure,ThisAllocQty) END DO IntegrateSchedule IF JustStockCheck THEN DO ProcedureExit. !go no further if just checking stock DO CloneFirstSummary ExistingStatus = BOR(ExistingStatus,xpStkFlgMaxStockSplit) !note we had to split RequiredQty = umSubtract(RequiredQty,ThisAllocQty) !reduce demand IF umIsPositive(OrderQty) THEN OrderQty = umSubtract(OrderQty,ThisAllocQty). !reduce constraint IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. !constraint met IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'remainder of ^@',zvMeasure,RequiredQty) END ELSE !Got it all from stock DO IntegrateSchedule DO ProcedureExit END !}}} !{{{ try physical stock (+MustBePhysical) single !The MustBePhysical option here allows the demand to be made but !prohibits its kit from being made. I.e. the kit must already be !available. !{{{ try whole lot IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'trying full batch of @ from usable stock',zvMeasure,RequiredQty) END SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetQty = RequiredQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:MaxStockPhysicalMax !for log message DO ThisSchedule !}}} IF umIsSmaller(ThisAllocQty,RequiredQty) IF umIsSmaller(QuantiseQty,RequiredQty) !{{{ max failed, try minimum IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'full batch of @ from usable stock failed',zvMeasure,RequiredQty) xpWriteLog(TopWS,SummaryWS,2,'trying min qty of @ from usable stock',zvMeasure,QuantiseQty) END SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetQty = QuantiseQty SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:MaxStockPhysicalMin !for log message DO ThisSchedule !}}} IF umIsSmaller(ThisAllocQty,QuantiseQty) !{{{ can't do the min, give up xpWriteLog(TopWS,SummaryWS,-2,'min qty of @ from usable stock failed',zvMeasure,QuantiseQty) BestQty = umZeroVal !}}} ELSE !{{{ min OK, see what max we can do Lower = 1 Upper = umValueOf(umCeiling(umDivide(RequiredQty,QuantiseQty))) Next = 0 ThisQty = umZeroVal BestQty = QuantiseQty NextOK = FALSE LOOP Err# = gxMonitorStep() IF Err# THEN DO ProcedureExit. gxNextChop(Lower,Upper,Next,NextOK) IF Next = 0 THEN BREAK. ThisQty = umScale(QuantiseQty,Next) SetQty = ThisQty SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'trying @ from usable stock',zvMeasure,ThisQty) END Action = Action:MaxStockPhysicalProbe !for log message DO ThisSchedule IF ~umIsSmaller(ThisAllocQty,ThisQty) NextOK = TRUE IF umIsBigger(ThisAllocQty,BestQty) BestQty = ThisQty END IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'@ from usable stock was OK, best so far is @',zvMeasure,ThisQty,zvMeasure,BestQty) END ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'@ from usable stock failed, best so far is @',zvMeasure,ThisQty,zvMeasure,BestQty) END NextOK = FALSE END END !}}} END ELSE !{{{ can't do the min, give up xpWriteLog(TopWS,SummaryWS,-2,'min qty of @ from usable stock failed',zvMeasure,QuantiseQty) BestQty = umZeroVal !}}} END ELSE !{{{ we've done it all IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'full batch of @ from usable stock was OK',zvMeasure,RequiredQty) END BestQty = RequiredQty ThisQty = BestQty !}}} END IF umIsZero(BestQty) !{{{ can't do any from stock xpWriteLog(TopWS,SummaryWS,-2,'can''t do @ from usable stock',zvMeasure,RequiredQty) !}}} ELSE !{{{ redo best if necessary IF NOT umIsSame(BestQty,ThisQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'re-doing best of @ from usable stock (last was @)',zvMeasure,BestQty,zvMeasure,ThisQty) END SetQty = BestQty SetSty = gxSetBitInMask(xpMustBePhysicalStrategy,OrigStrategy) SetOrderQty = umZeroVal !no constraint while probing stock SetFlags = Flags Action = Action:MaxStockPhysicalBest !for log message DO ThisSchedule END DO IntegrateSchedule !}}} !{{{ any more to do? RequiredQty = umSubtract(RequiredQty,BestQty) !reduce demand IF umIsPositive(OrderQty) THEN OrderQty = umSubtract(OrderQty,BestQty). !reduce constraint IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. !constarint met IF ~umIsPositive(RequiredQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'done required qty from usable stock') END ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'^@ more to do',zvMeasure,RequiredQty) END DO CloneFirstSummary ExistingStatus = BOR(ExistingStatus,xpStkFlgMaxStockSplit) !note we had to split END !}}} END !}}} !{{{ then rest normally (-MaxStock) single !This does whatever is left as a normal WO with no MaxStock constraint. IF umIsPositive(RequiredQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'doing @ normally',zvMeasure,RequiredQty) END SetSty = gxClearBitInMask(xpBatchingStrategy,OrigStrategy) SetQty = RequiredQty SetOrderQty = OrderQty !apply the constraint to the rest SetFlags = Flags Action = Action:MaxStockResidue !for log message DO ThisSchedule DO IntegrateFinal ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'no need for final normal stage') END END !}}} !}}} ELSE !{{{ ~NOMIX + ~MAXSTOCK !No special constraints apply. Just do it. IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Constraints: None') END Err# = xpCreateScheduleHelper(SummaryWS,ss,Flags,MethodUsed) IF Err# THEN DO ProcedureExit. IF umIsZero(AllocQty) !We failed ELSE DO FixFloaters END !}}} END END !}}} ELSE !{{{ pass through !We've recursed through some sub-assembly to get here or we're doing an 'allocate' xpSetLogContext() ContextSet = FALSE Err# = xpCreateScheduleHelper(SummaryWS,ss,Flags,MethodUsed) IF Err# THEN DO ProcedureExit. !}}} END DO ProcedureExit !{{{ ProcedureEntry ProcedureEntry ROUTINE DATA SavedWS LONG CODE StartDate = 0 StartTime = 0 EndDate = 0 EndTime = 0 RaiseDate = 0 RaiseTime = 0 ExpiresOn = 0 AllocQty = umZeroVal AllocCost = umMake(msCurrency:Value) ExistingDate = 0 ExistingTime = 0 ExistingStatus = 0 Free(CloneQ) !Note the orig trace log settings OldMaxTraceDetail = xpMaxTraceDetail:Value OldDebugDetail = xpDebugDetail:Value !Decode the Flags IF BAND(xpSchFlgJustStockCheck,Flags) THEN JustStockCheck = TRUE ELSE JustStockCheck = FALSE. DumpPending = FALSE EmptyClone = FALSE SummaryWS = FirstSummaryWS Err# = xpLoadWS(SummaryWS,3109734) IF Err# THEN RETURN Err#. xpSetLogContext(xpwC:TopHandle,SummaryWS,xpLogOpt:CreateSchedule) ContextSet = TRUE !Note the stuff we may blatt so can put back if things fails OrigStrategy = xpwC:AllocationStrategy OrigRequiredQty = xpwC:RequiredQty OrigOrderQty = xpwC:OrderQty IF xpwC:ParentHandle = xpwC:TopHandle !We're the top of a schedule - clear the AbandonJIT status in our meta-summary SavedWS = xpSaveWS() Err# = xpLoadWS(xpwC:ParentHandle,01220212); IF Err# THEN DO ProcedureExit. xpwC:AbandonJIT = 0 Err# = xpPutWS() ; IF Err# THEN DO ProcedureExit. xpRestoreWS(SavedWS) END !IF xpMaxTraceDetail:Value ! xpWriteLog(,,1,'CreateSchedule: @',zvWS,FirstSummaryWS) ! xpSetLogDepth(+1); LogDepth += 1 !END !}}} !{{{ ProbeForNoMixStock !Get what we can of top level from stock (+JustStockCheck+SysNoMix-NoMix) !This pass finds stock of the demand and allocates lumps of that. !Its only legitimate to allocate multiple lumps when the demand !is a SOL, if its a WO its not allowed 'cos it'd violate the NoMix !rule. 03/02/12 DCN @@TBD@@ is this true? ProbeForNoMixStock ROUTINE SetSty = gxClearBitInMask(xpUniqStockStrategy,OrigStrategy) SetSty = gxSetBitInMask (xpSysUniqStockFilterStrategy,SetSty) SetQty = RequiredQty SetOrderQty = umZeroVal !remove constraint while looking in stock IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'Looking for demand stock of @',zvMeasure,RequiredQty). SetFlags = BOR(Flags,xpSchFlgJustStockCheck) !this prohibits make/buy of the demand LOOP Action = Action:NoMixTop !for log message DO ThisSchedule IF umIsZero(ThisAllocQty) !Got nothing from stock IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'none in stock'). DO DumpSchedule IF JustStockCheck THEN DO ProcedureExit. !go no further if just checking stock ELSIF umIsSmaller(ThisAllocQty,RequiredQty) !Got some of it from stock !{{{ check not got too much (BREAK and CYCLE in here) !We must ensure either what we've got is at least the MinTryQty or the residue !is. I.e. we must not split things such that neither part is big enough. An !allocation below the MinTryQty is likely to get orphaned because it will be !too small for our parent to fulfil the NoMix constraint. If we've got too much, !we give it back and get it again with a lesser amount such that the residue is !at least MinTryQty. SetQty = umSubtract(RequiredQty,ThisAllocQty) !NB: 'cos of guards above we know ! this is +ve and < RequiredQty IF umIsSmaller(ThisAllocQty,MinTryQty) AND umIsSmaller(SetQty,MinTryQty) !What we've got is too small and the residue is also too small. !If we can give part of what we found back to allow the residue to be big !enough, do so. SetQty = umSubtract(RequiredQty,MinTryQty) !calc what we'd need to reduce it to IF umIsSmaller(SetQty,umLike(SetQty,1)) !To achieve sufficient residue we need less than 1 from stock, !this is likely to be rounded up to 1, so we can't use stock at all IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Found @, too small to meet min of @',| zvMeasure,ThisAllocQty,zvMeasure,MinTryQty) END DO DumpSchedule IF JustStockCheck THEN DO ProcedureExit. !go no further if just checking stock BREAK END !We can achieve the min by asking for less from stock, do so IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Found @, reducing to @ to keep residue above @',| zvMeasure,ThisAllocQty,zvMeasure,SetQty,zvMeasure,MinTryQty) END DO DumpSchedule CYCLE END !Either what we've got meets the min or the residue does, both cases are OK. !}}} IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'@ in stock',zvMeasure,ThisAllocQty). DO IntegrateSchedule IF JustStockCheck THEN DO ProcedureExit. !go no further if just checking stock DO CloneFirstSummary !23/03/17 DCN Don't consider this a split. ! What we've found here is in stock so the resource issue described in #35089 does not apply. ! There are no resources involed for existing stock. ! Even for WIP, the resources involved are already committed so cannot affect schedules here. !ExistingStatus = BOR(ExistingStatus,xpStkFlgMaxStockSplit) !note we had to split ! !this suppresses the JIT:LAST pass RequiredQty = umSubtract(RequiredQty,ThisAllocQty) !reduce the demand IF umIsPositive(OrderQty) THEN OrderQty = umSubtract(OrderQty,ThisAllocQty). !reduce the constraint IF ~umIsPositive(OrderQty) THEN OrderQty = umZeroVal. !no constraint left IF xpMaxTraceDetail:Value > 1 THEN xpWriteLog(TopWS,SummaryWS,2,'remainder of ^@',zvMeasure,RequiredQty). ELSE !Got it all from stock DO IntegrateSchedule DO ProcedureExit END BREAK END !}}} !{{{ MakeNoMixResidue !This pass catches whatever is left after probing for NOMIX or NOMIX+MAXSTOCK stock. !It buys/makes whatever is left with only the NOMIX constraint, i.e. the MAXSTOCK !constraint is removed. MakeNoMixResidue ROUTINE IF umIsPositive(RequiredQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'doing @, allowing make/buy/SC no stock (inc WIP)',zvMeasure,RequiredQty) END SetSty = gxClearBitInMask(xpBatchingStrategy,OrigStrategy) SetQty = RequiredQty SetOrderQty = OrderQty !apply the constraint now SetFlags = Flags Action = Action:NoMixMaxStockResidue !for log message DO ThisSchedule DO IntegrateFinal ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'no need for final allow make/buy/SC stage') END END !}}} !{{{ DumpSchedule !This destroys the schedule but leaves the summary behind !ready for another attempt at something. DumpSchedule ROUTINE DumpPending = FALSE !note not got a schedule for dumping !NB: Do early in case recurse via ProcedureExit Err# = xpDestroySummaryWS(SummaryWS,,TRUE) !only do children IF Err# THEN DO ProcedureExit. IF SummaryWS <> FirstSummaryWS EmptyClone = TRUE !note now got an empty clone END !}}} !{{{ DumpClones !This destroys the schedule *and* its summary. DumpClones ROUTINE xpSetLogDepth(+1); LogDepth += 1 LOOP WHILE Records(CloneQ) Get(CloneQ,1) Delete(CloneQ) xpDestroySummaryWS(CloneQ:RecNo) END xpSetLogDepth(-1); LogDepth -= 1 EmptyClone = FALSE !}}} !{{{ IntegrateSchedule !This is called when we're going to keep a (partial) schedule. !It does 2 things: ! 1. It merges the dates/costs of the last schedule into the return params ! 2. It produces a log trace message of the last schedule results IntegrateSchedule ROUTINE !{{{ 1. merge dates/costs/status ExpiresOn = gxEarliest(ThisExpiresOn,ExpiresOn) xpSetEarliest(RaiseDate,RaiseTime,ThisRaiseDate,ThisRaiseTime) xpSetEarliest(StartDate,StartTime,ThisStartDate,ThisStartTime) xpSetLatest (EndDate ,EndTime ,ThisEndDate ,ThisEndTime ) AllocCost = umAdd(ThisAllocCost,AllocCost) AllocQty = umAdd(ThisAllocQty,AllocQty) xpSetLatest(ExistingDate,ExistingTime,ThisExistingDate,ThisExistingTime) ExistingStatus = BOR(ExistingStatus,ThisStatus) !}}} !{{{ 2. fix floaters DO FixFloaters !}}} !{{{ 3. log trace last result IF xpMaxTraceDetail:Value > 0 CASE MethodUsed OF xpStockUseRecord What" = 'Allocated' OF xpBuyRecord What" = 'Bought' OF xpMadeRecord What" = 'Made' OF xpInventedRecord What" = 'Invented' OF xpSubContractRecord What" = 'Sub-contracted' ELSE What" = 'Achieved' END xpWriteLog(TopWS,SummaryWS,1,Clip(What") & ' ^@ of @ required on ^@ @',| zvMeasure,ThisAllocQty,| zvMeasure,OrigRequiredQty,| zvDate,ThisEndDate,zvClock,ThisEndTime) END IF xpDebugDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Start ^@ @, end ^@ @, raise order ^@ @',| zvDate,StartDate,zvClock,StartTime,| zvDate,EndDate,zvClock,EndTime,| zvDate,RaiseDate,zvClock,RaiseTime) END !}}} DumpPending = FALSE !note not got a schedule for dumping EmptyClone = FALSE !..nor an empty clone !}}} !{{{ IntegrateFinal !This is called after the final schedule. IntegrateFinal ROUTINE IF umIsZero(ThisAllocQty) !We failed ELSE !We made it DO IntegrateSchedule END !}}} !{{{ CloneFirstSummary CloneFirstSummary ROUTINE SummaryWS = xpCreateType(FirstSummaryWS,xpSummaryRecord,TRUE) !create as a peer, not a child IF ~SummaryWS THEN Err# = gxUnspecifiedErr; DO ProcedureExit. !Allocate the next tentative WO# (so it doesn't get muddled with other clones) xpAllocateWONum(,4,'CloneFirstSummary') xpwC:Sibling = FirstSummaryWS !note where we came from !These 3 fields get set by ThisSchedule xpwC:AllocationStrategy = 0 xpwC:RequiredQty = umZeroVal xpwC:OrderQty = umZeroVal Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !Note it for possible dump later CloneQ:RecNo = SummaryWS Add(CloneQ) DumpPending = FALSE !note not got a schedule for dumping EmptyClone = TRUE !note created a new clone !}}} !{{{ ThisSchedule !NB: No final trace is made when scheduling from here. ! All callers to this must make their own appropriate trace ! so the user can see what happenned. Calling IntegrateSchedule ! after calling this will merge dates and make an appropriate ! trace message. ThisSchedule ROUTINE IF DumpPending THEN DO DumpSchedule. Err# = xpLoadWS(SummaryWS,9801124) IF Err# THEN DO ProcedureExit. xpwC:AllocationStrategy = SetSty xpwC:RequiredQty = SetQty xpwC:OrderQty = SetOrderQty Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !{{{ show log title IF xpMaxTraceDetail:Value CASE Action OF Action:NoMixTop xpWriteLog(TopWS,SummaryWS,1,'Stock probe for NoMix') OF Action:NoMixMaxStockPhysicalMax xpWriteLog(TopWS,SummaryWS,1,'Max physical probe for NoMix+MaxStock') OF Action:NoMixMaxStockPhysicalMin xpWriteLog(TopWS,SummaryWS,1,'Min physical probe for NoMix+MaxStock') OF Action:NoMixMaxStockPhysicalProbe xpWriteLog(TopWS,SummaryWS,1,'Best physical probe for NoMix+MaxStock') OF Action:NoMixMaxStockPhysicalBest xpWriteLog(TopWS,SummaryWS,1,'Re-do best physical probe for NoMix+MaxStock') OF Action:NoMixMaxStockMax xpWriteLog(TopWS,SummaryWS,1,'Max probe for NoMix+MaxStock') OF Action:NoMixMaxStockMin xpWriteLog(TopWS,SummaryWS,1,'Min probe for NoMix+MaxStock') OF Action:NoMixMaxStockProbe xpWriteLog(TopWS,SummaryWS,1,'Best probe for NoMix+MaxStock') OF Action:NoMixMaxStockBest xpWriteLog(TopWS,SummaryWS,1,'Re-do best probe for NoMix+MaxStock') OF Action:NoMixMaxStockResidue xpWriteLog(TopWS,SummaryWS,1,'Residue for NoMix+MaxStock') OF Action:MaxStockTop xpWriteLog(TopWS,SummaryWS,1,'Stock probe for MaxStock') OF Action:MaxStockPhysicalMax xpWriteLog(TopWS,SummaryWS,1,'Max physical probe for MaxStock') OF Action:MaxStockPhysicalMin xpWriteLog(TopWS,SummaryWS,1,'Min physical probe for MaxStock') OF Action:MaxStockPhysicalProbe xpWriteLog(TopWS,SummaryWS,1,'Best physical probe for MaxStock') OF Action:MaxStockPhysicalBest xpWriteLog(TopWS,SummaryWS,1,'Re-do best physical probe for MaxStock') OF Action:MaxStockResidue xpWriteLog(TopWS,SummaryWS,1,'Residue for MaxStock') ELSE xpWriteLog(TopWS,SummaryWS,1,'Schedule for action @',zvInt,Action) END END !}}} xpSetLogDepth(+1); LogDepth += 1 Err# = xpCreateScheduleHelper(SummaryWS,ThisState,SetFlags,MethodUsed) xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. DumpPending = TRUE !note got a schedule for potentially dumping EmptyClone = FALSE !note the clone has been used !}}} !{{{ FixFloaters !If we're doing a WO kit shortage and fulfilling that is done !from a committed or done batch, we may have created return !floaters. Fix those now. FixFloaters ROUTINE Err# = xpFixFloaters(TopWS,EndDate,EndTime) IF Err# THEN DO ProcedureExit. !}}} !{{{ ScheduleFail ScheduleFail ROUTINE IF DumpPending THEN DO DumpSchedule. DO DumpClones RaiseDate = gxDateUnknown RaiseTime = 0 StartDate = gxDateUnknown StartTime = 0 EndDate = gxDateUnknown EndTime = 0 ExpiresOn = 0 AllocQty = umZeroVal AllocCost = umZeroVal !}}} !{{{ ProcedureExit ProcedureExit ROUTINE IF DumpPending THEN DO DumpSchedule. IF Err# DO ScheduleFail ELSIF EmptyClone AND SummaryWS <> FirstSummaryWS xpDestroySummaryWS(SummaryWS) END !Restore orig trace level xpMaxTraceDetail:Value = OldMaxTraceDetail xpDebugDetail:Value = OldDebugDetail IF ContextSet THEN xpSetLogContext(). LOOP LogDepth TIMES xpSetLogDepth(-1) END RETURN Err# !}}} !}}} !{{{ xpCreateScheduleHelper !{{{ history ! !{{{ 1995 ! 29/3/95 DCN Created ! 5/4/95 DCN Separated from xpSchedule so that... ! Removed restriction that XPW record buffer must be preserved ! Removed file open/close ! 7/4/95 DCN Tweaked log messages to show #'s ! 13/4/95 DCN Don't propagate 0 dates ! 16/4/95 DCN Added EndDate parameter ! Used lazy zfLoad ! Re-jig to allow forward and back scanning ! 17/4/95 DCN Sort out restting of ConsiderDate ! 18/4/95 DCN Add StockDate parameter and its handling to stock allocation ! 21/5/95 DCN Honour the No-Allocate Strategy option ! 20/6/95 DCN Clear sticky error code on abort !}}} !{{{ 1996 ! 19/5/96 DCN Propagate EndDate to caller as well as update the XPW when get ! a stock only situation (see update the summary) ! Update StartDate at end too ! Use xpFirstConsiderDate instead of DIY ! 23/5/96 CRJ Tweaked so that when scheduling for stock the top level ! stock allocation is not performed. ! 28/5/96 CRJ Tweaked to remove schedule if aborted. ! 5/6/96 CRJ Don't give stock back when NoAllocate is set. ! 8/7/96 CRJ Use Q ! 9/8/96 CRJ Skip the putting back message if we didn't find any ! 13/9/96 CRJ Added stock-only ability ! 16/9/96 CRJ Pass the purchased iterations to the stock allocator ! 15/11/96 CRJ Tweak top level to one of buy,make or sub-con ! 16/11/96 CRJ Add MakeDate param ! 16/11/96 CRJ Integrate 'make' date ! 16/11/96 CRJ Rearrange for explicit sub-contracting !}}} !{{{ 1997 ! 25/1/97 CRJ Rearrange to to non-allocating stock check first ! 28/1/97 CRJ API and other changes to work from quantities ! 29/1/97 CRJ include short look-ahead when checking stock levels ! 29/1/97 CRJ Extended trace messages etc ! 30/1/97 CRJ Skip tentative buy etc of no stock available in future ! 31/1/97 DCN Don't do tentatiev stuff, just look-ahead number of days given ! by mcGetMinLeadTime ! 31/1/97 CRJ Add error frame, fix typos. ! 4/2/97 CRJ Remove internal demand capability ! 20/2/97 CRJ Add dispatch date into lookahead date for 1st alloc ! 26/2/97 CRJ Tweak trace messages ! 10/4/97 CRJ Tweaks for AdHocWOrderStrategy ! 11/4/97 CRJ Allow make if a module boundary but at top level! ! 11/4/97 CRJ Don't allow AdHoc WO schedules to be satisfied from stock ! 11/4/97 CRJ Fix whoopsie ! 18/4/97 CRJ Fix subcon schedule in ad-hoc conditions ! 8/7/97 CRJ Only set a projected 'look date' ! 16/7/97 CRJ Fix stock-only schedule error messages ! 17/7/97 CRJ Better stock-only fail messages ! 22/7/97 DCN Add weight handling ! 21/8/97 DCN Show level 1 trace message if fail to stock enough after a schedule attempt ! 21/8/97 CRJ Implement StockFirst strategy ! 11/9/97 CRJ Unique stock stuff ! 24/9/97 CRJ Was xpCreateSchedule ! 29/9/97 CRJ Rearrange and integrate xpMoreStockStrategy ! 29/9/97 CRJ Integrate xpSysNoBuyOrSCStrategy ! 29/9/97 CRJ Don't delete log ! 1/10/97 CRJ Fill in used summary qty/value as well. ! 3/10/97 CRJ Allow use of non buy/make/SC materials to be used from stock ! 9/10/97 CRJ Claer up after DCN re: ad hoc WO strategy! ! 10/10/97 CRJ Handle max physical stock usage etc ! 14/10/97 CRJ Integrate IssueOnly strategy (for dispatch scheduling) ! 3/11/97 CRJ Fix dispatch/lookahead logic - this was resulting in orphaned buys etc. ! 25/11/97 DCN Add handling of null material (for a process step make) ! 27/11/97 DCN mcIsSupplied/SubContractable/Manufactured API change ! 19/12/97 CRJ Fix no method handling and failed makes ! 21/12/97 DCN Make method a level 1 trace !}}} !{{{ 1998 ! 1/1/98 DCN Show dims in qty trace messages ! 12/1/98 DCN Do after the event trace, not before ! Add method feedback, add no final trace option ! Don't do initial stock check if just checking stock ! 15/1/98 CRJ Remove use of IssueOnly strategy ! 21/1/98 CRJ Remove skip stock check heuristic if JustStockCheck is asserted. ! 16/2/98 DCN Put more monitor messages in ! 24/2/98 CRJ Remove allocations to top level non-SOL owners but do it in a way that ! doesn't destroy the final totalling results. ! 25/2/98 CRJ Tweak trace messages but keep within DCNs new overall scheme ! 4/3/98 CRJ Don't always set ScanAhead for second stock scan ! 8/3/98 CRJ Separate semantics of scan-ahead and WS-first/MSI-first ! 18/3/98 CRJ Make sure we look up to the old look date when regrabbing ! stock we found on the first stock scan - duh! ! 19/3/98 CRJ Integrate the IgnoreStock strategy ! 23/3/98 CRJ Fix orphaned PO's \o/ ! 24/3/98 CRJ Clear LocalStockDate if second stock scan isn't done ! 30/3/98 CRJ Fix min stock strategy I think. ! 3/4/98 CRJ Imply bump if owner is not a SOL. ! 3/4/98 CRJ When not allocatable do a WSOnly final stock check. ! 7/4/98 CRJ Fix consider date banging out to minstock date ! 20/6/98 DCN Remove MakeDate param ! 22/6/98 DCN StartDate is now just the earliest WO date ! Re-instate MakeDate! (but call it RaiseDate) ! Add more level 3 log messages ! 23/6/98 DCN Add reason for skip initial stock check and skip buy/make in log message ! 10/7/98 CRJ Remove xpSysSCKitstrategy stuff ! 21/7/98 DCN Add xpUrgentStrategy handling when doing ASAP ! 4/8/98 CRJ Don't auto-bump for non-SOL owner ! 18/8/98 DCN Honour xpIgnoreStockStrategy in final stock check too ! 31/8/98 DCN Use mcDescribeStep not xpDescribe on materials ! 8/9/98 DCN Log and fail nil quantity ! 11/9/98 DCN Hang on to initial stock check allocation for as long as possible ! 17/9/98 DCN Only treat top level owners of ASU, POL and WOH as non-allocatable ! instead of anything that isn't a SOL. (i.e. explicitly exclude ad-hoc ! demand sources rather than include 'real' demand sources) ! xpAllocateStock API change ! 10/10/98 DCN Add dimensions to final trace message ! 29/10/98 DCN xpwC:OwnerFile is now a FileNo (SHORT), not a TLA (STRING(3)) ! 30/10/98 DCN Add better trace when no method ! 26/11/98 DCN Honour xpAllowBuyAndMake:Value and xpBuyBeforeMake:Value ! 7/12/98 CRJ Sort out non-SOL owner stuff ! 24/12/98 DCN Do a better system for detecting and using existing schedules (see description) !}}} !{{{ 1999 ! 12/1/99 CRJ Dump bump strategy ! 13/1/99 CRJ Fail with nil qty if depth gtr 128, ug ! 14/1/99 CRJ Change fail depth to 24 and use 'failure log' messages. ! 18/1/99 DCN Do the dispatch date loop at all levels not just the top ! Add log message for final stock check ! Use xpAllocateStock in explore mode to find 'dispatch' date ! 19/1/99 DCN Consider dispatch and goods-in methods as 'make' criteria ! if they're present ! 20/1/99 DCN Add log message if look-ahead goes past ASAP cut-off date ! 24/1/99 DCN Always use committed stock unless urgent is asserted in ASAP ! even if its arrival date could be beaten ! 27/1/99 DCN Pick up late existing WO's even when doing JIT unless 'urgent' is asserted ! 29/1/99 DCN Add xpASAPCutOffDays:Value when looking ahead for JIT 'cos it ain't there in the EndLimit ! 2/2/99 DCN Use optimistic lead time when job is urgent ! 8/2/99 DCN Only do Pass 2 when just checking stock ! 9/2/99 DCN Allocate to stock orders as well as SOLs and ENQs ! 10/2/99 DCN Add log message when release stock ! 1/3/99 DCN Add trace message to show stock being released ! Keep allocation to stock orders ! 1/3/99 DCN Don't release stock order allocation ! 23/3/99 DCN Show WO found date not look date when abandon JIT ! 17/4/99 DCN Be lazy about cost updates when getting min lead time ! 27/5/99 DCN xpAllocateStock API ! show a trace message when find stock and doing MINSTOCK ! 30/6/99 DCN Allow stock control lag when pick up WIP to use ! 6/7/99 DCN Change name of LastCommittedWO to LastCommittedWOPO ! Allow for stock control lag when deciding to use late WIP ! 6/8/99 DCN Don't limit the forward scan when looking for WIP ! 14/8/99 DCN Honour the buy before option in the strategy ! Increase log depth when looking for WIP ! 18/11/99 DCN Don't adjust dates for WIP when doing a MINSTOCK schedule ! 16/12/99 DCN Carry on when find late WIP when doing JIT and let xpSchedule sort it out ! 23/12/99 DCN Use mcCurrency as the default currency not umCurrency() ! 28/12/99 DCN xpAllocateStock API ! Add ExpiresOn param !}}} !{{{ 2000 ! 26/1/100 CRJ Reset EndLimit before final stock check ! 10/02/00 DCN When the job is urgent consider the min lead time to be 0 when looking for stock ! 07/04/00 DCN xpCheck API ! 29/07/00 DCN Always use ConsiderDate when looking for existing stock, not LookDate ! (so can pick up earliest appropriate stock that is committed) ! 03/08/00 DCN Don't do stock check on split batches ! 08/08/00 DCN Remove MemFirst# stuff on final stock check, it'll always get it right by ! virtue of being given the end date of the created stock. If MemFirst is ! asserted, it ends up finding reserved stuff before comitted. ! 24/08/00 DCN Add xpMerge::Id detection to indicate do stock check and don't keep it ! 26/08/00 DCN Make waiting for existing WIP a level 1 alert ! 01/09/00 DCN Allow for WIP beyond the end limit date ! 14/09/00 DCN Don't adjust raise date to end of PO (xpMakeMaterial sorts that out) ! 19/09/00 DCN When raise end limit to wait for WIP, allow for stock control lag ! 22/09/00 DCN Don't fiddle with local end limit dates when find WIP ! 30/11/00 DCN Release tentatively allocated stock after made the shortfall not before, ! otherwise dispatch methods pinch stock they shouldn't !}}} !{{{ 2001 ! 16/03/01 DCN Add height stuff ! 22/03/01 DCN xpAllocateStock API (now returns committed availability date, not create date) ! 08/04/01 DCN Show method type in strategy log too ! 20/05/01 DCN Allow for SOP ! 29/05/01 DCN Log a trace message when not looking for WIP cos just doing stock check ! Do WIP stock check when doing sys-physical too ! Do not skip stock check when NoMix is set ! 30/06/01 DCN xpStockRecord renamed xpStockUseRecord ! 02/10/01 DCN xpPurgeTypes API ! Detect case when no stock is found in final stock check and say so in the log message ! 08/10/01 DCN Skip stock check when xpwC:MakeThis is set ! 12/10/01 DCN Rename xpOutputRecord to xpMadeRecord ! 13/10/01 DCN JustStockCheck stops the make/buy/subcon and prevents a second pass - *and that's all* ! (specifically, it still looks for late WIP) ! Make debug log message when snap end limit date ! 30/11/01 DCN Don't call mcGetMinLeadTime, now unnecessary 'cos we always look ahead for WIP anyway ! xpAllocateStock API ! Don't look for MSI WIP when doing MinStock schedule ! 31/12/01 DCN Remove weight !}}} !{{{ 2002 ! 11/01/02 DCN Put stock constraints in initial log message ! 26/01/02 DCN Show dims in fail trace message ! Set correct LookDate when doing non-urgent ASAP stock probe!! ! 28/01/02 DCN Ignore urgent when doing JIT ! 30/01/02 DCN Remove OrderDate ! xpBuyMaterial and xpMakeMaterial API ! Consider date is the target date, don't use xpFirstConsiderDate (already done) ! 31/01/02 DCN Use xpSetLatest/Earliest not gxLatest/Earliest ! Add StartTime and EndTime params ! 15/02/02 DCN Log the ref size too ! 23/03/02 DCN xpBuyMaterial API ! Consider free issue ! 16/04/02 DCN Increase recursion limit from 24 to 128 ! 02/05/02 DCN Add more diagnostic logging messages ! 06/06/02 DCN Do depth checking with a local not WS field ! 13/06/02 DCN When trying to beat existing WIP on an urgent job abandon if get same date as well as older ! Add alert message when an urgent job beats WIP ! 05/07/02 DCN Get Method used from the called function instead of assuming it here ! Use MakeCycles as the 'success' trigger for the 'buy' cases as well as 'make' ! 17/10/02 DCN Honour the 'IsModule' flag returned from xpMakeMaterial ! 03/11/02 DCN Always try to beat existing WIP, even when not urgent and even when doing JIT ! Implement 2 passes for JIT too, pass 1 = try to beat WIP, pass 2 = calc date using it (as before) ! Return error code not T/F ! 08/11/02 DCN When checking if we beat WIP use *EXACTLY* the same criteria as the first time round, else ! get inconsistent behaviour. Specifically, check the committed WO/PO date not the ExistingLimit ! date. ! 09/11/02 DCN Just check dates when checking if beat WIP, the new quantity is irrelevant !}}} !{{{ 2003 ! 30/01/03 DCN Don't generate spurious alerts about trying to beat WIP when doing stock check only ! 01/02/03 DCN Never wait for late reserved WIP, only wait for late committed WIP that is 'close' ! 13/02/03 DCN Use a default to control how long to wait for reserved WIP (to try experiments) ! Honour the NoWIPRace strategy ! 15/02/03 DCN Remove guard from xpDestroySummaryWS, its done internally now ! 15/03/03 DCN Only abandon jit if existing stuff is late when being told not to race it ! 22/04/03 DCN Allow for MostCost strategy ! 13/06/03 DCN Never try to beat late committed WIP ! 08/07/03 DCN Add more debug trace messages ! 21/07/03 DCN Show target date too in abandon JIT trace ! 11/08/03 DCN Extend JustStockCheck param to a set of flags ! Add AlwaysUseWIP option ! 12/08/03 DCN Rename AlwaysUseWIP option to AllocatingTool ! xpAllocateStock API ! 14/08/03 DCN Don't look ahead in stock when just doing physical stock check ! 17/12/03 CRJ Don't buy/make if we've recursed into a module boundary doing physical/batching [1422]. !}}} !{{{ 2004 ! 01/03/04 DCN Pass buy/make priority to mcIsSupplied/SubContractable/Manufactured ! 12/03/04 DCN Allow for mcMethodUnMakeItem [1817] ! 15/03/04 DCN Add xpSetLogContext calls ! 17/03/04 DCN Add evaluation of buy *and* make when there is a choice [1817] ! 22/03/04 DCN Add evaluation of buy/make *and* MO when there are choices [1817] ! 11/04/04 DCN Honour the order qty if not enough stock ! 13/04/04 DCN Normalise OrderQty and NeedQty before check if got an order qty ! 01/05/04 DCN Use SelfOnly scan for final stock check when not allocatable ! 06/05/04 CRJ Use target costs if set ! 09/07/04 DCN Only use target costs if bigger than scheduled costs and alert it ! Allow for invented records ! 12/07/04 DCN Invent records for phantom stock ! 12/08/04 DCN Add a log when date limits change ! 23/08/04 DCN Have separate thresholds for return WIP/Plan when racing [4575] ! 15/09/04 DCN Allow for multiple MO's being the only choices when allowed to explore all ! 29/10/04 DCN Fix best solution picking system when there are choices that fail [5479]/16 ! (use qty not cycles as the success trigger everywhere) !}}} !{{{ 2005 ! 24/02/05 DCN Do not invent returns [6956]/3 ! Correct calls to mcIsService ! 11/03/05 DCN Don't release stock created by an MO container schedule (outer loop now does it) [7311] ! 26/04/05 DCN If being forced to buy or make, do it even if flag not set in the MCH [7821] ! Don't allow buy when doing a container ! 27/05/05 DCN Use xpDescribeStep not mcDescribeStep ! 08/06/05 DCN [8344] Treat order qty as the desired final level when got a shortage, not the make/buy level ! 14/06/05 DCN xpMakeContainer API ! 22/06/05 DCN Generate an alert if multiple MO methods and not being allowed to use them ! 20/07/05 CRJ Make JustStockCheck do what it says it should do above. !}}} !{{{ 2006 ! 07/06/06 CRJ Allow urgent ASAP strategy to race WIP. ! 10/07/06 DCN Allow for mcIsMultipleOutput returning the method type not T/F for dis-assembly !}}} !{{{ 2007 ! 12/02/07 DCN Differentiate all types in LastMethod (not just buy/make/mo) ! When acquistion choices produce level 1 alert of #choices and why 1 was chosen ! 29/03/07 DCN Tweak logs to make easier to see what's being done (!?) ! 24/10/07 CRJ Preserve the WO# when exploring buy and make options. !}}} !{{{ 2008 ! 12/03/08 DCN Allow for a diff material to 'make' that being 'allocated' ! 29/04/08 DCN Ignore final stock check when an action has been ignored ! Honour ignore stock session option ! 30/04/08 DCN Honour xpSession:StockMustBePhysical ! In-line sub-con is not considered 'physical' when doing a back-flush !}}} !{{{ 2009 ! 28/03/09 DCN Allow for MSC owners ! 24/04/09 DCN mcGetCost API ! 27/04/09 DCN Treat buy/make priority strategy bits as an override !}}} !{{{ 2010 ! 25/10/10 DCN Rename mcIsModule() to mcGetIsModule ! 28/10/10 CRJ Throw level 1 alert when recursion depth reached !}}} !{{{ 2011 ! 05/04/11 DCN Use xpMaxStructureDepth default not hard wired 128 in the above ! 24/06/11 DCN Ignore cheap part limit when doing JIT ! Honour the xpSchFlgPhysical/CommittedCheck flags ! 27/06/11 DCN xpAllocateStock API ! Implement the notion of being forced to use late WIP ! 28/06/11 DCN Make logs for existing WIP/Plan stock found and race results more explicit ! 29/06/11 DCN Honour the xpSchFlgReservedCheck flag ! 01/07/11 DCN Add a reason to abandon JIT log ! 07/07/11 DCN xpAllocateStock API ! 09/07/11 DCN Use xpGetConsiderDate not DIY ! 14/07/11 DCN Set xpwC:UsedDate/Time in the summary to reflect the true EndDate/Time of the schedule ! 27/07/11 DCN Allow for no plan or WIP when racing!! ! 29/07/11 DCN Remove NoFinalTrace param (no such thing anymore) ! 02/08/11 DCN Remove use of xpStockLookAheadDays (completely outmoded by modern WIP racing) ! 03/08/11 DCN Reduce number of gxMonitorStep calls ! 04/08/11 DCN Put the existing limit in the abandon JIT message ! 07/08/11 DCN Allow for xpWait4Late..Limit values being -ve (means no limit - i.e. wait forever) ! 11/08/11 DCN Consider time in day too when check WIP race result ! Check race result even when result is identical ! 15/08/11 DCN Fix WIP race result testing!! ! 18/08/11 DCN Set start/end time in worksheets too ! Calc elapsed time when picking best to seconds, not days ! Handle RaiseTime ! xpMakeMaterial API ! 23/08/11 DCN Don't pick xpAnonBuyRecord as a best choice when there others that are not ! 30/08/11 DCN Count WIP race wins ! 03/09/11 DCN Use xpOlder not gxOlder ! 07/09/11 DCN Save and restore the IsModule flag too when dumping a probe ! 14/09/11 DCN When doing JIT and not WIP racing and find late WIP and we're at depth 1, snap to late WIP ! When doing ASAP and not WIP racing, always snap to late WIP ! 15/09/11 DCN Always race WIP but chuck it if not allowed to use it ! 16/09/11 DCN Fix Method:AnonBuy logic ! Use WIP thresholds for planned when racing a 'top' ! 20/09/11 DCN Add xpSchFlgAnyStockCheck handling ! 26/09/11 DCN Don't attempt a WIP race unless required date is at least threshold days away from what's there ! Remove separate return race thresholds ! 03/10/11 DCN xpAllocateStock API ! When racing returns, race their originator creation date/time not the return itself ! 07/10/11 DCN Re-instate not racing WIP when told ! 12/10/11 DCN Don't call xpDescribeStep twice ! 24/10/11 DCN Implement NoTopRace option ! 31/10/11 DCN Don't delay JIT for late stock when doing a back-flush schedule ! Imply NoWIPRace when doing a back-flush schedule ! Get NoWIPRace from session option not the strategy ! 03/11/11 DCN Re-jig for unified stock cache ! 07/11/11 DCN Always race MSI stock - NoWIPRace applies to xpwC stock only ! 10/11/11 DCN Remove IsSplitBatch guard on allocatable/release guards (caller does it) ! 18/11/11 DCN Use IsTop not Depth = 1 as a guard ! 23/11/11 DCN Honour the xpSchFlgVirtualCheck flag ! 30/11/11 DCN Don't go urgent when doing JIT ! 07/12/11 DCN Cater for an 'allocate' schedule ! 12/12/11 DCN Use IsFuture status from xpAllocateStock not DIY with xpOlder ! 16/12/11 DCN NoWIPRace is unconditional - not just for virtual ! 30/12/11 DCN Consider a stock-use method to be a module boundary !}}} !{{{ 2012 ! 02/02/12 DCN Don't race WIP when reject partial NOMIX batch ! 03/02/12 DCN Remove batching boundary guard (MAXSTOCK algorithm relies only on MustbePhysical guard) ! 11/02/12 DCN Implement NoPlanRace and NoSchedRace options ! NoTopRace becomes a qualifier on the no-race option, not an option in itself ! 20/02/12 DCN Use xpSchedStateType for return params not separate params ! Return existing stock status too ! Use xpIsRaceWinByDate, not DIY ! 22/02/12 DCN xpIsRaceWinByDate API ! Re-factor to only set ExistingDate/Time if we raced WIP and won ! Honour xpIgnoreLateWIPStrategy and session option ! Set xpwC:AbandonJIT in our meta-summary as approp ! Stop exploring options if JIT was abandonned ! 23/02/12 DCN Ignore late WIP when doing urgent ! 24/02/12 DCN Use xpIsRaceCandidate not DIY ! 09/03/12 DCN Fail just this schedule when reach structure depth limit, i.e. do *NOT* return an error code !}}} !{{{ 2013 ! 18/01/13 DCN Don't drop urgent if doing JIT ! xpIsRaceWinByDate and xpIsRaceCandidate API ! 07/03/13 DCN Initialise MethodType!! ! 15/03/13 DCN Allow for MO's in race winner marking ! 30/05/13 DCN When lose a JIT WIP race, probe again a day later, not at the existing date ! 17/06/13 DCN Use xpFinishSummary not DIY !}}} !{{{ 2015 ! 27/10/15 DCN [50742]Look for earliest WIP when abandon JIT !}}} !{{{ 2017 ! 04/10/17 DCN [55482]Add BatchesUsed feedback ! 05/10/17 DCN [55482]Apply CyclesExtra when make/buy !}}} !{{{ 2018 ! 01/03/18 DCN [55480]xpSetExtraQty API ! 03/03/18 DCN xpSetOrderQty API ! 20/05/18 DCN [56498]Don't race stock that has late kit ! 17/10/18 DCN [57426]Honour xpSchFlgLateKitCheck !}}} !{{{ 2019 ! 16/12/19 DCN [59270]Note race losers ! 17/12/19 DCN [59270]If win a race against a late merged demand, note the win but don't use it !}}} !{{{ 2022 ! 24/11/22 CRJ [63291]Use the never race wip default. !}}} ! 24/09/23 DCN Fix re-doing best when its MO (that path has obviously never been covered before!) ! 22/11/23 DCN When doing ASAP, reset ConsiderDate/Time when race WIP or lose a WIP race ! 24/11/23 DCN Re-start ASAP to use existing if WIP race is later than exiting ! 06/12/23 DCN Tell xpMakeMaterial when we're making a container ! Detect when a material is a container as a 'top' ! 11/12/23 DCN Fix missing log when abandon JIT for existing stock that is not a race candidate ! Don't race small partial existing when doing JIT ! 04/01/24 DCN Re-load summary WS at start of pass loop ! 05/01/24 DCN Use xpScheduleAsContainer not xpIsTop ! !}}} !{{{ description ! ! This is the helper function that actually performes the scheduling ! for xpCreateSchedule. It can be called recursively via xpCreateSchedule. ! ! xpCreateScheduleHelper(LONG,*xpSchedStateType,LONG,*LONG),LONG ! ! ! ! ! !ErrorCode ! ! ! ! !Method used (xpwC:RecType) ! ! ! !Flags - see 'xp\xp_priv.equ'@xpSchFlg<...>@ ! ! !schedule results ! !Handle - the summary WS to schedule ! !{{{ notes on schedule dates ! ! There are several dates associated with a schedule: ! StartLimitDate = the earliest permissible date ! EndLimitDate = the latest permissible date ! StartDate = the earliest date used by the schedule *** ! EndDate = the latest date used by the schedule *** ! ConsiderDate = the current date under consideration *** ! The constraints on these are: ! StartLimitDate <= StartDate <= ConsiderDate <= EndDate <= EndLimitDate ! ! All allocation functions take the ConsiderDate as an input parameter. ! Some may return one or two dates to reflect the result of the allocation. ! ! Those dates marked with *** also have an associated time of day. ! !}}} !{{{ notes on raise date ! The RaiseDate is only signficant to xpMakeMaterial. It is the date that ! will eventually represent a WO start date. It must be passed around the ! top level scheduling functions 'cos the WO raise date is set by the lowest ! level process, or break, step *within* a structure. This means it must be ! passed up through the recursion so that the top level knows what it is when ! it creates the output record. This is the *only* case where the 'object' ! scheduling function cannot determine the time span itself. The logic here ! will, for example, 'snap' the raise date to PO completion boundaries. ! xpMakeMaterial can't do it 'cos it can't differentiate between parts that ! are made and parts that are bought. !NB: The xpwC:RaiseDate meaning is dependent on the worksheet type it is ! in. In a summary, its the overall WO raise date, in a make record or ! a sub-con record its the start date of that *step*. It has no meaning ! anywhere else. !}}} !{{{ notes on detecting existing schedules !20/02/12 DCN This discussion is out of date. !This only applies when doing ASAP !--------------------------------- ! The method for detecting and using existing schedules involves two ! 'stabs' at the stock. Stab 1 looks for stock at the min lead time. ! If this succeeds, we're all done, easy! ! If this fails, we look for stock at the end of time and note the ! date its avialable. Then we try to schedule for the stab 1 date. ! If we achieve that date whoopie! Its done. ! If the achieved date is later than the min lead time, then we go ! round the loop again, but this time the target date is that we found ! when looking for 'end of time' stock. It then schedules on this basis. ! It will either succeed or fail as normal. ! ! This algorithm is modified if the stab2 stock date includes a committed ! WO/PO. In this case we always go with the stab2 date to make sure we pick ! up the already started work, *even if its late and could be bettered*. ! The assumption is that the user will always want to use work already ! started. If they don't they can assert the 'urgent' option. In this ! case the scheduler will try to beat the existing date. ! To appreciate the significance of all this, consider: ! Material M has a lead time of L ! An existing schedule is in place, and *committed*, for E where E >> L ! The existing schedule is late 'cos a resource its relying on is severely overloaded. ! This means the standard lead times are meaningless. ! The existing schedule is committed (i.e. started) at the bottom level ! but the top level is still reserved. ! In this context, the scheduler looks for stock at L and finds none, so it ! tries to make some. But 'cos the resources are so overloaded, the date it ! achieves, A, is > E. I.e. its even later. Without the second stab, the ! scheduler would commit to this even later schedule and orphan the earlier ! one. Real dumb! !When doing JIT !-------------- !When doing JIT, if we detect any existing late WO's we abort the schedule to !force a re-try in ASAP mode. This ensures the late work is picked up and used. !}}} !{{{ notes on the significance of xpwC:MakeThis !8/10/01: Eek! !There's a problem with the current MO approach. !The current approach when an MO is detected that is not in stock is to schedule some !of the thing its an output of. But this falls down when there *is* stock of the parent !thing. The scheduler says 'fine I've got some parent' and looks no further. The MO !then doesn't get made. !E.g. ! !Parent Thing ! Makes Output Thing as an 'output' ! Also makes Parent Thing in its own right ! !My thing ! Wants a part of Output Thing ! !Order already in place that needs an Output Thing that doesn't want the Parent Thing !Parent Thing is now in un-allocated WIP ! !Take an order for My Thing ! !Scheduler looks for and doesn't find any free Output Thing. !So it decides to schedule a Parent Thing. !Parent Thing schedule succeeds from WIP *without* making any more Output Thing. !Bang! Schedule fails. ! !Its not just of case of forcing it to make 'cos that may go too far down the tree. !The scheduler needs to know whats going on. ! !This is acheived via the xpwC:MakeThis field. This field is set (by xpCreateSchedule) !when it determines that an MO is not in stock and has to be made. In this state !xpwC:Material is the container and xpwC:MakeThis is the thing actually needed. !While xpwC:MakeThis is set, no stock checking is done, this forces everything to be !made. When the recursion gets down to the level where xpwC:MakeThis is actually !created, xpwC:MakeThis is cleared. This reverts the scheduler to normal stock checking !behaviour. The thing that knows the output is being created is xpMakeMaterial. That !is where xpwC:MakeThis is cleared. !}}} !AllocatingTool: ! When this option is set the stock allocation algorithm is modified as follows: ! Returns are never ignored ! Returns are never raced !}}} xpCreateScheduleHelper FUNCTION(SummaryWS,ss,Flags,Method) !{{{ data !Our result state param synonyms StartDate EQUATE(ss.StartDate) StartTime EQUATE(ss.StartTime) EndDate EQUATE(ss.EndDate) EndTime EQUATE(ss.EndTime) RaiseDate EQUATE(ss.RaiseDate) RaiseTime EQUATE(ss.RaiseTime) ExpiresOn EQUATE(ss.ExpiresOn) TotalCost EQUATE(ss.AllocCost) TotalQty EQUATE(ss.AllocQty) ExistingDate EQUATE(ss.ExistingDate) ExistingTime EQUATE(ss.ExistingTime) ExistingStatus EQUATE(ss.ExistingStatus) BatchesUsed EQUATE(ss.BatchesUsed) !xpAllocateStock spec s GROUP(xpStockSpecType) . !'xp\xp_priv.equ'@xpStockSpecType@ ExistingQ QUEUE(gxRecNoQType). !xpAllocateStock param synonyms !In ConsiderDate EQUATE(s.StartDate) !The reference date we are currently considering ConsiderTime EQUATE(s.StartTime) !..and time LookDate EQUATE(s.EndDate) !latest date we want LookTime EQUATE(s.EndTime) !..and time RequiredQty EQUATE(s.RequiredQty) !how may we want !Out LocalStockDate EQUATE(s.FoundDate) !The date our stock demand is satisfied LocalStockTime EQUATE(s.FoundTime) !..and its time LocalExpiresOn EQUATE(s.EarliestExpiryDate)!The expires on date pertaining to one fulfillment option ! ExistingQty LIKE(s.FoundQty) !Existing stock qty at start RequiredLen LIKE(xpwC:Length) RequiredWid LIKE(xpwC:Width) RequiredHeight LIKE(xpwC:Height) !Decoded flags JustStockCheck BYTE AllocatingTool BYTE WorkBackwards BYTE IsContainer BYTE Urgent BYTE NotUrgent BYTE MostCost BYTE Normal BYTE ScheduleMode BYTE PhysicalCheck BYTE CommittedCheck BYTE ReservedCheck BYTE VirtualCheck BYTE AnyStockCheck BYTE LateKitCheck BYTE MustBePhysical BYTE IgnoreStock BYTE IgnoreLateWIP BYTE NoWIPRace BYTE NoPlanRace BYTE NoSchedRace BYTE IsTop BYTE !TRUE iff this is a top level schedule NeedQty STRING(12) !Qty required after looking in stock OrderQty STRING(12) !Qty required to be made if not enough stock NeedOrderQty STRING(12) !Qty required to be made after looking in stock ReqOrderQty STRING(12) !Workspace for calculating the above InitialDate LONG !Initial consider date (used to reset an ASAP when lose a WIP race InitialTime LONG !.. LocalRaiseDate LONG !The raise date pertaining to one fulfillment option LocalRaiseTime LONG !.. LocalStartDate LONG !The start date pertaining to one fulfillment option LocalStartTime LONG !.. LocalEndDate LONG !The end date pertaining to one fulfillment option LocalEndTime LONG !.. FinalMethod LONG !The final method set in the summary StartLimit LONG !The start limit of the schedule EndLimit LONG !The end limit of the schedule scanKey LONG !used when setting WIP race winners SetRaiseDate BYTE !feedback from xpMakeMaterial to ask for raise date to be set !in the summary and then cleared for passing back to the caller ScheduleName STRING(64) Material LIKE(xpwC:Material) MakeMat LIKE(xpwC:MakeMat) MethodType LIKE(xpwC:MethodType) StepMethod LIKE(xpwC:StepMethod) AllocationStrategy LIKE(xpwC:AllocationStrategy) ParentWS LIKE(xpwC:ParentHandle) TopWS LIKE(xpwC:TopHandle) MOContext LIKE(xpwC:mcaRef) Depth LONG IsModule LONG Pass SHORT RaceWon SHORT !+1 = we raced WIP and won, -1 = we won but are not allowed to use it, 0 = we lost a WIP race Allocatable BYTE !TRUE iff stock is allocated to the schedule owner ReleaseStock BYTE !TRUE iff stock allocated 'early' is to be released BuyPriority BYTE !TRUE iff buying priority when there is a choice MakePriority BYTE !TRUE iff making priority when there is a choice BuyForced BYTE !TRUE iff being forced to buy it MakeForced BYTE !TRUE iff being forced to make it WSOnly LONG !set to xpSchFlgWSOnly iff only checking WS stock SelfOnly LONG !set to xpSchFlgSelfOnly iff not allocatable on final stock check NewStockStatus LONG !xpAllocateStock results from final stock check OldStockStatus LONG !xpAllocateStock results from initial stock check RecursionDepth LONG(0),THREAD !used to detect dependency loops MonitorOpen BYTE LogDepth LONG(0) !{{{ best choice control CanBuy BYTE CanCallIn BYTE CanSubCon BYTE CanMake BYTE CanMO LONG MOList QUEUE(gxRecNoQType);END MOMethod LONG MONum LONG MakeType LONG LastMethod BYTE Method:None EQUATE(0) Method:Invent EQUATE(1) Method:FreeIss EQUATE(2) Method:Buy EQUATE(3) Method:AnonBuy EQUATE(4) Method:SubCon EQUATE(5) Method:Make EQUATE(6) Method:MO EQUATE(7) !first MO choice=7, 2nd=8, 3rd=9, etc Best GROUP Method LONG !See Method:<...> mnemonics above StartDate LONG StartTime LONG EndDate LONG EndTime LONG TotalQty STRING(12) TotalCost STRING(12) Reason STRING(12) !reason for choice (for log message) END JITabandonned BYTE !TRUE iff a choice explored was abandonned to wait for late WIP !}}} NoStockCheckReason STRING(32) NoMakeBuyReason STRING(32) JITFailReason STRING(32) ForceUseReason STRING(32) ThresholdName STRING(16) !Threshold name (for log message) of WIP race limit !WO num cache stuff ThisWONum LIKE(xpwC:WONum) ThisWODepth LIKE(xpwC:WODepth) ThisIsModule LIKE(xpwC:IsModule) !}}} CODE !{{{ body DO ProcedureEntry !{{{ pre-init Err# = xpLoadWS(SummaryWS,12123); IF Err# THEN DO ProcedureExit. IsContainer = xpScheduleAsContainer() IF IsContainer Flags = BOR(Flags,xpSchFlgIsContainer) IF xpDebugDetail:Value > 2 xpWriteLog(,,3,'Scheduling ^@ as a full container',zvMaterial,xpwC:Material) END END ScheduleName = 'Scheduling ' & xpDescribeStep(xpwC:Material,xpwC:StepMethod,xpwC:Method) IF NOT gxMonitorStart(ScheduleName) Err# = gxUnspecifiedErr DO ProcedureExit END gxBeginErrorFrame (ScheduleName) MonitorOpen = TRUE xpSetLogContext(xpwC:TopHandle,SummaryWS,xpLogOpt:CreateScheduleHelper) Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. Pass = 1 ExistingDate = 0 ExistingTime = 0 ExistingStatus = 0 IF xpwC:ParentHandle = xpwC:TopHandle IsTop = TRUE ELSE IsTop = FALSE END ScheduleMode = xpwC:ScheduleMode !{{{ cache the WO# ! ! When we're exploring make versus buy the summary WONum can get changed. ! This is fatal for IsModule=FALSE steps. So if we dump the explorations ! we bring the WO# back to what it was. See DumpSchedule. This is a safe ! place to cache. We never buy and make for example at this level. ! ThisIsModule = xpwC:IsModule ThisWONum = xpwC:WONum ThisWODepth = xpwC:WODepth !Note: It is not necessary to wind back xpwC:CreateNum because the dependency ! rule of always 'ascending' is still valid. Not resetting it just means ! there will be a gap. !}}} IF xpIsSessionOption(xpSession:IgnoreStock) xpWriteLog(xpwC:TopHandle,xpwC:Handle,-2,'Ignoring stock of @ (@)',zvMaterial,xpwC:Material,zvMaterial,xpwC:MakeMat) IgnoreStock = TRUE ELSIF gxBitInMask(xpIgnoreStockStrategy,xpwC:AllocationStrategy) IgnoreStock = TRUE END !{{{ set NoWIPRace !When no WIP race is asserted, it means: ! "we must use any committed stock we find, even if its late" !NB: Committed stock in this context includes merged demands that are using committed kit that is also late IF xpIsSessionOption(xpSession:NoWIPRace) OR xpNeverRaceWIP:Value NoWIPRace = TRUE ELSE NoWIPRace = FALSE END !}}} !{{{ set NoPlanRace !When no Plan race is asserted, it means: ! "we must use any reserved stock we find, even if its late" NoPlanRace = xpIsSessionOption(xpSession:NoPlanRace) !}}} !{{{ set NoSchedRace !When no Sched race is asserted, it means: ! "we must not race schedules already in place" NoSchedRace = xpIsSessionOption(xpSession:NoSchedRace) !}}} !{{{ set NoTopRace !The NoTopRace variant means no schedule racing only applies to entries !already in the demand list, in this context new sub-assemblies are !allowed to be raced. They'll become a 'top' on the next pass. !When NoTopRace is given and we're not a top, !it means turn the no race flags off. IF ~IsTop AND xpIsSessionOption(xpSession:NoTopRace) !Race constraints do not apply - 20/05/18 DCN Why? NoSchedRace = FALSE NoPlanRace = FALSE NoWIPRace = FALSE !25/09/23 DCN Removed, not correct in this context-->NoWIPRace = xpNeverRaceWIP:Value !24/11/22 CRJ END !}}} !{{{ set IgnoreLateWIP IF xpIsSessionOption(xpSession:IgnoreLateWIP) IgnoreLateWIP = TRUE ELSIF gxBitInMask(xpIgnoreLateWIPStrategy,xpwC:AllocationStrategy) IgnoreLateWIP = TRUE ELSE IgnoreLateWIP = FALSE END !}}} !{{{ determine if allocatable IF xpwC:MakeThis !this is something further up the hierarchy forcing us to make this !so don't allocate so it gets made, but do keep it Allocatable = FALSE ReleaseStock = FALSE ELSE !Use the options provided by the caller END !}}} s.SummaryWS = SummaryWS !tell stock allocator who we're doing it for !}}} LOOP !{{{ initialise ! ! Set fail safe condition ! TotalCost = umMake(msCurrency:Value) !No cost yet TotalQty = umZeroVal !Not done any yet BatchesUsed = 0 !.. !Re-load our summary Err# = xpLoadWS(SummaryWS,0124042056); IF Err# THEN DO ProcedureExit. IsModule = xpwC:IsModule OrderQty = xpwC:OrderQty RequiredQty = xpwC:RequiredQty RequiredLen = xpwC:Length RequiredWid = xpwC:Width RequiredHeight = xpwC:Height Material = xpwC:Material IF xpwC:MakeMat MakeMat = xpwC:MakeMat ELSE MakeMat = Material END MethodType = xpwC:MethodType StepMethod = xpwC:StepMethod AllocationStrategy = xpwC:AllocationStrategy StartDate = xpwC:StartDate StartTime = xpwC:StartTime EndDate = xpwC:EndDate EndTime = xpwC:EndTime RaiseDate = xpwC:RaiseDate RaiseTime = xpwC:RaiseTime ExpiresOn = 0 SetRaiseDate = FALSE ParentWS = xpwC:ParentHandle TopWS = xpwC:TopHandle StartLimit = xpwC:StartLimitDate EndLimit = xpwC:EndLimitDate Depth = xpwC:Depth MOContext = xpwC:mcaRef !{{{ decode direction WorkBackwards = gxBitInMask(xpWorkBackStrategy,AllocationStrategy) !}}} !{{{ decode urgency xpDecodeUrgency(AllocationStrategy,Urgent,NotUrgent,MostCost,Normal) IF Urgent IF FALSE ! 18/01/13 DCN Rubbish, we want to leave as much time as possible to make kit-->WorkBackwards !Don't go urgent when doing JIT. !When doing JIT anything that fits time-wise is OK. !If it doesn't fit then JIT will be abandonned and we'll do an ASAP pass. Urgent = FALSE Normal = TRUE END !Urgent should always ignore late WIP, user is saying "I want it now" ! 18/01/13 DCN Rubbish, we should just have tighter race limits-->IgnoreLateWIP = TRUE END !}}} !{{{ set must be physical option IF xpIsSessionOption(xpSession:StockMustBePhysical) MustBePhysical = TRUE ELSIF gxBitInMask(xpMustBePhysicalStrategy,AllocationStrategy) MustBePhysical = TRUE ELSE MustBePhysical = FALSE END !}}} !{{{ determine buy before make option IF xpwC:IsSplitBatch !If we're doing a split batch it means we've already decided to !make it and the requirement is too big for a single batch. So !we don't allow the buy choice now either. !NB: We're looking at a summary here, so we know we've been through xpMakeMaterial BuyPriority = FALSE MakePriority = FALSE BuyForced = FALSE MakeForced = TRUE ELSE xpDecodeBuyOrMake(AllocationStrategy,BuyPriority,MakePriority,TRUE) !get strategy bits first IF BuyPriority AND ~MakePriority !Being forced to buy it BuyPriority = FALSE MakePriority = FALSE BuyForced = TRUE MakeForced = FALSE ELSIF ~BuyPriority AND MakePriority !Being forced to make it BuyPriority = FALSE MakePriority = FALSE BuyForced = FALSE MakeForced = TRUE ELSE !May have a choice xpDecodeBuyOrMake(AllocationStrategy,BuyPriority,MakePriority) !get defaults BuyForced = FALSE MakeForced = FALSE END END !}}} !{{{ too deep? IF RecursionDepth > xpMaxStructureDepth:Value xpWriteLog(TopWS,SummaryWS,-1,'Maximum structure depth reached:@',zvInt,xpMaxStructureDepth:Value) xpWriteLog(TopWS,SummaryWS,-1,'Probable cause: self referencing structure') TotalQty = umZeroVal !this is the failure signal TotalCost = umZeroVal !..just for completeness DO ProcedureExit END !}}} xpGetConsiderDate(ConsiderDate,ConsiderTime) InitialDate = ConsiderDate !save in case have to re-start an ASAP after a WIP race InitialTime = ConsiderTime !.. Method = xpStockUseRecord NeedQty = RequiredQty !{{{ log what we're doing IF Pass = 1 IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'NoWIP/Plan/SchedRace:@@@, Alloc:@, Release:@, IgnoreLateWIP:@', | zvFlag,NoWIPRace,zvFlag,NoPlanRace,zvFlag,NoSchedRace,| zvFlag,Allocatable,zvFlag,ReleaseStock,zvFlag,IgnoreLateWIP) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Scheduling @[of @][x @]for ^@ @ (on behalf of ^@, IsContainer:@)',| zvMeasure,RequiredQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvMaterial,xpwC:MakeThis,zvFlag,IsContainer) IF xpDebugDetail:Value > 2 xpSetLogDepth(+1); LogDepth += 1 xpWriteLog(TopWS,SummaryWS,3,'Strategy is @ (@) (Method:@, Constraints:@, Session options:@)',| zvSSMask,AllocationStrategy,zvInt,AllocationStrategy,zvStr,MethodType,zvInt,xpwC:StockConstraints,zvHex,xpIsSessionOption()) xpWriteLog(TopWS,SummaryWS,3,'Ref is @[of @][x @][x @]',| zvMeasure,xpwC:RefQty,zvMeasure,xpwC:RefLen,zvMeasure,xpwC:RefWid,zvMeasure,xpwC:RefHeight) xpSetLogDepth(-1); LogDepth -= 1 END END END END !}}} DoStockCheck# = TRUE DoMakeBuySubCon# = TRUE DoFinalStockCheck# = TRUE Free(ExistingQ) !}}} !{{{ do the initial stock check? ! ! Possible optimisation: if JustStock is asserted skip first stock check ! *but* make sure we do second stock check with the Look date rather ! than the target date - otherwise we'll miss stuff and start to make ! it... ! !{{{ not allocatable IF DoStockCheck# AND ~Allocatable NoStockCheckReason = 'increasing stock' DoStockCheck# = FALSE END !}}} !{{{ null material IF DoStockCheck# AND Material = 0 NoStockCheckReason = 'process step' DoStockCheck# = FALSE END !}}} !{{{ nil quantity IF DoStockCheck# AND umIsZero(RequiredQty) NoStockCheckReason = 'no quantity' DoStockCheck# = FALSE END !}}} !}}} !{{{ ignore stock strategy ! ! When ignore stock is asserted we only look for WS's. ! IF IgnoreStock WSOnly = xpSchFlgWSOnly ELSE WSOnly = xpSchFlgNone END !}}} IF DoStockCheck# !{{{ check stock levels Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF JustStockCheck AND (MustBePhysical OR PhysicalCheck) !{{{ just looking for physical stock that is on the shelf now !To get here we only want stuff that is physically on the shelf now. Pass = 2 !no second chance LookDate = ConsiderDate !don't look past 'now' LookTime = ConsiderTime !.. !}}} ELSIF JustStockCheck AND (CommittedCheck OR ReservedCheck OR VirtualCheck OR AnyStockCheck) !{{{ just looking for stock for the consider date !To get here we're looking for stock that may be on the shelf when we get !to our consider date. This means we do not need to look beyond 'now'. Pass = 2 !no second chance LookDate = ConsiderDate !don't look past 'now' LookTime = ConsiderTime !.. !}}} ELSIF WorkBackwards !{{{ backwards !We're looking for stock that is avialable now or earlier, but we must also !consider stock that may arrive in the future. For future stock we can !either race it or give up JIT so we can re-start ASAP and wait for it. IF Pass = 2 !Pass 1 failed - so abandon JIT !29/06/11 DCN We can't actually get here any more, see final stock check logic DO JITLostWIPrace ELSE !{{{ look to see if there is any existing WO's we should be using IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Looking for WIP beyond @ @',zvDate,ConsiderDate,zvClock,ConsiderTime) END xpSetLogDepth(+1); LogDepth += 1 LookDate = 0 !look to the end of eternity LookTime = 0 !.. Err# = xpAllocateStock(s,ExistingQ, | !the spec BOR(BOR(Flags,xpSchFlgFindOnly),WSOnly),| !options (explore only) OldStockStatus ) xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. LookDate = ConsiderDate !set initial upcoming stock probe limit date LookTime = ConsiderTime !.. !Grab the results we want for later ExistingQty = s.FoundQty ExistingDate = s.FoundDate ExistingTime = s.FoundTime ExistingStatus = OldStockStatus IF ExistingDate !{{{ determine what to do about the existing stock IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Stock @ available on @ @',| zvMeasure,ExistingQty,zvDate,ExistingDate,zvClock,ExistingTime) END IF WSOnly AND NOT BAND(xpStkFlgOnlyFoundWS,ExistingStatus) IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Scheduling for MINSTOCK, stock will be ignored') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSIF ~xpOlder(ConsiderDate,ConsiderTime,ExistingDate,ExistingTime) !Existing stuff is in our time frame, so just use it !NB: This can happen even when xpStkFlgIsFuture is asserted due to optimistic returns moving the date ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 ELSIF MustBePhysical !OR JustStockCheck 20/07/05 CRJ !09/08/05 DCN See comment in ASAP case above, and "10/08/05 DCN" below !24/06/11 DCN NB: If MustBePhysical is asserted then JustStockCheck is ! not, because that case has already been dealt with. ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !don't allow a second chance when just looking for physical stock ELSIF gxBitInMask(xpUniqStockStrategy,AllocationStrategy) | !doing NOMIX AND ~gxBitInMask(xpBatchingStrategy,AllocationStrategy) | !not doing MAXSTOCK AND umIsSmaller(ExistingQty,RequiredQty) THEN !didn't find enough !Must not abandon JIT for the cases where the stock found is going to be rejected !No second chance when doing NOMIX and all there is is not big enough ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSIF IgnoreLateWIP !27/02/12 DCN Now implied-->AND BAND(ExistingStatus,xpStkFlgIsFuture) !being told to ignore it IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Ignoring late WIP') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSIF xpIsRaceWinByQty(RequiredQty,ExistingQty,WorkBackwards,'Require') !its not worth racing this (it means its a small partial qty) xpWriteLog(TopWS,SummaryWS,-1,'@: Ignoring small future WIP of @ when require @',| zvMaterial,Material,zvMeasure,ExistingQty,zvMeasure,RequiredQty) ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSE !27/02/12 DCN Now implied-->IF BAND(ExistingStatus,xpStkFlgIsFuture) !{{{ there is existing future work IF ScheduleMode = xpScheduleMode:Allocate !Just grabbing stock that should be there already. !In this context we try for the given date but allow it to be delayed. !An 'allocate' schedule is put in the schedule list immediately after a demand by the merging system. !Its purpose is to reserve the stock for the demand it was intended for. If the schedule was late !we must still find and allocate it. ForceUseReason = '' DO ForceUseExisting ELSIF VirtualCheck OR ReservedCheck OR CommittedCheck OR PhysicalCheck !Run with this but be silent in the log !NB: JustStockCheck will also be asserted for this so we won't try to make/buy more. !17/10/18 DCN Can't actually get here as this case has already been dealt with ELSIF ScheduleMode = xpScheduleMode:BackFlush !Not allowed to race WIP JitFailReason = 'WIP race not allowed' DO JITAbandon4Existing !and abandon JIT to make use of existing stock !{{{ check if delaying a top ELSIF IsTop AND NoWIPRace AND BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) !This is the top of a schedule that is going to be late, so just snap to the WIP we found xpWriteLog(TopWS,SummaryWS,-1,'Delaying JIT: @ for @ @ to use WIP of @ @ (must not race WIP)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting DO SetHasLateKit ELSIF IsTop AND NoPlanRace AND BAND(ExistingStatus,xpStkFlgHasReserved) !This is the top of a schedule that is going to be late, so just snap to the Plan we found xpWriteLog(TopWS,SummaryWS,-1,'Delaying JIT: @ for @ @ to use Plan of @ @ (must not race Plan)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting ELSIF IsTop AND NoSchedRace AND BAND(ExistingStatus,xpStkFlgHasVirtual) !This is the top of a schedule that is going to be late, so just snap to the schedule we found xpWriteLog(TopWS,SummaryWS,-1,'Delaying JIT: @ for @@ to use Sched of @@ (must not race Sched)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting !}}} ELSIF (AllocatingTool AND BAND(ExistingStatus,xpStkFlgOnlyFoundReturn)) JitFailReason = 'cannot race tool ret' DO JITAbandon4Existing !and abandon JIT to make use of existing stock ELSIF BAND(ExistingStatus,xpStkFlgForcedWIP) JitFailReason = 'must not race batch' DO JITAbandon4Existing !and abandon JIT to make use of existing stock ELSIF JustStockCheck AND ~LateKitCheck !wait for committed stuff (this is typically some top level demand looking !for what has been done on its behalf) JitFailReason = 'just checking stock' DO JITAbandon4Existing !and abandon JIT to make use of existing stock !{{{ check race allowed ELSIF NoWIPRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) !Not allowed to race WIP JitFailReason = 'WIP race not allowed' DO JITAbandon4Existing !and abandon JIT to make use of existing stock ELSIF NoPlanRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasReserved) !Not allowed to race Plan JitFailReason = 'Plan race not allowed' DO JITAbandon4Existing !and abandon JIT to make use of existing stock ELSIF NoSchedRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasVirtual) !Not allowed to race Sched JitFailReason = 'Sched race not allowed' DO JITAbandon4Existing !and abandon JIT to make use of existing stock !}}} ELSIF ~xpIsRaceCandidate(,LookDate,ExistingDate,ExistingTime,ExistingStatus,Urgent,JitFailReason) !JitFailReason set by xpIsRaceCandidate but not logged 'cos we gave it no Material DO JITAbandon4Existing ELSIF LateKitCheck !This is xpMakeMaterial looking for late kit and we've now determined the existing WIP !is a race candidate. So just report back that there is no stock. xpMakeMaterial will !then come back later to try and make up the shortfall. !NB: JustStockCheck is also asserted for this, so we won't try to make/buy more IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Kit check: Ignoring WIP that is a race candidate') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSE !We'e gonna race this !NB: When racing JIT the race must either succeed in meeting our date or nothing. ! There is no tolerance threshold, we just abandon the JIT. !{{{ log it IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Existing @ @ too late for @ @. Trying to beat it. Flags=@',| zvDate,ExistingDate,zvClock,ExistingTime,zvDate,LookDate,zvClock,LookTime,zvHex,ExistingStatus) END !}}} END !}}} END !}}} ELSE !Nothing to beat, so don't do a second attempt Pass = 2 END !}}} END !}}} ELSE !{{{ forwards !We're looking for stock that is available now or earlier, but we must also !consider stock that may arrive in the future. For future stock we can !either race it or wait for it. IF Pass = 2 !pass 1 failed, so look again using the existing stock date ForceUseReason = '' DO ForceUseExisting ELSE !{{{ look for existing stock IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Looking for WIP beyond @ @',zvDate,ConsiderDate,zvClock,ConsiderTime) END xpSetLogDepth(+1); LogDepth += 1 LookDate = 0 !look to the end of eternity LookTime = 0 !.. Err# = xpAllocateStock(s,ExistingQ, | !the spec BOR(BOR(Flags,xpSchFlgFindOnly),WSOnly),| !options (explore only) OldStockStatus ) xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. LookDate = ConsiderDate !set initial upcoming stock probe limit date LookTime = ConsiderTime !.. !Grab the results we want for later ExistingQty = s.FoundQty ExistingDate = s.FoundDate ExistingTime = s.FoundTime ExistingStatus = OldStockStatus IF ExistingDate !{{{ determine what to do about the existing stock IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Stock @ available on @ @. Flags=@',| zvMeasure,ExistingQty,zvDate,ExistingDate,zvClock,ExistingTime,zvHex,ExistingStatus) END IF WSOnly AND NOT BAND(xpStkFlgOnlyFoundWS,ExistingStatus) IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Scheduling for MINSTOCK, stock will be ignored') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSIF ~xpOlder(ConsiderDate,ConsiderTime,ExistingDate,ExistingTime) !Existing stuff is in our time frame, so just use it !NB: This can happen even when xpStkFlgIsFuture is asserted due to optimistic returns moving the date ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !we're not racing ELSIF MustBePhysical !OR JustStockCheck 20/07/05 CRJ !09/08/05 DCN The merging system relies on *not* trying to beat late WIP, ! demands must wait else top level SOL's re-create demands ! which causes the previous pass to be removed, the new ! stuff then gets merged, which becomes late which causes a ! a re-create which gets merged, which... ad infinitum ! JustStockCheck is used to stop this, but you've removed ! it from the guard, so... "See 10/08/05 DCN" below !24/06/11 DCN NB: If MustBePhysical is asserted then JustStockCheck is ! not because that case has already been dealt with. ForceUseReason = 'must be physical' DO ForceUseExisting ELSIF IgnoreLateWIP !27/02/12 DCN Now implied-->AND BAND(ExistingStatus,xpStkFlgIsFuture) !being told to ignore it IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Ignoring late WIP') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSE !27/02/12 DCN Now implied-->IF BAND(ExistingStatus,xpStkFlgIsFuture) !{{{ there is existing future work IF ScheduleMode = xpScheduleMode:Allocate !Just grabbing stock that should be there already. !In this context we try for the given date but allow it to be delayed. !An 'allocate' schedule is put in the schedule list immediately after a demand by the merging system. !Its purpose is to reserve the stock for the demand it was intended for. If the schedule was late !we must still find and allocate it. ForceUseReason = '' DO ForceUseExisting ELSIF VirtualCheck OR ReservedCheck OR CommittedCheck OR PhysicalCheck !Run with it, but be silent in the log !NB: JustStockCheck is also asserted for this, so we won't try to make/buy more !17/10/18 DCN Can't actually get here as this case has already been dealt with ELSIF ScheduleMode = xpScheduleMode:BackFlush !Being told not to race WIP ForceUseReason = 'mode is backflush' DO ForceUseExisting !{{{ check if delaying a top ELSIF IsTop AND NoWIPRace AND BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) !This is the top of a schedule that is going to be late, so just snap to the WIP we found xpWriteLog(TopWS,SummaryWS,-1,'Delaying ASAP: @ for @ @ to use WIP of @ @ (must not race WIP)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting DO SetHasLateKit ELSIF IsTop AND NoPlanRace AND BAND(ExistingStatus,xpStkFlgHasReserved) !This is the top of a schedule that is going to be late, so just snap to the Plan we found xpWriteLog(TopWS,SummaryWS,1,'Delaying ASAP: @ for @ @ to use Plan of @ @(must not race Plan)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting ELSIF IsTop AND NoSchedRace AND BAND(ExistingStatus,xpStkFlgHasVirtual) !This is the top of a schedule that is going to be late, so just snap to the schedule we found xpWriteLog(TopWS,SummaryWS,1,'Delaying ASAP: @ for @@ to use Sched of @@(must not race Sched)',| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) ForceUseReason = '' DO ForceUseExisting !}}} ELSIF AllocatingTool AND BAND(ExistingStatus,xpStkFlgOnlyFoundReturn) ForceUseReason = 'must not race tool return' DO ForceUseExisting ELSIF BAND(ExistingStatus,xpStkFlgForcedWIP) !We're being told we must wait for this ForceUseReason = 'must not race batch' DO ForceUseExisting DO SetHasLateKit ELSIF JustStockCheck AND ~LateKitCheck !wait for 'committed' stuff (this is typically some top level demand looking !for what has been done on its behalf) !{{{ log it IF gxBitInMask(xpStockOnlyStrategy,AllocationStrategy) !Only an alert if got no choice xpWriteLog(TopWS,SummaryWS,-1,'Stock check: @ waiting for existing WIP @ @',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) ELSE xpWriteLog(TopWS,SummaryWS,1,'Stock check: @ waiting for existing WIP @ @',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} ForceUseReason = '' DO ForceUseExisting !{{{ check racing allowed ELSIF NoWIPRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) !Being told not to race WIP ForceUseReason = 'must not race WIP' DO ForceUseExisting DO SetHasLateKit ELSIF NoPlanRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasReserved) !Being told not to race Plan ForceUseReason = 'must not race plan' DO ForceUseExisting ELSIF NoSchedRace AND ~LateKitCheck AND BAND(ExistingStatus,xpStkFlgHasVirtual) !Being told not to race a schedule ForceUseReason = 'must not race sched' DO ForceUseExisting !}}} ELSIF ~xpIsRaceCandidate(Material,LookDate,ExistingDate,ExistingTime,ExistingStatus,Urgent) ForceUseReason = '' !above call has already logged the reason DO ForceUseExisting ELSIF LateKitCheck !This is xpMakeMaterial looking for late kit and we've now determined the existing WIP !is a race candidate. So just report back that there is no stock. xpMakeMaterial will !then come back later to try and make up the shortfall. !NB: JustStockCheck is also asserted for this, so we won't try to make/buy more IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Kit check: Ignoring WIP that is a race candidate') END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance ELSE !We're gonna race this !{{{ log it IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Existing @ @ too late for @ @. Trying to beat it. Flags=@',| zvDate,ExistingDate,zvClock,ExistingTime,zvDate,LookDate,zvClock,LookTime,| zvHex,ExistingStatus) END !}}} END !}}} END !}}} ELSE !Nothing to beat, so don't do a second attempt Pass = 2 END !}}} END !}}} END !{{{ raise end limit to meet WIP IF xpOlder(EndLimit,gxEndOfDay,ExistingDate,ExistingTime) xpWriteLog(TopWS,SummaryWS,-1,'Raising end limit (@) to meet existing WIP (@ @)',| zvDate,EndLimit,zvDate,ExistingDate,zvClock,ExistingTime) !Found stock beyond the end limit, raise the end limit to meet it Err# = xpLoadWS(SummaryWS,5010900); IF Err# THEN DO ProcedureExit. xpwC:EndLimitDate = ExistingDate+1 !paranoia, +1 JIC Err# = xpPutWS(); IF Err# THEN DO ProcedureExit. END !}}} IF ~DoMakeBuySubCon# !This means JIT has been abandonned ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Trying to allocate @[of @][x @][x @] for ^@ @',| zvMeasure,RequiredQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,zvMeasure,RequiredHeight,| zvDate,LookDate,zvClock,LookTime) END Err# = xpAllocateStock(s,, | !the spec BOR(Flags,WSOnly),| !options (not explore mode) OldStockStatus ) IF Err# THEN DO ProcedureExit. !Grab results we want TotalQty = s.FoundQty TotalCost = s.FoundVal BatchesUsed = s.BatchesUsed NeedQty = umSubtract(RequiredQty,TotalQty) IF xpMaxTraceDetail:Value > 2 IF umIsPositive(TotalQty) xpWriteLog(TopWS,SummaryWS,3,'@[of @][x @] will be available on ^@ @ in @ batches',| zvMeasure,TotalQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,LocalStockDate,zvClock,LocalStockTime,zvInt,BatchesUsed) IF umIsPositive(NeedQty) xpWriteLog(TopWS,SummaryWS,3,'Shortfall is @, Status is ^@',zvMeasure,NeedQty,zvInt,OldStockStatus) END ELSE xpWriteLog(TopWS,SummaryWS,3,'None available for ^@ @',zvDate,LookDate,zvClock,LookTime) END END END !}}} ELSE !{{{ skipping stock check LocalStockDate = 0 !for final stock check LocalStockTime = 0 !.. IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Skipping initial stock check - ' & NoStockCheckReason) END ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance if not checking stock !}}} END !{{{ do the make/buy/subcon? IF DoMakeBuySubCon# AND umIsZero(NeedQty) NoMakeBuyReason = 'got stock' DoMakeBuySubCon# = FALSE END IF DoMakeBuySubCon# AND umIsNegative(NeedQty) NoMakeBuyReason = 'got stock+' DoMakeBuySubCon# = FALSE END IF DoMakeBuySubCon# AND JustStockCheck NoMakeBuyReason = 'stock check only' DoMakeBuySubCon# = FALSE END IF DoMakeBuySubCon# AND Material AND gxBitInMask(xpUniqStockStrategy,AllocationStrategy) IF MustBePhysical OR gxBitInMask(xpBatchingStrategy,AllocationStrategy) IF umIsPositive(TotalQty) NoMakeBuyReason = 'using stock only' DoMakeBuySubCon# = FALSE !force use of just this qty or nothing END ELSE IF umIsPositive(TotalQty) AND umIsSmaller(TotalQty,RequiredQty) !We're doing NOMIX and there isn't a batch big enough available. !We must treat this the same as if nothing was available. !In particular, we are *NOT* in a WIP race condition when this happens. !So set the state to as if we found nothing. NeedQty = RequiredQty !need the lot ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !no second chance IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Doing NOMIX, so ignoring partial stock of @ for @',| zvMeasure,TotalQty,zvMeasure,NeedQty) END END END END IF Material = 0 DoMakeBuySubCon# = TRUE END IF DoMakeBuySubCon# AND umIsZero(RequiredQty) NoMakeBuyReason = 'no quantity' DoMakeBuySubCon# = FALSE END ! ! If we're doing MustBePhysical and have recursed (hence Depth>1) into a material that is ! a module boundary we can abort at this level with anything we've found so far. If we're ! not a module boundary we need to recurse so that physical stuff can be found further down ! the tree (i.e. we must recurse through in-line steps). ! IF DoMakeBuySubCon# AND IsModule AND Depth > 1 AND MustBePhysical NoMakeBuyReason = 'physical boundary' DoMakeBuySubCon# = FALSE END !03/02/12 DCN I don't think this is correct 'cos on pass N+1 this won't be at depth ! Its not correct, the boundary is set by the MustBePhysical option. ! When thats not set we do want to allow recursion into module kit. !IF DoMakeBuySubCon# AND IsModule AND Depth > 1 AND gxBitInMask(xpBatchingStrategy,AllocationStrategy) ! NoMakeBuyReason = 'batching boundary' ! DoMakeBuySubCon# = FALSE !END !}}} IF DoMakeBuySubCon# !{{{ get make/buy/subcon options !NB: We're checking the 'make' material here which may be diff to the 'allocate' material. !Get the options available (NB: Buy/CallIn are mutually exclusive, as are Subcon/Make) CanMake = FALSE CanBuy = FALSE CanSubCon = FALSE CanCallIn = FALSE CanMO = FALSE; Free(MOList) IF MethodType = mcMethodIssueItem OR MethodType = mcMethodBookInItem OR MethodType = mcMethodUnMakeItem !Method type is implying what we can do CanMake = TRUE ELSIF IsContainer !Containers can only be made CanMake = TRUE ELSIF BuyPriority AND MakePriority !{{{ can buy or make as we like CanCallIn = mcIsFreeIssue(MakeMat,StepMethod) IF ~CanCallIn CanBuy = mcIsSupplied(MakeMat,StepMethod,TRUE) END CanSubCon = mcIsSubContractable(MakeMat,StepMethod,FALSE) IF ~CanSubCon CanMake = mcIsManufactured(MakeMat,StepMethod,FALSE) END CanMO = mcIsMultipleOutput(MakeMat,MOMethod,MOContext,MOList) !}}} ELSIF BuyPriority !{{{ got to buy it if can CanCallIn = mcIsFreeIssue(MakeMat,StepMethod) IF ~CanCallIn CanBuy = mcIsSupplied(MakeMat,StepMethod,TRUE) IF ~CanBuy CanSubCon = mcIsSubContractable(MakeMat,StepMethod,TRUE) IF ~CanSubCon CanMake = mcIsManufactured(MakeMat,StepMethod,TRUE) IF ~CanMake CanMO = mcIsMultipleOutput(MakeMat,MOMethod,MOContext,MOList) END END END END !}}} ELSIF MakePriority !{{{ got to make it if can CanSubCon = mcIsSubContractable(MakeMat,StepMethod,FALSE) IF ~CanSubCon CanMake = mcIsManufactured(MakeMat,StepMethod,FALSE) IF ~CanMake CanMO = mcIsMultipleOutput(MakeMat,MOMethod,MOContext,MOList) IF ~CanMO CanCallIn = mcIsFreeIssue(MakeMat,StepMethod) IF ~CanCallIn CanBuy = mcIsSupplied(MakeMat,StepMethod,FALSE) END END END END !}}} ELSE !{{{ check if being forced IF BuyForced CanBuy = TRUE ELSIF MakeForced CanMake = TRUE ELSE !Can't get here END !}}} END !See if allowed to do the options available IF CanSubCon OR CanBuy OR CanMake OR CanCallIn OR CanMO !Got some options IF MustBePhysical !Only allowed to make or sub-con at top, turn the rest off CanBuy = FALSE CanCallIn = FALSE IF Depth > 1 AND ScheduleMode <> xpScheduleMode:BackFlush THEN CanSubCon = FALSE. !any sub-con not allowed IF Depth > 1 AND ScheduleMode = xpScheduleMode:BackFlush AND mcGetIsModule(MakeMat) THEN CanSubCon = FALSE. !in-line allowed when back-flushing END !Any options left? IF CanBuy OR CanCallIn OR CanMake OR CanSubCon OR CanMO !Yes, carry on ELSE !No, forget it DoMakeBuySubCon# = FALSE NoMakeBuyReason = 'physical boundary' END ELSIF xpAllowNoMethod:Value AND NOT mcIsReturnOutput(MakeMat) AND NOT mcIsService(MakeMat,TRUE) !{{{ work out how to do it IF BuyPriority AND MakePriority IF xpBuyBeforeMake:Value !Buy is preferred CanBuy = TRUE !it'll buy it anonymously ELSE !Make is preferred CanMake = TRUE !it'll use the default method END ELSIF BuyPriority !Buy is preferred CanBuy = TRUE !it'll buy it anonymously ELSIF MakePriority !Make is preferred CanMake = TRUE !it'll use the default method ELSE !Can't get here END IF CanBuy xpWriteLog(TopWS,SummaryWS,-1,'@ has no explicit method, buying it by default',zvMaterial,MakeMat) ELSIF CanMake xpWriteLog(TopWS,SummaryWS,-1,'@ has no explicit method, making it by default',zvMaterial,MakeMat) ELSE xpWriteLog(TopWS,SummaryWS,-1,'@ has no explicit method, inventing it by default',zvMaterial,MakeMat) END !}}} ELSIF mcIsService(MakeMat,TRUE) !{{{ attempt to use a phantom service IF MakeMat xpWriteLog(TopWS,SummaryWS,-1,'@ is a phantom service, it cannot be a PART',zvMaterial,MakeMat) ELSIF StepMethod xpWriteLog(TopWS,SummaryWS,-1,'@ is a phantom service, it cannot be a PART',zvMatStep,StepMethod) ELSE xpWriteLog(TopWS,SummaryWS,-1,'*******Nothing to schedule!!') END NoMethod# = TRUE DoMakeBuySubCon# = FALSE NoMakeBuyReason = 'phantom service!' !}}} ELSE !{{{ no method IF MakeMat xpWriteLog(TopWS,SummaryWS,-1,'Material @ cannot be made, bought or subcon''ed',zvMaterial,MakeMat) ELSIF StepMethod xpWriteLog(TopWS,SummaryWS,-1,'Step @ cannot be made, bought or subcontracted',zvMatStep,StepMethod) ELSE xpWriteLog(TopWS,SummaryWS,-1,'*******Nothing to schedule!!') END NoMethod# = TRUE DoMakeBuySubCon# = FALSE NoMakeBuyReason = 'no method!' !}}} END !}}} END IF DoMakeBuySubCon# !{{{ make/buy/subcon as allowed !NB: When there are choices and we're allowed to explore them, the buy choice is ! tried first ('cos that's relatively easy), the result recorded, then the ! make choice is explored. If the make choice is best, its kept, otherwise ! its dumped and the buy choice done again. IF xpDebugDetail:Value > 2 MONum = Records(MOList) xpWriteLog(TopWS,SummaryWS,3,'CanBuy:@, CallIn:@, SubCon:@, Make:@, MO#s:^@',| zvFlag,CanBuy,zvFlag,CanCallIn,zvFlag,CanSubCon,zvFlag,CanMake,zvInt,MONum) END !{{{ calculate order qty !The order quantity here is misnamed, its really a max qty !NB: Not using the make material here, 'cos its the allocate material that has the order constraints !The NeedOrderQty calculated here is what must be made/bought to achieve what is required with all !the batch constraints applied. That must be the greater of (Required+CyclesExtra) or MinUseBatch. IF umIsZero(OrderQty) !No order qty, get just what we need xpSetOrderQty(SummaryWS,NeedOrderQty,NeedQty) ELSE NeedOrderQty = mcNormalise(Material,OrderQty) xpSetOrderQty(SummaryWS,ReqOrderQty,mcNormalise(Material,RequiredQty)) IF umIsBigger(NeedOrderQty,ReqOrderQty) !We've got a stock shortage and an order qty that is more than the required, !so do the order qty minus what we've got (i.e. bring the total level up to order qty). !NB: The re-order qty will get applied to this in due course too. NeedOrderQty = umSubtract(NeedOrderQty,mcNormalise(Material,TotalQty)) !NB: We've got a shortage, thus TotalQty is < OrderQty xpWriteLog(TopWS,SummaryWS,-1,'Not enough stock (@), raising demand from @ to @',| zvMaterial,Material,zvMeasure,RequiredQty,zvMeasure,OrderQty) ELSE !Required is more than the order, so get what we need (else we'll get a schedule failure) xpSetOrderQty(SummaryWS,NeedOrderQty,NeedQty) END END !NB: NeedOrderQty now takes into account the CyclesExtra allowance when applicable. ! We need to make/buy too much such that the stock allocator (which adds it) ! succeeds on what we make/buy. !}}} Options# = 0 IF CanCallIn OR CanBuy THEN Options# += 1. IF CanSubCon OR CanMake THEN Options# += 1. IF CanMO THEN Options# += 1. IF CanMO AND Records(MOList) > 1 IF xpAllowMultipleOutputMethods:Value Options# += Records(MOList)-1 ELSE MONum = Records(MOList) xpWriteLog(TopWS,SummaryWS,-1,'@ has @ side-effect methods. Only considering the first',zvMaterial,MakeMat,zvInt,MONum) END END IF ~WorkBackwards !Reset the consider date in case an ASAP WIP race got in the way ConsiderDate = InitialDate ConsiderTime = InitialTime END IF Options# > 1 !Got choices to explore xpSetLogDepth(+1,'Explore buy/make options'); LogDepth += 1 !{{{ explore buy and make options JITabandonned = FALSE LastMethod = Method:None !not done anything yet Best:Method = Method:None !no result yet !Try buy first IF CanCallIn THEN DO CallInSome; DO PickBest ELSIF CanBuy THEN DO BuySome ; DO PickBest. !Now try making it IF CanSubCon OR CanMake AND Best:Method THEN DO DumpSchedule. !dump the last thing we did IF CanSubCon THEN DO SubConSome; DO PickBest ELSIF CanMake THEN DO MakeSome ; DO PickBest. !Now try MO'ing it IF CanMO AND Best:Method THEN DO DumpSchedule. !dump the last thing we did IF CanMO THEN Get(MOList,1); DO MOSome; DO PickBest. !See if there are more MO options IF CanMO AND Records(MOList) > 1 IF xpAllowMultipleOutputMethods:Value !Iterate the rest LOOP MONum = 2 TO Records(MOList) IF Best:Method THEN DO DumpSchedule. !dump the last thing we did Get(MOList,MONum) DO MOSome DO PickBest END ELSE !Not being allowed to consider the others xpWriteLog(,,-1,'Ignoring excess container methods') END END !Now re-do the best IF LastMethod AND LastMethod <> Best:Method AND ~JITabandonned !Last one was not best, chuck it and go again on the best DO DumpSchedule CASE Best:Method OF Method:None OROF Method:Invent !Eh? !do nothing OF Method:Buy OROF Method:AnonBuy OROF Method:FreeIss IF CanCallIn THEN DO CallInSome !call-in is best ELSIF CanBuy THEN DO BuySome. !buy is best OF Method:Make OROF Method:SubCon IF CanSubCon THEN DO SubConSome !subcon is best ELSIF CanMake THEN DO MakeSome. !make is best ELSE !MO IF CanMO THEN Get(MOList,Best:Method-Method:MO); DO MOSome. !MO is best END END IF IsTop AND ~JITabandonned !{{{ log what we did and why CASE Best:Method OF Method:AnonBuy Option" = 'anon buy' OF Method:Buy Option" = 'buy' OF Method:Make Option" = 'make' OF Method:FreeIss Option" = 'free issue' OF Method:SubCon Option" = 'sub-con' OF Method:Invent Option" = 'invent' OF Method:None Option" = 'nothing' ELSE Option" = 'MO#' & (Best:Method-Method:MO+1) END xpWriteLog(,,-1,'@ has @ acquisition options, @ chosen because @',| zvMaterial,MakeMat,zvInt,Options#,zvStr,Option",zvStr,Best:Reason) !}}} END !}}} xpSetLogDepth(-1); LogDepth -= 1 ELSE !No choice, just do it !NB: Only one (or none) of these choices will be set IF CanCallIn THEN DO CallInSome ELSIF CanBuy THEN DO BuySome ELSIF CanSubCon THEN DO SubConSome ELSIF CanMake THEN DO MakeSome ELSIF CanMO THEN Get(MOList,1); DO MOSome ELSE DO InventSome. END !Make solution stick DO UpdateDateLimits !}}} IF DoStockCheck# !{{{ dump the stock we allocated earlier !NB: We must do this after we make the shortfall, otherwise dispatch ! methods might pinch not packed stock and turn it into packed. We ! are then in an overall shortfall situation again! Err# = xpDeAllocateStock(SummaryWS) IF Err# THEN DO ProcedureExit. !}}} END ELSE IF DoStockCheck# AND ReleaseStock !{{{ dump the stock we allocated earlier IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Releasing tentatively allocated stock') END Err# = xpDeAllocateStock(SummaryWS) IF Err# THEN DO ProcedureExit. !}}} END IF ScheduleMode = xpScheduleMode:BackFlush AND NoMakeBuyReason = 'physical boundary' xpWriteLog(TopWS,SummaryWS,-1,'No physical stock of @ (required by back-flush schedule)',zvMaterial,MakeMat) ELSIF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Skipping make/buy/subcon - ' & NoMakeBuyReason) END END !{{{ do final stock check? ! ! If we didn't do the first stock check we were trying to build up ! the stock, so if we failed we DO NOT want to allocate from stock. ! IF DoFinalStockCheck# AND NoMethod# AND NOT DoStockCheck# NoStockCheckReason = 'no method' DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND ~umIsPositive(TotalQty) AND NOT DoStockCheck# AND Method = xpMadeRecord NoStockCheckReason = 'nothing made' DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND ~umIsPositive(TotalQty) AND NOT DoStockCheck# AND Method = xpInventedRecord NoStockCheckReason = 'nothing invented' DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND ~umIsPositive(TotalQty) AND NOT DoStockCheck# AND Method = xpSubContractRecord NoStockCheckReason = 'nothing sub-con''d' DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND ~umIsPositive(TotalQty) AND NOT DoStockCheck# AND Method = xpBuyRecord NoStockCheckReason = 'nothing bought' DoFinalStockCheck# = FALSE END IF IgnoreStock !Always look for what we just did when ignoring stock DoFinalStockCheck# = TRUE END IF DoFinalStockCheck# AND umIsZero(RequiredQty) NoStockCheckReason = 'no quantity' DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND NOT DoMakeBuySubCon# AND DoStockCheck# IF umIsZero(TotalQty) NoStockCheckReason = 'none available' ELSE NoStockCheckReason = 'already allocated' END DoFinalStockCheck# = FALSE END IF DoFinalStockCheck# AND DoMakeBuySubCon# AND Method = xpIgnoredRecord NoStockCheckReason = 'action ignored' DoFinalStockCheck# = FALSE END !NB: All above tests pass when doing pass 1. ! This is important to ensure that pass 2 is attempted if pass 1 fails. !}}} IF DoFinalStockCheck# !{{{ do final stock check IF Allocatable AND ~IgnoreStock SelfOnly = 0 ELSE !~Allocatable OR IgnoreStock SelfOnly = xpSchFlgSelfOnly END Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. EndLimit = gxLatest(EndLimit,LocalStockDate) !26/01/00 CRJ !Set how far ahead to look for stock LookDate = EndLimit LookTime = gxEndOfDay Err# = xpAllocateStock(s,, | !the spec BOR(BOR(Flags,WSOnly),SelfOnly),| !options (not explore mode) NewStockStatus ) IF Err# THEN DO ProcedureExit. !Grab results we want TotalQty = s.FoundQty TotalCost = s.FoundVal BatchesUsed = s.BatchesUsed IF xpMaxTraceDetail:Value > 1 IF umIsZero(TotalQty) xpWriteLog(TopWS,SummaryWS,2,'Found none in stock') ELSE xpWriteLog(TopWS,SummaryWS,2,'Found stock of @[of @][x @] on @ @ in @ batches',| zvMeasure,TotalQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,LocalStockDate,zvClock,LocalStockTime,zvInt,BatchesUsed ) END END IF Pass = 1 !{{{ we're trying to beat existing WIP, see if we did !{{{ check what happenned wrt qty IF WorkBackwards !If we raced we know what we're racing is a race win by qty ('cos we called xpIsRaceWinByQty earlier) !So set to check the date in a mo RaceWon = FALSE ELSE RaceWon = xpIsRaceWinByQty(TotalQty,ExistingQty,WorkBackwards,'Achieved') !if what we made is later than existing then start again to pick up the existing !this is the context where existing only partially met the requirement, but our attempt for the lot !took longer, so its worth starting again to pick up what is there already and then only have to !make the shortfall. END IF RaceWon !NB: Can't get here for JIT IF xpOlder(LocalStockDate,LocalStockTime,ExistingDate,ExistingTime) !we beat the existing, so treat as a conventional race win ThresholdName = 'partial WIP' !for log message ELSE !we're later, so re-do to pick up the existing DO DumpSchedule ForceUseReason = 'late WIP race' DO UseExisting CYCLE END END !}}} !{{{ check what happenned wrt date IF ~RaceWon !We didn't win on qty, see if we did on date RaceWon = xpIsRaceWinByDate(LocalStockDate,LocalStockTime, | ExistingDate,ExistingTime,ExistingStatus,| ThresholdName,Urgent, | WorkBackwards,ConsiderDate,ConsiderTime ) END !}}} IF RaceWon !{{{ check if we should use this race win !If we won against late merged demands, note the losers but do not use !it. Merged demands are only visible when its expected they get used (see the !allocate/release stuff in the merge logic), so if we can see it and beat it !it means the merging system needs to make this one earlier. This is done by !noting the desired date in the loser which is visible to the merging system. !{{{ mark the actualisers of the stock we raced as race losers !This is used as a hint to the merging system to try and bring the demand forward. !If it can, then on the next pass it will not be raced. The issue this is trying !to resolve is discussed at length in [59270]. But in essence the issue is that !a race win (partially) orphans some demand that may be consuming resources lower !down in its structure that could be used in another (later) schedule. Such later !schedules become compromised as a result (e.g. stock is not available that could !prevent a later unnecessary buy/make). !The stock instances we raced were noted in ExistingQ during the initial stock probe. !Iterate those instances and note the earliest race win date/time against them. LOOP WHILE Records(ExistingQ) Get(ExistingQ,1) Delete(ExistingQ) IF xpOlder(LocalStockDate,LocalStockTime,ExistingQ.RecNo3,ExistingQ.RecNo4) !This is an instance that we beat CASE ExistingQ.RecNo OF xpwC::Id IF ~xpLoadWS(ExistingQ.RecNo2,01161219,TRUE) !We've now got the actualiser for the stock we raced (this may be a container for MO). !CreateDate/Time in there is what it was aiming at. !DesiredDate/Time in there is the current earliest race win. !LocalStockDate/Time is what we achieved here. IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Schedule @ for @@ is a race loser to @@.',| zvWS,ExistingQ.RecNo2,| zvDate,ExistingQ.RecNo3,zvClock,ExistingQ.RecNo4,| zvDate,LocalStockDate,zvClock,LocalStockTime) END xpSetEarliest(xpwC:DesiredDate,xpwC:DesiredTime,LocalStockDate,LocalStockTime) IF xpwC:OwnerFile = xpDemand::Id !We raced a merged demand and won, but we must not use such a case, instead treat !as a race lost and let the merging system do better next time. !NB: We can only see merged demands that we are expected to use, so if its late ! something significant to us has changed, requiring another merge pass. ! 12/10/23 DCN Not true if some higher priority demand over-produced, we'll ! see that excess. How to tell? RaceWon = -1 !note we're not allowed to use it xpwC:RaceWithdraws += 1 IF xpMaxTraceDetail:Value > 2 xpSetLogDepth(+1) xpWriteLog(TopWS,SummaryWS,3,'Schedule is a merged demand. New desired date is @@.',| zvDate,xpwC:DesiredDate,zvClock,xpwC:DesiredTime) xpSetLogDepth(-1) END ELSE xpwC:RaceLoses += 1 END Err# = xpPutWS() IF Err# THEN DO ProcedureExit. END OF msi::Id IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Batch @ for @@ is a race loser to @@.',| zvMatBatch,ExistingQ.RecNo2,| zvDate,ExistingQ.RecNo3,zvClock,ExistingQ.RecNo4,| zvDate,LocalStockDate,zvClock,LocalStockTime) END END END END !}}} !RaceWon will be set to -1 in the above if we should not use it. This is used !as a hint when generating the log message. !}}} END CASE RaceWon OF TRUE !{{{ we won a WIP race and are allowed to use it !{{{ mark our actualisers as race winners !We note race winners for stats purposes so they can be used as a metric !by the merging system. The actualiser may be directly below us or if its !an MO there will be a container-need that refers to it via its instance. xpStartWSChildScan(SummaryWS,scanKey) LOOP xpNextWSChildScan(SummaryWS,scanKey) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpBuyRecord OROF xpMadeRecord OROF xpSubContractRecord OROF xpInventedRecord xpwC:RaceWins += 1 Err# = xpPutWS() IF Err# THEN DO ProcedureExit. OF xpContainerNeedRecord IF xpwC:InstanceFile = xpwC::Id !paranoia it can't be anything else IF ~xpLoadWS(xpwc:InstanceRec,01150313,TRUE) !We've now got the container make record, !if its a sibling of us, its a race winner. IF xpwC:GrandParentHandle = SummaryWS xpwC:RaceWins += 1 Err# = xpPutWS() IF Err# THEN DO ProcedureExit. END END END ELSE !Nothing else is an actualiser END END !}}} ExistingStatus = BOR(ExistingStatus,xpStkFlgRaceWon) !tell caller what happenned !{{{ log it IF BAND(ExistingStatus,xpStkFlgHasReserved+xpStkFlgHasCommitted+xpStkFlgHasLateKit) !We raced reserved/committed stuff - make sure the user is aware of that - generate an alert AlertLevel# = -1 ELSE !We raced virtual stuff - be less intrusive about that AlertLevel# = -2 END xpWriteLog(TopWS,SummaryWS,AlertLevel#,'@ ignoring @ due @ @ (new plan is @ @)',| zvMaterial,Material,zvStr,ThresholdName,| zvDate,ExistingDate,zvClock,ExistingTime,| zvDate,LocalStockDate,zvClock,LocalStockTime) !}}} !}}} OF -1 Pass = 2 !{{{ we won a WIP race but are not allowed to use it !{{{ log it xpWriteLog(TopWS,SummaryWS,-2,'@ cannot use new plan for @@ (must use merged plan of @@)',| zvMaterial,Material,| zvDate,ConsiderDate,zvClock,ConsiderTime,| zvDate,ExistingDate,zvClock,ExistingTime) !}}} DO DumpSchedule IF WorkBackwards !Give up when doing JIT JitFailReason = 'cannot use race win' DO AbandonJIT ELSE !Have another go when doing ASAP to pick up existing stuff !{{{ log it xpWriteLog(TopWS,SummaryWS,-2,'ASAP: @ waiting for merged WIP due @@ (new attempt achieved @@)',| zvMaterial,Material, | zvDate,ExistingDate ,zvClock,ExistingTime, | zvDate,LocalStockDate,zvClock,LocalStockTime ) !}}} CYCLE END !}}} ELSE Pass = 2 !{{{ we lost a WIP race !{{{ log it IF LocalStockDate xpWriteLog(TopWS,SummaryWS,-2,'@ new plan for @@ no better than existing plan (@@) (local stock date/time @@)',| zvMaterial,Material,| zvDate,ConsiderDate,zvClock,ConsiderTime,| zvDate,ExistingDate,zvClock,ExistingTime,| zvDate,LocalStockDate,zvClock,LocalStockTime) ELSE xpWriteLog(TopWS,SummaryWS,-2,'@ new plan for @ @ failed',zvMaterial,Material,| zvDate,ConsiderDate,zvClock,ConsiderTime) END !}}} DO DumpSchedule IF WorkBackwards !Give up when doing JIT DO JITLostWIPrace ELSE !Have another go when doing ASAP to pick up existing stuff !{{{ log it xpWriteLog(TopWS,SummaryWS,-1,'ASAP: @ using WIP due @ @ (new attempt achieved @ @)',| zvMaterial,Material, | zvDate,ExistingDate ,zvClock,ExistingTime, | zvDate,LocalStockDate,zvClock,LocalStockTime ) !}}} ForceUseReason = 'lost WIP race' DO UseExisting CYCLE END !}}} END !}}} END IF ReleaseStock IF xpMaxTraceDetail:Value > 1 IF umIsPositive(TotalQty) xpWriteLog(TopWS,SummaryWS,2,'Stock of @[of @][x @] is available on @ @',| zvMeasure,TotalQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,LocalStockDate,zvClock,LocalStockTime) END END Err# = xpDeAllocateStock(SummaryWS) !remove the moves to top level ad-hoc IF Err# THEN DO ProcedureExit. !owners without changing the totalling vars END !}}} ELSE IF xpMaxTraceDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Skipping final stock check - ' & NoStockCheckReason) END END BREAK END !{{{ update the summary and ret params !Set what we wanted EndDate = ConsiderDate EndTime = ConsiderTime !adjust for what we actually achieved xpSetLatest(EndDate,EndTime,LocalStockDate,LocalStockTime) ExpiresOn = LocalExpiresOn !{{{ set final method IF umIsZero(TotalQty) FinalMethod = 0 ELSIF Method = xpAnonBuyRecord FinalMethod = xpBuyRecord !note what it really was ELSE FinalMethod = Method END !}}} IF SetRaiseDate !this is the true WO raise date, the one in the make record is for the last step LocalRaiseDate = RaiseDate LocalRaiseTime = RaiseTime !now clear it for the caller (see discussion in xpMakeMaterial ...description) RaiseDate = 0 RaiseTime = 0 ELSE LocalRaiseDate = 0 LocalRaiseTime = 0 END Err# = xpFinishSummary(SummaryWS,FinalMethod,TRUE, | !TRUE==update total cost if needed TotalQty,TotalCost, | StartDate,StartTime, | EndDate,EndTime, | LocalRaiseDate,LocalRaiseTime) IF Err# THEN DO ProcedureExit. !}}} DO ProcedureExit !}}} !{{{ ROUTINEs !{{{ ProcedureEntry ProcedureEntry ROUTINE MonitorOpen = FALSE RecursionDepth += 1 PhysicalCheck = FALSE CommittedCheck = FALSE ReservedCheck = FALSE VirtualCheck = FALSE AnyStockCheck = FALSE LateKitCheck = FALSE !Decode the Flags IF BAND(xpSchFlgJustStockCheck,Flags) THEN JustStockCheck = TRUE ELSE JustStockCheck = FALSE. IF BAND(xpSchFlgAllocatingTool,Flags) THEN AllocatingTool = TRUE ELSE AllocatingTool = FALSE. IF BAND(xpSchFlgIsContainer ,Flags) THEN IsContainer = TRUE ELSE IsContainer = FALSE. IF BAND(xpSchFlgNoAllocate ,Flags) THEN Allocatable = FALSE ELSE Allocatable = TRUE. IF BAND(xpSchFlgReleaseStock ,Flags) THEN ReleaseStock = TRUE ELSE ReleaseStock = FALSE. IF BAND(xpSchFlgDoneCheck,Flags) JustStockCheck = TRUE PhysicalCheck = TRUE ELSIF BAND(xpSchFlgCommittedCheck,Flags) JustStockCheck = TRUE CommittedCheck = TRUE ELSIF BAND(xpSchFlgReservedCheck,Flags) JustStockCheck = TRUE ReservedCheck = TRUE ELSIF BAND(xpSchFlgVirtualCheck,Flags) JustStockCheck = TRUE VirtualCheck = TRUE ELSIF BAND(xpSchFlgAnyStockCheck,Flags) JustStockCheck = TRUE AnyStockCheck = TRUE ELSIF BAND(xpSchFlgLateKitCheck,Flags) JustStockCheck = TRUE LateKitCheck = TRUE END Clear(s) !IF xpMaxTraceDetail:Value ! xpWriteLog(,,1,'CreateScheduleHelper: @',zvWS,SummaryWS) ! xpSetLogDepth(+1); LogDepth +=1 !END !}}} !{{{ MakeMaterial !A wrapper around xpMakeMaterial for its three uses: ! make something that is not a conatiner ! make a container ! sub-con something !Which we are doing is set by the MakeType param. MakeMaterial ROUTINE DATA TypeName STRING(16) CODE CASE MakeType OF xpMkeFlgSubCon TypeName = 'sub-con' LastMethod = Method:SubCon OF xpMkeFlgMake TypeName = 'make' LastMethod = Method:Make OF xpMkeFlgContainer TypeName = 'make container' LastMethod = Method:Make ELSE Err# = xpDisplayError(,'xpCreateScheduleHelper: Invalid MakeType: ' & MakeType,gxInternalErr) DO ProcedureExit END Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF xpMaxTraceDetail:Value xpWriteLog(TopWS,SummaryWS,1,'>>>> Trying to @ @: @[of @][x @]for ^@ @ from @',| zvStr,TypeName,zvMaterial,MakeMat,| zvMeasure,NeedOrderQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvDate,StartLimit) END xpSetLogDepth(+1); LogDepth += 1 Err# = xpMakeMaterial(SummaryWS, | !The context NeedOrderQty, | !How many we want ConsiderDate,ConsiderTime, | !When we want them LocalStartDate,LocalStartTime, | !Start date necessary LocalEndDate,LocalEndTime, | !End date necessary LocalRaiseDate,LocalRaiseTime, | !Raise date necessary MakeType, | !what to do Method, | !how it was done SetRaiseDate, | !TRUE on a WO/SC-PO boundary TotalQty, | !how many we made TotalCost ) !cost of doing it xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Limits for @ are @ @ and @ @ [at @] for ^@', | zvStr,TypeName, | zvDate,LocalStartDate,zvClock,LocalStartTime, | zvDate,LocalEndDate ,zvClock,LocalEndTime, | zvMoney,TotalCost,zvMeasure,TotalQty ) END !}}} !{{{ MakeSome MakeSome ROUTINE IF IsContainer MakeType = xpMkeFlgContainer ELSE MakeType = xpMkeFlgMake END DO MakeMaterial !}}} !{{{ SubConSome SubConSome ROUTINE MakeType = xpMkeFlgSubCon DO MakeMaterial !}}} !{{{ InventSome InventSome ROUTINE Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF xpMaxTraceDetail:Value xpWriteLog(TopWS,SummaryWS,1,'Trying invent @: @[of @][x @]for ^@ @ from @',| zvMaterial,MakeMat,| zvMeasure,NeedOrderQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvDate,StartLimit) END xpSetLogDepth(+1); LogDepth += 1 Err# = xpBuyMaterial(SummaryWS, | !The context NeedOrderQty, | !How many we want ConsiderDate,ConsiderTime,2, | !When we want them and doing invent LocalStartDate,LocalStartTime, | !When they must be ordered LocalEndDate,LocalEndTime, | !When they will arrive TotalQty,TotalCost, | !For info only Method ) !How it was done xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Invent limits are @ @ and @ @ at @ for @', | zvDate,LocalStartDate,zvClock,LocalStartTime,| zvDate,LocalEndDate ,zvClock,LocalEndTime, | zvMoney,TotalCost,zvMeasure,TotalQty ) END SetRaiseDate = FALSE LocalRaiseDate = 0 LocalRaiseTime = 0 LastMethod = Method:Invent !}}} !{{{ CallInSome CallInSome ROUTINE Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF xpMaxTraceDetail:Value xpWriteLog(TopWS,SummaryWS,1,'Trying free issue call-in @: @[of @][x @]for ^@ @ from @',| zvMaterial,MakeMat,| zvMeasure,NeedOrderQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvDate,StartLimit) END xpSetLogDepth(+1); LogDepth += 1 Err# = xpBuyMaterial(SummaryWS, | !The context NeedOrderQty, | !How many we want ConsiderDate,ConsiderTime,1, | !When we want them and doing free issue LocalStartDate,LocalStartTime, | !When they must be ordered LocalEndDate,LocalEndTime, | !When they will arrive TotalQty,TotalCost, | !For info only Method ) !How it was done xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Call-in limits are @ @ and @ @ at @ for @', | zvDate,LocalStartDate,zvClock,LocalStartTime,| zvDate,LocalEndDate ,zvClock,LocalEndTime, | zvMoney,TotalCost,zvMeasure,TotalQty ) END SetRaiseDate = FALSE LocalRaiseDate = 0 LocalRaiseTime = 0 LastMethod = Method:FreeIss !}}} !{{{ BuySome BuySome ROUTINE Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF xpMaxTraceDetail:Value xpWriteLog(TopWS,SummaryWS,1,'Trying buy @: @[of @][x @]for ^@ @ from @',| zvMaterial,MakeMat,| zvMeasure,NeedOrderQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvDate,StartLimit) END xpSetLogDepth(+1); LogDepth += 1 Err# = xpBuyMaterial(SummaryWS, | !The context NeedOrderQty, | !How many we want ConsiderDate,ConsiderTime,0, | !When we want them and buying it LocalStartDate,LocalStartTime, | !When they must be ordered LocalEndDate,LocalEndTime, | !When they will arrive TotalQty,TotalCost, | !For info only Method ) !How it was done xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Buy limits are ^@ @ and ^@ @ at ^@ for ^@', | zvDate,LocalStartDate,zvClock,LocalStartTime,| zvDate,LocalEndDate ,zvClock,LocalEndTime, | zvMoney,TotalCost,zvMeasure,TotalQty ) END SetRaiseDate = FALSE LocalRaiseDate = 0 LocalRaiseTime = 0 IF Method = xpAnonBuyRecord !This means there are no supliers for the buy option. !This degrades its 'best-ness' when there are choices. LastMethod = Method:AnonBuy ELSE LastMethod = Method:Buy END !}}} !{{{ MOSome !When we get here, the thing to be MO'd is in the MOList buffer. !If ErrorCode() is set it means the Get() on that failed. MOSome ROUTINE IF gxErrCode() Err# = xpDisplayError(,'xpCreateScheduleHelper: Get(MOList,...',gxErrCode()) DO ProcedureExit END Err# = gxMonitorStep(); IF Err# THEN DO ProcedureExit. IF xpMaxTraceDetail:Value mo# = Pointer(MOList) mo" = CHR(MOList:RecNo3) xpWriteLog(TopWS,SummaryWS,1,'>>>> Trying (@) MO#@ of: @[of @][x @]for ^@ @ from @',| zvStr,mo",zvInt,mo#,zvMeasure,NeedOrderQty,zvMeasure,RequiredLen,zvMeasure,RequiredWid,| zvDate,ConsiderDate,zvClock,ConsiderTime,zvDate,StartLimit) END xpSetLogDepth(+1); LogDepth += 1 Err# = xpMakeContainer(SummaryWS, | !The context MOList:RecNo, | !The container material we want MOList:RecNo2, | !The OUTPUT record we're using MOList:RecNo3, | !The method type to use NeedOrderQty, | !How many we want ConsiderDate,ConsiderTime, | !When we want them LocalStartDate,LocalStartTime, | !Start date necessary LocalEndDate,LocalEndTime, | !End date necessary TotalQty, | !How much we did (of the container) TotalCost, | !cost of doing it Method ) !how it was done xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'MO limits are @ @ and @ @ at @ for @', | zvDate,LocalStartDate,zvClock,LocalStartTime,| zvDate,LocalEndDate ,zvClock,LocalEndTime, | zvMoney,TotalCost,zvMeasure,TotalQty ) END SetRaiseDate = FALSE LocalRaiseDate = 0 LocalRaiseTime = 0 LastMethod = Method:MO + Pointer(MOList) !}}} !{{{ UpdateDateLimits !This function updates the date limits after successfully scheduling something. !Only call this once its decided to commit to what you just did. !It should be called after calling one of the Make/SubCon/Buy/CallInSome routines. UpdateDateLimits ROUTINE xpSetEarliest(StartDate,StartTime,LocalStartDate,LocalStartTime) xpSetLatest (EndDate ,EndTime ,LocalEndDate ,LocalEndTime ) xpSetLatest (ConsiderDate,ConsiderTime,EndDate,EndTime) !For re-allocation of stock xpSetEarliest(RaiseDate,RaiseTime,LocalRaiseDate,LocalRaiseTime) IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Dates: Start ^@ @, End ^@ @, Consider ^@ @, Raise ^@ @',| zvDate,StartDate, zvClock,StartTime, | zvDate,EndDate, zvClock,EndTime, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,RaiseDate, zvClock,RaiseTime ) END !}}} !{{{ UseExisting !We get here when being forced to use WIP in an ASAP schedule. !It differs from ForceUseExisting in that it just moves the stock probe date and !not the time-line. UseExisting ROUTINE IF ForceUseReason AND xpDebugDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'@ re-starting from @@ to use existing WIP @@ - ' &Clip(ForceUseReason),| zvMaterial,Material,zvDate,InitialDate,zvClock,InitialTime,zvDate,ExistingDate,zvClock,ExistingTime) END ConsiderDate = InitialDate !start from same time-line ConsiderTime = InitialTime LookDate = ExistingDate !make stock probe find it LookTime = ExistingTime ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !tell final stock check we're not racing !}}} !{{{ ForceUseExisting !We get here if its determined that existing stock found during !the initial stock check must be used. ForceUseExisting ROUTINE IF ~WorkBackwards DO UseExisting !when doing ASAP do not move the time-line EXIT END IF ForceUseReason AND xpDebugDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'@ waiting for existing WIP @ @ - ' & Clip(ForceUseReason),| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END xpSetlatest(ConsiderDate,ConsiderTime,| ExistingDate,ExistingTime ) !move time-line to meet it InitialDate = ConsiderDate !note new (forced) start time-line for ASAP re-start InitialTime = ConsiderTime LookDate = ConsiderDate !make stock probe find it LookTime = ConsiderTime ExistingDate = 0 !tell xpMakeMaterial we're not racing ExistingTime = 0 !.. Pass = 2 !tell final stock check we're not racing !}}} !{{{ SetHasLateKit !If we get here it means we are waiting for late committed stuff. !A flag is set to note this so it can be propagated up the dependency !hierarchy. Waiting for late kit must be taken into consideration !when we are not allowed to race WIP. A WIP kit is considered to be !WIP from a racing POV. SetHasLateKit ROUTINE ExistingStatus = BOR(ExistingStatus,xpStkFlgHasLateKit) !note we had to wait for late kit IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Setting "HasLateKit"') END !}}} !{{{ AbandonJIT !{{{ IMPORTANT: Read this !When doing JIT we look to see if there is any existing, but late, !WO/PO's that can (partially) satisfy the demand. If there is, the !JIT schedule is abandonned. This forces a re-try in a mode that picks !up the existing work. If we don't do this the existing work could get !orphaned which is usually undesirable, especially if the 'lateness' is !a lie! !This function is called when we discover JIT must be abandonned. !ExistingDate/Time will reflect what is there that must be picked !up and ConsiderDate/Time is what we were aiming at. The difference !between these 2 is lodged in our meta-summary so xpSchedule can !react to it. !}}} AbandonJIT ROUTINE DATA MissedBy REAL LogLevel LONG CODE IF ~JitFailReason !Caller doesn't want a log LogLevel = 0 ELSIF (JustStockCheck AND ~gxBitInMask(xpStockOnlyStrategy,AllocationStrategy)) | OR gxBitInMask(xpOnFailStrategy,AllocationStrategy) THEN !This is just a stock probe or a JIT/SPLIT probe - so not an alert LogLevel = 1 ELSE !This ia an alert LogLevel = -1 END IF LogLevel xpWriteLog(TopWS,SummaryWS,LogLevel,'Abandon JIT: @ for @@ to use WIP of @@ - ' & Clip(JitFailReason),| zvMaterial,Material, | zvDate,ConsiderDate,zvClock,ConsiderTime, | zvDate,ExistingDate,zvClock,ExistingTime ) END TotalQty = umZeroVal !this is the failure signal TotalCost = umZeroVal !..just for completeness LocalStockDate = ConsiderDate !tell upper levels where we gave up LocalStockTime = ConsiderTime !..this gets propagated to EndDate/Time DoMakeBuySubCon# = FALSE DoFinalStockCheck# = FALSE NoMakeBuyReason = 'JIT abandonned' NoStockCheckReason = 'JIT abandonned' ExistingStatus = BOR(ExistingStatus,xpStkFlgJITabandon) !tell caller what happenned Pass = 2 !tell final stock check we're not racing !Tell xpSchedule what happenned Err# = xpLoadWS(TopWS,02220212); IF Err# THEN DO ProcedureExit. MissedBy = xpDate2ElapsedTime(ConsiderDate,ConsiderTime,ExistingDate,ExistingTime) IF MissedBy > xpwC:AbandonJIT !This is worse than we've seen so far - use this !NB: We must do this even for the ALTSTOCK exploration being done by xpMakeMaterial. ! If we get here during ALTSTOCK probing it means all existing stock options have ! been exhausted and we're trying to make/buy more. In that context we've found ! something that *must* be used, so its correct to abandon JIT to reach it. IF xpDebugDetail:Value > 2 xpWriteLog(TopWS,SummaryWS,3,'Updating delay from ^@ to ^@ seconds',zvReal,xpwC:AbandonJIT,zvReal,MissedBy) END xpwC:AbandonJIT = MissedBy Err# = xpPutWS(); IF Err# THEN DO ProcedureExit. END !}}} !{{{ JITLostWIPrace !30/05/13 DCN Just because we lost a WIP race it does not mean that ! the existing WIP is the best that can be done. Take ! this case: ! 1. there is a forward order for stuff a long way in the future ! 2. our JIT failed due to insufficient time to make/buy any ! 3. an ASAP could still beat the WIP by a substantial margin ! How to detect this? ! If we do nothing the next JIT pass may try a very late date. ! What we need to do is calculate the latest existing date that ! is worth shooting for. What's that? The WIP race limit from here! ! So if the existing stock is more than that in the future its ! not a lost WIP race its a JIT failure. ! To get here we already know the existing stock is outside the ! wait limit - else we would've abandonned the JIT without a race. ! Unlike the other AbandonJIT cases, in this case we are allowed ! to race, so we are allowed to try again at a date that can be ! earlier than the existing WIP. ! So probe again at most xpJITprobeMaxInterval into the future. JITLostWIPrace ROUTINE DATA MissedBy REAL AdjustedDate LONG AdjustedTime LONG CODE MissedBy = xpDate2ElapsedTime(ConsiderDate,ConsiderTime,ExistingDate,ExistingTime) IF MissedBy > (xpJITprobeMaxInterval:Value/100) !We missed by too much - set to the limit xpElapsedTime2Date(ConsiderDate,ConsiderTime,xpJITprobeMaxInterval:Value/100,FALSE,AdjustedDate,AdjustedTime) IF xpMaxTraceDetail:Value > 0 xpWriteLog(TopWS,SummaryWS,1,'Existing WIP of @@ too far away, adjusting target to @@',| zvDate,ExistingDate,zvClock,ExistingTime, | zvDate,AdjustedDate,zvClock,AdjustedTime ) END ExistingDate = AdjustedDate ExistingTime = AdjustedTime END JitFailReason = 'lost WIP race' DO AbandonJIT !}}} !{{{ JITAbandon4Existing !We get here when doing JIT if we discover there is existing stock that we are not allowed to race. !But we cannot assume that existing stock is the best we can do. There may be earlier stock that !was ignored on this JIT pass because its use is prohibited. But on a subsequent later pass it will !be within our revised time-line and thus usable. So we do the stock probe again looking for the !earliest WIP and ignoring any prohibit condition. !17/10/18 DCN The above is mumbo-jumbo! The JIT schedule is nowadays done in a probing loop where ! the time-line is extended on each probe until it succeeds or it gives up and does ! an ASAP. So if we can't see better stock here we will on the next pass and that will ! extend the time-line some more. So do nothing here. JITAbandon4Existing ROUTINE OMIT('ENDOMIT') !17/10/18 DCN See above IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Looking for WIP earlier than @ @ beyond @ @',| zvDate,ExistingDate,zvClock,ExistingTime,| zvDate,ConsiderDate,zvClock,ConsiderTime) END xpSetLogDepth(+1); LogDepth += 1 LookDate = 0 !look to the end of eternity LookTime = 0 !.. Err# = xpAllocateStock(s,, | !the spec BOR(BOR(Flags,xpSchFlgFindOnly+xpSchFlgIgnoreProhibit),WSOnly),| !options (explore only + ignore prohibit) OldStockStatus ) xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. !Grab the results we want ExistingDate = s.FoundDate ExistingTime = s.FoundTime ExistingStatus = OldStockStatus !ENDOMIT DO AbandonJIT !}}} !{{{ DumpSchedule DumpSchedule ROUTINE Err# = xpDestroySummaryWS(SummaryWS,,TRUE) !only do children IF Err# THEN DO ProcedureExit. Err# = xpLoadWS(SummaryWS,241008) IF Err# THEN DO ProcedureExit. !Undo the WO that may have been allocated, and the IsModule flag xpwC:WONum = ThisWONum !NB: Do *NOT* use xpAllocateWONum here xpwC:IsModule = ThisIsModule xpwC:WODepth = ThisWODepth Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !}}} !{{{ PickBest !We get here when probing choices and we've just done a choice. !If that choice was abandonned due to having to wait for existing !WIP, then we must abandon all choices and give up. When exploring !these choices we've already exhausted whatever may be in stock, so !we're trying to buy/make more. In that context any WIP in any choice !must be found and used. We note any such failures, but allow choices !to be explored so the worst case is found and noted (in the meta-sumamry). !BestResult:<...> is the best solution so far. !LastMethod and Local<...> is the last solution we did. !Check for the best one and set it as the best. ! !When urgent - pick the quickest. !When not urgent - pick the cheapest. !Otherwise pick the cheapest that is within the wait limit. ! !On exit the best solution is reflected BestResult:<...>. PickBest ROUTINE DATA BestWaitDays REAL !actually elapsed seconds ThisWaitDays REAL !.. CODE IF gxMonitorStep() THEN Err# = gxInterruptErr; DO ProcedureExit. !{{{ see if JIT was abandonned !This is detected by looking for xpwC:AbandonJIT in our meta-summary Err# = xpLoadWS(TopWS,03220212); IF Err# THEN DO ProcedureExit. IF xpwC:AbandonJIT > umNoise:Value !We've been abandonned JITabandonned = TRUE EXIT END !}}} IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'Picking best of last and this'). !{{{ do the special cases first IF ~Best:Method !Not done anything yet, so this is best Best:Reason = 'best' DO SetLastAsBest EXIT ELSIF umIsZero(Best:TotalQty) !Last failed so pick this IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This didn''t fail'). Best:Reason = 'best' DO SetLastAsBest EXIT ELSIF umIsZero(TotalQty) !This failed so leave last IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This failed'). EXIT ELSIF Best:Method = Method:AnonBuy and LastMethod <> Method:AnonBuy !This is not anonymous and best is, so use this IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is not an anonymous buy'). Best:Reason = 'best' DO SetLastAsBest EXIT ELSIF Best:Method <> Method:AnonBuy AND LastMethod = Method:AnonBuy !An anonymous buy is never better than something that isn't IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is an anonymous buy'). EXIT END !}}} IF Urgent DO PickQuickest ELSIF NotUrgent DO PickCheapest ELSIF MostCost DO PickCostliest ELSE !{{{ pick the cheapest within the wait limit !This is the same as picking the cheapest provided there is no delay beyond the wait limit. IF WorkBackwards !When doing JIT all solutions are valid, so just pick the cheapest DO PickCheapest ELSE !{{{ get date spans BestWaitDays = xpDate2ElapsedTime(Best:StartDate,Best:StartTime,Best:EndDate,Best:EndTime) ThisWaitDays = xpDate2ElapsedTime(LocalStartDate,LocalStartTime,LocalEndDate,LocalEndTime) !}}} IF BestWaitDays > ThisWaitDays + xpCheapPartWaitLimit:Value !Last is too slow, so do this IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This has a shorter delay'). Best:Reason = 'shortest' DO SetLastAsBest EXIT END !Nothing in it for time, so pick cheapest DO PickCheapest END !}}} END !{{{ PickQuickest PickQuickest ROUTINE IF WorkBackwards AND xpOlder(Best:StartDate,Best:StartTime,LocalStartDate,LocalStartTime) !This is starting later, this is best when doing JIT IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This starts later'). Best:Reason = 'latest start' DO SetLastAsBest EXIT ELSIF ~WorkBackwards AND xpOlder(LocalEndDate,LocalEndTime,Best:EndDate,Best:EndTime) !This is ending earlier, this is best when doing ASAP IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This ends earlier'). Best:Reason = 'earliest end' DO SetLastAsBest EXIT ELSIF WorkBackwards AND xpOlder(LocalStartDate,LocalStartTime,Best:StartDate,Best:StartTime) !This is starting earlier, when doing JIT this is slower IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is slower'). EXIT ELSIF ~WorkBackwards AND xpOlder(Best:EndDate,Best:EndTime,LocalEndDate,LocalEndTime) !This is ending later, when doing ASAP this is slower IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is slower'). EXIT ELSE !same time, check price !********NB: Do not call PickCheapest here, else could get an infinite recursion loop IF umIsSmaller(TotalCost,Best:TotalCost) !This is cheaper IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is cheaper'). Best:Reason = 'cheapest' DO SetLastAsBest EXIT ELSIF umIsBigger(TotalCost,Best:TotalCost) !This is more expensive IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is more expensive'). EXIT ELSE !They're identical, use this (to minimise schedule dumps) IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is as good'). !Leave orig reason in place DO SetLastAsBest EXIT END END !}}} !{{{ PickCheapest PickCheapest ROUTINE IF umIsSmaller(Best:TotalCost,TotalCost) !Last is cheaper IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is more expensive'). EXIT ELSIF umIsBigger(Best:TotalCost,TotalCost) !Last is more expensive IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is cheaper'). Best:Reason = 'cheapest' DO SetLastAsBest EXIT ELSE !Same cost, pick quickest DO PickQuickest END !}}} !{{{ PickCostliest PickCostliest ROUTINE IF umIsBigger(Best:TotalCost,TotalCost) !Last is more expensive IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is cheaper'). EXIT ELSIF umIsSmaller(Best:TotalCost,TotalCost) !This is more expensive IF xpDebugDetail:Value > 2 THEN xpWriteLog(,,3,'This is more expensive'). Best:Reason = 'most costly' DO SetLastAsBest EXIT ELSE !Same cost, pick quickest DO PickQuickest END !}}} !}}} !{{{ SetLastAsBest !Whatever we did last it set as the best so far !NB: The caller must have already set Best:Reason SetLastAsBest ROUTINE Best:Method = LastMethod Best:StartDate = LocalStartDate Best:StartTime = LocalStartTime Best:EndDate = LocalEndDate Best:EndTime = LocalEndTime Best:TotalQty = TotalQty Best:TotalCost = TotalCost !}}} !{{{ ProcedureExit ProcedureExit ROUTINE IF Method = xpAnonBuyRecord !This was only for our 'best' descrimination use, tell caller what it really was Method = xpBuyRecord END IF Err# xpDestroySummaryWS(SummaryWS) ELSE ; END Free(ExistingQ) IF MonitorOpen gxEndErrorFrame() gxMonitorStop() END LOOP LogDepth TIMES xpSetLogDepth(-1) END xpSetLogContext() RecursionDepth -= 1 RETURN Err# !}}} !}}} !}}} !{{{ xpMakeContainer !{{{ history ! ! 22/03/04 DCN Created by extraction from xpCreateSchedule [1817] ! 05/08/04 DCN Don't pass the MCB to mcGetPreviousStep ! 15/09/04 DCN Consider the cost of the required part to be the entire cost, including ! the container, not just the required output cost. ! 11/03/05 DCN Release the container stock we made (we don't want it) [7311] ! 26/04/05 DCN Force make when doing a container ! 14/06/05 DCN Add IsUnMake param ! 10/07/06 DCN Change IsUnMake to the method type (encoded as a BYTE) ! 12/03/08 DCN xpCreateSummary API ! 28/10/10 CRJ Use xpMakeCycles ! 19/08/11 DCN xpCreateSchedule API ! 23/11/11 DCN Remove stock control days stuff ! 13/02/12 DCN Set a new WO# for the container ! 20/02/12 DCN xpCreateSchedule API ! 27/02/13 DCN Use xpDeAllocateContainer not xpDeAllocateStock ! 01/03/13 DCN Use xpGetContainerQty not DIY ! 27/11/23 DCN Use xpAllocateWONum() not DIY ! 10/12/23 DCN Allow for split batches when release the unwanted containers ! !}}} !{{{ description ! ! This function 'makes' a container in the context of the given summary WS. ! ! xpMakeContainer(LONG,LONG,LONG,BYTE,STRING,LONG,LONG,*LONG,*LONG,*LONG,*LONG,*STRING,*STRING,*LONG),LONG ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !ErrorCode ! ! ! ! ! ! ! ! ! ! ! ! ! ! !Method used ! ! ! ! ! ! ! ! ! ! ! ! ! !Money - total cost ! ! ! ! ! ! ! ! ! ! ! ! !Measure - total qty ! ! ! ! ! ! ! ! ! ! ! !Time - end time ! ! ! ! ! ! ! ! ! ! !Date - end date ! ! ! ! ! ! ! ! ! !Time - start time ! ! ! ! ! ! ! ! !Date - start date ! ! ! ! ! ! ! !Time - when we want it ! ! ! ! ! ! !Date - when we want it ! ! ! ! ! !Measure - quantity required of the OUTPUT (NB: Not the container) ! ! ! ! !STRING(1) - method type to use to make the container ! ! ! !RecNo - (MCB) the OUTPUT record we're using ! ! !RecNo - (MCH) container material ! !Handle - summary WS ! ! NB: The total cost is considered to be the entire cost of doing all the outputs *and* the container ! itself. This is because we are being forced to do more than necessary 'cos a side-effect is ! the only way to get it. ! !}}} xpMakeContainer FUNCTION(SummaryWS,Container,MethodRec,MethodTypeB, | RequiredQty,TargetDate,TargetTime, | StartDate,StartTime,EndDate,EndTime,TotalQty,TotalCost,Method) !{{{ data LogDepth LONG(0) MethodType LIKE(xpwC:MethodType),OVER(MethodTypeB) EndLimit LIKE(xpwC:EndLimitDate) Strategy LIKE(xpwC:AllocationStrategy) TopWS LIKE(xpwC:TopHandle) ContainerWS LONG Material LIKE(xpwC:Material) RequiredLen LIKE(xpwC:Length) RequiredWid LIKE(xpwC:Width) RequiredHght LIKE(xpwC:Height) SchedState GROUP(xpSchedStateType) . ContainerStartDate EQUATE(SchedState.StartDate) ContainerStartTime EQUATE(SchedState.StartTime) ContainerEndDate EQUATE(SchedState.EndDate) ContainerEndTime EQUATE(SchedState.EndTime) ContainerQty EQUATE(SchedState.AllocQty) ContainerCost EQUATE(SchedState.AllocCost) SiblingQ QUEUE(gxRecNoQType). Sibling LONG Flags LONG(0) !flags to give xpCreateSchedule !}}} CODE DO ProcedureEntry !{{{ get stuff from summary we need TopWS = xpwC:TopHandle Strategy = xpwC:AllocationStrategy Material = xpwC:Material RequiredLen = xpwC:Length RequiredWid = xpwC:Width RequiredHght = xpwC:Height !}}} !{{{ schedule container !This must be done in a different context to the actual demand. !The purpose of this is just to create stock of the demand but by !a completely different route. That route must not intermingle !with the main demand. We ensure this by taking a copy of the !summary and using that as the vehicle to create the stock. DO MakeContainerWorksheet Container = xpwC:Material !note what container really is EndLimit = xpwC:EndLimitDate !note original end limit xpWriteLog(TopWS,SummaryWS,-1,'Trying @ to make MO @ shortfall of @',| zvMaterial,Container,zvMaterial,Material,zvMeasure,RequiredQty) !08/10/01 DCN !NB: Must force increase stock here 'cos we know there is not enough of the side-effect we want. ! The quantity of the container itself is *irrelevant*, there may well be some of those but ! we don't want those, we want the side effect, which can only happen by making some *more* ! of the container. This is done by setting the xpwC:MakeThis = Material in the WS (done in ! MakeContainerWorksheet). Flags += xpSchFlgNoAllocate !force make Flags += xpSchFlgReleaseStock !release it Flags += xpSchFlgIsContainer !its a container xpSetLogDepth(+1); LogDepth += 1 Err# = xpCreateSchedule(ContainerWS,SchedState,Flags) xpSetLogDepth(-1); LogDepth -= 1 IF Err# THEN DO ProcedureExit. StartDate = ContainerStartDate StartTime = ContainerStartTime !}}} !{{{ react to what happenned IF umIsPositive(ContainerQty) IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Made @ of container @ for @',| zvMeasure,ContainerQty,zvMaterial,Container,zvDate,ContainerEndDate) END !{{{ adjust end limit if necessary !ContainerEndDate is what was achieved. !EndLimit is the current end limit in our summary. !If the actual end date was beyond the existing end limit, !move the end limit up to meet it. IF NOT gxBitInMask(xpWorkBackStrategy,Strategy) !Only do it when doing ASAP IF xpOlder(EndLimit,,ContainerEndDate) xpWriteLog(TopWS,SummaryWS,-1,'Raising end limit (@) to meet container WIP (@)',| zvDate,EndLimit,zvDate,ContainerEndDate) Err# = xpLoadWS(SummaryWS,01111001); IF Err# THEN DO ProcedureExit. xpwC:EndLimitDate = ContainerEndDate Err# = xpPutWS(); IF Err# THEN DO ProcedureExit. END END !}}} !{{{ release the container stock !We made the container itself as a side-effect of the output we wanted. !We want the output but not the container. !So we release the container here so it can be (possibly) used by others. !We want to release the container stock because it may be useful to others. !However, we need to leave evidence of its 'need' to the merging system !so that containers can be merged if appropriate. We do that by putting !a 'container need' record in our parent. That is done when the output !stock is allocated, not here. The reason the stock allocator has to do !it is that the same hints must exist on subsequent merge passes, and on !pass 2+ the container will already have been made so the demand for its !output will not come through here. !NB: Any split batches will have been released by xpMakeMaterial SiblingQ:RecNo = ContainerWS Add(SiblingQ) Err# = xpDeAllocateContainers(SiblingQ) IF Err# THEN DO ProcedureExit. !}}} ELSE IF xpMaxTraceDetail:Value > 1 xpWriteLog(TopWS,SummaryWS,2,'Cannot make any of container @',zvMaterial,Container) END !{{{ dump the container schedule Err# = xpDestroySummaryWS(ContainerWS) ContainerWS = 0 DO ProcedureExit !}}} END !}}} !{{{ get output dates, qty and cost xpwC:Handle = ContainerWS !scan this TotalCost = ContainerCost !include container cost DO GetOutputInfo !}}} DO ProcedureExit !{{{ ProcedureEntry ProcedureEntry ROUTINE StartDate = 0 StartTime = 0 EndDate = 0 EndTime = 0 TotalQty = umZeroVal TotalCost = umZeroVal Method = xpStockUseRecord !this is how the OUTPUT is being done Err# = gxMonitorStep(); IF Err# THEN RETURN Err#. Err# = xpLoadWS(SummaryWS,01220304); IF Err# THEN RETURN Err#. xpSetLogContext(xpwC:TopHandle,SummaryWS,xpLogOpt:MakeContainer) ContainerWS = 0 !}}} !{{{ ProcedureExit ProcedureExit ROUTINE IF Err# AND ContainerWS THEN xpDestroySummaryWS(ContainerWS). LOOP LogDepth TIMES xpSetLogDepth(-1) END xpSetLogContext() RETURN Err# !}}} !{{{ MakeContainerWorksheet !This logic is a bastardised version of what xpMakeMaterial does. MakeContainerWorksheet ROUTINE DATA MakeCycles REAL StepMethod LIKE(xpwC:StepMethod) PartMat LIKE(xpwC:Material) PartQty LIKE(xpwC:CreateQuantity) PartLen LIKE(xpwC:Length) PartWid LIKE(xpwC:Width) PartHeight LIKE(xpwC:Height) PhysicalOnly BYTE IsModule EQUATE(TRUE) !containers are always done as modules CODE !{{{ get the step method !NB: We don't pass MethodRec in here 'cos it can cause an erroneous StepMethod ! to be returned. This could lead to some ambiguity if there are multiple ! methods and the one we find here is not the same as the one found by ! mcIsMultipleOutput. Tough. Its just horrendous to resolve. StepMethod = mcGetPreviousStep(,Container,MethodType) !}}} IF StepMethod PartMat = mcGetStepMaterial(StepMethod) IF ~PartMat THEN PartMat = Container. ELSE PartMat = Container END MakeCycles = xpGetContainerQty(MethodRec, | Material,RequiredQty,RequiredLen,RequiredWid,RequiredHght,| PartMat,PartQty,PartLen,PartWid,PartHeight ) PhysicalOnly = gxBitInMask(xpMustBePhysicalStrategy,Strategy) ContainerWS = xpCreateSummary(SummaryWS,,,PartMat,IsModule,TargetDate,TargetTime,| MakeCycles,PartQty,PartLen,PartWid,PartHeight, | umZeroVal,umZeroVal,0, | !No source size constraints MethodRec,,PhysicalOnly ) IF ~ContainerWS THEN Err# = gxUnspecifiedErr; DO ProcedureExit. xpwC:MakeThis = Material !note what it is we really want xpwC:MethodType = MethodType !update method type !Allocate the next tentative WO# (so it doesn't get muddled with our parent) xpAllocateWONum(,4,'MakeContainerWorksheet') Err# = xpPutWS() IF Err# THEN DO ProcedureExit. !}}} !{{{ GetOutputInfo !The info returned from xpCreateSchedule will be for the container. !We want the cost for this and *all* the output materials. !We only want dates and quantities for the required output. !Go find them. !NB: The StartDate/Time will be the same for the container and its ! outputs. !Note: They may be at some depth in what we just did. GetOutputInfo ROUTINE DATA ScanWS LONG ScanKey LONG CODE ScanWS = xpwC:Handle !set summary to scan IF xpStartWSChildScan(ScanWS,ScanKey). !..and scan for its output records LOOP IF xpNextWSChildScan(ScanWS,ScanKey). IF ScanKey = 0 THEN BREAK. IF xpwC:RecType = xpSummaryRecord !Recurse on this DO GetOutputInfo CYCLE END IF xpwC:RecType <> xpOutputRecord THEN CYCLE. !Found an output, accumulate !Do this unconditionally TotalCost = umAdd(TotalCost,xpwC:CreateValue) IF xpwC:Material = Material !Only do this for the required output xpSetLatest(EndDate,EndTime,xpwC:CreateDate,xpwC:CreateTime) TotalQty = umAdd(TotalQty,xpwC:CreateQuantity) !assume units compatibility END END !}}} !}}} !{{{ xpDeallocateContainers !{{{ history !10/12/23 DCN Created !}}} !{{{ description !De-allocate the container allocations for the given list of container summaries !xpDeAllocateContainers(*gxRecNoQType),LONG,PROC ! ! !ErrorCode ! !list of summary WS to release stock for !}}} xpDeAllocateContainers FUNCTION(SummaryQ) This LONG Err LONG(0) CODE IF xpMaxTraceDetail:Value > 1 xpWriteLog(,,1,'Releasing ^@ unwanted container stock allocations',zvInt,Records(SummaryQ)) END LOOP This = 1 TO Records(SummaryQ) Get(SummaryQ,This) xpSetLogDepth(+1) Err = xpDeAllocateStock(SummaryQ:RecNo) !remove the allocation of the container xpSetLogDepth(-1) IF Err THEN BREAK. END RETURN Err !}}} !{{{ xpGetContainerQty !{{{ history ! 01/03/13 DCN Created by extraction from xpMakeContainer ! 15/08/17 DCN [55209]Add instrumentation !}}} !{{{ description !Given a demand for an output, calculate how much of the !container is required to achieve that. !xpGetContainerQty(LONG,LONG,STRING,STRING,STRING,STRING,LONG,*STRING,*STRING,*STRING,*STRING),REAL ! ! ! ! ! ! ! ! ! ! ! ! !the number of container make cycles required ! ! ! ! ! ! ! ! ! ! ! ! !0 on error ! ! ! ! ! ! ! ! ! ! ! !Measure - the container height to make ! ! ! ! ! ! ! ! ! ! !Measure - the container width to make ! ! ! ! ! ! ! ! ! !Measure - the container length to make ! ! ! ! ! ! ! ! !Measure - the *cycle* qty of the container ! ! ! ! ! ! ! !RecNo - (MCH) the container material we need ! ! ! ! ! ! !Measure - the output height ! ! ! ! ! !Measure - the output width ! ! ! ! !Measure - the output length ! ! ! !Measure - the output quantity required ! ! !RecNo - (MCH) the material we need (the OUTPUT material) ! !RecNo - (MCB) the OUTPUT record within the container method !}}} xpGetContainerQty FUNCTION(Output,NeedM,NeedQ,NeedL,NeedW,NeedH,MakeM,MakeQ,MakeL,MakeW,MakeH) mcbB &mcb:Record NormQ STRING(12) MakeCycles REAL CODE IF xpDebugDetail:Value > 4 xpWriteLog(,,5,'xpGetContainerQty: Need:@ @[of @][x @][x @]',zvMaterial,NeedM,zvMeasure,NeedQ,zvMeasure,NeedL,zvMeasure,NeedW,zvMeasure,NeedH) END IF Output ! ! We normalise the required qty (of the MO) to the MO frame in the method ! rec and then divide this by the qty in the method rec. This gives the ! make cycles of the container. ! mcbB &= zfBufferOpen(mcb::Id,Output) IF mcbB &= NULL !Oops - now what? MakeCycles = umValueOf(NeedQ) IF xpDebugDetail:Value > 4 xpWriteLog(,,5,'xpGetContainerQty(no mcb): Cycles:^@',zvReal,MakeCycles) END ELSE NormQ = mcNormaliseTo(NeedM,NeedQ,NeedL,NeedW,NeedH,mcbB.Quantity,mcbB.Length,mcbB.Width,mcbB.Height) MakeCycles = xpMakeCycles(NormQ,mcbB.Quantity) IF xpDebugDetail:Value > 4 xpWriteLog(,,5,'xpGetContainerQty: mcbB:@, @[of @][x @][x @], NormQ:^@ Cycles:^@',| zvMaterial,mcbB.Material,zvMeasure,mcbB.Quantity,| zvMeasure,mcbB.Length,zvMeasure,mcbB.Width,zvMeasure,mcbB.Height,| zvMeasure,NormQ,zvReal,MakeCycles) END zfBufferClose(mcb::Id) END ELSE MakeCycles = umValueOf(NeedQ) IF xpDebugDetail:Value > 4 xpWriteLog(,,5,'xpGetContainerQty(no Output): Cycles:^@',zvReal,MakeCycles) END END mcGetTypicalMakeQty(MakeM,MakeQ,MakeL,MakeW,MakeH) IF xpDebugDetail:Value > 4 xpWriteLog(,,5,'xpGetContainerQty: Make:^@ @[of @][x @][x @]',zvMaterial,MakeM,zvMeasure,MakeQ,zvMeasure,MakeL,zvMeasure,MakeW,zvMeasure,MakeH) END RETURN MakeCycles !}}} !{{{ xpZapSchedule !{{{ history ! ! 24/10/11 DCN Created ! 10/11/11 DCN Use xpwC:Sibling not xpwC:Ancester ! 22/12/11 DCN Fix return logic!! ! 17/02/12 DCN xpUnschedule API ! 21/02/12 DCN Allow zapping self due to recursion ! 07/03/12 DCN Generate a level 1 alert on the source of schedule that got emptied ! Promote kit summaries to IsModule boundaries if we can't chuck 'em ! 17/12/12 DCN Re-jig PromotKit inner LOOP to overcome compiler gen'ing bad code ! Turn off IsModule when empty a summary too ! 18/12/12 DCN When promoting kit check for returns on all summaries first ! Recurse on buy and invented records too ! 28/02/13 DCN Add xpContainerNeedRecord ! 14/03/13 DCN Call xpStopWSChildScan ! 21/03/13 DCN Initialise ScanStarted!! ! !}}} !{{{ description ! ! Given a summary of a schedule, make that schedule 'empty'. This ! is called after an on-the-fly stock re-allocation has rendered ! an actualiser unused. In that context we want to remove all stock ! allocated to it as kit and remove any resources allocated to it. ! The actualiser is also removed and the summary marked as an xpEmptyRecord ! (in its Method field as a diagnostic aid). This function is somewhat ! less aggressive than xpUnSchedule. However, the result of this function ! may render all, or part of, the schedule as an orphan. Such orphans are ! removed entirely (via xpUnschedule). ! ! This function can be called tentatively. If it is not possible to ! empty the schedule (due to some usage constraint) then it just ! silently returns with -1. If it succeeds, it returns with +1 or +2. ! If it fails, the error is reported here and it returns with 0. ! This is a show-stopper and the caller should terminate. ! ! xpZapSchedule(LONG,LONG=0,<*LONG>),LONG,PROC ! ! ! ! !0=error, +1=orphaned it, +2=emptied it, -1=not allowed ! ! ! !Count of kit promotions done - internal use for alert message ! ! !Recursion depth - internal use for log message ! !Handle - the *summary* to be 'emptied' !{{{ worksheet relations considered ! ! GivenHandle --> Summary(given) ! | | | | | | | ! | | | | | | +----->Actualiser - Buy,Make,Sub-Con,etc - deleted ! | | | | | +------->Resource - de-allocated and deleted ! | | | | +--------->StockUse - may be a re-allocation - see below (1) ! | | | +----------->Return - if in use, not-allowed, else - stock removed and deleted ! | | +------------->Output - if in use, not-allowed, else - stock removed and deleted ! | +-------------+ ! | | ! V V ! Summary(kit) Summary(split batch) ! | | ! | +------------>StockUse - kit issued to our summary - de-allocated and deleted (2) ! | what about an in-use return? ! +-------------->anything else - ignored ! ! ! (1) - if an on-the-fly stock re-allocation occured, the original stock use ! that referred to this schedule can be replaced by one from outside, ! such stock use records must be preserved (the method is changed to StockUse) ! ! (2) - if this de-allocation renders the source unused, then we either ! delete it if its become an orphan, or recurse to also empty that. ! !}}} !}}} xpZapSchedule FUNCTION(Summary,Depth,Promotions) ! PromotionsP EQUATE(3) !<-----------+ OldLogDepth LONG scanKey LONG scanKey2 LONG ScanStarted BYTE Sibling LIKE(xpwC:Sibling) RetCode LONG KitPromoted LONG RetCode:Orphaned EQUATE(+1) RetCode:Emptied EQUATE(+2) RetCode:Error EQUATE(0) RetCode:NotAllowed EQUATE(-1) DetailLog EQUATE(TRUE) !18/12/12 DCN HACK diagnostic aid, set FALSE for issued builds LogDetail LONG !18/12/12 DCN HACK diagnostic aid CODE !{{{ prepare ScanStarted = FALSE IF xpDebugDetail:Value > 2 LogDetail = 3 ELSIF DetailLog LogDetail = 1 ELSE LogDetail = 0 END IF LogDetail OldLogDepth = xpSetLogDepth(+1,Depth & ':Zap schedule: @',zvWS,Summary) ELSE OldLogDepth = 0 END RetCode = RetCode:Error Err# = 0 Err# = xpLoadWS(Summary,03241011) IF Err# THEN DO ProcedureExit. xpSetLogContext(xpwC:TopHandle,Summary,xpLogOpt:ZapSchedule) Sibling = Summary !we need this to detect split-batches IF ~Depth OR Omitted(PromotionsP) KitPromoted = 0 ELSE KitPromoted = Promotions END !}}} !{{{ Phase 1 - probe to see if it can be done --calls CheckKit xpStartWSChildScan(Summary,scanKey) LOOP xpNextWSChildScan(Summary,scanKey) IF ~scanKey THEN BREAK. IF BAND(xpwC:RecType,xpOrphanedRecord) THEN CYCLE. !ignore these CASE xpwC:RecType OF xpSummaryRecord !Got a kit item IF xpwC:Sibling = Sibling THEN CYCLE. !ignore our split batches DO CheckKit OF xpStockUseRecord OROF xpContainerNeedRecord !This is kit being issued to our parent. !This function is only called if the 'make' actualiser is not in use. !So this can only be a stock issue that is not relevant to us. !I.e. ignore it. (but it must be preserved) OF xpResourceRecord !No constraints on these OF xpReturnRecord !NB: These are explicit returns - implicit returns are under a stock-use record !If these are in use then we cannot be zapped IF xpGetStockUse(,xpwC::Id,xpwC:Handle,,,xpStockUse:All) !*MUST* look at *all* allocations !Still in use IF LogDetail xpWriteLog(,,LogDetail,'Return in use, cannot empty: @ in @',zvMaterial,xpwC:Material,zvWS,Summary) END RetCode = RetCode:NotAllowed DO ProcedureExit END ELSE !an actualiser !These must not be in use !In use here means they must not be issued to anything other than our parent IF xpGetStockUse(,xpwC::Id,xpwC:Handle,,,xpStockUse:All) !*MUST* look at *all* allocations !Still in use IF LogDetail xpWriteLog(,,LogDetail,'Stock created in use, cannot empty: @ in @',zvMaterial,xpwC:Material,zvWS,Summary) END RetCode = RetCode:NotAllowed DO ProcedureExit END END END !}}} !{{{ Phase 2 - de-allocate and delete stuff --calls UnUseKit xpStartWSChildScan(Summary,scanKey,scanKey2) ScanStarted = TRUE LOOP xpNextWSChildScan(Summary,scanKey,scanKey2) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpSummaryRecord !Got a kit item IF xpwC:Sibling = Sibling THEN CYCLE. !ignore our split batches DO UnUseKit OF xpStockUseRecord OROF xpContainerNeedRecord !This is kit being issued to our parent. !This function is only called if the 'make' actualiser is not in use. !So this can only be a stock issue that is not relevant to us. !I.e. ignore it. (but it must be preserved) OF xpResourceRecord !Dump this resource use DO DeleteWS OF xpReturnRecord !NB: These are explicit returns - implicit returns are under a stock-use record !Previous phase checked these are not in use - so just chuck 'em now DO DeleteWS ELSE !an actualiser !Previous phase checked these are not in use - so just chuck 'em now DO DeleteWS END END ScanStarted = FALSE xpStopWSChildScan(Summary,scanKey,scanKey2) !}}} !{{{ Phase 3 - dump it if its now an orphan --calls PromoteKit IF xpIsOrphan(Summary,,TRUE) !ignore direct stock use !'cos we're only unscheduling the actualisers IF LogDetail !Can't use zvWS in this log 'cos its about to be destroyed xpLoadWS(Summary,01241011,TRUE) xpWriteLog(,,LogDetail,'Removing orphan @ (^@/^@) due to re-allocation',| zvMaterial,xpwC:Material,zvInt,xpwC:WONum,zvInt,xpwC:WODepth) xpSetLogDepth(+1) END Err# = xpUnSchedule(Summary) !NB: This will chuck the kit too IF Err# THEN DO ProcedureExit. IF LogDetail xpSetLogDepth(-1) END !Tell caller we did it RetCode = RetCode:Orphaned ELSE !This probably means there is a child schedule that over-produced and !that over production is being used elsewhere. So leave the summary !behind so things stay hooked-up, but mark it as empty as a diagnostic aid. !We must also promote the summaries of the kit left behind to IsModule !boundaries to ensure they get reserved. We want to promote to as close !as possible to the usage boundary so that the minimum of unecessary work !is done. How? Scan all child summaries looking for actualisers that are !in use, then set its parent to IsModule. Err# = xpLoadWS(Summary,02241011); IF Err# THEN DO ProcedureExit. IF xpwC:RecType = xpSummaryRecord xpwC:Method = xpEmptyRecord !mark it as empty xpwC:IsModule = FALSE !..and no longer an actualiser xpPutWS() END IF LogDetail xpWriteLog(,,LogDetail,'Emptied @ (^@/^@) due to re-allocation: @',| zvMaterial,xpwC:Material,zvInt,xpwC:WONum,zvInt,xpwC:WODepth,| zvWS,Summary) END !Tell user there's a problem IF Depth IF LogDetail xpWriteLog(,Summary,LogDetail,'@:Demand @ not required but side-effects/returns are',zvInt,Depth,zvWS,Summary) END ELSE xpWriteLog(,Summary,-1,'Demand @ not required but side-effects/returns are!',zvWS,Summary) END DO PromoteKit IF ~Depth AND ~KitPromoted xpWriteLog(,Summary,-1,'No in-use side-effects/returns found: @',zvWS,Summary) END !Tell caller we did it RetCode = RetCode:Emptied END !}}} DO ProcedureExit !{{{ DeleteWS ROUTINE DeleteWS ROUTINE Err# = xpDeleteWS(xpwC:Handle) IF Err# THEN DO ProcedureExit. !}}} !{{{ CheckKit ROUTINE !For the summary in the worksheet, check its StockUse to !our parent has not got any returns, or they are not used. CheckKit ROUTINE DATA Kit LONG kitKey LONG Child LONG childKey LONG CODE Kit = xpwC:Handle xpStartWSChildScan(Kit,kitKey) LOOP xpNextWSChildScan(Kit,kitKey) IF ~kitKey THEN BREAK. CASE xpwC:RecType OF xpStockUseRecord OROF xpContainerNeedRecord !Must check that there are no returns, or they are not in use Child = xpwC:Handle xpStartWSChildScan(Child,childKey) LOOP xpNextWSChildScan(Child,childKey) IF ~childKey THEN BREAK. !This can only be a return record IF xpGetStockUse(,xpwC::Id,xpwC:Handle,,,xpStockUse:All) !*MUST* look at *all* allocations !Return in use IF LogDetail xpWriteLog(,,LogDetail,'Kit return in use, cannot empty: @ in @',zvMaterial,xpwC:Material,zvWS,Summary) END RetCode = RetCode:NotAllowed DO ProcedureExit END END ELSE !Not interested in anything else END END !}}} !{{{ UnUseKit ROUTINE **** Recursion to xpZapSchedule in here **** !For the summary in the worksheet, remove all the stock use !records below it. These represent kit allocations to the summary !that is being emptied, so they are no longer required. UnUseKit ROUTINE DATA Kit LIKE(xpwC:Handle) scanKey LONG SourceWS LIKE(xpwC:Handle) HadOne LONG CODE Kit = xpwC:Handle IF LogDetail xpSetLogDepth(+1,'Un-use kit: @',zvWS,Kit) END !Because we delete as we go and we also recurse and that deletes !too, we can't do a normal scan here. This loop cycles until all !stock-use records are gone. So we just keep re-starting until !there are none left. HadOne = TRUE LOOP WHILE HadOne HadOne = FALSE xpStartWSChildScan(Kit,scanKey) LOOP WHILE ~HadOne xpNextWSChildScan(Kit,scanKey) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpStockUseRecord OROF xpContainerNeedRecord !{{{ found another one !Previous pass has checked any returns are not in use. !Note source for possible recursion IF xpwC:InstanceFile = xpwC::Id SourceWS = xpwC:InstanceRec ELSE SourceWS = 0 END !27/02/12 DCN @@TBD@@ allow zapping when kit returns are in use and re-allocate ! The crude thing to do is change the user of the return to use the ! instance this stock-use is using and attach all the returns from ! here to that instead. A better thing to do is to re-tesselate the ! new user with the new instance we're giving it and add returns to ! reflect that. The complexity is that the new user may already have ! created returns and they too may be in use. So the re-tesselation ! would need to 'follow' the chain to the bottom. Can we guarantee ! that is always possible? I think not because the new tesselation ! may produce off-cuts of a different shape and that new shape may ! not be usable downstream. Its always true with 1D, but not 2D. ! Another complexity: There may be multiple returns under the stock ! use we want to chuck, each of which may be in use. !Unschedule this stock-use now. Err# = xpUnschedule(xpwC:Handle) IF Err# THEN DO ProcedureExit. !{{{ if we've now orphaned this source chuck that too IF SourceWS IF xpGetStockUse(,xpwC::Id,SourceWS,,,xpStockUse:All) !*MUST* look at *all* allocations !Still in-use, so leave alone IF LogDetail xpWriteLog(,,LogDetail,'Kit source still in use: @ in @',zvWS,SourceWS,zvWS,Kit) END ELSE !Not in use - so attempt to chuck it Err# = xpLoadWS(SourceWS,04241011); IF Err# THEN DO ProcedureExit. CASE xpwC:RecType OF xpReturnRecord !Just un-schedule these Err# = xpUnschedule(xpwC:Handle) IF Err# THEN DO ProcedureExit. OF xpInventedRecord OROF xpBuyRecord OROF xpMadeRecord OROF xpSubContractRecord !Recurse on parents of these (NB: Only one of these per summary) !NB: The source parent may be the kit item we just 'un-used' - that's fine RetCode = xpZapSchedule(xpwC:ParentHandle,Depth+1,KitPromoted) CASE RetCode OF RetCode:Error Err# = gxUnspecifiedErr DO ProcedureExit ELSE !Carry on END ELSE !Ignore anything else END END END !}}} !}}} HadOne = TRUE ELSE !Not interested in anything else END END END IF LogDetail xpSetLogDepth(-1) END !}}} !{{{ PromoteKit ROUTINE **** Recursion to PromoteKit in here **** !Find all summaries, below the one in the current worksheet, housing !in-use actualisers or returns and promote them to IsModule boundaries. PromoteKit ROUTINE DATA Kit LONG kitKey LONG Child LONG childKey LONG GrandChild LONG GrandChildKey LONG GreatGrandParent LIKE(xpwC:GrandParentHandle) Promoted LONG CODE Kit = xpwC:Handle xpStartWSChildScan(Kit,kitKey) LOOP xpNextWSChildScan(Kit,kitKey) IF ~kitKey THEN BREAK. CASE xpwC:RecType OF xpSummaryRecord Child = xpwC:Handle !{{{ scan for in-use returns below this summary !We must look for in-use returns even for modules, 'cos those returns !go 'above' us to our grandparent. So we check for those first. Promoted = FALSE xpStartWSChildScan(Child,childKey) LOOP WHILE ~Promoted xpNextWSChildScan(Child,childKey) IF ~childKey THEN BREAK. CASE xpwC:RecType OF xpStockUseRecord OROF xpContainerNeedRecord !Must check returns under these GreatGrandParent = xpwC:GrandParentHandle GrandChild = xpwC:Handle xpStartWSChildScan(GrandChild,grandChildKey) LOOP WHILE ~Promoted xpNextWSChildScan(GrandChild,grandChildKey) IF ~grandChildKey THEN BREAK. !This can only be a return record. !Our great grandparent is the summary that may need promoting 'cos that's !what creates this return. This is the grandparent of the stock use. IF xpZapSchedule:Promoted(GreatGrandParent,xpwC:Handle,LogDetail) !NB: Once we've promoted one we don't need to look for any more ! returns 'cos they've all got the same great grand parent Promoted = TRUE KitPromoted += 1 END END END END !NB: Child must be preserved across this logic !}}} Err# = xpLoadWS(Child,01181212) IF Err# THEN DO ProcedureExit. IF FALSE !18/12/12 DCN Need to go lower in case its now an orphan-->xpwC:IsModule AND xpwC:Method <> xpStockUseRecord !Its already a module (or been made so by the returns scan above) !A StockUse method means its only using stock - IsModule meaningless for those IF LogDetail xpWriteLog(,xpwC:Handle,LogDetail,'Skip promoting: @, already a module',zvWS,xpwC:Handle) END ELSE !{{{ scan for in-use actualisers below this summary !This is only looking for actualisers 'cos we've already checked for returns. Promoted = FALSE Child = xpwC:Handle xpStartWSChildScan(Child,childKey) LOOP WHILE ~Promoted xpNextWSChildScan(Child,childKey) IF ~childKey THEN BREAK. CASE xpwC:RecType OF xpMadeRecord OROF xpInventedRecord OROF xpBuyRecord OROF xpSubContractRecord OROF xpReturnRecord OROF xpOutputRecord !Our parent is the summary that may need promoting IF xpZapSchedule:Promoted(xpwC:ParentHandle,xpwC:Handle,LogDetail) Promoted = TRUE KitPromoted += 1 END END END !}}} IF ~Promoted !Not found an edge, go down to our child summaries Err# = xpLoadWS(Child,04070312); IF Err# THEN DO ProcedureExit. DO PromoteKit END END END END !}}} !{{{ ProcedureExit ROUTINE ProcedureExit ROUTINE IF ScanStarted ScanStarted = FALSE xpStopWSChildScan(Summary,scanKey,scanKey2) END IF ~Omitted(PromotionsP) Promotions = KitPromoted END xpSetLogContext() IF OldLogDepth THEN xpSetLogDepth(OldLogDepth). IF Err# RETURN RetCode:Error ELSE RETURN RetCode END !}}} !{{{ xpZapSchedule:Promoted FUNCTION(ParentWS,ChildWS) !{{{ history ! 08/03/12 DCN Created ! 18/12/12 DCN Add LogLevel param ! 27/11/23 DCN Use xpAllocateWONum() not DIY !}}} !{{{ description !This is a helper for xpZapSchedule. !It checks if the given actualiser is in use and if it is, promotes !the given summary to an IsModule boundary. !xpZapSchedule:Promoted(LONG,LONG,LONG=0),LONG ! ! ! ! !TRUE iff it was promoted or was already promoted ! ! ! !log detail level to use for non-alerts, 0=no logs ! ! !the actualiser WS to check ! !the parent summary !}}} xpZapSchedule:Promoted FUNCTION(ParentWS,ChildWS,LogLevel) OldWONum LIKE(xpwC:WONum) NewWONum LIKE(xpwC:WONum) CODE IF xpGetStockUse(,xpwC::Id,ChildWS,,,xpStockUse:All) !*MUST* look at *all* allocations !Its in-use, promote this summary (we must also give it a new WO#) xpWriteLog(,ParentWS,-1,'Side-effect/return in use: @',zvWS,ChildWS) Err# = xpLoadWS(ParentWS,03070312); IF Err# THEN RETURN FALSE. OldWONum = xpwC:WONum IF xpwC:IsModule AND xpwC:Method <> xpStockUseRecord !its already a module IF LogLevel xpWriteLog(,ParentWS,LogLevel,'Not promoting: @, already a module',zvWS,ParentWS) END RETURN TRUE END !This is mimicing what xpMakeMaterial does xpAllocateWONum(,4,'xpZapSchedule:Promoted') xpwC:WODepth = 1 xpwC:IsModule = TRUE Err# = xpPutWS(); IF Err# THEN RETURN FALSE. NewWONum = xpwC:WONum xpWriteLog(,ParentWS,-1,'Promoting WO#@ as WO#@: @',zvInt,OldWONum,zvInt,NewWONum,zvWS,ParentWS) Err# = xpZapSchedule:SetNewWONum(xpwC:ParentHandle,xpwC:Handle,OldWONum,NewWONum); IF Err# THEN RETURN FALSE. RETURN TRUE ELSE IF LogLevel xpWriteLog(,ParentWS,LogLevel,'Side-effect/return NOT in use: @',zvWS,ChildWS) xpWriteLog(,ParentWS,LogLevel,'Not promoting: @, side-effect/return not used',zvWS,ParentWS,zvWS,ChildWS) END RETURN FALSE END !}}} !{{{ xpZapSchedule:SetNewWONum FUNCTION(TopHandle,Child,OldWONum,NewWONum) !{{{ history ! 08/03/12 DCN Created ! 27/11/23 DCN Use xpAllocateWONum not DIY ! 11/12/23 DCN Do not use xpAllocateWONum (the given NewWONum has already been allocated) !}}} !{{{ description !This is a recursion helper for xpZapSchedule. !It sets a new WO# in all the children of the given worksheet. !xpZapSchedule:SetNewWONum(LONG,LONG,LONG,LONG),LONG ! ! ! ! ! !ErrorCode ! ! ! ! !the new WO# to set ! ! ! !the old WO# ! ! !Handle - the worksheet whose children need to be set ! !TopHandle - see below !The TopHandle is the destination of any stock created by the child. !Any xpStockUseRecords with this as their destination (i.e. their !grandparent) are not part of the works order whose number is being !changed. Nor are any returns under such stock-use records. !They (now) represent an issue across a works order boundary. !}}} xpZapSchedule:SetNewWONum FUNCTION(TopHandle,Child,OldWONum,NewWONum) childKey LONG CODE xpStartWSChildScan(Child,childKey) LOOP xpNextWSChildScan(Child,childKey) IF ~childKey THEN BREAK. IF (xpwC:RecType = xpStockUseRecord OR xpwC:RecType = xpContainerNeedRecord) | AND xpwC:GrandParentHandle = TopHandle THEN !This is an issue across the (new) WO boundary, leave it alone CYCLE END IF xpwC:WONum = OldWONum !Change the WO xpwC:WONum = NewWONum !do *NOT* use xpAllocateWONum here, its already been allocated by the caller Err# = xpPutWS(); IF Err# THEN RETURN Err#. IF xpwC:RecType = xpStockUseRecord OR xpwC:RecType = xpContainerNeedRecord !Move the allocation to its new WO Err# = xpReAllocWONum() IF Err# THEN RETURN Err#. END END !Do this even if not same WO# 'cos its safe and ensures we find !stock use records that are below a 'foreign' summary. Err# = xpZapSchedule:SetNewWONum(TopHandle,xpwC:Handle,OldWONum,NewWONum) IF Err# THEN RETURN Err#. END RETURN 0 !}}} !}}} !{{{ xpFinishSummary !{{{ history ! 17/06/13 DCN Created by extraction from xpCreateScheduleHelper ! 30/12/20 DCN Don't give mcGetCost a pack qty (its the same as the qty) ! 24/05/21 DCN [61703]Give mcGetCost a PackQty !}}} !{{{ description !Finalise the given summary. !This sets the results of some schedule in the worksheet. !A summary worksheet's time-line is xpCreateSummary followed !by either xpFinishSummary or xpDestroySummaryWS. !xpFinishSummary(LONG SummaryWS,LONG Method ,BYTE UpdateCost=0,| ! STRING TotalQty ,STRING TotalCost ,| ! LONG StartDate ,LONG StartTime ,| ! LONG EndDate ,LONG EndTime ,| ! LONG RaiseDate=0,LONG RaiseTime =0 ),LONG,PROC !}}} xpFinishSummary FUNCTION(SummaryWS,Method,UpdateCost, | TotalQty,TotalCost, | StartDate,StartTime,| EndDate,EndTime, | RaiseDate,RaiseTime ) TargetCost LIKE(xpwC:CreateValue) CODE Err# = xpLoadWS(SummaryWS,9801126); IF Err# THEN RETURN Err#. IF UpdateCost AND xpwC:Material AND mcUseTargetCosts(xpwC:Material) !Note that the target lead time is handled by the call to !mcGetOrderConstraints() and is *already* integrated !into the schedule as necessary by the resource allocator. TargetCost = mcGetCost(xpwC:Material,,TotalQty,xpwC:Length,xpwC:Width,xpwC:Height,TotalQty,,mcGetCost:NeverRefresh) IF umIsBigger(TargetCost,TotalCost) !Adjust cost up to meet target xpWriteLog(,SummaryWS,-1,'Adjusting cost of @ of @ for min cost of @',| zvMaterial,xpwC:Material,zvMoney,TotalCost,zvMoney,TargetCost) TotalCost = TargetCost END END xpwC:StartDate = StartDate !Earliest WO/PO start xpwC:StartTime = StartTime !.. xpwC:EndDate = EndDate !Latest done PO/WO xpwC:EndTime = EndTime !.. xpwC:CreateQuantity = TotalQty !total *allocated* (NB: This is in the demand frame, i.e. xpwC:Length/Width/Height) xpwC:CreateValue = TotalCost xpwC:UsedQuantity = TotalQty !total *allocated* xpwC:UsedValue = TotalCost xpwC:UsedDate = EndDate xpwC:UsedTime = EndTime !{{{ set method xpwC:Method = Method IF xpwC:Method = xpStockUseRecord !Consider a demand satisfied from stock to be a module boundary !('cos it was some previous WO/PO that created it) xpwC:IsModule = TRUE END !}}} IF RaiseDate !this is the true WO raise date, the one in the make record is for the last step xpwC:RaiseDate = RaiseDate xpwC:RaiseTime = RaiseTime END Err# = xpPutWS(); IF Err# THEN RETURN Err#. RETURN 0 !}}} !{{{ xpUnSchedule !{{{ history ! ! 30/3/95 DCN Created ! 2/4/95 DCN Allow for multiple schedules per owner ! 12/4/95 DCN Use zfSet not Set ! 1/6/95 DCN The XPT is now identified yb TopXPW and not Owner ! 2/6/95 DCN Remove the loop to remove multiple schedules (its now valid) ! 4/6/95 DCN Add the state param ! 18/7/95 DCN Re-jig such that XPT is not in the transaction (to save a LogOut) ! 21/9/95 CRJ Added xpTentativeSchedule to allow so to be free osf WO constants. ! 21/6/96 CRJ No reserved or committed states now. ! 8/7/96 CRJ Use Q ! 18/09/00 DCN Force WS deletion so that trace goes and itself always goes ! 21/10/02 DCN Ditch the owner index too ! 14/06/03 DCN Add PreserveAlerts option ! 06/04/04 DCN Add Instance param ! 13/08/11 DCN Use xpIsScheduled not xpGetWorksheet ! 21/08/11 DCN Allow for being given a WS owner ! Allow for selective alert preservation ! 26/08/11 DCN If we're the last top of a meta-summary, chuck the meta-summary too ! If we're the last actualiser under a summary, chuck the summary too ! 27/08/11 DCN Get rid of all stock use records, depth first, then do the rest ! Allow for xpNextWSChildScan breaking during a scan due to 'self' being deleted. ! Allow a stock use record to be 'un-scheduled' - means de-allocate and delete ! 03/09/11 DCN Allow a return record to be 'un-scheduled' - means delete ! 10/11/11 DCN Use xpwC:Sibling not xpwC:Ancester ! 25/11/11 DCN Ignore xpDeleteLogFor return value ! 17/02/12 DCN Don't delete stock use records with the parent, do them with the grandparent ! unless told otherwise ! Take a WS handle as param, not an owner ! 08/03/12 DCN Demote summary method to stock-use if all its actualisers are dumped ! 28/02/13 DCN Add xpContainerNeedRecord ! 14/03/13 DCN Call xpStopWSChildScan ! !}}} !{{{ description ! This undoes the schedule made in some previous call to xpSchedule. ! It deletes all necessary WS records. The worksheet does not exist ! after this operation. The ErrorCode() is returned if there was an ! error or 0 if all's well. This function is a silent no-op if the ! given owner has not been scheduled. ! ! xpUnSchedule(LONG,LONG=0,LONG=0),LONG ! ! ! ! !ErrorCode() or 0 ! ! ! ! 0=do not preserve alerts ! ! ! !<0=preserve all alerts ! ! ! !>0=preserve alerts up to this pass number ! ! !iff TRUE unconditionally un-schedule summaries, ! ! !else preserve stock use to its parent ! !WS handle to un-schedule ! ! This 'undoes' a schedule. The SummaryWS tree is recursed down and the ! appropriate 'undo' operation performed on all the components at the ! bottom. The parent SummaryWS is then deleted. Thus the schedule is ! undone depth first. ! ! If the given schedule is the last 'top' of its meta-summary, the meta-summary ! is also removed and also the owner is also removed from the owner list. ! After this function completes, the scheduler state is as if the demand ! never existed (apart from orphans and the trace logs). ! When given a WS handle instead of an owner, that handle may be for a summary ! or an actualiser (buy, make, etc.) or a stock use. In all cases behaviour is ! consistent. E.g. if a make handle was given, its parent summary is unscheduled, ! and if that summary is the last in its parent, that'll go too, et al, all the ! way up to the top. !{{{ worksheet relations considered ! ! TopHandle--> MetaSummary(me) ! | | ! | +----------------+ ! | | ! V V ! GrandParentHandle--> TopSummary(me) TopSummary (JIT/SPLIT clones - see xpSchedule) ! | | ! | +----------------+ ! | | ! V V ! ParentHandle--> Summary(me)____ Summary (NOMIX/MAXSTOCK stock clones - see xpCreateSchedule) ! | | | | | | ! | | | | | +--------------+ ! | | | | +----------+ | ! | | | +------+ | V ! | | +--+ | | StockUse (stock issued to above) (see note 1) ! | | | | V ! | | | | Summary (split batches - see xpMakeMaterial) ! | | | V ! | | | Summary (kit for the actualiser) ! | | V ! | | Buy (split buys - see xpBuyMaterial ! | | ! | +---->Stuff put there by xpMakeMaterial ! V ! Handle--> Actualiser(me) ! ! (1) - the stock use record is 'owned' by the grandparent ('cos that's where ! the stock is going), so a stock use record is deleted when its grandapent ! is, not when its parent is. This is significant when the source of the ! stock use is outside the structure being deleted (eg. its a stock allocation) ! ! We can be given anything in this tree, and we 'eat' our way up it. ! If a tree delete leaves our parent orphaned then the parent goes too. ! Ditto, its grandparent, all the way to the top. Split batches are ! recognised by their ancestry - they'll be the same as the actualiser ! parent. ! !}}} !xpRemoveOrphans, xpIsOrphan and xpUnschedule should be considered a !single 'item', their behaviours are mutually dependant. !}}} xpUnSchedule FUNCTION(GivenHandle,Forced,PreserveAlerts) !{{{ data Handle LIKE(xpwC:Handle) SelfHandle LIKE(xpwC:Handle) ParentHandle LIKE(xpwC:ParentHandle) !}}} CODE Handle = xpIsScheduled(xpwC::Id,GivenHandle) !NB: The WS is loaded and checked by this. IF ~Handle THEN RETURN 0. CASE xpwC:RecType OF xpSummaryRecord IF Forced DO Unallocate ELSE !{{{ un-pick all actualisers SelfHandle = 0 DO UnpickKit !}}} END OF xpMadeRecord OROF xpInventedRecord OROF xpBuyRecord OROF xpSubContractRecord !{{{ un-pick just this actualiser Err# = xpLoadWS(xpwC:ParentHandle,01260811); IF Err# THEN RETURN Err#. SelfHandle = Handle DO UnpickKit !}}} OF xpStockUseRecord OROF xpContainerNeedRecord OROF xpReturnRecord !Drop through to un-schedule this ELSE xpWriteLog(xpwC:TopHandle,xpwC:Handle,-1,'@:Attempt to un-schedule ' & xpRecordType(xpwC:RecType),| zvMaterial,xpwC:Material) RETURN xpDisplayError(,'xpUnschedule: Not allowed to un-schedule a ' & xpRecordType(xpwC:RecType) & ' record!',gxInternalErr) END LOOP Err# = xpLoadWS(Handle,02260811); IF Err# THEN RETURN Err#. ParentHandle = xpwC:ParentHandle DO Unschedule; IF Err# THEN RETURN Err#. IF ~ParentHandle !We've done the top - so that's it BREAK ELSIF xpHasChildren(ParentHandle) !Our parent is not an orphan - so that's it !But demote the summary iff necessary RETURN xpUnSchedule:DemoteSummary(ParentHandle) ELSE !Our parent is now an orphan - do that too Handle = ParentHandle END END RETURN 0 !{{{ UnpickKit ROUTINE !This unschedules all the kit of the current (summary) worksheet. !Find all our kit, this will be all the summaries under ourselves that !are not split-batches of us. NB: If there is kit, then we are a make or !sub-con and all the kit summaries are ours. Specifically, if we're a buy !there will not be any. This is significant because there can be many buy !actualisers under a single summary, but only 1 make or sub-con. !There are 2 contexts this is called from: ! (1) When un-picking a given actualiser ! (2) When un-picking a given summary !For case (1) we only delete actualiser records we discover that are !the one given. For case (2) they all go. The two cases are differentiated !by the caller setting SelfHandle. Its set to 0 for (1) and the given !handle for (2). UnpickKit ROUTINE DATA Sibling LIKE(xpwC:Sibling) scanKey LONG scanKey2 LONG CODE ParentHandle = xpwC:Handle Sibling = ParentHandle !{{{ pass 1 - un-hook all the kit !We must remove all kit allocations first, else can't delete the creators xpStartWSChildScan(ParentHandle,scanKey) LOOP xpNextWSChildScan(ParentHandle,scanKey) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpSummaryRecord IF xpwC:Sibling = Sibling THEN CYCLE. !ignore our split batches !Chuck this kit item DO Unallocate !un-hook its stock first (all the way to the bottom) END END !}}} !{{{ pass 2 - un-schedule stuff xpStartWSChildScan(ParentHandle,scanKey,scanKey2) LOOP xpNextWSChildScan(ParentHandle,scanKey,scanKey2) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpSummaryRecord IF xpwC:Sibling = Sibling THEN CYCLE. !ignore our split batches !Chuck this kit item OF xpStockUseRecord OROF xpContainerNeedRecord !This is a stock use that is a peer of our actualiser. !That can only be an allocation to our parent from us. !So it must go too. But only if its 'ours'. To be ours !its source must be our actualiser. IF ~SelfHandle THEN CYCLE. !we're not doing a specific actualiser, so ignore it IF xpwC:InstanceFile <> xpwC::Id OR xpwC:InstanceRec <> SelfHandle THEN CYCLE. !its not ours, ignore it !Its ours, chuck it OF xpBuyRecord OROF xpMadeRecord OROF xpSubContractRecord IF SelfHandle AND xpwC:Handle <> SelfHandle THEN CYCLE. !ignore other actualisers that are not us !Chuck self actualiser ELSE !Chuck self stuff: resources, outputs, returns, etc. END !03/09/11 DCN This will fail if the kit is dimensioned and has generated ! a return and that return is in use. In that context the ! actualiser cannot be un-scheduled. So only kit that is an ! orphan can be un-scheduled. DO Unschedule IF Err# xpStopWSChildScan(ParentHandle,scanKey,scanKey2) RETURN Err# END END xpStopWSChildScan(ParentHandle,scanKey,scanKey2) !}}} !We're gone now - if the summary is now empty, promote to doing that IF xpHasChildren(ParentHandle) !Nope - its still got stuff, so we're done now !But demote the summary iff necessary RETURN xpUnSchedule:DemoteSummary(ParentHandle) END !The summary is now an orphan - so promote to that Handle = ParentHandle !}}} !{{{ Unschedule ROUTINE Unschedule ROUTINE DATA TopHandle LIKE(xpwC:TopHandle) OurHandle LIKE(xpwC:Handle) OwnerFile LIKE(xpwC:OwnerFile) OwnerRec LIKE(xpwC:OwnerRec) OwnerInstance LIKE(xpwC:OwnerInstance) CODE !Note what we're doing TopHandle = xpwC:TopHandle OurHandle = xpwC:Handle !Get the real owner (in case we were given a WS initially) OwnerFile = xpwC:OwnerFile OwnerRec = xpwC:OwnerRec OwnerInstance = xpwC:OwnerInstance IF PreserveAlerts !{{{ move the log !This must be done before delete the worksheet 'cos that deletes the logs. !So we do this first so it can't see them. IF PreserveAlerts < 0 THEN PreserveAlerts = 0. !set to preserve all xpDeleteLogFor(OurHandle,OwnerFile,OwnerRec,PreserveAlerts) !}}} END Err# = xpDestroySummaryWS(OurHandle,TRUE) !force it all to go, inc logs IF TopHandle = OurHandle !We're the meta-summary - chuck the owner too (NB: This can only be a summary) Err2# = xpRemoveOwner(OwnerFile,OwnerRec,OwnerInstance) ELSE !Leave owner alone for these Err2# = 0 END IF Err2# AND ~Err# Err# = Err2# END !}}} !{{{ Unallocate ROUTINE !We need to get rid of our stock use records first else we'll !get instance in use errors when we try to delete actualisers !that are feeding self. We must recurse right through the !structure to do this so that the lot are gone before we try !to remove the actualisers that created the stock being allocated. Unallocate ROUTINE Err# = xpRemoveStockAlloc(xpwC:Handle) IF Err# THEN RETURN Err#. !}}} !{{{ xpUnSchedule:DemoteSummary FUNCTION(ParentHandle) !{{{ history ! 08/03/12 DCN Created !}}} !{{{ description !If the given summary handle has nothing but stock-use records below !it, demote its method to stock-use. This is important to stop the !summary being considered as an IsModule boundary, which it no longer is. !xpUnSchedule:DemoteSummary(LONG),LONG,PROC ! ! !ErrorCode ! !Handle to check !The worksheet buffer is trashed by this function. !}}} xpUnSchedule:DemoteSummary FUNCTION(ParentHandle) scanKey LONG CODE xpStartWSChildScan(ParentHandle,scanKey) LOOP xpNextWSChildScan(ParentHandle,scanKey) IF ~scanKey THEN BREAK. CASE xpwC:RecType OF xpStockUseRecord OROF xpContainerNeedRecord !OK ELSE RETURN 0 END END Err# = xpLoadWS(ParentHandle,01080312) IF Err# THEN RETURN Err#. IF xpwC:RecType = xpSummaryRecord AND xpwC:Method <> xpStockUseRecord xpwC:Method = xpStockUseRecord Err# = xpPutWS() END RETURN Err# !}}} !}}} !{{{ xpDestroySummaryWS !{{{ history ! ! 30/3/95 DCN Created ! 6/4/95 DCN Re-jig to detect errors better ! 18/4/95 DCN Undo things in the EXACT reverse order to their 'doing' ! 8/7/96 CRJ Use Q ! 18/09/00 DCN Add Forced param ! 02/10/01 DCN xpPurgeTypes API ! 15/02/03 DCN Make benign to missing WS ! 22/03/04 DCN Add OnlyChildren option ! 01/03/13 DCN xpPurgeTypes API ! 18/03/13 DCN xpDeleteWS API ! !}}} !{{{ description ! ! This function destroys the given WS and its children. It does this ! by calling the 'undo' operation for each of the stock allocation, ! buying and making schedulers. It then destroys itself. ! ! This function is assumed to be called inside a monitor session. ! ! It returns 0 if successful or the ErrorCode() otherwise. ! ! The summary WS is assumed to be loaded. ! ! xpDestroySummaryWS(LONG,BYTE=0,BYTE=0),LONG,PROC ! ! ! ! !Int - 0 or error code ! ! ! !Flag - iff TRUE only do children, not self ! ! !Flag - iff TRUE force delete of xpwC even if tracing backtracking ! !Handle - the summary WS ! !}}} xpDestroySummaryWS FUNCTION(SummaryWS,Forced,OnlyChildren) !{{{ data Options LONG !}}} CODE IF ~xpExistsWS(SummaryWS) THEN RETURN 0. IF OnlyChildren xpSetLogdepth(+1,'Destroy children @',zvWS,SummaryWS) ELSE xpSetLogdepth(+1,'Destroy summary @',zvWS,SummaryWS) END xpSetLogContext(,,xpLogOpt:DestroySummaryWS) IF Forced Options = xpPurge:Forced ELSE Options = 0 END Err# = xpPurgeTypes(SummaryWS,,Options) IF ~Err# AND ~OnlyChildren Err# = xpDeleteWS(SummaryWS,Options) END xpSetLogContext() xpSetLogdepth(-1) RETURN Err# !}}} !{{{ xpGetPeers !{{{ history ! 10/11/11 DCN Created !}}} !{{{ description !Given a summary WS return a list of all the peers of that summary. !Peers are all sibling summaries of a top summary. Otherwise a !summary has no peers. Self is never added to the peer list. !xpGetPeers(LONG,gxRecNoQType,BYTE=0),LONG,PROC ! ! ! ! !ErrorCode ! ! ! !iff TRUE be silent about errors ! ! !List of peer summaries. ! !Handle - the summary to check !The WS buffer is preserved. !}}} xpGetPeers FUNCTION(SummaryWS,PeerQ,Silent) SavedWS LONG scanKey LONG PeerParent LONG CODE Free(PeerQ) SavedWS = xpSaveWS() LOOP Err# = xpLoadWS(SummaryWS,01101111,Silent) IF Err# THEN BREAK. IF xpwC:RecType <> xpSummaryRecord THEN BREAK. !not a summary IF xpwC:GrandParentHandle THEN BREAK. !not a meta or a top IF ~xpwC:ParentHandle THEN BREAK. !not a top PeerParent = xpwC:ParentHandle xpStartWSChildScan(PeerParent,scanKey) LOOP xpNextWSChildScan(PeerParent,scanKey) IF ~scanKey THEN BREAK. !that's it IF xpwC:RecType <> xpSummaryRecord THEN CYCLE. !ignore non-summaries IF xpwC:Sibling <> SummaryWS THEN CYCLE. !not a sibling IF xpwC:Handle = SummaryWS THEN CYCLE. !ignore self !Found a sibling PeerQ.RecNo = xpwC:Handle Add(PeerQ) Err# = gxErrCode() IF Err# IF ~Silent THEN gxDisplayError(,'xpGetPeers: Add(PeerQ)',Err#). BREAK END END BREAK END xpRestoreWS(SavedWS) RETURN Err# !}}} !{{{ xpGetGranularity !{{{ history ! 16/11/11 DCN Created by extraction from xpSchedule ! 18/11/11 DCN Don't let the MinTryQty be bigger than the required qty ! 02/01/12 DCN Normalise ReOrderQty before use it ! 10/10/17 DCN [55482]Don't allow MinTryQty to go below (Cycles)ExtraQty ! 03/03/18 DCN xpSetExtraQty API ! 23/11/23 DCN Allow for ExtraQty not being in ref units !}}} !{{{ description !For the currently loaded worksheet, calculate the batching granularity, !the JIT/SPLIT and MAXSTOCK minimum probe quantities. !xpGetGranularity(STRING,BYTE=0,*STRING,*STRING) ! ! ! ! !Measure - minimum try quantity ! ! ! !Measure - quantise qty = probe step size ! ! !iff TRUE ignore min batch and re-order qty limits ! !Measure - the limit qty from the context !All quantities are returned in units of the required qty. !}}} xpGetGranularity PROCEDURE(LimitQty,IgnoreConstraints,QuantiseQty,MinTryQty) ReOrderQty LIKE(xpwC:ReOrderQty) !a lower limit on min try qty MinBatchQty STRING(12) !..and another one ExtraQty STRING(12) !....and yet another one CODE !Get the ref qty in the same units as the required qty QuantiseQty = mcNormaliseTo(xpwC:Material,xpwC:RefQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:RefLen,xpwC:RefWid,xpwC:RefHeight) IF umIsZero(QuantiseQty) !paranoia QuantiseQty = umLike(xpwC:RequiredQty,1) END IF xpwC:MakeUnitSize > umNoise:Value QuantiseQty = umScale(QuantiseQty,xpwC:MakeUnitSize) END IF umIsZero(LimitQty) !Lower limit is the min possible in this case MinTryQty = QuantiseQty ELSE !Get the min try qty in the same units as the required qty MinTryQty = mcNormaliseTo(xpwC:Material,LimitQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:RefLen,xpwC:RefWid,xpwC:RefHeight) END !The min must not be below the quanta IF umIsSmaller(MinTryQty,QuantiseQty) THEN MinTryQty = QuantiseQty. IF umIsBigger(MinTryQty,xpwC:RequiredQty) !Don't be daft! MinTryQty = xpwC:RequiredQty END IF ~IgnoreConstraints !{{{ apply constraints !The min try qty must not be less than the min batch or re-order qty else this can happen: ! Re-order qty is 200 ! Demand is 248 ! No-can-do 248 but can-do 200, residue is 48 ! Residue of 48 gets jacked up to 200 !This is highly unlikely to be desirable. IF xpwC:MinBatch > umNoise:Value MinBatchQty = umScale(QuantiseQty,xpwC:MinBatch) ELSE MinBatchQty = umZeroVal END IF umIsPositive(xpwC:ReOrderQty) ReOrderQty = mcNormaliseTo(xpwC:Material,xpwC:ReOrderQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:RefLen,xpwC:RefWid,xpwC:RefHeight) ELSE ReOrderQty = umZeroVal END !We must make sure we do not probe below the minimum usable quantity in all cases. xpSetExtraQty(,,ExtraQty,TRUE) !forced and +1 so extra achieves an effective yield of at least 1 !Get the extra qty in the same units as the required qty ExtraQty = mcNormaliseTo(xpwC:Material,ExtraQty,xpwC:Length,xpwC:Width,xpwC:Height, | xpwC:RequiredQty,xpwC:RefLen,xpwC:RefWid,xpwC:RefHeight) IF umIsSmaller(MinTryQty,MinBatchQty) THEN MinTryQty = MinBatchQty. IF umIsSmaller(MinTryQty,ReOrderQty ) THEN MinTryQty = ReOrderQty. IF umIsSmaller(MinTryQty,ExtraQty ) THEN MinTryQty = ExtraQty. !}}} END !}}} !{{{ xpSetOrderQty !{{{ history !01/03/18 DCN Created !}}} !{{{ description !Calculate the order quantity required for the given demand. !This is used to ensure a buy/make demand meets all the applicable !batch size constraints. !xpSetOrderQty(LONG,*STRING,STRING) ! ! ! !zvMeasure - the demand quantity ! ! !zvMeasure - where to put the result ! !zvWS - the context worksheet (to access CyclesExtra and MinUseBatch) ! the current WS buffer is preserved across this call !The order quantity must be the greater of (demand+CyclesExtra) or MinUseBatch. !}}} xpSetOrderQty PROCEDURE(WS,OrderQty,NeedQty) ExtraQty STRING(12) CODE OrderQty = NeedQty !04/03/18 DCN This is too early to apply this as it assumes the buy/make loops ! do not split batches. They can so its invalid to apply this here. ! Instead it must be considered within the buy/make loops explicitly. !xpSetExtraQty(WS,NeedQty,ExtraQty) !OrderQty = umAdd(NeedQty,ExtraQty) !}}} !{{{ xpIsRaceWinByDate !{{{ history ! 20/02/12 DCN Creeated by extraction from xpCreateScheduleHelper ! 22/02/12 DCN Re-arrange params ! 24/02/12 DCN Add virtual return limit ! 18/01/13 DCN Add urgent option ! 15/03/13 DCN Use correct guard for scheduled return ! 26/03/15 DCN Allow for a threshold of 'None' (i.e. -1) ! 20/05/18 DCN [56498]Consider HasLateKit !}}} !{{{ description !Check the stock date/time we've achieved is allowed as a WIP race win. !xpIsRaceWinByDate(LONG,LONG,LONG,LONG,LONG,*STRING,LONG=0,LONG=0,LONG=0,LONG=0),LONG ! ! ! ! ! ! ! ! ! ! ! !TRUE if this is a legitimate race win ! ! ! ! ! ! ! ! ! ! !schedule target time when doing JIT ! ! ! ! ! ! ! ! ! !schedule target date when doing JIT ! ! ! ! ! ! ! ! !TRUE iff doing JIT ! ! ! ! ! ! ! !TRUE iff urgent ! ! ! ! ! ! !the name of the WIP limit used (for log messages) ! ! ! ! ! !existing stock status flags ! ! ! ! !existing time ! ! ! !existing date ! ! !time achieved ! !date achieved !}}} xpIsRaceWinByDate FUNCTION(NewStockDate,NewStockTime, | ExistingDate,ExistingTime,ExistingStatus,| ThresholdName,Urgent, | WorkBackwards,ConsiderDate,ConsiderTime ) IsRaceWin LONG Threshold LONG CODE IsRaceWin = TRUE IF xpOlder(NewStockDate,NewStockTime,ExistingDate,ExistingTime) !{{{ we did better, but is it enough? !NB: If we get here it means we were racing and the existing stock was ! too late. The result here may still be too late. If we're doing ASAP ! the final date/time does not matter, but if we're doing JIT its a ! show stopper. The logic here just determines whether its a stopper or ! not. That's indicated by returning FALSE. Note: When doing JIT there ! can be no tolerance threshold, it either has to meet our date or its ! useless. Also, when urgent the tolerance must be very tight. !Work out what our context is. There are 4: waiting for WIP (aka committed) !or planned (aka reserved) or a scheduled return (aka virtual return) or a !schedule (aka virtual). The context affects which tolerance threshold to apply. !The limits must be checked in committed then planned then returns then !schedule order. The presumption is that this will represent a tighter and !tighter time-line. IF BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) !We were waiting for WIP before, use the WIP thresholds Threshold = xpWait4LateWIPLimit:Value ThresholdName = 'WIP' ELSIF BAND(ExistingStatus,xpStkFlgHasReserved) !We were waiting for planned before, use the plan thresholds Threshold = xpWait4LatePlanLimit:Value ThresholdName = 'planned' ELSIF BAND(ExistingStatus,xpStkFlgHasVirtual+xpStkFlgHasReturn) = (xpStkFlgHasVirtual+xpStkFlgHasReturn) !We were waiting for a scheduled return before, use the return thresholds Threshold = xpWait4LateReturnLimit:Value ThresholdName = 'return' ELSIF BAND(ExistingStatus,xpStkFlgHasVirtual) !We were waiting for a schedule before, use the schedule thresholds Threshold = xpWait4LateSchedLimit:Value ThresholdName = 'schedule' ELSE !can't get here !We were waiting for virtual before, threshold is 0 Threshold = 0 ThresholdName = 'virtual' END IF Urgent AND Threshold THEN Threshold = 1. !go tight on tolerance if urgent (see also xpIsRaceCandidate) IF (Threshold >= 0) AND (NewStockDate+Threshold <= ExistingDate) !We just tried to beat the existing and succeeded by a good enough margin !If we're doing JIT, the result must also be good enough to meet the requirement IF WorkBackwards AND xpOlder(ConsiderDate,ConsiderTime,NewStockDate,NewStockTime) !The result is still not good enough for JIT IsRaceWin = FALSE END ELSE !what we just scheduled is not better enough than the existing schedule, !so not a race win IsRaceWin = FALSE END !}}} ELSE !what we scheduled failed or is no better than the existing schedule, !so its not a race win ThresholdName = '' IsRaceWin = FALSE END RETURN IsRaceWin !}}} !{{{ xpIsRaceCandidate !{{{ history ! 24/02/12 DCN Created by extraction from xpCreateScheduleHelper ! 18/01/13 DCN Add urgent option ! 20/05/18 DCN [56498]Consider HasLateKit !}}} !{{{ description !Determine if we have a race candidate from the given status. !xpIsRaceCandidate(LONG=0,LONG,LONG,LONG,LONG,LONG,<*STRING>),LONG ! ! ! ! ! ! ! ! !TRUE iff its a race candidate ! ! ! ! ! ! ! !Reason for not being a candidate, omitted=don't care ! ! ! ! ! ! !iff TRUE its urgent ! ! ! ! ! !stock status of what we found ! ! ! ! !Time - what we found ! ! ! !Date - what we found ! ! !Date - what we want to check against ! !Material we're doing (for the log message), 0=no log !}}} xpIsRaceCandidate FUNCTION(Material,LookDate,ExistingDate,ExistingTime,ExistingStatus,Urgent,Reason) ! ReasonP EQUATE(7) !<---------------------------------------------------------------+ !{{{ data IsVReturn EQUATE(xpStkFlgHasVirtual+xpStkFlgHasReturn) ElapsedDays LONG Wait4LateWIPLimit LIKE(xpWait4LateWIPLimit:Value) Wait4LatePlanLimit LIKE(xpWait4LatePlanLimit:Value) Wait4LateReturnLimit LIKE(xpWait4LateReturnLimit:Value) Wait4LateSchedLimit LIKE(xpWait4LateSchedLimit:Value) !}}} CODE IF ~Omitted(ReasonP) THEN Reason = ''. !{{{ check race limits !{{{ set limits ElapsedDays = gxElapsedDays(LookDate,ExistingDate) Wait4LateWIPLimit = xpWait4LateWIPLimit:Value Wait4LatePlanLimit = xpWait4LatePlanLimit:Value Wait4LateReturnLimit = xpWait4LateReturnLimit:Value Wait4LateSchedLimit = xpWait4LateSchedLimit:Value IF Urgent !If we're urgent tighten the wait limits (see also xpIsRaceWinByDate) IF Wait4LateWIPLimit <> 0 THEN Wait4LateWIPLimit = 1. IF Wait4LatePlanLimit <> 0 THEN Wait4LatePlanLimit = 1. IF Wait4LateReturnLimit <> 0 THEN Wait4LateReturnLimit = 1. IF Wait4LateSchedLimit <> 0 THEN Wait4LateSchedLimit = 1. END !}}} !{{{ check no limit cases IF BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) AND (Wait4LateWIPLimit < 0) !We're racing WIP and it has no wait limit - so no point !This means the user has decided to always wait for WIP no matter how late it is. !Bonkers! But we let them do it. IF ~Omitted(ReasonP) THEN Reason = 'no WIP wait limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing WIP @ @ (no WIP wait limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,xpStkFlgHasReserved) AND (Wait4LatePlanLimit < 0) !We're racing planned and it has no wait limit - so no point !This means the user has decided to always wait for WIP no matter how late it is. !Bonkers! But we let them do it. IF ~Omitted(ReasonP) THEN Reason = 'no plan wait limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing plan @ @ (no plan wait limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,IsVReturn)=IsVReturn AND (Wait4LateReturnLimit < 0) !We're racing a scheduled return and it has no wait limit - so no point !This means the user has decided to always wait for scheduled returns no matter how late. !Bonkers! But we let them do it. IF ~Omitted(ReasonP) THEN Reason = 'no return wait limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for scheduled return @ @ (no return wait limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,xpStkFlgHasVirtual) AND (Wait4LateSchedLimit < 0) !We're racing a schedule and it has no wait limit - so no point !This means the user has decided to always wait for schedules no matter how late. !Bonkers! But we let them do it. IF ~Omitted(ReasonP) THEN Reason = 'no sched wait limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing sched @ @ (no sched wait limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END !}}} !{{{ check within limit cases IF BAND(ExistingStatus,xpStkFlgHasCommitted+xpStkFlgHasLateKit) AND ElapsedDays < Wait4LateWIPLimit !We're racing WIP and what's there is within the race limit, so no point racing IF ~Omitted(ReasonP) THEN Reason = 'existing WIP in limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing WIP @ @ (existing in limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,xpStkFlgHasReserved) AND ElapsedDays < Wait4LatePlanLimit !We're racing a plan and what's there is within the race limit, so no point racing IF ~Omitted(ReasonP) THEN Reason = 'existing plan in limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing Plan @ @ (existing in limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,IsVReturn)=IsVReturn AND ElapsedDays < Wait4LateReturnLimit !We're racing a scheduled return and what's there is within the race limit, so no point racing IF ~Omitted(ReasonP) THEN Reason = 'existing return in limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for scheduled return @ @ (existing in limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END IF BAND(ExistingStatus,xpStkFlgHasVirtual) AND ElapsedDays < Wait4LateSchedLimit !We're racing a schedule and what's there is within the race limit, so no point racing IF ~Omitted(ReasonP) THEN Reason = 'existing sched in limit'. !{{{ log it IF Material xpWriteLog(,,-1,'@ waiting for existing Sched @ @ (existing in limit)',| zvMaterial,Material,zvDate,ExistingDate,zvClock,ExistingTime) END !}}} RETURN FALSE END !}}} !}}} RETURN TRUE !}}} !{{{ xpIsRaceWinByQty !{{{ history !11/12/23 DCN Created !}}} !{{{ description !From the given qty pair and direction determine if a race is worthwhile or a race was won !xpIsRaceWinByQty(STRING TotalQty,STRING ExistingQty,LONG WorkBackwards,STRING LogPrefix),LONG ! ! ! ! ! !TRUE if worth racing ! ! ! ! ! !or the race was won ! ! ! ! !prefix for the log ! ! ! !TRUE iff doing JIT (determines the threshold) ! ! !zvMeasure - how nuch is there already ! !zvMeasure - how much we want/made !This is called in two contexts: ! 1. When probing for existing stock to determine if its worth racing ! 2. After a race to determine if we beat existing !}}} xpIsRaceWinByQty FUNCTION(TotalQty,ExistingQty,WorkBackwards,LogPrefix) ExistingRatio REAL RaceWon LONG CODE IF ~umIsPositive(ExistingQTY) !Silly ExistingRatio = gxMaxInt ELSE !This is how the total compares to what is there already !Range is 0..1 total less than or same as existing ! 1+ total more, 2=twice as much, 10=tens times as much, etc ExistingRatio = umValueOf(umDivide(TotalQty,ExistingQty)) END IF WorkBackwards AND (ExistingRatio - 1.0 + umNoise:Value) > (xpJITwinQtyRatio:Value - umNoise:Value) !we want/made significantly more than what is there already, that is a win and/or worth a race for JIT !{{{ log it xpWriteLog(,,2,Clip(LogPrefix) & ' (^@) significantly more than existing (^@) (ratio is ^@, JIT threshold is ^@)',| zvMeasure,TotalQty,zvMeasure,ExistingQty,zvReal,ExistingRatio,zvReal,xpJITwinQtyRatio:Value) !}}} RaceWon = TRUE ELSIF ~WorkBackwards AND (ExistingRatio - 1.0 + umNoise:Value) > (xpASAPwinQtyRatio:Value - umNoise:Value) !we made significantly more than what is there already, that is a win for ASAP !if what we made is later than existing then start again to pick up the existing !this is the context where existing only partially met the requirement, but our attempt for the lot !took longer, so its worth starting again to pick up what is there already and then only have to !make the shortfall. !{{{ log it xpWriteLog(,,2,Clip(LogPrefix) & ' (^@) significantly more than existing (^@) (ratio is ^@, ASAP threshold is ^@)',| zvMeasure,TotalQty,zvMeasure,ExistingQty,zvReal,ExistingRatio,zvReal,xpASAPwinQtyRatio:Value) !}}} RaceWon = TRUE ELSE RaceWon = FALSE END RETURN RaceWon !}}}