package gw.rating.rtm uses gw.lang.reflect.features.PropertyReference uses gw.rating.flow.util.QueryUtils uses gw.rating.rtm.domain.DatabasePopulator uses gw.rating.rtm.domain.table.RateTableModel uses gw.util.Pair uses java.lang.IllegalStateException uses java.util.ArrayList uses java.util.Map uses java.util.Set uses gw.api.locale.DisplayKey class RateBookMerge { var _rateBook1 : RateBook var _rateBook2 : RateBook var _mergedRateBook : RateBook as MergedRateBook // Rate Tables - These maps are keyed by (Code, PolicyLine) var _mergedRateTablesMap : Map, RateTable> as MergedRateTables var _rateTableConflictsMap : Map, List> as readonly RateTableConflictsMap var _rateTableIconMap : Map, conflictStatus> as readonly RateTableIconMap // Rate Routines - Keyed by Code var _mergedRateRoutinesMap : Map as MergedRateRoutines var _rateRoutineConflictsMap : Map> as readonly RateRoutinesConflictMap var _rateRoutineIconMap : Map as readonly RateRoutinesIconMap // RateBook properties - This map is only for the rate book properties that are nullable fields, so that we can display "none" when the property is null var _radioCellWrapperMap : Map as RadioCellWrapperMap enum conflictStatus{ CONFLICT, MODIFIED_ONLY_IN_BOOK1, MODIFIED_ONLY_IN_BOOK2, ONLY_IN_BOOK1, ONLY_IN_BOOK2, IDENTICAL } construct(rb1 : RateBook, rb2 : RateBook) { _rateBook1 = rb1 _rateBook2 = rb2 _mergedRateBook = new RateBook() initializeRateBookProperties() initializeRateTablesMap() initializeRateRoutinesMap() } function merge(): RateBook { _radioCellWrapperMap.eachKeyAndValue(\rbProperty, val -> { _mergedRateBook.setFieldValue(rbProperty, val.GenericVar) }) _mergedRateTablesMap.values().each(\rateTable -> { var newTable = _mergedRateBook.addReferenceRateTable(rateTable) // The reference is really only safe if the book it points at is active; // the rest of the time we want the rate book to own a copy of the rows. // Making the reference first and then doing makeOwned() is the simplest // way to copy the table data. if (not newTable.RefTable.RateBook.isActive()) { newTable.makeOwned() } }) _mergedRateRoutinesMap.values().each(\rateRoutine -> { var rateBookCalcRoutine = new RateBookCalcRoutine(_mergedRateBook.Bundle) rateBookCalcRoutine.RateBook = _mergedRateBook rateBookCalcRoutine.CalcRoutineDefinition = rateRoutine _mergedRateBook.addToRateBookCalcRoutines(rateBookCalcRoutine) }) return _mergedRateBook } property get RateBookProperties() : List> { var rateBookProperties : List> = {} rateBookProperties.add(RateBook#BookCode) rateBookProperties.add(RateBook#BookName) rateBookProperties.add(RateBook#BookJurisdiction) rateBookProperties.add(RateBook#BookOffering) rateBookProperties.add(RateBook#BookDesc) rateBookProperties.add(RateBook#EffectiveDate) rateBookProperties.add(RateBook#RenewalEffectiveDate) rateBookProperties.add(RateBook#ExpirationDate) rateBookProperties.add(RateBook#LastStatusChangeDate) rateBookProperties.add(RateBook#PolicyLine) rateBookProperties.add(RateBook#UWCompany) rateBookProperties.add(RateBook#CascadedLookup) if (RateBook.Type.TypeInfo.getProperty("BookGroup") != null) { rateBookProperties.add(new PropertyReference(RateBook, "BookGroup")) } return rateBookProperties } property get PossibleRateTablesDefinitions() : List { var tables : Set = {} tables.addAll(_rateBook1.RateTables*.Definition.toList()) tables.addAll(_rateBook2.RateTables*.Definition.toList()) return tables.toList() } function isFieldValueEqual(ref : PropertyReference) : boolean { return(_rateBook1.getFieldValue(ref.PropertyInfo.toString()) == _rateBook2.getFieldValue(ref.PropertyInfo.toString())) } //rate table methods property get RateTableKeys() : List> { return MergedRateTables.keySet().toList() } property get RateRoutines() : List { return MergedRateRoutines.Values.toList() } property get IsMergedRateBookUnique() : String { var rBook = QueryUtils.AllRateBooks.where( \ rb -> ((rb.BookCode == _mergedRateBook.BookCode) and (rb.BookEdition == _mergedRateBook.BookEdition))) if (rBook.HasElements) { return DisplayKey.get("Web.Rating.RateBooks.Merge.Errors.DuplicateCodeEdition", _mergedRateBook.BookCode, _mergedRateBook.BookEdition) } return null } private function initializeRateBookProperties() { RateBookProperties.each( \ prop -> { _mergedRateBook.setFieldValue(prop.PropertyInfo.toString(), _rateBook1.getFieldValue(prop.PropertyInfo.toString())) }) _mergedRateBook.Status = TC_DRAFT _radioCellWrapperMap = {} //For all the RateBook Properties that might be null _radioCellWrapperMap[RateBook#BookDesc.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.BookDesc) _radioCellWrapperMap[RateBook#UWCompany.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.UWCompany) _radioCellWrapperMap[RateBook#BookJurisdiction.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.BookJurisdiction) _radioCellWrapperMap[RateBook#PolicyLine.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.PolicyLine) _radioCellWrapperMap[RateBook#BookOffering.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.BookOffering) _radioCellWrapperMap[RateBook#ExpirationDate.PropertyInfo.Name] = new NullableRadioValueWrapper(_rateBook1.ExpirationDate) if (RateBook.Type.TypeInfo.getProperty("BookGroup") != null) { _radioCellWrapperMap["BookGroup"] = new NullableRadioValueWrapper(_rateBook1["BookGroup"]) } } private function initializeRateTablesMap() { var allRateTables = _rateBook1.RateTables.toList() allRateTables.addAll(_rateBook2.RateTables.toList()) var allRateTablesMap = allRateTables.partition( \ elt -> new Pair(elt.Definition.TableCode, elt.Definition.PolicyLine)) _mergedRateTablesMap = allRateTablesMap.mapValues( \ value -> value.first()) identifyRateTableConflicts(allRateTablesMap) } private function identifyRateTableConflicts(allRateTablesMap : Map, List>) { var book1RateTables = _rateBook1.RateTables.toList() _rateTableConflictsMap = {} _rateTableIconMap = {} allRateTablesMap.eachKeyAndValue( \ k, val -> { if (val.Count > 1) { //rate table exists in both books if (val.Count != 2) { throw new IllegalStateException("Rate table with code and policy line must be unique to a rate book. So there should only be 2 when merging two books") } var table1 = val.get(0) var table2 = val.get(1) if (table1.Definition != table2.Definition) { //check rate table definition // can happen if the PolicyLines differ. Will only hit if/when we allow merging Generic + line-specific books. _rateTableConflictsMap.put(k, val) _rateTableIconMap.put(k, CONFLICT) } else { //check rows in rate table var populator = new DatabasePopulator() var tableModel1 = new RateTableModel(table1) var tableModel2 = new RateTableModel(table2) populator.refresh(tableModel1) populator.refresh(tableModel2) if (not tableModel1.areRowsEqual(tableModel2)) { _rateTableConflictsMap.put(k, val) var commonParent = getCommonParent(table1, table2) if (commonParent != null) { var parentModel = new RateTableModel(commonParent) populator.refresh(parentModel) var table1Same = isSameAsParent(tableModel1, parentModel) var table2Same = isSameAsParent(tableModel2, parentModel) if (table1Same == table2Same) { //Both should not be true at the same time, if it reached here _rateTableIconMap.put(k, CONFLICT) } else if (not table1Same) { _rateTableIconMap.put(k, MODIFIED_ONLY_IN_BOOK1) } else { _rateTableIconMap.put(k, MODIFIED_ONLY_IN_BOOK2) } } else { _rateTableIconMap.put(k, CONFLICT) } } } //rate table exists in both books, and no conflict was found then must be identical if (not _rateTableIconMap.containsKey(k)) { _rateTableIconMap.put(k, IDENTICAL) } } //rate table exists in only one of the ratebooks if (val.Count == 1) { var table = val.get(0) if(book1RateTables.contains(table)) { _rateTableIconMap.put(k, ONLY_IN_BOOK1) } else { _rateTableIconMap.put(k, ONLY_IN_BOOK2) } } }) } private function initializeRateRoutinesMap() { _mergedRateRoutinesMap = _rateBook1.RateBookCalcRoutines*.CalcRoutineDefinition.mapToKeyAndValue( \ k -> k.Code, \ k -> k) _rateRoutineConflictsMap = {} _rateRoutineIconMap = {} var book2RateRoutines = _rateBook2.RateBookCalcRoutines*.CalcRoutineDefinition.toList() var book1RateRoutines = _rateBook1.RateBookCalcRoutines*.CalcRoutineDefinition.toList() book2RateRoutines.each( \ rateRoutine -> { if (_mergedRateRoutinesMap.containsKey(rateRoutine.Code)) { var currentRateRoutine = _mergedRateRoutinesMap.get(rateRoutine.Code) if ((currentRateRoutine.Version != rateRoutine.Version) or (CalcRoutineDefinition.Type.TypeInfo.getProperty("Jurisdiction") != null and (currentRateRoutine["Jurisdiction"] != rateRoutine["Jurisdiction"]))) { _rateRoutineConflictsMap.put(rateRoutine.Code, new Pair(currentRateRoutine,rateRoutine)) _rateRoutineIconMap.put(rateRoutine.Code, CONFLICT ) } } else { _mergedRateRoutinesMap.put(rateRoutine.Code, rateRoutine) } }) _mergedRateRoutinesMap.eachValue( \ rateRoutine -> { var alreadyExistInIconMap = _rateRoutineIconMap.containsKey(rateRoutine.Code) var existRateBook1 = book1RateRoutines.contains(rateRoutine) var existRateBook2 = book2RateRoutines.contains(rateRoutine) if (not alreadyExistInIconMap) { if (existRateBook1 and existRateBook2) { _rateRoutineIconMap.put(rateRoutine.Code, IDENTICAL) } else if (existRateBook1) { _rateRoutineIconMap.put(rateRoutine.Code, ONLY_IN_BOOK1) } else { _rateRoutineIconMap.put(rateRoutine.Code, ONLY_IN_BOOK2) } } }) } function tableConflictExists(key: Pair) : boolean { return RateTableConflictsMap.containsKey(key) } function getRateTableFromBothBooks(key : Pair) : List { var table1 = _rateBook1.RateTables.where( \ elt -> elt.Definition.TableCode == key.First and elt.Definition.PolicyLine == key.Second).toList() var table2 = _rateBook2.RateTables.where( \ elt -> elt.Definition.TableCode == key.First and elt.Definition.PolicyLine == key.Second) table1.addAll(table2.toList()) return table1 } function rateRoutineConflictsExist(routine : CalcRoutineDefinition) : boolean { return _rateRoutineConflictsMap.containsKey(routine.Code) } function getRateRoutinesFromBooks(key : String) : List { if (_rateRoutineConflictsMap.containsKey(key)) { return {_rateRoutineConflictsMap.get(key).First, _rateRoutineConflictsMap.get(key).Second} } else { return {_mergedRateRoutinesMap.get(key)} } } /** * Computes the BasedOn hierarchy of the RateTable */ private function computeTableHierarchy(rateTable: RateTable): List { var hierarchy = new ArrayList() while (rateTable != null) { hierarchy.add(rateTable) rateTable = rateTable.BasedOnTable } return hierarchy } /** * Returns the first common parent of the two RateTables if found, null if not */ private function getCommonParent(rateTable1: RateTable, rateTable2: RateTable): RateTable { var table1Hierarchy = computeTableHierarchy(rateTable1) var table2Hierarchy = computeTableHierarchy(rateTable2) return table1Hierarchy.firstWhere(\table -> table2Hierarchy.contains(table)) } /** * Checks if the RateTable is recursively BasedOn the the parent RateTable */ private function isSameAsParent(rateTableModel: RateTableModel, parentModel: RateTableModel): Boolean { if (rateTableModel == null or parentModel == null) { return false } if (rateTableModel == parentModel or rateTableModel.RateTable.TableOwningFactors == parentModel.RateTable.TableOwningFactors){ return true } return rateTableModel.areRowsEqual(parentModel) } }