Пример #1
0
        public override void Finalize(decimal totalTime, decimal picTime)
        {
            // compute the expiration date.
            if (!HasBeenCurrent)
            {
                return;
            }

            // see when last check would have expired
            // we have to go through each of the ones we have seen, due to 61.58(i)'s ridiculous rules
            // So, for example, suppose we see the following dates:
            //   June 8, 2012 - good until June 30, 2013
            //   June 12, 2013 - good until June 30, 2014
            //   July 9, 2014 - ONLY GOOD until June 30 (since this is in the month after it is due
            //   May 28, 2015 - GOOD UNTIL June 30 2015 (since this is in the month before it was due)
            //   Aug 8, 2016 - Good until Aug 31, 2017
            lstDatesOfCurrencyChecks.Sort();    // need to go in ascending order.
            DateTime effectiveLastCheck = lstDatesOfCurrencyChecks[0];

            foreach (DateTime dt in lstDatesOfCurrencyChecks)
            {
                DateTime dtBracketStart = effectiveLastCheck.Date.AddCalendarMonths(ExpirationSpan - 2).AddDays(1); // first day of the calendar month preceding expiration
                DateTime dtBracketEnd   = effectiveLastCheck.Date.AddCalendarMonths(ExpirationSpan + 1);            // last day of the month following expiration
                if (dtBracketStart.CompareTo(dt.Date) <= 0 && dtBracketEnd.CompareTo(dt.Date) >= 0)                 // fell in the bracket
                {
                    effectiveLastCheck = effectiveLastCheck.AddCalendarMonths(ExpirationSpan);                      // so go ExpirationSpan from that prior check
                }
                else
                {
                    effectiveLastCheck = dt;
                }
            }

            dtExpiration = effectiveLastCheck.AddCalendarMonths(ExpirationSpan);
        }
Пример #2
0
        private static void AddFAA2ndClassItems(List <CurrencyStatusItem> lst, DateTime lastMedical, bool fWas40AtExam)
        {
            CurrencyStatusItem cs = StatusForDate(lastMedical.AddCalendarMonths(12), Resources.Currency.NextMedical2ndClass, CurrencyStatusItem.CurrencyGroups.Medical);

            lst.Add(cs);
            if (cs.Status == CurrencyState.NotCurrent)
            {
                lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 24 : 60), Resources.Currency.NextMedical3rdClassPrivs, CurrencyStatusItem.CurrencyGroups.Medical));
            }
        }
Пример #3
0
 private static void AddCanadaCommercialItems(List <CurrencyStatusItem> lst, DateTime lastMedical, bool fWas40AtExam, bool fWas60AtExam)
 {
     // Canadian rules: https://laws-lois.justice.gc.ca/eng/regulations/sor-96-433/page-36.html#h-991075 and https://tc.canada.ca/sites/default/files/2021-09/AIM-2021-2_LRA-E.pdf
     // 12 months for commercial/ATP, unless (a) over 60, or (b) single-pilot and over 40
     lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas60AtExam ? 6 : 12), Resources.Currency.NextMedicalCanadaCommercial, CurrencyStatusItem.CurrencyGroups.Medical));
     if (fWas40AtExam && !fWas60AtExam)
     {
         lst.Add(StatusForDate(lastMedical.AddCalendarMonths(6), Resources.Currency.NextMedicalCanadaSinglePilot, CurrencyStatusItem.CurrencyGroups.Medical));
     }
     lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 24 : 60), Resources.Currency.NextMedicalCanadaPPL, CurrencyStatusItem.CurrencyGroups.Medical));
 }
Пример #4
0
        private static void AddFAA1stClassItems(List <CurrencyStatusItem> lst, DateTime lastMedical, bool fWas40AtExam)
        {
            CurrencyStatusItem cs = StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 6 : 12), Resources.Currency.NextMedical1stClass, CurrencyStatusItem.CurrencyGroups.Medical);

            lst.Add(cs);
            if (cs.Status == CurrencyState.NotCurrent)
            {
                if (fWas40AtExam)   // may still have some non-ATP commercial time left
                {
                    lst.Add(StatusForDate(lastMedical.AddCalendarMonths(12), Resources.Currency.NextMedical1stClassCommercial, CurrencyStatusItem.CurrencyGroups.Medical));
                }
                lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 24 : 60), Resources.Currency.NextMedical3rdClassPrivs, CurrencyStatusItem.CurrencyGroups.Medical));
            }
        }
Пример #5
0
        /// <summary>
        /// Computes the overall currency
        /// </summary>
        public void RefreshCurrency()
        {
            // Compute currency according to 61.57(c)(6) ( => (c)(3)) (i) and (ii) including each one's expiration.

            if (m_fCacheValid)
            {
                return;
            }

            // 61.57(c)(6)(i) => (c)(3)(i) -  no passengers.  IPC covers this.
            FlightCurrency fc6157c6i = fcGliderIFRTime.AND(fcGliderInstrumentManeuvers).OR(fcGliderIPC);

            // 61.57(c)(6)(ii) => (c)(3)(ii) -  passengers.  Above + two more.  Again, IPC covers this too.
            FlightCurrency fc6157c6ii = fc6157c6i.AND(fcGliderIFRTimePassengers).AND(fcGliderInstrumentPassengers).OR(fcGliderIPC);

            m_fCurrentSolo       = fc6157c6i.IsCurrent();
            m_fCurrentPassengers = fc6157c6ii.IsCurrent();

            if (m_fCurrentSolo || m_fCurrentPassengers)
            {
                if (m_fCurrentPassengers)
                {
                    m_csCurrent     = fc6157c6ii.CurrentState;
                    m_dtExpiration  = fc6157c6ii.ExpirationDate;
                    m_szDiscrepancy = fc6157c6ii.DiscrepancyString;
                }
                else // must be current solo)
                {
                    m_csCurrent     = fc6157c6i.CurrentState;
                    m_dtExpiration  = fc6157c6i.ExpirationDate;
                    m_szDiscrepancy = fc6157c6i.DiscrepancyString;
                }

                // check to see if there is an embedded gap that needs an IPC
                CurrencyPeriod[] rgcpMissingIPC = fc6157c6i.FindCurrencyGap(fcGliderIPC.MostRecentEventDate, 6);
                if (rgcpMissingIPC != null && m_szDiscrepancy.Length == 0)
                {
                    m_szDiscrepancy = String.Format(CultureInfo.CurrentCulture, Resources.Currency.IPCMayBeRequired, rgcpMissingIPC[0].EndDate.ToShortDateString(), rgcpMissingIPC[1].StartDate.ToShortDateString());
                }
            }
            else
            {
                // And expiration date is the later of the passenger/no-passenger date
                m_dtExpiration = fc6157c6i.ExpirationDate.LaterDate(fc6157c6ii.ExpirationDate);

                // see if we need an IPC
                // if more than 6 calendar months have passed since expiration, an IPC is required.
                // otherwise, just name the one that is short
                if (DateTime.Compare(m_dtExpiration.AddCalendarMonths(6).Date, DateTime.Now.Date) > 0)
                {
                    m_szDiscrepancy = String.Format(CultureInfo.CurrentCulture, Resources.Currency.DiscrepancyTemplateGliderIFRPassengers, fcGliderIFRTime.Discrepancy, fcGliderInstrumentManeuvers.Discrepancy);
                }
                else
                {
                    m_szDiscrepancy = Resources.Currency.IPCRequired;
                }
            }

            m_fCacheValid = true;
        }
Пример #6
0
 private static void AddOtherMedicalItems(List <CurrencyStatusItem> lst, DateTime lastMedical, int monthsToMedical, bool fUsesICAOMedical)
 {
     if (monthsToMedical > 0)
     {
         lst.Add(StatusForDate(fUsesICAOMedical ?
                               lastMedical.AddMonths(monthsToMedical) :
                               lastMedical.AddCalendarMonths(monthsToMedical), Resources.Currency.NextMedical, CurrencyStatusItem.CurrencyGroups.Medical));
     }
 }
Пример #7
0
        /// <summary>
        /// Computes a new due date based on the passed-in date and the regen rules
        /// </summary>
        /// <param name="dt">The date the regen is requested</param>
        /// <returns>The new due date</returns>
        public DateTime NewDueDateBasedOnDate(DateTime dt)
        {
            if (!dt.HasValue())
            {
                dt = Expiration;
            }

            switch (RegenType)
            {
            default:
            case RegenUnit.None:
                return(dt);

            case RegenUnit.Days:
                return(dt.AddDays(RegenSpan));

            case RegenUnit.CalendarMonths:
                return(dt.AddCalendarMonths(RegenSpan));

            case RegenUnit.Hours:
                throw new MyFlightbookException("Deadline is hour based, not date based!");
            }
        }
Пример #8
0
        /// <summary>
        /// Returns an enumerable of medical status based on https://www.law.cornell.edu/cfr/text/14/61.23 for FAA medicals, or else expiration and type
        /// </summary>
        /// <returns></returns>
        public static IEnumerable <CurrencyStatusItem> MedicalStatus(DateTime lastMedical, int monthsToMedical, MedicalType mt, DateTime?dob, bool fUsesICAOMedical)
        {
            // if no last medical, then this is easy: we know nothing.
            if (!lastMedical.HasValue())
            {
                return(Array.Empty <CurrencyStatusItem>());
            }

            if (RequiresBirthdate(mt) && (dob == null || !dob.HasValue))
            {
                return new CurrencyStatusItem[] { new CurrencyStatusItem(Resources.Currency.NextMedical, Resources.Currency.NextMedicalRequiresBOD, CurrencyState.NotCurrent)
                                                  {
                                                      CurrencyGroup = CurrencyStatusItem.CurrencyGroups.Medical
                                                  } }
            }
            ;

            bool fWas40AtExam = dob != null && dob.Value.AddYears(40).CompareTo(lastMedical) < 0;
            bool fWas60AtExam = dob != null && dob.Value.AddYears(60).CompareTo(lastMedical) < 0;
            bool fWas50AtExam = dob != null && dob.Value.AddYears(50).CompareTo(lastMedical) < 0;

            List <CurrencyStatusItem> lst = new List <CurrencyStatusItem>();

            switch (mt)
            {
            case MedicalType.Other:
                AddOtherMedicalItems(lst, lastMedical, monthsToMedical, fUsesICAOMedical);

                break;

            case MedicalType.EASA1stClass:
                AddEASA1stClassItems(lst, lastMedical, fWas40AtExam, fWas60AtExam);
                break;

            case MedicalType.EASA2ndClass:
                AddEASA2ndClassItems(lst, lastMedical, fWas40AtExam, fWas50AtExam, dob.Value);
                break;

            case MedicalType.EASALAPL:
                AddEASALAPLItems(lst, lastMedical, fWas40AtExam, dob.Value);
                break;

            case MedicalType.FAA1stClass:
                AddFAA1stClassItems(lst, lastMedical, fWas40AtExam);
                break;

            case MedicalType.FAA2ndClass:
                AddFAA2ndClassItems(lst, lastMedical, fWas40AtExam);
                break;

            case MedicalType.FAA3rdClass:
                lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 24 : 60), Resources.Currency.NextMedical, CurrencyStatusItem.CurrencyGroups.Medical));
                break;

            case MedicalType.CASAClass1:
                AddCASA1stClassItems(lst, lastMedical);
                break;

            case MedicalType.CASAClass2:
                AddCASA2ndClassItems(lst, lastMedical, fWas40AtExam);
                break;

            case MedicalType.CanadaPPL:
                // Canadian rules: https://laws-lois.justice.gc.ca/eng/regulations/sor-96-433/page-36.html#h-991075 and https://tc.canada.ca/sites/default/files/2021-09/AIM-2021-2_LRA-E.pdf
                // Under 40 years of age: 60 month, Over 40 years of age: 24 month
                lst.Add(StatusForDate(lastMedical.AddCalendarMonths(fWas40AtExam ? 24 : 60), Resources.Currency.NextMedicalCanadaPPL, CurrencyStatusItem.CurrencyGroups.Medical));
                break;

            case MedicalType.CanadaGlider:
                // Canadian rules: https://laws-lois.justice.gc.ca/eng/regulations/sor-96-433/page-36.html#h-991075 and https://tc.canada.ca/sites/default/files/2021-09/AIM-2021-2_LRA-E.pdf
                // Glider or ultralight - 60 months
                lst.Add(StatusForDate(lastMedical.AddCalendarMonths(60), Resources.Currency.NextMedicalCanadaGlider, CurrencyStatusItem.CurrencyGroups.Medical));
                break;

            case MedicalType.CanadaCommercial:
                AddCanadaCommercialItems(lst, lastMedical, fWas40AtExam, fWas60AtExam);
                break;
            }
            return(lst);
        }
Пример #9
0
 /// <summary>
 /// Predicted date that next BFR is due
 /// </summary>
 /// <param name="bfrLast">Date of the last BFR</param>
 /// <returns>Datetime representing the date of the next BFR, Datetime.minvalue for unknown</returns>
 public static DateTime NextBFR(DateTime bfrLast)
 {
     return(bfrLast.AddCalendarMonths(24));
 }
Пример #10
0
        /// <summary>
        /// Computes the overall currency
        /// </summary>
        protected void RefreshCurrency()
        {
            // Compute currency according to 61.57(c)(1)-(5) and 61.57(e)
            // including each one's expiration.  The one that expires latest is the expiration date if you are current, the one that
            // expired most recently is the expiration date since when you were not current.

            if (m_fCacheValid)
            {
                return;
            }

            // This is ((61.57(c)(1) OR (2) OR (3) OR (4) OR (5) OR 61.57(e)), each of which is the AND of several currencies.

            // 61.57(c)(1) (66-HIT in airplane)
            FlightCurrency fc6157c1 = fcIFRApproach.AND(fcIFRHold);

            // 61.57(c)(2) (66-HIT in an FTD or flight simulator)
            FlightCurrency fc6157c2 = fcFTDApproach.AND(fcFTDHold);

            // 61.57(c)(3) - ATD
            FlightCurrency fc6157c3 = fcATDHold.AND(fcATDApproach).AND(fcUnusualAttitudesAsc).AND(fcUnusualAttitudesDesc).AND(fcInstrumentHours);

            // 61.57(c)(4) - Combo STRICT - 6 approaches/hold in an ATD PLUS an approach/hold in an airplane PLUS an approach/hold in an FTD
            FlightCurrency fc6157c4 = fcATDAppch6Month.AND(fcATDHold6Month).AND(fcIFRHold).AND(fcAirplaneApproach6Month).AND(fcFTDApproach6Month).AND(fcFTDHold);

            // 61.57(c)(4) - Combo LOOSE - any combination that yields 66-HIT, but require at least one aircraft approach and hold.
            // FlightCurrency fc6157c4Loose = fcComboApproach6Month.AND(fcAirplaneApproach6Month).AND(fcIFRHold.OR(fcATDHold6Month).OR(fcFTDHold));

            // 61.57(c)(5) - combo meal, but seems redundant with (c)(2)/(3).  I.e., if you've met this, you've met (2) or (3).

            // 61.57 (e) - IPC; no need to AND anything together for this one.

            FlightCurrency fcIFR = fc6157c1.OR(fc6157c2).OR(fc6157c3).OR(fc6157c4).OR(fcIPCOrCheckride);

            /*
             * if (m_fUseLoose6157c4)
             *  fcIFR = fcIFR.OR(fc6157c4Loose);
             */

            m_csCurrent    = fcIFR.CurrentState;
            m_dtExpiration = fcIFR.ExpirationDate;

            if (fcIPCOrCheckride.IsCurrent() && fcIPCOrCheckride.ExpirationDate.CompareTo(m_dtExpiration) >= 0)
            {
                Query.HasHolds = Query.HasApproaches = false;
                Query.PropertiesConjunction = GroupConjunction.Any;
                Query.PropertyTypes.Clear();
                foreach (CustomPropertyType cpt in CustomPropertyType.GetCustomPropertyTypes())
                {
                    if (cpt.IsIPC)
                    {
                        Query.PropertyTypes.Add(cpt);
                    }
                }
            }

            if (m_csCurrent == CurrencyState.NotCurrent)
            {
                // if more than 6 calendar months have passed since expiration, an IPC is required.
                // otherwise, just assume 66-HIT.
                if (DateTime.Compare(m_dtExpiration.AddCalendarMonths(6).Date, DateTime.Now.Date) > 0)
                {
                    m_szDiscrepancy = String.Format(CultureInfo.CurrentCulture, Resources.Currency.DiscrepancyTemplateIFR,
                                                    fcIFRHold.Discrepancy,
                                                    (fcIFRHold.Discrepancy == 1) ? Resources.Currency.Hold : Resources.Currency.Holds,
                                                    fcIFRApproach.Discrepancy,
                                                    (fcIFRApproach.Discrepancy == 1) ? Resources.Totals.Approach : Resources.Totals.Approaches);
                }
                else
                {
                    m_szDiscrepancy = Resources.Currency.IPCRequired;
                }
            }
            else
            {
                // Check to see if IPC is required by looking for > 6 month gap in IFR currency.
                // For now, we won't make you un-current, but we'll warn.
                // (IPC above, though, is un-current).
                CurrencyPeriod[] rgcpMissingIPC = fcIFR.FindCurrencyGap(fcIPCOrCheckride.MostRecentEventDate, 6);

                if (rgcpMissingIPC != null)
                {
                    m_szDiscrepancy = String.Format(CultureInfo.CurrentCulture, Resources.Currency.IPCMayBeRequired, rgcpMissingIPC[0].EndDate.ToShortDateString(), rgcpMissingIPC[1].StartDate.ToShortDateString());
                }
                else
                {
                    m_szDiscrepancy = string.Empty;
                }
            }

            m_fCacheValid = true;
        }