package gw.lob.wc
uses gw.api.locale.DisplayKey
uses com.guidewire.pl.system.util.DateTimeUtil
uses java.util.Date
uses java.util.ArrayList
uses java.lang.IllegalStateException
uses org.apache.commons.lang.StringUtils
uses java.util.Calendar
uses gw.pl.persistence.core.effdate.EffDatedVersionList
uses gw.api.util.DateUtil
uses gw.lob.wc.rating.WCRatingPeriod
uses gw.api.domain.StateSet
uses gw.api.util.StateJurisdictionMappingUtil
uses gw.api.util.DisplayableException
uses java.util.HashMap
uses gw.api.productmodel.ModifierPattern
uses java.util.List
uses gw.api.upgrade.Coercions
enhancement WCJurisdictionEnhancement: WCJurisdiction {
property get AnniversaryDate(): Date {
return Coercions.makeDateFrom(this.getFieldValue("AnniversaryDateInternal"))
}
/**
* Sets the anniversary date on this policy line and adds or modifies an
* anniversary-type {@link com.guidewire.pc.domain.policy.lines.wc.RatingPeriodStartDate}
* (RPSD) to the PolicyPeriod if required.
Side Effects:
May
* add a new RPSD to the policy period, or change an existng anniversary-type
* RPSD. Use {@link com.guidewire.pc.domain.policy.lines.wc.WorkersCompLine#setFieldValue} if the side effects are not
* desired.
*/
property set AnniversaryDate(value: Date) {
if (value == null) {
throw new DisplayableException(DisplayKey.get("Java.Job.WC.AnniversaryNotNull"))
}
var periodStart = PeriodStart
var newAnniversary = DateTimeUtil.setHourMinuteSecondFromDate(value, periodStart)
if (newAnniversary > periodStart) {
newAnniversary = periodStart
}
// find the anniversary date for this period
if (newAnniversary < periodStart.addYears(- 1)) {
newAnniversary = newAnniversary.addYears(1)
}
this.getSlice(this.EffectiveDate).setFieldValue("AnniversaryDateInternal", newAnniversary)
// Either anniversary date change or policy period effective window change (eg. renewal, cancel),
// we'll have to update the RPSDs
var existingSplits = new HashMap()
var existingAnniversarySplit: entity.RatingPeriodStartDate
for (rpsd in this.RatingPeriodStartDates) {
if (rpsd.Type == TC_ANNIVERSARY) {
existingAnniversarySplit = rpsd
} else {
existingSplits.put(rpsd.StartDate, rpsd)
}
}
// Add new RPSDs
var splitDate = newAnniversary.addYears(1)
if (splitDate != existingAnniversarySplit.StartDate){
var otherSplit = existingSplits.get(splitDate)
if (otherSplit != null) {
// AnniversaryDate should take priority
otherSplit.getSlice(otherSplit.EffectiveDate).Type = TC_ANNIVERSARY
} else if (newAnniversary != periodStart and canAddRPSD(splitDate) == null){
addRatingPeriodStartDate(splitDate, TC_ANNIVERSARY)
}
// Removing irrelavent RPSDs
if (existingAnniversarySplit != null){
existingAnniversarySplit.getSlice(existingAnniversarySplit.EffectiveDate).remove()
}
}
}
property get ReferenceDate(): Date {
return Coercions.makeDateFrom(this.getFieldValue("ReferenceDateInternal"))
}
/**
* @see get AnniversaryDate
*/
property set ReferenceDate(refDate: Date) {
this.getSlice(this.EffectiveDate).setFieldValue("ReferenceDateInternal", refDate)
}
property get AllModifierVersions(): List {
return this.VersionList.WCModifiers.flatMap(\w -> w.AllVersions)
}
property get AllCoverageVersions(): List {
return this.VersionList.Coverages.flatMap(\w -> w.AllVersions)
}
/**
* Indicates whether or not this jurisdiction can be removed. A jurisidiction can only be removed if it has no covered employees at
* any point during the policy and if it is not used by the primary location
*/
property get CanRemove(): boolean {
var usedByPrimaryLoc = this.WCLine.Branch.PrimaryLocation.State.Code == this.State.Code
return !usedByPrimaryLoc and this.WCLine.getWCCoveredEmployeesWM(this.State).IsEmpty
}
function adjustAnniversaryDate() {
var policyStart = this.WCLine.Branch.PeriodStart.trimToMidnight()
if (this.AnniversaryDate.addYears(1) < policyStart) {
this.AnniversaryDate = policyStart
}
}
function removeRatingPeriodStartDate(date: Date, type: RPSDType) {
var rpsd = getRPSD(date, type)
if (rpsd == null) {
return
} else {
rpsd.getSlice(rpsd.EffectiveDate).remove()
}
}
function getSortedRPSDs(): RatingPeriodStartDate[] {
var ratingPeriodsList = innerGetUnsortedRPSDs(null, null)
ratingPeriodsList = ratingPeriodsList.sortBy(\rpsd -> rpsd.StartDate)
return ratingPeriodsList.toTypedArray()
}
function getSortedRPSDs(type: RPSDType): RatingPeriodStartDate[] {
var ratingPeriodsList = innerGetUnsortedRPSDs(null, type)
ratingPeriodsList = ratingPeriodsList.sortBy(\rpsd -> rpsd.StartDate)
return ratingPeriodsList.toTypedArray()
}
function hasRPSDOnDate(startDate: Date): boolean {
var ratingPeriodsList = innerGetUnsortedRPSDs(startDate, null)
return not ratingPeriodsList.isEmpty()
}
function getRPSD(startDate: Date, type: RPSDType): RatingPeriodStartDate {
var ratingPeriodsList = innerGetUnsortedRPSDs(startDate, type)
if (ratingPeriodsList.isEmpty()) {
return null
} else if (ratingPeriodsList.size() > 1) {
throw new IllegalStateException(DisplayKey.get("WorkersComp.Jurisdiction.MultipleRPSDs", startDate, type))
} else {
return ratingPeriodsList.get(0)
}
}
function getPriorRatingDate(date: Date): Date {
checkIncludesDateIgnoreTime(date)
var _sortedRPSDs = getSortedRPSDs()
var ratingDateList = new ArrayList()
var anniversary: Date = null
for (rpsd in _sortedRPSDs) {
var type = rpsd.getType()
if (type == RPSDType.TC_FORCEDRERATING) {
ratingDateList.add(rpsd.getStartDate())
}
if (type == RPSDType.TC_ANNIVERSARY) {
ratingDateList.add(rpsd.getStartDate())
anniversary = rpsd.getStartDate()
}
}
if (anniversary == null and this.WCLine != null) {
var effDate = PeriodStart
var wcAnni = this.AnniversaryDate
if (wcAnni != null) {
if (effDate.before(wcAnni)) {
wcAnni = calculateUpcomingAnniversaryDate()
}
if (effDate != wcAnni) {
anniversary = wcAnni.addYears(1)
}
}
}
if (anniversary != null) {
ratingDateList.add(0, anniversary.addYears(- 1))
} else {
ratingDateList.add(0, PeriodStart)
}
ratingDateList.add(PeriodEnd)
if (ratingDateList.size() > 0) {
for (i in 0..|(ratingDateList.size() - 1)) {
var date1 = ratingDateList.get(i)
var date2 = ratingDateList.get(i + 1)
if (DateUtil.compareIgnoreTime(date, date1) >= 0 and DateUtil.compareIgnoreTime(date, date2) < 0) {
return date1
}
}
}
// we got this far but did not find an acceptable date - if date is between period start & end, use period start
if (DateUtil.compareIgnoreTime(date, PeriodStart) >= 0 and DateUtil.compareIgnoreTime(date, PeriodEnd) < 0) {
return PeriodStart
}
throw new IllegalStateException(DisplayKey.get("WorkersComp.Jurisdiction.NoDateFound", date, StringUtils.join(ratingDateList.iterator(), ", ")))
}
function calculateUpcomingAnniversaryDate(): Date {
var anniversaryCal = getTrimmedCalendar(this.AnniversaryDate)
var effectiveCal = getTrimmedCalendar(PeriodStart)
setAnniversaryYear(anniversaryCal, effectiveCal)
return anniversaryCal.getTime()
}
/**
* Return the rating periods in this jurisdiction that are in-force (not completely canceled
* or hidden by an audit window). The rating periods are sorted by start date.
*/
property get InForceRatingPeriods(): List
{
return RatingPeriods.where(\rp -> rp.NumRatingDays > 0).toList()
}
/**
* Return the rating periods in this jurisdiction sorted by start date. For example,
* if a jurisdiction has a forced rerating date, followed by an anniversary date,
* this will return three rating periods:- [PolicyPeriodStart-RerateDate),
*
- [RerateDate-AnniversaryDate),
*
- [AnniversaryDate-PolicyPeriodEnd)
*
* A jurisdiction without any rating period start dates will only return one rating period
* [PolicyPeriodStart-PolicyPeriodEnd).
*/
property get RatingPeriods(): List {
return createRatingPeriods(SplitDates)
}
/**
* Sorted list of the dates this period is splitted on
*/
property get SplitDates(): List {
var rpsds = this.VersionList.RatingPeriodStartDates
.map(\rpsd -> rpsd.AllVersions.single().StartDate)
rpsds.add(this.Branch.PeriodStart)
rpsds.add(this.Branch.PeriodEnd)
return rpsds.sort()
}
property get AuditRatingPeriods(): List {
var start = this.Branch.Audit.AuditInformation.AuditPeriodStartDate
var end = this.Branch.Audit.AuditInformation.AuditPeriodEndDate
var rpsds = this.getSingleVersionsOfRatingPeriodStartDates().map(\rpsd -> rpsd.StartDate).where(\d -> d > start and d < end).toList()
rpsds.add(start)
rpsds.add(end)
var sortedRPSDs = rpsds.sort()
return createRatingPeriods(sortedRPSDs)
}
/**
* If cannot add RPSD, return error message. This is used in UI as well.
*/
function canAddRPSD(date: Date): String {
// this compare is based on assumption that all RPSD has the same time as the period start
if (SplitDates.hasMatch(\r -> r == date)){
return DisplayKey.get("Web.Policy.WC.DuplicatedDate", this.State, date.ShortFormat)
}
if (not this.Branch.includes(date)) {
return DisplayKey.get("Java.Job.WC.RPSDOutsidePeriod", date, PeriodStart, PeriodEnd)
}
return null
}
function addRatingPeriodStartDateWithNoARDCheck(splitDate: Date, type: RPSDType): RatingPeriodStartDate {
var date = DateTimeUtil.setHourMinuteSecondFromDate(splitDate, PeriodStart)
var rpsd = new RatingPeriodStartDate(this.Branch)
rpsd.StartDate = date
rpsd.Type = type
this.addToRatingPeriodStartDates(rpsd)
rpsd = rpsd.getUnsliced()
rpsd.EffectiveDate = PeriodStart
return rpsd
}
function addRatingPeriodStartDate(splitDate: Date, type: RPSDType): RatingPeriodStartDate {
var date = DateTimeUtil.setHourMinuteSecondFromDate(splitDate, PeriodStart)
var rpsdErrorMsg = canAddRPSD(date)
if (rpsdErrorMsg != null){
throw new DisplayableException(rpsdErrorMsg)
}
return addRatingPeriodStartDateWithNoARDCheck(splitDate, type)
}
function getSingleVersionsOfRatingPeriodStartDates(): RatingPeriodStartDate[] {
return this.VersionList.RatingPeriodStartDates
.map(\r -> r.AllVersions.single()).toTypedArray()
}
function splitModifiers() {
updateSplitsOnEffDatedBeans(this.VersionList.WCModifiers, \e -> {
var pattern = (e as WCModifier).Pattern as ModifierPattern
return pattern.SplitOnAnniversary
})
// split rate factors
for (modifier in this.VersionList.WCModifiers) {
var pattern = modifier.AllVersions.first().Pattern
updateSplitsOnEffDatedBeans(modifier.WCRateFactors, \r -> {
return (pattern as ModifierPattern).SplitOnAnniversary
})
}
}
function joinExposures(allVersions: List) {
var arrow = 0
var dot = 1
while (dot < allVersions.size()) {
var bean1 = allVersions.get(arrow) as WCCoveredEmployeeBase
var bean2 = allVersions.get(dot) as WCCoveredEmployeeBase
if (bean1.ExpirationDate == bean2.EffectiveDate and not hasRPSDOnDate(bean1.ExpirationDate)) {
// Override automatic basis calculation. If we just relied on them
// being scaled automatically then we'd get rounding errors.
// instead we'll add them up and use that new value. Alternatively
// we could store a denormalized basis amount as a double and scale
// that to get the correct value.
var newBasis = bean1.BasisAmount + bean2.BasisAmount
bean1.ExpirationDate = bean2.ExpirationDate
bean2.ExpirationDate = bean2.EffectiveDate
bean1.BasisAmount = newBasis
dot = dot + 1
} else {
arrow = dot
dot = arrow + 1
}
}
}
function splitCoverages() {
updateSplitsOnEffDatedBeans(this.VersionList.Coverages, \e -> true)
}
function updateRPSDsAfterPeriodChange(oldPeriodStart: Date) {
var periodStart = PeriodStart
var periodEnd = PeriodEnd
var ratingPeriodStartDates = this.VersionList.RatingPeriodStartDates
for (rpsdVL in ratingPeriodStartDates) {
var versions = rpsdVL.AllVersions
if (versions.size() != 1) {
throw new IllegalStateException(DisplayKey.get("WorkersComp.Jurisdiction.CannotSplitRPSD"))
}
var rpsd = versions.get(0).getUnslicedUntyped() as RatingPeriodStartDate
if (DateUtil.compareIgnoreTime(rpsd.getStartDate(), periodStart) <= 0 or DateUtil.compareIgnoreTime(rpsd.getStartDate(), periodEnd) >= 0) {
rpsd.getSlice(rpsd.EffectiveDate).remove()
}
}
var anniDate = this.AnniversaryDate
if (anniDate != null and DateUtil.compareIgnoreTime(anniDate, oldPeriodStart) == 0) {
this.AnniversaryDate = periodStart
} else {
this.AnniversaryDate = anniDate
}
}
function updateRPSDsInNewPeriod() {
for (oldRPSD in this.BasedOn.RatingPeriodStartDates) {
var splitDate = oldRPSD.StartDate
if (oldRPSD.Type != TC_ANNIVERSARY and this.Branch.includes(splitDate)) {
// Rewrite Jobs do not need to do check on adding RSPD as the job itself may be on an ARD Date
this.addRatingPeriodStartDateWithNoARDCheck(splitDate, oldRPSD.Type)
}
}
var oldARD1 = this.getBasedOn().AnniversaryDate
var oldARD2 = DateTimeUtil.addYears(this.getBasedOn().AnniversaryDate, 1)
var periodStart = PeriodStart
if (DateUtil.compareIgnoreTime(periodStart, oldARD2) >= 0) {
this.AnniversaryDate = periodStart
} else {
this.AnniversaryDate = oldARD1
}
updateExposures()
}
function updateExposures() {
for (beanVL in this.WCLine.VersionList.WCCoveredEmployees) {
var beanVersions = beanVL.AllVersions
var location = beanVersions[0].getLocation()
if (location != null and location.State == StateJurisdictionMappingUtil.getStateMappingForJurisdiction(this.getState())) {
joinExposures(beanVersions)
}
}
splitWCCoveredEmployees()
// we should not split WCFedCoveredEmployees
}
function getPossibleGoverningLaws(): SpecialCov[] {
var typeKeys = SpecialCov.getTypeKeys(false)
.where(\typeKey -> typeKey.hasCategory(typekey.LiabilityAct.TC_WORKERSCOMP))
var monopolisticStates = StateSet.get(StateSet.WC_MONOPOLISTIC)
if (monopolisticStates.contains(StateJurisdictionMappingUtil.getStateMappingForJurisdiction(this.State))) {
typeKeys.remove(SpecialCov.TC_STAT)
typeKeys.remove(SpecialCov.TC_VOCO)
typeKeys.remove(SpecialCov.TC_LTDM)
}
return typeKeys?.toTypedArray()
}
// ---------------------------- Private Helper Methods
private property get PeriodStart(): Date {
return this.PolicyLine.Branch.PeriodStart
}
private property get PeriodEnd(): Date {
return this.PolicyLine.Branch.PeriodEnd
}
private function checkIncludesDateIgnoreTime(date: Date) {
if (PeriodStart.compareIgnoreTime(date) > 0 or PeriodEnd.compareIgnoreTime(date) <= 0) {
throw DisplayKey.get("WorkersComp.Jurisdiction.DateOutsidePeriod", date, PeriodStart, PeriodEnd)
}
}
private function setAnniversaryYear(anniversaryCal: Calendar, effectiveCal: Calendar) {
anniversaryCal.set(Calendar.YEAR, effectiveCal.get(Calendar.YEAR))
var anniDate = anniversaryCal.getTime()
var effectiveDate = effectiveCal.getTime()
if (anniDate != effectiveDate
and anniDate == DateTimeUtil.getEarlierOfTwoDates(anniDate, effectiveDate)) {
anniversaryCal.add(Calendar.YEAR, 1)
}
}
private function getTrimmedCalendar(date: Date): Calendar {
if (null == date) {
return null
}
var cal = Calendar.getInstance()
cal.setTime(DateTimeUtil.trimToDay(date))
return cal
}
private function splitWCCoveredEmployees() {
var ratingPeriodStartDates = getSingleVersionsOfRatingPeriodStartDates()
for (rpsd in ratingPeriodStartDates) {
var wcCoveredEmployeesVLs = this.getWCLine().VersionList.WCCoveredEmployees
for (wcCoveredEmployeeVL in wcCoveredEmployeesVLs) {
var allVersions = wcCoveredEmployeeVL.AllVersions
var location = allVersions.get(0).getLocation()
if (location != null and location.State != StateJurisdictionMappingUtil.getStateMappingForJurisdiction(this.getState())) {
continue
}
for (wcCoveredEmployee in allVersions) {
if (wcCoveredEmployee.isEffective(rpsd.getStartDate())) {
wcCoveredEmployee.split(rpsd.getStartDate())
}
}
}
}
}
/**
* Helper: returns the list of RPSDs for this PolicyPeriod, optionally
* filtered by RPSDType
*
* @param startDate if non-null, filters RPSDs by date
* @param type if non-null, filters RPSDs by type
* @return unsorted List of RPSDs
*/
private function innerGetUnsortedRPSDs(startDate: Date, type: RPSDType): List {
var ratingPeriodsList = new ArrayList()
var rpsd = this.RatingPeriodStartDates
for (ratingPeriod in rpsd) {
if ((startDate == null or startDate == ratingPeriod.getStartDate() ) and
(type == null or type == ratingPeriod.getType())) {
ratingPeriodsList.add(ratingPeriod)
}
}
return ratingPeriodsList
}
/**
* Takes the list of version lists, joins the beans where necessary, and then re-splits them as appropriate based on the
* current set of RPSDs.
*/
private function updateSplitsOnEffDatedBeans(versionLists: List, shouldSplit(bean: EffDated): boolean) {
var ratingPeriodStartDates = getSingleVersionsOfRatingPeriodStartDates()
// join all beans that are split with no RPSD
for (beanVL in versionLists) {
joinBeans(beanVL.AllVersionsUntyped)
}
for (rpsd in ratingPeriodStartDates) {
var coverageVLs = versionLists
for (coverageVL in coverageVLs) {
var allVersions = coverageVL.AllVersionsUntyped
for (coverage in allVersions) {
if (shouldSplit(coverage) && coverage.isEffective(rpsd.getStartDate())) {
coverage.splitUntyped(rpsd.getStartDate())
}
}
}
}
}
/**
* Given a list of beans that represent the same bean through time, joins adjacent versions if there's
* currently no RPSD that should separate them.
*/
private function joinBeans(allVersions: java.util.List) {
var arrow = 0
var dot = 1
while (dot < allVersions.size()) {
var bean1 = allVersions.get(arrow)
var bean2 = allVersions.get(dot)
if (bean1.ExpirationDate == bean2.EffectiveDate and not hasRPSDOnDate(bean1.ExpirationDate)) {
bean1.ExpirationDate = bean2.ExpirationDate
bean2.ExpirationDate = bean2.EffectiveDate
dot = dot + 1
} else {
arrow = dot
dot = arrow + 1
}
}
}
private function createRatingPeriods(rpsds: List): List {
var periods = new ArrayList()
if (rpsds.size() > 0) {
for (i in 0..|(rpsds.size() - 1)) {
var startDate = rpsds[i]
var endDate = rpsds[i + 1]
if (startDate < endDate) {
periods.add(new WCRatingPeriod(this, startDate, endDate, i + 1))
}
}
}
return periods
}
}