diff --git a/.rubocop.yml b/.rubocop.yml index d4b6c86..63e4e8e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,7 +7,7 @@ inherit_gem: rubocop-rails-omakase: rubocop.yml AllCops: - TargetRubyVersion: 3.3.9 + TargetRubyVersion: 3.4.8 TargetRailsVersion: 7.2.2 DisabledByDefault: true Exclude: diff --git a/.ruby-version b/.ruby-version index 3b47f2e..7921bd0 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.9 +3.4.8 diff --git a/Gemfile b/Gemfile index fa5d223..253f562 100644 --- a/Gemfile +++ b/Gemfile @@ -83,9 +83,9 @@ gem 'devise' gem 'pundit' gem "tailwindcss-rails" gem 'docx' -gem 'rubyzip' gem 'httparty' gem 'combine_pdf' gem 'rails_icons' gem 'fastimage' -gem 'rubyzip', require: 'zip' \ No newline at end of file +gem 'rubyzip', require: 'zip' +gem "solid_queue" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index bd1fa19..8f53c16 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,7 +56,7 @@ GEM activemodel (= 7.2.3) activesupport (= 7.2.3) timeout (>= 0.4.0) - activerecord-sqlserver-adapter (7.2.8) + activerecord-sqlserver-adapter (7.2.9) activerecord (~> 7.2.0) tiny_tds activestorage (7.2.3) @@ -77,21 +77,21 @@ GEM minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) base64 (0.3.0) - bcrypt (3.1.20) + bcrypt (3.1.22) benchmark (0.5.0) - bigdecimal (3.3.1) + bigdecimal (4.0.1) bindex (0.8.1) - bootsnap (1.18.6) + bootsnap (1.23.0) msgpack (~> 1.2) - brakeman (7.1.1) + brakeman (8.0.4) racc builder (3.3.0) - bundler-audit (0.9.2) - bundler (>= 1.2.0, < 3) + bundler-audit (0.9.3) + bundler (>= 1.2.0) thor (~> 1.0) capybara (3.40.0) addressable @@ -102,20 +102,20 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - cgi (0.5.0) + cgi (0.5.1) coderay (1.1.3) combine_pdf (1.0.31) matrix ruby-rc4 (>= 0.1.5) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) crass (1.0.6) csv (3.3.5) - date (3.5.0) - devise (4.9.4) + date (3.5.1) + devise (5.0.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 4.1.0) + railties (>= 7.0) responders warden (~> 1.2.3) diff-lcs (1.6.2) @@ -123,34 +123,42 @@ GEM nokogiri (~> 1.13, >= 1.13.0) rubyzip (>= 2.0, < 4) drb (2.2.3) - erb (5.1.3) + erb (6.0.2) erubi (1.13.1) - fastimage (2.4.0) + et-orbi (1.4.0) + tzinfo + fastimage (2.4.1) + fugit (1.12.1) + et-orbi (~> 1.4) + raabro (~> 1.4) globalid (1.3.0) activesupport (>= 6.1) - httparty (0.23.2) + httparty (0.24.2) csv mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.14.7) + i18n (1.14.8) concurrent-ruby (~> 1.0) - importmap-rails (2.2.2) + icons (0.8.1) + nokogiri (~> 1.16, >= 1.16.4) + importmap-rails (2.2.3) actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.17.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.15.2) + json (2.19.2) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) - loofah (2.24.1) + loofah (2.25.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.9.0) @@ -163,11 +171,13 @@ GEM matrix (0.4.3) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.26.0) + minitest (6.0.2) + drb (~> 2.0) + prism (~> 1.5) msgpack (1.8.0) - multi_xml (0.7.2) - bigdecimal (~> 3.1) - net-imap (0.5.12) + multi_xml (0.8.1) + bigdecimal (>= 3.1, < 5) + net-imap (0.6.3) date net-protocol net-pop (0.1.2) @@ -177,46 +187,34 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.18.10-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-musl) + nokogiri (1.19.2-x86_64-linux-gnu) racc (~> 1.4) orm_adapter (0.5.0) parallel (1.27.0) - parser (3.3.10.0) + parser (3.3.10.2) ast (~> 2.4.1) racc pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) - pry (0.15.2) + prism (1.9.0) + pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) + reline (>= 0.6.0) pry-rails (0.3.11) pry (>= 0.13.0) - psych (5.2.6) + psych (5.3.1) date stringio - public_suffix (6.0.2) + public_suffix (7.0.5) puma (6.6.1) nio4r (~> 2.0) pundit (2.5.2) activesupport (>= 3.0.0) + raabro (1.4.0) racc (1.8.1) - rack (3.2.4) + rack (3.2.5) rack-mini-profiler (4.0.1) rack (>= 1.2.0) rack-session (2.1.1) @@ -224,7 +222,7 @@ GEM rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) rails (7.2.3) actioncable (= 7.2.3) @@ -244,11 +242,11 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails_icons (1.5.1) - nokogiri (~> 1.16, >= 1.16.4) + rails_icons (1.7.1) + icons (~> 0.8.1) rails (>= 7.0) railties (7.2.3) actionpack (= 7.2.3) @@ -262,12 +260,12 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) - rdoc (6.15.1) + rdoc (7.2.0) erb psych (>= 4.0.0) tsort regexp_parser (2.11.3) - reline (0.6.2) + reline (0.6.3) io-console (~> 0.5) responders (3.2.0) actionpack (>= 7.0) @@ -278,19 +276,19 @@ GEM rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.2) + rspec-rails (8.0.4) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) - rspec-core (~> 3.13) - rspec-expectations (~> 3.13) - rspec-mocks (~> 3.13) - rspec-support (~> 3.13) - rspec-support (3.13.6) - rubocop (1.81.7) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) + rubocop (1.86.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -298,17 +296,17 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) - prism (~> 1.4) + prism (~> 1.7) rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.33.4) + rubocop-rails (2.34.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -318,19 +316,26 @@ GEM rubocop (>= 1.72) rubocop-performance (>= 1.24) rubocop-rails (>= 2.30) - rubocop-rspec (3.7.0) + rubocop-rspec (3.9.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) + rubocop (~> 1.81) ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) rubyzip (3.2.2) securerandom (0.4.1) - selenium-webdriver (4.38.0) + selenium-webdriver (4.41.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) + solid_queue (1.4.0) + activejob (>= 7.1) + activerecord (>= 7.1) + concurrent-ruby (>= 1.3.1) + fugit (~> 1.11) + railties (>= 7.1) + thor (>= 1.3.1) sprockets (4.2.2) concurrent-ruby (~> 1.0) logger @@ -341,38 +346,24 @@ GEM sprockets (>= 3.0.0) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.1.7) + stringio (3.2.0) tailwindcss-rails (4.4.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) - tailwindcss-ruby (4.1.16) - tailwindcss-ruby (4.1.16-aarch64-linux-gnu) - tailwindcss-ruby (4.1.16-aarch64-linux-musl) - tailwindcss-ruby (4.1.16-arm64-darwin) - tailwindcss-ruby (4.1.16-x86_64-darwin) - tailwindcss-ruby (4.1.16-x86_64-linux-gnu) - tailwindcss-ruby (4.1.16-x86_64-linux-musl) - thor (1.4.0) - timeout (0.4.4) - tiny_tds (3.3.0) - bigdecimal (~> 3) - tiny_tds (3.3.0-aarch64-linux-gnu) - bigdecimal (~> 3) - tiny_tds (3.3.0-aarch64-linux-musl) - bigdecimal (~> 3) - tiny_tds (3.3.0-x86_64-linux-gnu) - bigdecimal (~> 3) - tiny_tds (3.3.0-x86_64-linux-musl) - bigdecimal (~> 3) + tailwindcss-ruby (4.2.1-x86_64-linux-gnu) + thor (1.5.0) + timeout (0.6.1) + tiny_tds (3.4.0-x86_64-linux-gnu) + bigdecimal (>= 2.0.0) tsort (0.2.0) - turbo-rails (2.0.20) + turbo-rails (2.0.23) actionpack (>= 7.1.0) railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) useragent (0.16.11) warden (1.2.9) rack (>= 2.0.9) @@ -388,17 +379,10 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.3) + zeitwerk (2.7.5) PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl + x86_64-linux DEPENDENCIES activerecord-sqlserver-adapter @@ -425,6 +409,7 @@ DEPENDENCIES rubocop-rspec rubyzip selenium-webdriver + solid_queue sprockets-rails stimulus-rails tailwindcss-rails @@ -437,4 +422,4 @@ RUBY VERSION ruby 3.4.8p72 BUNDLED WITH - 2.5.22 + 2.6.9 diff --git a/app/controllers/employers_controller.rb b/app/controllers/employers_controller.rb index 3cf17cf..eb60505 100644 --- a/app/controllers/employers_controller.rb +++ b/app/controllers/employers_controller.rb @@ -72,7 +72,7 @@ class EmployersController < ApplicationController def import word_doc = params[:employer][:import_from_word] if word_doc.present? && word_doc.is_a?(ActionDispatch::Http::UploadedFile) - @employer = WordDocProcessor.new(word_doc.tempfile).call + @employer = BenefitsWordDocService::WordDocProcessor.new(word_doc.tempfile).call @employer.save redirect_to employer_path(@employer.slug), notice: 'Employer Imported' else diff --git a/app/controllers/id_card/print_controller.rb b/app/controllers/id_card/print_controller.rb index 4ef93e4..da1007c 100644 --- a/app/controllers/id_card/print_controller.rb +++ b/app/controllers/id_card/print_controller.rb @@ -70,7 +70,7 @@ module IdCard slug = params[:id] @employer = Employer.find_by(slug: slug) end - cards_pdf = IdCardPrinterService::EmployerCardsGenerator.new(@employer, "PrintCard").call + cards_pdf = IdCardPrinterService::CardsGenerator.new(@employer.employer_member_keys, "PrintCard").call send_data cards_pdf.to_pdf, filename: "#{@employer.name.parameterize(separator: "_")}_print_cards_#{Date.today}.pdf", diff --git a/app/jobs/process_id_card_data_job.rb b/app/jobs/process_id_card_data_job.rb new file mode 100644 index 0000000..7b0cf00 --- /dev/null +++ b/app/jobs/process_id_card_data_job.rb @@ -0,0 +1,90 @@ +class ProcessIdCardDataJob < ApplicationJob + queue_as :default + + def perform(member_key, field_exception_ids, has_divisions = false) + member = Member.find_by(pb_entity_key: member_key) + + effect_date = determine_eff_date(member) + if effect_date + member_card = @base_card.dup + member_attributes = { + full_name: member.id_card_display_name, + full_name_last_name_first: member.name, + primary_mb_member_key: member.pb_entity_key, + family_id: member.family_id, + plan_id: member.id_card_plan_id, + medical_eff_date: effect_date.strftime("%m/%d/%Y") + } + + dependent_attributes = get_dependent_fields(member) + if dependent_attributes.present? + member_attributes.merge!(dependent_attributes) + end + + if has_divisions + member_attributes.merge!({employer_name: member.division}) + end + + if field_exception_ids.present? + exceptions_attributes = {} + field_exceptions = IdCard::FieldException.where(id: field_exception_ids) + member_exception_values = member.id_card_field_exception_values + field_exceptions.each do |fe| + if member_exception_values[fe.field_type] == fe.field_value + fe.field_exception_items.each do |fei| + if fei.field_value.present? + exceptions_attributes[fei.field_name] = fei.field_value + elsif fei.provider_section_id.present? + provider_attributes = IdCard::ProviderSection.find(fei.provider_section_id).attributes.with_indifferent_access.slice( + :provider_line_1, :provider_line_2, :provider_line_3, :provider_line_4, :provider_line_5, :provider_line_6, + :provider_line_7, :provider_line_8, :provider_line_9, :provider_line_10, :provider_line_11, :provider_line_12, + :claim_to_1, :claim_to_2, :claim_to_3, :claim_to_4, :claim_to_5, :claim_to_6, + :claim_to_7, :claim_to_8, :claim_to_9, :claim_to_10, :claim_to_11, :claim_to_12 + ) + exceptions_attributes.merge!(provider_attributes) + elsif fei.network_logo_id.present? + exceptions_attributes.merge!(network_logo_id: fei.network_logo_id) + exceptions_attributes.merge!(network_logo_filename: fei.network_logo.filename) + end + end + end + end + member_attributes.merge!(exceptions_attributes) + end + + member_card = IdCard::PrintData.find_by(pl_plan_key: member.pl_plan_key, plan_id: member.id_card_plan_id, primary_mb_member_key: nil).dup + member_card.assign_attributes(member_attributes) + member_card.save + end + + true + + # BatchProcess.increment_counter(:completed_jobs, batch_process_id) + + end + + private + + def determine_eff_date(member) + participation = Vhcs::PbProductParticipation.joins('INNER JOIN "PBCoveredEntities" ON "PBProductParticipation"."PBProductParticipationKey" = "PBCoveredEntities"."PBProductParticipationKey"').where('"PBCoveredEntities"."PBEntityKey" = ?', member.pb_entity_key).last + in_effect = participation.in_effect + out_of_effect = participation.out_of_effect + + if in_effect <= (Date.today + 90.days) && (out_of_effect - 1.day) > Date.today && out_of_effect > in_effect + in_effect + else + false + end + end + + def get_dependent_fields(member) + dependent_attributes = {} + dependents = Vhcs::VwmbMember.where(pl_plan_key: member.pl_plan_key, family_id: member.family_id).where.not(pb_entity_key: member.pb_entity_key) + dependents.each do |dep| + dependent_name = dep.first_name + ' ' + dep.last_name + dependent_attributes["dependent_#{dep.sequence_number - 1}".to_sym] = dependent_name + end + dependent_attributes + end + +end \ No newline at end of file diff --git a/app/models/employer.rb b/app/models/employer.rb index 2e85310..177dc62 100644 --- a/app/models/employer.rb +++ b/app/models/employer.rb @@ -46,6 +46,13 @@ class Employer < ApplicationRecord false end + def employer_member_keys + { + pl_plan_key: self.pl_plan_key, + member_keys: self.members.pluck(:pb_entity_key) + } + end + def name_to_logo_filename(extension) self.employer_trim_name(self.name).titleize.gsub(/[^a-zA-Z]/, '').concat('Logo').concat(extension.downcase) end diff --git a/app/models/id_card/setup.rb b/app/models/id_card/setup.rb index b6e32b4..01ef045 100644 --- a/app/models/id_card/setup.rb +++ b/app/models/id_card/setup.rb @@ -36,6 +36,10 @@ module IdCard plan end + def has_field_exceptions? + self.field_exceptions.present? + end + def self.permitted_params(params) params.require(:id_card_setup).permit( :print_name, diff --git a/app/models/member.rb b/app/models/member.rb index ec21f22..aa81b8f 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -2,5 +2,16 @@ class Member < ApplicationRecord belongs_to :id_card_plan, class_name: 'IdCard::Plan', optional: true belongs_to :employer + def id_card_field_exception_values + address = Vhcs::PbEntityAddress.find_by(pb_entity_key: self.pb_entity_key) + { + zipcode: address.zip, + state: address.state, + family_id: self.family_id + } + end + + + end diff --git a/app/models/vhcs/pb_entity_address.rb b/app/models/vhcs/pb_entity_address.rb new file mode 100644 index 0000000..aac5539 --- /dev/null +++ b/app/models/vhcs/pb_entity_address.rb @@ -0,0 +1,47 @@ +module Vhcs + class PbEntityAddress < VhcsRecord + + self.table_name = 'PBEntityAddress' + + alias_attribute :pb_entity_address_key, :PBEntityAddressKey + alias_attribute :pb_entity_key, :PBEntityKey + alias_attribute :address_type_id, :AddressTypeID + alias_attribute :street_line_1, :StreetLine1 + alias_attribute :street_line_2, :StreetLine2 + alias_attribute :city, :City + alias_attribute :state, :State + alias_attribute :zip, :Zip + alias_attribute :country_code, :CountryCode + alias_attribute :county, :County + alias_attribute :override_pb_entity_address_key, :OverridePBEntityAddressKey + alias_attribute :when_last_changed, :WhenLastChanged + alias_attribute :who_last_changed, :WhoLastChanged + alias_attribute :full_address_to_use, :FullAddressToUse + alias_attribute :country_sub_div, :CountrySubDiv + alias_attribute :full_address, :FullAddress + + def attributes + rails_like = { + pb_entity_address_key: self.pb_entity_address_key, + pb_entity_key: self.pb_entity_key, + address_type_id: self.address_type_id, + street_line_1: self.street_line_1, + street_line_2: self.street_line_2, + city: self.city, + state: self.state, + zip: self.zip, + country_code: self.country_code, + county: self.county, + override_pb_entity_address_key: self.override_pb_entity_address_key, + when_last_changed: self.when_last_changed, + who_last_changed: self.who_last_changed, + full_address_to_use: self.full_address_to_use, + country_sub_div: self.country_sub_div, + full_address: self.full_address, + } + super.merge(rails_like) + end + + + end +end \ No newline at end of file diff --git a/app/services/id_card_printer_service/card_data_formatter.rb b/app/services/id_card_printer_service/card_data_formatter.rb new file mode 100644 index 0000000..5ab5f63 --- /dev/null +++ b/app/services/id_card_printer_service/card_data_formatter.rb @@ -0,0 +1,180 @@ +module IdCardPrinterService + class CardDataFormatter + + def initialize(employers_member_keys) + # @employer = employer + @employers_member_keys = employers_member_keys + + # @employer = Employer.includes(id_card_setup: [:provider_section, :rx_section]).find_by(pl_plan_key: pl_plan_key) + # @card_setup = @employer.id_card_setup + end + + def call + # blank_card = IdCard::PrintData.new() + # @employer_cards = [] + print_card_futures = [] + @employers_member_keys.each do |emk| + pl_plan_key = emk[:pl_plan_key] + @member_keys = emk[:member_keys] + @employer = Employer.includes(id_card_setup: [:provider_section, :rx_section]).find_by(pl_plan_key: pl_plan_key) + @card_setup = @employer.id_card_setup + + base_card = initialize_employer_base_card() + create_plan_base_cards(base_card) + + employer_card_futures = create_member_cards_async() + + print_card_futures << employer_card_futures + end + + + print_card_futures.flatten.map(&:value) + + end + + private + + def initialize_employer_base_card + + employer_attributes = { + pl_plan_key: @employer.pl_plan_key, + group_number: @employer.group_number, + rx_group: @card_setup.rx_group_number, + network_provider: @card_setup.network_provider, + network_logo_id: @card_setup.network_logo_id, + network_logo_filename: @card_setup.network_logo.filename, + employer_logo_filename: @card_setup.employer_logo.filename + } + unless @card_setup.has_divisions + employer_attributes.merge!({employer_name: @card_setup.print_name}) + end + + rx_attributes = @card_setup.rx_section.attributes.with_indifferent_access.slice( + :customer_service, + :web_url + ) + + provider_attributes = @card_setup.provider_section.attributes.with_indifferent_access.slice( + :provider_line_1, :provider_line_2, :provider_line_3, :provider_line_4, :provider_line_5, :provider_line_6, + :provider_line_7, :provider_line_8, :provider_line_9, :provider_line_10, :provider_line_11, :provider_line_12, + :claim_to_1, :claim_to_2, :claim_to_3, :claim_to_4, :claim_to_5, :claim_to_6, + :claim_to_7, :claim_to_8, :claim_to_9, :claim_to_10, :claim_to_11, :claim_to_12 + ) + + selected_attributes = employer_attributes.merge(rx_attributes).merge(provider_attributes) + # blank_card.assign_attributes(selected_attributes) + IdCard::PrintData.new(selected_attributes) + end + + def create_plan_base_cards(common_fields_card) + needed_plans_ids = @employer.members.where(pb_entity_key: @member_keys).distinct.pluck(:id_card_plan_id) + needed_plans = @card_setup.plans.where(id: needed_plans_ids) + needed_plans.each do |plan| + selected_attributes = { plan_id: plan.id } + plan.plan_benefits.each do |bene| + selected_attributes["benefit_desc_#{bene.sequence}".to_sym] = bene.benefit_desc + selected_attributes["benefit_#{bene.sequence}".to_sym] = bene.benefit + end + + plan_base_card = common_fields_card.dup + plan_base_card.assign_attributes(selected_attributes) + plan_base_card.save + end + end + + def create_member_cards_async + field_exceptions = @card_setup.field_exceptions.pluck(:id) + mmember_card_futures = @member_keys.map do |member_key| + Concurrent::Future.execute do + ActiveRecord::Base.connection_pool.with_connection do + ProcessIdCardDataJob.perform_now(member_key, field_exceptions, @card_setup.has_divisions) + end + end + end + + mmember_card_futures + + # batch_process = BatchProcess.create!(total_jobs: @member_keys.count) + # @member_keys.each do |member_key| + # ProcessIdCardDataJob.perform_later(member_key, @card_setup.has_divisions, batch_process.id) + # end + + # @batch_id = batch_process.id + # @members.each do |me| + # effect_date = determine_eff_date(me) + # if effect_date + # member_card = @base_card.dup + # member_attributes = { + # full_name: me.id_card_display_name, + # full_name_last_name_first: me.name, + # primary_mb_member_key: me.pb_entity_key, + # family_id: me.family_id, + # plan_id: me.id_card_plan_id, + # medical_eff_date: effect_date.strftime("%m/%d/%Y") + # } + + # if @card_setup.has_divisions + # member_attributes.merge!({employer_name: me.division}) + # end + + # dependent_attributes = get_dependent_fields(me) + # if dependent_attributes.present? + # selected_attributes = member_attributes.merge(dependent_attributes) + # else + # selected_attributes = member_attributes + # end + + # member_card.assign_attributes(selected_attributes) + # @employer_cards.push(member_card) + # end + # end + end + + # def set_network_fields + # selected_attributes = @employer.card_provider.attributes.with_indifferent_access.slice( + # :provider_line_1, :provider_line_2, :provider_line_3, :provider_line_4, :provider_line_5, :provider_line_6, + # :provider_line_7, :provider_line_8, :provider_line_9, :provider_line_10, :provider_line_11, :provider_line_12, + # :claim_to_1, :claim_to_2, :claim_to_3, :claim_to_4, :claim_to_5, :claim_to_6, + # :claim_to_7, :claim_to_8, :claim_to_9, :claim_to_10, :claim_to_11, :claim_to_12 + # ) + # @employer_cards.all do |card| + # card.assign_attributes(selected_attributes) + # end + # end + + # def set_rx_fields + # # fairos_information = Vhcs::HlrxCrosRef.where(pl_plan_key: 52).first + # selected_attributes = @employer.card_rx.attributes.with_indifferent_access.slice( + # :customer_service, + # :web_url + # ) + # @employer_cards.all do |card| + # card.assign_attributes(selected_attributes) + # end + # end + + def determine_eff_date(member) + + participation = Vhcs::PbProductParticipation.joins('INNER JOIN "PBCoveredEntities" ON "PBProductParticipation"."PBProductParticipationKey" = "PBCoveredEntities"."PBProductParticipationKey"').where('"PBCoveredEntities"."PBEntityKey" = ?', member.pb_entity_key).last + in_effect = participation.in_effect + out_of_effect = participation.out_of_effect + + if in_effect <= (Date.today + 90.days) && (out_of_effect - 1.day) > Date.today && out_of_effect > in_effect + in_effect + else + false + end + + end + + def get_dependent_fields(member) + dependent_attributes = {} + dependents = @group_dependents.where(family_id: member.family_id).where.not(pb_entity_key: member.pb_entity_key) + dependents.each do |dep| + dependent_name = dep.first_name + ' ' + dep.last_name + dependent_attributes["dependent_#{dep.sequence_number - 1}".to_sym] = dependent_name + end + dependent_attributes + end + end +end \ No newline at end of file diff --git a/app/services/id_card_printer_service/cards_generator.rb b/app/services/id_card_printer_service/cards_generator.rb new file mode 100644 index 0000000..a68ce0f --- /dev/null +++ b/app/services/id_card_printer_service/cards_generator.rb @@ -0,0 +1,68 @@ +module IdCardPrinterService + class CardsGenerator + + def initialize(employers_member_keys, layout, zip=false) + @employers_member_keys = Array.wrap(employers_member_keys) + @layout = layout + @zip = zip + end + + def call + pl_plan_keys = @employers_member_keys.map { |emk| emk[:pl_plan_key] } + IdCard::PrintData.where(pl_plan_key: pl_plan_keys).destroy_all + IdCardPrinterService::CardDataFormatter.new(@employers_member_keys).call + + IdCard::PrintData.where(pl_plan_key: pl_plan_keys, primary_mb_member_key: nil).destroy_all + + pdf_array = [] + batches = break_into_batches(pl_plan_keys) + batches.each do |card_template, batch_pl_plan_keys| + jasper_batch_id = "#{batch_pl_plan_keys.join('_')}-#{Time.current.utc.to_i}" + batch = IdCard::PrintData.where(pl_plan_key: batch_pl_plan_keys) + batch.update!(jasper_batch_id: jasper_batch_id) + batch_pdf = IdCardPrinterService::PdfBatchProcessor.new(card_template, jasper_batch_id, @layout).call + pdf_array << batch_pdf + end + # @employers_member_keys.each do |emk| + # employer_jasper_batches = IdCard::PrintData.where(pl_plan_key: emk[:pl_plan_key]).pluck(:network_logo_id).uniq + # employer_jasper_batches.each do |batch_network_logo| + # jasper_batch_id = "#{emk[:pl_plan_key]}-#{Time.current.utc.to_i}" + # batch = IdCard::PrintData.where(pl_plan_key: emk[:pl_plan_key], network_logo_id: batch_network_logo) + # # batch = employer_card_data.where(network_logo_id: batch_network_logo) + # batch.update!(jasper_batch_id: jasper_batch_id) + # batch_pdf = IdCardPrinterService::PdfBatchProcessor.new(emk[:pl_plan_key], batch_network_logo, jasper_batch_id, @layout).call + # pdf_array << batch_pdf + # end + # end + + + + group_pdfs = combine_pdfs(pdf_array) + group_pdfs + + end + + private + + def break_into_batches(pl_plan_keys) + batches_by_card_template = IdCard::Setup.where(pl_plan_key: pl_plan_keys).group_by(&:card_template) + .transform_values { |setups| setups.map(&:pl_plan_key) } + .compact_blank + end + + def combine_pdfs(pdf_array) + if @zip + group_cards_pdf = Zip::OutputStream.write_buffer do |zio| + pdf_array.each do |file| + zio.put_next_entry(file[:name]) + zio.write(file[:data]) + end + end + else + group_cards_pdf = CombinePDF.new + pdf_array.each { |pdf| group_cards_pdf << pdf } + end + group_cards_pdf + end + end +end diff --git a/app/services/id_card_printer_service/employer_cards_generator.rb b/app/services/id_card_printer_service/employer_cards_generator.rb index f5d8d36..1b817f3 100644 --- a/app/services/id_card_printer_service/employer_cards_generator.rb +++ b/app/services/id_card_printer_service/employer_cards_generator.rb @@ -3,13 +3,36 @@ module IdCardPrinterService def initialize(employer, layout, zip=false) @employer = employer + @member_keys = @employer.members.pluck(:pb_entity_key) @layout = layout @zip = zip end def call IdCard::PrintData.where(pl_plan_key: @employer.pl_plan_key).destroy_all - IdCardPrinterService::EmployerDataFormatter.new(@employer).call + IdCardPrinterService::EmployerDataFormatter.new(@employer.pl_plan_key, @member_keys).call + + + # card_futures.each(&:value) + + # max_retries = 60 + # retries = 0 + # finished = false + + # batch_process = BatchProcess.find(batch_id) + # until finished || retries > max_retries + # # Solid Queue stores finished jobs here if preserve_finished_jobs = true + # if batch_process.completed_jobs < batch_process.total_jobs + # sleep 0.5 + # retries += 1 + # else + # batch_process.destroy! + # finished = true + # end + # end + + IdCard::PrintData.where(pl_plan_key: @employer.pl_plan_key, primary_mb_member_key: nil).destroy_all + pdf_array = IdCardPrinterService::PdfProcessor.new(@employer, @layout, @zip).call diff --git a/app/services/id_card_printer_service/employer_data_formatter.rb b/app/services/id_card_printer_service/employer_data_formatter.rb index 035c09b..7135397 100644 --- a/app/services/id_card_printer_service/employer_data_formatter.rb +++ b/app/services/id_card_printer_service/employer_data_formatter.rb @@ -1,37 +1,35 @@ module IdCardPrinterService class EmployerDataFormatter - def initialize(employer, member_keys = nil) - @employer = employer + def initialize(pl_plan_key, member_keys) + # @employer = employer + @member_keys = member_keys + + @employer = Employer.includes(id_card_setup: [:provider_section, :rx_section]).find_by(pl_plan_key: pl_plan_key) @card_setup = @employer.id_card_setup - if member_keys - @members = @employer.members.where(pb_entity_key: member_keys).order(:name) - else - @members = @employer.members.order(:name) - end + @batch_id end def call - @base_card = IdCard::PrintData.new() - @employer_cards = [] + # blank_card = IdCard::PrintData.new() + # @employer_cards = [] - set_common_fields - set_member_fields + base_card = initialize_employer_base_card() + create_plan_base_cards(base_card) + card_futures = create_member_cards_async() - set_plan_fields - - @employer_cards.each(&:save!) + card_futures end private - def set_common_fields + def initialize_employer_base_card employer_attributes = { - pl_plan_key: @card_setup.pl_plan_key, + pl_plan_key: @employer.pl_plan_key, group_number: @employer.group_number, rx_group: @card_setup.rx_group_number, network_provider: @card_setup.network_provider @@ -53,52 +51,70 @@ module IdCardPrinterService ) selected_attributes = employer_attributes.merge(rx_attributes).merge(provider_attributes) - @base_card.assign_attributes(selected_attributes) + # blank_card.assign_attributes(selected_attributes) + IdCard::PrintData.new(selected_attributes) end - def set_plan_fields - @card_setup.plans.each do |plan| - selected_attributes = {} + def create_plan_base_cards(common_fields_card) + needed_plans_ids = @employer.members.where(pb_entity_key: @member_keys).distinct.pluck(:id_card_plan_id) + needed_plans = @card_setup.plans.where(id: needed_plans_ids) + needed_plans.each do |plan| + selected_attributes = { plan_id: plan.id } plan.plan_benefits.each do |bene| selected_attributes["benefit_desc_#{bene.sequence}".to_sym] = bene.benefit_desc selected_attributes["benefit_#{bene.sequence}".to_sym] = bene.benefit end - @employer_cards.find_all { |card| card.plan_id == plan.id.to_s }.each do |card| - card.assign_attributes(selected_attributes) - end + + plan_base_card = common_fields_card.dup + plan_base_card.assign_attributes(selected_attributes) + plan_base_card.save end end - def set_member_fields - @group_dependents = Vhcs::VwmbMember.where(pl_plan_key: @employer.pl_plan_key) - @members.each do |me| - effect_date = determine_eff_date(me) - if effect_date - member_card = @base_card.dup - member_attributes = { - full_name: me.id_card_display_name, - full_name_last_name_first: me.name, - primary_mb_member_key: me.pb_entity_key, - family_id: me.family_id, - plan_id: me.id_card_plan_id, - medical_eff_date: effect_date.strftime("%m/%d/%Y") - } + def create_member_cards_async - if @card_setup.has_divisions - member_attributes.merge!({employer_name: me.division}) + mmember_card_futures = @member_keys.map do |member_key| + Concurrent::Future.execute do + ActiveRecord::Base.connection_pool.with_connection do + ProcessIdCardDataJob.perform_now(member_key, @card_setup.has_divisions) end - - dependent_attributes = get_dependent_fields(me) - if dependent_attributes.present? - selected_attributes = member_attributes.merge(dependent_attributes) - else - selected_attributes = member_attributes - end - - member_card.assign_attributes(selected_attributes) - @employer_cards.push(member_card) end end + mmember_card_futures + # batch_process = BatchProcess.create!(total_jobs: @member_keys.count) + # @member_keys.each do |member_key| + # ProcessIdCardDataJob.perform_later(member_key, @card_setup.has_divisions, batch_process.id) + # end + + # @batch_id = batch_process.id + # @members.each do |me| + # effect_date = determine_eff_date(me) + # if effect_date + # member_card = @base_card.dup + # member_attributes = { + # full_name: me.id_card_display_name, + # full_name_last_name_first: me.name, + # primary_mb_member_key: me.pb_entity_key, + # family_id: me.family_id, + # plan_id: me.id_card_plan_id, + # medical_eff_date: effect_date.strftime("%m/%d/%Y") + # } + + # if @card_setup.has_divisions + # member_attributes.merge!({employer_name: me.division}) + # end + + # dependent_attributes = get_dependent_fields(me) + # if dependent_attributes.present? + # selected_attributes = member_attributes.merge(dependent_attributes) + # else + # selected_attributes = member_attributes + # end + + # member_card.assign_attributes(selected_attributes) + # @employer_cards.push(member_card) + # end + # end end # def set_network_fields diff --git a/app/services/id_card_printer_service/jasper_batch_url_generator.rb b/app/services/id_card_printer_service/jasper_batch_url_generator.rb new file mode 100644 index 0000000..7876167 --- /dev/null +++ b/app/services/id_card_printer_service/jasper_batch_url_generator.rb @@ -0,0 +1,40 @@ +module IdCardPrinterService + class JasperBatchUrlGenerator + + def initialize(card_template, jasper_batch_id, layout) + @card_template = card_template + @jasper_batch_id = jasper_batch_id + @layout = layout + end + + def call + URI::HTTP.build(url_components) + end + + private + + # def determine_network_logo + # # if @network_logos.length > 1 + # # member_geographic_info = Vhcs::PbEntityAddress.joins("INNER JOIN vwMBMember ON PBEntityAddress.PBEntityKey = vwMBMember.PBEntityKey AND PBEntityAddress.AddressTypeID = 1137").where("vwMBMember.FamilyID = ?", @family_id).first + # # @network_logos.where.not(default: true).each do |pnl| + # # if member_geographic_info[pnl.exception_type] == pnl.exception_value + # # return pnl.net_logo + # # end + # # end + # # end + # # @network_logos.find_by(default: true).net_logo + # IdCard::NetworkLogo.find(@network_logo_id).filename + # end +# http://localhost:8080/trunk/PdfServlet?reportConn=BrittonConnect&id=&reportName=FairosRxSampleIDCard-Half&family_id=Classic%202K&employer_logo=BryanPestControl.jpeg&network_logo=CignaLogo.png&reportDir=secure/Documents&SUBREPORT_DIR=/&ImageDir=secure/Documents&netToken=3a4a8b03f4dfb0e6e3fc82dd369f70ef&FileType=PDF +# http://localhost:8080/trunk/PdfServlet?reportConn=BrittonConnect&id=&reportName=FairosRxSampleIDCard-Half&family_id=Classic%202K&employer_logo=BryanPestControl.jpeg&network_logo=CignaLogo.png&reportDir=secure/Documents&SUBREPORT_DIR=/&ImageDir=secure/Documents&netToken=3a4a8b03f4dfb0e6e3fc82dd369f70ef&FileType=PDF + def url_components + { + host: '10.41.1.115', + port: 8080, + path: '/trunk/IdCardsServlet', + query: "reportConn=BrittonConnect&cardTemplate=#{@card_template}Batch&printType=Batch#{@layout}&jasper_batch_id=#{@jasper_batch_id}&FileType=PDF" + } + end + + end +end \ No newline at end of file diff --git a/app/services/id_card_printer_service/pdf_batch_processor.rb b/app/services/id_card_printer_service/pdf_batch_processor.rb new file mode 100644 index 0000000..6b77b2e --- /dev/null +++ b/app/services/id_card_printer_service/pdf_batch_processor.rb @@ -0,0 +1,36 @@ +module IdCardPrinterService + class PdfBatchProcessor + + def initialize(card_template, jasper_batch_id, layout) + @card_template = card_template + @jasper_batch_id = jasper_batch_id + @layout = layout + end + + def call + # if @zip + # group_cards_pdf_array = [] + # else + # group_cards_pdf = CombinePDF.new + # end + url = IdCardPrinterService::JasperBatchUrlGenerator.new(@card_template, @jasper_batch_id, @layout).call + puts url + batch_pdf = IdCardPrinterService::JasperPdfGenerator.new(url).call + + + # if @zip + # group_cards_pdf = Zip::OutputStream.write_buffer do |zio| + # group_cards_pdf_array.each do |file| + # zio.put_next_entry(file[:name]) + # zio.write(file[:data]) + # end + # end + # # else + # # todays_date = DateTime.current.strftime('%Y%m%d%H%M%S') + # # group_cards_pdf.save("tmp/#{@employer.name}_print_cards_#{todays_date}.pdf") + # end + + batch_pdf + end + end +end \ No newline at end of file diff --git a/app/services/id_card_printer_service/pdf_processor.rb b/app/services/id_card_printer_service/pdf_processor.rb index 1b0c7fa..524d4f2 100644 --- a/app/services/id_card_printer_service/pdf_processor.rb +++ b/app/services/id_card_printer_service/pdf_processor.rb @@ -3,7 +3,6 @@ module IdCardPrinterService def initialize(employer, layout, zip = false) @employer = employer - @card_setup = @employer.id_card_setup @layout = layout @zip = zip end diff --git a/app/services/id_card_printer_service/sample_cards_generator.rb b/app/services/id_card_printer_service/sample_cards_generator.rb index a03840f..df6fb50 100644 --- a/app/services/id_card_printer_service/sample_cards_generator.rb +++ b/app/services/id_card_printer_service/sample_cards_generator.rb @@ -6,7 +6,7 @@ module IdCardPrinterService end def call - IdCard::PrintData.where(employer_name: @employer.name).destroy_all + IdCard::PrintData.where(employer_name: @employer.id_card_setup.print_name).destroy_all IdCardPrinterService::SampleDataFormatter.new(@employer).call IdCardPrinterService::SamplePdfProcessor.new(@employer).call diff --git a/app/services/id_card_printer_service/sample_data_formatter.rb b/app/services/id_card_printer_service/sample_data_formatter.rb index 8f88b88..fedf012 100644 --- a/app/services/id_card_printer_service/sample_data_formatter.rb +++ b/app/services/id_card_printer_service/sample_data_formatter.rb @@ -23,7 +23,7 @@ module IdCardPrinterService def set_employer_fields selected_attributes = { - employer_name: @employer.name, + employer_name: @card_setup.print_name, group_number: @employer.group_number.present? ? @employer.group_number : "999999", medical_eff_date: @employer.effective_date } diff --git a/app/services/id_card_printer_service/sample_pdf_processor.rb b/app/services/id_card_printer_service/sample_pdf_processor.rb index f7f0325..dc544e4 100644 --- a/app/services/id_card_printer_service/sample_pdf_processor.rb +++ b/app/services/id_card_printer_service/sample_pdf_processor.rb @@ -8,7 +8,7 @@ module IdCardPrinterService def call group_cards_pdf = CombinePDF.new - IdCard::PrintData.where(employer_name: @employer.name).each do |card| + IdCard::PrintData.where(employer_name: @card_setup.print_name).each do |card| url = IdCardPrinterService::SampleJasperUrlGenerator.new(@employer, card.sample_key, card.sample_plan_title).call puts url card_pdf = IdCardPrinterService::JasperPdfGenerator.new(url).call diff --git a/app/services/id_card_queue_service/get_queued_cards.rb b/app/services/id_card_queue_service/get_queued_cards.rb index 3667d18..bf3cc4e 100644 --- a/app/services/id_card_queue_service/get_queued_cards.rb +++ b/app/services/id_card_queue_service/get_queued_cards.rb @@ -10,7 +10,21 @@ module IdCardQueueService end def call - CallStoredProc.new('BrittonGetQueuedIdCardMemberKeysTPA', { PLPlanKeys: @employer_pl_plan_keys }).call.to_ary + raw_employers_member_keys = CallStoredProc.new('BrittonGetQueuedIdCardMemberKeysTPA', { PLPlanKeys: @employer_pl_plan_keys }).call.to_ary + + format_employers_member_keys(raw_employers_member_keys) + end + + private + + def format_employers_member_keys(raw_employers_member_keys) + key_map = { "PlanKey" => :pl_plan_key, "MemberKeys" => :member_keys } + + raw_employers_member_keys.map do |hash| + hash["PlanKey"] = hash["PlanKey"].to_s + hash["MemberKeys"] = hash["MemberKeys"].split(", ").map(&:to_i) + hash.transform_keys(key_map) + end end end diff --git a/bin/jobs b/bin/jobs new file mode 100644 index 0000000..dcf59f3 --- /dev/null +++ b/bin/jobs @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require_relative "../config/environment" +require "solid_queue/cli" + +SolidQueue::Cli.start(ARGV) diff --git a/config/application.rb b/config/application.rb index d625241..843048d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,5 +40,6 @@ module Baclight # Don't generate system test files. config.generators.system_tests = nil + config.active_job.queue_adapter = :solid_queue end end diff --git a/config/environments/development.rb b/config/environments/development.rb index b8d969a..7de874a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -81,4 +81,8 @@ Rails.application.configure do # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. # config.generators.apply_rubocop_autocorrect_after_generate! + + config.active_job.queue_adapter = :solid_queue + config.solid_queue.connects_to = { database: { writing: :baclight } } + # config.active_job.queue_adapter = :async end diff --git a/config/environments/production.rb b/config/environments/production.rb index 680dfd5..02d3080 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -71,7 +71,9 @@ Rails.application.configure do # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment). - # config.active_job.queue_adapter = :resque + config.active_job.queue_adapter = :solid_queue + # config.solid_queue.connects_to = { database: { writing: :queue } } + # config.solid_queue.connects_to = { database: { writing: :queue } } # config.active_job.queue_name_prefix = "railsondocker_production" # Disable caching for Action Mailer templates even if Action Controller diff --git a/config/puma.rb b/config/puma.rb index 17feadd..86e5b46 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -29,6 +29,8 @@ port ENV.fetch("PORT", 3002) # Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart +plugin :solid_queue if Rails.env.development? + # plugin :tailwindcss # Only use a pidfile when requested diff --git a/config/queue.yml b/config/queue.yml new file mode 100644 index 0000000..9eace59 --- /dev/null +++ b/config/queue.yml @@ -0,0 +1,18 @@ +default: &default + dispatchers: + - polling_interval: 1 + batch_size: 500 + workers: + - queues: "*" + threads: 3 + processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> + polling_interval: 0.1 + +development: + <<: *default + +test: + <<: *default + +production: + <<: *default diff --git a/config/recurring.yml b/config/recurring.yml new file mode 100644 index 0000000..b4207f9 --- /dev/null +++ b/config/recurring.yml @@ -0,0 +1,15 @@ +# examples: +# periodic_cleanup: +# class: CleanSoftDeletedRecordsJob +# queue: background +# args: [ 1000, { batch_size: 500 } ] +# schedule: every hour +# periodic_cleanup_with_command: +# command: "SoftDeletedRecord.due.delete_all" +# priority: 2 +# schedule: at 5am every day + +production: + clear_solid_queue_finished_jobs: + command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)" + schedule: every hour at minute 12 diff --git a/db/migrate/20251206190810_create_id_card_print_data.rb b/db/migrate/20251206190810_create_id_card_print_data.rb index 3a810ae..d00f3ca 100644 --- a/db/migrate/20251206190810_create_id_card_print_data.rb +++ b/db/migrate/20251206190810_create_id_card_print_data.rb @@ -74,9 +74,13 @@ class CreateIdCardPrintData < ActiveRecord::Migration[7.0] t.string :benefit_13 t.string :benefit_desc_14 t.string :benefit_14 + t.integer :network_logo_id + t.string :network_logo_filename + t.string :employer_logo_filename t.boolean :sample, default: false t.string :sample_key t.string :sample_plan_title + t.string :jasper_batch_id t.timestamps end diff --git a/db/migrate/20260320152726_solid_queue_setup.rb b/db/migrate/20260320152726_solid_queue_setup.rb new file mode 100644 index 0000000..2e4b208 --- /dev/null +++ b/db/migrate/20260320152726_solid_queue_setup.rb @@ -0,0 +1,131 @@ +class SolidQueueSetup < ActiveRecord::Migration[7.2] + def change + create_table "solid_queue_blocked_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.string "concurrency_key", null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release" + t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance" + t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true + end + + create_table "solid_queue_claimed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.bigint "process_id" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true + t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id" + end + + create_table "solid_queue_failed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.text "error" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true + end + + create_table "solid_queue_jobs", force: :cascade do |t| + t.string "queue_name", null: false + t.string "class_name", null: false + t.text "arguments" + t.integer "priority", default: 0, null: false + t.string "active_job_id" + t.datetime "scheduled_at" + t.datetime "finished_at" + t.string "concurrency_key" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id" + t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name" + t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at" + t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering" + t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting" + end + + create_table "solid_queue_pauses", force: :cascade do |t| + t.string "queue_name", null: false + t.datetime "created_at", null: false + t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true + end + + create_table "solid_queue_processes", force: :cascade do |t| + t.string "kind", null: false + t.datetime "last_heartbeat_at", null: false + t.bigint "supervisor_id" + t.integer "pid", null: false + t.string "hostname" + t.text "metadata" + t.datetime "created_at", null: false + t.string "name", null: false + t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at" + t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true + t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id" + end + + create_table "solid_queue_ready_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true + t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all" + t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue" + end + + create_table "solid_queue_recurring_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "task_key", null: false + t.datetime "run_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true + t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true + end + + create_table "solid_queue_recurring_tasks", force: :cascade do |t| + t.string "key", null: false + t.string "schedule", null: false + t.string "command", limit: 2048 + t.string "class_name" + t.text "arguments" + t.string "queue_name" + t.integer "priority", default: 0 + t.boolean "static", default: true, null: false + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true + t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static" + end + + create_table "solid_queue_scheduled_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "scheduled_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true + t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all" + end + + create_table "solid_queue_semaphores", force: :cascade do |t| + t.string "key", null: false + t.integer "value", default: 1, null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at" + t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value" + t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true + end + + add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 307dfd7..0b1b494 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_01_16_182836) do +ActiveRecord::Schema[7.2].define(version: 2026_03_20_152726) do create_table "employers", force: :cascade do |t| t.string "name" t.string "slug" @@ -162,9 +162,13 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_16_182836) do t.string "benefit_13" t.string "benefit_desc_14" t.string "benefit_14" + t.integer "network_logo_id" + t.string "network_logo_filename" + t.string "employer_logo_filename" t.boolean "sample", default: false t.string "sample_key" t.string "sample_plan_title" + t.string "jasper_batch_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -265,6 +269,127 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_16_182836) do t.index ["id_card_plan_id"], name: "index_members_on_id_card_plan_id" end + create_table "solid_queue_blocked_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.string "concurrency_key", null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.index ["concurrency_key", "priority", "job_id"], name: "index_solid_queue_blocked_executions_for_release" + t.index ["expires_at", "concurrency_key"], name: "index_solid_queue_blocked_executions_for_maintenance" + t.index ["job_id"], name: "index_solid_queue_blocked_executions_on_job_id", unique: true + end + + create_table "solid_queue_claimed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.bigint "process_id" + t.datetime "created_at", null: false + t.index ["job_id"], name: "index_solid_queue_claimed_executions_on_job_id", unique: true + t.index ["process_id", "job_id"], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id" + end + + create_table "solid_queue_failed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.text "error" + t.datetime "created_at", null: false + t.index ["job_id"], name: "index_solid_queue_failed_executions_on_job_id", unique: true + end + + create_table "solid_queue_jobs", force: :cascade do |t| + t.string "queue_name", null: false + t.string "class_name", null: false + t.text "arguments" + t.integer "priority", default: 0, null: false + t.string "active_job_id" + t.datetime "scheduled_at" + t.datetime "finished_at" + t.string "concurrency_key" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["active_job_id"], name: "index_solid_queue_jobs_on_active_job_id" + t.index ["class_name"], name: "index_solid_queue_jobs_on_class_name" + t.index ["finished_at"], name: "index_solid_queue_jobs_on_finished_at" + t.index ["queue_name", "finished_at"], name: "index_solid_queue_jobs_for_filtering" + t.index ["scheduled_at", "finished_at"], name: "index_solid_queue_jobs_for_alerting" + end + + create_table "solid_queue_pauses", force: :cascade do |t| + t.string "queue_name", null: false + t.datetime "created_at", null: false + t.index ["queue_name"], name: "index_solid_queue_pauses_on_queue_name", unique: true + end + + create_table "solid_queue_processes", force: :cascade do |t| + t.string "kind", null: false + t.datetime "last_heartbeat_at", null: false + t.bigint "supervisor_id" + t.integer "pid", null: false + t.string "hostname" + t.text "metadata" + t.datetime "created_at", null: false + t.string "name", null: false + t.index ["last_heartbeat_at"], name: "index_solid_queue_processes_on_last_heartbeat_at" + t.index ["name", "supervisor_id"], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true + t.index ["supervisor_id"], name: "index_solid_queue_processes_on_supervisor_id" + end + + create_table "solid_queue_ready_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "created_at", null: false + t.index ["job_id"], name: "index_solid_queue_ready_executions_on_job_id", unique: true + t.index ["priority", "job_id"], name: "index_solid_queue_poll_all" + t.index ["queue_name", "priority", "job_id"], name: "index_solid_queue_poll_by_queue" + end + + create_table "solid_queue_recurring_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "task_key", null: false + t.datetime "run_at", null: false + t.datetime "created_at", null: false + t.index ["job_id"], name: "index_solid_queue_recurring_executions_on_job_id", unique: true + t.index ["task_key", "run_at"], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true + end + + create_table "solid_queue_recurring_tasks", force: :cascade do |t| + t.string "key", null: false + t.string "schedule", null: false + t.string "command", limit: 2048 + t.string "class_name" + t.text "arguments" + t.string "queue_name" + t.integer "priority", default: 0 + t.boolean "static", default: true, null: false + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["key"], name: "index_solid_queue_recurring_tasks_on_key", unique: true + t.index ["static"], name: "index_solid_queue_recurring_tasks_on_static" + end + + create_table "solid_queue_scheduled_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "scheduled_at", null: false + t.datetime "created_at", null: false + t.index ["job_id"], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true + t.index ["scheduled_at", "priority", "job_id"], name: "index_solid_queue_dispatch_all" + end + + create_table "solid_queue_semaphores", force: :cascade do |t| + t.string "key", null: false + t.integer "value", default: 1, null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["expires_at"], name: "index_solid_queue_semaphores_on_expires_at" + t.index ["key", "value"], name: "index_solid_queue_semaphores_on_key_and_value" + t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true + end + add_foreign_key "id_card_field_exception_items", "id_card_field_exceptions", column: "field_exception_id" add_foreign_key "id_card_field_exception_items", "id_card_network_logos", column: "network_logo_id" add_foreign_key "id_card_field_exception_items", "id_card_provider_sections", column: "provider_section_id" @@ -278,4 +403,10 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_16_182836) do add_foreign_key "id_card_setups", "id_card_rx_sections", column: "rx_section_id" add_foreign_key "members", "employers" add_foreign_key "members", "id_card_plans" + add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade end diff --git a/development3.Dockerfile b/development3.Dockerfile index 0d7468b..f5f03b4 100644 --- a/development3.Dockerfile +++ b/development3.Dockerfile @@ -1,7 +1,7 @@ # ----- Build Stage ----- # Using a specific Ruby version (e.g., 3.3) for stability ARG RUBY_VERSION=3.4.8 -FROM docker.io/library/ruby:$RUBY_VERSION-slim as base +FROM docker.io/library/ruby:3.4.8-slim as base # Install production system dependencies RUN --mount=type=cache,target=/var/cache/apt \ @@ -27,6 +27,8 @@ WORKDIR /usr/src/app RUN gem install foreman +# RUN chmod +x /usr/src/app/bin/jobs + # Copy runtime Ruby dependencies and the precompiled assets from the builder stage # COPY --from=builder /usr/local/bundle /usr/local/bundle # COPY --from=builder /usr/src/app /usr/src/app @@ -37,6 +39,17 @@ RUN gem install foreman # && touch /usr/local/var/run/watchman/.not-empty \ # && chmod 2777 /usr/local/var/run/watchman +# COPY . . + +# 1. Add bin/jobs script to container +COPY bin/jobs /usr/src/bin/jobs + +# 2. Make the script executable +RUN chmod +x /usr/src/bin/jobs + +# 3. Set it as the entrypoint +# ENTRYPOINT ["/usr/src/bin/jobs"] + # The entrypoint script is a good practice for handling Rails server startup ENTRYPOINT ["./bin/docker-entrypoint-development"] @@ -44,4 +57,4 @@ ENTRYPOINT ["./bin/docker-entrypoint-development"] EXPOSE 3002 # Set the default command to run the Rails server -CMD ["./bin/dev"] +CMD ["./bin/dev", "bin/jobs start"]