package gw.plugin.job.impl uses gw.plugin.job.IPolicyRenewalPlugin uses gw.api.productmodel.Product uses gw.plugin.notification.INotificationPlugin uses gw.plugin.Plugins uses gw.api.database.Query uses gw.util.concurrent.LocklessLazyVar uses gw.api.archive.PolicyTermInArchiveException uses gw.api.system.PCLoggerCategory uses java.util.Date uses gw.api.upgrade.PCCoercions @Export class PolicyRenewalPlugin implements IPolicyRenewalPlugin { public final static var CONCURRENT_JOB_DELAY_DAYS : int = 3 /** * This method determines whether to start a renewal for the given PolicyPeriod. It is called by * the PolicyRenewalStartWorkQueue, which will start the actual renewal when this method returns true * * This implementation will first evaluate the date the Renewal would normally start by calling {@link #getRenewalStartDate}, * and then see if there are any open conflicting jobs on the Policy. If there are no conflicting jobs, this method returns true * if getRenewalStartDate() returns a date in the past; when there are conflicting jobs, this method returns true if getRenewalStartDate * + 3 days is in the past. All other cases return false. */ override function shouldStartRenewal( periodToRenew: PolicyPeriod ) : boolean { if ((periodToRenew.Status != PolicyPeriodStatus.TC_BOUND) || (periodToRenew.Job.LatestPeriod != periodToRenew)) { return false } if (periodToRenew.PolicyTerm.CheckArchived) { throw new PolicyTermInArchiveException(periodToRenew.PolicyTerm) } var plannedRenewalStartDate = getRenewalStartDate(periodToRenew) var currentTime = Date.CurrentDate if (hasOpenConflictingJob(periodToRenew)) { var concurrentJobDelayExceeded = currentTime.afterOrEqual(plannedRenewalStartDate.addDays(CONCURRENT_JOB_DELAY_DAYS)) if (not concurrentJobDelayExceeded) { PCLoggerCategory.SERVER_BATCHPROCESS.warn("Renewal for policy, " + periodToRenew.PolicyNumber + ", not started due to concurrent job") } return concurrentJobDelayExceeded } else { return currentTime.afterOrEqual(plannedRenewalStartDate) } } /** * This method evaluates the given PolicyPeriod to determine the earliest date that a Renewal Job * would be created for this PolicyPeriod. The {@link #shouldStartRenewal} calls this method to * help determine when to start a Renewal. This method is additionally useful to get the date * a Renewal should start, whether for display or for testing purposes. * * This implementation gets a notification date for the PolicyPeriod from the * {@Link INotificationPlugin}, and then adds further lead time based on the Product and PeriodEndDate * of the PolicyPeriod. The date returned is always truncated to Midnight so that any date comparison * to the Date returned by this plugin will be reduced to a Day comparison (ignoring the time of day) */ override function getRenewalStartDate( periodToRenew: PolicyPeriod ) : Date { var renewalMailDate = getRenewalMailDate(periodToRenew) var daysOutToStartRenewal = getLeadTimeByProduct(periodToRenew.Policy.Product) + getLeadTimeByExpirationDate(periodToRenew.PeriodEnd) // we want the renewal to start daysOutToStartRenewal days earlier than the renewalMailDate return renewalMailDate.addDays(-1 * daysOutToStartRenewal).trimToMidnight() } private var _renewalUser = new LocklessLazyVar() { override function init() : User { return findUserByCredential("renewal_daemon") } } override function getAutomatedRenewalUser(periodToRenew : PolicyPeriod) : User { return _renewalUser.get() } protected function findUserByCredential(userName : String) : User { var q = Query.make(User) q.join("Credential").compare(Credential#UserName, Equals, userName) var results = q.select() return results.AtMostOneRow } /** * This method will return an offset based on Product, given the different lead * times to process a Renewal based on the Product of the PolicyPeriod */ function getLeadTimeByProduct(product : Product) : int { switch (product) { case PCCoercions.makeProductModel("BusinessOwners") : return 90 case PCCoercions.makeProductModel("BusinessAuto") : return 30 case PCCoercions.makeProductModel("CommercialProperty") : return 60 case PCCoercions.makeProductModel("CommercialPackage") : return 90 case PCCoercions.makeProductModel("GeneralLiability") : return 60 //case "HomeOwners" : return 30 case PCCoercions.makeProductModel("InlandMarine") : return 60 case PCCoercions.makeProductModel("PersonalAuto") : return 30 case PCCoercions.makeProductModel("WorkersComp") : return 60 default : { return 60 } } } /** * This method will return an offset for additional/less time needed to process a Renewal based on when the expiration date * of the period falls on the calendar according to the following rules: * - between Jan 1 and Jan 15 - add 15 days * - between Aug 1 and Sep 15 - subtract 10 days */ function getLeadTimeByExpirationDate(expirationDate : Date) : int { var expDateYear = expirationDate.YearOfDate var newYearDateRange = Date.createDateInstance(1, 1, expDateYear)..Date.createDateInstance(1, 15, expDateYear) if (newYearDateRange.contains(expirationDate.trimToMidnight())) { return 15 } var summerDateRange = Date.createDateInstance(8, 1, expDateYear)..Date.createDateInstance(9, 15, expDateYear) if (summerDateRange.contains(expirationDate.trimToMidnight())) { return -10 } return 0 } /** * This method calls out to the INotificationPlugin to figure out the date materials must be mailed by, * given the expiration date, jurisdictions and lines of the PolicyPeriod being evaluated for renewal */ private function getRenewalMailDate(periodToRenew : PolicyPeriod) : Date { var notificationPlugin = Plugins.get(INotificationPlugin) var periodEnd = periodToRenew.PeriodEnd var lineToJurisdictions = periodToRenew.AllPolicyLinePatternsAndJurisdictions var leadTime = notificationPlugin.getMaximumLeadTime(periodEnd, lineToJurisdictions, NotificationCategory.TC_RENEWAL) return periodEnd.addDays(-leadTime) } /** * This method checks for conflicting jobs for the Policy. Currently Open PolicyChange and Cancellation jobs are considered * as conflicts with the Renewal */ private function hasOpenConflictingJob(periodToRenew: PolicyPeriod) : boolean { return periodToRenew.Policy.Jobs.hasMatch( \ job -> not job.Complete and ((job typeis PolicyChange) or (job typeis Cancellation))) } override function isRenewalOffered( periodToRenew : PolicyPeriod ) : boolean { return false } override function doesRenewalRequireConfirmation( periodToRenew : PolicyPeriod) : boolean { return false } }