{"action":"created","comment":{"url":"https://api.github.com/repos/dchbx/enroll/pulls/comments/267044573","pull_request_review_id":216352399,"id":267044573,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI2NzA0NDU3Mw==","diff_hunk":"@@ -0,0 +1,678 @@\n+module SponsoredBenefits\n+  class BenefitGroup\n+    include Mongoid::Document\n+    include Mongoid::Timestamps\n+\n+    include ::Eligibility::BenefitGroup\n+    include Config::AcaModelConcern\n+\n+    embedded_in :benefit_application, class_name: \"SponsoredBenefits::BenefitApplications::BenefitApplication\"\n+\n+    attr_accessor :metal_level_for_elected_plan, :carrier_for_elected_plan\n+\n+    PLAN_OPTION_KINDS = %w(sole_source single_plan single_carrier metal_level)\n+    EFFECTIVE_ON_KINDS = %w(date_of_hire first_of_month)\n+    OFFSET_KINDS = [0, 1, 30, 60]\n+    PERSONAL_RELATIONSHIP_KINDS = [\n+      :employee,\n+      :spouse,\n+      :domestic_partner,\n+      :child_under_26,\n+      :child_26_and_over\n+    ]\n+\n+    field :title, type: String, default: \"\"\n+    field :description, type: String, default: \"\"\n+    field :effective_on_kind, type: String, default: \"first_of_month\"\n+    field :terminate_on_kind, type: String, default: \"end_of_month\"\n+    field :plan_option_kind, type: String\n+    field :default, type: Boolean, default: true  # is default benefit group\n+\n+    field :contribution_pct_as_int, type: Integer, default: 0\n+    field :employee_max_amt, type: Money, default: 0\n+    field :first_dependent_max_amt, type: Money, default: 0\n+    field :over_one_dependents_max_amt, type: Money, default: 0\n+\n+    # Number of days following date of hire\n+    field :effective_on_offset, type: Integer, default: 0\n+\n+    # Non-congressional\n+    # belongs_to :reference_plan, class_name: \"Plan\"\n+    field :reference_plan_id, type: BSON::ObjectId\n+    field :lowest_cost_plan_id, type: BSON::ObjectId\n+    field :highest_cost_plan_id, type: BSON::ObjectId\n+\n+\n+    # Employer contribution amount as percentage of reference plan premium\n+    field :employer_max_amt_in_cents, type: Integer, default: 0\n+\n+    # Array of plan_ids\n+    field :elected_plan_ids, type: Array, default: []\n+    field :is_congress, type: Boolean, default: false\n+    field :_type, type: String, default: self.name\n+\n+\n+    embeds_many :composite_tier_contributions, cascade_callbacks: true\n+    accepts_nested_attributes_for :composite_tier_contributions, reject_if: :all_blank, allow_destroy: true\n+\n+    embeds_many :relationship_benefits, cascade_callbacks: true\n+    accepts_nested_attributes_for :relationship_benefits, reject_if: :all_blank, allow_destroy: true\n+\n+    embeds_many :dental_relationship_benefits, cascade_callbacks: true\n+    accepts_nested_attributes_for :dental_relationship_benefits, reject_if: :all_blank, allow_destroy: true\n+\n+\n+    #TODO add following attributes: :title,\n+    validates_presence_of :relationship_benefits, :effective_on_kind, :terminate_on_kind, :effective_on_offset,\n+                          :reference_plan_id, :plan_option_kind, :elected_plan_ids\n+\n+    validates_uniqueness_of :title\n+\n+    validates :plan_option_kind,\n+      allow_blank: false,\n+      inclusion: {\n+        in: PLAN_OPTION_KINDS,\n+        message: \"%{value} is not a valid plan option kind\"\n+      }\n+\n+    validates :effective_on_kind,\n+      allow_blank: false,\n+      inclusion: {\n+        in: EFFECTIVE_ON_KINDS,\n+        message: \"%{value} is not a valid effective date kind\"\n+      }\n+\n+    validates :effective_on_offset,\n+      allow_blank: false,\n+      inclusion: {\n+        in: OFFSET_KINDS,\n+        message: \"%{value} is not a valid effective date offset kind\"\n+      }\n+\n+    validate :plan_integrity\n+    validate :check_employer_contribution_for_employee\n+    validate :check_offered_for_employee\n+\n+    before_save :update_dependent_composite_tiers\n+    before_destroy :delete_benefit_group_assignments_and_enrollments\n+\n+    # def plan_option_kind=(new_plan_option_kind)\n+    #   super new_plan_option_kind.to_s\n+    # end\n+\n+    alias_method :is_default?, :default\n+    alias_method :is_congress?, :is_congress\n+\n+\n+    def benefit_group_assignments\n+      BenefitGroupAssignment.by_benefit_group_id(id)\n+    end\n+\n+    def census_employees\n+      CensusEmployee.find_all_by_benefit_group(self)\n+    end\n+\n+    def employer_profile\n+      benefit_application.employer_profile\n+    end\n+\n+    def sorted_composite_tier_contributions\n+      self.composite_tier_contributions.sort{|a,b| a.sort_val <=> b.sort_val}\n+    end\n+\n+    def reference_plan=(new_reference_plan)\n+      raise ArgumentError.new(\"expected Plan\") unless new_reference_plan.is_a? Plan\n+      self.reference_plan_id = new_reference_plan._id\n+      @reference_plan = new_reference_plan\n+    end\n+\n+    def reference_plan\n+      return @reference_plan if defined? @reference_plan\n+      @reference_plan = Plan.find(reference_plan_id) unless reference_plan_id.nil?\n+    end\n+\n+    def is_open_enrollment?\n+      benefit_application.open_enrollment.cover?(TimeKeeper.date_of_record)\n+    end\n+\n+    def termination_effective_on_for(new_date)\n+      if benefit_application.open_enrollment_period.cover?(new_date) || new_date < benefit_application.effective_period.begin\n+        benefit_application.effective_period.begin\n+      else\n+        new_date.end_of_month if terminate_on_kind == \"end_of_month\"\n+      end\n+    end\n+\n+    def set_bounding_cost_plans\n+      return if reference_plan_id.nil?\n+\n+      if plan_option_kind == \"single_plan\"\n+        plans = [reference_plan]\n+      elsif plan_option_kind == \"sole_source\"\n+        plans = [reference_plan]\n+      else\n+        if plan_option_kind == \"single_carrier\"\n+          plans = Plan.shop_health_by_active_year(reference_plan.active_year).by_carrier_profile(reference_plan.carrier_profile)\n+        else\n+          plans = Plan.shop_health_by_active_year(reference_plan.active_year).by_health_metal_levels([reference_plan.metal_level])\n+        end\n+      end\n+\n+      set_lowest_and_highest(plans)\n+    end\n+\n+    def set_lowest_and_highest(plans)\n+      if plans.size > 0\n+        plans_by_cost = plans.sort_by { |plan| plan.premium_tables.first.cost }\n+\n+        self.lowest_cost_plan_id  = plans_by_cost.first.id\n+        @lowest_cost_plan = plans_by_cost.first\n+\n+        self.highest_cost_plan_id = plans_by_cost.last.id\n+        @highest_cost_plan = plans_by_cost.last\n+      end\n+    end\n+\n+    def lowest_cost_plan\n+      return @lowest_cost_plan if defined? @lowest_cost_plan\n+    end\n+\n+    def highest_cost_plan\n+      return @highest_cost_plan if defined? @highest_cost_plan\n+    end\n+\n+    def elected_plans=(new_plans)\n+      return unless new_plans.present?\n+\n+      if new_plans.is_a? Array\n+        self.elected_plan_ids = new_plans.reduce([]) { |list, plan| list << plan._id }\n+      else\n+        self.elected_plan_ids = Array.new(1, new_plans.try(:_id))\n+      end\n+\n+      set_bounding_cost_plans\n+      @elected_plans = new_plans\n+    end\n+\n+    def elected_plans\n+      return @elected_plans if defined? @elected_plans\n+      @elected_plans ||= Plan.where(:id => {\"$in\" => elected_plan_ids}).to_a\n+    end\n+\n+    def decorated_elected_plans(member_provider, coverage_kind=\"\")\n+      max_contribution_cache = Hash.new\n+      get_elected_plans = (coverage_kind == \"health\" ? elected_plans : elected_dental_plans)\n+      ref_plan = (coverage_kind == \"health\" ? reference_plan : dental_reference_plan)\n+      get_elected_plans.collect(){|plan| decorated_plan(plan, member_provider, ref_plan, max_contribution_cache)}\n+    end\n+\n+    def decorated_plan(plan, member_provider, ref_plan, max_contribution_cache = {})\n+      if self.sole_source? && (!plan.dental?)\n+        CompositeRatedPlanCostDecorator.new(plan, self, member_provider.composite_rating_tier, member_provider.is_cobra_status?)\n+      else\n+        PlanCostDecorator.new(plan, member_provider, self, ref_plan, max_contribution_cache)\n+      end\n+    end\n+\n+    def effective_composite_tier(ce)\n+      employer_offered_family_benefits = composite_tier_contributions.find_by(composite_rating_tier: 'family').offered?\n+      employer_offered_family_benefits ? ce.composite_rating_tier : 'employee_only'\n+    end\n+\n+    def assignable_to?(census_employee)\n+      return !(census_employee.employment_terminated_on < benefit_sponsorship.effective_period.begin || census_employee.hired_on > benefit_sponsorship.effective_period.end)\n+    end\n+\n+    def employer_max_amt_in_cents=(new_employer_max_amt_in_cents)\n+      write_attribute(:employer_max_amt_in_cents, dollars_to_cents(new_employer_max_amt_in_cents))\n+    end\n+\n+    def premium_in_dollars\n+      cents_to_dollars(employer_max_amt_in_cents)\n+    end\n+\n+    def relationship_benefit_for(relationship)\n+      relationship_benefits.where(relationship: relationship).first\n+    end\n+\n+    def build_relationship_benefits\n+      self.relationship_benefits = PERSONAL_RELATIONSHIP_KINDS.map do |relationship|\n+         self.relationship_benefits.build(relationship: relationship, offered: true)\n+      end\n+    end\n+\n+    def build_composite_tier_contributions\n+      self.composite_tier_contributions = CompositeRatingTier::NAMES.map do |rating_tier|\n+        self.composite_tier_contributions.build(composite_rating_tier: rating_tier, offered: true)\n+      end\n+    end\n+\n+    def build_dental_relationship_benefits\n+      self.dental_relationship_benefits = PERSONAL_RELATIONSHIP_KINDS.map do |relationship|\n+         self.dental_relationship_benefits.build(relationship: relationship, offered: true)\n+      end\n+    end\n+\n+    def simple_benefit_list(employee_premium_pct, dependent_premium_pct, employer_max_amount)\n+      [\n+        RelationshipBenefit.new(benefit_group: self,\n+                                relationship: :employee,\n+                                premium_pct: employee_premium_pct,\n+                                employer_max_amt: employer_max_amount,\n+                                offered: true),\n+      ] + PERSONAL_RELATIONSHIP_KINDS.dup.delete_if{|kind| [:employee, :child_26_and_over].include?(kind)}.collect do |relationship|\n+        RelationshipBenefit.new(benefit_group: self,\n+                                relationship: relationship,\n+                                premium_pct: dependent_premium_pct,\n+                                employer_max_amt: employer_max_amount,\n+                                offered: true)\n+      end + [\n+        RelationshipBenefit.new(benefit_group: self,\n+                                relationship: :child_26_and_over,\n+                                premium_pct: employee_premium_pct,\n+                                employer_max_amt: employer_max_amount,\n+                                offered: false),\n+      ]\n+    end\n+\n+    def self.find(id)\n+      ::Caches::RequestScopedCache.lookup(:employer_calculation_cache_for_benefit_groups, id) do\n+        organizations = PlanDesignOrganization.unscoped.where({\"plan_design_proposals.profile.benefit_sponsorships.benefit_applications.benefit_groups._id\" => id })\n+        organizations.flat_map(&:plan_design_proposals).lazy.map(&:profile).flat_map(&:benefit_sponsorships).flat_map(&:benefit_applications).flat_map(&:benefit_groups).select do |bg|\n+          bg.id == id\n+        end.first\n+      end\n+    end\n+\n+    def monthly_employer_contribution_amount(plan = reference_plan)\n+      return 0 if targeted_census_employees.count > 100\n+\n+      if self.sole_source? && self.composite_tier_contributions.empty?\n+        build_composite_tier_contributions\n+        estimate_composite_rates\n+      end\n+      targeted_census_employees.active.collect do |ce|\n+        pcd = if self.sole_source? && (!plan.dental?)\n+          CompositeRatedPlanCostDecorator.new(plan, self, effective_composite_tier(ce), ce.is_cobra_status?)\n+        else\n+          PlanCostDecorator.new(plan, ce, self, reference_plan)\n+        end\n+        pcd.total_employer_contribution\n+      end.sum\n+    end\n+\n+    def monthly_employee_cost(coverage_kind=nil)\n+      rp = coverage_kind == \"dental\" ? dental_reference_plan : reference_plan\n+      return 0 if targeted_census_employees.count > 100\n+      targeted_census_employees.active.collect do |ce|\n+        pcd = if self.sole_source? && (!rp.dental?)\n+          CompositeRatedPlanCostDecorator.new(rp, self, effective_composite_tier(ce), ce.is_cobra_status?)\n+        else\n+          PlanCostDecorator.new(rp, ce, self, rp)\n+        end\n+        pcd.total_employee_cost\n+      end\n+    end\n+\n+    def monthly_min_employee_cost(coverage_kind = nil)\n+      monthly_employee_cost(coverage_kind).min\n+    end\n+\n+    def monthly_max_employee_cost(coverage_kind = nil)\n+      monthly_employee_cost(coverage_kind).max\n+    end\n+\n+    def targeted_census_employees\n+      target_object = persisted? ? self : benefit_application.benefit_sponsorship\n+      target_object.census_employees\n+    end\n+\n+    def employee_cost_for_plan(ce, plan = reference_plan)\n+      pcd = if @is_congress\n+        decorated_plan(plan, ce)\n+      elsif plan_option_kind == 'sole_source' && !plan.dental?\n+        CompositeRatedPlanCostDecorator.new(reference_plan, self, effective_composite_tier(ce), ce.is_cobra_status?)\n+      else\n+        PlanCostDecorator.new(plan, ce, self, reference_plan)\n+      end\n+      pcd.total_employee_cost\n+    end\n+\n+    def single_plan_type?\n+      plan_option_kind == \"single_plan\"\n+    end\n+\n+    def elected_plans_by_option_kind\n+      start_on = benefit_application.effective_period.begin\n+      @profile_and_service_area_pairs = CarrierProfile.carrier_profile_service_area_pairs_for(employer_profile, benefit_application.effective_period.begin.year)\n+\n+      case plan_option_kind\n+      when \"sole_source\"\n+        Plan.where(id: reference_plan_id).first\n+      when \"single_plan\"\n+        Plan.where(id: reference_plan_id).first\n+      when \"single_carrier\"\n+        if carrier_for_elected_plan.blank?\n+          @carrier_for_elected_plan = reference_plan.carrier_profile_id if reference_plan.present?\n+        end\n+        carrier_profile_id = reference_plan.carrier_profile_id\n+        plans = Plan.check_plan_offerings_for_single_carrier # filter by vertical choice(as there should be no bronze plans for one carrier.)\n+        plans.valid_shop_health_plans_for_service_area(\"carrier\", carrier_for_elected_plan, start_on.year, @profile_and_service_area_pairs.select { |pair| pair.first == carrier_profile_id }).to_a\n+      when \"metal_level\"\n+        Plan.valid_shop_health_plans_for_service_area(\"carrier\", carrier_for_elected_plan, start_on.year, @profile_and_service_area_pairs).and(:metal_level => reference_plan.metal_level).to_a\n+      end\n+    end\n+\n+    def effective_title_by_offset\n+      case effective_on_offset\n+      when 0\n+        \"First of the month following or coinciding with date of hire\"\n+      when 1\n+        \"First of the month following date of hire\"\n+      when 30\n+        \"First of the month following 30 days\"\n+      when 60\n+        \"First of the month following 60 days\"\n+      end\n+    end\n+\n+    def delete_benefit_group_assignments_and_enrollments # Also assigns default benefit group assignment\n+      census_employees.each do |ce|\n+        benefit_group_assignments = ce.benefit_group_assignments.where(benefit_group_id: self.id)\n+\n+        if benefit_group_assignments.present?\n+          benefit_group_assignments.each do |bga|\n+            bga.hbx_enrollments.each { |enrollment| enrollment.destroy }\n+            bga.destroy\n+          end\n+\n+          benefit_groups = self.benefit_application.benefit_groups.select { |bg| bg.id != self.id}\n+          ce.find_or_create_benefit_group_assignment(benefit_groups.first)\n+        end\n+      end\n+    end\n+\n+    # Interface for composite and list bill.\n+    # Defines the methods needed for calculation of both composite and list\n+    # bill values.\n+\n+    # Provide the sic factor for this benefit group.\n+    def sic_factor_for(plan)\n+      if use_simple_employer_calculation_model?\n+        return 1.0\n+      end\n+      factor_carrier_id = plan.carrier_profile_id\n+      @scff_cache ||= Hash.new do |h, k|\n+        h[k] = lookup_cached_scf_for(k)\n+      end\n+      @scff_cache[factor_carrier_id]\n+    end\n+\n+    def lookup_cached_scf_for(carrier_id)\n+      year = benefit_application.effective_period.begin.year\n+      SicCodeRatingFactorSet.value_for(carrier_id, year, benefit_application.recorded_sic_code)\n+    end\n+\n+    # Provide the base factor for this composite rating tier.\n+    def composite_rating_tier_factor_for(composite_rating_tier, plan)\n+      factor_carrier_id = plan.carrier_profile_id\n+      lookup_key = [factor_carrier_id, composite_rating_tier]\n+      @crtbf_cache ||= Hash.new do |h, k|\n+        h[k] = lookup_cached_crtbf_for(k)\n+      end\n+      @crtbf_cache[lookup_key]\n+    end\n+\n+    def lookup_cached_crtbf_for(carrier_tier_pair)\n+      year = benefit_application.effective_period.begin.year\n+      CompositeRatingTierFactorSet.value_for(carrier_tier_pair.first, year, carrier_tier_pair.last)\n+    end\n+\n+    # Provide the rating area value for this benefit group.\n+    def rating_area\n+      @rating_area ||= benefit_application.recorded_rating_area\n+    end\n+\n+    # Provide the participation rate factor for this group.\n+    def composite_participation_rate_factor_for(plan)\n+      factor_carrier_id = plan.carrier_profile_id\n+      @cprf_cache ||= Hash.new do |h, k|\n+        h[k] = lookup_cached_cprf_for(k)\n+      end\n+      @cprf_cache[factor_carrier_id]\n+    end\n+\n+    def lookup_cached_cprf_for(carrier_id)\n+      year = benefit_application.effective_period.begin.year\n+      EmployerParticipationRateRatingFactorSet.value_for(carrier_id, year, participation_rate * 100.0)\n+    end\n+\n+    def participation_rate\n+      total_employees = targeted_census_employees.count\n+      return(0.0) if total_employees < 1\n+      waived_and_active_count = if benefit_application.estimate_group_size?\n+                                  targeted_census_employees.select { |ce| ce.expected_to_enroll_or_valid_waive? }.length\n+                                else\n+                                  all_active_and_waived_health_enrollments.length\n+                                end\n+      waived_and_active_count/(total_employees * 1.0)\n+    end\n+\n+    # Provide the group size factor for this benefit group.\n+    def group_size_factor_for(plan)\n+      if use_simple_employer_calculation_model?\n+        return 1.0\n+      end\n+      factor_carrier_id = plan.carrier_profile_id\n+      @gsf_cache ||= Hash.new do |h, k|\n+        h[k] = lookup_cached_gsf_for(k)\n+      end\n+      @gsf_cache[factor_carrier_id]\n+    end\n+\n+    def lookup_cached_gsf_for(carrier_id)\n+      year = benefit_application.effective_period.begin.year\n+      if plan_option_kind == \"sole_source\"\n+        EmployerGroupSizeRatingFactorSet.value_for(carrier_id, year, group_size_count)\n+      else\n+        EmployerGroupSizeRatingFactorSet.value_for(carrier_id, year, 1)\n+      end\n+    end\n+\n+    # Provide the premium for a given composite rating tier.\n+    def composite_rating_tier_premium_for(composite_rating_tier)\n+      @crtp_cache ||= Hash.new do |h, k|\n+        h[k] = lookup_cached_crtp_for(k)\n+      end\n+      @crtp_cache[composite_rating_tier]\n+    end\n+\n+                        def lookup_cached_crtp_for(composite_rating_tier)\n+                          ct_contribution = composite_tier_contributions.detect { |ctc| ctc.composite_rating_tier == composite_rating_tier }\n+                          benefit_application.estimate_group_size? ? ct_contribution.estimated_tier_premium : ct_contribution.final_tier_premium\n+                        end\n+\n+    # Provide the contribution factor for a given composite rating tier.\n+    def composite_employer_contribution_factor_for(composite_rating_tier)\n+      @cecf_cache ||= Hash.new do |h, k|\n+         h[k] = lookup_cached_eccf_for(k)\n+      end\n+      @cecf_cache[composite_rating_tier]\n+    end\n+\n+    def lookup_cached_eccf_for(composite_rating_tier)\n+      ct_contribution = composite_tier_contributions.detect { |ctc| ctc.composite_rating_tier == composite_rating_tier }\n+      ct_contribution.contribution_factor\n+    end\n+\n+    # Count of enrolled employees - either estimated or actual depending on plan\n+    # year status\n+    def group_size_count\n+      if benefit_application.estimate_group_size?\n+        targeted_census_employees.select { |ce| ce.expected_to_enroll? }.length\n+      else\n+        all_active_health_enrollments.length\n+      end\n+    end\n+\n+    def composite_rating_enrollment_objects\n+      if benefit_application.estimate_group_size?\n+        targeted_census_employees.select { |ce| ce.expected_to_enroll? }\n+      else\n+        all_active_health_enrollments\n+      end\n+    end\n+\n+    def all_active_and_waived_health_enrollments\n+      benefit_group_assignments.flat_map do |bga|\n+        bga.active_and_waived_enrollments.reject do |en|\n+          en.dental?\n+        end\n+      end\n+    end\n+\n+    def all_active_health_enrollments\n+      benefit_group_assignments.flat_map do |bga|\n+        bga.active_enrollments.reject do |en|\n+          en.dental?\n+        end\n+      end\n+    end\n+\n+    def sole_source?\n+      plan_option_kind == \"sole_source\"\n+    end\n+\n+    def build_estimated_composite_rates\n+      return(nil) unless sole_source?\n+      rate_calc = CompositeRatingBaseRatesCalculator.new(self, self.elected_plans.try(:first) || reference_plan)\n+      rate_calc.build_estimated_premiums\n+    end\n+\n+    def estimate_composite_rates\n+      return(nil) unless sole_source?\n+      rate_calc = CompositeRatingBaseRatesCalculator.new(self, self.elected_plans.try(:first) || reference_plan)\n+      rate_calc.assign_estimated_premiums\n+    end\n+\n+    def finalize_composite_rates\n+      return(nil) unless sole_source?\n+      rate_calc = CompositeRatingBaseRatesCalculator.new(self, self.elected_plans.first)\n+      rate_calc.assign_final_premiums\n+    end\n+\n+    private\n+\n+    def update_dependent_composite_tiers\n+      family_tier = self.composite_tier_contributions.where(composite_rating_tier: 'family')\n+      return unless family_tier.present?\n+\n+      # Comment out for BGT\n+      # return if benefit_application.is_conversion\n+\n+      contribution = family_tier.first.employer_contribution_percent\n+      estimated_tier_premium = family_tier.first.estimated_tier_premium\n+      offered = family_tier.first.offered\n+\n+      (CompositeRatingTier::NAMES - CompositeRatingTier::VISIBLE_NAMES).each do |crt|\n+        tier = self.composite_tier_contributions.find_or_initialize_by(\n+          composite_rating_tier: crt\n+        )\n+        tier.employer_contribution_percent = contribution\n+        tier.offered = offered\n+      end\n+    end\n+\n+    def dollars_to_cents(amount_in_dollars)\n+      Rational(amount_in_dollars) * Rational(100) if amount_in_dollars\n+    end\n+\n+    def cents_to_dollars(amount_in_cents)\n+      (Rational(amount_in_cents) / Rational(100)).to_f if amount_in_cents\n+    end\n+\n+    def is_eligible_to_enroll_on?(date_of_hire, enrollment_date = TimeKeeper.date_of_record)\n+\n+      # Length of time prior to effective date that EE may purchase plan\n+      Settings.aca.shop_market.earliest_enroll_prior_to_effective_on.days\n+\n+      # Length of time following effective date that EE may purchase plan\n+      Settings.aca.shop_market.latest_enroll_after_effective_on.days\n+\n+      # Length of time that EE may enroll following correction to Census Employee Identifying info\n+      Settings.aca.shop_market.latest_enroll_after_employee_roster_correction_on.days\n+\n+    end\n+\n+    # Non-congressional\n+    # pick reference plan\n+    # two pctages\n+    # toward employee\n+    # toward each dependent type\n+\n+    # member level premium in reference plan, apply pctage by type, calc $$ amount.\n+    # may be applied toward and other offered plan\n+    # never pay more than premium per person\n+    # extra may not be applied toward other members\n+\n+    def plan_integrity\n+      return if elected_plan_ids.blank?\n+\n+      if (plan_option_kind == \"single_plan\") && (elected_plan_ids.first != reference_plan_id)\n+        self.errors.add(:elected_plans, \"single plan must be the reference plan\")\n+      end\n+\n+      if (plan_option_kind == \"single_carrier\")\n+        if !(elected_plan_ids.include? reference_plan_id)\n+          self.errors.add(:elected_plans, \"single carrier must include reference plan\")\n+        end\n+        if elected_plans.detect { |plan| plan.carrier_profile_id != reference_plan.try(:carrier_profile_id) }\n+          self.errors.add(:elected_plans, \"not all from the same carrier as reference plan\")\n+        end\n+      end\n+\n+      if (plan_option_kind == \"metal_level\") && !(elected_plan_ids.include? reference_plan_id)\n+        self.errors.add(:elected_plans, \"not all of the same metal level as reference plan\")\n+      end\n+    end\n+\n+    def check_employer_contribution_for_employee\n+      start_on = benefit_application.effective_period.try(:begin)\n+      return if start_on.try(:at_beginning_of_year) == start_on\n+\n+      # all employee contribution < 50% for 1/1 employers\n+      if start_on.month == 1 && start_on.day == 1\n+      else\n+        if self.sole_source?\n+          unless composite_tier_contributions.present?\n+            self.errors.add(:composite_rating_tier, \"Employer must set contribution percentages\")\n+          else\n+            employee_tier = composite_tier_contributions.find_by(composite_rating_tier: 'employee_only')\n+\n+            if aca_shop_market_employer_contribution_percent_minimum > (employee_tier.try(:employer_contribution_percent) || 0)\n+              self.errors.add(:composite_tier_contributions,\n+              \"Employer contribution for employee must be ≥ #{aca_shop_market_employer_contribution_percent_minimum}%\")\n+            else\n+              family_tier = composite_tier_contributions.find_by(composite_rating_tier: 'family')\n+              if family_tier.offered? &&\n+                (family_tier.try(:employer_contribution_percent) || 0) < aca_shop_market_employer_family_contribution_percent_minimum\n+                  self.errors.add(:composite_tier_contributions, \"Employer contribution for family plans must be ≥ #{aca_shop_market_employer_family_contribution_percent_minimum}\")\n+              end\n+            end\n+          end\n+        else\n+          if relationship_benefits.present? && (relationship_benefits.find_by(relationship: \"employee\").try(:premium_pct) || 0) < aca_shop_market_employer_contribution_percent_minimum\n+            self.errors.add(:relationship_benefits, \"Employer contribution must be ≥ #{aca_shop_market_employer_contribution_percent_minimum}% for employee\")\n+          end\n+        end\n+      end\n+    end\n+\n+    def check_offered_for_employee\n+      if relationship_benefits.present? && (relationship_benefits.find_by(relationship: \"employee\").try(:offered) != true)","path":"components/sponsored_benefits/app/models/sponsored_benefits/benefit_group.rb","position":672,"original_position":672,"commit_id":"181dd91e35fd7b0d32221eef7d876de3a5d96a48","original_commit_id":"181dd91e35fd7b0d32221eef7d876de3a5d96a48","user":{"login":"houndci-bot","id":6697940,"node_id":"MDQ6VXNlcjY2OTc5NDA=","avatar_url":"https://avatars0.githubusercontent.com/u/6697940?v=4","gravatar_id":"","url":"https://api.github.com/users/houndci-bot","html_url":"https://github.com/houndci-bot","followers_url":"https://api.github.com/users/houndci-bot/followers","following_url":"https://api.github.com/users/houndci-bot/following{/other_user}","gists_url":"https://api.github.com/users/houndci-bot/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci-bot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci-bot/subscriptions","organizations_url":"https://api.github.com/users/houndci-bot/orgs","repos_url":"https://api.github.com/users/houndci-bot/repos","events_url":"https://api.github.com/users/houndci-bot/events{/privacy}","received_events_url":"https://api.github.com/users/houndci-bot/received_events","type":"User","site_admin":false},"body":"Style/GuardClause: Use a guard clause instead of wrapping the code inside a conditional expression.<br>Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.","created_at":"2019-03-19T18:44:25Z","updated_at":"2019-03-19T18:44:31Z","html_url":"https://github.com/dchbx/enroll/pull/2515#discussion_r267044573","pull_request_url":"https://api.github.com/repos/dchbx/enroll/pulls/2515","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/comments/267044573"},"html":{"href":"https://github.com/dchbx/enroll/pull/2515#discussion_r267044573"},"pull_request":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/2515"}}},"pull_request":{"url":"https://api.github.com/repos/dchbx/enroll/pulls/2515","id":247714860,"node_id":"MDExOlB1bGxSZXF1ZXN0MjQ3NzE0ODYw","html_url":"https://github.com/dchbx/enroll/pull/2515","diff_url":"https://github.com/dchbx/enroll/pull/2515.diff","patch_url":"https://github.com/dchbx/enroll/pull/2515.patch","issue_url":"https://api.github.com/repos/dchbx/enroll/issues/2515","number":2515,"state":"open","locked":false,"title":"40068","user":{"login":"nisanthyaganti9","id":17226451,"node_id":"MDQ6VXNlcjE3MjI2NDUx","avatar_url":"https://avatars0.githubusercontent.com/u/17226451?v=4","gravatar_id":"","url":"https://api.github.com/users/nisanthyaganti9","html_url":"https://github.com/nisanthyaganti9","followers_url":"https://api.github.com/users/nisanthyaganti9/followers","following_url":"https://api.github.com/users/nisanthyaganti9/following{/other_user}","gists_url":"https://api.github.com/users/nisanthyaganti9/gists{/gist_id}","starred_url":"https://api.github.com/users/nisanthyaganti9/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nisanthyaganti9/subscriptions","organizations_url":"https://api.github.com/users/nisanthyaganti9/orgs","repos_url":"https://api.github.com/users/nisanthyaganti9/repos","events_url":"https://api.github.com/users/nisanthyaganti9/events{/privacy}","received_events_url":"https://api.github.com/users/nisanthyaganti9/received_events","type":"User","site_admin":false},"body":"|Field|Value|\n|-----|-----|\n| Redmine Ticket Number | [40068](https://devops.dchbx.org/redmine/issues/40068) |\n| App-Dev Road Map # | n/a |\n| Start Date | n/a |\n| Due Date | n/a  |\n| App-Dev Priority | UP-Disqueued |\n| Development Story Points | n/a |\n| Peer Review Story Points | n/a |\n| Ticket Author | Angus Irvine |","created_at":"2019-01-25T15:36:24Z","updated_at":"2019-03-19T18:44:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0ed2a262b139831dc31dd9593f7657b3dd986d25","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":850356543,"node_id":"MDU6TGFiZWw4NTAzNTY1NDM=","url":"https://api.github.com/repos/dchbx/enroll/labels/Assignee:%20Angus%20Irvine","name":"Assignee: Angus Irvine","color":"ffffff","default":false},{"id":511705379,"node_id":"MDU6TGFiZWw1MTE3MDUzNzk=","url":"https://api.github.com/repos/dchbx/enroll/labels/Mergeable:%20Yes","name":"Mergeable: Yes","color":"bfdadc","default":false},{"id":442912940,"node_id":"MDU6TGFiZWw0NDI5MTI5NDA=","url":"https://api.github.com/repos/dchbx/enroll/labels/Priority:%20Immediate","name":"Priority: Immediate","color":"1d76db","default":false},{"id":634542275,"node_id":"MDU6TGFiZWw2MzQ1NDIyNzU=","url":"https://api.github.com/repos/dchbx/enroll/labels/Status:%20Promote%20to%20Test%20Environment","name":"Status: Promote to Test Environment","color":"ffffff","default":false},{"id":442931642,"node_id":"MDU6TGFiZWw0NDI5MzE2NDI=","url":"https://api.github.com/repos/dchbx/enroll/labels/Tracker:%20App-Dev_User%20Story","name":"Tracker: App-Dev_User Story","color":"d4c5f9","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/dchbx/enroll/pulls/2515/commits","review_comments_url":"https://api.github.com/repos/dchbx/enroll/pulls/2515/comments","review_comment_url":"https://api.github.com/repos/dchbx/enroll/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dchbx/enroll/issues/2515/comments","statuses_url":"https://api.github.com/repos/dchbx/enroll/statuses/181dd91e35fd7b0d32221eef7d876de3a5d96a48","head":{"label":"dchbx:bqt_dc","ref":"bqt_dc","sha":"181dd91e35fd7b0d32221eef7d876de3a5d96a48","user":{"login":"dchbx","id":4580217,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1ODAyMTc=","avatar_url":"https://avatars1.githubusercontent.com/u/4580217?v=4","gravatar_id":"","url":"https://api.github.com/users/dchbx","html_url":"https://github.com/dchbx","followers_url":"https://api.github.com/users/dchbx/followers","following_url":"https://api.github.com/users/dchbx/following{/other_user}","gists_url":"https://api.github.com/users/dchbx/gists{/gist_id}","starred_url":"https://api.github.com/users/dchbx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dchbx/subscriptions","organizations_url":"https://api.github.com/users/dchbx/orgs","repos_url":"https://api.github.com/users/dchbx/repos","events_url":"https://api.github.com/users/dchbx/events{/privacy}","received_events_url":"https://api.github.com/users/dchbx/received_events","type":"Organization","site_admin":false},"repo":{"id":28518679,"node_id":"MDEwOlJlcG9zaXRvcnkyODUxODY3OQ==","name":"enroll","full_name":"dchbx/enroll","private":false,"owner":{"login":"dchbx","id":4580217,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1ODAyMTc=","avatar_url":"https://avatars1.githubusercontent.com/u/4580217?v=4","gravatar_id":"","url":"https://api.github.com/users/dchbx","html_url":"https://github.com/dchbx","followers_url":"https://api.github.com/users/dchbx/followers","following_url":"https://api.github.com/users/dchbx/following{/other_user}","gists_url":"https://api.github.com/users/dchbx/gists{/gist_id}","starred_url":"https://api.github.com/users/dchbx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dchbx/subscriptions","organizations_url":"https://api.github.com/users/dchbx/orgs","repos_url":"https://api.github.com/users/dchbx/repos","events_url":"https://api.github.com/users/dchbx/events{/privacy}","received_events_url":"https://api.github.com/users/dchbx/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dchbx/enroll","description":"An ACA-compliant enrollment system for individual and SHOP markets, built on Ruby on Rails, with a Mongo database","fork":false,"url":"https://api.github.com/repos/dchbx/enroll","forks_url":"https://api.github.com/repos/dchbx/enroll/forks","keys_url":"https://api.github.com/repos/dchbx/enroll/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dchbx/enroll/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dchbx/enroll/teams","hooks_url":"https://api.github.com/repos/dchbx/enroll/hooks","issue_events_url":"https://api.github.com/repos/dchbx/enroll/issues/events{/number}","events_url":"https://api.github.com/repos/dchbx/enroll/events","assignees_url":"https://api.github.com/repos/dchbx/enroll/assignees{/user}","branches_url":"https://api.github.com/repos/dchbx/enroll/branches{/branch}","tags_url":"https://api.github.com/repos/dchbx/enroll/tags","blobs_url":"https://api.github.com/repos/dchbx/enroll/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dchbx/enroll/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dchbx/enroll/git/refs{/sha}","trees_url":"https://api.github.com/repos/dchbx/enroll/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dchbx/enroll/statuses/{sha}","languages_url":"https://api.github.com/repos/dchbx/enroll/languages","stargazers_url":"https://api.github.com/repos/dchbx/enroll/stargazers","contributors_url":"https://api.github.com/repos/dchbx/enroll/contributors","subscribers_url":"https://api.github.com/repos/dchbx/enroll/subscribers","subscription_url":"https://api.github.com/repos/dchbx/enroll/subscription","commits_url":"https://api.github.com/repos/dchbx/enroll/commits{/sha}","git_commits_url":"https://api.github.com/repos/dchbx/enroll/git/commits{/sha}","comments_url":"https://api.github.com/repos/dchbx/enroll/comments{/number}","issue_comment_url":"https://api.github.com/repos/dchbx/enroll/issues/comments{/number}","contents_url":"https://api.github.com/repos/dchbx/enroll/contents/{+path}","compare_url":"https://api.github.com/repos/dchbx/enroll/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dchbx/enroll/merges","archive_url":"https://api.github.com/repos/dchbx/enroll/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dchbx/enroll/downloads","issues_url":"https://api.github.com/repos/dchbx/enroll/issues{/number}","pulls_url":"https://api.github.com/repos/dchbx/enroll/pulls{/number}","milestones_url":"https://api.github.com/repos/dchbx/enroll/milestones{/number}","notifications_url":"https://api.github.com/repos/dchbx/enroll/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dchbx/enroll/labels{/name}","releases_url":"https://api.github.com/repos/dchbx/enroll/releases{/id}","deployments_url":"https://api.github.com/repos/dchbx/enroll/deployments","created_at":"2014-12-26T18:59:14Z","updated_at":"2019-03-18T16:09:21Z","pushed_at":"2019-03-19T18:44:20Z","git_url":"git://github.com/dchbx/enroll.git","ssh_url":"git@github.com:dchbx/enroll.git","clone_url":"https://github.com/dchbx/enroll.git","svn_url":"https://github.com/dchbx/enroll","homepage":"https://dchealthlink.com/","size":146369,"stargazers_count":20,"watchers_count":20,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":30,"mirror_url":null,"archived":false,"open_issues_count":405,"license":null,"forks":30,"open_issues":405,"watchers":20,"default_branch":"master"}},"base":{"label":"dchbx:master","ref":"master","sha":"68e55a32ba4bf3a89b2edab9e280f074fa368c07","user":{"login":"dchbx","id":4580217,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1ODAyMTc=","avatar_url":"https://avatars1.githubusercontent.com/u/4580217?v=4","gravatar_id":"","url":"https://api.github.com/users/dchbx","html_url":"https://github.com/dchbx","followers_url":"https://api.github.com/users/dchbx/followers","following_url":"https://api.github.com/users/dchbx/following{/other_user}","gists_url":"https://api.github.com/users/dchbx/gists{/gist_id}","starred_url":"https://api.github.com/users/dchbx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dchbx/subscriptions","organizations_url":"https://api.github.com/users/dchbx/orgs","repos_url":"https://api.github.com/users/dchbx/repos","events_url":"https://api.github.com/users/dchbx/events{/privacy}","received_events_url":"https://api.github.com/users/dchbx/received_events","type":"Organization","site_admin":false},"repo":{"id":28518679,"node_id":"MDEwOlJlcG9zaXRvcnkyODUxODY3OQ==","name":"enroll","full_name":"dchbx/enroll","private":false,"owner":{"login":"dchbx","id":4580217,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ1ODAyMTc=","avatar_url":"https://avatars1.githubusercontent.com/u/4580217?v=4","gravatar_id":"","url":"https://api.github.com/users/dchbx","html_url":"https://github.com/dchbx","followers_url":"https://api.github.com/users/dchbx/followers","following_url":"https://api.github.com/users/dchbx/following{/other_user}","gists_url":"https://api.github.com/users/dchbx/gists{/gist_id}","starred_url":"https://api.github.com/users/dchbx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dchbx/subscriptions","organizations_url":"https://api.github.com/users/dchbx/orgs","repos_url":"https://api.github.com/users/dchbx/repos","events_url":"https://api.github.com/users/dchbx/events{/privacy}","received_events_url":"https://api.github.com/users/dchbx/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/dchbx/enroll","description":"An ACA-compliant enrollment system for individual and SHOP markets, built on Ruby on Rails, with a Mongo database","fork":false,"url":"https://api.github.com/repos/dchbx/enroll","forks_url":"https://api.github.com/repos/dchbx/enroll/forks","keys_url":"https://api.github.com/repos/dchbx/enroll/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dchbx/enroll/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dchbx/enroll/teams","hooks_url":"https://api.github.com/repos/dchbx/enroll/hooks","issue_events_url":"https://api.github.com/repos/dchbx/enroll/issues/events{/number}","events_url":"https://api.github.com/repos/dchbx/enroll/events","assignees_url":"https://api.github.com/repos/dchbx/enroll/assignees{/user}","branches_url":"https://api.github.com/repos/dchbx/enroll/branches{/branch}","tags_url":"https://api.github.com/repos/dchbx/enroll/tags","blobs_url":"https://api.github.com/repos/dchbx/enroll/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dchbx/enroll/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dchbx/enroll/git/refs{/sha}","trees_url":"https://api.github.com/repos/dchbx/enroll/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dchbx/enroll/statuses/{sha}","languages_url":"https://api.github.com/repos/dchbx/enroll/languages","stargazers_url":"https://api.github.com/repos/dchbx/enroll/stargazers","contributors_url":"https://api.github.com/repos/dchbx/enroll/contributors","subscribers_url":"https://api.github.com/repos/dchbx/enroll/subscribers","subscription_url":"https://api.github.com/repos/dchbx/enroll/subscription","commits_url":"https://api.github.com/repos/dchbx/enroll/commits{/sha}","git_commits_url":"https://api.github.com/repos/dchbx/enroll/git/commits{/sha}","comments_url":"https://api.github.com/repos/dchbx/enroll/comments{/number}","issue_comment_url":"https://api.github.com/repos/dchbx/enroll/issues/comments{/number}","contents_url":"https://api.github.com/repos/dchbx/enroll/contents/{+path}","compare_url":"https://api.github.com/repos/dchbx/enroll/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dchbx/enroll/merges","archive_url":"https://api.github.com/repos/dchbx/enroll/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dchbx/enroll/downloads","issues_url":"https://api.github.com/repos/dchbx/enroll/issues{/number}","pulls_url":"https://api.github.com/repos/dchbx/enroll/pulls{/number}","milestones_url":"https://api.github.com/repos/dchbx/enroll/milestones{/number}","notifications_url":"https://api.github.com/repos/dchbx/enroll/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dchbx/enroll/labels{/name}","releases_url":"https://api.github.com/repos/dchbx/enroll/releases{/id}","deployments_url":"https://api.github.com/repos/dchbx/enroll/deployments","created_at":"2014-12-26T18:59:14Z","updated_at":"2019-03-18T16:09:21Z","pushed_at":"2019-03-19T18:44:20Z","git_url":"git://github.com/dchbx/enroll.git","ssh_url":"git@github.com:dchbx/enroll.git","clone_url":"https://github.com/dchbx/enroll.git","svn_url":"https://github.com/dchbx/enroll","homepage":"https://dchealthlink.com/","size":146369,"stargazers_count":20,"watchers_count":20,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":30,"mirror_url":null,"archived":false,"open_issues_count":405,"license":null,"forks":30,"open_issues":405,"watchers":20,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/2515"},"html":{"href":"https://github.com/dchbx/enroll/pull/2515"},"issue":{"href":"https://api.github.com/repos/dchbx/enroll/issues/2515"},"comments":{"href":"https://api.github.com/repos/dchbx/enroll/issues/2515/comments"},"review_comments":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/2515/comments"},"review_comment":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dchbx/enroll/pulls/2515/commits"},"statuses":{"href":"https://api.github.com/repos/dchbx/enroll/statuses/181dd91e35fd7b0d32221eef7d876de3a5d96a48"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"houndci-bot"}}