From 3d3f0a5dca0421434d0a7fbf3bcce7c71f78451a Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 3 Dec 2021 14:19:37 +0700 Subject: [PATCH 01/94] 2021120309 added referral details --- app/classes/user_notification.rb | 7 ++++++- app/controllers/referrals_controller.rb | 7 ++++++- app/views/notifications/referrals.haml | 7 +++++-- app/views/notifications/repeat_referrals.haml | 3 +++ app/views/referrals/show.haml | 8 +++++--- config/locales/en.yml | 1 + config/locales/km.yml | 1 + config/locales/my.yml | 1 + config/routes.rb | 2 ++ 9 files changed, 30 insertions(+), 7 deletions(-) diff --git a/app/classes/user_notification.rb b/app/classes/user_notification.rb index 819e6244cf..50a5d4e7b1 100644 --- a/app/classes/user_notification.rb +++ b/app/classes/user_notification.rb @@ -347,7 +347,12 @@ def get_referrals(referral_type) referrals.each do |referral| referral_slug = referral.slug client = Client.find_by(slug: referral_slug) || Client.find_by(archived_slug: referral_slug) - client.present? ? existing_client_referrals << referral : new_client_referrals << referral + if client.present? + existing_client_referrals << referral + referral.update_column(:client_id, client.id) unless referral.client_id + else + new_client_referrals << referral + end end referral_type == 'new_referral' ? new_client_referrals : existing_client_referrals diff --git a/app/controllers/referrals_controller.rb b/app/controllers/referrals_controller.rb index 1b6aaad362..2a3960d332 100644 --- a/app/controllers/referrals_controller.rb +++ b/app/controllers/referrals_controller.rb @@ -68,10 +68,15 @@ def update private def find_referral - @referral = @client.referrals.find(params[:id]) + if params[:client_id] + @referral = @client.referrals.find(params[:id]) + else + Referral.find(params[:id]) + end end def find_client + return unless params[:client_id] @client = Client.accessible_by(current_ability).friendly.find(params[:client_id]) end diff --git a/app/views/notifications/referrals.haml b/app/views/notifications/referrals.haml index ab1097c87c..41d6a43a8e 100644 --- a/app/views/notifications/referrals.haml +++ b/app/views/notifications/referrals.haml @@ -10,5 +10,8 @@ - @unsaved_referrals.each do |referral| %li.list-group-item = link_to new_client_path(referral_id: referral.id), target: '_blank' do - = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from]})" - %span.label.label-primary= referral.level_of_risk&.capitalize \ No newline at end of file + = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from] || "I don't see the NGO I'm looking for..."})" + %span.label.label-primary= referral.level_of_risk&.capitalize + + = link_to referral_path(referral), target: '_blank', class: 'btn btn-primary btn-xs' do + = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/notifications/repeat_referrals.haml b/app/views/notifications/repeat_referrals.haml index 4b98f6fd65..93f237f016 100644 --- a/app/views/notifications/repeat_referrals.haml +++ b/app/views/notifications/repeat_referrals.haml @@ -13,3 +13,6 @@ = link_to client_path(id: client&.slug, referral_id: referral.id), target: '_blank' do = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from]})" %span.label.label-primary= referral.level_of_risk&.capitalize + + = link_to client_referral_path(client, referral), target: '_blank', class: 'btn btn-primary btn-xs' do + = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/referrals/show.haml b/app/views/referrals/show.haml index a1d998ea17..1e17212f9f 100644 --- a/app/views/referrals/show.haml +++ b/app/views/referrals/show.haml @@ -12,6 +12,7 @@ = fa_icon('pencil') .ibox-content + %h2= t('referral_details') %section.overflow-case %table.table.table-bordered %tr @@ -27,7 +28,7 @@ %tr %td= t('.client_id') %td - %strong= @referral.client.slug + %strong= @referral.client.slug if @referral.client_id? %tr %td= t('.referred_from') @@ -73,5 +74,6 @@ = link_to t('.preview_download'), @referral.consent_form.first.url, class: 'btn btn-info btn-sm btn-download', target: :_blank .ibox-footer .text-right - = link_to client_referrals_path(@client, referral_type: referral_type(@referral)), class: 'btn btn-default' do - = t('back') + - if @referral.client_id + = link_to client_referrals_path(@client, referral_type: referral_type(@referral)), class: 'btn btn-default' do + = t('back') diff --git a/config/locales/en.yml b/config/locales/en.yml index bcb030a1d1..9229fc15d3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6017,6 +6017,7 @@ en: <<: *SERVICE_DELIVERY_ATTR update: successfully_updated: Service has been successfully updated. + referral_details: Referral Details results: Results shared: x_lists_of: "%{x} lists of" diff --git a/config/locales/km.yml b/config/locales/km.yml index c8d0fe7270..b8a7f77ce3 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -5974,6 +5974,7 @@ km: <<: *SERVICE_DELIVERY_ATTR update: successfully_updated: Service has been successfully updated. + referral_details: ព័ត៌មានលំអិតនៃការបញ្ជូន results: លទ្ធផល shared: x_lists_of: "បញ្ជី%{x}" diff --git a/config/locales/my.yml b/config/locales/my.yml index a3a9b0902f..5d03a7e212 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -5958,6 +5958,7 @@ my: <<: *SERVICE_DELIVERY_ATTR update: successfully_updated: Service has been successfully updated. + referral_details: Referral Details results: Results shared: x_lists_of: "%{x}စာရင်း" diff --git a/config/routes.rb b/config/routes.rb index 4a716f9645..1b96b20bc3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -101,6 +101,8 @@ get '/data_trackers' => 'data_trackers#index' get 'clients/:client_id/book' => 'client_books#index', as: 'client_books' + get 'referrals/:id' => 'referrals#show', as: 'referral' + resources :clients do resources :referrals resources :internal_referrals From 934e43b2bbf3c51fea477899cc8e031a95ba90c0 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 17 Dec 2021 11:05:34 +0700 Subject: [PATCH 02/94] added field client_status to referrals --- app/controllers/api/v1/organizations_controller.rb | 2 +- app/models/concerns/client_constants.rb | 2 +- app/views/referrals/show.haml | 4 ++++ db/migrate/20211217032514_add_field_to_referrals.rb | 5 +++++ db/schema.rb | 12 +++++++----- 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20211217032514_add_field_to_referrals.rb diff --git a/app/controllers/api/v1/organizations_controller.rb b/app/controllers/api/v1/organizations_controller.rb index 833b85a32c..54d4075185 100644 --- a/app/controllers/api/v1/organizations_controller.rb +++ b/app/controllers/api/v1/organizations_controller.rb @@ -161,7 +161,7 @@ def clients_params :address_current_village_code, :reason_for_referral, :reason_for_exiting, :organization_id, :organization_name, :external_case_worker_name, :external_case_worker_id, :external_case_worker_mobile, :external_case_worker_email, - :level_of_risk, :is_referred, + :level_of_risk, :is_referred, :client_status, services: [:uuid, :name] ) end diff --git a/app/models/concerns/client_constants.rb b/app/models/concerns/client_constants.rb index b4ef24390c..4fedb86ffd 100644 --- a/app/models/concerns/client_constants.rb +++ b/app/models/concerns/client_constants.rb @@ -16,7 +16,7 @@ module ClientConstants CLIENT_LEVELS = ['No', 'Level 1', 'Level 2'] LEGAL_DOC_FIELDS = %w( - national_id_files + national_id_filee birth_cert_files family_book_files passport_files diff --git a/app/views/referrals/show.haml b/app/views/referrals/show.haml index a1d998ea17..aeefef20d3 100644 --- a/app/views/referrals/show.haml +++ b/app/views/referrals/show.haml @@ -28,6 +28,10 @@ %td= t('.client_id') %td %strong= @referral.client.slug + %tr + %td= t('.client_status') + %td + %strong= @referral.client_status %tr %td= t('.referred_from') diff --git a/db/migrate/20211217032514_add_field_to_referrals.rb b/db/migrate/20211217032514_add_field_to_referrals.rb new file mode 100644 index 0000000000..6d05210202 --- /dev/null +++ b/db/migrate/20211217032514_add_field_to_referrals.rb @@ -0,0 +1,5 @@ +class AddFieldToReferrals < ActiveRecord::Migration + def change + add_column :referrals, :client_status, :string, default: 'Referred' + end +end diff --git a/db/schema.rb b/db/schema.rb index f75a23e3a7..7807f7e020 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20211020030551) do +ActiveRecord::Schema.define(version: 20211217032514) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" enable_extension "hstore" - enable_extension "uuid-ossp" enable_extension "pgcrypto" create_table "able_screening_questions", force: :cascade do |t| @@ -1339,6 +1338,8 @@ t.boolean "label_only", default: false end + add_index "field_settings", ["name"], name: "index_field_settings_on_name", using: :btree + create_table "form_builder_attachments", force: :cascade do |t| t.string "name", default: "" t.jsonb "file", default: [] @@ -2058,11 +2059,11 @@ t.string "referral_phone", default: "" t.integer "referee_id" t.string "client_name", default: "" - t.string "consent_form", default: [], array: true + t.string "consent_form", default: [], array: true t.boolean "saved", default: false t.integer "client_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "ngo_name", default: "" t.string "client_global_id" t.string "external_id" @@ -2075,6 +2076,7 @@ t.string "village_code", default: "" t.string "referee_email" t.string "level_of_risk" + t.string "client_status", default: "Referred" end add_index "referrals", ["client_global_id"], name: "index_referrals_on_client_global_id", using: :btree From adc2ea0512f734a94401f9c831d5e4f399a66b3e Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 17 Dec 2021 15:32:33 +0700 Subject: [PATCH 03/94] added client status to referral table --- app/controllers/api/v1/organizations_controller.rb | 1 + app/models/referral.rb | 4 ++++ app/views/notifications/referrals.haml | 2 +- app/views/notifications/repeat_referrals.haml | 2 +- app/views/referrals/index.haml | 2 ++ app/views/referrals/show.haml | 5 ++--- config/locales/en.yml | 1 + config/locales/km.yml | 1 + config/locales/my.yml | 1 + 9 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/v1/organizations_controller.rb b/app/controllers/api/v1/organizations_controller.rb index 54d4075185..c1b4705867 100644 --- a/app/controllers/api/v1/organizations_controller.rb +++ b/app/controllers/api/v1/organizations_controller.rb @@ -75,6 +75,7 @@ def render_client_data client = Client.find_by(global_id: clients_params['global_id']) attributes = Client.get_client_attribute(clients_params, client.referral_source_category_id) if client if client && client.update_attributes(attributes) + client.referrals.last&.update_client_status(clients_params[:client_status]) if clients_params[:client_status] render json: { external_id: client.external_id, message: 'Record saved.' } else render json: { external_id: clients_params[:external_id], message: client.errors }, status: :unprocessable_entity diff --git a/app/models/referral.rb b/app/models/referral.rb index 1a5a776c99..c816355faf 100644 --- a/app/models/referral.rb +++ b/app/models/referral.rb @@ -78,6 +78,10 @@ def self.get_referral_attribute(attribute) } end + def update_client_status(value) + update_column(:client_status, value) + end + private def check_saved_referral_in_target_ngo diff --git a/app/views/notifications/referrals.haml b/app/views/notifications/referrals.haml index 41d6a43a8e..180d737520 100644 --- a/app/views/notifications/referrals.haml +++ b/app/views/notifications/referrals.haml @@ -12,6 +12,6 @@ = link_to new_client_path(referral_id: referral.id), target: '_blank' do = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from] || "I don't see the NGO I'm looking for..."})" %span.label.label-primary= referral.level_of_risk&.capitalize - + = status_style referral.client_status = link_to referral_path(referral), target: '_blank', class: 'btn btn-primary btn-xs' do = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/notifications/repeat_referrals.haml b/app/views/notifications/repeat_referrals.haml index 93f237f016..fdb2ba8bae 100644 --- a/app/views/notifications/repeat_referrals.haml +++ b/app/views/notifications/repeat_referrals.haml @@ -13,6 +13,6 @@ = link_to client_path(id: client&.slug, referral_id: referral.id), target: '_blank' do = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from]})" %span.label.label-primary= referral.level_of_risk&.capitalize - + = status_style referral.client_status = link_to client_referral_path(client, referral), target: '_blank', class: 'btn btn-primary btn-xs' do = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/referrals/index.haml b/app/views/referrals/index.haml index c09033ba84..3ad2053112 100644 --- a/app/views/referrals/index.haml +++ b/app/views/referrals/index.haml @@ -14,6 +14,7 @@ %th{ class: referred_from_hidden?(@referrals.first) }= t('.referred_from') %th{ class: referred_to_hidden?(@referrals.first) }= t('.referred_to') %th= t('.date_of_referral') + %th= t('referrals.client_status') %th.text-center.custom_column_manage= t('.detail') %tbody - @referrals.each do |referral| @@ -25,6 +26,7 @@ - else %td{ class: referred_to_hidden?(referral) }= referral.referred_to_ngo %td= date_format(referral.date_of_referral) + %td= status_style referral.client_status %td.text-center -# - if policy(referral).edit? -# = link_to edit_client_referral_path(@client, referral, referral_type: params[:referral_type]), class: 'button btn btn-outline btn-success btn-xs' do diff --git a/app/views/referrals/show.haml b/app/views/referrals/show.haml index 0a4058b784..7a1f25cd74 100644 --- a/app/views/referrals/show.haml +++ b/app/views/referrals/show.haml @@ -28,12 +28,11 @@ %tr %td= t('.client_id') %td - %strong= @referral.client.slug %strong= @referral.client.slug if @referral.client_id? %tr - %td= t('.client_status') + %td= t('referrals.client_status') %td - %strong= @referral.client_status + %strong= status_style @referral.client_status %tr %td= t('.referred_from') diff --git a/config/locales/en.yml b/config/locales/en.yml index deb5ed6fae..375e09fa0a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5808,6 +5808,7 @@ en: modification_of: Changelog of referrals: level_of_risk: Level of Risk + client_status: Client Status confirm_referral: body_second: The client you want to refer already existed in the target NGO. body_third: You have already referred this client, please wait for the response. diff --git a/config/locales/km.yml b/config/locales/km.yml index 085415cfaa..00fc7a3a16 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -5760,6 +5760,7 @@ km: modification_of: ប្រវត្តិកំណែរបស់ referrals: level_of_risk: កម្រិតហានិភ័យ + client_status: ស្ថានភាពកុមារ confirm_referral: body_second: អតិថិជនដែលអ្នកចង់បញ្ជូនមាននៅក្នុងអង្គការគោលដៅរួចទៅហើយ body_third: អ្នកបានបញ្ជូនអតិថិជននេះរួចហើយសូមរង់ចាំការឆ្លើយតប diff --git a/config/locales/my.yml b/config/locales/my.yml index 510d99621e..9c6d1d0fd0 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -5745,6 +5745,7 @@ my: modification_of: "၏ ေျပာင္းလဲမႈ" referrals: level_of_risk: အန္တရာယ်အဆင့် + client_status: ဖောက်သည်အခြေအနေ confirm_referral: body_second: သငျသညျ alreay ရည်ညွှန်းချင်သော client ကိုပစ်မှတ်အန်ဂျီအိုနေပါသည်။ body_third: သငျသညျပြီးသားဒီ client ကို, ထိုတုံ့ပြန်မှုကိုစောင့်ဆိုင်းကျေးဇူးပြုပြီးရည်ညွှန်းကြသည်။ From e51dc6d5f8dbabfacea12b094c1c089cc768dff8 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 20 Dec 2021 17:24:00 +0700 Subject: [PATCH 04/94] change referral field from client status to referral status --- app/controllers/api/v1/organizations_controller.rb | 14 +++++++++++--- app/models/referral.rb | 4 ++-- app/views/notifications/referrals.haml | 2 +- app/views/notifications/repeat_referrals.haml | 2 +- app/views/referrals/index.haml | 4 ++-- app/views/referrals/show.haml | 4 ++-- config/locales/en.yml | 2 +- config/locales/km.yml | 2 +- config/locales/my.yml | 2 +- .../20211217032514_add_field_to_referrals.rb | 2 +- db/schema.rb | 2 +- 11 files changed, 24 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/v1/organizations_controller.rb b/app/controllers/api/v1/organizations_controller.rb index c1b4705867..3dba00bbfe 100644 --- a/app/controllers/api/v1/organizations_controller.rb +++ b/app/controllers/api/v1/organizations_controller.rb @@ -75,8 +75,7 @@ def render_client_data client = Client.find_by(global_id: clients_params['global_id']) attributes = Client.get_client_attribute(clients_params, client.referral_source_category_id) if client if client && client.update_attributes(attributes) - client.referrals.last&.update_client_status(clients_params[:client_status]) if clients_params[:client_status] - render json: { external_id: client.external_id, message: 'Record saved.' } + check_referral_status(client, clients_params['referral_status']) else render json: { external_id: clients_params[:external_id], message: client.errors }, status: :unprocessable_entity end @@ -162,7 +161,7 @@ def clients_params :address_current_village_code, :reason_for_referral, :reason_for_exiting, :organization_id, :organization_name, :external_case_worker_name, :external_case_worker_id, :external_case_worker_mobile, :external_case_worker_email, - :level_of_risk, :is_referred, :client_status, + :level_of_risk, :is_referred, :referral_status, services: [:uuid, :name] ) end @@ -285,6 +284,15 @@ def create_second_referral referral = Referral.new(referral_attributes.merge(referred_from: external_system_name)) end + def check_referral_status(client, status) + if ['Accepted', 'Exited', 'Referred'].include?(status) + client.referrals.last&.update_referral_status(status) + render json: { external_id: client.external_id, message: 'Record saved.' } + else + render json: { external_id: client.external_id, message: "Referral status must be one of ['Accepted', 'Exited', 'Referred'].", status: :unprocessable_entity } + end + end + end end end diff --git a/app/models/referral.rb b/app/models/referral.rb index c816355faf..e6c2008976 100644 --- a/app/models/referral.rb +++ b/app/models/referral.rb @@ -78,8 +78,8 @@ def self.get_referral_attribute(attribute) } end - def update_client_status(value) - update_column(:client_status, value) + def update_referral_status(value) + update_column(:referral_status, value) end private diff --git a/app/views/notifications/referrals.haml b/app/views/notifications/referrals.haml index 180d737520..8cac13e2a8 100644 --- a/app/views/notifications/referrals.haml +++ b/app/views/notifications/referrals.haml @@ -12,6 +12,6 @@ = link_to new_client_path(referral_id: referral.id), target: '_blank' do = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from] || "I don't see the NGO I'm looking for..."})" %span.label.label-primary= referral.level_of_risk&.capitalize - = status_style referral.client_status + = status_style referral.referral_status = link_to referral_path(referral), target: '_blank', class: 'btn btn-primary btn-xs' do = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/notifications/repeat_referrals.haml b/app/views/notifications/repeat_referrals.haml index fdb2ba8bae..91846958aa 100644 --- a/app/views/notifications/repeat_referrals.haml +++ b/app/views/notifications/repeat_referrals.haml @@ -13,6 +13,6 @@ = link_to client_path(id: client&.slug, referral_id: referral.id), target: '_blank' do = "#{referral.client_name} (#{ngo_hash_mapping[referral.referred_from]})" %span.label.label-primary= referral.level_of_risk&.capitalize - = status_style referral.client_status + = status_style referral.referral_status = link_to client_referral_path(client, referral), target: '_blank', class: 'btn btn-primary btn-xs' do = fa_icon 'external-link' \ No newline at end of file diff --git a/app/views/referrals/index.haml b/app/views/referrals/index.haml index 3ad2053112..2fd441cb89 100644 --- a/app/views/referrals/index.haml +++ b/app/views/referrals/index.haml @@ -14,7 +14,7 @@ %th{ class: referred_from_hidden?(@referrals.first) }= t('.referred_from') %th{ class: referred_to_hidden?(@referrals.first) }= t('.referred_to') %th= t('.date_of_referral') - %th= t('referrals.client_status') + %th= t('referrals.referral_status') %th.text-center.custom_column_manage= t('.detail') %tbody - @referrals.each do |referral| @@ -26,7 +26,7 @@ - else %td{ class: referred_to_hidden?(referral) }= referral.referred_to_ngo %td= date_format(referral.date_of_referral) - %td= status_style referral.client_status + %td= status_style referral.referral_status %td.text-center -# - if policy(referral).edit? -# = link_to edit_client_referral_path(@client, referral, referral_type: params[:referral_type]), class: 'button btn btn-outline btn-success btn-xs' do diff --git a/app/views/referrals/show.haml b/app/views/referrals/show.haml index 7a1f25cd74..512f5361a7 100644 --- a/app/views/referrals/show.haml +++ b/app/views/referrals/show.haml @@ -30,9 +30,9 @@ %td %strong= @referral.client.slug if @referral.client_id? %tr - %td= t('referrals.client_status') + %td= t('referrals.referral_status') %td - %strong= status_style @referral.client_status + %strong= status_style @referral.referral_status %tr %td= t('.referred_from') diff --git a/config/locales/en.yml b/config/locales/en.yml index 375e09fa0a..8ae0e2bcef 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5808,7 +5808,6 @@ en: modification_of: Changelog of referrals: level_of_risk: Level of Risk - client_status: Client Status confirm_referral: body_second: The client you want to refer already existed in the target NGO. body_third: You have already referred this client, please wait for the response. @@ -5857,6 +5856,7 @@ en: select_ngos: Select NGOs new: new_referral: New Referral + referral_status: Referral Status show: cannot_edit_referral: You cannot edit this referral because the target NGO already accepted this referral client_id: Client ID diff --git a/config/locales/km.yml b/config/locales/km.yml index 00fc7a3a16..00cdfcb3ab 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -5760,7 +5760,6 @@ km: modification_of: ប្រវត្តិកំណែរបស់ referrals: level_of_risk: កម្រិតហានិភ័យ - client_status: ស្ថានភាពកុមារ confirm_referral: body_second: អតិថិជនដែលអ្នកចង់បញ្ជូនមាននៅក្នុងអង្គការគោលដៅរួចទៅហើយ body_third: អ្នកបានបញ្ជូនអតិថិជននេះរួចហើយសូមរង់ចាំការឆ្លើយតប @@ -5811,6 +5810,7 @@ km: select_ngos: ជ្រើសរើសអង្គការ new: new_referral: បន្ថែមទម្រង់បញ្ជូន + referral_status: ស្ថានភាពទម្រង់បញ្ជូន show: cannot_edit_referral: អ្នកមិនអាចកែសម្រួលការបញ្ជូននេះបានទេដោយសារអង្គការគោលដៅបានទទួលយកការបញ្ជូននេះរួចទៅហើយ client_id: លេខសំគាល់អតិថិជន diff --git a/config/locales/my.yml b/config/locales/my.yml index 9c6d1d0fd0..fd0c252da7 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -5745,7 +5745,6 @@ my: modification_of: "၏ ေျပာင္းလဲမႈ" referrals: level_of_risk: အန္တရာယ်အဆင့် - client_status: ဖောက်သည်အခြေအနေ confirm_referral: body_second: သငျသညျ alreay ရည်ညွှန်းချင်သော client ကိုပစ်မှတ်အန်ဂျီအိုနေပါသည်။ body_third: သငျသညျပြီးသားဒီ client ကို, ထိုတုံ့ပြန်မှုကိုစောင့်ဆိုင်းကျေးဇူးပြုပြီးရည်ညွှန်းကြသည်။ @@ -5796,6 +5795,7 @@ my: select_ngos: အဖွဲ့အစည်းကရှေးခယျြ new: new_referral: ဂီယာ Add + referral_status: ရည်ညွှန်းမှုအခြေအနေ show: cannot_edit_referral: ပစ်မှတ်အန်ဂျီအိုပြီးသားဒီလွှဲပြောင်းလက်ခံခဲ့သည်ကြောင့်သင်ဤလွှဲပြောင်းတည်းဖြတ်မရနိုင်ပါ client_id: မွတ္ပံုတင္ diff --git a/db/migrate/20211217032514_add_field_to_referrals.rb b/db/migrate/20211217032514_add_field_to_referrals.rb index 6d05210202..5a4dba8f53 100644 --- a/db/migrate/20211217032514_add_field_to_referrals.rb +++ b/db/migrate/20211217032514_add_field_to_referrals.rb @@ -1,5 +1,5 @@ class AddFieldToReferrals < ActiveRecord::Migration def change - add_column :referrals, :client_status, :string, default: 'Referred' + add_column :referrals, :referral_status, :string, default: 'Referred' end end diff --git a/db/schema.rb b/db/schema.rb index 7807f7e020..e037289a87 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2076,7 +2076,7 @@ t.string "village_code", default: "" t.string "referee_email" t.string "level_of_risk" - t.string "client_status", default: "Referred" + t.string "referral_status", default: "Referred" end add_index "referrals", ["client_global_id"], name: "index_referrals_on_client_global_id", using: :btree From ff7aa952212e62ab240114af6e64eabac059dcfc Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 5 Jan 2022 10:55:55 +0700 Subject: [PATCH 05/94] added field status to client_share_external_serializer.rb --- app/serializers/client_share_external_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/client_share_external_serializer.rb b/app/serializers/client_share_external_serializer.rb index e2edadf274..83739144a4 100644 --- a/app/serializers/client_share_external_serializer.rb +++ b/app/serializers/client_share_external_serializer.rb @@ -1,6 +1,6 @@ class ClientShareExternalSerializer < ActiveModel::Serializer attributes :given_name, :family_name, :local_given_name, :local_family_name, :gender, - :date_of_birth, :global_id, :slug, :external_id, :external_id_display, + :date_of_birth, :global_id, :slug, :external_id, :external_id_display, :status, :mosvy_number, :location_current_village_code, :case_worker_name, :case_worker_mobile, :is_referred, :organization_name, :organization_address_code From bef6212494957a86e1b684df01ad2482c82cb9a8 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 10 Jan 2022 10:30:26 +0700 Subject: [PATCH 06/94] fixed error in app/serializers/organization_client_serializer.rb:91 NoMethodError level_of_risk for Client object --- app/serializers/organization_client_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/organization_client_serializer.rb b/app/serializers/organization_client_serializer.rb index 60bbe0fec4..2ccc56b3f6 100644 --- a/app/serializers/organization_client_serializer.rb +++ b/app/serializers/organization_client_serializer.rb @@ -88,7 +88,7 @@ def address_current_village_code end def level_of_risk - last_referral&.level_of_risk || object.level_of_risk || '' + last_referral&.level_of_risk || '' end private From 24796bb80eddef924c8450667b4f64e4b6626de0 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 13 Jan 2022 23:25:33 +0700 Subject: [PATCH 07/94] working on dashboard V2 --- .../javascripts/dashboards/index.coffee | 2 +- app/assets/javascripts/report_creator.coffee | 43 ++++++++++++++++++- app/assets/stylesheets/dashboards/index.scss | 9 ++++ app/views/dashboards/index.html.haml | 36 +++++++++++++--- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index e990ce64e8..a0fe07b9ab 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -95,7 +95,7 @@ CIF.DashboardsIndex = do -> data = $(element).data('content-count') title = $(element).data('title') report = new CIF.ReportCreator(data, title, '', element) - report.donutChart() + report.barChart() _clientProgramStream = -> element = $('#client-by-program-stream') diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index d2ccf84e2a..f40e031cf5 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -4,9 +4,48 @@ class CIF.ReportCreator @title = title @yAxisTitle = yAxisTitle @element = element - @colors = ['#4caf50', '#00695c', '#01579b', '#4dd0e1', '#2e7d32', '#4db6ac', '#00897b', '#a5d6a7', '#43a047', '#c5e1a5', '#7cb342', '#fdd835', '#fb8c00', '#6d4c41', '#757575', + @colors = ['f9c00c', '#4caf50', '#00695c', '#01579b', '#4dd0e1', '#2e7d32', '#4db6ac', '#00897b', '#a5d6a7', '#43a047', '#c5e1a5', '#7cb342', '#fdd835', '#fb8c00', '#6d4c41', '#757575', '#ef9a9a', '#e53935', '#f48fb1', '#d81b60', '#ce93d8', '#8e24aa', '#b39ddb', '#7e57c2', '#9fa8da', '#3949ab', '#64b5f6', '#827717'] - + barChart: -> + theData = @data + if @data != undefined + $(@element).highcharts + chart: + type: 'bar' + legend: + verticalAlign: 'top' + y: 30 + plotOptions: + series: + stacking: 'normal' + bar: + dataLabels: + enabled: true + tooltip: + shared: true + xDateFormat: '%b %Y' + title: + text: @title + xAxis: [ + categories: @data[0].active_data.map (element) -> + element['name'] + dateTimeLabelFormats: + month: '%b %Y' + tickmarkPlacement: 'on' + ] + yAxis: [ + allowDecimals: false + title: + text: @yAxisTitle + ] + series: @data.map (element, index) -> + { + name: element['name'] + data: theData[index].active_data.map((subElement) -> + subElement['y'] + ) + } + $('.highcharts-credits').css('display', 'none') lineChart: -> if @data != undefined $(@element).highcharts diff --git a/app/assets/stylesheets/dashboards/index.scss b/app/assets/stylesheets/dashboards/index.scss index 3b1d89e5bb..5c40e01c7a 100644 --- a/app/assets/stylesheets/dashboards/index.scss +++ b/app/assets/stylesheets/dashboards/index.scss @@ -181,4 +181,13 @@ body[id="dashboards-index"] { cursor: not-allowed; color: #333333 !important; } + + .highcharts-color-0 { + fill: #7cb5ec; + stroke: #7cb5ec; + } + .highcharts-color-1 { + fill: #f9c00c; + stroke: #f0d16b; + } } diff --git a/app/views/dashboards/index.html.haml b/app/views/dashboards/index.html.haml index 5e8862511d..675de2e529 100644 --- a/app/views/dashboards/index.html.haml +++ b/app/views/dashboards/index.html.haml @@ -10,9 +10,33 @@ = render 'multiple_forms' = render 'client' = render 'go_to_client' - .row - = render 'client_program_stream_by_gender' - .row - - if can? :read, Family - = render 'family' - = render 'third_party' + + %ul.nav.nav-tabs.csi-tab{ role: "tablist" } + %li.active{role: "presentation"} + %a{"aria-controls" => "client-tab", "data-toggle" => "tab", href: "#client-tab", role: "tab"}= t('.client_dashboard') + %li{role: "presentation"} + %a{"aria-controls" => "family-tab", "data-toggle" => "tab", href: "#family-tab", role: "tab"}= t('.family_dashboard') + %li{role: "presentation"} + %a{"aria-controls" => "community-tab", "data-toggle" => "tab", href: "#community-tab", role: "tab"}= t('.community_dashboard') + %li{role: "presentation"} + %a{"aria-controls" => "hotline-tab", "data-toggle" => "tab", href: "#hotline-tab", role: "tab"}= t('.hotline_dashboard') + .tab-content + #client-tab.tab-pane.active{ role: "tabpanel" } + .row + = render 'client_program_stream_by_gender' + .row + - if can? :read, Family + = render 'family' + = render 'third_party' + #family-tab.tab-pane{ role: 'tabpanel' } + .row + .col-xs-12 + %h3 Hello This is family panel + #community-tab.tab-pane{ role: 'tabpanel' } + .row + .col-xs-12 + %h3 Hello This is Community panel + #hotline-tab.tab-pane{ role: 'tabpanel' } + .row + .col-xs-12 + %h3 Hello This is Hotline panel \ No newline at end of file From 95d8b2e63e6908604c8e044e5d22231c79217ce2 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 26 Jan 2022 14:45:51 +0700 Subject: [PATCH 08/94] added callback update referral in origin NGO if the target NGO accept or reject the referral --- app/models/client.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/models/client.rb b/app/models/client.rb index 2e47b2dbd9..7f001c569d 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -137,6 +137,7 @@ class Client < ActiveRecord::Base after_commit :remove_family_from_case_worker after_commit :update_related_family_member, on: :update after_commit :delete_referee, on: :destroy + after_save :update_referral_status_on_target_ngo, if: :status_changed? scope :given_name_like, ->(value) { where('clients.given_name iLIKE :value OR clients.local_given_name iLIKE :value', { value: "%#{value.squish}%"}) } scope :family_name_like, ->(value) { where('clients.family_name iLIKE :value OR clients.local_family_name iLIKE :value', { value: "%#{value.squish}%"}) } @@ -826,4 +827,18 @@ def delete_referee referee.destroy end + def update_referral_status_on_target_ngo + referral = referrals.received.last + return if referral.blank? + + current_ngo = Apartment::Tenant.current + Apartment::Tenant.switch referral.referred_from + original_referral = Referral.where(slug: referral.slug).last + if original_referral + original_referral.referral_status = status + original_referral.save(validate: false) + end + Apartment::Tenant.switch current_ngo + end + end From 4d4263c33fecc014a56803d854e779d47e37fbde Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 27 Jan 2022 10:18:52 +0700 Subject: [PATCH 09/94] updated high chart font family --- vendor/assets/javascripts/highcharts.js | 11620 +++++++++++++++++++++- 1 file changed, 11244 insertions(+), 376 deletions(-) diff --git a/vendor/assets/javascripts/highcharts.js b/vendor/assets/javascripts/highcharts.js index cd54438e10..21a861b7ee 100644 --- a/vendor/assets/javascripts/highcharts.js +++ b/vendor/assets/javascripts/highcharts.js @@ -5,379 +5,11247 @@ License: www.highcharts.com/license */ -(function(L,a){"object"===typeof module&&module.exports?module.exports=L.document?a(L):a:L.Highcharts=a(L)})("undefined"!==typeof window?window:this,function(L){L=function(){var a=window,D=a.document,C=a.navigator&&a.navigator.userAgent||"",G=D&&D.createElementNS&&!!D.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect,I=/(edge|msie|trident)/i.test(C)&&!window.opera,h=!G,f=/Firefox/.test(C),p=f&&4>parseInt(C.split("Firefox/")[1],10);return a.Highcharts?a.Highcharts.error(16,!0):{product:"Highcharts", -version:"5.0.6",deg2rad:2*Math.PI/360,doc:D,hasBidiBug:p,hasTouch:D&&void 0!==D.documentElement.ontouchstart,isMS:I,isWebKit:/AppleWebKit/.test(C),isFirefox:f,isTouchDevice:/(Mobile|Android|Windows Phone)/.test(C),SVG_NS:"http://www.w3.org/2000/svg",chartCount:0,seriesTypes:{},symbolSizes:{},svg:G,vml:h,win:a,charts:[],marginNames:["plotTop","marginRight","marginBottom","plotLeft"],noop:function(){}}}();(function(a){var D=[],C=a.charts,G=a.doc,I=a.win;a.error=function(h,f){h=a.isNumber(h)?"Highcharts error #"+ -h+": www.highcharts.com/errors/"+h:h;if(f)throw Error(h);I.console&&console.log(h)};a.Fx=function(a,f,p){this.options=f;this.elem=a;this.prop=p};a.Fx.prototype={dSetter:function(){var a=this.paths[0],f=this.paths[1],p=[],v=this.now,l=a.length,u;if(1===v)p=this.toD;else if(l===f.length&&1>v)for(;l--;)u=parseFloat(a[l]),p[l]=isNaN(u)?a[l]:v*parseFloat(f[l]-u)+u;else p=f;this.elem.attr("d",p,null,!0)},update:function(){var a=this.elem,f=this.prop,p=this.now,v=this.options.step;if(this[f+"Setter"])this[f+ -"Setter"]();else a.attr?a.element&&a.attr(f,p,null,!0):a.style[f]=p+this.unit;v&&v.call(a,p,this)},run:function(a,f,p){var h=this,l=function(a){return l.stopped?!1:h.step(a)},u;this.startTime=+new Date;this.start=a;this.end=f;this.unit=p;this.now=this.start;this.pos=0;l.elem=this.elem;l.prop=this.prop;l()&&1===D.push(l)&&(l.timerId=setInterval(function(){for(u=0;u=u+this.startTime){this.now=this.end;this.pos=1;this.update();a=d[this.prop]=!0;for(c in d)!0!==d[c]&&(a=!1);a&&l&&l.call(h);h=!1}else this.pos=v.easing((f-this.startTime)/u),this.now=this.start+(this.end-this.start)*this.pos,this.update(),h=!0;return h},initPath:function(h,f,p){function v(a){var e,b;for(q=a.length;q--;)e="M"===a[q]||"L"===a[q],b=/[a-zA-Z]/.test(a[q+3]),e&&b&&a.splice(q+1,0,a[q+1],a[q+2],a[q+1],a[q+ -2])}function l(a,e){for(;a.lengthd?"AM":"PM",P:12>d?"am":"pm",S:z(l.getSeconds()),L:z(Math.round(f% -1E3),3)},a.dateFormats);for(u in v)for(;-1!==h.indexOf("%"+u);)h=h.replace("%"+u,"function"===typeof v[u]?v[u](f):v[u]);return p?h.substr(0,1).toUpperCase()+h.substr(1):h};a.formatSingle=function(h,f){var p=/\.([0-9])/,v=a.defaultOptions.lang;/f$/.test(h)?(p=(p=h.match(p))?p[1]:-1,null!==f&&(f=a.numberFormat(f,p,v.decimalPoint,-1=p&&(f=[1/p]))); -for(v=0;v=h||!l&&u<=(f[v]+(f[v+1]||f[v]))/2);v++);return d*p};a.stableSort=function(a,f){var p=a.length,h,l;for(l=0;lp&&(p=a[f]);return p};a.destroyObjectProperties=function(a,f){for(var p in a)a[p]&&a[p]!==f&&a[p].destroy&& -a[p].destroy(),delete a[p]};a.discardElement=function(h){var f=a.garbageBin;f||(f=a.createElement("div"));h&&f.appendChild(h);f.innerHTML=""};a.correctFloat=function(a,f){return parseFloat(a.toPrecision(f||14))};a.setAnimation=function(h,f){f.renderer.globalAnimation=a.pick(h,f.options.chart.animation,!0)};a.animObject=function(h){return a.isObject(h)?a.merge(h):{duration:h?500:0}};a.timeUnits={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5,month:24192E5,year:314496E5};a.numberFormat= -function(h,f,p,v){h=+h||0;f=+f;var l=a.defaultOptions.lang,u=(h.toString().split(".")[1]||"").length,d,c,n=Math.abs(h);-1===f?f=Math.min(u,20):a.isNumber(f)||(f=2);d=String(a.pInt(n.toFixed(f)));c=3h?"-":"")+(c?d.substr(0,c)+v:"");h+=d.substr(c).replace(/(\d{3})(?=\d)/g,"$1"+v);f&&(v=Math.abs(n-d+Math.pow(10,-Math.max(f,u)-1)),h+=p+v.toFixed(f).slice(2));return h};Math.easeInOutSine=function(a){return-.5*(Math.cos(Math.PI* -a)-1)};a.getStyle=function(h,f){return"width"===f?Math.min(h.offsetWidth,h.scrollWidth)-a.getStyle(h,"padding-left")-a.getStyle(h,"padding-right"):"height"===f?Math.min(h.offsetHeight,h.scrollHeight)-a.getStyle(h,"padding-top")-a.getStyle(h,"padding-bottom"):(h=I.getComputedStyle(h,void 0))&&a.pInt(h.getPropertyValue(f))};a.inArray=function(a,f){return f.indexOf?f.indexOf(a):[].indexOf.call(f,a)};a.grep=function(a,f){return[].filter.call(a,f)};a.find=function(a,f){return[].find.call(a,f)};a.map=function(a, -f){for(var p=[],h=0,l=a.length;hf;f++)v[f]+=h(255*a),0>v[f]&&(v[f]=0),255E.width)E={width:0, -height:0}}else E=this.htmlGetBBox();H.isSVG&&(a=E.width,H=E.height,e&&c&&"11px"===c.fontSize&&"16.9"===H.toPrecision(3)&&(E.height=H=14),g&&(E.width=Math.abs(H*Math.sin(k))+Math.abs(a*Math.cos(k)),E.height=Math.abs(H*Math.cos(k))+Math.abs(a*Math.sin(k))));if(x&&0]*>/g,"")))},textSetter:function(a){a!==this.textStr&&(delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this))},fillSetter:function(a, -g,e){"string"===typeof a?e.setAttribute(g,a):a&&this.colorGradient(a,g,e)},visibilitySetter:function(a,g,e){"inherit"===a?e.removeAttribute(g):e.setAttribute(g,a)},zIndexSetter:function(a,g){var e=this.renderer,k=this.parentGroup,b=(k||e).element||e.box,c,B=this.element,H;c=this.added;var r;u(a)&&(B.zIndex=a,a=+a,this[g]===a&&(c=!1),this[g]=a);if(c){(a=this.zIndex)&&k&&(k.handleZ=!0);g=b.childNodes;for(r=0;ra||!u(a)&&u(c)||0>a&&!u(c)&&b!==e.box)&&(b.insertBefore(B, -k),H=!0);H||b.appendChild(B)}return H},_defaultSetter:function(a,g,e){e.setAttribute(g,a)}};D.prototype.yGetter=D.prototype.xGetter;D.prototype.translateXSetter=D.prototype.translateYSetter=D.prototype.rotationSetter=D.prototype.verticalAlignSetter=D.prototype.scaleXSetter=D.prototype.scaleYSetter=function(a,g){this[g]=a;this.doTransform=!0};D.prototype["stroke-widthSetter"]=D.prototype.strokeSetter=function(a,g,e){this[g]=a;this.stroke&&this["stroke-width"]?(D.prototype.fillSetter.call(this,this.stroke, -"stroke",e),e.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0):"stroke-width"===g&&0===a&&this.hasStroke&&(e.removeAttribute("stroke"),this.hasStroke=!1)};C=a.SVGRenderer=function(){this.init.apply(this,arguments)};C.prototype={Element:D,SVG_NS:S,init:function(a,g,e,k,b,c){var B;k=this.createElement("svg").attr({version:"1.1","class":"highcharts-root"}).css(this.getStyle(k));B=k.element;a.appendChild(B);-1===a.innerHTML.indexOf("xmlns")&&h(B,"xmlns",this.SVG_NS);this.isSVG=!0; -this.box=B;this.boxWrapper=k;this.alignedObjects=[];this.url=(F||A)&&n.getElementsByTagName("base").length?R.location.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(n.createTextNode("Created with Highcharts 5.0.6"));this.defs=this.createElement("defs").add();this.allowHTML=c;this.forExport=b;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(g,e,!1);var H;F&&a.getBoundingClientRect&&(g=function(){v(a, -{left:0,top:0});H=a.getBoundingClientRect();v(a,{left:Math.ceil(H.left)-H.left+"px",top:Math.ceil(H.top)-H.top+"px"})},g(),this.unSubPixelFix=G(R,"resize",g))},getStyle:function(a){return this.style=t({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},setStyle:function(a){this.boxWrapper.css(this.getStyle(a))},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy(); -c(this.gradients||{});this.gradients=null;a&&(this.defs=a.destroy());this.unSubPixelFix&&this.unSubPixelFix();return this.alignedObjects=null},createElement:function(a){var g=new this.Element;g.init(this,a);return g},draw:w,getRadialAttr:function(a,g){return{cx:a[0]-a[2]/2+g.cx*a[2],cy:a[1]-a[2]/2+g.cy*a[2],r:g.r*a[2]}},buildText:function(a){for(var g=a.element,e=this,k=e.forExport,c=K(a.textStr,"").toString(),r=-1!==c.indexOf("\x3c"),m=g.childNodes,w,E,q,x,d=h(g,"x"),t=a.styles,A=a.textWidth,z=t&& -t.lineHeight,l=t&&t.textOutline,F=t&&"ellipsis"===t.textOverflow,f=m.length,u=A&&!a.added&&this.box,p=function(a){var k;k=/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:t&&t.fontSize||e.style.fontSize||12;return z?J(z):e.fontMetrics(k,a.getAttribute("style")?a:g).h};f--;)g.removeChild(m[f]);r||l||F||A||-1!==c.indexOf(" ")?(w=/<.*class="([^"]+)".*>/,E=/<.*style="([^"]+)".*>/,q=/<.*href="(http[^"]+)".*>/,u&&u.appendChild(g),c=r?c.replace(/<(b|strong)>/g,'\x3cspan style\x3d"font-weight:bold"\x3e').replace(/<(i|em)>/g, -'\x3cspan style\x3d"font-style:italic"\x3e').replace(//g,"\x3c/span\x3e").split(//g):[c],c=b(c,function(a){return""!==a}),y(c,function(b,c){var r,H=0;b=b.replace(/^\s+|\s+$/g,"").replace(//g,"\x3c/span\x3e|||");r=b.split("|||");y(r,function(b){if(""!==b||1===r.length){var m={},l=n.createElementNS(e.SVG_NS,"tspan"),z,f;w.test(b)&&(z=b.match(w)[1],h(l,"class",z));E.test(b)&&(f=b.match(E)[1].replace(/(;| |^)color([ :])/, -"$1fill$2"),h(l,"style",f));q.test(b)&&!k&&(h(l,"onclick",'location.href\x3d"'+b.match(q)[1]+'"'),v(l,{cursor:"pointer"}));b=(b.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"\x3c").replace(/>/g,"\x3e");if(" "!==b){l.appendChild(n.createTextNode(b));H?m.dx=0:c&&null!==d&&(m.x=d);h(l,m);g.appendChild(l);!H&&c&&(!B&&k&&v(l,{display:"block"}),h(l,"dy",p(l)));if(A){m=b.replace(/([^\^])-/g,"$1- ").split(" ");z="nowrap"===t.whiteSpace;for(var K=1A,void 0===x&&(x=u),F&&x?(N/=2,""===O||!u&&.5>N?m=[]:(O=b.substring(0,O.length+(u?-1:1)*Math.ceil(N)),m=[O+(3A&&(A=y)),m.length&& -l.appendChild(n.createTextNode(m.join(" ").replace(/- /g,"-")));a.rotation=P}H++}}})}),x&&a.attr("title",a.textStr),u&&u.removeChild(g),l&&a.applyTextOutline&&a.applyTextOutline(l)):g.appendChild(n.createTextNode(c.replace(/</g,"\x3c").replace(/>/g,"\x3e")))},getContrast:function(a){a=p(a).rgba;return 510e?k>g+B&&kr?k>g+B&&kb&&r>a+B&&rk&&r>a+B&&ra?a+3:Math.round(1.2* -a);return{h:g,b:Math.round(.8*g),f:a}},rotCorr:function(a,g,e){var b=a;g&&e&&(b=Math.max(b*Math.cos(g*d),4));return{x:-a/3*Math.sin(g*d),y:b}},label:function(a,g,e,b,c,B,r,m,w){var q=this,x=q.g("button"!==w&&"label"),d=x.text=q.text("",0,0,r).attr({zIndex:1}),H,n,l=0,A=3,z=0,F,f,K,p,J,h={},M,S,E=/^url\((.*?)\)$/.test(b),v=E,P,R,O,Q;w&&x.addClass("highcharts-"+w);v=E;P=function(){return(M||0)%2/2};R=function(){var a=d.element.style,g={};n=(void 0===F||void 0===f||J)&&u(d.textStr)&&d.getBBox();x.width= -(F||n.width||0)+2*A+z;x.height=(f||n.height||0)+2*A;S=A+q.fontMetrics(a&&a.fontSize,d).b;v&&(H||(x.box=H=q.symbols[b]||E?q.symbol(b):q.rect(),H.addClass(("button"===w?"":"highcharts-label-box")+(w?" highcharts-"+w+"-box":"")),H.add(x),a=P(),g.x=a,g.y=(m?-S:0)+a),g.width=Math.round(x.width),g.height=Math.round(x.height),H.attr(t(g,h)),h={})};O=function(){var a=z+A,g;g=m?0:S;u(F)&&n&&("center"===J||"right"===J)&&(a+={center:.5,right:1}[J]*(F-n.width));if(a!==d.x||g!==d.y)d.attr("x",a),void 0!==g&&d.attr("y", -g);d.x=a;d.y=g};Q=function(a,g){H?H.attr(a,g):h[a]=g};x.onAdd=function(){d.add(x);x.attr({text:a||0===a?a:"",x:g,y:e});H&&u(c)&&x.attr({anchorX:c,anchorY:B})};x.widthSetter=function(a){F=a};x.heightSetter=function(a){f=a};x["text-alignSetter"]=function(a){J=a};x.paddingSetter=function(a){u(a)&&a!==A&&(A=x.padding=a,O())};x.paddingLeftSetter=function(a){u(a)&&a!==z&&(z=a,O())};x.alignSetter=function(a){a={left:0,center:.5,right:1}[a];a!==l&&(l=a,n&&x.attr({x:K}))};x.textSetter=function(a){void 0!== -a&&d.textSetter(a);R();O()};x["stroke-widthSetter"]=function(a,g){a&&(v=!0);M=this["stroke-width"]=a;Q(g,a)};x.strokeSetter=x.fillSetter=x.rSetter=function(a,g){"fill"===g&&a&&(v=!0);Q(g,a)};x.anchorXSetter=function(a,g){c=a;Q(g,Math.round(a)-P()-K)};x.anchorYSetter=function(a,g){B=a;Q(g,a-p)};x.xSetter=function(a){x.x=a;l&&(a-=l*((F||n.width)+2*A));K=Math.round(a);x.attr("translateX",K)};x.ySetter=function(a){p=x.y=Math.round(a);x.attr("translateY",p)};var V=x.css;return t(x,{css:function(a){if(a){var g= -{};a=k(a);y(x.textProps,function(e){void 0!==a[e]&&(g[e]=a[e],delete a[e])});d.css(g)}return V.call(x,a)},getBBox:function(){return{width:n.width+2*A,height:n.height+2*A,x:n.x-A,y:n.y-A}},shadow:function(a){a&&(R(),H&&H.shadow(a));return x},destroy:function(){N(x.element,"mouseenter");N(x.element,"mouseleave");d&&(d=d.destroy());H&&(H=H.destroy());D.prototype.destroy.call(x);x=q=R=O=Q=null}})}};a.Renderer=C})(L);(function(a){var D=a.attr,C=a.createElement,G=a.css,I=a.defined,h=a.each,f=a.extend,p= -a.isFirefox,v=a.isMS,l=a.isWebKit,u=a.pInt,d=a.SVGRenderer,c=a.win,n=a.wrap;f(a.SVGElement.prototype,{htmlCss:function(a){var c=this.element;if(c=a&&"SPAN"===c.tagName&&a.width)delete a.width,this.textWidth=c,this.updateTransform();a&&"ellipsis"===a.textOverflow&&(a.whiteSpace="nowrap",a.overflow="hidden");this.styles=f(this.styles,a);G(this.element,a);return this},htmlGetBBox:function(){var a=this.element;"text"===a.nodeName&&(a.style.position="absolute");return{x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth, -height:a.offsetHeight}},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,c=this.element,m=this.translateX||0,b=this.translateY||0,q=this.x||0,d=this.y||0,n=this.textAlign||"left",e={left:0,center:.5,right:1}[n],r=this.styles;G(c,{marginLeft:m,marginTop:b});this.shadows&&h(this.shadows,function(a){G(a,{marginLeft:m+1,marginTop:b+1})});this.inverted&&h(c.childNodes,function(e){a.invertChild(e,c)});if("SPAN"===c.tagName){var x=this.rotation,A=u(this.textWidth),k=r&&r.whiteSpace,w=[x, -n,c.innerHTML,this.textWidth,this.textAlign].join();w!==this.cTT&&(r=a.fontMetrics(c.style.fontSize).b,I(x)&&this.setSpanRotation(x,e,r),G(c,{width:"",whiteSpace:k||"nowrap"}),c.offsetWidth>A&&/[ \-]/.test(c.textContent||c.innerText)&&G(c,{width:A+"px",display:"block",whiteSpace:k||"normal"}),this.getSpanCorrection(c.offsetWidth,r,e,x,n));G(c,{left:q+(this.xCorr||0)+"px",top:d+(this.yCorr||0)+"px"});l&&(r=c.offsetHeight);this.cTT=w}}else this.alignOnAdd=!0},setSpanRotation:function(a,d,m){var b={}, -q=v?"-ms-transform":l?"-webkit-transform":p?"MozTransform":c.opera?"-o-transform":"";b[q]=b.transform="rotate("+a+"deg)";b[q+(p?"Origin":"-origin")]=b.transformOrigin=100*d+"% "+m+"px";G(this.element,b)},getSpanCorrection:function(a,c,m){this.xCorr=-a*m;this.yCorr=-c}});f(d.prototype,{html:function(a,c,m){var b=this.createElement("span"),q=b.element,d=b.renderer,l=d.isSVG,e=function(a,e){h(["opacity","visibility"],function(b){n(a,b+"Setter",function(a,b,c,r){a.call(this,b,c,r);e[c]=b})})};b.textSetter= -function(a){a!==q.innerHTML&&delete this.bBox;q.innerHTML=this.textStr=a;b.htmlUpdateTransform()};l&&e(b,b.element.style);b.xSetter=b.ySetter=b.alignSetter=b.rotationSetter=function(a,e){"align"===e&&(e="textAlign");b[e]=a;b.htmlUpdateTransform()};b.attr({text:a,x:Math.round(c),y:Math.round(m)}).css({fontFamily:this.style.fontFamily,fontSize:this.style.fontSize,position:"absolute"});q.style.whiteSpace="nowrap";b.css=b.htmlCss;l&&(b.add=function(a){var c,r=d.box.parentNode,k=[];if(this.parentGroup= -a){if(c=a.div,!c){for(;a;)k.push(a),a=a.parentGroup;h(k.reverse(),function(a){var m,x=D(a.element,"class");x&&(x={className:x});c=a.div=a.div||C("div",x,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px",display:a.display,opacity:a.opacity,pointerEvents:a.styles&&a.styles.pointerEvents},c||r);m=c.style;f(a,{on:function(){b.on.apply({element:k[0].div},arguments);return a},translateXSetter:function(e,g){m.left=e+"px";a[g]=e;a.doTransform=!0},translateYSetter:function(e,g){m.top= -e+"px";a[g]=e;a.doTransform=!0}});e(a,m)})}}else c=r;c.appendChild(q);b.added=!0;b.alignOnAdd&&b.htmlUpdateTransform();return b});return b}})})(L);(function(a){var D,C,G=a.createElement,I=a.css,h=a.defined,f=a.deg2rad,p=a.discardElement,v=a.doc,l=a.each,u=a.erase,d=a.extend;D=a.extendClass;var c=a.isArray,n=a.isNumber,y=a.isObject,t=a.merge;C=a.noop;var m=a.pick,b=a.pInt,q=a.SVGElement,z=a.SVGRenderer,F=a.win;a.svg||(C={docMode8:v&&8===v.documentMode,init:function(a,b){var e=["\x3c",b,' filled\x3d"f" stroked\x3d"f"'], -c=["position: ","absolute",";"],k="div"===b;("shape"===b||k)&&c.push("left:0;top:0;width:1px;height:1px;");c.push("visibility: ",k?"hidden":"visible");e.push(' style\x3d"',c.join(""),'"/\x3e');b&&(e=k||"span"===b||"img"===b?e.join(""):a.prepVML(e),this.element=G(e));this.renderer=a},add:function(a){var e=this.renderer,b=this.element,c=e.box,k=a&&a.inverted,c=a?a.element||a:c;a&&(this.parentGroup=a);k&&e.invertChild(b,c);c.appendChild(b);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform(); -if(this.onAdd)this.onAdd();this.className&&this.attr("class",this.className);return this},updateTransform:q.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=Math.cos(a*f),c=Math.sin(a*f);I(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11\x3d",b,", M12\x3d",-c,", M21\x3d",c,", M22\x3d",b,", sizingMethod\x3d'auto expand')"].join(""):"none"})},getSpanCorrection:function(a,b,c,q,k){var e=q?Math.cos(q*f):1,r=q?Math.sin(q*f):0,x=m(this.elemHeight,this.element.offsetHeight), -d;this.xCorr=0>e&&-a;this.yCorr=0>r&&-x;d=0>e*r;this.xCorr+=r*b*(d?1-c:c);this.yCorr-=e*b*(q?d?c:1-c:1);k&&"left"!==k&&(this.xCorr-=a*c*(0>e?-1:1),q&&(this.yCorr-=x*c*(0>r?-1:1)),I(this.element,{textAlign:k}))},pathToVML:function(a){for(var e=a.length,b=[];e--;)n(a[e])?b[e]=Math.round(10*a[e])-5:"Z"===a[e]?b[e]="x":(b[e]=a[e],!a.isArc||"wa"!==a[e]&&"at"!==a[e]||(b[e+5]===b[e+7]&&(b[e+7]+=a[e+7]>a[e+5]?1:-1),b[e+6]===b[e+8]&&(b[e+8]+=a[e+8]>a[e+6]?1:-1)));return b.join(" ")||"x"},clip:function(a){var e= -this,b;a?(b=a.members,u(b,e),b.push(e),e.destroyClip=function(){u(b,e)},a=a.getCSS(e)):(e.destroyClip&&e.destroyClip(),a={clip:e.docMode8?"inherit":"rect(auto)"});return e.css(a)},css:q.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&p(a)},destroy:function(){this.destroyClip&&this.destroyClip();return q.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=F.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,c){var e;a=a.split(/[ ,]/); -e=a.length;if(9===e||11===e)a[e-4]=a[e-2]=b(a[e-2])-10*c;return a.join(" ")},shadow:function(a,c,q){var e=[],k,r=this.element,d=this.renderer,x,n=r.style,g,B=r.path,l,t,z,f;B&&"string"!==typeof B.value&&(B="x");t=B;if(a){z=m(a.width,3);f=(a.opacity||.15)/z;for(k=1;3>=k;k++)l=2*z+1-2*k,q&&(t=this.cutOffPath(B.value,l+.5)),g=['\x3cshape isShadow\x3d"true" strokeweight\x3d"',l,'" filled\x3d"false" path\x3d"',t,'" coordsize\x3d"10 10" style\x3d"',r.style.cssText,'" /\x3e'],x=G(d.prepVML(g),null,{left:b(n.left)+ -m(a.offsetX,1),top:b(n.top)+m(a.offsetY,1)}),q&&(x.cutOff=l+1),g=['\x3cstroke color\x3d"',a.color||"#000000",'" opacity\x3d"',f*k,'"/\x3e'],G(d.prepVML(g),null,null,x),c?c.element.appendChild(x):r.parentNode.insertBefore(x,r),e.push(x);this.shadows=e}return this},updateShadows:C,setAttr:function(a,b){this.docMode8?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){(this.added?this.element:this).className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]|| -G(this.renderer.prepVML(["\x3cstroke/\x3e"]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var e=this.shadows;a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(e)for(c=e.length;c--;)e[c].path=e[c].cutOff?this.cutOffPath(a,e[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var e=c.nodeName;"SPAN"===e?c.style.color=a:"IMG"!==e&&(c.filled="none"!==a,this.setAttr("fillcolor",this.renderer.color(a,c,b,this)))},"fill-opacitySetter":function(a,b,c){G(this.renderer.prepVML(["\x3c", -b.split("-")[0],' opacity\x3d"',a,'"/\x3e']),null,null,c)},opacitySetter:C,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-Math.round(Math.sin(a*f)+1)+"px";c.top=Math.round(Math.cos(a*f))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b,this))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;n(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){"inherit"===a&& -(a="visible");this.shadows&&l(this.shadows,function(c){c.style[b]=a});"DIV"===c.nodeName&&(a="hidden"===a?"-999em":0,this.docMode8||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;"x"===b?b="left":"y"===b&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}},C["stroke-opacitySetter"]=C["fill-opacitySetter"],a.VMLElement=C=D(q,C),C.prototype.ySetter=C.prototype.widthSetter=C.prototype.heightSetter= -C.prototype.xSetter,C={Element:C,isIE8:-1h[0]&&b.push([1,h[1]]);l(b,function(g,b){d.test(g[1])?(r=a.color(g[1]),B=r.get("rgb"),t=r.get("a")):(B=g[1],t=1);y.push(100*g[0]+"% "+B);b?(H=t,A=B):(F=t,u=B)});if("fill"===m)if("gradient"===x)m=z.x1||z[0]||0,b=z.y1||z[1]||0,f=z.x2||z[2]||0,z=z.y2||z[3]||0,p='angle\x3d"'+(90-180*Math.atan((z-b)/(f-m))/Math.PI)+'"', -v();else{var g=z.r,C=2*g,D=2*g,I=z.cx,U=z.cy,L=c.radialReference,T,g=function(){L&&(T=q.getBBox(),I+=(L[0]-T.x)/T.width-.5,U+=(L[1]-T.y)/T.height-.5,C*=L[2]/T.width,D*=L[2]/T.height);p='src\x3d"'+a.getOptions().global.VMLRadialGradientURL+'" size\x3d"'+C+","+D+'" origin\x3d"0.5,0.5" position\x3d"'+I+","+U+'" color2\x3d"'+u+'" ';v()};q.added?g():q.onAdd=g;g=A}else g=B}else d.test(b)&&"IMG"!==c.tagName?(r=a.color(b),q[m+"-opacitySetter"](r.get("a"),m,c),g=r.get("rgb")):(g=c.getElementsByTagName(m), -g.length&&(g[0].opacity=1,g[0].type="solid"),g=b);return g},prepVML:function(a){var b=this.isIE8;a=a.join("");b?(a=a.replace("/\x3e",' xmlns\x3d"urn:schemas-microsoft-com:vml" /\x3e'),a=-1===a.indexOf('style\x3d"')?a.replace("/\x3e",' style\x3d"display:inline-block;behavior:url(#default#VML);" /\x3e'):a.replace('style\x3d"','style\x3d"display:inline-block;behavior:url(#default#VML);')):a=a.replace("\x3c","\x3chcv:");return a},text:z.prototype.html,path:function(a){var b={coordsize:"10 10"};c(a)?b.d= -a:y(a)&&d(b,a);return this.createElement("shape").attr(b)},circle:function(a,b,c){var e=this.symbol("circle");y(a)&&(c=a.r,b=a.y,a=a.x);e.isCircle=!0;e.r=c;return e.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:"highcharts-"+a,"class":"highcharts-"+a});return this.createElement("div").attr(b)},image:function(a,b,c,m,k){var e=this.createElement("img").attr({src:a});1t&&h-m*bc&&(e=Math.round((d-h)/Math.cos(t*p)));else if(d=h+(1-m)*b,h-m*bc&&(z=c-a.x+z*m,F=-1),z=Math.min(q, -z),zz||l.autoRotation&&(y.styles||{}).width)e=z;e&&(r.width=e,(l.options.labels.style||{}).textOverflow||(r.textOverflow="ellipsis"),y.css(r))},getPosition:function(a,f,h,d){var c=this.axis,n=c.chart,l=d&&n.oldChartHeight||n.chartHeight;return{x:a?c.translate(f+h,null,null,d)+c.transB:c.left+c.offset+(c.opposite?(d&&n.oldChartWidth||n.chartWidth)-c.right-c.left:0),y:a?l-c.bottom+c.offset-(c.opposite?c.height:0):l-c.translate(f+h,null, -null,d)-c.transB}},getLabelPosition:function(a,f,h,d,c,n,y,t){var m=this.axis,b=m.transA,q=m.reversed,z=m.staggerLines,l=m.tickRotCorr||{x:0,y:0},e=c.y;C(e)||(e=0===m.side?h.rotation?-8:-h.getBBox().height:2===m.side?l.y+8:Math.cos(h.rotation*p)*(l.y-h.getBBox(!1,0).height/2));a=a+c.x+l.x-(n&&d?n*b*(q?-1:1):0);f=f+e-(n&&!d?n*b*(q?1:-1):0);z&&(h=y/(t||1)%z,m.opposite&&(h=z-h-1),f+=m.labelOffset/z*h);return{x:a,y:Math.round(f)}},getMarkPath:function(a,f,h,d,c,n){return n.crispLine(["M",a,f,"L",a+(c? -0:-h),f+(c?h:0)],d)},render:function(a,l,h){var d=this.axis,c=d.options,n=d.chart.renderer,p=d.horiz,t=this.type,m=this.label,b=this.pos,q=c.labels,z=this.gridLine,F=t?t+"Tick":"tick",e=d.tickSize(F),r=this.mark,x=!r,A=q.step,k={},w=!0,K=d.tickmarkOffset,J=this.getPosition(p,b,K,l),u=J.x,J=J.y,g=p&&u===d.pos+d.len||!p&&J===d.pos?-1:1,B=t?t+"Grid":"grid",S=c[B+"LineWidth"],M=c[B+"LineColor"],v=c[B+"LineDashStyle"],B=f(c[F+"Width"],!t&&d.isXAxis?1:0),F=c[F+"Color"];h=f(h,1);this.isActive=!0;z||(k.stroke= -M,k["stroke-width"]=S,v&&(k.dashstyle=v),t||(k.zIndex=1),l&&(k.opacity=0),this.gridLine=z=n.path().attr(k).addClass("highcharts-"+(t?t+"-":"")+"grid-line").add(d.gridGroup));if(!l&&z&&(b=d.getPlotLinePath(b+K,z.strokeWidth()*g,l,!0)))z[this.isNew?"attr":"animate"]({d:b,opacity:h});e&&(d.opposite&&(e[0]=-e[0]),x&&(this.mark=r=n.path().addClass("highcharts-"+(t?t+"-":"")+"tick").add(d.axisGroup),r.attr({stroke:F,"stroke-width":B})),r[x?"attr":"animate"]({d:this.getMarkPath(u,J,e[0],r.strokeWidth()* -g,p,n),opacity:h}));m&&I(u)&&(m.xy=J=this.getLabelPosition(u,J,m,p,q,K,a,A),this.isFirst&&!this.isLast&&!f(c.showFirstLabel,1)||this.isLast&&!this.isFirst&&!f(c.showLastLabel,1)?w=!1:!p||d.isRadial||q.step||q.rotation||l||0===h||this.handleOverflow(J),A&&a%A&&(w=!1),w&&I(J.y)?(J.opacity=h,m[this.isNew?"attr":"animate"](J)):m.attr("y",-9999),this.isNew=!1)},destroy:function(){G(this,this.axis)}}})(L);(function(a){var D=a.addEvent,C=a.animObject,G=a.arrayMax,I=a.arrayMin,h=a.AxisPlotLineOrBandExtension, -f=a.color,p=a.correctFloat,v=a.defaultOptions,l=a.defined,u=a.deg2rad,d=a.destroyObjectProperties,c=a.each,n=a.extend,y=a.fireEvent,t=a.format,m=a.getMagnitude,b=a.grep,q=a.inArray,z=a.isArray,F=a.isNumber,e=a.isString,r=a.merge,x=a.normalizeTickInterval,A=a.pick,k=a.PlotLineOrBand,w=a.removeEvent,K=a.splat,J=a.syncTimeout,N=a.Tick;a.Axis=function(){this.init.apply(this,arguments)};a.Axis.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M", -day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,labels:{enabled:!0,style:{color:"#666666",cursor:"default",fontSize:"11px"},x:0},minPadding:.01,maxPadding:.01,minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",title:{align:"middle",style:{color:"#666666"}},type:"linear",minorGridLineColor:"#f2f2f2",minorGridLineWidth:1,minorTickColor:"#999999",lineColor:"#ccd6eb",lineWidth:1, -gridLineColor:"#e6e6e6",tickColor:"#ccd6eb"},defaultYAxisOptions:{endOnTick:!0,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8},maxPadding:.05,minPadding:.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return a.numberFormat(this.total,-1)},style:{fontSize:"11px",fontWeight:"bold",color:"#000000",textOutline:"1px contrast"}},gridLineWidth:1,lineWidth:0},defaultLeftAxisOptions:{labels:{x:-15},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15}, -title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},init:function(a,b){var g=b.isX;this.chart=a;this.horiz=a.inverted?!g:g;this.isXAxis=g;this.coll=this.coll||(g?"xAxis":"yAxis");this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var c=this.options,e=c.type;this.labelFormatter=c.labels.formatter||this.defaultLabelFormatter; -this.userOptions=b;this.minPixelPadding=0;this.reversed=c.reversed;this.visible=!1!==c.visible;this.zoomEnabled=!1!==c.zoomEnabled;this.hasNames="category"===e||!0===c.categories;this.categories=c.categories||this.hasNames;this.names=this.names||[];this.isLog="logarithmic"===e;this.isDatetimeAxis="datetime"===e;this.isLinked=l(c.linkedTo);this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=c.minRange||c.maxZoom; -this.range=c.range;this.offset=c.offset||0;this.stacks={};this.oldStacks={};this.stacksTouched=0;this.min=this.max=null;this.crosshair=A(c.crosshair,K(a.options.tooltip.crosshairs)[g?0:1],!1);var k;b=this.options.events;-1===q(this,a.axes)&&(g?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];a.inverted&&g&&void 0===this.reversed&&(this.reversed=!0);this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(k in b)D(this,k,b[k]); -this.isLog&&(this.val2lin=this.log2lin,this.lin2val=this.lin2log)},setOptions:function(a){this.options=r(this.defaultOptions,"yAxis"===this.coll&&this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],r(v[this.coll],a))},defaultLabelFormatter:function(){var g=this.axis,b=this.value,c=g.categories,e=this.dateTimeLabelFormat,k=v.lang,m=k.numericSymbols,k=k.numericSymbolMagnitude||1E3,q=m&&m.length,d,r=g.options.labels.format, -g=g.isLog?b:g.tickInterval;if(r)d=t(r,this);else if(c)d=b;else if(e)d=a.dateFormat(e,b);else if(q&&1E3<=g)for(;q--&&void 0===d;)c=Math.pow(k,q+1),g>=c&&0===10*b%c&&null!==m[q]&&0!==b&&(d=a.numberFormat(b/c,-1)+m[q]);void 0===d&&(d=1E4<=Math.abs(b)?a.numberFormat(b,-1):a.numberFormat(b,-1,void 0,""));return d},getSeriesExtremes:function(){var a=this,e=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.threshold=null;a.softThreshold=!a.isXAxis;a.buildStacks&&a.buildStacks();c(a.series,function(g){if(g.visible|| -!e.options.chart.ignoreHiddenSeries){var c=g.options,k=c.threshold,B;a.hasVisibleSeries=!0;a.isLog&&0>=k&&(k=null);if(a.isXAxis)c=g.xData,c.length&&(g=I(c),F(g)||g instanceof Date||(c=b(c,function(a){return F(a)}),g=I(c)),a.dataMin=Math.min(A(a.dataMin,c[0]),g),a.dataMax=Math.max(A(a.dataMax,c[0]),G(c)));else if(g.getExtremes(),B=g.dataMax,g=g.dataMin,l(g)&&l(B)&&(a.dataMin=Math.min(A(a.dataMin,g),g),a.dataMax=Math.max(A(a.dataMax,B),B)),l(k)&&(a.threshold=k),!c.softThreshold||a.isLog)a.softThreshold= -!1}})},translate:function(a,b,c,e,k,m){var g=this.linkedParent||this,B=1,q=0,d=e?g.oldTransA:g.transA;e=e?g.oldMin:g.min;var r=g.minPixelPadding;k=(g.isOrdinal||g.isBroken||g.isLog&&k)&&g.lin2val;d||(d=g.transA);c&&(B*=-1,q=g.len);g.reversed&&(B*=-1,q-=B*(g.sector||g.len));b?(a=(a*B+q-r)/d+e,k&&(a=g.lin2val(a))):(k&&(a=g.val2lin(a)),a=B*(a-e)*d+q+B*r+(F(m)?d*m:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a- -(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,e,k){var g=this.chart,B=this.left,m=this.top,q,d,r=c&&g.oldChartHeight||g.chartHeight,n=c&&g.oldChartWidth||g.chartWidth,f;q=this.transB;var w=function(a,g,b){if(ab)e?a=Math.min(Math.max(g,a),b):f=!0;return a};k=A(k,this.translate(a,null,null,c));a=c=Math.round(k+q);q=d=Math.round(r-k-q);F(k)?this.horiz?(q=m,d=r-this.bottom,a=c=w(a,B,B+this.width)):(a=B,c=n-this.right,q=d=w(q,m,m+this.height)):f=!0;return f&&!e?null:g.renderer.crispLine(["M", -a,q,"L",c,d],b||1)},getLinearTickPositions:function(a,b,c){var g,k=p(Math.floor(b/a)*a),e=p(Math.ceil(c/a)*a),B=[];if(b===c&&F(b))return[b];for(b=k;b<=e;){B.push(b);b=p(b+a);if(b===g)break;g=b}return B},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,k=[],e,m=this.pointRangePadding||0;e=this.min-m;var m=this.max+m,q=m-e;if(q&&q/c=this.minRange,q,d,r,f,n,w;this.isXAxis&&void 0===this.minRange&&!this.isLog&&(l(a.min)||l(a.max)?this.minRange=null:(c(this.series,function(a){f=a.xData;for(d=n=a.xIncrement? -1:f.length-1;0=J?(N=J,t=0):g.dataMax<=J&&(v=J,w=0)),g.min=A(C,N,g.dataMin),g.max=A(D,v,g.dataMax));q&&(!b&&0>= -Math.min(g.min,A(g.dataMin,g.min))&&a.error(10,1),g.min=p(d(g.min),15),g.max=p(d(g.max),15));g.range&&l(g.max)&&(g.userMin=g.min=C=Math.max(g.min,g.minFromRange()),g.userMax=D=g.max,g.range=null);y(g,"foundExtremes");g.beforePadding&&g.beforePadding();g.adjustForMinRange();!(K||g.axisPointRange||g.usePercentage||n)&&l(g.min)&&l(g.max)&&(d=g.max-g.min)&&(!l(C)&&t&&(g.min-=d*t),!l(D)&&w&&(g.max+=d*w));F(e.floor)?g.min=Math.max(g.min,e.floor):F(e.softMin)&&(g.min=Math.min(g.min,e.softMin));F(e.ceiling)? -g.max=Math.min(g.max,e.ceiling):F(e.softMax)&&(g.max=Math.max(g.max,e.softMax));u&&l(g.dataMin)&&(J=J||0,!l(C)&&g.min=J?g.min=J:!l(D)&&g.max>J&&g.dataMax<=J&&(g.max=J));g.tickInterval=g.min===g.max||void 0===g.min||void 0===g.max?1:n&&!z&&h===g.linkedParent.options.tickPixelInterval?z=g.linkedParent.tickInterval:A(z,this.tickAmount?(g.max-g.min)/Math.max(this.tickAmount-1,1):void 0,K?1:(g.max-g.min)*h/Math.max(g.len,h));f&&!b&&c(g.series,function(a){a.processData(g.min!==g.oldMin||g.max!== -g.oldMax)});g.setAxisTranslation(!0);g.beforeSetTickPositions&&g.beforeSetTickPositions();g.postProcessTickInterval&&(g.tickInterval=g.postProcessTickInterval(g.tickInterval));g.pointRange&&!z&&(g.tickInterval=Math.max(g.pointRange,g.tickInterval));b=A(e.minTickInterval,g.isDatetimeAxis&&g.closestPointRange);!z&&g.tickIntervalg.tickInterval&&1E3g.max)),!!this.tickAmount)); -this.tickAmount||(g.tickInterval=g.unsquish());this.setTickPositions()},setTickPositions:function(){var a=this.options,b,c=a.tickPositions,k=a.tickPositioner,e=a.startOnTick,m=a.endOnTick,q;this.tickmarkOffset=this.categories&&"between"===a.tickmarkPlacement&&1===this.tickInterval?.5:0;this.minorTickInterval="auto"===a.minorTickInterval&&this.tickInterval?this.tickInterval/5:a.minorTickInterval;this.tickPositions=b=c&&c.slice();!b&&(b=this.isDatetimeAxis?this.getTimeTicks(this.normalizeTimeTickInterval(this.tickInterval, -a.units),this.min,this.max,a.startOfWeek,this.ordinalPositions,this.closestPointRange,!0):this.isLog?this.getLogTickPositions(this.tickInterval,this.min,this.max):this.getLinearTickPositions(this.tickInterval,this.min,this.max),b.length>this.len&&(b=[b[0],b.pop()]),this.tickPositions=b,k&&(k=k.apply(this,[this.min,this.max])))&&(this.tickPositions=b=k);this.isLinked||(this.trimTicks(b,e,m),this.min===this.max&&l(this.min)&&!this.tickAmount&&(q=!0,this.min-=.5,this.max+=.5),this.single=q,c||k||this.adjustTickAmount())}, -trimTicks:function(a,b,c){var g=a[0],k=a[a.length-1],e=this.minPointOffset||0;if(b)this.min=g;else for(;this.min-e>a[0];)a.shift();if(c)this.max=k;else for(;this.max+eb&&(this.finalTickAmt=b,b=5);this.tickAmount=b},adjustTickAmount:function(){var a=this.tickInterval,b=this.tickPositions,c=this.tickAmount,k=this.finalTickAmt,e=b&&b.length;if(ec&&(this.tickInterval*=2,this.setTickPositions());if(l(k)){for(a=c=b.length;a--;)(3===k&&1===a%2||2>=k&&0k&&(a=k)),l(c)&&(bk&&(b=k))),this.displayBtn=void 0!==a||void 0!==b,this.setExtremes(a,b,!1,void 0,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,k=this.horiz,e=A(b.width,a.plotWidth-c+(b.offsetRight||0)),m=A(b.height,a.plotHeight),q=A(b.top,a.plotTop),b=A(b.left, -a.plotLeft+c),c=/%$/;c.test(m)&&(m=Math.round(parseFloat(m)/100*a.plotHeight));c.test(q)&&(q=Math.round(parseFloat(q)/100*a.plotHeight+a.plotTop));this.left=b;this.top=q;this.width=e;this.height=m;this.bottom=a.chartHeight-m-q;this.right=a.chartWidth-e-b;this.len=Math.max(k?e:m,0);this.pos=k?b:q},getExtremes:function(){var a=this.isLog,b=this.lin2log;return{min:a?p(b(this.min)):this.min,max:a?p(b(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}}, -getThreshold:function(a){var b=this.isLog,g=this.lin2log,c=b?g(this.min):this.min,b=b?g(this.max):this.max;null===a?a=c:c>a?a=c:ba?"right":195a?"left":"center"},tickSize:function(a){var b=this.options,g=b[a+"Length"],c=A(b[a+"Width"],"tick"===a&&this.isXAxis?1:0);if(c&&g)return"inside"===b[a+"Position"]&&(g=-g),[g,c]},labelMetrics:function(){return this.chart.renderer.fontMetrics(this.options.labels.style&& -this.options.labels.style.fontSize,this.ticks[0]&&this.ticks[0].label)},unsquish:function(){var a=this.options.labels,b=this.horiz,k=this.tickInterval,e=k,m=this.len/(((this.categories?1:0)+this.max-this.min)/k),q,d=a.rotation,r=this.labelMetrics(),f,n=Number.MAX_VALUE,w,t=function(a){a/=m||1;a=1=a)f=t(Math.abs(r.h/Math.sin(u*a))),b=f+ -Math.abs(a/360),b(c.step||0)&&!c.rotation&&(this.staggerLines||1)*a.plotWidth/k||!b&&(e&&e-a.spacing[3]||.33*a.chartWidth)},renderUnsquish:function(){var a=this.chart,b=a.renderer,k=this.tickPositions,m=this.ticks,q=this.options.labels,d=this.horiz, -f=this.getSlotWidth(),n=Math.max(1,Math.round(f-2*(q.padding||5))),w={},t=this.labelMetrics(),z=q.style&&q.style.textOverflow,h,l=0,x,F;e(q.rotation)||(w.rotation=q.rotation||0);c(k,function(a){(a=m[a])&&a.labelLength>l&&(l=a.labelLength)});this.maxLabelLength=l;if(this.autoRotation)l>n&&l>t.h?w.rotation=this.labelRotation:this.labelRotation=0;else if(f&&(h={width:n+"px"},!z))for(h.textOverflow="clip",x=k.length;!d&&x--;)if(F=k[x],n=m[F].label)n.styles&&"ellipsis"===n.styles.textOverflow?n.css({textOverflow:"clip"}): -m[F].labelLength>f&&n.css({width:f+"px"}),n.getBBox().height>this.len/k.length-(t.h-t.f)&&(n.specCss={textOverflow:"ellipsis"});w.rotation&&(h={width:(l>.5*a.chartHeight?.33*a.chartHeight:a.chartHeight)+"px"},z||(h.textOverflow="ellipsis"));if(this.labelAlign=q.align||this.autoLabelAlign(this.labelRotation))w.align=this.labelAlign;c(k,function(a){var b=(a=m[a])&&a.label;b&&(b.attr(w),h&&b.css(r(h,b.specCss)),delete b.specCss,a.rotation=w.rotation)});this.tickRotCorr=b.rotCorr(t.b,this.labelRotation|| -0,0!==this.side)},hasData:function(){return this.hasVisibleSeries||l(this.min)&&l(this.max)&&!!this.tickPositions},addTitle:function(a){var b=this.chart.renderer,g=this.horiz,c=this.opposite,k=this.options.title,e;this.axisTitle||((e=k.textAlign)||(e=(g?{low:"left",middle:"center",high:"right"}:{low:c?"right":"left",middle:"center",high:c?"left":"right"})[k.align]),this.axisTitle=b.text(k.text,0,0,k.useHTML).attr({zIndex:7,rotation:k.rotation||0,align:e}).addClass("highcharts-axis-title").css(k.style).add(this.axisGroup), -this.axisTitle.isNew=!0);this.axisTitle[a?"show":"hide"](!0)},getOffset:function(){var a=this,b=a.chart,k=b.renderer,e=a.options,m=a.tickPositions,q=a.ticks,d=a.horiz,r=a.side,n=b.inverted?[1,0,3,2][r]:r,w,f,t=0,z,h=0,x=e.title,F=e.labels,p=0,K=b.axisOffset,b=b.clipOffset,J=[-1,1,1,-1][r],u,y=e.className,v=a.axisParent,C=this.tickSize("tick");w=a.hasData();a.showAxis=f=w||A(e.showEmpty,!0);a.staggerLines=a.horiz&&F.staggerLines;a.axisGroup||(a.gridGroup=k.g("grid").attr({zIndex:e.gridZIndex||1}).addClass("highcharts-"+ -this.coll.toLowerCase()+"-grid "+(y||"")).add(v),a.axisGroup=k.g("axis").attr({zIndex:e.zIndex||2}).addClass("highcharts-"+this.coll.toLowerCase()+" "+(y||"")).add(v),a.labelGroup=k.g("axis-labels").attr({zIndex:F.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels "+(y||"")).add(v));if(w||a.isLinked)c(m,function(b){q[b]?q[b].addLabel():q[b]=new N(a,b)}),a.renderUnsquish(),!1===F.reserveSpace||0!==r&&2!==r&&{1:"left",3:"right"}[r]!==a.labelAlign&&"center"!==a.labelAlign||c(m,function(a){p= -Math.max(q[a].getLabelSize(),p)}),a.staggerLines&&(p*=a.staggerLines,a.labelOffset=p*(a.opposite?-1:1));else for(u in q)q[u].destroy(),delete q[u];x&&x.text&&!1!==x.enabled&&(a.addTitle(f),f&&(t=a.axisTitle.getBBox()[d?"height":"width"],z=x.offset,h=l(z)?0:A(x.margin,d?5:10)));a.renderLine();a.offset=J*A(e.offset,K[r]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};k=0===r?-a.labelMetrics().h:2===r?a.tickRotCorr.y:0;h=Math.abs(p)+h;p&&(h=h-k+J*(d?A(F.y,a.tickRotCorr.y+8*J):F.x));a.axisTitleMargin=A(z,h); -K[r]=Math.max(K[r],a.axisTitleMargin+t+J*a.offset,h,w&&m.length&&C?C[0]:0);e=e.offset?0:2*Math.floor(a.axisLine.strokeWidth()/2);b[n]=Math.max(b[n],e)},getLinePath:function(a){var b=this.chart,c=this.opposite,g=this.offset,k=this.horiz,e=this.left+(c?this.width:0)+g,g=b.chartHeight-this.bottom-(c?this.height:0)+g;c&&(a*=-1);return b.renderer.crispLine(["M",k?this.left:e,k?g:this.top,"L",k?b.chartWidth-this.right:e,k?g:b.chartHeight-this.bottom],a)},renderLine:function(){this.axisLine||(this.axisLine= -this.chart.renderer.path().addClass("highcharts-axis-line").add(this.axisGroup),this.axisLine.attr({stroke:this.options.lineColor,"stroke-width":this.options.lineWidth,zIndex:7}))},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,k=this.len,e=this.options.title,m=a?b:c,q=this.opposite,d=this.offset,r=e.x||0,n=e.y||0,w=this.chart.renderer.fontMetrics(e.style&&e.style.fontSize,this.axisTitle).f,k={low:m+(a?0:k),middle:m+k/2,high:m+(a?k:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)* -(q?-1:1)*this.axisTitleMargin+(2===this.side?w:0);return{x:a?k+r:b+(q?this.width:0)+d+r,y:a?b+n-(q?this.height:0)+d:k+n}},render:function(){var a=this,b=a.chart,e=b.renderer,m=a.options,q=a.isLog,d=a.lin2log,r=a.isLinked,n=a.tickPositions,w=a.axisTitle,f=a.ticks,t=a.minorTicks,z=a.alternateBands,h=m.stackLabels,l=m.alternateGridColor,x=a.tickmarkOffset,p=a.axisLine,A=b.hasRendered&&F(a.oldMin),K=a.showAxis,u=C(e.globalAnimation),y,v;a.labelEdge.length=0;a.overlap=!1;c([f,t,z],function(a){for(var b in a)a[b].isActive= -!1});if(a.hasData()||r)a.minorTickInterval&&!a.categories&&c(a.getMinorTickPositions(),function(b){t[b]||(t[b]=new N(a,b,"minor"));A&&t[b].isNew&&t[b].render(null,!0);t[b].render(null,!1,1)}),n.length&&(c(n,function(b,c){if(!r||b>=a.min&&b<=a.max)f[b]||(f[b]=new N(a,b)),A&&f[b].isNew&&f[b].render(c,!0,.1),f[b].render(c)}),x&&(0===a.min||a.single)&&(f[-1]||(f[-1]=new N(a,-1,null,!0)),f[-1].render(-1))),l&&c(n,function(c,g){v=void 0!==n[g+1]?n[g+1]+x:a.max-x;0===g%2&&c=c.second?0:A*Math.floor(e.getMilliseconds()/A));if(x>=c.second)e[C.hcSetSeconds](x>=c.minute?0:A*Math.floor(e.getSeconds()/A));if(x>=c.minute)e[C.hcSetMinutes](x>=c.hour?0:A*Math.floor(e[C.hcGetMinutes]()/A));if(x>=c.hour)e[C.hcSetHours](x>=c.day?0:A*Math.floor(e[C.hcGetHours]()/A));if(x>=c.day)e[C.hcSetDate](x>=c.month?1:A*Math.floor(e[C.hcGetDate]()/A));x>=c.month&&(e[C.hcSetMonth](x>=c.year?0:A*Math.floor(e[C.hcGetMonth]()/A)),F=e[C.hcGetFullYear]()); -if(x>=c.year)e[C.hcSetFullYear](F-F%A);if(x===c.week)e[C.hcSetDate](e[C.hcGetDate]()-e[C.hcGetDay]()+d(m,1));F=e[C.hcGetFullYear]();m=e[C.hcGetMonth]();var w=e[C.hcGetDate](),K=e[C.hcGetHours]();if(C.hcTimezoneOffset||C.hcGetTimezoneOffset)k=(!n||!!C.hcGetTimezoneOffset)&&(t-y>4*c.month||l(y)!==l(t)),e=e.getTime(),e=new C(e+l(e));n=e.getTime();for(y=1;np&&(!l||q<=v)&&void 0!==q&&y.push(q),q>v&&(z=!0),q=b;else p=c(p),v=c(v),a=f[l?"minorTickInterval":"tickInterval"],a=h("auto"===a?null:a,this._minorAutoInterval,f.tickPixelInterval/(l?5:1)*(v-p)/((l?d/this.tickPositions.length:d)||1)),a=I(a,null,C(a)),y=G(this.getLinearTickPositions(a,p,v),n),l||(this._minorAutoInterval=a/5);l||(this.tickInterval=a);return y};D.prototype.log2lin= -function(a){return Math.log(a)/Math.LN10};D.prototype.lin2log=function(a){return Math.pow(10,a)}})(L);(function(a){var D=a.dateFormat,C=a.each,G=a.extend,I=a.format,h=a.isNumber,f=a.map,p=a.merge,v=a.pick,l=a.splat,u=a.syncTimeout,d=a.timeUnits;a.Tooltip=function(){this.init.apply(this,arguments)};a.Tooltip.prototype={init:function(a,d){this.chart=a;this.options=d;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.split=d.split&&!a.inverted;this.shared=d.shared||this.split},cleanSplit:function(a){C(this.chart.series, -function(c){var d=c&&c.tt;d&&(!d.isActive||a?c.tt=d.destroy():d.isActive=!1)})},getLabel:function(){var a=this.chart.renderer,d=this.options;this.label||(this.split?this.label=a.g("tooltip"):(this.label=a.label("",0,0,d.shape||"callout",null,null,d.useHTML,null,"tooltip").attr({padding:d.padding,r:d.borderRadius}),this.label.attr({fill:d.backgroundColor,"stroke-width":d.borderWidth}).css(d.style).shadow(d.shadow)),this.label.attr({zIndex:8}).add());return this.label},update:function(a){this.destroy(); -this.init(this.chart,p(!0,this.options,a))},destroy:function(){this.label&&(this.label=this.label.destroy());this.split&&this.tt&&(this.cleanSplit(this.chart,!0),this.tt=this.tt.destroy());clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,d,f,t){var c=this,b=c.now,q=!1!==c.options.animation&&!c.isHidden&&(1n-q?n:n-q);else if(w)b[a]=Math.max(e,g+q+k>c?g:g+q);else return!1},p=function(a,c,k,g){var e;gc-m?e=!1:b[a]=gc-k/2?c-k-2:g-k/2;return e},k=function(a){var b=h;h=e;e=b;n=a},w=function(){!1!==l.apply(0,h)?!1!==p.apply(0,e)||n||(k(!0),w()):n?b.x=b.y=0:(k(!0), -w())};(c.inverted||1w&&(q=!1);a=(c.series&&c.series.yAxis&&c.series.yAxis.pos)+(c.plotY||0);a-=m.plotTop;n.push({target:c.isHeader? -m.plotHeight+l:a,rank:c.isHeader?1:0,size:r.tt.getBBox().height+1,point:c,x:w,tt:k})});this.cleanSplit();a.distribute(n,m.plotHeight+l);C(n,function(a){var b=a.point,c=b.series;a.tt.attr({visibility:void 0===a.pos?"hidden":"inherit",x:q||b.isHeader?a.x:b.plotX+m.plotLeft+v(h.distance,16),y:a.pos+m.plotTop,anchorX:b.isHeader?b.plotX+m.plotLeft:b.plotX+c.xAxis.pos,anchorY:b.isHeader?a.pos+m.plotTop-15:b.plotY+c.yAxis.pos})})},updatePosition:function(a){var c=this.chart,d=this.getLabel(),d=(this.options.positioner|| -this.getPosition).call(this,d.width,d.height,a);this.move(Math.round(d.x),Math.round(d.y||0),a.plotX+c.plotLeft,a.plotY+c.plotTop)},getXDateFormat:function(a,f,h){var c;f=f.dateTimeLabelFormats;var m=h&&h.closestPointRange,b,q={millisecond:15,second:12,minute:9,hour:6,day:3},n,l="millisecond";if(m){n=D("%m-%d %H:%M:%S.%L",a.x);for(b in d){if(m===d.week&&+D("%w",a.x)===h.options.startOfWeek&&"00:00:00.000"===n.substr(6)){b="week";break}if(d[b]>m){b=l;break}if(q[b]&&n.substr(q[b])!=="01-01 00:00:00.000".substr(q[b]))break; -"week"!==b&&(l=b)}b&&(c=f[b])}else c=f.day;return c||f.year},tooltipFooterHeaderFormatter:function(a,d){var c=d?"footer":"header";d=a.series;var f=d.tooltipOptions,m=f.xDateFormat,b=d.xAxis,q=b&&"datetime"===b.options.type&&h(a.key),c=f[c+"Format"];q&&!m&&(m=this.getXDateFormat(a,f,b));q&&m&&(c=c.replace("{point.key}","{point.key:"+m+"}"));return I(c,{point:a,series:d})},bodyFormatter:function(a){return f(a,function(a){var c=a.series.tooltipOptions;return(c.pointFormatter||a.point.tooltipFormatter).call(a.point, -c.pointFormat)})}}})(L);(function(a){var D=a.addEvent,C=a.attr,G=a.charts,I=a.color,h=a.css,f=a.defined,p=a.doc,v=a.each,l=a.extend,u=a.fireEvent,d=a.offset,c=a.pick,n=a.removeEvent,y=a.splat,t=a.Tooltip,m=a.win;a.Pointer=function(a,c){this.init(a,c)};a.Pointer.prototype={init:function(a,m){this.options=m;this.chart=a;this.runChartClick=m.chart.events&&!!m.chart.events.click;this.pinchDown=[];this.lastValidTouch={};t&&m.tooltip.enabled&&(a.tooltip=new t(a,m.tooltip),this.followTouchMove=c(m.tooltip.followTouchMove, -!0));this.setDOMEvents()},zoomOption:function(a){var b=this.chart,m=b.options.chart,d=m.zoomType||"",b=b.inverted;/touch/.test(a.type)&&(d=c(m.pinchType,d));this.zoomX=a=/x/.test(d);this.zoomY=d=/y/.test(d);this.zoomHor=a&&!b||d&&b;this.zoomVert=d&&!b||a&&b;this.hasZoom=a||d},normalize:function(a,c){var b,q;a=a||m.event;a.target||(a.target=a.srcElement);q=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;c||(this.chartPosition=c=d(this.chart.container));void 0===q.pageX?(b=Math.max(a.x, -a.clientX-c.left),c=a.y):(b=q.pageX-c.left,c=q.pageY-c.top);return l(a,{chartX:Math.round(b),chartY:Math.round(c)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};v(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},runPointActions:function(b){var m=this.chart,d=m.series,f=m.tooltip,e=f?f.shared:!1,r=!0,n=m.hoverPoint,h=m.hoverSeries,k,w,l,t=[],u;if(!e&&!h)for(k=0;kb.series.index?-1:1}));if(e)for(k=t.length;k--;)(t[k].x!==t[0].x||t[k].series.noSharedTooltip)&& -t.splice(k,1);if(t[0]&&(t[0]!==this.prevKDPoint||f&&f.isHidden)){if(e&&!t[0].series.noSharedTooltip){for(k=0;kn+w&&(m=n+w),ek+h&&(e=k+h),this.hasDragged=Math.sqrt(Math.pow(g-m,2)+Math.pow(p-e,2)),10k.max&&(f=k.max-e,B=!0);B?(J-=.8*(J-n[m][0]),w||(g-=.8*(g-n[m][1])),h()):n[m]=[J, -g];A||(c[m]=r-p,c[l]=e);c=A?1/x:x;d[l]=e;d[m]=f;u[A?a?"scaleY":"scaleX":"scale"+b]=x;u["translate"+b]=c*p+(J-c*K)},pinch:function(a){var p=this,l=p.chart,u=p.pinchDown,d=a.touches,c=d.length,n=p.lastValidTouch,y=p.hasZoom,t=p.selectionMarker,m={},b=1===c&&(p.inClass(a.target,"highcharts-tracker")&&l.runTrackerClick||p.runChartClick),q={};1c-6&&h(p||b.chartWidth-2*k-B-m.x)&&(this.itemX=B,this.itemY+=g+this.lastLineHeight+y,this.lastLineHeight=0);this.maxItemWidth=Math.max(this.maxItemWidth,e);this.lastItemY=g+this.itemY+y;this.lastLineHeight=Math.max(h,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];f?this.itemX+=e: -(this.itemY+=g+h+y,this.lastLineHeight=h);this.offsetWidth=p||Math.max((f?this.itemX-B-w:e)+k,this.offsetWidth)},getAllItems:function(){var a=[];f(this.chart.series,function(b){var c=b&&b.options;b&&d(c.showInLegend,h(c.linkedTo)?!1:void 0,!0)&&(a=a.concat(b.legendItems||("point"===c.legendType?b.data:b)))});return a},adjustMargins:function(a,b){var c=this.chart,m=this.options,n=m.align.charAt(0)+m.verticalAlign.charAt(0)+m.layout.charAt(0);m.floating||f([/(lth|ct|rth)/,/(rtv|rm|rbv)/,/(rbh|cb|lbh)/, -/(lbv|lm|ltv)/],function(e,f){e.test(n)&&!h(a[f])&&(c[l[f]]=Math.max(c[l[f]],c.legend[(f+1)%2?"legendHeight":"legendWidth"]+[1,-1,-1,1][f]*m[f%2?"x":"y"]+d(m.margin,12)+b[f]))})},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,h,e,r,l,t=a.box,k=a.options,w=a.padding;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;d||(a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup));a.renderTitle(); -h=a.getAllItems();n(h,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});k.reversed&&h.reverse();a.allItems=h;a.display=e=!!h.length;a.lastLineHeight=0;f(h,function(b){a.renderItem(b)});r=(k.width||a.offsetWidth)+w;l=a.lastItemY+a.lastLineHeight+a.titleHeight;l=a.handleOverflow(l);l+=w;t||(a.box=t=c.rect().addClass("highcharts-legend-box").attr({r:k.borderRadius}).add(d),t.isNew=!0);t.attr({stroke:k.borderColor,"stroke-width":k.borderWidth||0,fill:k.backgroundColor|| -"none"}).shadow(k.shadow);0c&&!1!==t.enabled?(this.clipHeight=n=Math.max(c-20-this.titleHeight-y,0),this.currentPage=d(this.currentPage,1),this.fullHeight= -a,f(B,function(a,b){var c=a._legendItemPos[1];a=Math.round(a.legendItem.getBBox().height);var k=u.length;if(!k||c-u[k-1]>n&&(g||c)!==u[k-1])u.push(g||c),k++;b===B.length-1&&c+a-u[k-1]>n&&u.push(c);c!==g&&(g=c)}),l||(l=b.clipRect=m.clipRect(0,y,9999,0),b.contentGroup.clip(l)),v(n),p||(this.nav=p=m.g().attr({zIndex:1}).add(this.group),this.up=m.symbol("triangle",0,0,w,w).on("click",function(){b.scroll(-1,k)}).add(p),this.pager=m.text("",15,10).addClass("highcharts-legend-navigation").css(t.style).add(p), -this.down=m.symbol("triangle-down",0,0,w,w).on("click",function(){b.scroll(1,k)}).add(p)),b.scroll(0),a=c):p&&(v(),p.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0);return a},scroll:function(a,b){var d=this.pages,f=d.length;a=this.currentPage+a;var m=this.clipHeight,e=this.options.navigation,h=this.pager,n=this.padding;a>f&&(a=f);0m&&(d=typeof a[0],"string"===d?c.name=a[0]:"number"===d&&(c.x=a[0]),b++);l=h.value;)h=c[++f];h&&h.color&&!this.options.color&&(this.color=h.color);return h},destroy:function(){var a=this.series.chart,c=a.hoverPoints,f;a.pointCount--;c&&(this.setState(),I(c,this),c.length||(a.hoverPoints=null));if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)u(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(f in this)this[f]=null},destroyElements:function(){for(var a= -["graphic","dataLabel","dataLabelUpper","connector","shadowGroup"],c,f=6;f--;)c=a[f],this[c]&&(this[c]=this[c].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,color:this.color,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},tooltipFormatter:function(a){var c=this.series,d=c.tooltipOptions,h=l(d.valueDecimals,""),t=d.valuePrefix||"",m=d.valueSuffix||"";C(c.pointArrayMap||["y"],function(b){b="{point."+b; -if(t||m)a=a.replace(b+"}",t+b+"}"+m);a=a.replace(b+"}",b+":,."+h+"f}")});return f(a,{point:this,series:this.series})},firePointEvent:function(a,c,f){var d=this,n=this.series.options;(n.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();"click"===a&&n.allowPointSelect&&(f=function(a){d.select&&d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});h(this,a,c,f)},visible:!0}})(L);(function(a){var D=a.addEvent,C=a.animObject,G=a.arrayMax,I=a.arrayMin,h=a.correctFloat, -f=a.Date,p=a.defaultOptions,v=a.defaultPlotOptions,l=a.defined,u=a.each,d=a.erase,c=a.extend,n=a.fireEvent,y=a.grep,t=a.isArray,m=a.isNumber,b=a.isString,q=a.merge,z=a.pick,F=a.removeEvent,e=a.splat,r=a.SVGElement,x=a.syncTimeout,A=a.win;a.Series=a.seriesType("line",null,{lineWidth:2,allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},marker:{lineWidth:0,lineColor:"#ffffff",radius:4,states:{hover:{animation:{duration:50},enabled:!0,radiusPlus:2,lineWidthPlus:1},select:{fillColor:"#cccccc", -lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:{align:"center",formatter:function(){return null===this.y?"":a.numberFormat(this.y,-1)},style:{fontSize:"11px",fontWeight:"bold",color:"contrast",textOutline:"1px contrast"},verticalAlign:"bottom",x:0,y:0,padding:5},cropThreshold:300,pointRange:0,softThreshold:!0,states:{hover:{lineWidthPlus:1,marker:{},halo:{size:10,opacity:.25}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3},{isCartesian:!0,pointClass:a.Point,sorted:!0,requireSorting:!0, -directTouch:!1,axisTypes:["xAxis","yAxis"],colorCounter:0,parallelArrays:["x","y"],coll:"series",init:function(a,b){var k=this,e,d,g=a.series,f;k.chart=a;k.options=b=k.setOptions(b);k.linkedSeries=[];k.bindAxes();c(k,{name:b.name,state:"",visible:!1!==b.visible,selected:!0===b.selected});d=b.events;for(e in d)D(k,e,d[e]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;k.getColor();k.getSymbol();u(k.parallelArrays,function(a){k[a+"Data"]=[]});k.setData(b.data, -!1);k.isCartesian&&(a.hasCartesianSeries=!0);g.length&&(f=g[g.length-1]);k._i=z(f&&f._i,-1)+1;for(a=this.insert(g);a=z(a[c].options.index,a[c]._i)){a.splice(c+1,0,this);break}-1===c&&a.unshift(this);c+=1}else a.push(this);return z(c,a.length-1)},bindAxes:function(){var b=this,c=b.options,e=b.chart,d;u(b.axisTypes||[],function(k){u(e[k],function(a){d= -a.options;if(c[k]===d.index||void 0!==c[k]&&c[k]===d.id||void 0===c[k]&&0===d.index)b.insert(a.series),b[k]=a,a.isDirty=!0});b[k]||b.optionalAxis===k||a.error(18,!0)})},updateParallelArrays:function(a,b){var c=a.series,k=arguments,e=m(b)?function(g){var k="y"===g&&c.toYData?c.toYData(a):a[g];c[g+"Data"][b]=k}:function(a){Array.prototype[b].apply(c[a+"Data"],Array.prototype.slice.call(k,2))};u(c.parallelArrays,e)},autoIncrement:function(){var a=this.options,b=this.xIncrement,c,e=a.pointIntervalUnit, -b=z(b,a.pointStart,0);this.pointInterval=c=z(this.pointInterval,a.pointInterval,1);e&&(a=new f(b),"day"===e?a=+a[f.hcSetDate](a[f.hcGetDate]()+c):"month"===e?a=+a[f.hcSetMonth](a[f.hcGetMonth]()+c):"year"===e&&(a=+a[f.hcSetFullYear](a[f.hcGetFullYear]()+c)),c=a-b);this.xIncrement=b+c;return b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},k=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=q(e,c.series,a);this.tooltipOptions=q(p.tooltip,p.plotOptions[this.type].tooltip, -b.tooltip,k.series&&k.series.tooltip,k[this.type]&&k[this.type].tooltip,a.tooltip);null===e.marker&&delete c.marker;this.zoneAxis=c.zoneAxis;a=this.zones=(c.zones||[]).slice();!c.negativeColor&&!c.negativeFillColor||c.zones||a.push({value:c[this.zoneAxis+"Threshold"]||c.threshold||0,className:"highcharts-negative",color:c.negativeColor,fillColor:c.negativeFillColor});a.length&&l(a[a.length-1].value)&&a.push({color:this.color,fillColor:this.fillColor});return c},getCyclic:function(a,b,c){var k,e=this.userOptions, -g=a+"Index",d=a+"Counter",f=c?c.length:z(this.chart.options.chart[a+"Count"],this.chart[a+"Count"]);b||(k=z(e[g],e["_"+g]),l(k)||(e["_"+g]=k=this.chart[d]%f,this.chart[d]+=1),c&&(b=c[k]));void 0!==k&&(this[g]=k);this[a]=b},getColor:function(){this.options.colorByPoint?this.options.color=null:this.getCyclic("color",this.options.color||v[this.type].color,this.chart.options.colors)},getSymbol:function(){this.getCyclic("symbol",this.options.marker.symbol,this.chart.options.symbols)},drawLegendSymbol:a.LegendSymbolMixin.drawLineMarker, -setData:function(c,e,d,f){var k=this,g=k.points,h=g&&g.length||0,n,r=k.options,w=k.chart,l=null,q=k.xAxis,p=r.turboThreshold,x=this.xData,A=this.yData,F=(n=k.pointArrayMap)&&n.length;c=c||[];n=c.length;e=z(e,!0);if(!1!==f&&n&&h===n&&!k.cropped&&!k.hasGroupedData&&k.visible)u(c,function(a,b){g[b].update&&a!==r.data[b]&&g[b].update(a,!1,null,!1)});else{k.xIncrement=null;k.colorCounter=0;u(this.parallelArrays,function(a){k[a+"Data"].length=0});if(p&&n>p){for(d=0;null===l&&dh||this.forceCrop))if(c[e-1]p)c=[], -k=[];else if(c[0]p)d=this.cropData(this.xData,this.yData,t,p),c=d.xData,k=d.yData,d=d.start,g=!0;for(h=c.length||1;--h;)e=q?n(c[h])-n(c[h-1]):c[h]-c[h-1],0e&&this.requireSorting&&a.error(15);this.cropped=g;this.cropStart=d;this.processedXData=c;this.processedYData=k;this.closestPointRange=f},cropData:function(a,b,c,e){var k=a.length,g=0,d=k,f=z(this.cropShoulder,1),h;for(h=0;h=c){g=Math.max(0,h-f);break}for(c=h;ce){d=c+f;break}return{xData:a.slice(g, -d),yData:b.slice(g,d),start:g,end:d}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,f=this.processedYData,g=this.pointClass,h=d.length,m=this.cropStart||0,n,r=this.hasGroupedData,l,q=[],t;b||r||(b=[],b.length=a.length,b=this.data=b);for(t=0;t=d&&(c[r-1]||n)<=f,h&&n)if(h=l.length)for(;h--;)null!==l[h]&&(k[g++]= -l[h]);else k[g++]=l;this.dataMin=I(k);this.dataMax=G(k)},translate:function(){this.processedXData||this.processData();this.generatePoints();var a=this.options,b=a.stacking,c=this.xAxis,e=c.categories,d=this.yAxis,g=this.points,f=g.length,n=!!this.modifyValue,r=a.pointPlacement,t="between"===r||m(r),q=a.threshold,p=a.startFromThreshold?q:0,x,u,A,F,v=Number.MAX_VALUE;"between"===r&&(r=.5);m(r)&&(r*=z(a.pointRange||c.pointRange));for(a=0;a=D&&(y.isNull=!0);y.plotX=x=h(Math.min(Math.max(-1E5,c.translate(C,0,0,0,1,r,"flags"===this.type)),1E5));b&&this.visible&&!y.isNull&&G&&G[C]&&(F=this.getStackIndicator(F,C,this.index),I=G[C],D=I.points[F.key],u=D[0],D=D[1],u===p&&F.key===G[C].base&&(u=z(q,d.min)),d.isLog&&0>=u&&(u=null),y.total=y.stackTotal=I.total,y.percentage=I.total&&y.y/I.total*100,y.stackY=D,I.setOffset(this.pointXOffset||0,this.barW||0));y.yBottom=l(u)?d.translate(u,0, -1,0,1):null;n&&(D=this.modifyValue(D,y));y.plotY=u="number"===typeof D&&Infinity!==D?Math.min(Math.max(-1E5,d.translate(D,0,1,0,1)),1E5):void 0;y.isInside=void 0!==u&&0<=u&&u<=d.len&&0<=x&&x<=c.len;y.clientX=t?h(c.translate(C,0,0,0,1,r)):x;y.negative=y.y<(q||0);y.category=e&&void 0!==e[y.x]?e[y.x]:y.x;y.isNull||(void 0!==A&&(v=Math.min(v,Math.abs(x-A))),A=x);y.zone=this.zones.length&&y.getZone()}this.closestPointRangePx=v},getValidPoints:function(a,b){var c=this.chart;return y(a||this.points||[], -function(a){return b&&!c.isInsidePlot(a.plotX,a.plotY,c.inverted)?!1:!a.isNull})},setClip:function(a){var b=this.chart,c=this.options,e=b.renderer,k=b.inverted,g=this.clipBox,d=g||b.clipBox,f=this.sharedClipKey||["_sharedClip",a&&a.duration,a&&a.easing,d.height,c.xAxis,c.yAxis].join(),h=b[f],m=b[f+"m"];h||(a&&(d.width=0,b[f+"m"]=m=e.clipRect(-99,k?-b.plotLeft:-b.plotTop,99,k?b.chartWidth:b.chartHeight)),b[f]=h=e.clipRect(d),h.count={length:0});a&&!h.count[this.index]&&(h.count[this.index]=!0,h.count.length+= -1);!1!==c.clip&&(this.group.clip(a||g?h:b.clipRect),this.markerGroup.clip(m),this.sharedClipKey=f);a||(h.count[this.index]&&(delete h.count[this.index],--h.count.length),0===h.count.length&&f&&b[f]&&(g||(b[f]=b[f].destroy()),b[f+"m"]&&(b[f+"m"]=b[f+"m"].destroy())))},animate:function(a){var b=this.chart,c=C(this.options.animation),e;a?this.setClip(c):(e=this.sharedClipKey,(a=b[e])&&a.animate({width:b.plotSizeX},c),b[e+"m"]&&b[e+"m"].animate({width:b.plotSizeX+99},c),this.animate=null)},afterAnimate:function(){this.setClip(); -n(this,"afterAnimate")},drawPoints:function(){var a=this.points,b=this.chart,c,e,d,g,f=this.options.marker,h,n,r,l,t=this.markerGroup,q=z(f.enabled,this.xAxis.isRadial?!0:null,this.closestPointRangePx>2*f.radius);if(!1!==f.enabled||this._hasPointMarkers)for(e=a.length;e--;)d=a[e],c=d.plotY,g=d.graphic,h=d.marker||{},n=!!d.marker,r=q&&void 0===h.enabled||h.enabled,l=d.isInside,r&&m(c)&&null!==d.y?(c=z(h.symbol,this.symbol),d.hasImage=0===c.indexOf("url"),r=this.markerAttribs(d,d.selected&&"select"), -g?g[l?"show":"hide"](!0).animate(r):l&&(0g&&b.shadow));d&&(d.startX=c.xMap,d.isArea=c.isArea)})},applyZones:function(){var a=this,b=this.chart,c=b.renderer,e=this.zones,d,g,f=this.clips||[], -h,m=this.graph,n=this.area,r=Math.max(b.chartWidth,b.chartHeight),l=this[(this.zoneAxis||"y")+"Axis"],q,t,p=b.inverted,x,A,F,y,v=!1;e.length&&(m||n)&&l&&void 0!==l.min&&(t=l.reversed,x=l.horiz,m&&m.hide(),n&&n.hide(),q=l.getExtremes(),u(e,function(e,k){d=t?x?b.plotWidth:0:x?0:l.toPixels(q.min);d=Math.min(Math.max(z(g,d),0),r);g=Math.min(Math.max(Math.round(l.toPixels(z(e.value,q.max),!0)),0),r);v&&(d=g=l.toPixels(q.max));A=Math.abs(d-g);F=Math.min(d,g);y=Math.max(d,g);l.isXAxis?(h={x:p?y:F,y:0,width:A, -height:r},x||(h.x=b.plotHeight-h.x)):(h={x:0,y:p?y:F,width:r,height:A},x&&(h.y=b.plotWidth-h.y));p&&c.isVML&&(h=l.isXAxis?{x:0,y:t?F:y,height:h.width,width:b.chartWidth}:{x:h.y-b.plotLeft-b.spacingBox.x,y:0,width:h.height,height:b.chartHeight});f[k]?f[k].animate(h):(f[k]=c.clipRect(h),m&&a["zone-graph-"+k].clip(f[k]),n&&a["zone-area-"+k].clip(f[k]));v=e.value>q.max}),this.clips=f)},invertGroups:function(a){function b(){var b={width:c.yAxis.len,height:c.xAxis.len};u(["group","markerGroup"],function(e){c[e]&& -c[e].attr(b).invert(a)})}var c=this,e;c.xAxis&&(e=D(c.chart,"resize",b),D(c,"destroy",e),b(a),c.invertGroups=b)},plotGroup:function(a,b,c,e,d){var g=this[a],k=!g;k&&(this[a]=g=this.chart.renderer.g(b).attr({zIndex:e||.1}).add(d),g.addClass("highcharts-series-"+this.index+" highcharts-"+this.type+"-series highcharts-color-"+this.colorIndex+" "+(this.options.className||"")));g.attr({visibility:c})[k?"attr":"animate"](this.getPlotBox());return g},getPlotBox:function(){var a=this.chart,b=this.xAxis,c= -this.yAxis;a.inverted&&(b=c,c=this.xAxis);return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,e=a.options,d=!!a.animate&&b.renderer.isSVG&&C(e.animation).duration,g=a.visible?"inherit":"hidden",f=e.zIndex,h=a.hasRendered,m=b.seriesGroup,n=b.inverted;c=a.plotGroup("group","series",g,f,m);a.markerGroup=a.plotGroup("markerGroup","markers",g,f,m);d&&a.animate(!0);c.inverted=a.isCartesian?n:!1;a.drawGraph&&(a.drawGraph(),a.applyZones()); -a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&!1!==a.options.enableMouseTracking&&a.drawTracker();a.invertGroups(n);!1===e.clip||a.sharedClipKey||h||c.clip(b.clipRect);d&&a.animate();h||(a.animationTimeout=x(function(){a.afterAnimate()},d));a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirty||this.isDirtyData,c=this.group,e=this.xAxis,d=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:z(e&& -e.left,a.plotLeft),translateY:z(d&&d.top,a.plotTop)}));this.translate();this.render();b&&delete this.kdTree},kdDimensions:1,kdAxisArray:["clientX","plotY"],searchPoint:function(a,b){var c=this.xAxis,e=this.yAxis,d=this.chart.inverted;return this.searchKDTree({clientX:d?c.len-a.chartY+c.pos:a.chartX-c.pos,plotY:d?e.len-a.chartX+e.pos:a.chartY-e.pos},b)},buildKDTree:function(){function a(c,e,g){var d,k;if(k=c&&c.length)return d=b.kdAxisArray[e%g],c.sort(function(a,b){return a[d]-b[d]}),k=Math.floor(k/ -2),{point:c[k],left:a(c.slice(0,k),e+1,g),right:a(c.slice(k+1),e+1,g)}}var b=this,c=b.kdDimensions;delete b.kdTree;x(function(){b.kdTree=a(b.getValidPoints(null,!b.directTouch),c,c)},b.options.kdNow?0:1)},searchKDTree:function(a,b){function c(a,b,f,h){var m=b.point,n=e.kdAxisArray[f%h],r,q,t=m;q=l(a[d])&&l(m[d])?Math.pow(a[d]-m[d],2):null;r=l(a[g])&&l(m[g])?Math.pow(a[g]-m[g],2):null;r=(q||0)+(r||0);m.dist=l(r)?Math.sqrt(r):Number.MAX_VALUE;m.distX=l(q)?Math.sqrt(q):Number.MAX_VALUE;n=a[n]-m[n];r= -0>n?"left":"right";q=0>n?"right":"left";b[r]&&(r=c(a,b[r],f+1,h),t=r[k]p;)q--;this.updateParallelArrays(g,"splice",q,0,0);this.updateParallelArrays(g,q);m&&g.name&&(m[p]=g.name);n.splice(q,0,a);l&&(this.data.splice(q,0,null),this.processData());"point"===e.legendType&&this.generatePoints();c&&(f[0]&&f[0].remove? -f[0].remove(!1):(f.shift(),this.updateParallelArrays(g,"shift"),n.shift()));this.isDirtyData=this.isDirty=!0;b&&h.redraw(d)},removePoint:function(a,b,c){var e=this,d=e.data,f=d[a],h=e.points,m=e.chart,n=function(){h&&h.length===d.length&&h.splice(a,1);d.splice(a,1);e.options.data.splice(a,1);e.updateParallelArrays(f||{series:e},"splice",a,1);f&&f.destroy();e.isDirty=!0;e.isDirtyData=!0;b&&m.redraw()};z(c,m);b=t(b,!0);f?f.firePointEvent("remove",null,n):n()},remove:function(a,b,c){function e(){d.destroy(); -f.isDirtyLegend=f.isDirtyBox=!0;f.linkSeries();t(a,!0)&&f.redraw(b)}var d=this,f=d.chart;!1!==c?u(d,"remove",null,e):e()},update:function(a,b){var c=this,e=this.chart,d=this.userOptions,f=this.type,h=a.type||d.type||e.options.chart.type,m=q[f].prototype,n=["group","markerGroup","dataLabelsGroup"],g;if(h&&h!==f||void 0!==a.zIndex)n.length=0;p(n,function(a){n[a]=c[a];delete c[a]});a=y(d,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1,null,!1);for(g in m)this[g]= -void 0;l(this,q[h||f].prototype);p(n,function(a){c[a]=n[a]});this.init(e,a);e.linkSeries();t(b,!0)&&e.redraw(!1)}});l(G.prototype,{update:function(a,b){var c=this.chart;a=c.options[this.coll][this.options.index]=y(this.userOptions,a);this.destroy(!0);this.init(c,l(a,{events:void 0}));c.isDirtyBox=!0;t(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,e=this.series,d=e.length;d--;)e[d]&&e[d].remove(!1);v(b.axes,this);v(b[c],this);b.options[c].splice(this.options.index,1);p(b[c], -function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;t(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}})})(L);(function(a){var D=a.color,C=a.each,G=a.map,I=a.pick,h=a.Series,f=a.seriesType;f("area","line",{softThreshold:!1,threshold:0},{singleStacks:!1,getStackPoints:function(){var a=[],f=[],h=this.xAxis,u=this.yAxis,d=u.stacks[this.stackKey],c={},n=this.points,y=this.index,t=u.series,m=t.length,b,q=I(u.options.reversedStacks, -!0)?1:-1,z,F;if(this.options.stacking){for(z=0;za&&l>f?(l=Math.max(a,f),d=2*f-l):lI&&d>f?(d=Math.max(I,f),l=2*f-d):d=Math.abs(c)&&.5a.closestPointRange*a.xAxis.transA,h=a.borderWidth=p(f.borderWidth,h?0:1),l=a.yAxis,m=a.translatedThreshold=l.getThreshold(f.threshold),b=p(f.minPointLength, -5),q=a.getColumnMetrics(),u=q.width,F=a.barW=Math.max(u,1+2*h),e=a.pointXOffset=q.offset;c.inverted&&(m-=.5);f.pointPadding&&(F=Math.ceil(F));v.prototype.translate.apply(a);G(a.points,function(d){var f=p(d.yBottom,m),h=999+Math.abs(f),h=Math.min(Math.max(-h,d.plotY),l.len+h),k=d.plotX+e,n=F,q=Math.min(h,f),r,t=Math.max(h,f)-q;Math.abs(t)b?f-b:m-(r?b:0));d.barX=k;d.pointWidth=u;d.tooltipPos=c.inverted?[l.len+l.pos-c.plotLeft- -h,a.xAxis.len-k-n/2,t]:[k+n/2,h+l.pos-c.plotTop,t];d.shapeType="rect";d.shapeArgs=a.crispCol.apply(a,d.isNull?[d.plotX,l.len/2,0,0]:[k,q,n,t])})},getSymbol:a.noop,drawLegendSymbol:a.LegendSymbolMixin.drawRectangle,drawGraph:function(){this.group[this.dense?"addClass":"removeClass"]("highcharts-dense-data")},pointAttribs:function(a,c){var d=this.options,f,h=this.pointAttrToOptions||{};f=h.stroke||"borderColor";var m=h["stroke-width"]||"borderWidth",b=a&&a.color||this.color,l=a[f]||d[f]||this.color|| -b,p=a[m]||d[m]||this[m]||0,h=d.dashStyle;a&&this.zones.length&&(b=(b=a.getZone())&&b.color||a.options.color||this.color);c&&(a=d.states[c],c=a.brightness,b=a.color||void 0!==c&&C(b).brighten(a.brightness).get()||b,l=a[f]||l,p=a[m]||p,h=a.dashStyle||h);f={fill:b,stroke:l,"stroke-width":p};d.borderRadius&&(f.r=d.borderRadius);h&&(f.dashstyle=h);return f},drawPoints:function(){var a=this,c=this.chart,l=a.options,p=c.renderer,t=l.animationLimit||250,m;G(a.points,function(b){var d=b.graphic;if(h(b.plotY)&& -null!==b.y){m=b.shapeArgs;if(d)d[c.pointCountl;++l)u=p[l],a=2>l||2===l&&/%$/.test(u),p[l]=C(u,[f,I,v,p[2]][l])+(a?h:0);p[3]>p[2]&&(p[3]=p[2]);return p}}})(L);(function(a){var D=a.addEvent,C=a.defined,G=a.each,I=a.extend,h=a.inArray,f=a.noop,p=a.pick,v=a.Point,l=a.Series,u=a.seriesType,d=a.setAnimation;u("pie","line",{center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return null===this.y? -void 0:this.point.name},x:0},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,stickyTracking:!1,tooltip:{followPointer:!0},borderColor:"#ffffff",borderWidth:1,states:{hover:{brightness:.1,shadow:!1}}},{isCartesian:!1,requireSorting:!1,directTouch:!0,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttribs:a.seriesTypes.column.prototype.pointAttribs,animate:function(a){var c=this,d=c.points,f=c.startAngleRad;a||(G(d,function(a){var b= -a.graphic,d=a.shapeArgs;b&&(b.attr({r:a.startR||c.center[3]/2,start:f,end:f}),b.animate({r:d.r,start:d.start,end:d.end},c.options.animation))}),c.animate=null)},updateTotals:function(){var a,d=0,f=this.points,h=f.length,m,b=this.options.ignoreHiddenPoint;for(a=0;am.y&&(m.y=null),d+=b&&!m.visible?0:m.y;this.total=d;for(a=0;a1.5*Math.PI?u-=2*Math.PI:u<-Math.PI/2&&(u+=2*Math.PI);w.slicedTranslation={translateX:Math.round(Math.cos(u)*f),translateY:Math.round(Math.sin(u)*f)};b=Math.cos(u)*a[2]/2;l=Math.sin(u)*a[2]/2;w.tooltipPos=[a[0]+.7*b,a[1]+.7*l];w.half=u<-Math.PI/2||u>Math.PI/2?1:0;w.angle=u;h=Math.min(h,x/5);w.labelPos=[a[0]+b+Math.cos(u)*x,a[1]+l+Math.sin(u)*x,a[0]+b+Math.cos(u)*h,a[1]+l+Math.sin(u)* -h,a[0]+b,a[1]+l,0>x?"center":w.half?"right":"left",u]}},drawGraph:null,drawPoints:function(){var a=this,d=a.chart.renderer,f,h,m,b,l=a.options.shadow;l&&!a.shadowGroup&&(a.shadowGroup=d.g("shadow").add(a.group));G(a.points,function(c){if(null!==c.y){h=c.graphic;b=c.shapeArgs;f=c.sliced?c.slicedTranslation:{};var n=c.shadowGroup;l&&!n&&(n=c.shadowGroup=d.g("shadow").add(a.shadowGroup));n&&n.attr(f);m=a.pointAttribs(c,c.selected&&"select");h?h.setRadialReference(a.center).attr(m).animate(I(b,f)):(c.graphic= -h=d[c.shapeType](b).addClass(c.getClassName()).setRadialReference(a.center).attr(f).add(a.group),c.visible||h.attr({visibility:"hidden"}),h.attr(m).attr({"stroke-linejoin":"round"}).shadow(l,n))}})},searchPoint:f,sortByAngle:function(a,d){a.sort(function(a,c){return void 0!==a.angle&&(c.angle-a.angle)*d})},drawLegendSymbol:a.LegendSymbolMixin.drawRectangle,getCenter:a.CenteredSeriesMixin.getCenter,getSymbol:f},{init:function(){v.prototype.init.apply(this,arguments);var a=this,d;a.name=p(a.name,"Slice"); -d=function(c){a.slice("select"===c.type)};D(a,"select",d);D(a,"unselect",d);return a},setVisible:function(a,d){var c=this,f=c.series,m=f.chart,b=f.options.ignoreHiddenPoint;d=p(d,b);a!==c.visible&&(c.visible=c.options.visible=a=void 0===a?!c.visible:a,f.options.data[h(c,f.data)]=c.options,G(["graphic","dataLabel","connector","shadowGroup"],function(b){if(c[b])c[b][a?"show":"hide"](!0)}),c.legendItem&&m.legend.colorizeItem(c,a),a||"hover"!==c.state||c.setState(""),b&&(f.isDirty=!0),d&&m.redraw())}, -slice:function(a,f,l){var c=this.series;d(l,c.chart);p(f,!0);this.sliced=this.options.sliced=a=C(a)?a:!this.sliced;c.options.data[h(this,c.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var c=this.shapeArgs;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.x,c.y,c.r+a,c.r+a,{innerR:this.shapeArgs.r,start:c.start,end:c.end})}})})(L);(function(a){var D= -a.addEvent,C=a.arrayMax,G=a.defined,I=a.each,h=a.extend,f=a.format,p=a.map,v=a.merge,l=a.noop,u=a.pick,d=a.relativeLength,c=a.Series,n=a.seriesTypes,y=a.stableSort;a.distribute=function(a,c){function b(a,b){return a.target-b.target}var d,f=!0,h=a,e=[],m;m=0;for(d=a.length;d--;)m+=a[d].size;if(m>c){y(a,function(a,b){return(b.rank||0)-(a.rank||0)});for(m=d=0;m<=c;)m+=a[d].size,d++;e=a.splice(d-1,a.length)}y(a,b);for(a=p(a,function(a){return{size:a.size,targets:[a.target]}});f;){for(d=a.length;d--;)f= -a[d],m=(Math.min.apply(0,f.targets)+Math.max.apply(0,f.targets))/2,f.pos=Math.min(Math.max(0,m-f.size/2),c-f.size);d=a.length;for(f=!1;d--;)0a[d].pos&&(a[d-1].size+=a[d].size,a[d-1].targets=a[d-1].targets.concat(a[d].targets),a[d-1].pos+a[d-1].size>c&&(a[d-1].pos=c-a[d-1].size),a.splice(d,1),f=!0)}d=0;I(a,function(a){var b=0;I(a.targets,function(){h[d].pos=a.pos+b;b+=h[d].size;d++})});h.push.apply(h,e);y(h,b)};c.prototype.drawDataLabels=function(){var a=this,c=a.options, -b=c.dataLabels,d=a.points,l,n,e=a.hasRendered||0,r,p,A=u(b.defer,!0),k=a.chart.renderer;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),p=a.plotGroup("dataLabelsGroup","data-labels",A&&!e?"hidden":"visible",b.zIndex||6),A&&(p.attr({opacity:+e}),e||D(a,"afterAnimate",function(){a.visible&&p.show(!0);p[c.animation?"animate":"attr"]({opacity:1},{duration:200})})),n=b,I(d,function(e){var d,m=e.dataLabel,q,g,t=e.connector,F=!0,x,A={};l=e.dlOptions||e.options&&e.options.dataLabels; -d=u(l&&l.enabled,n.enabled)&&null!==e.y;if(m&&!d)e.dataLabel=m.destroy();else if(d){b=v(n,l);x=b.style;d=b.rotation;q=e.getLabelConfig();r=b.format?f(b.format,q):b.formatter.call(q,b);x.color=u(b.color,x.color,a.color,"#000000");if(m)G(r)?(m.attr({text:r}),F=!1):(e.dataLabel=m=m.destroy(),t&&(e.connector=t.destroy()));else if(G(r)){m={fill:b.backgroundColor,stroke:b.borderColor,"stroke-width":b.borderWidth,r:b.borderRadius||0,rotation:d,padding:b.padding,zIndex:1};"contrast"===x.color&&(A.color=b.inside|| -0>b.distance||c.stacking?k.getContrast(e.color||a.color):"#000000");c.cursor&&(A.cursor=c.cursor);for(g in m)void 0===m[g]&&delete m[g];m=e.dataLabel=k[d?"text":"label"](r,0,-9999,b.shape,null,null,b.useHTML,null,"data-label").attr(m);m.addClass("highcharts-data-label-color-"+e.colorIndex+" "+(b.className||"")+(b.useHTML?"highcharts-tracker":""));m.css(h(x,A));m.add(p);m.shadow(b.shadow)}m&&a.alignDataLabel(e,m,b,null,F)}})};c.prototype.alignDataLabel=function(a,c,b,d,f){var m=this.chart,e=m.inverted, -l=u(a.plotX,-9999),n=u(a.plotY,-9999),p=c.getBBox(),k,q=b.rotation,t=b.align,v=this.visible&&(a.series.forceDL||m.isInsidePlot(l,Math.round(n),e)||d&&m.isInsidePlot(l,e?d.x+1:d.y+d.height-1,e)),z="justify"===u(b.overflow,"justify");v&&(k=b.style.fontSize,k=m.renderer.fontMetrics(k,c).b,d=h({x:e?m.plotWidth-n:l,y:Math.round(e?m.plotHeight-l:n),width:0,height:0},d),h(b,{width:p.width,height:p.height}),q?(z=!1,e=m.renderer.rotCorr(k,q),e={x:d.x+b.x+d.width/2+e.x,y:d.y+b.y+{top:0,middle:.5,bottom:1}[b.verticalAlign]* -d.height},c[f?"attr":"animate"](e).attr({align:t}),l=(q+720)%360,l=180l,"left"===t?e.y-=l?p.height:0:"center"===t?(e.x-=p.width/2,e.y-=p.height/2):"right"===t&&(e.x-=p.width,e.y-=l?0:p.height)):(c.align(b,null,d),e=c.alignAttr),z?this.justifyDataLabel(c,b,e,p,d,f):u(b.crop,!0)&&(v=m.isInsidePlot(e.x,e.y)&&m.isInsidePlot(e.x+p.width,e.y+p.height)),b.shape&&!q&&c.attr({anchorX:a.plotX,anchorY:a.plotY}));v||(c.attr({y:-9999}),c.placed=!1)};c.prototype.justifyDataLabel=function(a,c,b,d,f,h){var e= -this.chart,m=c.align,l=c.verticalAlign,n,k,p=a.box?0:a.padding||0;n=b.x+p;0>n&&("right"===m?c.align="left":c.x=-n,k=!0);n=b.x+d.width-p;n>e.plotWidth&&("left"===m?c.align="right":c.x=e.plotWidth-n,k=!0);n=b.y+p;0>n&&("bottom"===l?c.verticalAlign="top":c.y=-n,k=!0);n=b.y+d.height-p;n>e.plotHeight&&("top"===l?c.verticalAlign="bottom":c.y=e.plotHeight-n,k=!0);k&&(a.placed=!h,a.align(c,null,f))};n.pie&&(n.pie.prototype.drawDataLabels=function(){var d=this,f=d.data,b,h=d.chart,l=d.options.dataLabels,n= -u(l.connectorPadding,10),e=u(l.connectorWidth,1),r=h.plotWidth,v=h.plotHeight,A,k=l.distance,w=d.center,y=w[2]/2,D=w[1],G=0m-2?u:H,e),g._attr={visibility:P,align:L[6]},g._pos={x:E+l.x+({left:n,right:-n}[L[6]]||0),y:H+l.y-10},L.x=E,L.y=H,null===d.options.size&&(B=g.width,E-Br-n&& -(O[1]=Math.max(Math.round(E+B-r+n),O[1])),0>H-M/2?O[0]=Math.max(Math.round(-H+M/2),O[0]):H+M/2>v&&(O[2]=Math.max(Math.round(H+M/2-v),O[2])))}),0===C(O)||this.verifyDataLabelOverflow(O))&&(this.placeDataLabels(),G&&e&&I(this.points,function(a){var b;A=a.connector;if((g=a.dataLabel)&&g._pos&&a.visible){P=g._attr.visibility;if(b=!A)a.connector=A=h.renderer.path().addClass("highcharts-data-label-connector highcharts-color-"+a.colorIndex).add(d.dataLabelsGroup),A.attr({"stroke-width":e,stroke:l.connectorColor|| -a.color||"#666666"});A[b?"attr":"animate"]({d:d.connectorPath(a.labelPos)});A.attr("visibility",P)}else A&&(a.connector=A.destroy())}))},n.pie.prototype.connectorPath=function(a){var c=a.x,b=a.y;return u(this.options.dataLabels.softConnector,!0)?["M",c+("left"===a[6]?5:-5),b,"C",c,b,2*a[2]-a[4],2*a[3]-a[5],a[2],a[3],"L",a[4],a[5]]:["M",c+("left"===a[6]?5:-5),b,"L",a[2],a[3],"L",a[4],a[5]]},n.pie.prototype.placeDataLabels=function(){I(this.points,function(a){var c=a.dataLabel;c&&a.visible&&((a=c._pos)? -(c.attr(c._attr),c[c.moved?"animate":"attr"](a),c.moved=!0):c&&c.attr({y:-9999}))})},n.pie.prototype.alignDataLabel=l,n.pie.prototype.verifyDataLabelOverflow=function(a){var c=this.center,b=this.options,f=b.center,h=b.minSize||80,l,e;null!==f[0]?l=Math.max(c[2]-Math.max(a[1],a[3]),h):(l=Math.max(c[2]-a[1]-a[3],h),c[0]+=(a[3]-a[1])/2);null!==f[1]?l=Math.max(Math.min(l,c[2]-Math.max(a[0],a[2])),h):(l=Math.max(Math.min(l,c[2]-a[0]-a[2]),h),c[1]+=(a[0]-a[2])/2);lu(this.translatedThreshold,e.yAxis.len)),p=u(b.inside,!!this.options.stacking);m&&(f=v(m),0>f.y&&(f.height+=f.y,f.y=0),m=f.y+f.height-e.yAxis.len,0a+d||e+lc+f||h+mthis.pointCount))}, -pan:function(a,b){var c=this,e=c.hoverPoints,d;e&&p(e,function(a){a.setState()});p("xy"===b?[1,0]:[1],function(b){b=c[b?"xAxis":"yAxis"][0];var e=b.horiz,f=a[e?"chartX":"chartY"],e=e?"mouseDownX":"mouseDownY",h=c[e],k=(b.pointRange||0)/2,g=b.getExtremes(),l=b.toValue(h-f,!0)+k,k=b.toValue(h+b.len-f,!0)-k,m=kk&&0>g&&(b.setExtremes(h,l,!1,!1,{trigger:"pan"}),d=!0);c[e]=f});d&&c.redraw(!1);I(c.container, -{cursor:"move"})}});v(m.prototype,{select:function(a,b){var c=this,e=c.series,f=e.chart;a=t(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=c.options.selected=a;e.options.data[d(c,e.data)]=c.options;c.setState(a&&"select");b||p(f.getSelectedPoints(),function(a){a.selected&&a!==c&&(a.selected=a.options.selected=!1,e.options.data[d(a,e.data)]=a.options,a.setState(""),a.firePointEvent("unselect"))})})},onMouseOver:function(a,b){var c=this.series,e=c.chart,d= -e.tooltip,f=e.hoverPoint;if(this.series){if(!b){if(f&&f!==this)f.onMouseOut();if(e.hoverSeries!==c)c.onMouseOver();e.hoverPoint=this}!d||d.shared&&!c.noSharedTooltip?d||this.setState("hover"):(this.setState("hover"),d.refresh(this,a));this.firePointEvent("mouseOver")}},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;this.firePointEvent("mouseOut");b&&-1!==d(this,b)||(this.setState(),a.hoverPoint=null)},importEvents:function(){if(!this.hasImportedEvents){var a=y(this.series.options.point, -this.options).events,b;this.events=a;for(b in a)D(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a,b){var c=Math.floor(this.plotX),d=this.plotY,e=this.series,h=e.options.states[a]||{},l=f[e.type].marker&&e.options.marker,m=l&&!1===l.enabled,n=l&&l.states&&l.states[a]||{},p=!1===n.enabled,g=e.stateMarkerGraphic,q=this.marker||{},u=e.chart,y=e.halo,z,F=l&&e.markerAttribs;a=a||"";if(!(a===this.state&&!b||this.selected&&"select"!==a||!1===h.enabled||a&&(p||m&&!1===n.enabled)||a&&q.states&& -q.states[a]&&!1===q.states[a].enabled)){F&&(z=e.markerAttribs(this,a));if(this.graphic)this.state&&this.graphic.removeClass("highcharts-point-"+this.state),a&&this.graphic.addClass("highcharts-point-"+a),this.graphic.attr(e.pointAttribs(this,a)),z&&this.graphic.animate(z,t(u.options.chart.animation,n.animation,l.animation)),g&&g.hide();else{if(a&&n){l=q.symbol||e.symbol;g&&g.currentSymbol!==l&&(g=g.destroy());if(g)g[b?"animate":"attr"]({x:z.x,y:z.y});else l&&(e.stateMarkerGraphic=g=u.renderer.symbol(l, -z.x,z.y,z.width,z.height).add(e.markerGroup),g.currentSymbol=l);g&&g.attr(e.pointAttribs(this,a))}g&&(g[a&&u.isInsidePlot(c,d,u.inverted)?"show":"hide"](),g.element.point=this)}(c=h.halo)&&c.size?(y||(e.halo=y=u.renderer.path().add(F?e.markerGroup:e.group)),y[b?"animate":"attr"]({d:this.haloPath(c.size)}),y.attr({"class":"highcharts-halo highcharts-color-"+t(this.colorIndex,e.colorIndex)}),y.point=this,y.attr(v({fill:this.color||e.color,"fill-opacity":c.opacity,zIndex:-1},c.attributes))):y&&y.point&& -y.point.haloPath&&y.animate({d:y.point.haloPath(0)});this.state=a}},haloPath:function(a){return this.series.chart.renderer.symbols.circle(Math.floor(this.plotX)-a,this.plotY-a,2*a,2*a)}});v(b.prototype,{onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&l(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;b.hoverSeries=null;if(d)d.onMouseOut(); -this&&a.events.mouseOut&&l(this,"mouseOut");!c||a.stickyTracking||c.shared&&!this.noSharedTooltip||c.hide();this.setState()},setState:function(a){var b=this,c=b.options,d=b.graph,f=c.states,h=c.lineWidth,c=0;a=a||"";if(b.state!==a&&(p([b.group,b.markerGroup],function(c){c&&(b.state&&c.removeClass("highcharts-series-"+b.state),a&&c.addClass("highcharts-series-"+a))}),b.state=a,!f[a]||!1!==f[a].enabled)&&(a&&(h=f[a].lineWidth||h+(f[a].lineWidthPlus||0)),d&&!d.dashstyle))for(f={"stroke-width":h},d.attr(f);b["zone-graph-"+ -c];)b["zone-graph-"+c].attr(f),c+=1},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,h=d.options.chart.ignoreHiddenSeries,m=c.visible;f=(c.visible=a=c.options.visible=c.userOptions.visible=void 0===a?!m:a)?"show":"hide";p(["group","dataLabelsGroup","markerGroup","tracker","tt"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c||(d.hoverPoint&&d.hoverPoint.series)===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&p(d.series,function(a){a.options.stacking&& -a.visible&&(a.isDirty=!0)});p(c.linkedSeries,function(b){b.setVisible(a,!1)});h&&(d.isDirtyBox=!0);!1!==b&&d.redraw();l(c,f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=void 0===a?!this.selected:a;this.checkbox&&(this.checkbox.checked=a);l(this,a?"select":"unselect")},drawTracker:a.drawTrackerGraph})})(L);(function(a){var D=a.Chart,C=a.each,G=a.inArray,I=a.isObject,h=a.pick,f=a.splat;D.prototype.setResponsive=function(a){var f=this.options.responsive; -f&&f.rules&&C(f.rules,function(f){this.matchResponsiveRule(f,a)},this)};D.prototype.matchResponsiveRule=function(f,v){var l=this.respRules,p=f.condition,d;d=p.callback||function(){return this.chartWidth<=h(p.maxWidth,Number.MAX_VALUE)&&this.chartHeight<=h(p.maxHeight,Number.MAX_VALUE)&&this.chartWidth>=h(p.minWidth,0)&&this.chartHeight>=h(p.minHeight,0)};void 0===f._id&&(f._id=a.uniqueKey());d=d.call(this);!l[f._id]&&d?f.chartOptions&&(l[f._id]=this.currentOptions(f.chartOptions),this.update(f.chartOptions, -v)):l[f._id]&&!d&&(this.update(l[f._id],v),delete l[f._id])};D.prototype.currentOptions=function(a){function h(a,d,c){var l,p;for(l in a)if(-1 parseInt(C.split("Firefox/")[1], 10); + return a.Highcharts + ? a.Highcharts.error(16, !0) + : { + product: "Highcharts", + version: "5.0.6", + deg2rad: (2 * Math.PI) / 360, + doc: D, + hasBidiBug: p, + hasTouch: D && void 0 !== D.documentElement.ontouchstart, + isMS: I, + isWebKit: /AppleWebKit/.test(C), + isFirefox: f, + isTouchDevice: /(Mobile|Android|Windows Phone)/.test(C), + SVG_NS: "http://www.w3.org/2000/svg", + chartCount: 0, + seriesTypes: {}, + symbolSizes: {}, + svg: G, + vml: h, + win: a, + charts: [], + marginNames: ["plotTop", "marginRight", "marginBottom", "plotLeft"], + noop: function () {}, + }; + })(); + (function (a) { + var D = [], + C = a.charts, + G = a.doc, + I = a.win; + a.error = function (h, f) { + h = a.isNumber(h) + ? "Highcharts error #" + h + ": www.highcharts.com/errors/" + h + : h; + if (f) throw Error(h); + I.console && console.log(h); + }; + a.Fx = function (a, f, p) { + this.options = f; + this.elem = a; + this.prop = p; + }; + a.Fx.prototype = { + dSetter: function () { + var a = this.paths[0], + f = this.paths[1], + p = [], + v = this.now, + l = a.length, + u; + if (1 === v) p = this.toD; + else if (l === f.length && 1 > v) + for (; l--; ) + (u = parseFloat(a[l])), + (p[l] = isNaN(u) ? a[l] : v * parseFloat(f[l] - u) + u); + else p = f; + this.elem.attr("d", p, null, !0); + }, + update: function () { + var a = this.elem, + f = this.prop, + p = this.now, + v = this.options.step; + if (this[f + "Setter"]) this[f + "Setter"](); + else + a.attr + ? a.element && a.attr(f, p, null, !0) + : (a.style[f] = p + this.unit); + v && v.call(a, p, this); + }, + run: function (a, f, p) { + var h = this, + l = function (a) { + return l.stopped ? !1 : h.step(a); + }, + u; + this.startTime = +new Date(); + this.start = a; + this.end = f; + this.unit = p; + this.now = this.start; + this.pos = 0; + l.elem = this.elem; + l.prop = this.prop; + l() && + 1 === D.push(l) && + (l.timerId = setInterval(function () { + for (u = 0; u < D.length; u++) D[u]() || D.splice(u--, 1); + D.length || clearInterval(l.timerId); + }, 13)); + }, + step: function (a) { + var f = +new Date(), + h, + v = this.options; + h = this.elem; + var l = v.complete, + u = v.duration, + d = v.curAnim, + c; + if (h.attr && !h.element) h = !1; + else if (a || f >= u + this.startTime) { + this.now = this.end; + this.pos = 1; + this.update(); + a = d[this.prop] = !0; + for (c in d) !0 !== d[c] && (a = !1); + a && l && l.call(h); + h = !1; + } else + (this.pos = v.easing((f - this.startTime) / u)), + (this.now = this.start + (this.end - this.start) * this.pos), + this.update(), + (h = !0); + return h; + }, + initPath: function (h, f, p) { + function v(a) { + var e, b; + for (q = a.length; q--; ) + (e = "M" === a[q] || "L" === a[q]), + (b = /[a-zA-Z]/.test(a[q + 3])), + e && + b && + a.splice(q + 1, 0, a[q + 1], a[q + 2], a[q + 1], a[q + 2]); + } + function l(a, e) { + for (; a.length < m; ) { + a[0] = e[m - a.length]; + var b = a.slice(0, t); + [].splice.apply(a, [0, 0].concat(b)); + z && + ((b = a.slice(a.length - t)), + [].splice.apply(a, [a.length, 0].concat(b)), + q--); + } + a[0] = "M"; + } + function u(a, e) { + for (var c = (m - a.length) / t; 0 < c && c--; ) + (b = a.slice().splice(a.length / F - t, t * F)), + (b[0] = e[m - t - c * t]), + y && ((b[t - 6] = b[t - 2]), (b[t - 5] = b[t - 1])), + [].splice.apply(a, [a.length / F, 0].concat(b)), + z && c--; + } + f = f || ""; + var d, + c = h.startX, + n = h.endX, + y = -1 < f.indexOf("C"), + t = y ? 7 : 3, + m, + b, + q; + f = f.split(" "); + p = p.slice(); + var z = h.isArea, + F = z ? 2 : 1, + e; + y && (v(f), v(p)); + if (c && n) { + for (q = 0; q < c.length; q++) + if (c[q] === n[0]) { + d = q; + break; + } else if (c[0] === n[n.length - c.length + q]) { + d = q; + e = !0; + break; + } + void 0 === d && (f = []); + } + f.length && + a.isNumber(d) && + ((m = p.length + d * F * t), + e ? (l(f, p), u(p, f)) : (l(p, f), u(f, p))); + return [f, p]; + }, + }; + a.extend = function (a, f) { + var h; + a || (a = {}); + for (h in f) a[h] = f[h]; + return a; + }; + a.merge = function () { + var h, + f = arguments, + p, + v = {}, + l = function (h, d) { + var c, n; + "object" !== typeof h && (h = {}); + for (n in d) + d.hasOwnProperty(n) && + ((c = d[n]), + a.isObject(c, !0) && + "renderTo" !== n && + "number" !== typeof c.nodeType + ? (h[n] = l(h[n] || {}, c)) + : (h[n] = d[n])); + return h; + }; + !0 === f[0] && ((v = f[1]), (f = Array.prototype.slice.call(f, 2))); + p = f.length; + for (h = 0; h < p; h++) v = l(v, f[h]); + return v; + }; + a.pInt = function (a, f) { + return parseInt(a, f || 10); + }; + a.isString = function (a) { + return "string" === typeof a; + }; + a.isArray = function (a) { + a = Object.prototype.toString.call(a); + return "[object Array]" === a || "[object Array Iterator]" === a; + }; + a.isObject = function (h, f) { + return h && "object" === typeof h && (!f || !a.isArray(h)); + }; + a.isNumber = function (a) { + return "number" === typeof a && !isNaN(a); + }; + a.erase = function (a, f) { + for (var h = a.length; h--; ) + if (a[h] === f) { + a.splice(h, 1); + break; + } + }; + a.defined = function (a) { + return void 0 !== a && null !== a; + }; + a.attr = function (h, f, p) { + var v, l; + if (a.isString(f)) + a.defined(p) + ? h.setAttribute(f, p) + : h && h.getAttribute && (l = h.getAttribute(f)); + else if (a.defined(f) && a.isObject(f)) + for (v in f) h.setAttribute(v, f[v]); + return l; + }; + a.splat = function (h) { + return a.isArray(h) ? h : [h]; + }; + a.syncTimeout = function (a, f, p) { + if (f) return setTimeout(a, f, p); + a.call(0, p); + }; + a.pick = function () { + var a = arguments, + f, + p, + v = a.length; + for (f = 0; f < v; f++) + if (((p = a[f]), void 0 !== p && null !== p)) return p; + }; + a.css = function (h, f) { + a.isMS && + !a.svg && + f && + void 0 !== f.opacity && + (f.filter = "alpha(opacity\x3d" + 100 * f.opacity + ")"); + a.extend(h.style, f); + }; + a.createElement = function (h, f, p, v, l) { + h = G.createElement(h); + var u = a.css; + f && a.extend(h, f); + l && u(h, { padding: 0, border: "none", margin: 0 }); + p && u(h, p); + v && v.appendChild(h); + return h; + }; + a.extendClass = function (h, f) { + var p = function () {}; + p.prototype = new h(); + a.extend(p.prototype, f); + return p; + }; + a.pad = function (a, f, p) { + return Array((f || 2) + 1 - String(a).length).join(p || 0) + a; + }; + a.relativeLength = function (a, f) { + return /%$/.test(a) ? (f * parseFloat(a)) / 100 : parseFloat(a); + }; + a.wrap = function (a, f, p) { + var h = a[f]; + a[f] = function () { + var a = Array.prototype.slice.call(arguments), + f = arguments, + d = this; + d.proceed = function () { + h.apply(d, arguments.length ? arguments : f); + }; + a.unshift(h); + a = p.apply(this, a); + d.proceed = null; + return a; + }; + }; + a.getTZOffset = function (h) { + var f = a.Date; + return ( + 6e4 * + ((f.hcGetTimezoneOffset && f.hcGetTimezoneOffset(h)) || + f.hcTimezoneOffset || + 0) + ); + }; + a.dateFormat = function (h, f, p) { + if (!a.defined(f) || isNaN(f)) + return a.defaultOptions.lang.invalidDate || ""; + h = a.pick(h, "%Y-%m-%d %H:%M:%S"); + var v = a.Date, + l = new v(f - a.getTZOffset(f)), + u, + d = l[v.hcGetHours](), + c = l[v.hcGetDay](), + n = l[v.hcGetDate](), + y = l[v.hcGetMonth](), + t = l[v.hcGetFullYear](), + m = a.defaultOptions.lang, + b = m.weekdays, + q = m.shortWeekdays, + z = a.pad, + v = a.extend( + { + a: q ? q[c] : b[c].substr(0, 3), + A: b[c], + d: z(n), + e: z(n, 2, " "), + w: c, + b: m.shortMonths[y], + B: m.months[y], + m: z(y + 1), + y: t.toString().substr(2, 2), + Y: t, + H: z(d), + k: d, + I: z(d % 12 || 12), + l: d % 12 || 12, + M: z(l[v.hcGetMinutes]()), + p: 12 > d ? "AM" : "PM", + P: 12 > d ? "am" : "pm", + S: z(l.getSeconds()), + L: z(Math.round(f % 1e3), 3), + }, + a.dateFormats + ); + for (u in v) + for (; -1 !== h.indexOf("%" + u); ) + h = h.replace("%" + u, "function" === typeof v[u] ? v[u](f) : v[u]); + return p ? h.substr(0, 1).toUpperCase() + h.substr(1) : h; + }; + a.formatSingle = function (h, f) { + var p = /\.([0-9])/, + v = a.defaultOptions.lang; + /f$/.test(h) + ? ((p = (p = h.match(p)) ? p[1] : -1), + null !== f && + (f = a.numberFormat( + f, + p, + v.decimalPoint, + -1 < h.indexOf(",") ? v.thousandsSep : "" + ))) + : (f = a.dateFormat(h, f)); + return f; + }; + a.format = function (h, f) { + for (var p = "{", v = !1, l, u, d, c, n = [], y; h; ) { + p = h.indexOf(p); + if (-1 === p) break; + l = h.slice(0, p); + if (v) { + l = l.split(":"); + u = l.shift().split("."); + c = u.length; + y = f; + for (d = 0; d < c; d++) y = y[u[d]]; + l.length && (y = a.formatSingle(l.join(":"), y)); + n.push(y); + } else n.push(l); + h = h.slice(p + 1); + p = (v = !v) ? "}" : "{"; + } + n.push(h); + return n.join(""); + }; + a.getMagnitude = function (a) { + return Math.pow(10, Math.floor(Math.log(a) / Math.LN10)); + }; + a.normalizeTickInterval = function (h, f, p, v, l) { + var u, + d = h; + p = a.pick(p, 1); + u = h / p; + f || + ((f = l + ? [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] + : [1, 2, 2.5, 5, 10]), + !1 === v && + (1 === p + ? (f = a.grep(f, function (a) { + return 0 === a % 1; + })) + : 0.1 >= p && (f = [1 / p]))); + for ( + v = 0; + v < f.length && + !((d = f[v]), + (l && d * p >= h) || (!l && u <= (f[v] + (f[v + 1] || f[v])) / 2)); + v++ + ); + return d * p; + }; + a.stableSort = function (a, f) { + var p = a.length, + h, + l; + for (l = 0; l < p; l++) a[l].safeI = l; + a.sort(function (a, d) { + h = f(a, d); + return 0 === h ? a.safeI - d.safeI : h; + }); + for (l = 0; l < p; l++) delete a[l].safeI; + }; + a.arrayMin = function (a) { + for (var f = a.length, p = a[0]; f--; ) a[f] < p && (p = a[f]); + return p; + }; + a.arrayMax = function (a) { + for (var f = a.length, p = a[0]; f--; ) a[f] > p && (p = a[f]); + return p; + }; + a.destroyObjectProperties = function (a, f) { + for (var p in a) + a[p] && a[p] !== f && a[p].destroy && a[p].destroy(), delete a[p]; + }; + a.discardElement = function (h) { + var f = a.garbageBin; + f || (f = a.createElement("div")); + h && f.appendChild(h); + f.innerHTML = ""; + }; + a.correctFloat = function (a, f) { + return parseFloat(a.toPrecision(f || 14)); + }; + a.setAnimation = function (h, f) { + f.renderer.globalAnimation = a.pick(h, f.options.chart.animation, !0); + }; + a.animObject = function (h) { + return a.isObject(h) ? a.merge(h) : { duration: h ? 500 : 0 }; + }; + a.timeUnits = { + millisecond: 1, + second: 1e3, + minute: 6e4, + hour: 36e5, + day: 864e5, + week: 6048e5, + month: 24192e5, + year: 314496e5, + }; + a.numberFormat = function (h, f, p, v) { + h = +h || 0; + f = +f; + var l = a.defaultOptions.lang, + u = (h.toString().split(".")[1] || "").length, + d, + c, + n = Math.abs(h); + -1 === f ? (f = Math.min(u, 20)) : a.isNumber(f) || (f = 2); + d = String(a.pInt(n.toFixed(f))); + c = 3 < d.length ? d.length % 3 : 0; + p = a.pick(p, l.decimalPoint); + v = a.pick(v, l.thousandsSep); + h = (0 > h ? "-" : "") + (c ? d.substr(0, c) + v : ""); + h += d.substr(c).replace(/(\d{3})(?=\d)/g, "$1" + v); + f && + ((v = Math.abs(n - d + Math.pow(10, -Math.max(f, u) - 1))), + (h += p + v.toFixed(f).slice(2))); + return h; + }; + Math.easeInOutSine = function (a) { + return -0.5 * (Math.cos(Math.PI * a) - 1); + }; + a.getStyle = function (h, f) { + return "width" === f + ? Math.min(h.offsetWidth, h.scrollWidth) - + a.getStyle(h, "padding-left") - + a.getStyle(h, "padding-right") + : "height" === f + ? Math.min(h.offsetHeight, h.scrollHeight) - + a.getStyle(h, "padding-top") - + a.getStyle(h, "padding-bottom") + : (h = I.getComputedStyle(h, void 0)) && a.pInt(h.getPropertyValue(f)); + }; + a.inArray = function (a, f) { + return f.indexOf ? f.indexOf(a) : [].indexOf.call(f, a); + }; + a.grep = function (a, f) { + return [].filter.call(a, f); + }; + a.find = function (a, f) { + return [].find.call(a, f); + }; + a.map = function (a, f) { + for (var p = [], h = 0, l = a.length; h < l; h++) + p[h] = f.call(a[h], a[h], h, a); + return p; + }; + a.offset = function (a) { + var f = G.documentElement; + a = a.getBoundingClientRect(); + return { + top: a.top + (I.pageYOffset || f.scrollTop) - (f.clientTop || 0), + left: a.left + (I.pageXOffset || f.scrollLeft) - (f.clientLeft || 0), + }; + }; + a.stop = function (a, f) { + for (var p = D.length; p--; ) + D[p].elem !== a || (f && f !== D[p].prop) || (D[p].stopped = !0); + }; + a.each = function (a, f, p) { + return Array.prototype.forEach.call(a, f, p); + }; + a.addEvent = function (h, f, p) { + function v(a) { + a.target = a.srcElement || I; + p.call(h, a); + } + var l = (h.hcEvents = h.hcEvents || {}); + h.addEventListener + ? h.addEventListener(f, p, !1) + : h.attachEvent && + (h.hcEventsIE || (h.hcEventsIE = {}), + (h.hcEventsIE[p.toString()] = v), + h.attachEvent("on" + f, v)); + l[f] || (l[f] = []); + l[f].push(p); + return function () { + a.removeEvent(h, f, p); + }; + }; + a.removeEvent = function (h, f, p) { + function v(a, c) { + h.removeEventListener + ? h.removeEventListener(a, c, !1) + : h.attachEvent && + ((c = h.hcEventsIE[c.toString()]), h.detachEvent("on" + a, c)); + } + function l() { + var a, c; + if (h.nodeName) + for (c in (f ? ((a = {}), (a[f] = !0)) : (a = d), a)) + if (d[c]) for (a = d[c].length; a--; ) v(c, d[c][a]); + } + var u, + d = h.hcEvents, + c; + d && + (f + ? ((u = d[f] || []), + p + ? ((c = a.inArray(p, u)), + -1 < c && (u.splice(c, 1), (d[f] = u)), + v(f, p)) + : (l(), (d[f] = []))) + : (l(), (h.hcEvents = {}))); + }; + a.fireEvent = function (h, f, p, v) { + var l; + l = h.hcEvents; + var u, d; + p = p || {}; + if (G.createEvent && (h.dispatchEvent || h.fireEvent)) + (l = G.createEvent("Events")), + l.initEvent(f, !0, !0), + a.extend(l, p), + h.dispatchEvent ? h.dispatchEvent(l) : h.fireEvent(f, l); + else if (l) + for ( + l = l[f] || [], + u = l.length, + p.target || + a.extend(p, { + preventDefault: function () { + p.defaultPrevented = !0; + }, + target: h, + type: f, + }), + f = 0; + f < u; + f++ + ) + (d = l[f]) && !1 === d.call(h, p) && p.preventDefault(); + v && !p.defaultPrevented && v(p); + }; + a.animate = function (h, f, p) { + var v, + l = "", + u, + d, + c; + a.isObject(p) || + ((v = arguments), + (p = { duration: v[2], easing: v[3], complete: v[4] })); + a.isNumber(p.duration) || (p.duration = 400); + p.easing = + "function" === typeof p.easing + ? p.easing + : Math[p.easing] || Math.easeInOutSine; + p.curAnim = a.merge(f); + for (c in f) + a.stop(h, c), + (d = new a.Fx(h, p, c)), + (u = null), + "d" === c + ? ((d.paths = d.initPath(h, h.d, f.d)), + (d.toD = f.d), + (v = 0), + (u = 1)) + : h.attr + ? (v = h.attr(c)) + : ((v = parseFloat(a.getStyle(h, c)) || 0), + "opacity" !== c && (l = "px")), + u || (u = f[c]), + u.match && u.match("px") && (u = u.replace(/px/g, "")), + d.run(v, u, l); + }; + a.seriesType = function (h, f, p, v, l) { + var u = a.getOptions(), + d = a.seriesTypes; + u.plotOptions[h] = a.merge(u.plotOptions[f], p); + d[h] = a.extendClass(d[f] || function () {}, v); + d[h].prototype.type = h; + l && (d[h].prototype.pointClass = a.extendClass(a.Point, l)); + return d[h]; + }; + a.uniqueKey = (function () { + var a = Math.random().toString(36).substring(2, 9), + f = 0; + return function () { + return "highcharts-" + a + "-" + f++; + }; + })(); + I.jQuery && + (I.jQuery.fn.highcharts = function () { + var h = [].slice.call(arguments); + if (this[0]) + return h[0] + ? (new a[a.isString(h[0]) ? h.shift() : "Chart"]( + this[0], + h[0], + h[1] + ), + this) + : C[a.attr(this[0], "data-highcharts-chart")]; + }); + G && + !G.defaultView && + (a.getStyle = function (h, f) { + var p = { width: "clientWidth", height: "clientHeight" }[f]; + if (h.style[f]) return a.pInt(h.style[f]); + "opacity" === f && (f = "filter"); + if (p) + return ( + (h.style.zoom = 1), Math.max(h[p] - 2 * a.getStyle(h, "padding"), 0) + ); + h = + h.currentStyle[ + f.replace(/\-(\w)/g, function (a, l) { + return l.toUpperCase(); + }) + ]; + "filter" === f && + (h = h.replace(/alpha\(opacity=([0-9]+)\)/, function (a, l) { + return l / 100; + })); + return "" === h ? 1 : a.pInt(h); + }); + Array.prototype.forEach || + (a.each = function (a, f, p) { + for (var h = 0, l = a.length; h < l; h++) + if (!1 === f.call(p, a[h], h, a)) return h; + }); + Array.prototype.indexOf || + (a.inArray = function (a, f) { + var p, + h = 0; + if (f) for (p = f.length; h < p; h++) if (f[h] === a) return h; + return -1; + }); + Array.prototype.filter || + (a.grep = function (a, f) { + for (var p = [], h = 0, l = a.length; h < l; h++) + f(a[h], h) && p.push(a[h]); + return p; + }); + Array.prototype.find || + (a.find = function (a, f) { + var p, + h = a.length; + for (p = 0; p < h; p++) if (f(a[p], p)) return a[p]; + }); + })(L); + (function (a) { + var D = a.each, + C = a.isNumber, + G = a.map, + I = a.merge, + h = a.pInt; + a.Color = function (f) { + if (!(this instanceof a.Color)) return new a.Color(f); + this.init(f); + }; + a.Color.prototype = { + parsers: [ + { + regex: + /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, + parse: function (a) { + return [h(a[1]), h(a[2]), h(a[3]), parseFloat(a[4], 10)]; + }, + }, + { + regex: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, + parse: function (a) { + return [h(a[1], 16), h(a[2], 16), h(a[3], 16), 1]; + }, + }, + { + regex: + /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/, + parse: function (a) { + return [h(a[1]), h(a[2]), h(a[3]), 1]; + }, + }, + ], + names: { white: "#ffffff", black: "#000000" }, + init: function (f) { + var p, h, l, u; + if ((this.input = f = this.names[f] || f) && f.stops) + this.stops = G(f.stops, function (d) { + return new a.Color(d[1]); + }); + else + for (l = this.parsers.length; l-- && !h; ) + (u = this.parsers[l]), (p = u.regex.exec(f)) && (h = u.parse(p)); + this.rgba = h || []; + }, + get: function (a) { + var f = this.input, + h = this.rgba, + l; + this.stops + ? ((l = I(f)), + (l.stops = [].concat(l.stops)), + D(this.stops, function (f, d) { + l.stops[d] = [l.stops[d][0], f.get(a)]; + })) + : (l = + h && C(h[0]) + ? "rgb" === a || (!a && 1 === h[3]) + ? "rgb(" + h[0] + "," + h[1] + "," + h[2] + ")" + : "a" === a + ? h[3] + : "rgba(" + h.join(",") + ")" + : f); + return l; + }, + brighten: function (a) { + var f, + v = this.rgba; + if (this.stops) + D(this.stops, function (l) { + l.brighten(a); + }); + else if (C(a) && 0 !== a) + for (f = 0; 3 > f; f++) + (v[f] += h(255 * a)), + 0 > v[f] && (v[f] = 0), + 255 < v[f] && (v[f] = 255); + return this; + }, + setOpacity: function (a) { + this.rgba[3] = a; + return this; + }, + }; + a.color = function (f) { + return new a.Color(f); + }; + })(L); + (function (a) { + var D, + C, + G = a.addEvent, + I = a.animate, + h = a.attr, + f = a.charts, + p = a.color, + v = a.css, + l = a.createElement, + u = a.defined, + d = a.deg2rad, + c = a.destroyObjectProperties, + n = a.doc, + y = a.each, + t = a.extend, + m = a.erase, + b = a.grep, + q = a.hasTouch, + z = a.isArray, + F = a.isFirefox, + e = a.isMS, + r = a.isObject, + x = a.isString, + A = a.isWebKit, + k = a.merge, + w = a.noop, + K = a.pick, + J = a.pInt, + N = a.removeEvent, + g = a.stop, + B = a.svg, + S = a.SVG_NS, + M = a.symbolSizes, + R = a.win; + D = a.SVGElement = function () { + return this; + }; + D.prototype = { + opacity: 1, + SVG_NS: S, + textProps: + "direction fontSize fontWeight fontFamily fontStyle color lineHeight width textDecoration textOverflow textOutline".split( + " " + ), + init: function (a, H) { + this.element = "span" === H ? l(H) : n.createElementNS(this.SVG_NS, H); + this.renderer = a; + }, + animate: function (E, H, g) { + H = a.animObject(K(H, this.renderer.globalAnimation, !0)); + 0 !== H.duration + ? (g && (H.complete = g), I(this, E, H)) + : this.attr(E, null, g); + return this; + }, + colorGradient: function (E, H, g) { + var e = this.renderer, + c, + b, + B, + r, + m, + w, + q, + d, + x, + n, + P, + t = [], + A; + E.linearGradient + ? (b = "linearGradient") + : E.radialGradient && (b = "radialGradient"); + if (b) { + B = E[b]; + m = e.gradients; + q = E.stops; + n = g.radialReference; + z(B) && + (E[b] = B = + { + x1: B[0], + y1: B[1], + x2: B[2], + y2: B[3], + gradientUnits: "userSpaceOnUse", + }); + "radialGradient" === b && + n && + !u(B.gradientUnits) && + ((r = B), + (B = k(B, e.getRadialAttr(n, r), { + gradientUnits: "userSpaceOnUse", + }))); + for (P in B) "id" !== P && t.push(P, B[P]); + for (P in q) t.push(q[P]); + t = t.join(","); + m[t] + ? (n = m[t].attr("id")) + : ((B.id = n = a.uniqueKey()), + (m[t] = w = e.createElement(b).attr(B).add(e.defs)), + (w.radAttr = r), + (w.stops = []), + y(q, function (E) { + 0 === E[1].indexOf("rgba") + ? ((c = a.color(E[1])), (d = c.get("rgb")), (x = c.get("a"))) + : ((d = E[1]), (x = 1)); + E = e + .createElement("stop") + .attr({ offset: E[0], "stop-color": d, "stop-opacity": x }) + .add(w); + w.stops.push(E); + })); + A = "url(" + e.url + "#" + n + ")"; + g.setAttribute(H, A); + g.gradient = t; + E.toString = function () { + return A; + }; + } + }, + applyTextOutline: function (a) { + var E = this.element, + g, + e, + k, + b; + -1 !== a.indexOf("contrast") && + (a = a.replace(/contrast/g, this.renderer.getContrast(E.style.fill))); + this.fakeTS = !0; + this.ySetter = this.xSetter; + g = [].slice.call(E.getElementsByTagName("tspan")); + a = a.split(" "); + e = a[a.length - 1]; + (k = a[0]) && + "none" !== k && + ((k = k.replace(/(^[\d\.]+)(.*?)$/g, function (a, E, H) { + return 2 * E + H; + })), + y(g, function (a) { + "highcharts-text-outline" === a.getAttribute("class") && + m(g, E.removeChild(a)); + }), + (b = E.firstChild), + y(g, function (a, H) { + 0 === H && + (a.setAttribute("x", E.getAttribute("x")), + (H = E.getAttribute("y")), + a.setAttribute("y", H || 0), + null === H && E.setAttribute("y", 0)); + a = a.cloneNode(1); + h(a, { + class: "highcharts-text-outline", + fill: e, + stroke: e, + "stroke-width": k, + "stroke-linejoin": "round", + }); + E.insertBefore(a, b); + })); + }, + attr: function (a, H, e, k) { + var E, + b = this.element, + c, + B = this, + r; + "string" === typeof a && + void 0 !== H && + ((E = a), (a = {}), (a[E] = H)); + if ("string" === typeof a) + B = (this[a + "Getter"] || this._defaultGetter).call(this, a, b); + else { + for (E in a) + (H = a[E]), + (r = !1), + k || g(this, E), + this.symbolName && + /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test( + E + ) && + (c || (this.symbolAttr(a), (c = !0)), (r = !0)), + !this.rotation || + ("x" !== E && "y" !== E) || + (this.doTransform = !0), + r || + ((r = this[E + "Setter"] || this._defaultSetter), + r.call(this, H, E, b), + this.shadows && + /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test( + E + ) && + this.updateShadows(E, H, r)); + this.doTransform && (this.updateTransform(), (this.doTransform = !1)); + } + e && e(); + return B; + }, + updateShadows: function (a, H, g) { + for (var E = this.shadows, e = E.length; e--; ) + g.call( + E[e], + "height" === a + ? Math.max(H - (E[e].cutHeight || 0), 0) + : "d" === a + ? this.d + : H, + a, + E[e] + ); + }, + addClass: function (a, H) { + var E = this.attr("class") || ""; + -1 === E.indexOf(a) && + (H || (a = (E + (E ? " " : "") + a).replace(" ", " ")), + this.attr("class", a)); + return this; + }, + hasClass: function (a) { + return -1 !== h(this.element, "class").indexOf(a); + }, + removeClass: function (a) { + h( + this.element, + "class", + (h(this.element, "class") || "").replace(a, "") + ); + return this; + }, + symbolAttr: function (a) { + var E = this; + y( + "x y r start end width height innerR anchorX anchorY".split(" "), + function (g) { + E[g] = K(a[g], E[g]); + } + ); + E.attr({ + d: E.renderer.symbols[E.symbolName](E.x, E.y, E.width, E.height, E), + }); + }, + clip: function (a) { + return this.attr( + "clip-path", + a ? "url(" + this.renderer.url + "#" + a.id + ")" : "none" + ); + }, + crisp: function (a, g) { + var E, + H = {}, + e; + g = g || a.strokeWidth || 0; + e = (Math.round(g) % 2) / 2; + a.x = Math.floor(a.x || this.x || 0) + e; + a.y = Math.floor(a.y || this.y || 0) + e; + a.width = Math.floor((a.width || this.width || 0) - 2 * e); + a.height = Math.floor((a.height || this.height || 0) - 2 * e); + u(a.strokeWidth) && (a.strokeWidth = g); + for (E in a) this[E] !== a[E] && (this[E] = H[E] = a[E]); + return H; + }, + css: function (a) { + var g = this.styles, + E = {}, + k = this.element, + b, + c, + r = ""; + b = !g; + a && a.color && (a.fill = a.color); + if (g) for (c in a) a[c] !== g[c] && ((E[c] = a[c]), (b = !0)); + if (b) { + b = this.textWidth = + (a && + a.width && + "text" === k.nodeName.toLowerCase() && + J(a.width)) || + this.textWidth; + g && (a = t(g, E)); + this.styles = a; + b && !B && this.renderer.forExport && delete a.width; + if (e && !B) v(this.element, a); + else { + g = function (a, g) { + return "-" + g.toLowerCase(); + }; + for (c in a) r += c.replace(/([A-Z])/g, g) + ":" + a[c] + ";"; + h(k, "style", r); + } + this.added && + (b && this.renderer.buildText(this), + a && a.textOutline && this.applyTextOutline(a.textOutline)); + } + return this; + }, + strokeWidth: function () { + return this["stroke-width"] || 0; + }, + on: function (a, g) { + var E = this, + e = E.element; + q && "click" === a + ? ((e.ontouchstart = function (a) { + E.touchEventFired = Date.now(); + a.preventDefault(); + g.call(e, a); + }), + (e.onclick = function (a) { + (-1 === R.navigator.userAgent.indexOf("Android") || + 1100 < Date.now() - (E.touchEventFired || 0)) && + g.call(e, a); + })) + : (e["on" + a] = g); + return this; + }, + setRadialReference: function (a) { + var g = this.renderer.gradients[this.element.gradient]; + this.element.radialReference = a; + g && g.radAttr && g.animate(this.renderer.getRadialAttr(a, g.radAttr)); + return this; + }, + translate: function (a, g) { + return this.attr({ translateX: a, translateY: g }); + }, + invert: function (a) { + this.inverted = a; + this.updateTransform(); + return this; + }, + updateTransform: function () { + var a = this.translateX || 0, + g = this.translateY || 0, + e = this.scaleX, + k = this.scaleY, + b = this.inverted, + c = this.rotation, + B = this.element; + b && ((a += this.attr("width")), (g += this.attr("height"))); + a = ["translate(" + a + "," + g + ")"]; + b + ? a.push("rotate(90) scale(-1,1)") + : c && + a.push( + "rotate(" + + c + + " " + + (B.getAttribute("x") || 0) + + " " + + (B.getAttribute("y") || 0) + + ")" + ); + (u(e) || u(k)) && a.push("scale(" + K(e, 1) + " " + K(k, 1) + ")"); + a.length && B.setAttribute("transform", a.join(" ")); + }, + toFront: function () { + var a = this.element; + a.parentNode.appendChild(a); + return this; + }, + align: function (a, g, e) { + var E, + H, + k, + b, + c = {}; + H = this.renderer; + k = H.alignedObjects; + var B, r; + if (a) { + if ( + ((this.alignOptions = a), (this.alignByTranslate = g), !e || x(e)) + ) + (this.alignTo = E = e || "renderer"), + m(k, this), + k.push(this), + (e = null); + } else + (a = this.alignOptions), + (g = this.alignByTranslate), + (E = this.alignTo); + e = K(e, H[E], H); + E = a.align; + H = a.verticalAlign; + k = (e.x || 0) + (a.x || 0); + b = (e.y || 0) + (a.y || 0); + "right" === E ? (B = 1) : "center" === E && (B = 2); + B && (k += (e.width - (a.width || 0)) / B); + c[g ? "translateX" : "x"] = Math.round(k); + "bottom" === H ? (r = 1) : "middle" === H && (r = 2); + r && (b += (e.height - (a.height || 0)) / r); + c[g ? "translateY" : "y"] = Math.round(b); + this[this.placed ? "animate" : "attr"](c); + this.placed = !0; + this.alignAttr = c; + return this; + }, + getBBox: function (a, g) { + var E, + H = this.renderer, + k, + b = this.element, + c = this.styles, + B, + r = this.textStr, + m, + w = H.cache, + q = H.cacheKeys, + x; + g = K(g, this.rotation); + k = g * d; + B = c && c.fontSize; + void 0 !== r && + ((x = r.toString()), + -1 === x.indexOf("\x3c") && (x = x.replace(/[0-9]/g, "0")), + (x += [ + "", + g || 0, + B, + b.style.width, + b.style["text-overflow"], + ].join())); + x && !a && (E = w[x]); + if (!E) { + if (b.namespaceURI === this.SVG_NS || H.forExport) { + try { + (m = + this.fakeTS && + function (a) { + y( + b.querySelectorAll(".highcharts-text-outline"), + function (g) { + g.style.display = a; + } + ); + }) && m("none"), + (E = b.getBBox + ? t({}, b.getBBox()) + : { width: b.offsetWidth, height: b.offsetHeight }), + m && m(""); + } catch (T) {} + if (!E || 0 > E.width) E = { width: 0, height: 0 }; + } else E = this.htmlGetBBox(); + H.isSVG && + ((a = E.width), + (H = E.height), + e && + c && + "11px" === c.fontSize && + "16.9" === H.toPrecision(3) && + (E.height = H = 14), + g && + ((E.width = + Math.abs(H * Math.sin(k)) + Math.abs(a * Math.cos(k))), + (E.height = + Math.abs(H * Math.cos(k)) + Math.abs(a * Math.sin(k))))); + if (x && 0 < E.height) { + for (; 250 < q.length; ) delete w[q.shift()]; + w[x] || q.push(x); + w[x] = E; + } + } + return E; + }, + show: function (a) { + return this.attr({ visibility: a ? "inherit" : "visible" }); + }, + hide: function () { + return this.attr({ visibility: "hidden" }); + }, + fadeOut: function (a) { + var g = this; + g.animate( + { opacity: 0 }, + { + duration: a || 150, + complete: function () { + g.attr({ y: -9999 }); + }, + } + ); + }, + add: function (a) { + var g = this.renderer, + e = this.element, + E; + a && (this.parentGroup = a); + this.parentInverted = a && a.inverted; + void 0 !== this.textStr && g.buildText(this); + this.added = !0; + if (!a || a.handleZ || this.zIndex) E = this.zIndexSetter(); + E || (a ? a.element : g.box).appendChild(e); + if (this.onAdd) this.onAdd(); + return this; + }, + safeRemoveChild: function (a) { + var g = a.parentNode; + g && g.removeChild(a); + }, + destroy: function () { + var a = this.element || {}, + e = this.renderer.isSVG && "SPAN" === a.nodeName && this.parentGroup, + k, + b; + a.onclick = + a.onmouseout = + a.onmouseover = + a.onmousemove = + a.point = + null; + g(this); + this.clipPath && (this.clipPath = this.clipPath.destroy()); + if (this.stops) { + for (b = 0; b < this.stops.length; b++) + this.stops[b] = this.stops[b].destroy(); + this.stops = null; + } + this.safeRemoveChild(a); + for ( + this.destroyShadows(); + e && e.div && 0 === e.div.childNodes.length; + + ) + (a = e.parentGroup), + this.safeRemoveChild(e.div), + delete e.div, + (e = a); + this.alignTo && m(this.renderer.alignedObjects, this); + for (k in this) delete this[k]; + return null; + }, + shadow: function (a, g, e) { + var E = [], + b, + k, + H = this.element, + c, + B, + r, + m; + if (!a) this.destroyShadows(); + else if (!this.shadows) { + B = K(a.width, 3); + r = (a.opacity || 0.15) / B; + m = this.parentInverted + ? "(-1,-1)" + : "(" + K(a.offsetX, 1) + ", " + K(a.offsetY, 1) + ")"; + for (b = 1; b <= B; b++) + (k = H.cloneNode(0)), + (c = 2 * B + 1 - 2 * b), + h(k, { + isShadow: "true", + stroke: a.color || "#000000", + "stroke-opacity": r * b, + "stroke-width": c, + transform: "translate" + m, + fill: "none", + }), + e && + (h(k, "height", Math.max(h(k, "height") - c, 0)), + (k.cutHeight = c)), + g ? g.element.appendChild(k) : H.parentNode.insertBefore(k, H), + E.push(k); + this.shadows = E; + } + return this; + }, + destroyShadows: function () { + y( + this.shadows || [], + function (a) { + this.safeRemoveChild(a); + }, + this + ); + this.shadows = void 0; + }, + xGetter: function (a) { + "circle" === this.element.nodeName && + ("x" === a ? (a = "cx") : "y" === a && (a = "cy")); + return this._defaultGetter(a); + }, + _defaultGetter: function (a) { + a = K(this[a], this.element ? this.element.getAttribute(a) : null, 0); + /^[\-0-9\.]+$/.test(a) && (a = parseFloat(a)); + return a; + }, + dSetter: function (a, g, e) { + a && a.join && (a = a.join(" ")); + /(NaN| {2}|^$)/.test(a) && (a = "M 0 0"); + e.setAttribute(g, a); + this[g] = a; + }, + dashstyleSetter: function (a) { + var g, + e = this["stroke-width"]; + "inherit" === e && (e = 1); + if ((a = a && a.toLowerCase())) { + a = a + .replace("shortdashdotdot", "3,1,1,1,1,1,") + .replace("shortdashdot", "3,1,1,1") + .replace("shortdot", "1,1,") + .replace("shortdash", "3,1,") + .replace("longdash", "8,3,") + .replace(/dot/g, "1,3,") + .replace("dash", "4,3,") + .replace(/,$/, "") + .split(","); + for (g = a.length; g--; ) a[g] = J(a[g]) * e; + a = a.join(",").replace(/NaN/g, "none"); + this.element.setAttribute("stroke-dasharray", a); + } + }, + alignSetter: function (a) { + this.element.setAttribute( + "text-anchor", + { left: "start", center: "middle", right: "end" }[a] + ); + }, + opacitySetter: function (a, g, e) { + this[g] = a; + e.setAttribute(g, a); + }, + titleSetter: function (a) { + var g = this.element.getElementsByTagName("title")[0]; + g || + ((g = n.createElementNS(this.SVG_NS, "title")), + this.element.appendChild(g)); + g.firstChild && g.removeChild(g.firstChild); + g.appendChild( + n.createTextNode(String(K(a), "").replace(/<[^>]*>/g, "")) + ); + }, + textSetter: function (a) { + a !== this.textStr && + (delete this.bBox, + (this.textStr = a), + this.added && this.renderer.buildText(this)); + }, + fillSetter: function (a, g, e) { + "string" === typeof a + ? e.setAttribute(g, a) + : a && this.colorGradient(a, g, e); + }, + visibilitySetter: function (a, g, e) { + "inherit" === a ? e.removeAttribute(g) : e.setAttribute(g, a); + }, + zIndexSetter: function (a, g) { + var e = this.renderer, + k = this.parentGroup, + b = (k || e).element || e.box, + c, + B = this.element, + H; + c = this.added; + var r; + u(a) && + ((B.zIndex = a), (a = +a), this[g] === a && (c = !1), (this[g] = a)); + if (c) { + (a = this.zIndex) && k && (k.handleZ = !0); + g = b.childNodes; + for (r = 0; r < g.length && !H; r++) + (k = g[r]), + (c = k.zIndex), + k !== B && + (J(c) > a || + (!u(a) && u(c)) || + (0 > a && !u(c) && b !== e.box)) && + (b.insertBefore(B, k), (H = !0)); + H || b.appendChild(B); + } + return H; + }, + _defaultSetter: function (a, g, e) { + e.setAttribute(g, a); + }, + }; + D.prototype.yGetter = D.prototype.xGetter; + D.prototype.translateXSetter = + D.prototype.translateYSetter = + D.prototype.rotationSetter = + D.prototype.verticalAlignSetter = + D.prototype.scaleXSetter = + D.prototype.scaleYSetter = + function (a, g) { + this[g] = a; + this.doTransform = !0; + }; + D.prototype["stroke-widthSetter"] = D.prototype.strokeSetter = function ( + a, + g, + e + ) { + this[g] = a; + this.stroke && this["stroke-width"] + ? (D.prototype.fillSetter.call(this, this.stroke, "stroke", e), + e.setAttribute("stroke-width", this["stroke-width"]), + (this.hasStroke = !0)) + : "stroke-width" === g && + 0 === a && + this.hasStroke && + (e.removeAttribute("stroke"), (this.hasStroke = !1)); + }; + C = a.SVGRenderer = function () { + this.init.apply(this, arguments); + }; + C.prototype = { + Element: D, + SVG_NS: S, + init: function (a, g, e, k, b, c) { + var B; + k = this.createElement("svg") + .attr({ version: "1.1", class: "highcharts-root" }) + .css(this.getStyle(k)); + B = k.element; + a.appendChild(B); + -1 === a.innerHTML.indexOf("xmlns") && h(B, "xmlns", this.SVG_NS); + this.isSVG = !0; + this.box = B; + this.boxWrapper = k; + this.alignedObjects = []; + this.url = + (F || A) && n.getElementsByTagName("base").length + ? R.location.href + .replace(/#.*?$/, "") + .replace(/([\('\)])/g, "\\$1") + .replace(/ /g, "%20") + : ""; + this.createElement("desc") + .add() + .element.appendChild( + n.createTextNode("Created with Highcharts 5.0.6") + ); + this.defs = this.createElement("defs").add(); + this.allowHTML = c; + this.forExport = b; + this.gradients = {}; + this.cache = {}; + this.cacheKeys = []; + this.imgCount = 0; + this.setSize(g, e, !1); + var H; + F && + a.getBoundingClientRect && + ((g = function () { + v(a, { left: 0, top: 0 }); + H = a.getBoundingClientRect(); + v(a, { + left: Math.ceil(H.left) - H.left + "px", + top: Math.ceil(H.top) - H.top + "px", + }); + }), + g(), + (this.unSubPixelFix = G(R, "resize", g))); + }, + getStyle: function (a) { + return (this.style = t( + { + fontFamily: + '"Lucida Grande", "Khmer OS Battambang", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', + fontSize: "12px", + }, + a + )); + }, + setStyle: function (a) { + this.boxWrapper.css(this.getStyle(a)); + }, + isHidden: function () { + return !this.boxWrapper.getBBox().width; + }, + destroy: function () { + var a = this.defs; + this.box = null; + this.boxWrapper = this.boxWrapper.destroy(); + c(this.gradients || {}); + this.gradients = null; + a && (this.defs = a.destroy()); + this.unSubPixelFix && this.unSubPixelFix(); + return (this.alignedObjects = null); + }, + createElement: function (a) { + var g = new this.Element(); + g.init(this, a); + return g; + }, + draw: w, + getRadialAttr: function (a, g) { + return { + cx: a[0] - a[2] / 2 + g.cx * a[2], + cy: a[1] - a[2] / 2 + g.cy * a[2], + r: g.r * a[2], + }; + }, + buildText: function (a) { + for ( + var g = a.element, + e = this, + k = e.forExport, + c = K(a.textStr, "").toString(), + r = -1 !== c.indexOf("\x3c"), + m = g.childNodes, + w, + E, + q, + x, + d = h(g, "x"), + t = a.styles, + A = a.textWidth, + z = t && t.lineHeight, + l = t && t.textOutline, + F = t && "ellipsis" === t.textOverflow, + f = m.length, + u = A && !a.added && this.box, + p = function (a) { + var k; + k = /(px|em)$/.test(a && a.style.fontSize) + ? a.style.fontSize + : (t && t.fontSize) || e.style.fontSize || 12; + return z + ? J(z) + : e.fontMetrics(k, a.getAttribute("style") ? a : g).h; + }; + f--; + + ) + g.removeChild(m[f]); + r || l || F || A || -1 !== c.indexOf(" ") + ? ((w = /<.*class="([^"]+)".*>/), + (E = /<.*style="([^"]+)".*>/), + (q = /<.*href="(http[^"]+)".*>/), + u && u.appendChild(g), + (c = r + ? c + .replace( + /<(b|strong)>/g, + '\x3cspan style\x3d"font-weight:bold"\x3e' + ) + .replace( + /<(i|em)>/g, + '\x3cspan style\x3d"font-style:italic"\x3e' + ) + .replace(//g, "\x3c/span\x3e") + .split(//g) + : [c]), + (c = b(c, function (a) { + return "" !== a; + })), + y(c, function (b, c) { + var r, + H = 0; + b = b + .replace(/^\s+|\s+$/g, "") + .replace(//g, "\x3c/span\x3e|||"); + r = b.split("|||"); + y(r, function (b) { + if ("" !== b || 1 === r.length) { + var m = {}, + l = n.createElementNS(e.SVG_NS, "tspan"), + z, + f; + w.test(b) && ((z = b.match(w)[1]), h(l, "class", z)); + E.test(b) && + ((f = b + .match(E)[1] + .replace(/(;| |^)color([ :])/, "$1fill$2")), + h(l, "style", f)); + q.test(b) && + !k && + (h( + l, + "onclick", + 'location.href\x3d"' + b.match(q)[1] + '"' + ), + v(l, { cursor: "pointer" })); + b = (b.replace(/<(.|\n)*?>/g, "") || " ") + .replace(/</g, "\x3c") + .replace(/>/g, "\x3e"); + if (" " !== b) { + l.appendChild(n.createTextNode(b)); + H ? (m.dx = 0) : c && null !== d && (m.x = d); + h(l, m); + g.appendChild(l); + !H && + c && + (!B && k && v(l, { display: "block" }), h(l, "dy", p(l))); + if (A) { + m = b.replace(/([^\^])-/g, "$1- ").split(" "); + z = "nowrap" === t.whiteSpace; + for ( + var K = 1 < r.length || c || (1 < m.length && !z), + u, + y, + J = [], + M = p(l), + P = a.rotation, + O = b, + N = O.length; + (K || F) && (m.length || J.length); + + ) + (a.rotation = 0), + (u = a.getBBox(!0)), + (y = u.width), + !B && + e.forExport && + (y = e.measureSpanWidth( + l.firstChild.data, + a.styles + )), + (u = y > A), + void 0 === x && (x = u), + F && x + ? ((N /= 2), + "" === O || (!u && 0.5 > N) + ? (m = []) + : ((O = b.substring( + 0, + O.length + (u ? -1 : 1) * Math.ceil(N) + )), + (m = [O + (3 < A ? "\u2026" : "")]), + l.removeChild(l.firstChild))) + : u && 1 !== m.length + ? (l.removeChild(l.firstChild), J.unshift(m.pop())) + : ((m = J), + (J = []), + m.length && + !z && + ((l = n.createElementNS(S, "tspan")), + h(l, { dy: M, x: d }), + f && h(l, "style", f), + g.appendChild(l)), + y > A && (A = y)), + m.length && + l.appendChild( + n.createTextNode(m.join(" ").replace(/- /g, "-")) + ); + a.rotation = P; + } + H++; + } + } + }); + }), + x && a.attr("title", a.textStr), + u && u.removeChild(g), + l && a.applyTextOutline && a.applyTextOutline(l)) + : g.appendChild( + n.createTextNode( + c.replace(/</g, "\x3c").replace(/>/g, "\x3e") + ) + ); + }, + getContrast: function (a) { + a = p(a).rgba; + return 510 < a[0] + a[1] + a[2] ? "#000000" : "#FFFFFF"; + }, + button: function (a, g, b, c, B, r, m, w, q) { + var H = this.label(a, g, b, q, null, null, null, null, "button"), + E = 0; + H.attr(k({ padding: 8, r: 2 }, B)); + var x, d, n, l; + B = k( + { + fill: "#f7f7f7", + stroke: "#cccccc", + "stroke-width": 1, + style: { + color: "#333333", + cursor: "pointer", + fontWeight: "normal", + }, + }, + B + ); + x = B.style; + delete B.style; + r = k(B, { fill: "#e6e6e6" }, r); + d = r.style; + delete r.style; + m = k( + B, + { fill: "#e6ebf5", style: { color: "#000000", fontWeight: "bold" } }, + m + ); + n = m.style; + delete m.style; + w = k(B, { style: { color: "#cccccc" } }, w); + l = w.style; + delete w.style; + G(H.element, e ? "mouseover" : "mouseenter", function () { + 3 !== E && H.setState(1); + }); + G(H.element, e ? "mouseout" : "mouseleave", function () { + 3 !== E && H.setState(E); + }); + H.setState = function (a) { + 1 !== a && (H.state = E = a); + H.removeClass( + /highcharts-button-(normal|hover|pressed|disabled)/ + ).addClass( + "highcharts-button-" + + ["normal", "hover", "pressed", "disabled"][a || 0] + ); + H.attr([B, r, m, w][a || 0]).css([x, d, n, l][a || 0]); + }; + H.attr(B).css(t({ cursor: "default" }, x)); + return H.on("click", function (a) { + 3 !== E && c.call(H, a); + }); + }, + crispLine: function (a, g) { + a[1] === a[4] && (a[1] = a[4] = Math.round(a[1]) - (g % 2) / 2); + a[2] === a[5] && (a[2] = a[5] = Math.round(a[2]) + (g % 2) / 2); + return a; + }, + path: function (a) { + var g = { fill: "none" }; + z(a) ? (g.d = a) : r(a) && t(g, a); + return this.createElement("path").attr(g); + }, + circle: function (a, g, e) { + a = r(a) ? a : { x: a, y: g, r: e }; + g = this.createElement("circle"); + g.xSetter = g.ySetter = function (a, g, e) { + e.setAttribute("c" + g, a); + }; + return g.attr(a); + }, + arc: function (a, g, e, b, k, c) { + r(a) && + ((g = a.y), + (e = a.r), + (b = a.innerR), + (k = a.start), + (c = a.end), + (a = a.x)); + a = this.symbol("arc", a || 0, g || 0, e || 0, e || 0, { + innerR: b || 0, + start: k || 0, + end: c || 0, + }); + a.r = e; + return a; + }, + rect: function (a, g, e, b, k, c) { + k = r(a) ? a.r : k; + var B = this.createElement("rect"); + a = r(a) + ? a + : void 0 === a + ? {} + : { x: a, y: g, width: Math.max(e, 0), height: Math.max(b, 0) }; + void 0 !== c && ((a.strokeWidth = c), (a = B.crisp(a))); + a.fill = "none"; + k && (a.r = k); + B.rSetter = function (a, g, e) { + h(e, { rx: a, ry: a }); + }; + return B.attr(a); + }, + setSize: function (a, g, e) { + var b = this.alignedObjects, + k = b.length; + this.width = a; + this.height = g; + for ( + this.boxWrapper.animate( + { width: a, height: g }, + { + step: function () { + this.attr({ + viewBox: + "0 0 " + this.attr("width") + " " + this.attr("height"), + }); + }, + duration: K(e, !0) ? void 0 : 0, + } + ); + k--; + + ) + b[k].align(); + }, + g: function (a) { + var g = this.createElement("g"); + return a ? g.attr({ class: "highcharts-" + a }) : g; + }, + image: function (a, g, e, b, k) { + var c = { preserveAspectRatio: "none" }; + 1 < arguments.length && t(c, { x: g, y: e, width: b, height: k }); + c = this.createElement("image").attr(c); + c.element.setAttributeNS + ? c.element.setAttributeNS("http://www.w3.org/1999/xlink", "href", a) + : c.element.setAttribute("hc-svg-href", a); + return c; + }, + symbol: function (a, g, e, b, k, c) { + var B = this, + r, + H = this.symbols[a], + m = u(g) && H && H(Math.round(g), Math.round(e), b, k, c), + w = /^url\((.*?)\)$/, + q, + x; + H + ? ((r = this.path(m)), + r.attr("fill", "none"), + t(r, { symbolName: a, x: g, y: e, width: b, height: k }), + c && t(r, c)) + : w.test(a) && + ((q = a.match(w)[1]), + (r = this.image(q)), + (r.imgwidth = K(M[q] && M[q].width, c && c.width)), + (r.imgheight = K(M[q] && M[q].height, c && c.height)), + (x = function () { + r.attr({ width: r.width, height: r.height }); + }), + y(["width", "height"], function (a) { + r[a + "Setter"] = function (a, g) { + var e = {}, + b = this["img" + g], + k = "width" === g ? "translateX" : "translateY"; + this[g] = a; + u(b) && + (this.element && this.element.setAttribute(g, b), + this.alignByTranslate || + ((e[k] = ((this[g] || 0) - b) / 2), this.attr(e))); + }; + }), + u(g) && r.attr({ x: g, y: e }), + (r.isImg = !0), + u(r.imgwidth) && u(r.imgheight) + ? x() + : (r.attr({ width: 0, height: 0 }), + l("img", { + onload: function () { + var a = f[B.chartIndex]; + 0 === this.width && + (v(this, { position: "absolute", top: "-999em" }), + n.body.appendChild(this)); + M[q] = { width: this.width, height: this.height }; + r.imgwidth = this.width; + r.imgheight = this.height; + r.element && x(); + this.parentNode && this.parentNode.removeChild(this); + B.imgCount--; + if (!B.imgCount && a && a.onload) a.onload(); + }, + src: q, + }), + this.imgCount++)); + return r; + }, + symbols: { + circle: function (a, g, e, b) { + var k = 0.166 * e; + return [ + "M", + a + e / 2, + g, + "C", + a + e + k, + g, + a + e + k, + g + b, + a + e / 2, + g + b, + "C", + a - k, + g + b, + a - k, + g, + a + e / 2, + g, + "Z", + ]; + }, + square: function (a, g, e, b) { + return ["M", a, g, "L", a + e, g, a + e, g + b, a, g + b, "Z"]; + }, + triangle: function (a, g, e, b) { + return ["M", a + e / 2, g, "L", a + e, g + b, a, g + b, "Z"]; + }, + "triangle-down": function (a, g, e, b) { + return ["M", a, g, "L", a + e, g, a + e / 2, g + b, "Z"]; + }, + diamond: function (a, g, e, b) { + return [ + "M", + a + e / 2, + g, + "L", + a + e, + g + b / 2, + a + e / 2, + g + b, + a, + g + b / 2, + "Z", + ]; + }, + arc: function (a, g, e, b, k) { + var c = k.start; + e = k.r || e || b; + var B = k.end - 0.001; + b = k.innerR; + var r = k.open, + m = Math.cos(c), + H = Math.sin(c), + w = Math.cos(B), + B = Math.sin(B); + k = k.end - c < Math.PI ? 0 : 1; + return [ + "M", + a + e * m, + g + e * H, + "A", + e, + e, + 0, + k, + 1, + a + e * w, + g + e * B, + r ? "M" : "L", + a + b * w, + g + b * B, + "A", + b, + b, + 0, + k, + 0, + a + b * m, + g + b * H, + r ? "" : "Z", + ]; + }, + callout: function (a, g, e, b, k) { + var c = Math.min((k && k.r) || 0, e, b), + B = c + 6, + r = k && k.anchorX; + k = k && k.anchorY; + var m; + m = [ + "M", + a + c, + g, + "L", + a + e - c, + g, + "C", + a + e, + g, + a + e, + g, + a + e, + g + c, + "L", + a + e, + g + b - c, + "C", + a + e, + g + b, + a + e, + g + b, + a + e - c, + g + b, + "L", + a + c, + g + b, + "C", + a, + g + b, + a, + g + b, + a, + g + b - c, + "L", + a, + g + c, + "C", + a, + g, + a, + g, + a + c, + g, + ]; + r && r > e + ? k > g + B && k < g + b - B + ? m.splice( + 13, + 3, + "L", + a + e, + k - 6, + a + e + 6, + k, + a + e, + k + 6, + a + e, + g + b - c + ) + : m.splice( + 13, + 3, + "L", + a + e, + b / 2, + r, + k, + a + e, + b / 2, + a + e, + g + b - c + ) + : r && 0 > r + ? k > g + B && k < g + b - B + ? m.splice(33, 3, "L", a, k + 6, a - 6, k, a, k - 6, a, g + c) + : m.splice(33, 3, "L", a, b / 2, r, k, a, b / 2, a, g + c) + : k && k > b && r > a + B && r < a + e - B + ? m.splice( + 23, + 3, + "L", + r + 6, + g + b, + r, + g + b + 6, + r - 6, + g + b, + a + c, + g + b + ) + : k && + 0 > k && + r > a + B && + r < a + e - B && + m.splice(3, 3, "L", r - 6, g, r, g - 6, r + 6, g, e - c, g); + return m; + }, + }, + clipRect: function (g, e, b, k) { + var c = a.uniqueKey(), + B = this.createElement("clipPath").attr({ id: c }).add(this.defs); + g = this.rect(g, e, b, k, 0).add(B); + g.id = c; + g.clipPath = B; + g.count = 0; + return g; + }, + text: function (a, g, e, b) { + var k = !B && this.forExport, + c = {}; + if (b && (this.allowHTML || !this.forExport)) return this.html(a, g, e); + c.x = Math.round(g || 0); + e && (c.y = Math.round(e)); + if (a || 0 === a) c.text = a; + a = this.createElement("text").attr(c); + k && a.css({ position: "absolute" }); + b || + (a.xSetter = function (a, g, e) { + var b = e.getElementsByTagName("tspan"), + k, + c = e.getAttribute(g), + B; + for (B = 0; B < b.length; B++) + (k = b[B]), k.getAttribute(g) === c && k.setAttribute(g, a); + e.setAttribute(g, a); + }); + return a; + }, + fontMetrics: function (a, g) { + a = + a || + (g && g.style && g.style.fontSize) || + (this.style && this.style.fontSize); + a = /px/.test(a) + ? J(a) + : /em/.test(a) + ? parseFloat(a) * (g ? this.fontMetrics(null, g.parentNode).f : 16) + : 12; + g = 24 > a ? a + 3 : Math.round(1.2 * a); + return { h: g, b: Math.round(0.8 * g), f: a }; + }, + rotCorr: function (a, g, e) { + var b = a; + g && e && (b = Math.max(b * Math.cos(g * d), 4)); + return { x: (-a / 3) * Math.sin(g * d), y: b }; + }, + label: function (a, g, e, b, c, B, r, m, w) { + var q = this, + x = q.g("button" !== w && "label"), + d = (x.text = q.text("", 0, 0, r).attr({ zIndex: 1 })), + H, + n, + l = 0, + A = 3, + z = 0, + F, + f, + K, + p, + J, + h = {}, + M, + S, + E = /^url\((.*?)\)$/.test(b), + v = E, + P, + R, + O, + Q; + w && x.addClass("highcharts-" + w); + v = E; + P = function () { + return ((M || 0) % 2) / 2; + }; + R = function () { + var a = d.element.style, + g = {}; + n = + (void 0 === F || void 0 === f || J) && u(d.textStr) && d.getBBox(); + x.width = (F || n.width || 0) + 2 * A + z; + x.height = (f || n.height || 0) + 2 * A; + S = A + q.fontMetrics(a && a.fontSize, d).b; + v && + (H || + ((x.box = H = q.symbols[b] || E ? q.symbol(b) : q.rect()), + H.addClass( + ("button" === w ? "" : "highcharts-label-box") + + (w ? " highcharts-" + w + "-box" : "") + ), + H.add(x), + (a = P()), + (g.x = a), + (g.y = (m ? -S : 0) + a)), + (g.width = Math.round(x.width)), + (g.height = Math.round(x.height)), + H.attr(t(g, h)), + (h = {})); + }; + O = function () { + var a = z + A, + g; + g = m ? 0 : S; + u(F) && + n && + ("center" === J || "right" === J) && + (a += { center: 0.5, right: 1 }[J] * (F - n.width)); + if (a !== d.x || g !== d.y) + d.attr("x", a), void 0 !== g && d.attr("y", g); + d.x = a; + d.y = g; + }; + Q = function (a, g) { + H ? H.attr(a, g) : (h[a] = g); + }; + x.onAdd = function () { + d.add(x); + x.attr({ text: a || 0 === a ? a : "", x: g, y: e }); + H && u(c) && x.attr({ anchorX: c, anchorY: B }); + }; + x.widthSetter = function (a) { + F = a; + }; + x.heightSetter = function (a) { + f = a; + }; + x["text-alignSetter"] = function (a) { + J = a; + }; + x.paddingSetter = function (a) { + u(a) && a !== A && ((A = x.padding = a), O()); + }; + x.paddingLeftSetter = function (a) { + u(a) && a !== z && ((z = a), O()); + }; + x.alignSetter = function (a) { + a = { left: 0, center: 0.5, right: 1 }[a]; + a !== l && ((l = a), n && x.attr({ x: K })); + }; + x.textSetter = function (a) { + void 0 !== a && d.textSetter(a); + R(); + O(); + }; + x["stroke-widthSetter"] = function (a, g) { + a && (v = !0); + M = this["stroke-width"] = a; + Q(g, a); + }; + x.strokeSetter = + x.fillSetter = + x.rSetter = + function (a, g) { + "fill" === g && a && (v = !0); + Q(g, a); + }; + x.anchorXSetter = function (a, g) { + c = a; + Q(g, Math.round(a) - P() - K); + }; + x.anchorYSetter = function (a, g) { + B = a; + Q(g, a - p); + }; + x.xSetter = function (a) { + x.x = a; + l && (a -= l * ((F || n.width) + 2 * A)); + K = Math.round(a); + x.attr("translateX", K); + }; + x.ySetter = function (a) { + p = x.y = Math.round(a); + x.attr("translateY", p); + }; + var V = x.css; + return t(x, { + css: function (a) { + if (a) { + var g = {}; + a = k(a); + y(x.textProps, function (e) { + void 0 !== a[e] && ((g[e] = a[e]), delete a[e]); + }); + d.css(g); + } + return V.call(x, a); + }, + getBBox: function () { + return { + width: n.width + 2 * A, + height: n.height + 2 * A, + x: n.x - A, + y: n.y - A, + }; + }, + shadow: function (a) { + a && (R(), H && H.shadow(a)); + return x; + }, + destroy: function () { + N(x.element, "mouseenter"); + N(x.element, "mouseleave"); + d && (d = d.destroy()); + H && (H = H.destroy()); + D.prototype.destroy.call(x); + x = q = R = O = Q = null; + }, + }); + }, + }; + a.Renderer = C; + })(L); + (function (a) { + var D = a.attr, + C = a.createElement, + G = a.css, + I = a.defined, + h = a.each, + f = a.extend, + p = a.isFirefox, + v = a.isMS, + l = a.isWebKit, + u = a.pInt, + d = a.SVGRenderer, + c = a.win, + n = a.wrap; + f(a.SVGElement.prototype, { + htmlCss: function (a) { + var c = this.element; + if ((c = a && "SPAN" === c.tagName && a.width)) + delete a.width, (this.textWidth = c), this.updateTransform(); + a && + "ellipsis" === a.textOverflow && + ((a.whiteSpace = "nowrap"), (a.overflow = "hidden")); + this.styles = f(this.styles, a); + G(this.element, a); + return this; + }, + htmlGetBBox: function () { + var a = this.element; + "text" === a.nodeName && (a.style.position = "absolute"); + return { + x: a.offsetLeft, + y: a.offsetTop, + width: a.offsetWidth, + height: a.offsetHeight, + }; + }, + htmlUpdateTransform: function () { + if (this.added) { + var a = this.renderer, + c = this.element, + m = this.translateX || 0, + b = this.translateY || 0, + q = this.x || 0, + d = this.y || 0, + n = this.textAlign || "left", + e = { left: 0, center: 0.5, right: 1 }[n], + r = this.styles; + G(c, { marginLeft: m, marginTop: b }); + this.shadows && + h(this.shadows, function (a) { + G(a, { marginLeft: m + 1, marginTop: b + 1 }); + }); + this.inverted && + h(c.childNodes, function (e) { + a.invertChild(e, c); + }); + if ("SPAN" === c.tagName) { + var x = this.rotation, + A = u(this.textWidth), + k = r && r.whiteSpace, + w = [x, n, c.innerHTML, this.textWidth, this.textAlign].join(); + w !== this.cTT && + ((r = a.fontMetrics(c.style.fontSize).b), + I(x) && this.setSpanRotation(x, e, r), + G(c, { width: "", whiteSpace: k || "nowrap" }), + c.offsetWidth > A && + /[ \-]/.test(c.textContent || c.innerText) && + G(c, { + width: A + "px", + display: "block", + whiteSpace: k || "normal", + }), + this.getSpanCorrection(c.offsetWidth, r, e, x, n)); + G(c, { + left: q + (this.xCorr || 0) + "px", + top: d + (this.yCorr || 0) + "px", + }); + l && (r = c.offsetHeight); + this.cTT = w; + } + } else this.alignOnAdd = !0; + }, + setSpanRotation: function (a, d, m) { + var b = {}, + q = v + ? "-ms-transform" + : l + ? "-webkit-transform" + : p + ? "MozTransform" + : c.opera + ? "-o-transform" + : ""; + b[q] = b.transform = "rotate(" + a + "deg)"; + b[q + (p ? "Origin" : "-origin")] = b.transformOrigin = + 100 * d + "% " + m + "px"; + G(this.element, b); + }, + getSpanCorrection: function (a, c, m) { + this.xCorr = -a * m; + this.yCorr = -c; + }, + }); + f(d.prototype, { + html: function (a, c, m) { + var b = this.createElement("span"), + q = b.element, + d = b.renderer, + l = d.isSVG, + e = function (a, e) { + h(["opacity", "visibility"], function (b) { + n(a, b + "Setter", function (a, b, c, r) { + a.call(this, b, c, r); + e[c] = b; + }); + }); + }; + b.textSetter = function (a) { + a !== q.innerHTML && delete this.bBox; + q.innerHTML = this.textStr = a; + b.htmlUpdateTransform(); + }; + l && e(b, b.element.style); + b.xSetter = + b.ySetter = + b.alignSetter = + b.rotationSetter = + function (a, e) { + "align" === e && (e = "textAlign"); + b[e] = a; + b.htmlUpdateTransform(); + }; + b.attr({ text: a, x: Math.round(c), y: Math.round(m) }).css({ + fontFamily: this.style.fontFamily, + fontSize: this.style.fontSize, + position: "absolute", + }); + q.style.whiteSpace = "nowrap"; + b.css = b.htmlCss; + l && + (b.add = function (a) { + var c, + r = d.box.parentNode, + k = []; + if ((this.parentGroup = a)) { + if (((c = a.div), !c)) { + for (; a; ) k.push(a), (a = a.parentGroup); + h(k.reverse(), function (a) { + var m, + x = D(a.element, "class"); + x && (x = { className: x }); + c = a.div = + a.div || + C( + "div", + x, + { + position: "absolute", + left: (a.translateX || 0) + "px", + top: (a.translateY || 0) + "px", + display: a.display, + opacity: a.opacity, + pointerEvents: a.styles && a.styles.pointerEvents, + }, + c || r + ); + m = c.style; + f(a, { + on: function () { + b.on.apply({ element: k[0].div }, arguments); + return a; + }, + translateXSetter: function (e, g) { + m.left = e + "px"; + a[g] = e; + a.doTransform = !0; + }, + translateYSetter: function (e, g) { + m.top = e + "px"; + a[g] = e; + a.doTransform = !0; + }, + }); + e(a, m); + }); + } + } else c = r; + c.appendChild(q); + b.added = !0; + b.alignOnAdd && b.htmlUpdateTransform(); + return b; + }); + return b; + }, + }); + })(L); + (function (a) { + var D, + C, + G = a.createElement, + I = a.css, + h = a.defined, + f = a.deg2rad, + p = a.discardElement, + v = a.doc, + l = a.each, + u = a.erase, + d = a.extend; + D = a.extendClass; + var c = a.isArray, + n = a.isNumber, + y = a.isObject, + t = a.merge; + C = a.noop; + var m = a.pick, + b = a.pInt, + q = a.SVGElement, + z = a.SVGRenderer, + F = a.win; + a.svg || + ((C = { + docMode8: v && 8 === v.documentMode, + init: function (a, b) { + var e = ["\x3c", b, ' filled\x3d"f" stroked\x3d"f"'], + c = ["position: ", "absolute", ";"], + k = "div" === b; + ("shape" === b || k) && c.push("left:0;top:0;width:1px;height:1px;"); + c.push("visibility: ", k ? "hidden" : "visible"); + e.push(' style\x3d"', c.join(""), '"/\x3e'); + b && + ((e = k || "span" === b || "img" === b ? e.join("") : a.prepVML(e)), + (this.element = G(e))); + this.renderer = a; + }, + add: function (a) { + var e = this.renderer, + b = this.element, + c = e.box, + k = a && a.inverted, + c = a ? a.element || a : c; + a && (this.parentGroup = a); + k && e.invertChild(b, c); + c.appendChild(b); + this.added = !0; + this.alignOnAdd && + !this.deferUpdateTransform && + this.updateTransform(); + if (this.onAdd) this.onAdd(); + this.className && this.attr("class", this.className); + return this; + }, + updateTransform: q.prototype.htmlUpdateTransform, + setSpanRotation: function () { + var a = this.rotation, + b = Math.cos(a * f), + c = Math.sin(a * f); + I(this.element, { + filter: a + ? [ + "progid:DXImageTransform.Microsoft.Matrix(M11\x3d", + b, + ", M12\x3d", + -c, + ", M21\x3d", + c, + ", M22\x3d", + b, + ", sizingMethod\x3d'auto expand')", + ].join("") + : "none", + }); + }, + getSpanCorrection: function (a, b, c, q, k) { + var e = q ? Math.cos(q * f) : 1, + r = q ? Math.sin(q * f) : 0, + x = m(this.elemHeight, this.element.offsetHeight), + d; + this.xCorr = 0 > e && -a; + this.yCorr = 0 > r && -x; + d = 0 > e * r; + this.xCorr += r * b * (d ? 1 - c : c); + this.yCorr -= e * b * (q ? (d ? c : 1 - c) : 1); + k && + "left" !== k && + ((this.xCorr -= a * c * (0 > e ? -1 : 1)), + q && (this.yCorr -= x * c * (0 > r ? -1 : 1)), + I(this.element, { textAlign: k })); + }, + pathToVML: function (a) { + for (var e = a.length, b = []; e--; ) + n(a[e]) + ? (b[e] = Math.round(10 * a[e]) - 5) + : "Z" === a[e] + ? (b[e] = "x") + : ((b[e] = a[e]), + !a.isArc || + ("wa" !== a[e] && "at" !== a[e]) || + (b[e + 5] === b[e + 7] && + (b[e + 7] += a[e + 7] > a[e + 5] ? 1 : -1), + b[e + 6] === b[e + 8] && + (b[e + 8] += a[e + 8] > a[e + 6] ? 1 : -1))); + return b.join(" ") || "x"; + }, + clip: function (a) { + var e = this, + b; + a + ? ((b = a.members), + u(b, e), + b.push(e), + (e.destroyClip = function () { + u(b, e); + }), + (a = a.getCSS(e))) + : (e.destroyClip && e.destroyClip(), + (a = { clip: e.docMode8 ? "inherit" : "rect(auto)" })); + return e.css(a); + }, + css: q.prototype.htmlCss, + safeRemoveChild: function (a) { + a.parentNode && p(a); + }, + destroy: function () { + this.destroyClip && this.destroyClip(); + return q.prototype.destroy.apply(this); + }, + on: function (a, b) { + this.element["on" + a] = function () { + var a = F.event; + a.target = a.srcElement; + b(a); + }; + return this; + }, + cutOffPath: function (a, c) { + var e; + a = a.split(/[ ,]/); + e = a.length; + if (9 === e || 11 === e) a[e - 4] = a[e - 2] = b(a[e - 2]) - 10 * c; + return a.join(" "); + }, + shadow: function (a, c, q) { + var e = [], + k, + r = this.element, + d = this.renderer, + x, + n = r.style, + g, + B = r.path, + l, + t, + z, + f; + B && "string" !== typeof B.value && (B = "x"); + t = B; + if (a) { + z = m(a.width, 3); + f = (a.opacity || 0.15) / z; + for (k = 1; 3 >= k; k++) + (l = 2 * z + 1 - 2 * k), + q && (t = this.cutOffPath(B.value, l + 0.5)), + (g = [ + '\x3cshape isShadow\x3d"true" strokeweight\x3d"', + l, + '" filled\x3d"false" path\x3d"', + t, + '" coordsize\x3d"10 10" style\x3d"', + r.style.cssText, + '" /\x3e', + ]), + (x = G(d.prepVML(g), null, { + left: b(n.left) + m(a.offsetX, 1), + top: b(n.top) + m(a.offsetY, 1), + })), + q && (x.cutOff = l + 1), + (g = [ + '\x3cstroke color\x3d"', + a.color || "#000000", + '" opacity\x3d"', + f * k, + '"/\x3e', + ]), + G(d.prepVML(g), null, null, x), + c ? c.element.appendChild(x) : r.parentNode.insertBefore(x, r), + e.push(x); + this.shadows = e; + } + return this; + }, + updateShadows: C, + setAttr: function (a, b) { + this.docMode8 + ? (this.element[a] = b) + : this.element.setAttribute(a, b); + }, + classSetter: function (a) { + (this.added ? this.element : this).className = a; + }, + dashstyleSetter: function (a, b, c) { + (c.getElementsByTagName("stroke")[0] || + G(this.renderer.prepVML(["\x3cstroke/\x3e"]), null, null, c))[b] = + a || "solid"; + this[b] = a; + }, + dSetter: function (a, b, c) { + var e = this.shadows; + a = a || []; + this.d = a.join && a.join(" "); + c.path = a = this.pathToVML(a); + if (e) + for (c = e.length; c--; ) + e[c].path = e[c].cutOff ? this.cutOffPath(a, e[c].cutOff) : a; + this.setAttr(b, a); + }, + fillSetter: function (a, b, c) { + var e = c.nodeName; + "SPAN" === e + ? (c.style.color = a) + : "IMG" !== e && + ((c.filled = "none" !== a), + this.setAttr("fillcolor", this.renderer.color(a, c, b, this))); + }, + "fill-opacitySetter": function (a, b, c) { + G( + this.renderer.prepVML([ + "\x3c", + b.split("-")[0], + ' opacity\x3d"', + a, + '"/\x3e', + ]), + null, + null, + c + ); + }, + opacitySetter: C, + rotationSetter: function (a, b, c) { + c = c.style; + this[b] = c[b] = a; + c.left = -Math.round(Math.sin(a * f) + 1) + "px"; + c.top = Math.round(Math.cos(a * f)) + "px"; + }, + strokeSetter: function (a, b, c) { + this.setAttr("strokecolor", this.renderer.color(a, c, b, this)); + }, + "stroke-widthSetter": function (a, b, c) { + c.stroked = !!a; + this[b] = a; + n(a) && (a += "px"); + this.setAttr("strokeweight", a); + }, + titleSetter: function (a, b) { + this.setAttr(b, a); + }, + visibilitySetter: function (a, b, c) { + "inherit" === a && (a = "visible"); + this.shadows && + l(this.shadows, function (c) { + c.style[b] = a; + }); + "DIV" === c.nodeName && + ((a = "hidden" === a ? "-999em" : 0), + this.docMode8 || (c.style[b] = a ? "visible" : "hidden"), + (b = "top")); + c.style[b] = a; + }, + xSetter: function (a, b, c) { + this[b] = a; + "x" === b ? (b = "left") : "y" === b && (b = "top"); + this.updateClipping + ? ((this[b] = a), this.updateClipping()) + : (c.style[b] = a); + }, + zIndexSetter: function (a, b, c) { + c.style[b] = a; + }, + }), + (C["stroke-opacitySetter"] = C["fill-opacitySetter"]), + (a.VMLElement = C = D(q, C)), + (C.prototype.ySetter = + C.prototype.widthSetter = + C.prototype.heightSetter = + C.prototype.xSetter), + (C = { + Element: C, + isIE8: -1 < F.navigator.userAgent.indexOf("MSIE 8.0"), + init: function (a, b, c) { + var e, k; + this.alignedObjects = []; + e = this.createElement("div").css({ position: "relative" }); + k = e.element; + a.appendChild(e.element); + this.isVML = !0; + this.box = k; + this.boxWrapper = e; + this.gradients = {}; + this.cache = {}; + this.cacheKeys = []; + this.imgCount = 0; + this.setSize(b, c, !1); + if (!v.namespaces.hcv) { + v.namespaces.add("hcv", "urn:schemas-microsoft-com:vml"); + try { + v.createStyleSheet().cssText = + "hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "; + } catch (w) { + v.styleSheets[0].cssText += + "hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "; + } + } + }, + isHidden: function () { + return !this.box.offsetWidth; + }, + clipRect: function (a, b, c, m) { + var e = this.createElement(), + r = y(a); + return d(e, { + members: [], + count: 0, + left: (r ? a.x : a) + 1, + top: (r ? a.y : b) + 1, + width: (r ? a.width : c) - 1, + height: (r ? a.height : m) - 1, + getCSS: function (a) { + var b = a.element, + c = b.nodeName, + g = a.inverted, + e = this.top - ("shape" === c ? b.offsetTop : 0), + k = this.left, + b = k + this.width, + m = e + this.height, + e = { + clip: + "rect(" + + Math.round(g ? k : e) + + "px," + + Math.round(g ? m : b) + + "px," + + Math.round(g ? b : m) + + "px," + + Math.round(g ? e : k) + + "px)", + }; + !g && + a.docMode8 && + "DIV" === c && + d(e, { width: b + "px", height: m + "px" }); + return e; + }, + updateClipping: function () { + l(e.members, function (a) { + a.element && a.css(e.getCSS(a)); + }); + }, + }); + }, + color: function (b, c, m, q) { + var e = this, + r, + d = /^rgba/, + n, + x, + g = "none"; + b && b.linearGradient + ? (x = "gradient") + : b && b.radialGradient && (x = "pattern"); + if (x) { + var B, + t, + z = b.linearGradient || b.radialGradient, + f, + F, + H, + A, + u, + p = ""; + b = b.stops; + var h, + y = [], + v = function () { + n = [ + '\x3cfill colors\x3d"' + y.join(",") + '" opacity\x3d"', + H, + '" o:opacity2\x3d"', + F, + '" type\x3d"', + x, + '" ', + p, + 'focus\x3d"100%" method\x3d"any" /\x3e', + ]; + G(e.prepVML(n), null, null, c); + }; + f = b[0]; + h = b[b.length - 1]; + 0 < f[0] && b.unshift([0, f[1]]); + 1 > h[0] && b.push([1, h[1]]); + l(b, function (g, b) { + d.test(g[1]) + ? ((r = a.color(g[1])), (B = r.get("rgb")), (t = r.get("a"))) + : ((B = g[1]), (t = 1)); + y.push(100 * g[0] + "% " + B); + b ? ((H = t), (A = B)) : ((F = t), (u = B)); + }); + if ("fill" === m) + if ("gradient" === x) + (m = z.x1 || z[0] || 0), + (b = z.y1 || z[1] || 0), + (f = z.x2 || z[2] || 0), + (z = z.y2 || z[3] || 0), + (p = + 'angle\x3d"' + + (90 - (180 * Math.atan((z - b) / (f - m))) / Math.PI) + + '"'), + v(); + else { + var g = z.r, + C = 2 * g, + D = 2 * g, + I = z.cx, + U = z.cy, + L = c.radialReference, + T, + g = function () { + L && + ((T = q.getBBox()), + (I += (L[0] - T.x) / T.width - 0.5), + (U += (L[1] - T.y) / T.height - 0.5), + (C *= L[2] / T.width), + (D *= L[2] / T.height)); + p = + 'src\x3d"' + + a.getOptions().global.VMLRadialGradientURL + + '" size\x3d"' + + C + + "," + + D + + '" origin\x3d"0.5,0.5" position\x3d"' + + I + + "," + + U + + '" color2\x3d"' + + u + + '" '; + v(); + }; + q.added ? g() : (q.onAdd = g); + g = A; + } + else g = B; + } else + d.test(b) && "IMG" !== c.tagName + ? ((r = a.color(b)), + q[m + "-opacitySetter"](r.get("a"), m, c), + (g = r.get("rgb"))) + : ((g = c.getElementsByTagName(m)), + g.length && ((g[0].opacity = 1), (g[0].type = "solid")), + (g = b)); + return g; + }, + prepVML: function (a) { + var b = this.isIE8; + a = a.join(""); + b + ? ((a = a.replace( + "/\x3e", + ' xmlns\x3d"urn:schemas-microsoft-com:vml" /\x3e' + )), + (a = + -1 === a.indexOf('style\x3d"') + ? a.replace( + "/\x3e", + ' style\x3d"display:inline-block;behavior:url(#default#VML);" /\x3e' + ) + : a.replace( + 'style\x3d"', + 'style\x3d"display:inline-block;behavior:url(#default#VML);' + ))) + : (a = a.replace("\x3c", "\x3chcv:")); + return a; + }, + text: z.prototype.html, + path: function (a) { + var b = { coordsize: "10 10" }; + c(a) ? (b.d = a) : y(a) && d(b, a); + return this.createElement("shape").attr(b); + }, + circle: function (a, b, c) { + var e = this.symbol("circle"); + y(a) && ((c = a.r), (b = a.y), (a = a.x)); + e.isCircle = !0; + e.r = c; + return e.attr({ x: a, y: b }); + }, + g: function (a) { + var b; + a && (b = { className: "highcharts-" + a, class: "highcharts-" + a }); + return this.createElement("div").attr(b); + }, + image: function (a, b, c, m, k) { + var e = this.createElement("img").attr({ src: a }); + 1 < arguments.length && e.attr({ x: b, y: c, width: m, height: k }); + return e; + }, + createElement: function (a) { + return "rect" === a + ? this.symbol(a) + : z.prototype.createElement.call(this, a); + }, + invertChild: function (a, c) { + var e = this; + c = c.style; + var m = "IMG" === a.tagName && a.style; + I(a, { + flip: "x", + left: b(c.width) - (m ? b(m.top) : 1), + top: b(c.height) - (m ? b(m.left) : 1), + rotation: -90, + }); + l(a.childNodes, function (b) { + e.invertChild(b, a); + }); + }, + symbols: { + arc: function (a, b, c, m, k) { + var e = k.start, + q = k.end, + d = k.r || c || m; + c = k.innerR; + m = Math.cos(e); + var r = Math.sin(e), + g = Math.cos(q), + B = Math.sin(q); + if (0 === q - e) return ["x"]; + e = [ + "wa", + a - d, + b - d, + a + d, + b + d, + a + d * m, + b + d * r, + a + d * g, + b + d * B, + ]; + k.open && !c && e.push("e", "M", a, b); + e.push( + "at", + a - c, + b - c, + a + c, + b + c, + a + c * g, + b + c * B, + a + c * m, + b + c * r, + "x", + "e" + ); + e.isArc = !0; + return e; + }, + circle: function (a, b, c, m, k) { + k && h(k.r) && (c = m = 2 * k.r); + k && k.isCircle && ((a -= c / 2), (b -= m / 2)); + return [ + "wa", + a, + b, + a + c, + b + m, + a + c, + b + m / 2, + a + c, + b + m / 2, + "e", + ]; + }, + rect: function (a, b, c, m, k) { + return z.prototype.symbols[h(k) && k.r ? "callout" : "square"].call( + 0, + a, + b, + c, + m, + k + ); + }, + }, + }), + (a.VMLRenderer = D = + function () { + this.init.apply(this, arguments); + }), + (D.prototype = t(z.prototype, C)), + (a.Renderer = D)); + z.prototype.measureSpanWidth = function (a, b) { + var c = v.createElement("span"); + a = v.createTextNode(a); + c.appendChild(a); + I(c, b); + this.box.appendChild(c); + b = c.offsetWidth; + p(c); + return b; + }; + })(L); + (function (a) { + function D() { + var h = a.defaultOptions.global, + l, + u = h.useUTC, + d = u ? "getUTC" : "get", + c = u ? "setUTC" : "set"; + a.Date = l = h.Date || p.Date; + l.hcTimezoneOffset = u && h.timezoneOffset; + l.hcGetTimezoneOffset = u && h.getTimezoneOffset; + l.hcMakeTime = function (a, c, d, m, b, q) { + var n; + u + ? ((n = l.UTC.apply(0, arguments)), (n += I(n))) + : (n = new l(a, c, f(d, 1), f(m, 0), f(b, 0), f(q, 0)).getTime()); + return n; + }; + G("Minutes Hours Day Date Month FullYear".split(" "), function (a) { + l["hcGet" + a] = d + a; + }); + G( + "Milliseconds Seconds Minutes Hours Date Month FullYear".split(" "), + function (a) { + l["hcSet" + a] = c + a; + } + ); + } + var C = a.color, + G = a.each, + I = a.getTZOffset, + h = a.merge, + f = a.pick, + p = a.win; + a.defaultOptions = { + colors: + "#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1".split( + " " + ), + symbols: ["circle", "diamond", "square", "triangle", "triangle-down"], + lang: { + loading: "Loading...", + months: + "January February March April May June July August September October November December".split( + " " + ), + shortMonths: "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split( + " " + ), + weekdays: + "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "), + decimalPoint: ".", + numericSymbols: "kMGTPE".split(""), + resetZoom: "Reset zoom", + resetZoomTitle: "Reset zoom level 1:1", + thousandsSep: " ", + }, + global: { + useUTC: !0, + VMLRadialGradientURL: + "http://code.highcharts.com/5.0.6/gfx/vml-radial-gradient.png", + }, + chart: { + borderRadius: 0, + defaultSeriesType: "line", + ignoreHiddenSeries: !0, + spacing: [10, 10, 15, 10], + resetZoomButton: { + theme: { zIndex: 20 }, + position: { align: "right", x: -10, y: 10 }, + }, + width: null, + height: null, + borderColor: "#335cad", + backgroundColor: "#ffffff", + plotBorderColor: "#cccccc", + }, + title: { + text: "Chart title", + align: "center", + margin: 15, + widthAdjust: -44, + }, + subtitle: { text: "", align: "center", widthAdjust: -44 }, + plotOptions: {}, + labels: { style: { position: "absolute", color: "#333333" } }, + legend: { + enabled: !0, + align: "center", + layout: "horizontal", + labelFormatter: function () { + return this.name; + }, + borderColor: "#999999", + borderRadius: 0, + navigation: { activeColor: "#003399", inactiveColor: "#cccccc" }, + itemStyle: { color: "#333333", fontSize: "12px", fontWeight: "bold" }, + itemHoverStyle: { color: "#000000" }, + itemHiddenStyle: { color: "#cccccc" }, + shadow: !1, + itemCheckboxStyle: { + position: "absolute", + width: "13px", + height: "13px", + }, + squareSymbol: !0, + symbolPadding: 5, + verticalAlign: "bottom", + x: 0, + y: 0, + title: { style: { fontWeight: "bold" } }, + }, + loading: { + labelStyle: { fontWeight: "bold", position: "relative", top: "45%" }, + style: { + position: "absolute", + backgroundColor: "#ffffff", + opacity: 0.5, + textAlign: "center", + }, + }, + tooltip: { + enabled: !0, + animation: a.svg, + borderRadius: 3, + dateTimeLabelFormats: { + millisecond: "%A, %b %e, %H:%M:%S.%L", + second: "%A, %b %e, %H:%M:%S", + minute: "%A, %b %e, %H:%M", + hour: "%A, %b %e, %H:%M", + day: "%A, %b %e, %Y", + week: "Week from %A, %b %e, %Y", + month: "%B %Y", + year: "%Y", + }, + footerFormat: "", + padding: 8, + snap: a.isTouchDevice ? 25 : 10, + backgroundColor: C("#f7f7f7").setOpacity(0.85).get(), + borderWidth: 1, + headerFormat: + '\x3cspan style\x3d"font-size: 10px"\x3e{point.key}\x3c/span\x3e\x3cbr/\x3e', + pointFormat: + '\x3cspan style\x3d"color:{point.color}"\x3e\u25cf\x3c/span\x3e {series.name}: \x3cb\x3e{point.y}\x3c/b\x3e\x3cbr/\x3e', + shadow: !0, + style: { + color: "#333333", + cursor: "default", + fontSize: "12px", + pointerEvents: "none", + whiteSpace: "nowrap", + }, + }, + credits: { + enabled: !0, + href: "http://www.highcharts.com", + position: { align: "right", x: -10, verticalAlign: "bottom", y: -5 }, + style: { cursor: "pointer", color: "#999999", fontSize: "9px" }, + text: "Highcharts.com", + }, + }; + a.setOptions = function (f) { + a.defaultOptions = h(!0, a.defaultOptions, f); + D(); + return a.defaultOptions; + }; + a.getOptions = function () { + return a.defaultOptions; + }; + a.defaultPlotOptions = a.defaultOptions.plotOptions; + D(); + })(L); + (function (a) { + var D = a.arrayMax, + C = a.arrayMin, + G = a.defined, + I = a.destroyObjectProperties, + h = a.each, + f = a.erase, + p = a.merge, + v = a.pick; + a.PlotLineOrBand = function (a, f) { + this.axis = a; + f && ((this.options = f), (this.id = f.id)); + }; + a.PlotLineOrBand.prototype = { + render: function () { + var a = this, + f = a.axis, + d = f.horiz, + c = a.options, + n = c.label, + h = a.label, + t = c.to, + m = c.from, + b = c.value, + q = G(m) && G(t), + z = G(b), + F = a.svgElem, + e = !F, + r = [], + x, + A = c.color, + k = v(c.zIndex, 0), + w = c.events, + r = { + class: + "highcharts-plot-" + + (q ? "band " : "line ") + + (c.className || ""), + }, + K = {}, + J = f.chart.renderer, + N = q ? "bands" : "lines", + g = f.log2lin; + f.isLog && ((m = g(m)), (t = g(t)), (b = g(b))); + z + ? ((r = { stroke: A, "stroke-width": c.width }), + c.dashStyle && (r.dashstyle = c.dashStyle)) + : q && + (A && (r.fill = A), + c.borderWidth && + ((r.stroke = c.borderColor), + (r["stroke-width"] = c.borderWidth))); + K.zIndex = k; + N += "-" + k; + (A = f[N]) || + (f[N] = A = + J.g("plot-" + N) + .attr(K) + .add()); + e && (a.svgElem = F = J.path().attr(r).add(A)); + if (z) r = f.getPlotLinePath(b, F.strokeWidth()); + else if (q) r = f.getPlotBandPath(m, t, c); + else return; + if (e && r && r.length) { + if ((F.attr({ d: r }), w)) + for (x in ((c = function (g) { + F.on(g, function (b) { + w[g].apply(a, [b]); + }); + }), + w)) + c(x); + } else + F && + (r + ? (F.show(), F.animate({ d: r })) + : (F.hide(), h && (a.label = h = h.destroy()))); + n && + G(n.text) && + r && + r.length && + 0 < f.width && + 0 < f.height && + !r.flat + ? ((n = p( + { + align: d && q && "center", + x: d ? !q && 4 : 10, + verticalAlign: !d && q && "middle", + y: d ? (q ? 16 : 10) : q ? 6 : -4, + rotation: d && !q && 90, + }, + n + )), + this.renderLabel(n, r, q, k)) + : h && h.hide(); + return a; + }, + renderLabel: function (a, f, d, c) { + var n = this.label, + l = this.axis.chart.renderer; + n || + ((n = { + align: a.textAlign || a.align, + rotation: a.rotation, + class: + "highcharts-plot-" + + (d ? "band" : "line") + + "-label " + + (a.className || ""), + }), + (n.zIndex = c), + (this.label = n = l.text(a.text, 0, 0, a.useHTML).attr(n).add()), + n.css(a.style)); + c = [f[1], f[4], d ? f[6] : f[1]]; + f = [f[2], f[5], d ? f[7] : f[2]]; + d = C(c); + l = C(f); + n.align(a, !1, { x: d, y: l, width: D(c) - d, height: D(f) - l }); + n.show(); + }, + destroy: function () { + f(this.axis.plotLinesAndBands, this); + delete this.axis; + I(this); + }, + }; + a.AxisPlotLineOrBandExtension = { + getPlotBandPath: function (a, f) { + f = this.getPlotLinePath(f, null, null, !0); + (a = this.getPlotLinePath(a, null, null, !0)) && f + ? ((a.flat = a.toString() === f.toString()), + a.push(f[4], f[5], f[1], f[2], "z")) + : (a = null); + return a; + }, + addPlotBand: function (a) { + return this.addPlotBandOrLine(a, "plotBands"); + }, + addPlotLine: function (a) { + return this.addPlotBandOrLine(a, "plotLines"); + }, + addPlotBandOrLine: function (f, h) { + var d = new a.PlotLineOrBand(this, f).render(), + c = this.userOptions; + d && + (h && ((c[h] = c[h] || []), c[h].push(f)), + this.plotLinesAndBands.push(d)); + return d; + }, + removePlotBandOrLine: function (a) { + for ( + var l = this.plotLinesAndBands, + d = this.options, + c = this.userOptions, + n = l.length; + n--; + + ) + l[n].id === a && l[n].destroy(); + h( + [ + d.plotLines || [], + c.plotLines || [], + d.plotBands || [], + c.plotBands || [], + ], + function (c) { + for (n = c.length; n--; ) c[n].id === a && f(c, c[n]); + } + ); + }, + }; + })(L); + (function (a) { + var D = a.correctFloat, + C = a.defined, + G = a.destroyObjectProperties, + I = a.isNumber, + h = a.merge, + f = a.pick, + p = a.deg2rad; + a.Tick = function (a, f, h, d) { + this.axis = a; + this.pos = f; + this.type = h || ""; + this.isNew = !0; + h || d || this.addLabel(); + }; + a.Tick.prototype = { + addLabel: function () { + var a = this.axis, + l = a.options, + p = a.chart, + d = a.categories, + c = a.names, + n = this.pos, + y = l.labels, + t = a.tickPositions, + m = n === t[0], + b = n === t[t.length - 1], + c = d ? f(d[n], c[n], n) : n, + d = this.label, + t = t.info, + q; + a.isDatetimeAxis && + t && + (q = l.dateTimeLabelFormats[t.higherRanks[n] || t.unitName]); + this.isFirst = m; + this.isLast = b; + l = a.labelFormatter.call({ + axis: a, + chart: p, + isFirst: m, + isLast: b, + dateTimeLabelFormat: q, + value: a.isLog ? D(a.lin2log(c)) : c, + }); + C(d) + ? d && d.attr({ text: l }) + : ((this.labelLength = + (this.label = d = + C(l) && y.enabled + ? p.renderer + .text(l, 0, 0, y.useHTML) + .css(h(y.style)) + .add(a.labelGroup) + : null) && d.getBBox().width), + (this.rotation = 0)); + }, + getLabelSize: function () { + return this.label + ? this.label.getBBox()[this.axis.horiz ? "height" : "width"] + : 0; + }, + handleOverflow: function (a) { + var l = this.axis, + h = a.x, + d = l.chart.chartWidth, + c = l.chart.spacing, + n = f(l.labelLeft, Math.min(l.pos, c[3])), + c = f(l.labelRight, Math.max(l.pos + l.len, d - c[1])), + y = this.label, + t = this.rotation, + m = { left: 0, center: 0.5, right: 1 }[l.labelAlign], + b = y.getBBox().width, + q = l.getSlotWidth(), + z = q, + F = 1, + e, + r = {}; + if (t) + 0 > t && h - m * b < n + ? (e = Math.round(h / Math.cos(t * p) - n)) + : 0 < t && + h + m * b > c && + (e = Math.round((d - h) / Math.cos(t * p))); + else if ( + ((d = h + (1 - m) * b), + h - m * b < n + ? (z = a.x + z * (1 - m) - n) + : d > c && ((z = c - a.x + z * m), (F = -1)), + (z = Math.min(q, z)), + z < q && + "center" === l.labelAlign && + (a.x += F * (q - z - m * (q - Math.min(b, z)))), + b > z || (l.autoRotation && (y.styles || {}).width)) + ) + e = z; + e && + ((r.width = e), + (l.options.labels.style || {}).textOverflow || + (r.textOverflow = "ellipsis"), + y.css(r)); + }, + getPosition: function (a, f, h, d) { + var c = this.axis, + n = c.chart, + l = (d && n.oldChartHeight) || n.chartHeight; + return { + x: a + ? c.translate(f + h, null, null, d) + c.transB + : c.left + + c.offset + + (c.opposite + ? ((d && n.oldChartWidth) || n.chartWidth) - c.right - c.left + : 0), + y: a + ? l - c.bottom + c.offset - (c.opposite ? c.height : 0) + : l - c.translate(f + h, null, null, d) - c.transB, + }; + }, + getLabelPosition: function (a, f, h, d, c, n, y, t) { + var m = this.axis, + b = m.transA, + q = m.reversed, + z = m.staggerLines, + l = m.tickRotCorr || { x: 0, y: 0 }, + e = c.y; + C(e) || + (e = + 0 === m.side + ? h.rotation + ? -8 + : -h.getBBox().height + : 2 === m.side + ? l.y + 8 + : Math.cos(h.rotation * p) * (l.y - h.getBBox(!1, 0).height / 2)); + a = a + c.x + l.x - (n && d ? n * b * (q ? -1 : 1) : 0); + f = f + e - (n && !d ? n * b * (q ? 1 : -1) : 0); + z && + ((h = (y / (t || 1)) % z), + m.opposite && (h = z - h - 1), + (f += (m.labelOffset / z) * h)); + return { x: a, y: Math.round(f) }; + }, + getMarkPath: function (a, f, h, d, c, n) { + return n.crispLine( + ["M", a, f, "L", a + (c ? 0 : -h), f + (c ? h : 0)], + d + ); + }, + render: function (a, l, h) { + var d = this.axis, + c = d.options, + n = d.chart.renderer, + p = d.horiz, + t = this.type, + m = this.label, + b = this.pos, + q = c.labels, + z = this.gridLine, + F = t ? t + "Tick" : "tick", + e = d.tickSize(F), + r = this.mark, + x = !r, + A = q.step, + k = {}, + w = !0, + K = d.tickmarkOffset, + J = this.getPosition(p, b, K, l), + u = J.x, + J = J.y, + g = (p && u === d.pos + d.len) || (!p && J === d.pos) ? -1 : 1, + B = t ? t + "Grid" : "grid", + S = c[B + "LineWidth"], + M = c[B + "LineColor"], + v = c[B + "LineDashStyle"], + B = f(c[F + "Width"], !t && d.isXAxis ? 1 : 0), + F = c[F + "Color"]; + h = f(h, 1); + this.isActive = !0; + z || + ((k.stroke = M), + (k["stroke-width"] = S), + v && (k.dashstyle = v), + t || (k.zIndex = 1), + l && (k.opacity = 0), + (this.gridLine = z = + n + .path() + .attr(k) + .addClass("highcharts-" + (t ? t + "-" : "") + "grid-line") + .add(d.gridGroup))); + if ( + !l && + z && + (b = d.getPlotLinePath(b + K, z.strokeWidth() * g, l, !0)) + ) + z[this.isNew ? "attr" : "animate"]({ d: b, opacity: h }); + e && + (d.opposite && (e[0] = -e[0]), + x && + ((this.mark = r = + n + .path() + .addClass("highcharts-" + (t ? t + "-" : "") + "tick") + .add(d.axisGroup)), + r.attr({ stroke: F, "stroke-width": B })), + r[x ? "attr" : "animate"]({ + d: this.getMarkPath(u, J, e[0], r.strokeWidth() * g, p, n), + opacity: h, + })); + m && + I(u) && + ((m.xy = J = this.getLabelPosition(u, J, m, p, q, K, a, A)), + (this.isFirst && !this.isLast && !f(c.showFirstLabel, 1)) || + (this.isLast && !this.isFirst && !f(c.showLastLabel, 1)) + ? (w = !1) + : !p || + d.isRadial || + q.step || + q.rotation || + l || + 0 === h || + this.handleOverflow(J), + A && a % A && (w = !1), + w && I(J.y) + ? ((J.opacity = h), m[this.isNew ? "attr" : "animate"](J)) + : m.attr("y", -9999), + (this.isNew = !1)); + }, + destroy: function () { + G(this, this.axis); + }, + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.animObject, + G = a.arrayMax, + I = a.arrayMin, + h = a.AxisPlotLineOrBandExtension, + f = a.color, + p = a.correctFloat, + v = a.defaultOptions, + l = a.defined, + u = a.deg2rad, + d = a.destroyObjectProperties, + c = a.each, + n = a.extend, + y = a.fireEvent, + t = a.format, + m = a.getMagnitude, + b = a.grep, + q = a.inArray, + z = a.isArray, + F = a.isNumber, + e = a.isString, + r = a.merge, + x = a.normalizeTickInterval, + A = a.pick, + k = a.PlotLineOrBand, + w = a.removeEvent, + K = a.splat, + J = a.syncTimeout, + N = a.Tick; + a.Axis = function () { + this.init.apply(this, arguments); + }; + a.Axis.prototype = { + defaultOptions: { + dateTimeLabelFormats: { + millisecond: "%H:%M:%S.%L", + second: "%H:%M:%S", + minute: "%H:%M", + hour: "%H:%M", + day: "%e. %b", + week: "%e. %b", + month: "%b '%y", + year: "%Y", + }, + endOnTick: !1, + labels: { + enabled: !0, + style: { color: "#666666", cursor: "default", fontSize: "11px" }, + x: 0, + }, + minPadding: 0.01, + maxPadding: 0.01, + minorTickLength: 2, + minorTickPosition: "outside", + startOfWeek: 1, + startOnTick: !1, + tickLength: 10, + tickmarkPlacement: "between", + tickPixelInterval: 100, + tickPosition: "outside", + title: { align: "middle", style: { color: "#666666" } }, + type: "linear", + minorGridLineColor: "#f2f2f2", + minorGridLineWidth: 1, + minorTickColor: "#999999", + lineColor: "#ccd6eb", + lineWidth: 1, + gridLineColor: "#e6e6e6", + tickColor: "#ccd6eb", + }, + defaultYAxisOptions: { + endOnTick: !0, + tickPixelInterval: 72, + showLastLabel: !0, + labels: { x: -8 }, + maxPadding: 0.05, + minPadding: 0.05, + startOnTick: !0, + title: { rotation: 270, text: "Values" }, + stackLabels: { + enabled: !1, + formatter: function () { + return a.numberFormat(this.total, -1); + }, + style: { + fontSize: "11px", + fontWeight: "bold", + color: "#000000", + textOutline: "1px contrast", + }, + }, + gridLineWidth: 1, + lineWidth: 0, + }, + defaultLeftAxisOptions: { labels: { x: -15 }, title: { rotation: 270 } }, + defaultRightAxisOptions: { labels: { x: 15 }, title: { rotation: 90 } }, + defaultBottomAxisOptions: { + labels: { autoRotation: [-45], x: 0 }, + title: { rotation: 0 }, + }, + defaultTopAxisOptions: { + labels: { autoRotation: [-45], x: 0 }, + title: { rotation: 0 }, + }, + init: function (a, b) { + var g = b.isX; + this.chart = a; + this.horiz = a.inverted ? !g : g; + this.isXAxis = g; + this.coll = this.coll || (g ? "xAxis" : "yAxis"); + this.opposite = b.opposite; + this.side = + b.side || + (this.horiz ? (this.opposite ? 0 : 2) : this.opposite ? 1 : 3); + this.setOptions(b); + var c = this.options, + e = c.type; + this.labelFormatter = c.labels.formatter || this.defaultLabelFormatter; + this.userOptions = b; + this.minPixelPadding = 0; + this.reversed = c.reversed; + this.visible = !1 !== c.visible; + this.zoomEnabled = !1 !== c.zoomEnabled; + this.hasNames = "category" === e || !0 === c.categories; + this.categories = c.categories || this.hasNames; + this.names = this.names || []; + this.isLog = "logarithmic" === e; + this.isDatetimeAxis = "datetime" === e; + this.isLinked = l(c.linkedTo); + this.ticks = {}; + this.labelEdge = []; + this.minorTicks = {}; + this.plotLinesAndBands = []; + this.alternateBands = {}; + this.len = 0; + this.minRange = this.userMinRange = c.minRange || c.maxZoom; + this.range = c.range; + this.offset = c.offset || 0; + this.stacks = {}; + this.oldStacks = {}; + this.stacksTouched = 0; + this.min = this.max = null; + this.crosshair = A( + c.crosshair, + K(a.options.tooltip.crosshairs)[g ? 0 : 1], + !1 + ); + var k; + b = this.options.events; + -1 === q(this, a.axes) && + (g ? a.axes.splice(a.xAxis.length, 0, this) : a.axes.push(this), + a[this.coll].push(this)); + this.series = this.series || []; + a.inverted && g && void 0 === this.reversed && (this.reversed = !0); + this.removePlotLine = this.removePlotBand = this.removePlotBandOrLine; + for (k in b) D(this, k, b[k]); + this.isLog && + ((this.val2lin = this.log2lin), (this.lin2val = this.lin2log)); + }, + setOptions: function (a) { + this.options = r( + this.defaultOptions, + "yAxis" === this.coll && this.defaultYAxisOptions, + [ + this.defaultTopAxisOptions, + this.defaultRightAxisOptions, + this.defaultBottomAxisOptions, + this.defaultLeftAxisOptions, + ][this.side], + r(v[this.coll], a) + ); + }, + defaultLabelFormatter: function () { + var g = this.axis, + b = this.value, + c = g.categories, + e = this.dateTimeLabelFormat, + k = v.lang, + m = k.numericSymbols, + k = k.numericSymbolMagnitude || 1e3, + q = m && m.length, + d, + r = g.options.labels.format, + g = g.isLog ? b : g.tickInterval; + if (r) d = t(r, this); + else if (c) d = b; + else if (e) d = a.dateFormat(e, b); + else if (q && 1e3 <= g) + for (; q-- && void 0 === d; ) + (c = Math.pow(k, q + 1)), + g >= c && + 0 === (10 * b) % c && + null !== m[q] && + 0 !== b && + (d = a.numberFormat(b / c, -1) + m[q]); + void 0 === d && + (d = + 1e4 <= Math.abs(b) + ? a.numberFormat(b, -1) + : a.numberFormat(b, -1, void 0, "")); + return d; + }, + getSeriesExtremes: function () { + var a = this, + e = a.chart; + a.hasVisibleSeries = !1; + a.dataMin = a.dataMax = a.threshold = null; + a.softThreshold = !a.isXAxis; + a.buildStacks && a.buildStacks(); + c(a.series, function (g) { + if (g.visible || !e.options.chart.ignoreHiddenSeries) { + var c = g.options, + k = c.threshold, + B; + a.hasVisibleSeries = !0; + a.isLog && 0 >= k && (k = null); + if (a.isXAxis) + (c = g.xData), + c.length && + ((g = I(c)), + F(g) || + g instanceof Date || + ((c = b(c, function (a) { + return F(a); + })), + (g = I(c))), + (a.dataMin = Math.min(A(a.dataMin, c[0]), g)), + (a.dataMax = Math.max(A(a.dataMax, c[0]), G(c)))); + else if ( + (g.getExtremes(), + (B = g.dataMax), + (g = g.dataMin), + l(g) && + l(B) && + ((a.dataMin = Math.min(A(a.dataMin, g), g)), + (a.dataMax = Math.max(A(a.dataMax, B), B))), + l(k) && (a.threshold = k), + !c.softThreshold || a.isLog) + ) + a.softThreshold = !1; + } + }); + }, + translate: function (a, b, c, e, k, m) { + var g = this.linkedParent || this, + B = 1, + q = 0, + d = e ? g.oldTransA : g.transA; + e = e ? g.oldMin : g.min; + var r = g.minPixelPadding; + k = (g.isOrdinal || g.isBroken || (g.isLog && k)) && g.lin2val; + d || (d = g.transA); + c && ((B *= -1), (q = g.len)); + g.reversed && ((B *= -1), (q -= B * (g.sector || g.len))); + b + ? ((a = (a * B + q - r) / d + e), k && (a = g.lin2val(a))) + : (k && (a = g.val2lin(a)), + (a = B * (a - e) * d + q + B * r + (F(m) ? d * m : 0))); + return a; + }, + toPixels: function (a, b) { + return ( + this.translate(a, !1, !this.horiz, null, !0) + (b ? 0 : this.pos) + ); + }, + toValue: function (a, b) { + return this.translate( + a - (b ? 0 : this.pos), + !0, + !this.horiz, + null, + !0 + ); + }, + getPlotLinePath: function (a, b, c, e, k) { + var g = this.chart, + B = this.left, + m = this.top, + q, + d, + r = (c && g.oldChartHeight) || g.chartHeight, + n = (c && g.oldChartWidth) || g.chartWidth, + f; + q = this.transB; + var w = function (a, g, b) { + if (a < g || a > b) e ? (a = Math.min(Math.max(g, a), b)) : (f = !0); + return a; + }; + k = A(k, this.translate(a, null, null, c)); + a = c = Math.round(k + q); + q = d = Math.round(r - k - q); + F(k) + ? this.horiz + ? ((q = m), + (d = r - this.bottom), + (a = c = w(a, B, B + this.width))) + : ((a = B), + (c = n - this.right), + (q = d = w(q, m, m + this.height))) + : (f = !0); + return f && !e + ? null + : g.renderer.crispLine(["M", a, q, "L", c, d], b || 1); + }, + getLinearTickPositions: function (a, b, c) { + var g, + k = p(Math.floor(b / a) * a), + e = p(Math.ceil(c / a) * a), + B = []; + if (b === c && F(b)) return [b]; + for (b = k; b <= e; ) { + B.push(b); + b = p(b + a); + if (b === g) break; + g = b; + } + return B; + }, + getMinorTickPositions: function () { + var a = this.options, + b = this.tickPositions, + c = this.minorTickInterval, + k = [], + e, + m = this.pointRangePadding || 0; + e = this.min - m; + var m = this.max + m, + q = m - e; + if (q && q / c < this.len / 3) + if (this.isLog) + for (m = b.length, e = 1; e < m; e++) + k = k.concat(this.getLogTickPositions(c, b[e - 1], b[e], !0)); + else if (this.isDatetimeAxis && "auto" === a.minorTickInterval) + k = k.concat( + this.getTimeTicks( + this.normalizeTimeTickInterval(c), + e, + m, + a.startOfWeek + ) + ); + else + for (b = e + ((b[0] - e) % c); b <= m && b !== k[0]; b += c) + k.push(b); + 0 !== k.length && this.trimTicks(k, a.startOnTick, a.endOnTick); + return k; + }, + adjustForMinRange: function () { + var a = this.options, + b = this.min, + k = this.max, + e, + m = this.dataMax - this.dataMin >= this.minRange, + q, + d, + r, + f, + n, + w; + this.isXAxis && + void 0 === this.minRange && + !this.isLog && + (l(a.min) || l(a.max) + ? (this.minRange = null) + : (c(this.series, function (a) { + f = a.xData; + for (d = n = a.xIncrement ? 1 : f.length - 1; 0 < d; d--) + if (((r = f[d] - f[d - 1]), void 0 === q || r < q)) q = r; + }), + (this.minRange = Math.min(5 * q, this.dataMax - this.dataMin)))); + k - b < this.minRange && + ((w = this.minRange), + (e = (w - k + b) / 2), + (e = [b - e, A(a.min, b - e)]), + m && (e[2] = this.isLog ? this.log2lin(this.dataMin) : this.dataMin), + (b = G(e)), + (k = [b + w, A(a.max, b + w)]), + m && (k[2] = this.isLog ? this.log2lin(this.dataMax) : this.dataMax), + (k = I(k)), + k - b < w && ((e[0] = k - w), (e[1] = A(a.min, k - w)), (b = G(e)))); + this.min = b; + this.max = k; + }, + getClosest: function () { + var a; + this.categories + ? (a = 1) + : c(this.series, function (b) { + var g = b.closestPointRange, + c = b.visible || !b.chart.options.chart.ignoreHiddenSeries; + !b.noSharedTooltip && + l(g) && + c && + (a = l(a) ? Math.min(a, g) : g); + }); + return a; + }, + nameToX: function (a) { + var b = z(this.categories), + g = b ? this.categories : this.names, + c = a.options.x, + k; + a.series.requireSorting = !1; + l(c) || + (c = + !1 === this.options.uniqueNames + ? a.series.autoIncrement() + : q(a.name, g)); + -1 === c ? b || (k = g.length) : (k = c); + this.names[k] = a.name; + return k; + }, + updateNames: function () { + var a = this; + 0 < this.names.length && + ((this.names.length = 0), + (this.minRange = void 0), + c(this.series || [], function (b) { + b.xIncrement = null; + if (!b.points || b.isDirtyData) b.processData(), b.generatePoints(); + c(b.points, function (g, c) { + var k; + g.options && + void 0 === g.options.x && + ((k = a.nameToX(g)), + k !== g.x && ((g.x = k), (b.xData[c] = k))); + }); + })); + }, + setAxisTranslation: function (a) { + var b = this, + g = b.max - b.min, + k = b.axisPointRange || 0, + m, + q = 0, + d = 0, + r = b.linkedParent, + f = !!b.categories, + n = b.transA, + w = b.isXAxis; + if (w || f || k) + (m = b.getClosest()), + r + ? ((q = r.minPointOffset), (d = r.pointRangePadding)) + : c(b.series, function (a) { + var g = f + ? 1 + : w + ? A(a.options.pointRange, m, 0) + : b.axisPointRange || 0; + a = a.options.pointPlacement; + k = Math.max(k, g); + b.single || + ((q = Math.max(q, e(a) ? 0 : g / 2)), + (d = Math.max(d, "on" === a ? 0 : g))); + }), + (r = b.ordinalSlope && m ? b.ordinalSlope / m : 1), + (b.minPointOffset = q *= r), + (b.pointRangePadding = d *= r), + (b.pointRange = Math.min(k, g)), + w && (b.closestPointRange = m); + a && (b.oldTransA = n); + b.translationSlope = b.transA = n = b.len / (g + d || 1); + b.transB = b.horiz ? b.left : b.bottom; + b.minPixelPadding = n * q; + }, + minFromRange: function () { + return this.max - this.range; + }, + setTickInterval: function (b) { + var g = this, + k = g.chart, + e = g.options, + q = g.isLog, + d = g.log2lin, + r = g.isDatetimeAxis, + f = g.isXAxis, + n = g.isLinked, + w = e.maxPadding, + t = e.minPadding, + z = e.tickInterval, + h = e.tickPixelInterval, + K = g.categories, + J = g.threshold, + u = g.softThreshold, + N, + v, + C, + D; + r || K || n || this.getTickAmount(); + C = A(g.userMin, e.min); + D = A(g.userMax, e.max); + n + ? ((g.linkedParent = k[g.coll][e.linkedTo]), + (k = g.linkedParent.getExtremes()), + (g.min = A(k.min, k.dataMin)), + (g.max = A(k.max, k.dataMax)), + e.type !== g.linkedParent.options.type && a.error(11, 1)) + : (!u && + l(J) && + (g.dataMin >= J + ? ((N = J), (t = 0)) + : g.dataMax <= J && ((v = J), (w = 0))), + (g.min = A(C, N, g.dataMin)), + (g.max = A(D, v, g.dataMax))); + q && + (!b && 0 >= Math.min(g.min, A(g.dataMin, g.min)) && a.error(10, 1), + (g.min = p(d(g.min), 15)), + (g.max = p(d(g.max), 15))); + g.range && + l(g.max) && + ((g.userMin = g.min = C = Math.max(g.min, g.minFromRange())), + (g.userMax = D = g.max), + (g.range = null)); + y(g, "foundExtremes"); + g.beforePadding && g.beforePadding(); + g.adjustForMinRange(); + !(K || g.axisPointRange || g.usePercentage || n) && + l(g.min) && + l(g.max) && + (d = g.max - g.min) && + (!l(C) && t && (g.min -= d * t), !l(D) && w && (g.max += d * w)); + F(e.floor) + ? (g.min = Math.max(g.min, e.floor)) + : F(e.softMin) && (g.min = Math.min(g.min, e.softMin)); + F(e.ceiling) + ? (g.max = Math.min(g.max, e.ceiling)) + : F(e.softMax) && (g.max = Math.max(g.max, e.softMax)); + u && + l(g.dataMin) && + ((J = J || 0), + !l(C) && g.min < J && g.dataMin >= J + ? (g.min = J) + : !l(D) && g.max > J && g.dataMax <= J && (g.max = J)); + g.tickInterval = + g.min === g.max || void 0 === g.min || void 0 === g.max + ? 1 + : n && !z && h === g.linkedParent.options.tickPixelInterval + ? (z = g.linkedParent.tickInterval) + : A( + z, + this.tickAmount + ? (g.max - g.min) / Math.max(this.tickAmount - 1, 1) + : void 0, + K ? 1 : ((g.max - g.min) * h) / Math.max(g.len, h) + ); + f && + !b && + c(g.series, function (a) { + a.processData(g.min !== g.oldMin || g.max !== g.oldMax); + }); + g.setAxisTranslation(!0); + g.beforeSetTickPositions && g.beforeSetTickPositions(); + g.postProcessTickInterval && + (g.tickInterval = g.postProcessTickInterval(g.tickInterval)); + g.pointRange && + !z && + (g.tickInterval = Math.max(g.pointRange, g.tickInterval)); + b = A(e.minTickInterval, g.isDatetimeAxis && g.closestPointRange); + !z && g.tickInterval < b && (g.tickInterval = b); + r || + q || + z || + (g.tickInterval = x( + g.tickInterval, + null, + m(g.tickInterval), + A( + e.allowDecimals, + !( + 0.5 < g.tickInterval && + 5 > g.tickInterval && + 1e3 < g.max && + 9999 > g.max + ) + ), + !!this.tickAmount + )); + this.tickAmount || (g.tickInterval = g.unsquish()); + this.setTickPositions(); + }, + setTickPositions: function () { + var a = this.options, + b, + c = a.tickPositions, + k = a.tickPositioner, + e = a.startOnTick, + m = a.endOnTick, + q; + this.tickmarkOffset = + this.categories && + "between" === a.tickmarkPlacement && + 1 === this.tickInterval + ? 0.5 + : 0; + this.minorTickInterval = + "auto" === a.minorTickInterval && this.tickInterval + ? this.tickInterval / 5 + : a.minorTickInterval; + this.tickPositions = b = c && c.slice(); + !b && + ((b = this.isDatetimeAxis + ? this.getTimeTicks( + this.normalizeTimeTickInterval(this.tickInterval, a.units), + this.min, + this.max, + a.startOfWeek, + this.ordinalPositions, + this.closestPointRange, + !0 + ) + : this.isLog + ? this.getLogTickPositions(this.tickInterval, this.min, this.max) + : this.getLinearTickPositions( + this.tickInterval, + this.min, + this.max + )), + b.length > this.len && (b = [b[0], b.pop()]), + (this.tickPositions = b), + k && (k = k.apply(this, [this.min, this.max]))) && + (this.tickPositions = b = k); + this.isLinked || + (this.trimTicks(b, e, m), + this.min === this.max && + l(this.min) && + !this.tickAmount && + ((q = !0), (this.min -= 0.5), (this.max += 0.5)), + (this.single = q), + c || k || this.adjustTickAmount()); + }, + trimTicks: function (a, b, c) { + var g = a[0], + k = a[a.length - 1], + e = this.minPointOffset || 0; + if (b) this.min = g; + else for (; this.min - e > a[0]; ) a.shift(); + if (c) this.max = k; + else for (; this.max + e < a[a.length - 1]; ) a.pop(); + 0 === a.length && l(g) && a.push((k + g) / 2); + }, + alignToOthers: function () { + var a = {}, + b, + k = this.options; + !1 === this.chart.options.chart.alignTicks || + !1 === k.alignTicks || + this.isLog || + c(this.chart[this.coll], function (g) { + var c = g.options, + c = [g.horiz ? c.left : c.top, c.width, c.height, c.pane].join(); + g.series.length && (a[c] ? (b = !0) : (a[c] = 1)); + }); + return b; + }, + getTickAmount: function () { + var a = this.options, + b = a.tickAmount, + c = a.tickPixelInterval; + !l(a.tickInterval) && + this.len < c && + !this.isRadial && + !this.isLog && + a.startOnTick && + a.endOnTick && + (b = 2); + !b && this.alignToOthers() && (b = Math.ceil(this.len / c) + 1); + 4 > b && ((this.finalTickAmt = b), (b = 5)); + this.tickAmount = b; + }, + adjustTickAmount: function () { + var a = this.tickInterval, + b = this.tickPositions, + c = this.tickAmount, + k = this.finalTickAmt, + e = b && b.length; + if (e < c) { + for (; b.length < c; ) b.push(p(b[b.length - 1] + a)); + this.transA *= (e - 1) / (c - 1); + this.max = b[b.length - 1]; + } else e > c && ((this.tickInterval *= 2), this.setTickPositions()); + if (l(k)) { + for (a = c = b.length; a--; ) + ((3 === k && 1 === a % 2) || (2 >= k && 0 < a && a < c - 1)) && + b.splice(a, 1); + this.finalTickAmt = void 0; + } + }, + setScale: function () { + var a, b; + this.oldMin = this.min; + this.oldMax = this.max; + this.oldAxisLength = this.len; + this.setAxisSize(); + b = this.len !== this.oldAxisLength; + c(this.series, function (b) { + if (b.isDirtyData || b.isDirty || b.xAxis.isDirty) a = !0; + }); + b || + a || + this.isLinked || + this.forceRedraw || + this.userMin !== this.oldUserMin || + this.userMax !== this.oldUserMax || + this.alignToOthers() + ? (this.resetStacks && this.resetStacks(), + (this.forceRedraw = !1), + this.getSeriesExtremes(), + this.setTickInterval(), + (this.oldUserMin = this.userMin), + (this.oldUserMax = this.userMax), + this.isDirty || + (this.isDirty = + b || this.min !== this.oldMin || this.max !== this.oldMax)) + : this.cleanStacks && this.cleanStacks(); + }, + setExtremes: function (a, b, k, e, m) { + var g = this, + q = g.chart; + k = A(k, !0); + c(g.series, function (a) { + delete a.kdTree; + }); + m = n(m, { min: a, max: b }); + y(g, "setExtremes", m, function () { + g.userMin = a; + g.userMax = b; + g.eventArgs = m; + k && q.redraw(e); + }); + }, + zoom: function (a, b) { + var g = this.dataMin, + c = this.dataMax, + k = this.options, + e = Math.min(g, A(k.min, g)), + k = Math.max(c, A(k.max, c)); + if (a !== this.min || b !== this.max) + this.allowZoomOutside || + (l(g) && (a < e && (a = e), a > k && (a = k)), + l(c) && (b < e && (b = e), b > k && (b = k))), + (this.displayBtn = void 0 !== a || void 0 !== b), + this.setExtremes(a, b, !1, void 0, { trigger: "zoom" }); + return !0; + }, + setAxisSize: function () { + var a = this.chart, + b = this.options, + c = b.offsetLeft || 0, + k = this.horiz, + e = A(b.width, a.plotWidth - c + (b.offsetRight || 0)), + m = A(b.height, a.plotHeight), + q = A(b.top, a.plotTop), + b = A(b.left, a.plotLeft + c), + c = /%$/; + c.test(m) && (m = Math.round((parseFloat(m) / 100) * a.plotHeight)); + c.test(q) && + (q = Math.round((parseFloat(q) / 100) * a.plotHeight + a.plotTop)); + this.left = b; + this.top = q; + this.width = e; + this.height = m; + this.bottom = a.chartHeight - m - q; + this.right = a.chartWidth - e - b; + this.len = Math.max(k ? e : m, 0); + this.pos = k ? b : q; + }, + getExtremes: function () { + var a = this.isLog, + b = this.lin2log; + return { + min: a ? p(b(this.min)) : this.min, + max: a ? p(b(this.max)) : this.max, + dataMin: this.dataMin, + dataMax: this.dataMax, + userMin: this.userMin, + userMax: this.userMax, + }; + }, + getThreshold: function (a) { + var b = this.isLog, + g = this.lin2log, + c = b ? g(this.min) : this.min, + b = b ? g(this.max) : this.max; + null === a ? (a = c) : c > a ? (a = c) : b < a && (a = b); + return this.translate(a, 0, 1, 0, 1); + }, + autoLabelAlign: function (a) { + a = (A(a, 0) - 90 * this.side + 720) % 360; + return 15 < a && 165 > a + ? "right" + : 195 < a && 345 > a + ? "left" + : "center"; + }, + tickSize: function (a) { + var b = this.options, + g = b[a + "Length"], + c = A(b[a + "Width"], "tick" === a && this.isXAxis ? 1 : 0); + if (c && g) return "inside" === b[a + "Position"] && (g = -g), [g, c]; + }, + labelMetrics: function () { + return this.chart.renderer.fontMetrics( + this.options.labels.style && this.options.labels.style.fontSize, + this.ticks[0] && this.ticks[0].label + ); + }, + unsquish: function () { + var a = this.options.labels, + b = this.horiz, + k = this.tickInterval, + e = k, + m = + this.len / (((this.categories ? 1 : 0) + this.max - this.min) / k), + q, + d = a.rotation, + r = this.labelMetrics(), + f, + n = Number.MAX_VALUE, + w, + t = function (a) { + a /= m || 1; + a = 1 < a ? Math.ceil(a) : 1; + return a * k; + }; + b + ? (w = + !a.staggerLines && + !a.step && + (l(d) + ? [d] + : m < A(a.autoRotationLimit, 80) && a.autoRotation)) && + c(w, function (a) { + var b; + if (a === d || (a && -90 <= a && 90 >= a)) + (f = t(Math.abs(r.h / Math.sin(u * a)))), + (b = f + Math.abs(a / 360)), + b < n && ((n = b), (q = a), (e = f)); + }) + : a.step || (e = t(r.h)); + this.autoRotation = w; + this.labelRotation = A(q, d); + return e; + }, + getSlotWidth: function () { + var a = this.chart, + b = this.horiz, + c = this.options.labels, + k = Math.max( + this.tickPositions.length - (this.categories ? 0 : 1), + 1 + ), + e = a.margin[3]; + return ( + (b && + 2 > (c.step || 0) && + !c.rotation && + ((this.staggerLines || 1) * a.plotWidth) / k) || + (!b && ((e && e - a.spacing[3]) || 0.33 * a.chartWidth)) + ); + }, + renderUnsquish: function () { + var a = this.chart, + b = a.renderer, + k = this.tickPositions, + m = this.ticks, + q = this.options.labels, + d = this.horiz, + f = this.getSlotWidth(), + n = Math.max(1, Math.round(f - 2 * (q.padding || 5))), + w = {}, + t = this.labelMetrics(), + z = q.style && q.style.textOverflow, + h, + l = 0, + x, + F; + e(q.rotation) || (w.rotation = q.rotation || 0); + c(k, function (a) { + (a = m[a]) && a.labelLength > l && (l = a.labelLength); + }); + this.maxLabelLength = l; + if (this.autoRotation) + l > n && l > t.h + ? (w.rotation = this.labelRotation) + : (this.labelRotation = 0); + else if (f && ((h = { width: n + "px" }), !z)) + for (h.textOverflow = "clip", x = k.length; !d && x--; ) + if (((F = k[x]), (n = m[F].label))) + n.styles && "ellipsis" === n.styles.textOverflow + ? n.css({ textOverflow: "clip" }) + : m[F].labelLength > f && n.css({ width: f + "px" }), + n.getBBox().height > this.len / k.length - (t.h - t.f) && + (n.specCss = { textOverflow: "ellipsis" }); + w.rotation && + ((h = { + width: + (l > 0.5 * a.chartHeight ? 0.33 * a.chartHeight : a.chartHeight) + + "px", + }), + z || (h.textOverflow = "ellipsis")); + if ( + (this.labelAlign = q.align || this.autoLabelAlign(this.labelRotation)) + ) + w.align = this.labelAlign; + c(k, function (a) { + var b = (a = m[a]) && a.label; + b && + (b.attr(w), + h && b.css(r(h, b.specCss)), + delete b.specCss, + (a.rotation = w.rotation)); + }); + this.tickRotCorr = b.rotCorr( + t.b, + this.labelRotation || 0, + 0 !== this.side + ); + }, + hasData: function () { + return ( + this.hasVisibleSeries || + (l(this.min) && l(this.max) && !!this.tickPositions) + ); + }, + addTitle: function (a) { + var b = this.chart.renderer, + g = this.horiz, + c = this.opposite, + k = this.options.title, + e; + this.axisTitle || + ((e = k.textAlign) || + (e = ( + g + ? { low: "left", middle: "center", high: "right" } + : { + low: c ? "right" : "left", + middle: "center", + high: c ? "left" : "right", + } + )[k.align]), + (this.axisTitle = b + .text(k.text, 0, 0, k.useHTML) + .attr({ zIndex: 7, rotation: k.rotation || 0, align: e }) + .addClass("highcharts-axis-title") + .css(k.style) + .add(this.axisGroup)), + (this.axisTitle.isNew = !0)); + this.axisTitle[a ? "show" : "hide"](!0); + }, + getOffset: function () { + var a = this, + b = a.chart, + k = b.renderer, + e = a.options, + m = a.tickPositions, + q = a.ticks, + d = a.horiz, + r = a.side, + n = b.inverted ? [1, 0, 3, 2][r] : r, + w, + f, + t = 0, + z, + h = 0, + x = e.title, + F = e.labels, + p = 0, + K = b.axisOffset, + b = b.clipOffset, + J = [-1, 1, 1, -1][r], + u, + y = e.className, + v = a.axisParent, + C = this.tickSize("tick"); + w = a.hasData(); + a.showAxis = f = w || A(e.showEmpty, !0); + a.staggerLines = a.horiz && F.staggerLines; + a.axisGroup || + ((a.gridGroup = k + .g("grid") + .attr({ zIndex: e.gridZIndex || 1 }) + .addClass( + "highcharts-" + this.coll.toLowerCase() + "-grid " + (y || "") + ) + .add(v)), + (a.axisGroup = k + .g("axis") + .attr({ zIndex: e.zIndex || 2 }) + .addClass("highcharts-" + this.coll.toLowerCase() + " " + (y || "")) + .add(v)), + (a.labelGroup = k + .g("axis-labels") + .attr({ zIndex: F.zIndex || 7 }) + .addClass( + "highcharts-" + a.coll.toLowerCase() + "-labels " + (y || "") + ) + .add(v))); + if (w || a.isLinked) + c(m, function (b) { + q[b] ? q[b].addLabel() : (q[b] = new N(a, b)); + }), + a.renderUnsquish(), + !1 === F.reserveSpace || + (0 !== r && + 2 !== r && + { 1: "left", 3: "right" }[r] !== a.labelAlign && + "center" !== a.labelAlign) || + c(m, function (a) { + p = Math.max(q[a].getLabelSize(), p); + }), + a.staggerLines && + ((p *= a.staggerLines), + (a.labelOffset = p * (a.opposite ? -1 : 1))); + else for (u in q) q[u].destroy(), delete q[u]; + x && + x.text && + !1 !== x.enabled && + (a.addTitle(f), + f && + ((t = a.axisTitle.getBBox()[d ? "height" : "width"]), + (z = x.offset), + (h = l(z) ? 0 : A(x.margin, d ? 5 : 10)))); + a.renderLine(); + a.offset = J * A(e.offset, K[r]); + a.tickRotCorr = a.tickRotCorr || { x: 0, y: 0 }; + k = 0 === r ? -a.labelMetrics().h : 2 === r ? a.tickRotCorr.y : 0; + h = Math.abs(p) + h; + p && (h = h - k + J * (d ? A(F.y, a.tickRotCorr.y + 8 * J) : F.x)); + a.axisTitleMargin = A(z, h); + K[r] = Math.max( + K[r], + a.axisTitleMargin + t + J * a.offset, + h, + w && m.length && C ? C[0] : 0 + ); + e = e.offset ? 0 : 2 * Math.floor(a.axisLine.strokeWidth() / 2); + b[n] = Math.max(b[n], e); + }, + getLinePath: function (a) { + var b = this.chart, + c = this.opposite, + g = this.offset, + k = this.horiz, + e = this.left + (c ? this.width : 0) + g, + g = b.chartHeight - this.bottom - (c ? this.height : 0) + g; + c && (a *= -1); + return b.renderer.crispLine( + [ + "M", + k ? this.left : e, + k ? g : this.top, + "L", + k ? b.chartWidth - this.right : e, + k ? g : b.chartHeight - this.bottom, + ], + a + ); + }, + renderLine: function () { + this.axisLine || + ((this.axisLine = this.chart.renderer + .path() + .addClass("highcharts-axis-line") + .add(this.axisGroup)), + this.axisLine.attr({ + stroke: this.options.lineColor, + "stroke-width": this.options.lineWidth, + zIndex: 7, + })); + }, + getTitlePosition: function () { + var a = this.horiz, + b = this.left, + c = this.top, + k = this.len, + e = this.options.title, + m = a ? b : c, + q = this.opposite, + d = this.offset, + r = e.x || 0, + n = e.y || 0, + w = this.chart.renderer.fontMetrics( + e.style && e.style.fontSize, + this.axisTitle + ).f, + k = { + low: m + (a ? 0 : k), + middle: m + k / 2, + high: m + (a ? k : 0), + }[e.align], + b = + (a ? c + this.height : b) + + (a ? 1 : -1) * (q ? -1 : 1) * this.axisTitleMargin + + (2 === this.side ? w : 0); + return { + x: a ? k + r : b + (q ? this.width : 0) + d + r, + y: a ? b + n - (q ? this.height : 0) + d : k + n, + }; + }, + render: function () { + var a = this, + b = a.chart, + e = b.renderer, + m = a.options, + q = a.isLog, + d = a.lin2log, + r = a.isLinked, + n = a.tickPositions, + w = a.axisTitle, + f = a.ticks, + t = a.minorTicks, + z = a.alternateBands, + h = m.stackLabels, + l = m.alternateGridColor, + x = a.tickmarkOffset, + p = a.axisLine, + A = b.hasRendered && F(a.oldMin), + K = a.showAxis, + u = C(e.globalAnimation), + y, + v; + a.labelEdge.length = 0; + a.overlap = !1; + c([f, t, z], function (a) { + for (var b in a) a[b].isActive = !1; + }); + if (a.hasData() || r) + a.minorTickInterval && + !a.categories && + c(a.getMinorTickPositions(), function (b) { + t[b] || (t[b] = new N(a, b, "minor")); + A && t[b].isNew && t[b].render(null, !0); + t[b].render(null, !1, 1); + }), + n.length && + (c(n, function (b, c) { + if (!r || (b >= a.min && b <= a.max)) + f[b] || (f[b] = new N(a, b)), + A && f[b].isNew && f[b].render(c, !0, 0.1), + f[b].render(c); + }), + x && + (0 === a.min || a.single) && + (f[-1] || (f[-1] = new N(a, -1, null, !0)), f[-1].render(-1))), + l && + c(n, function (c, g) { + v = void 0 !== n[g + 1] ? n[g + 1] + x : a.max - x; + 0 === g % 2 && + c < a.max && + v <= a.max + (b.polar ? -x : x) && + (z[c] || (z[c] = new k(a)), + (y = c + x), + (z[c].options = { + from: q ? d(y) : y, + to: q ? d(v) : v, + color: l, + }), + z[c].render(), + (z[c].isActive = !0)); + }), + a._addedPlotLB || + (c((m.plotLines || []).concat(m.plotBands || []), function (b) { + a.addPlotBandOrLine(b); + }), + (a._addedPlotLB = !0)); + c([f, t, z], function (a) { + var c, + g, + k = [], + e = u.duration; + for (c in a) + a[c].isActive || + (a[c].render(c, !1, 0), (a[c].isActive = !1), k.push(c)); + J( + function () { + for (g = k.length; g--; ) + a[k[g]] && + !a[k[g]].isActive && + (a[k[g]].destroy(), delete a[k[g]]); + }, + a !== z && b.hasRendered && e ? e : 0 + ); + }); + p && + (p[p.isPlaced ? "animate" : "attr"]({ + d: this.getLinePath(p.strokeWidth()), + }), + (p.isPlaced = !0), + p[K ? "show" : "hide"](!0)); + w && + K && + (w[w.isNew ? "attr" : "animate"](a.getTitlePosition()), + (w.isNew = !1)); + h && h.enabled && a.renderStackTotals(); + a.isDirty = !1; + }, + redraw: function () { + this.visible && + (this.render(), + c(this.plotLinesAndBands, function (a) { + a.render(); + })); + c(this.series, function (a) { + a.isDirty = !0; + }); + }, + keepProps: "extKey hcEvents names series userMax userMin".split(" "), + destroy: function (a) { + var b = this, + g = b.stacks, + k, + e = b.plotLinesAndBands, + m; + a || w(b); + for (k in g) d(g[k]), (g[k] = null); + c([b.ticks, b.minorTicks, b.alternateBands], function (a) { + d(a); + }); + if (e) for (a = e.length; a--; ) e[a].destroy(); + c( + "stackTotalGroup axisLine axisTitle axisGroup gridGroup labelGroup cross".split( + " " + ), + function (a) { + b[a] && (b[a] = b[a].destroy()); + } + ); + for (m in b) + b.hasOwnProperty(m) && -1 === q(m, b.keepProps) && delete b[m]; + }, + drawCrosshair: function (a, b) { + var c, + g = this.crosshair, + k = A(g.snap, !0), + e, + m = this.cross; + a || (a = this.cross && this.cross.e); + this.crosshair && !1 !== (l(b) || !k) + ? (k + ? l(b) && (e = this.isXAxis ? b.plotX : this.len - b.plotY) + : (e = + a && + (this.horiz + ? a.chartX - this.pos + : this.len - a.chartY + this.pos)), + l(e) && + (c = + this.getPlotLinePath( + b && (this.isXAxis ? b.x : A(b.stackY, b.y)), + null, + null, + null, + e + ) || null), + l(c) + ? ((b = this.categories && !this.isRadial), + m || + ((this.cross = m = + this.chart.renderer + .path() + .addClass( + "highcharts-crosshair highcharts-crosshair-" + + (b ? "category " : "thin ") + + g.className + ) + .attr({ zIndex: A(g.zIndex, 2) }) + .add()), + m.attr({ + stroke: + g.color || + (b ? f("#ccd6eb").setOpacity(0.25).get() : "#cccccc"), + "stroke-width": A(g.width, 1), + }), + g.dashStyle && m.attr({ dashstyle: g.dashStyle })), + m.show().attr({ d: c }), + b && !g.width && m.attr({ "stroke-width": this.transA }), + (this.cross.e = a)) + : this.hideCrosshair()) + : this.hideCrosshair(); + }, + hideCrosshair: function () { + this.cross && this.cross.hide(); + }, + }; + n(a.Axis.prototype, h); + })(L); + (function (a) { + var D = a.Axis, + C = a.Date, + G = a.dateFormat, + I = a.defaultOptions, + h = a.defined, + f = a.each, + p = a.extend, + v = a.getMagnitude, + l = a.getTZOffset, + u = a.normalizeTickInterval, + d = a.pick, + c = a.timeUnits; + D.prototype.getTimeTicks = function (a, y, t, m) { + var b = [], + q = {}, + n = I.global.useUTC, + F, + e = new C(y - l(y)), + r = C.hcMakeTime, + x = a.unitRange, + A = a.count, + k; + if (h(y)) { + e[C.hcSetMilliseconds]( + x >= c.second ? 0 : A * Math.floor(e.getMilliseconds() / A) + ); + if (x >= c.second) + e[C.hcSetSeconds]( + x >= c.minute ? 0 : A * Math.floor(e.getSeconds() / A) + ); + if (x >= c.minute) + e[C.hcSetMinutes]( + x >= c.hour ? 0 : A * Math.floor(e[C.hcGetMinutes]() / A) + ); + if (x >= c.hour) + e[C.hcSetHours]( + x >= c.day ? 0 : A * Math.floor(e[C.hcGetHours]() / A) + ); + if (x >= c.day) + e[C.hcSetDate]( + x >= c.month ? 1 : A * Math.floor(e[C.hcGetDate]() / A) + ); + x >= c.month && + (e[C.hcSetMonth]( + x >= c.year ? 0 : A * Math.floor(e[C.hcGetMonth]() / A) + ), + (F = e[C.hcGetFullYear]())); + if (x >= c.year) e[C.hcSetFullYear](F - (F % A)); + if (x === c.week) + e[C.hcSetDate](e[C.hcGetDate]() - e[C.hcGetDay]() + d(m, 1)); + F = e[C.hcGetFullYear](); + m = e[C.hcGetMonth](); + var w = e[C.hcGetDate](), + K = e[C.hcGetHours](); + if (C.hcTimezoneOffset || C.hcGetTimezoneOffset) + (k = + (!n || !!C.hcGetTimezoneOffset) && + (t - y > 4 * c.month || l(y) !== l(t))), + (e = e.getTime()), + (e = new C(e + l(e))); + n = e.getTime(); + for (y = 1; n < t; ) + b.push(n), + (n = + x === c.year + ? r(F + y * A, 0) + : x === c.month + ? r(F, m + y * A) + : !k || (x !== c.day && x !== c.week) + ? k && x === c.hour + ? r(F, m, w, K + y * A) + : n + x * A + : r(F, m, w + y * A * (x === c.day ? 1 : 7))), + y++; + b.push(n); + x <= c.hour && + f(b, function (a) { + "000000000" === G("%H%M%S%L", a) && (q[a] = "day"); + }); + } + b.info = p(a, { higherRanks: q, totalRange: x * A }); + return b; + }; + D.prototype.normalizeTimeTickInterval = function (a, d) { + var f = d || [ + ["millisecond", [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]], + ["second", [1, 2, 5, 10, 15, 30]], + ["minute", [1, 2, 5, 10, 15, 30]], + ["hour", [1, 2, 3, 4, 6, 8, 12]], + ["day", [1, 2]], + ["week", [1, 2]], + ["month", [1, 2, 3, 4, 6]], + ["year", null], + ]; + d = f[f.length - 1]; + var m = c[d[0]], + b = d[1], + q; + for ( + q = 0; + q < f.length && + !((d = f[q]), + (m = c[d[0]]), + (b = d[1]), + f[q + 1] && a <= (m * b[b.length - 1] + c[f[q + 1][0]]) / 2); + q++ + ); + m === c.year && a < 5 * m && (b = [1, 2, 5]); + a = u(a / m, b, "year" === d[0] ? Math.max(v(a / m), 1) : 1); + return { unitRange: m, count: a, unitName: d[0] }; + }; + })(L); + (function (a) { + var D = a.Axis, + C = a.getMagnitude, + G = a.map, + I = a.normalizeTickInterval, + h = a.pick; + D.prototype.getLogTickPositions = function (a, p, v, l) { + var f = this.options, + d = this.len, + c = this.lin2log, + n = this.log2lin, + y = []; + l || (this._minorAutoInterval = null); + if (0.5 <= a) + (a = Math.round(a)), (y = this.getLinearTickPositions(a, p, v)); + else if (0.08 <= a) + for ( + var d = Math.floor(p), + t, + m, + b, + q, + z, + f = + 0.3 < a + ? [1, 2, 4] + : 0.15 < a + ? [1, 2, 4, 6, 8] + : [1, 2, 3, 4, 5, 6, 7, 8, 9]; + d < v + 1 && !z; + d++ + ) + for (m = f.length, t = 0; t < m && !z; t++) + (b = n(c(d) * f[t])), + b > p && (!l || q <= v) && void 0 !== q && y.push(q), + q > v && (z = !0), + (q = b); + else + (p = c(p)), + (v = c(v)), + (a = f[l ? "minorTickInterval" : "tickInterval"]), + (a = h( + "auto" === a ? null : a, + this._minorAutoInterval, + ((f.tickPixelInterval / (l ? 5 : 1)) * (v - p)) / + ((l ? d / this.tickPositions.length : d) || 1) + )), + (a = I(a, null, C(a))), + (y = G(this.getLinearTickPositions(a, p, v), n)), + l || (this._minorAutoInterval = a / 5); + l || (this.tickInterval = a); + return y; + }; + D.prototype.log2lin = function (a) { + return Math.log(a) / Math.LN10; + }; + D.prototype.lin2log = function (a) { + return Math.pow(10, a); + }; + })(L); + (function (a) { + var D = a.dateFormat, + C = a.each, + G = a.extend, + I = a.format, + h = a.isNumber, + f = a.map, + p = a.merge, + v = a.pick, + l = a.splat, + u = a.syncTimeout, + d = a.timeUnits; + a.Tooltip = function () { + this.init.apply(this, arguments); + }; + a.Tooltip.prototype = { + init: function (a, d) { + this.chart = a; + this.options = d; + this.crosshairs = []; + this.now = { x: 0, y: 0 }; + this.isHidden = !0; + this.split = d.split && !a.inverted; + this.shared = d.shared || this.split; + }, + cleanSplit: function (a) { + C(this.chart.series, function (c) { + var d = c && c.tt; + d && (!d.isActive || a ? (c.tt = d.destroy()) : (d.isActive = !1)); + }); + }, + getLabel: function () { + var a = this.chart.renderer, + d = this.options; + this.label || + (this.split + ? (this.label = a.g("tooltip")) + : ((this.label = a + .label( + "", + 0, + 0, + d.shape || "callout", + null, + null, + d.useHTML, + null, + "tooltip" + ) + .attr({ padding: d.padding, r: d.borderRadius })), + this.label + .attr({ + fill: d.backgroundColor, + "stroke-width": d.borderWidth, + }) + .css(d.style) + .shadow(d.shadow)), + this.label.attr({ zIndex: 8 }).add()); + return this.label; + }, + update: function (a) { + this.destroy(); + this.init(this.chart, p(!0, this.options, a)); + }, + destroy: function () { + this.label && (this.label = this.label.destroy()); + this.split && + this.tt && + (this.cleanSplit(this.chart, !0), (this.tt = this.tt.destroy())); + clearTimeout(this.hideTimer); + clearTimeout(this.tooltipTimeout); + }, + move: function (a, d, f, t) { + var c = this, + b = c.now, + q = + !1 !== c.options.animation && + !c.isHidden && + (1 < Math.abs(a - b.x) || 1 < Math.abs(d - b.y)), + n = c.followPointer || 1 < c.len; + G(b, { + x: q ? (2 * b.x + a) / 3 : a, + y: q ? (b.y + d) / 2 : d, + anchorX: n ? void 0 : q ? (2 * b.anchorX + f) / 3 : f, + anchorY: n ? void 0 : q ? (b.anchorY + t) / 2 : t, + }); + c.getLabel().attr(b); + q && + (clearTimeout(this.tooltipTimeout), + (this.tooltipTimeout = setTimeout(function () { + c && c.move(a, d, f, t); + }, 32))); + }, + hide: function (a) { + var c = this; + clearTimeout(this.hideTimer); + a = v(a, this.options.hideDelay, 500); + this.isHidden || + (this.hideTimer = u(function () { + c.getLabel()[a ? "fadeOut" : "hide"](); + c.isHidden = !0; + }, a)); + }, + getAnchor: function (a, d) { + var c, + n = this.chart, + m = n.inverted, + b = n.plotTop, + q = n.plotLeft, + z = 0, + h = 0, + e, + r; + a = l(a); + c = a[0].tooltipPos; + this.followPointer && + d && + (void 0 === d.chartX && (d = n.pointer.normalize(d)), + (c = [d.chartX - n.plotLeft, d.chartY - b])); + c || + (C(a, function (a) { + e = a.series.yAxis; + r = a.series.xAxis; + z += a.plotX + (!m && r ? r.left - q : 0); + h += + (a.plotLow ? (a.plotLow + a.plotHigh) / 2 : a.plotY) + + (!m && e ? e.top - b : 0); + }), + (z /= a.length), + (h /= a.length), + (c = [ + m ? n.plotWidth - h : z, + this.shared && !m && 1 < a.length && d + ? d.chartY - b + : m + ? n.plotHeight - z + : h, + ])); + return f(c, Math.round); + }, + getPosition: function (a, d, f) { + var c = this.chart, + m = this.distance, + b = {}, + q = f.h || 0, + n, + h = [ + "y", + c.chartHeight, + d, + f.plotY + c.plotTop, + c.plotTop, + c.plotTop + c.plotHeight, + ], + e = [ + "x", + c.chartWidth, + a, + f.plotX + c.plotLeft, + c.plotLeft, + c.plotLeft + c.plotWidth, + ], + r = !this.followPointer && v(f.ttBelow, !c.inverted === !!f.negative), + l = function (a, c, k, g, e, d) { + var f = k < g - m, + w = g + m + k < c, + n = g - m - k; + g += m; + if (r && w) b[a] = g; + else if (!r && f) b[a] = n; + else if (f) b[a] = Math.min(d - k, 0 > n - q ? n : n - q); + else if (w) b[a] = Math.max(e, g + q + k > c ? g : g + q); + else return !1; + }, + p = function (a, c, k, g) { + var e; + g < m || g > c - m + ? (e = !1) + : (b[a] = g < k / 2 ? 1 : g > c - k / 2 ? c - k - 2 : g - k / 2); + return e; + }, + k = function (a) { + var b = h; + h = e; + e = b; + n = a; + }, + w = function () { + !1 !== l.apply(0, h) + ? !1 !== p.apply(0, e) || n || (k(!0), w()) + : n + ? (b.x = b.y = 0) + : (k(!0), w()); + }; + (c.inverted || 1 < this.len) && k(); + w(); + return b; + }, + defaultFormatter: function (a) { + var c = this.points || l(this), + d; + d = [a.tooltipFooterHeaderFormatter(c[0])]; + d = d.concat(a.bodyFormatter(c)); + d.push(a.tooltipFooterHeaderFormatter(c[0], !0)); + return d; + }, + refresh: function (a, d) { + var c = this.chart, + f, + m = this.options, + b, + q, + n = {}, + h = []; + f = m.formatter || this.defaultFormatter; + var n = c.hoverPoints, + e = this.shared; + clearTimeout(this.hideTimer); + this.followPointer = l(a)[0].series.tooltipOptions.followPointer; + q = this.getAnchor(a, d); + d = q[0]; + b = q[1]; + !e || (a.series && a.series.noSharedTooltip) + ? (n = a.getLabelConfig()) + : ((c.hoverPoints = a), + n && + C(n, function (a) { + a.setState(); + }), + C(a, function (a) { + a.setState("hover"); + h.push(a.getLabelConfig()); + }), + (n = { x: a[0].category, y: a[0].y }), + (n.points = h), + (this.len = h.length), + (a = a[0])); + n = f.call(n, this); + e = a.series; + this.distance = v(e.tooltipOptions.distance, 16); + !1 === n + ? this.hide() + : ((f = this.getLabel()), + this.isHidden && f.attr({ opacity: 1 }).show(), + this.split + ? this.renderSplit(n, c.hoverPoints) + : (f.attr({ text: n && n.join ? n.join("") : n }), + f + .removeClass(/highcharts-color-[\d]+/g) + .addClass( + "highcharts-color-" + v(a.colorIndex, e.colorIndex) + ), + f.attr({ + stroke: m.borderColor || a.color || e.color || "#666666", + }), + this.updatePosition({ + plotX: d, + plotY: b, + negative: a.negative, + ttBelow: a.ttBelow, + h: q[2] || 0, + })), + (this.isHidden = !1)); + }, + renderSplit: function (c, d) { + var f = this, + n = [], + m = this.chart, + b = m.renderer, + q = !0, + h = this.options, + l, + e = this.getLabel(); + C(c.slice(0, c.length - 1), function (a, c) { + c = d[c - 1] || { isHeader: !0, plotX: d[0].plotX }; + var r = c.series || f, + k = r.tt, + w = c.series || {}, + t = "highcharts-color-" + v(c.colorIndex, w.colorIndex, "none"); + k || + (r.tt = k = + b + .label(null, null, null, "callout") + .addClass("highcharts-tooltip-box " + t) + .attr({ + padding: h.padding, + r: h.borderRadius, + fill: h.backgroundColor, + stroke: c.color || w.color || "#333333", + "stroke-width": h.borderWidth, + }) + .add(e)); + k.isActive = !0; + k.attr({ text: a }); + k.css(h.style); + a = k.getBBox(); + w = a.width + k.strokeWidth(); + c.isHeader + ? ((l = a.height), + (w = Math.max( + 0, + Math.min(c.plotX + m.plotLeft - w / 2, m.chartWidth - w) + ))) + : (w = c.plotX + m.plotLeft - v(h.distance, 16) - w); + 0 > w && (q = !1); + a = + (c.series && c.series.yAxis && c.series.yAxis.pos) + (c.plotY || 0); + a -= m.plotTop; + n.push({ + target: c.isHeader ? m.plotHeight + l : a, + rank: c.isHeader ? 1 : 0, + size: r.tt.getBBox().height + 1, + point: c, + x: w, + tt: k, + }); + }); + this.cleanSplit(); + a.distribute(n, m.plotHeight + l); + C(n, function (a) { + var b = a.point, + c = b.series; + a.tt.attr({ + visibility: void 0 === a.pos ? "hidden" : "inherit", + x: q || b.isHeader ? a.x : b.plotX + m.plotLeft + v(h.distance, 16), + y: a.pos + m.plotTop, + anchorX: b.isHeader ? b.plotX + m.plotLeft : b.plotX + c.xAxis.pos, + anchorY: b.isHeader + ? a.pos + m.plotTop - 15 + : b.plotY + c.yAxis.pos, + }); + }); + }, + updatePosition: function (a) { + var c = this.chart, + d = this.getLabel(), + d = (this.options.positioner || this.getPosition).call( + this, + d.width, + d.height, + a + ); + this.move( + Math.round(d.x), + Math.round(d.y || 0), + a.plotX + c.plotLeft, + a.plotY + c.plotTop + ); + }, + getXDateFormat: function (a, f, h) { + var c; + f = f.dateTimeLabelFormats; + var m = h && h.closestPointRange, + b, + q = { millisecond: 15, second: 12, minute: 9, hour: 6, day: 3 }, + n, + l = "millisecond"; + if (m) { + n = D("%m-%d %H:%M:%S.%L", a.x); + for (b in d) { + if ( + m === d.week && + +D("%w", a.x) === h.options.startOfWeek && + "00:00:00.000" === n.substr(6) + ) { + b = "week"; + break; + } + if (d[b] > m) { + b = l; + break; + } + if (q[b] && n.substr(q[b]) !== "01-01 00:00:00.000".substr(q[b])) + break; + "week" !== b && (l = b); + } + b && (c = f[b]); + } else c = f.day; + return c || f.year; + }, + tooltipFooterHeaderFormatter: function (a, d) { + var c = d ? "footer" : "header"; + d = a.series; + var f = d.tooltipOptions, + m = f.xDateFormat, + b = d.xAxis, + q = b && "datetime" === b.options.type && h(a.key), + c = f[c + "Format"]; + q && !m && (m = this.getXDateFormat(a, f, b)); + q && m && (c = c.replace("{point.key}", "{point.key:" + m + "}")); + return I(c, { point: a, series: d }); + }, + bodyFormatter: function (a) { + return f(a, function (a) { + var c = a.series.tooltipOptions; + return (c.pointFormatter || a.point.tooltipFormatter).call( + a.point, + c.pointFormat + ); + }); + }, + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.attr, + G = a.charts, + I = a.color, + h = a.css, + f = a.defined, + p = a.doc, + v = a.each, + l = a.extend, + u = a.fireEvent, + d = a.offset, + c = a.pick, + n = a.removeEvent, + y = a.splat, + t = a.Tooltip, + m = a.win; + a.Pointer = function (a, c) { + this.init(a, c); + }; + a.Pointer.prototype = { + init: function (a, m) { + this.options = m; + this.chart = a; + this.runChartClick = m.chart.events && !!m.chart.events.click; + this.pinchDown = []; + this.lastValidTouch = {}; + t && + m.tooltip.enabled && + ((a.tooltip = new t(a, m.tooltip)), + (this.followTouchMove = c(m.tooltip.followTouchMove, !0))); + this.setDOMEvents(); + }, + zoomOption: function (a) { + var b = this.chart, + m = b.options.chart, + d = m.zoomType || "", + b = b.inverted; + /touch/.test(a.type) && (d = c(m.pinchType, d)); + this.zoomX = a = /x/.test(d); + this.zoomY = d = /y/.test(d); + this.zoomHor = (a && !b) || (d && b); + this.zoomVert = (d && !b) || (a && b); + this.hasZoom = a || d; + }, + normalize: function (a, c) { + var b, q; + a = a || m.event; + a.target || (a.target = a.srcElement); + q = a.touches + ? a.touches.length + ? a.touches.item(0) + : a.changedTouches[0] + : a; + c || (this.chartPosition = c = d(this.chart.container)); + void 0 === q.pageX + ? ((b = Math.max(a.x, a.clientX - c.left)), (c = a.y)) + : ((b = q.pageX - c.left), (c = q.pageY - c.top)); + return l(a, { chartX: Math.round(b), chartY: Math.round(c) }); + }, + getCoordinates: function (a) { + var b = { xAxis: [], yAxis: [] }; + v(this.chart.axes, function (c) { + b[c.isXAxis ? "xAxis" : "yAxis"].push({ + axis: c, + value: c.toValue(a[c.horiz ? "chartX" : "chartY"]), + }); + }); + return b; + }, + runPointActions: function (b) { + var m = this.chart, + d = m.series, + f = m.tooltip, + e = f ? f.shared : !1, + r = !0, + n = m.hoverPoint, + h = m.hoverSeries, + k, + w, + l, + t = [], + u; + if (!e && !h) + for (k = 0; k < d.length; k++) + if (d[k].directTouch || !d[k].options.stickyTracking) d = []; + h && (e ? h.noSharedTooltip : h.directTouch) && n + ? (t = [n]) + : (e || !h || h.options.stickyTracking || (d = [h]), + v(d, function (a) { + w = a.noSharedTooltip && e; + l = !e && a.directTouch; + a.visible && + !w && + !l && + c(a.options.enableMouseTracking, !0) && + (u = a.searchPoint(b, !w && 1 === a.kdDimensions)) && + u.series && + t.push(u); + }), + t.sort(function (a, b) { + var c = a.distX - b.distX, + g = a.dist - b.dist, + k = b.series.group.zIndex - a.series.group.zIndex; + return 0 !== c && e + ? c + : 0 !== g + ? g + : 0 !== k + ? k + : a.series.index > b.series.index + ? -1 + : 1; + })); + if (e) + for (k = t.length; k--; ) + (t[k].x !== t[0].x || t[k].series.noSharedTooltip) && + t.splice(k, 1); + if (t[0] && (t[0] !== this.prevKDPoint || (f && f.isHidden))) { + if (e && !t[0].series.noSharedTooltip) { + for (k = 0; k < t.length; k++) + t[k].onMouseOver(b, t[k] !== ((h && h.directTouch && n) || t[0])); + t.length && + f && + f.refresh( + t.sort(function (a, b) { + return a.series.index - b.series.index; + }), + b + ); + } else if ((f && f.refresh(t[0], b), !h || !h.directTouch)) + t[0].onMouseOver(b); + this.prevKDPoint = t[0]; + r = !1; + } + r && + ((d = h && h.tooltipOptions.followPointer), + f && + d && + !f.isHidden && + ((d = f.getAnchor([{}], b)), + f.updatePosition({ plotX: d[0], plotY: d[1] }))); + this.unDocMouseMove || + (this.unDocMouseMove = D(p, "mousemove", function (b) { + if (G[a.hoverChartIndex]) + G[a.hoverChartIndex].pointer.onDocumentMouseMove(b); + })); + v(e ? t : [c(n, t[0])], function (a) { + v(m.axes, function (c) { + (!a || (a.series && a.series[c.coll] === c)) && + c.drawCrosshair(b, a); + }); + }); + }, + reset: function (a, c) { + var b = this.chart, + m = b.hoverSeries, + e = b.hoverPoint, + d = b.hoverPoints, + q = b.tooltip, + f = q && q.shared ? d : e; + a && + f && + v(y(f), function (b) { + b.series.isCartesian && void 0 === b.plotX && (a = !1); + }); + if (a) + q && + f && + (q.refresh(f), + e && + (e.setState(e.state, !0), + v(b.axes, function (a) { + a.crosshair && a.drawCrosshair(null, e); + }))); + else { + if (e) e.onMouseOut(); + d && + v(d, function (a) { + a.setState(); + }); + if (m) m.onMouseOut(); + q && q.hide(c); + this.unDocMouseMove && (this.unDocMouseMove = this.unDocMouseMove()); + v(b.axes, function (a) { + a.hideCrosshair(); + }); + this.hoverX = this.prevKDPoint = b.hoverPoints = b.hoverPoint = null; + } + }, + scaleGroups: function (a, c) { + var b = this.chart, + m; + v(b.series, function (e) { + m = a || e.getPlotBox(); + e.xAxis && + e.xAxis.zoomEnabled && + e.group && + (e.group.attr(m), + e.markerGroup && + (e.markerGroup.attr(m), + e.markerGroup.clip(c ? b.clipRect : null)), + e.dataLabelsGroup && e.dataLabelsGroup.attr(m)); + }); + b.clipRect.attr(c || b.clipBox); + }, + dragStart: function (a) { + var b = this.chart; + b.mouseIsDown = a.type; + b.cancelClick = !1; + b.mouseDownX = this.mouseDownX = a.chartX; + b.mouseDownY = this.mouseDownY = a.chartY; + }, + drag: function (a) { + var b = this.chart, + c = b.options.chart, + m = a.chartX, + e = a.chartY, + d = this.zoomHor, + f = this.zoomVert, + n = b.plotLeft, + k = b.plotTop, + w = b.plotWidth, + h = b.plotHeight, + l, + t = this.selectionMarker, + g = this.mouseDownX, + p = this.mouseDownY, + u = c.panKey && a[c.panKey + "Key"]; + (t && t.touch) || + (m < n ? (m = n) : m > n + w && (m = n + w), + e < k ? (e = k) : e > k + h && (e = k + h), + (this.hasDragged = Math.sqrt( + Math.pow(g - m, 2) + Math.pow(p - e, 2) + )), + 10 < this.hasDragged && + ((l = b.isInsidePlot(g - n, p - k)), + b.hasCartesianSeries && + (this.zoomX || this.zoomY) && + l && + !u && + !t && + (this.selectionMarker = t = + b.renderer + .rect(n, k, d ? 1 : w, f ? 1 : h, 0) + .attr({ + fill: + c.selectionMarkerFill || + I("#335cad").setOpacity(0.25).get(), + class: "highcharts-selection-marker", + zIndex: 7, + }) + .add()), + t && + d && + ((m -= g), + t.attr({ width: Math.abs(m), x: (0 < m ? 0 : m) + g })), + t && + f && + ((m = e - p), + t.attr({ height: Math.abs(m), y: (0 < m ? 0 : m) + p })), + l && !t && c.panning && b.pan(a, c.panning))); + }, + drop: function (a) { + var b = this, + c = this.chart, + m = this.hasPinched; + if (this.selectionMarker) { + var e = { originalEvent: a, xAxis: [], yAxis: [] }, + d = this.selectionMarker, + n = d.attr ? d.attr("x") : d.x, + t = d.attr ? d.attr("y") : d.y, + k = d.attr ? d.attr("width") : d.width, + w = d.attr ? d.attr("height") : d.height, + p; + if (this.hasDragged || m) + v(c.axes, function (c) { + if ( + c.zoomEnabled && + f(c.min) && + (m || b[{ xAxis: "zoomX", yAxis: "zoomY" }[c.coll]]) + ) { + var d = c.horiz, + g = "touchend" === a.type ? c.minPixelPadding : 0, + q = c.toValue((d ? n : t) + g), + d = c.toValue((d ? n + k : t + w) - g); + e[c.coll].push({ + axis: c, + min: Math.min(q, d), + max: Math.max(q, d), + }); + p = !0; + } + }), + p && + u(c, "selection", e, function (a) { + c.zoom(l(a, m ? { animation: !1 } : null)); + }); + this.selectionMarker = this.selectionMarker.destroy(); + m && this.scaleGroups(); + } + c && + (h(c.container, { cursor: c._cursor }), + (c.cancelClick = 10 < this.hasDragged), + (c.mouseIsDown = this.hasDragged = this.hasPinched = !1), + (this.pinchDown = [])); + }, + onContainerMouseDown: function (a) { + a = this.normalize(a); + this.zoomOption(a); + a.preventDefault && a.preventDefault(); + this.dragStart(a); + }, + onDocumentMouseUp: function (b) { + G[a.hoverChartIndex] && G[a.hoverChartIndex].pointer.drop(b); + }, + onDocumentMouseMove: function (a) { + var b = this.chart, + c = this.chartPosition; + a = this.normalize(a, c); + !c || + this.inClass(a.target, "highcharts-tracker") || + b.isInsidePlot(a.chartX - b.plotLeft, a.chartY - b.plotTop) || + this.reset(); + }, + onContainerMouseLeave: function (b) { + var c = G[a.hoverChartIndex]; + c && + (b.relatedTarget || b.toElement) && + (c.pointer.reset(), (c.pointer.chartPosition = null)); + }, + onContainerMouseMove: function (b) { + var c = this.chart; + (f(a.hoverChartIndex) && + G[a.hoverChartIndex] && + G[a.hoverChartIndex].mouseIsDown) || + (a.hoverChartIndex = c.index); + b = this.normalize(b); + b.returnValue = !1; + "mousedown" === c.mouseIsDown && this.drag(b); + (!this.inClass(b.target, "highcharts-tracker") && + !c.isInsidePlot(b.chartX - c.plotLeft, b.chartY - c.plotTop)) || + c.openMenu || + this.runPointActions(b); + }, + inClass: function (a, c) { + for (var b; a; ) { + if ((b = C(a, "class"))) { + if (-1 !== b.indexOf(c)) return !0; + if (-1 !== b.indexOf("highcharts-container")) return !1; + } + a = a.parentNode; + } + }, + onTrackerMouseOut: function (a) { + var b = this.chart.hoverSeries; + a = a.relatedTarget || a.toElement; + if ( + !( + !b || + !a || + b.options.stickyTracking || + this.inClass(a, "highcharts-tooltip") || + (this.inClass(a, "highcharts-series-" + b.index) && + this.inClass(a, "highcharts-tracker")) + ) + ) + b.onMouseOut(); + }, + onContainerClick: function (a) { + var b = this.chart, + c = b.hoverPoint, + m = b.plotLeft, + e = b.plotTop; + a = this.normalize(a); + b.cancelClick || + (c && this.inClass(a.target, "highcharts-tracker") + ? (u(c.series, "click", l(a, { point: c })), + b.hoverPoint && c.firePointEvent("click", a)) + : (l(a, this.getCoordinates(a)), + b.isInsidePlot(a.chartX - m, a.chartY - e) && u(b, "click", a))); + }, + setDOMEvents: function () { + var b = this, + c = b.chart.container; + c.onmousedown = function (a) { + b.onContainerMouseDown(a); + }; + c.onmousemove = function (a) { + b.onContainerMouseMove(a); + }; + c.onclick = function (a) { + b.onContainerClick(a); + }; + D(c, "mouseleave", b.onContainerMouseLeave); + 1 === a.chartCount && D(p, "mouseup", b.onDocumentMouseUp); + a.hasTouch && + ((c.ontouchstart = function (a) { + b.onContainerTouchStart(a); + }), + (c.ontouchmove = function (a) { + b.onContainerTouchMove(a); + }), + 1 === a.chartCount && D(p, "touchend", b.onDocumentTouchEnd)); + }, + destroy: function () { + var b; + n(this.chart.container, "mouseleave", this.onContainerMouseLeave); + a.chartCount || + (n(p, "mouseup", this.onDocumentMouseUp), + n(p, "touchend", this.onDocumentTouchEnd)); + clearInterval(this.tooltipTimeout); + for (b in this) this[b] = null; + }, + }; + })(L); + (function (a) { + var D = a.charts, + C = a.each, + G = a.extend, + I = a.map, + h = a.noop, + f = a.pick; + G(a.Pointer.prototype, { + pinchTranslate: function (a, f, h, u, d, c) { + this.zoomHor && this.pinchTranslateDirection(!0, a, f, h, u, d, c); + this.zoomVert && this.pinchTranslateDirection(!1, a, f, h, u, d, c); + }, + pinchTranslateDirection: function (a, f, h, u, d, c, n, y) { + var t = this.chart, + m = a ? "x" : "y", + b = a ? "X" : "Y", + q = "chart" + b, + l = a ? "width" : "height", + p = t["plot" + (a ? "Left" : "Top")], + e, + r, + x = y || 1, + A = t.inverted, + k = t.bounds[a ? "h" : "v"], + w = 1 === f.length, + K = f[0][q], + J = h[0][q], + v = !w && f[1][q], + g = !w && h[1][q], + B; + h = function () { + !w && + 20 < Math.abs(K - v) && + (x = y || Math.abs(J - g) / Math.abs(K - v)); + r = (p - J) / x + K; + e = t["plot" + (a ? "Width" : "Height")] / x; + }; + h(); + f = r; + f < k.min + ? ((f = k.min), (B = !0)) + : f + e > k.max && ((f = k.max - e), (B = !0)); + B + ? ((J -= 0.8 * (J - n[m][0])), w || (g -= 0.8 * (g - n[m][1])), h()) + : (n[m] = [J, g]); + A || ((c[m] = r - p), (c[l] = e)); + c = A ? 1 / x : x; + d[l] = e; + d[m] = f; + u[A ? (a ? "scaleY" : "scaleX") : "scale" + b] = x; + u["translate" + b] = c * p + (J - c * K); + }, + pinch: function (a) { + var p = this, + l = p.chart, + u = p.pinchDown, + d = a.touches, + c = d.length, + n = p.lastValidTouch, + y = p.hasZoom, + t = p.selectionMarker, + m = {}, + b = + 1 === c && + ((p.inClass(a.target, "highcharts-tracker") && l.runTrackerClick) || + p.runChartClick), + q = {}; + 1 < c && (p.initiated = !0); + y && p.initiated && !b && a.preventDefault(); + I(d, function (a) { + return p.normalize(a); + }); + "touchstart" === a.type + ? (C(d, function (a, b) { + u[b] = { chartX: a.chartX, chartY: a.chartY }; + }), + (n.x = [u[0].chartX, u[1] && u[1].chartX]), + (n.y = [u[0].chartY, u[1] && u[1].chartY]), + C(l.axes, function (a) { + if (a.zoomEnabled) { + var b = l.bounds[a.horiz ? "h" : "v"], + c = a.minPixelPadding, + m = a.toPixels(f(a.options.min, a.dataMin)), + d = a.toPixels(f(a.options.max, a.dataMax)), + q = Math.max(m, d); + b.min = Math.min(a.pos, Math.min(m, d) - c); + b.max = Math.max(a.pos + a.len, q + c); + } + }), + (p.res = !0)) + : p.followTouchMove && 1 === c + ? this.runPointActions(p.normalize(a)) + : u.length && + (t || + (p.selectionMarker = t = G({ destroy: h, touch: !0 }, l.plotBox)), + p.pinchTranslate(u, d, m, t, q, n), + (p.hasPinched = y), + p.scaleGroups(m, q), + p.res && ((p.res = !1), this.reset(!1, 0))); + }, + touch: function (h, v) { + var l = this.chart, + p, + d; + if (l.index !== a.hoverChartIndex) + this.onContainerMouseLeave({ relatedTarget: !0 }); + a.hoverChartIndex = l.index; + 1 === h.touches.length + ? ((h = this.normalize(h)), + (d = l.isInsidePlot(h.chartX - l.plotLeft, h.chartY - l.plotTop)) && + !l.openMenu + ? (v && this.runPointActions(h), + "touchmove" === h.type && + ((v = this.pinchDown), + (p = v[0] + ? 4 <= + Math.sqrt( + Math.pow(v[0].chartX - h.chartX, 2) + + Math.pow(v[0].chartY - h.chartY, 2) + ) + : !1)), + f(p, !0) && this.pinch(h)) + : v && this.reset()) + : 2 === h.touches.length && this.pinch(h); + }, + onContainerTouchStart: function (a) { + this.zoomOption(a); + this.touch(a, !0); + }, + onContainerTouchMove: function (a) { + this.touch(a); + }, + onDocumentTouchEnd: function (f) { + D[a.hoverChartIndex] && D[a.hoverChartIndex].pointer.drop(f); + }, + }); + })(L); + (function (a) { + var D = a.addEvent, + C = a.charts, + G = a.css, + I = a.doc, + h = a.extend, + f = a.noop, + p = a.Pointer, + v = a.removeEvent, + l = a.win, + u = a.wrap; + if (l.PointerEvent || l.MSPointerEvent) { + var d = {}, + c = !!l.PointerEvent, + n = function () { + var a, + c = []; + c.item = function (a) { + return this[a]; + }; + for (a in d) + d.hasOwnProperty(a) && + c.push({ + pageX: d[a].pageX, + pageY: d[a].pageY, + target: d[a].target, + }); + return c; + }, + y = function (c, m, b, d) { + ("touch" !== c.pointerType && + c.pointerType !== c.MSPOINTER_TYPE_TOUCH) || + !C[a.hoverChartIndex] || + (d(c), + (d = C[a.hoverChartIndex].pointer), + d[m]({ + type: b, + target: c.currentTarget, + preventDefault: f, + touches: n(), + })); + }; + h(p.prototype, { + onContainerPointerDown: function (a) { + y(a, "onContainerTouchStart", "touchstart", function (a) { + d[a.pointerId] = { + pageX: a.pageX, + pageY: a.pageY, + target: a.currentTarget, + }; + }); + }, + onContainerPointerMove: function (a) { + y(a, "onContainerTouchMove", "touchmove", function (a) { + d[a.pointerId] = { pageX: a.pageX, pageY: a.pageY }; + d[a.pointerId].target || (d[a.pointerId].target = a.currentTarget); + }); + }, + onDocumentPointerUp: function (a) { + y(a, "onDocumentTouchEnd", "touchend", function (a) { + delete d[a.pointerId]; + }); + }, + batchMSEvents: function (a) { + a( + this.chart.container, + c ? "pointerdown" : "MSPointerDown", + this.onContainerPointerDown + ); + a( + this.chart.container, + c ? "pointermove" : "MSPointerMove", + this.onContainerPointerMove + ); + a(I, c ? "pointerup" : "MSPointerUp", this.onDocumentPointerUp); + }, + }); + u(p.prototype, "init", function (a, c, b) { + a.call(this, c, b); + this.hasZoom && + G(c.container, { + "-ms-touch-action": "none", + "touch-action": "none", + }); + }); + u(p.prototype, "setDOMEvents", function (a) { + a.apply(this); + (this.hasZoom || this.followTouchMove) && this.batchMSEvents(D); + }); + u(p.prototype, "destroy", function (a) { + this.batchMSEvents(v); + a.call(this); + }); + } + })(L); + (function (a) { + var D, + C = a.addEvent, + G = a.css, + I = a.discardElement, + h = a.defined, + f = a.each, + p = a.extend, + v = a.isFirefox, + l = a.marginNames, + u = a.merge, + d = a.pick, + c = a.setAnimation, + n = a.stableSort, + y = a.win, + t = a.wrap; + D = a.Legend = function (a, b) { + this.init(a, b); + }; + D.prototype = { + init: function (a, b) { + this.chart = a; + this.setOptions(b); + b.enabled && + (this.render(), + C(this.chart, "endResize", function () { + this.legend.positionCheckboxes(); + })); + }, + setOptions: function (a) { + var b = d(a.padding, 8); + this.options = a; + this.itemStyle = a.itemStyle; + this.itemHiddenStyle = u(this.itemStyle, a.itemHiddenStyle); + this.itemMarginTop = a.itemMarginTop || 0; + this.initialItemX = this.padding = b; + this.initialItemY = b - 5; + this.itemHeight = this.maxItemWidth = 0; + this.symbolWidth = d(a.symbolWidth, 16); + this.pages = []; + }, + update: function (a, b) { + var c = this.chart; + this.setOptions(u(!0, this.options, a)); + this.destroy(); + c.isDirtyLegend = c.isDirtyBox = !0; + d(b, !0) && c.redraw(); + }, + colorizeItem: function (a, b) { + a.legendGroup[b ? "removeClass" : "addClass"]( + "highcharts-legend-item-hidden" + ); + var c = this.options, + d = a.legendItem, + m = a.legendLine, + e = a.legendSymbol, + f = this.itemHiddenStyle.color, + c = b ? c.itemStyle.color : f, + h = b ? a.color || f : f, + n = a.options && a.options.marker, + k = { fill: h }, + w; + d && d.css({ fill: c, color: c }); + m && m.attr({ stroke: h }); + if (e) { + if (n && e.isMarker && ((k = a.pointAttribs()), !b)) + for (w in k) k[w] = f; + e.attr(k); + } + }, + positionItem: function (a) { + var b = this.options, + c = b.symbolPadding, + b = !b.rtl, + d = a._legendItemPos, + m = d[0], + d = d[1], + e = a.checkbox; + (a = a.legendGroup) && + a.element && + a.translate(b ? m : this.legendWidth - m - 2 * c - 4, d); + e && ((e.x = m), (e.y = d)); + }, + destroyItem: function (a) { + var b = a.checkbox; + f( + ["legendItem", "legendLine", "legendSymbol", "legendGroup"], + function (b) { + a[b] && (a[b] = a[b].destroy()); + } + ); + b && I(a.checkbox); + }, + destroy: function () { + function a(a) { + this[a] && (this[a] = this[a].destroy()); + } + f(this.getAllItems(), function (b) { + f(["legendItem", "legendGroup"], a, b); + }); + f(["box", "title", "group"], a, this); + this.display = null; + }, + positionCheckboxes: function (a) { + var b = this.group && this.group.alignAttr, + c, + d = this.clipHeight || this.legendHeight, + m = this.titleHeight; + b && + ((c = b.translateY), + f(this.allItems, function (e) { + var f = e.checkbox, + h; + f && + ((h = c + m + f.y + (a || 0) + 3), + G(f, { + left: b.translateX + e.checkboxOffset + f.x - 20 + "px", + top: h + "px", + display: h > c - 6 && h < c + d - 6 ? "" : "none", + })); + })); + }, + renderTitle: function () { + var a = this.padding, + b = this.options.title, + c = 0; + b.text && + (this.title || + (this.title = this.chart.renderer + .label( + b.text, + a - 3, + a - 4, + null, + null, + null, + null, + null, + "legend-title" + ) + .attr({ zIndex: 1 }) + .css(b.style) + .add(this.group)), + (a = this.title.getBBox()), + (c = a.height), + (this.offsetWidth = a.width), + this.contentGroup.attr({ translateY: c })); + this.titleHeight = c; + }, + setText: function (c) { + var b = this.options; + c.legendItem.attr({ + text: b.labelFormat + ? a.format(b.labelFormat, c) + : b.labelFormatter.call(c), + }); + }, + renderItem: function (a) { + var b = this.chart, + c = b.renderer, + m = this.options, + f = "horizontal" === m.layout, + e = this.symbolWidth, + h = m.symbolPadding, + n = this.itemStyle, + l = this.itemHiddenStyle, + k = this.padding, + w = f ? d(m.itemDistance, 20) : 0, + t = !m.rtl, + p = m.width, + y = m.itemMarginBottom || 0, + g = this.itemMarginTop, + B = this.initialItemX, + v = a.legendItem, + M = !a.series, + C = !M && a.series.drawLegendSymbol ? a.series : a, + E = C.options, + E = this.createCheckboxForItem && E && E.showCheckbox, + H = m.useHTML; + v || + ((a.legendGroup = c + .g("legend-item") + .addClass( + "highcharts-" + + C.type + + "-series highcharts-color-" + + a.colorIndex + + (a.options.className ? " " + a.options.className : "") + + (M ? " highcharts-series-" + a.index : "") + ) + .attr({ zIndex: 1 }) + .add(this.scrollGroup)), + (a.legendItem = v = + c + .text("", t ? e + h : -h, this.baseline || 0, H) + .css(u(a.visible ? n : l)) + .attr({ align: t ? "left" : "right", zIndex: 2 }) + .add(a.legendGroup)), + this.baseline || + ((n = n.fontSize), + (this.fontMetrics = c.fontMetrics(n, v)), + (this.baseline = this.fontMetrics.f + 3 + g), + v.attr("y", this.baseline)), + C.drawLegendSymbol(this, a), + this.setItemEvents && this.setItemEvents(a, v, H), + E && this.createCheckboxForItem(a)); + this.colorizeItem(a, a.visible); + this.setText(a); + c = v.getBBox(); + e = a.checkboxOffset = + m.itemWidth || + a.legendItemWidth || + e + h + c.width + w + (E ? 20 : 0); + this.itemHeight = h = Math.round(a.legendItemHeight || c.height); + f && + this.itemX - B + e > (p || b.chartWidth - 2 * k - B - m.x) && + ((this.itemX = B), + (this.itemY += g + this.lastLineHeight + y), + (this.lastLineHeight = 0)); + this.maxItemWidth = Math.max(this.maxItemWidth, e); + this.lastItemY = g + this.itemY + y; + this.lastLineHeight = Math.max(h, this.lastLineHeight); + a._legendItemPos = [this.itemX, this.itemY]; + f + ? (this.itemX += e) + : ((this.itemY += g + h + y), (this.lastLineHeight = h)); + this.offsetWidth = + p || Math.max((f ? this.itemX - B - w : e) + k, this.offsetWidth); + }, + getAllItems: function () { + var a = []; + f(this.chart.series, function (b) { + var c = b && b.options; + b && + d(c.showInLegend, h(c.linkedTo) ? !1 : void 0, !0) && + (a = a.concat( + b.legendItems || ("point" === c.legendType ? b.data : b) + )); + }); + return a; + }, + adjustMargins: function (a, b) { + var c = this.chart, + m = this.options, + n = + m.align.charAt(0) + m.verticalAlign.charAt(0) + m.layout.charAt(0); + m.floating || + f( + [/(lth|ct|rth)/, /(rtv|rm|rbv)/, /(rbh|cb|lbh)/, /(lbv|lm|ltv)/], + function (e, f) { + e.test(n) && + !h(a[f]) && + (c[l[f]] = Math.max( + c[l[f]], + c.legend[(f + 1) % 2 ? "legendHeight" : "legendWidth"] + + [1, -1, -1, 1][f] * m[f % 2 ? "x" : "y"] + + d(m.margin, 12) + + b[f] + )); + } + ); + }, + render: function () { + var a = this, + b = a.chart, + c = b.renderer, + d = a.group, + h, + e, + r, + l, + t = a.box, + k = a.options, + w = a.padding; + a.itemX = a.initialItemX; + a.itemY = a.initialItemY; + a.offsetWidth = 0; + a.lastItemY = 0; + d || + ((a.group = d = c.g("legend").attr({ zIndex: 7 }).add()), + (a.contentGroup = c.g().attr({ zIndex: 1 }).add(d)), + (a.scrollGroup = c.g().add(a.contentGroup))); + a.renderTitle(); + h = a.getAllItems(); + n(h, function (a, b) { + return ( + ((a.options && a.options.legendIndex) || 0) - + ((b.options && b.options.legendIndex) || 0) + ); + }); + k.reversed && h.reverse(); + a.allItems = h; + a.display = e = !!h.length; + a.lastLineHeight = 0; + f(h, function (b) { + a.renderItem(b); + }); + r = (k.width || a.offsetWidth) + w; + l = a.lastItemY + a.lastLineHeight + a.titleHeight; + l = a.handleOverflow(l); + l += w; + t || + ((a.box = t = + c + .rect() + .addClass("highcharts-legend-box") + .attr({ r: k.borderRadius }) + .add(d)), + (t.isNew = !0)); + t.attr({ + stroke: k.borderColor, + "stroke-width": k.borderWidth || 0, + fill: k.backgroundColor || "none", + }).shadow(k.shadow); + 0 < r && + 0 < l && + (t[t.isNew ? "attr" : "animate"]( + t.crisp({ x: 0, y: 0, width: r, height: l }, t.strokeWidth()) + ), + (t.isNew = !1)); + t[e ? "show" : "hide"](); + a.legendWidth = r; + a.legendHeight = l; + f(h, function (b) { + a.positionItem(b); + }); + e && d.align(p({ width: r, height: l }, k), !0, "spacingBox"); + b.isResizing || this.positionCheckboxes(); + }, + handleOverflow: function (a) { + var b = this, + c = this.chart, + m = c.renderer, + h = this.options, + e = h.y, + c = + c.spacingBox.height + + ("top" === h.verticalAlign ? -e : e) - + this.padding, + e = h.maxHeight, + n, + l = this.clipRect, + t = h.navigation, + k = d(t.animation, !0), + w = t.arrowSize || 12, + p = this.nav, + u = this.pages, + y = this.padding, + g, + B = this.allItems, + v = function (a) { + a + ? l.attr({ height: a }) + : l && ((b.clipRect = l.destroy()), b.contentGroup.clip()); + b.contentGroup.div && + (b.contentGroup.div.style.clip = a + ? "rect(" + y + "px,9999px," + (y + a) + "px,0)" + : "auto"); + }; + "horizontal" !== h.layout || + "middle" === h.verticalAlign || + h.floating || + (c /= 2); + e && (c = Math.min(c, e)); + u.length = 0; + a > c && !1 !== t.enabled + ? ((this.clipHeight = n = Math.max(c - 20 - this.titleHeight - y, 0)), + (this.currentPage = d(this.currentPage, 1)), + (this.fullHeight = a), + f(B, function (a, b) { + var c = a._legendItemPos[1]; + a = Math.round(a.legendItem.getBBox().height); + var k = u.length; + if (!k || (c - u[k - 1] > n && (g || c) !== u[k - 1])) + u.push(g || c), k++; + b === B.length - 1 && c + a - u[k - 1] > n && u.push(c); + c !== g && (g = c); + }), + l || + ((l = b.clipRect = m.clipRect(0, y, 9999, 0)), + b.contentGroup.clip(l)), + v(n), + p || + ((this.nav = p = m.g().attr({ zIndex: 1 }).add(this.group)), + (this.up = m + .symbol("triangle", 0, 0, w, w) + .on("click", function () { + b.scroll(-1, k); + }) + .add(p)), + (this.pager = m + .text("", 15, 10) + .addClass("highcharts-legend-navigation") + .css(t.style) + .add(p)), + (this.down = m + .symbol("triangle-down", 0, 0, w, w) + .on("click", function () { + b.scroll(1, k); + }) + .add(p))), + b.scroll(0), + (a = c)) + : p && + (v(), + p.hide(), + this.scrollGroup.attr({ translateY: 1 }), + (this.clipHeight = 0)); + return a; + }, + scroll: function (a, b) { + var d = this.pages, + f = d.length; + a = this.currentPage + a; + var m = this.clipHeight, + e = this.options.navigation, + h = this.pager, + n = this.padding; + a > f && (a = f); + 0 < a && + (void 0 !== b && c(b, this.chart), + this.nav.attr({ + translateX: n, + translateY: m + this.padding + 7 + this.titleHeight, + visibility: "visible", + }), + this.up.attr({ + class: + 1 === a + ? "highcharts-legend-nav-inactive" + : "highcharts-legend-nav-active", + }), + h.attr({ text: a + "/" + f }), + this.down.attr({ + x: 18 + this.pager.getBBox().width, + class: + a === f + ? "highcharts-legend-nav-inactive" + : "highcharts-legend-nav-active", + }), + this.up + .attr({ fill: 1 === a ? e.inactiveColor : e.activeColor }) + .css({ cursor: 1 === a ? "default" : "pointer" }), + this.down + .attr({ fill: a === f ? e.inactiveColor : e.activeColor }) + .css({ cursor: a === f ? "default" : "pointer" }), + (b = -d[a - 1] + this.initialItemY), + this.scrollGroup.animate({ translateY: b }), + (this.currentPage = a), + this.positionCheckboxes(b)); + }, + }; + a.LegendSymbolMixin = { + drawRectangle: function (a, b) { + var c = a.options, + f = c.symbolHeight || a.fontMetrics.f, + c = c.squareSymbol; + b.legendSymbol = this.chart.renderer + .rect( + c ? (a.symbolWidth - f) / 2 : 0, + a.baseline - f + 1, + c ? f : a.symbolWidth, + f, + d(a.options.symbolRadius, f / 2) + ) + .addClass("highcharts-point") + .attr({ zIndex: 3 }) + .add(b.legendGroup); + }, + drawLineMarker: function (a) { + var b = this.options, + c = b.marker, + d = a.symbolWidth, + f = this.chart.renderer, + e = this.legendGroup; + a = a.baseline - Math.round(0.3 * a.fontMetrics.b); + var m; + m = { "stroke-width": b.lineWidth || 0 }; + b.dashStyle && (m.dashstyle = b.dashStyle); + this.legendLine = f + .path(["M", 0, a, "L", d, a]) + .addClass("highcharts-graph") + .attr(m) + .add(e); + c && + !1 !== c.enabled && + ((b = 0 === this.symbol.indexOf("url") ? 0 : c.radius), + (this.legendSymbol = c = + f + .symbol(this.symbol, d / 2 - b, a - b, 2 * b, 2 * b, c) + .addClass("highcharts-point") + .add(e)), + (c.isMarker = !0)); + }, + }; + (/Trident\/7\.0/.test(y.navigator.userAgent) || v) && + t(D.prototype, "positionItem", function (a, b) { + var c = this, + d = function () { + b._legendItemPos && a.call(c, b); + }; + d(); + setTimeout(d); + }); + })(L); + (function (a) { + var D = a.addEvent, + C = a.animate, + G = a.animObject, + I = a.attr, + h = a.doc, + f = a.Axis, + p = a.createElement, + v = a.defaultOptions, + l = a.discardElement, + u = a.charts, + d = a.css, + c = a.defined, + n = a.each, + y = a.extend, + t = a.find, + m = a.fireEvent, + b = a.getStyle, + q = a.grep, + z = a.isNumber, + F = a.isObject, + e = a.isString, + r = a.Legend, + x = a.marginNames, + A = a.merge, + k = a.Pointer, + w = a.pick, + K = a.pInt, + J = a.removeEvent, + N = a.seriesTypes, + g = a.splat, + B = a.svg, + S = a.syncTimeout, + M = a.win, + R = a.Renderer, + E = (a.Chart = function () { + this.getArgs.apply(this, arguments); + }); + a.chart = function (a, b, c) { + return new E(a, b, c); + }; + E.prototype = { + callbacks: [], + getArgs: function () { + var a = [].slice.call(arguments); + if (e(a[0]) || a[0].nodeName) this.renderTo = a.shift(); + this.init(a[0], a[1]); + }, + init: function (b, c) { + var k, + g = b.series; + b.series = null; + k = A(v, b); + k.series = b.series = g; + this.userOptions = b; + this.respRules = []; + b = k.chart; + g = b.events; + this.margin = []; + this.spacing = []; + this.bounds = { h: {}, v: {} }; + this.callback = c; + this.isResizing = 0; + this.options = k; + this.axes = []; + this.series = []; + this.hasCartesianSeries = b.showAxes; + var e; + this.index = u.length; + u.push(this); + a.chartCount++; + if (g) for (e in g) D(this, e, g[e]); + this.xAxis = []; + this.yAxis = []; + this.pointCount = this.colorCounter = this.symbolCounter = 0; + this.firstRender(); + }, + initSeries: function (b) { + var c = this.options.chart; + (c = N[b.type || c.type || c.defaultSeriesType]) || a.error(17, !0); + c = new c(); + c.init(this, b); + return c; + }, + isInsidePlot: function (a, b, c) { + var k = c ? b : a; + a = c ? a : b; + return 0 <= k && k <= this.plotWidth && 0 <= a && a <= this.plotHeight; + }, + redraw: function (b) { + var c = this.axes, + k = this.series, + g = this.pointer, + e = this.legend, + d = this.isDirtyLegend, + f, + h, + w = this.hasCartesianSeries, + r = this.isDirtyBox, + l = k.length, + q = l, + t = this.renderer, + p = t.isHidden(), + H = []; + a.setAnimation(b, this); + p && this.cloneRenderTo(); + for (this.layOutTitles(); q--; ) + if (((b = k[q]), b.options.stacking && ((f = !0), b.isDirty))) { + h = !0; + break; + } + if (h) + for (q = l; q--; ) (b = k[q]), b.options.stacking && (b.isDirty = !0); + n(k, function (a) { + a.isDirty && + "point" === a.options.legendType && + (a.updateTotals && a.updateTotals(), (d = !0)); + a.isDirtyData && m(a, "updatedData"); + }); + d && e.options.enabled && (e.render(), (this.isDirtyLegend = !1)); + f && this.getStacks(); + w && + n(c, function (a) { + a.updateNames(); + a.setScale(); + }); + this.getMargins(); + w && + (n(c, function (a) { + a.isDirty && (r = !0); + }), + n(c, function (a) { + var b = a.min + "," + a.max; + a.extKey !== b && + ((a.extKey = b), + H.push(function () { + m(a, "afterSetExtremes", y(a.eventArgs, a.getExtremes())); + delete a.eventArgs; + })); + (r || f) && a.redraw(); + })); + r && this.drawChartBox(); + n(k, function (a) { + (r || a.isDirty) && a.visible && a.redraw(); + }); + g && g.reset(!0); + t.draw(); + m(this, "redraw"); + p && this.cloneRenderTo(!0); + n(H, function (a) { + a.call(); + }); + }, + get: function (a) { + function b(b) { + return b.id === a || b.options.id === a; + } + var c, + k = this.series, + g; + c = t(this.axes, b) || t(this.series, b); + for (g = 0; !c && g < k.length; g++) c = t(k[g].points || [], b); + return c; + }, + getAxes: function () { + var a = this, + b = this.options, + c = (b.xAxis = g(b.xAxis || {})), + b = (b.yAxis = g(b.yAxis || {})); + n(c, function (a, b) { + a.index = b; + a.isX = !0; + }); + n(b, function (a, b) { + a.index = b; + }); + c = c.concat(b); + n(c, function (b) { + new f(a, b); + }); + }, + getSelectedPoints: function () { + var a = []; + n(this.series, function (b) { + a = a.concat( + q(b.points || [], function (a) { + return a.selected; + }) + ); + }); + return a; + }, + getSelectedSeries: function () { + return q(this.series, function (a) { + return a.selected; + }); + }, + setTitle: function (a, b, c) { + var k = this, + g = k.options, + e; + e = g.title = A( + { + style: { color: "#333333", fontSize: g.isStock ? "16px" : "18px" }, + }, + g.title, + a + ); + g = g.subtitle = A({ style: { color: "#666666" } }, g.subtitle, b); + n( + [ + ["title", a, e], + ["subtitle", b, g], + ], + function (a, b) { + var c = a[0], + g = k[c], + e = a[1]; + a = a[2]; + g && e && (k[c] = g = g.destroy()); + a && + a.text && + !g && + ((k[c] = k.renderer + .text(a.text, 0, 0, a.useHTML) + .attr({ + align: a.align, + class: "highcharts-" + c, + zIndex: a.zIndex || 4, + }) + .add()), + (k[c].update = function (a) { + k.setTitle(!b && a, b && a); + }), + k[c].css(a.style)); + } + ); + k.layOutTitles(c); + }, + layOutTitles: function (a) { + var b = 0, + c, + k = this.renderer, + g = this.spacingBox; + n( + ["title", "subtitle"], + function (a) { + var c = this[a], + e = this.options[a], + d; + c && + ((d = e.style.fontSize), + (d = k.fontMetrics(d, c).b), + c + .css({ width: (e.width || g.width + e.widthAdjust) + "px" }) + .align( + y({ y: b + d + ("title" === a ? -3 : 2) }, e), + !1, + "spacingBox" + ), + e.floating || + e.verticalAlign || + (b = Math.ceil(b + c.getBBox().height))); + }, + this + ); + c = this.titleOffset !== b; + this.titleOffset = b; + !this.isDirtyBox && + c && + ((this.isDirtyBox = c), + this.hasRendered && w(a, !0) && this.isDirtyBox && this.redraw()); + }, + getChartSize: function () { + var a = this.options.chart, + k = a.width, + a = a.height, + g = this.renderToClone || this.renderTo; + c(k) || (this.containerWidth = b(g, "width")); + c(a) || (this.containerHeight = b(g, "height")); + this.chartWidth = Math.max(0, k || this.containerWidth || 600); + this.chartHeight = Math.max( + 0, + w(a, 19 < this.containerHeight ? this.containerHeight : 400) + ); + }, + cloneRenderTo: function (a) { + var b = this.renderToClone, + c = this.container; + if (a) { + if (b) { + for (; b.childNodes.length; ) + this.renderTo.appendChild(b.firstChild); + l(b); + delete this.renderToClone; + } + } else + c && c.parentNode === this.renderTo && this.renderTo.removeChild(c), + (this.renderToClone = b = this.renderTo.cloneNode(0)), + d(b, { position: "absolute", top: "-9999px", display: "block" }), + b.style.setProperty && + b.style.setProperty("display", "block", "important"), + h.body.appendChild(b), + c && b.appendChild(c); + }, + setClassName: function (a) { + this.container.className = "highcharts-container " + (a || ""); + }, + getContainer: function () { + var b, + c = this.options, + k = c.chart, + g, + d; + b = this.renderTo; + var f = a.uniqueKey(), + m; + b || (this.renderTo = b = k.renderTo); + e(b) && (this.renderTo = b = h.getElementById(b)); + b || a.error(13, !0); + g = K(I(b, "data-highcharts-chart")); + z(g) && u[g] && u[g].hasRendered && u[g].destroy(); + I(b, "data-highcharts-chart", this.index); + b.innerHTML = ""; + k.skipClone || b.offsetWidth || this.cloneRenderTo(); + this.getChartSize(); + g = this.chartWidth; + d = this.chartHeight; + m = y( + { + position: "relative", + overflow: "hidden", + width: g + "px", + height: d + "px", + textAlign: "left", + lineHeight: "normal", + zIndex: 0, + "-webkit-tap-highlight-color": "rgba(0,0,0,0)", + }, + k.style + ); + this.container = b = p("div", { id: f }, m, this.renderToClone || b); + this._cursor = b.style.cursor; + this.renderer = new (a[k.renderer] || R)( + b, + g, + d, + null, + k.forExport, + c.exporting && c.exporting.allowHTML + ); + this.setClassName(k.className); + this.renderer.setStyle(k.style); + this.renderer.chartIndex = this.index; + }, + getMargins: function (a) { + var b = this.spacing, + k = this.margin, + g = this.titleOffset; + this.resetMargins(); + g && + !c(k[0]) && + (this.plotTop = Math.max( + this.plotTop, + g + this.options.title.margin + b[0] + )); + this.legend.display && this.legend.adjustMargins(k, b); + this.extraBottomMargin && (this.marginBottom += this.extraBottomMargin); + this.extraTopMargin && (this.plotTop += this.extraTopMargin); + a || this.getAxisMargins(); + }, + getAxisMargins: function () { + var a = this, + b = (a.axisOffset = [0, 0, 0, 0]), + k = a.margin; + a.hasCartesianSeries && + n(a.axes, function (a) { + a.visible && a.getOffset(); + }); + n(x, function (g, e) { + c(k[e]) || (a[g] += b[e]); + }); + a.setChartSize(); + }, + reflow: function (a) { + var k = this, + g = k.options.chart, + e = k.renderTo, + d = c(g.width), + f = g.width || b(e, "width"), + g = g.height || b(e, "height"), + e = a ? a.target : M; + if (!d && !k.isPrinting && f && g && (e === M || e === h)) { + if (f !== k.containerWidth || g !== k.containerHeight) + clearTimeout(k.reflowTimeout), + (k.reflowTimeout = S( + function () { + k.container && k.setSize(void 0, void 0, !1); + }, + a ? 100 : 0 + )); + k.containerWidth = f; + k.containerHeight = g; + } + }, + initReflow: function () { + var a = this, + b; + b = D(M, "resize", function (b) { + a.reflow(b); + }); + D(a, "destroy", b); + }, + setSize: function (b, c, k) { + var g = this, + e = g.renderer; + g.isResizing += 1; + a.setAnimation(k, g); + g.oldChartHeight = g.chartHeight; + g.oldChartWidth = g.chartWidth; + void 0 !== b && (g.options.chart.width = b); + void 0 !== c && (g.options.chart.height = c); + g.getChartSize(); + b = e.globalAnimation; + (b ? C : d)( + g.container, + { width: g.chartWidth + "px", height: g.chartHeight + "px" }, + b + ); + g.setChartSize(!0); + e.setSize(g.chartWidth, g.chartHeight, k); + n(g.axes, function (a) { + a.isDirty = !0; + a.setScale(); + }); + g.isDirtyLegend = !0; + g.isDirtyBox = !0; + g.layOutTitles(); + g.getMargins(); + g.setResponsive && g.setResponsive(!1); + g.redraw(k); + g.oldChartHeight = null; + m(g, "resize"); + S(function () { + g && + m(g, "endResize", null, function () { + --g.isResizing; + }); + }, G(b).duration); + }, + setChartSize: function (a) { + var b = this.inverted, + c = this.renderer, + g = this.chartWidth, + k = this.chartHeight, + e = this.options.chart, + d = this.spacing, + f = this.clipOffset, + m, + h, + w, + r; + this.plotLeft = m = Math.round(this.plotLeft); + this.plotTop = h = Math.round(this.plotTop); + this.plotWidth = w = Math.max(0, Math.round(g - m - this.marginRight)); + this.plotHeight = r = Math.max( + 0, + Math.round(k - h - this.marginBottom) + ); + this.plotSizeX = b ? r : w; + this.plotSizeY = b ? w : r; + this.plotBorderWidth = e.plotBorderWidth || 0; + this.spacingBox = c.spacingBox = { + x: d[3], + y: d[0], + width: g - d[3] - d[1], + height: k - d[0] - d[2], + }; + this.plotBox = c.plotBox = { x: m, y: h, width: w, height: r }; + g = 2 * Math.floor(this.plotBorderWidth / 2); + b = Math.ceil(Math.max(g, f[3]) / 2); + c = Math.ceil(Math.max(g, f[0]) / 2); + this.clipBox = { + x: b, + y: c, + width: Math.floor(this.plotSizeX - Math.max(g, f[1]) / 2 - b), + height: Math.max( + 0, + Math.floor(this.plotSizeY - Math.max(g, f[2]) / 2 - c) + ), + }; + a || + n(this.axes, function (a) { + a.setAxisSize(); + a.setAxisTranslation(); + }); + }, + resetMargins: function () { + var a = this, + b = a.options.chart; + n(["margin", "spacing"], function (c) { + var g = b[c], + k = F(g) ? g : [g, g, g, g]; + n(["Top", "Right", "Bottom", "Left"], function (g, e) { + a[c][e] = w(b[c + g], k[e]); + }); + }); + n(x, function (b, c) { + a[b] = w(a.margin[c], a.spacing[c]); + }); + a.axisOffset = [0, 0, 0, 0]; + a.clipOffset = [0, 0, 0, 0]; + }, + drawChartBox: function () { + var a = this.options.chart, + b = this.renderer, + c = this.chartWidth, + g = this.chartHeight, + k = this.chartBackground, + e = this.plotBackground, + d = this.plotBorder, + f, + m = this.plotBGImage, + h = a.backgroundColor, + w = a.plotBackgroundColor, + n = a.plotBackgroundImage, + r, + l = this.plotLeft, + q = this.plotTop, + t = this.plotWidth, + p = this.plotHeight, + x = this.plotBox, + K = this.clipRect, + u = this.clipBox, + A = "animate"; + k || + ((this.chartBackground = k = + b.rect().addClass("highcharts-background").add()), + (A = "attr")); + f = a.borderWidth || 0; + r = f + (a.shadow ? 8 : 0); + h = { fill: h || "none" }; + if (f || k["stroke-width"]) + (h.stroke = a.borderColor), (h["stroke-width"] = f); + k.attr(h).shadow(a.shadow); + k[A]({ + x: r / 2, + y: r / 2, + width: c - r - (f % 2), + height: g - r - (f % 2), + r: a.borderRadius, + }); + A = "animate"; + e || + ((A = "attr"), + (this.plotBackground = e = + b.rect().addClass("highcharts-plot-background").add())); + e[A](x); + e.attr({ fill: w || "none" }).shadow(a.plotShadow); + n && + (m + ? m.animate(x) + : (this.plotBGImage = b.image(n, l, q, t, p).add())); + K + ? K.animate({ width: u.width, height: u.height }) + : (this.clipRect = b.clipRect(u)); + A = "animate"; + d || + ((A = "attr"), + (this.plotBorder = d = + b + .rect() + .addClass("highcharts-plot-border") + .attr({ zIndex: 1 }) + .add())); + d.attr({ + stroke: a.plotBorderColor, + "stroke-width": a.plotBorderWidth || 0, + fill: "none", + }); + d[A](d.crisp({ x: l, y: q, width: t, height: p }, -d.strokeWidth())); + this.isDirtyBox = !1; + }, + propFromSeries: function () { + var a = this, + b = a.options.chart, + c, + g = a.options.series, + k, + e; + n(["inverted", "angular", "polar"], function (d) { + c = N[b.type || b.defaultSeriesType]; + e = b[d] || (c && c.prototype[d]); + for (k = g && g.length; !e && k--; ) + (c = N[g[k].type]) && c.prototype[d] && (e = !0); + a[d] = e; + }); + }, + linkSeries: function () { + var a = this, + b = a.series; + n(b, function (a) { + a.linkedSeries.length = 0; + }); + n(b, function (b) { + var c = b.options.linkedTo; + e(c) && + (c = ":previous" === c ? a.series[b.index - 1] : a.get(c)) && + c.linkedParent !== b && + (c.linkedSeries.push(b), + (b.linkedParent = c), + (b.visible = w(b.options.visible, c.options.visible, b.visible))); + }); + }, + renderSeries: function () { + n(this.series, function (a) { + a.translate(); + a.render(); + }); + }, + renderLabels: function () { + var a = this, + b = a.options.labels; + b.items && + n(b.items, function (c) { + var g = y(b.style, c.style), + k = K(g.left) + a.plotLeft, + e = K(g.top) + a.plotTop + 12; + delete g.left; + delete g.top; + a.renderer.text(c.html, k, e).attr({ zIndex: 2 }).css(g).add(); + }); + }, + render: function () { + var a = this.axes, + b = this.renderer, + c = this.options, + g, + k, + e; + this.setTitle(); + this.legend = new r(this, c.legend); + this.getStacks && this.getStacks(); + this.getMargins(!0); + this.setChartSize(); + c = this.plotWidth; + g = this.plotHeight -= 21; + n(a, function (a) { + a.setScale(); + }); + this.getAxisMargins(); + k = 1.1 < c / this.plotWidth; + e = 1.05 < g / this.plotHeight; + if (k || e) + n(a, function (a) { + ((a.horiz && k) || (!a.horiz && e)) && a.setTickInterval(!0); + }), + this.getMargins(); + this.drawChartBox(); + this.hasCartesianSeries && + n(a, function (a) { + a.visible && a.render(); + }); + this.seriesGroup || + (this.seriesGroup = b.g("series-group").attr({ zIndex: 3 }).add()); + this.renderSeries(); + this.renderLabels(); + this.addCredits(); + this.setResponsive && this.setResponsive(); + this.hasRendered = !0; + }, + addCredits: function (a) { + var b = this; + a = A(!0, this.options.credits, a); + a.enabled && + !this.credits && + ((this.credits = this.renderer + .text(a.text + (this.mapCredits || ""), 0, 0) + .addClass("highcharts-credits") + .on("click", function () { + a.href && (M.location.href = a.href); + }) + .attr({ align: a.position.align, zIndex: 8 }) + .css(a.style) + .add() + .align(a.position)), + (this.credits.update = function (a) { + b.credits = b.credits.destroy(); + b.addCredits(a); + })); + }, + destroy: function () { + var b = this, + c = b.axes, + g = b.series, + k = b.container, + e, + d = k && k.parentNode; + m(b, "destroy"); + u[b.index] = void 0; + a.chartCount--; + b.renderTo.removeAttribute("data-highcharts-chart"); + J(b); + for (e = c.length; e--; ) c[e] = c[e].destroy(); + this.scroller && this.scroller.destroy && this.scroller.destroy(); + for (e = g.length; e--; ) g[e] = g[e].destroy(); + n( + "title subtitle chartBackground plotBackground plotBGImage plotBorder seriesGroup clipRect credits pointer rangeSelector legend resetZoomButton tooltip renderer".split( + " " + ), + function (a) { + var c = b[a]; + c && c.destroy && (b[a] = c.destroy()); + } + ); + k && ((k.innerHTML = ""), J(k), d && l(k)); + for (e in b) delete b[e]; + }, + isReadyToRender: function () { + var a = this; + return B || M != M.top || "complete" === h.readyState + ? !0 + : (h.attachEvent("onreadystatechange", function () { + h.detachEvent("onreadystatechange", a.firstRender); + "complete" === h.readyState && a.firstRender(); + }), + !1); + }, + firstRender: function () { + var a = this, + b = a.options; + if (a.isReadyToRender()) { + a.getContainer(); + m(a, "init"); + a.resetMargins(); + a.setChartSize(); + a.propFromSeries(); + a.getAxes(); + n(b.series || [], function (b) { + a.initSeries(b); + }); + a.linkSeries(); + m(a, "beforeRender"); + k && (a.pointer = new k(a, b)); + a.render(); + a.renderer.draw(); + if (!a.renderer.imgCount && a.onload) a.onload(); + a.cloneRenderTo(!0); + } + }, + onload: function () { + n( + [this.callback].concat(this.callbacks), + function (a) { + a && void 0 !== this.index && a.apply(this, [this]); + }, + this + ); + m(this, "load"); + c(this.index) && !1 !== this.options.chart.reflow && this.initReflow(); + this.onload = null; + }, + }; + })(L); + (function (a) { + var D, + C = a.each, + G = a.extend, + I = a.erase, + h = a.fireEvent, + f = a.format, + p = a.isArray, + v = a.isNumber, + l = a.pick, + u = a.removeEvent; + D = a.Point = function () {}; + D.prototype = { + init: function (a, c, f) { + this.series = a; + this.color = a.color; + this.applyOptions(c, f); + a.options.colorByPoint + ? ((c = a.options.colors || a.chart.options.colors), + (this.color = this.color || c[a.colorCounter]), + (c = c.length), + (f = a.colorCounter), + a.colorCounter++, + a.colorCounter === c && (a.colorCounter = 0)) + : (f = a.colorIndex); + this.colorIndex = l(this.colorIndex, f); + a.chart.pointCount++; + return this; + }, + applyOptions: function (a, c) { + var d = this.series, + f = d.options.pointValKey || d.pointValKey; + a = D.prototype.optionsToObject.call(this, a); + G(this, a); + this.options = this.options ? G(this.options, a) : a; + a.group && delete this.group; + f && (this.y = this[f]); + this.isNull = l( + this.isValid && !this.isValid(), + null === this.x || !v(this.y, !0) + ); + this.selected && (this.state = "select"); + "name" in this && + void 0 === c && + d.xAxis && + d.xAxis.hasNames && + (this.x = d.xAxis.nameToX(this)); + void 0 === this.x && + d && + (this.x = void 0 === c ? d.autoIncrement(this) : c); + return this; + }, + optionsToObject: function (a) { + var c = {}, + d = this.series, + f = d.options.keys, + h = f || d.pointArrayMap || ["y"], + m = h.length, + b = 0, + l = 0; + if (v(a) || null === a) c[h[0]] = a; + else if (p(a)) + for ( + !f && + a.length > m && + ((d = typeof a[0]), + "string" === d ? (c.name = a[0]) : "number" === d && (c.x = a[0]), + b++); + l < m; + + ) + (f && void 0 === a[b]) || (c[h[l]] = a[b]), b++, l++; + else + "object" === typeof a && + ((c = a), + a.dataLabels && (d._hasPointLabels = !0), + a.marker && (d._hasPointMarkers = !0)); + return c; + }, + getClassName: function () { + return ( + "highcharts-point" + + (this.selected ? " highcharts-point-select" : "") + + (this.negative ? " highcharts-negative" : "") + + (this.isNull ? " highcharts-null-point" : "") + + (void 0 !== this.colorIndex + ? " highcharts-color-" + this.colorIndex + : "") + + (this.options.className ? " " + this.options.className : "") + + (this.zone && this.zone.className ? " " + this.zone.className : "") + ); + }, + getZone: function () { + var a = this.series, + c = a.zones, + a = a.zoneAxis || "y", + f = 0, + h; + for (h = c[f]; this[a] >= h.value; ) h = c[++f]; + h && h.color && !this.options.color && (this.color = h.color); + return h; + }, + destroy: function () { + var a = this.series.chart, + c = a.hoverPoints, + f; + a.pointCount--; + c && (this.setState(), I(c, this), c.length || (a.hoverPoints = null)); + if (this === a.hoverPoint) this.onMouseOut(); + if (this.graphic || this.dataLabel) u(this), this.destroyElements(); + this.legendItem && a.legend.destroyItem(this); + for (f in this) this[f] = null; + }, + destroyElements: function () { + for ( + var a = [ + "graphic", + "dataLabel", + "dataLabelUpper", + "connector", + "shadowGroup", + ], + c, + f = 6; + f--; + + ) + (c = a[f]), this[c] && (this[c] = this[c].destroy()); + }, + getLabelConfig: function () { + return { + x: this.category, + y: this.y, + color: this.color, + key: this.name || this.category, + series: this.series, + point: this, + percentage: this.percentage, + total: this.total || this.stackTotal, + }; + }, + tooltipFormatter: function (a) { + var c = this.series, + d = c.tooltipOptions, + h = l(d.valueDecimals, ""), + t = d.valuePrefix || "", + m = d.valueSuffix || ""; + C(c.pointArrayMap || ["y"], function (b) { + b = "{point." + b; + if (t || m) a = a.replace(b + "}", t + b + "}" + m); + a = a.replace(b + "}", b + ":,." + h + "f}"); + }); + return f(a, { point: this, series: this.series }); + }, + firePointEvent: function (a, c, f) { + var d = this, + n = this.series.options; + (n.point.events[a] || + (d.options && d.options.events && d.options.events[a])) && + this.importEvents(); + "click" === a && + n.allowPointSelect && + (f = function (a) { + d.select && d.select(null, a.ctrlKey || a.metaKey || a.shiftKey); + }); + h(this, a, c, f); + }, + visible: !0, + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.animObject, + G = a.arrayMax, + I = a.arrayMin, + h = a.correctFloat, + f = a.Date, + p = a.defaultOptions, + v = a.defaultPlotOptions, + l = a.defined, + u = a.each, + d = a.erase, + c = a.extend, + n = a.fireEvent, + y = a.grep, + t = a.isArray, + m = a.isNumber, + b = a.isString, + q = a.merge, + z = a.pick, + F = a.removeEvent, + e = a.splat, + r = a.SVGElement, + x = a.syncTimeout, + A = a.win; + a.Series = a.seriesType( + "line", + null, + { + lineWidth: 2, + allowPointSelect: !1, + showCheckbox: !1, + animation: { duration: 1e3 }, + events: {}, + marker: { + lineWidth: 0, + lineColor: "#ffffff", + radius: 4, + states: { + hover: { + animation: { duration: 50 }, + enabled: !0, + radiusPlus: 2, + lineWidthPlus: 1, + }, + select: { + fillColor: "#cccccc", + lineColor: "#000000", + lineWidth: 2, + }, + }, + }, + point: { events: {} }, + dataLabels: { + align: "center", + formatter: function () { + return null === this.y ? "" : a.numberFormat(this.y, -1); + }, + style: { + fontSize: "11px", + fontWeight: "bold", + color: "contrast", + textOutline: "1px contrast", + }, + verticalAlign: "bottom", + x: 0, + y: 0, + padding: 5, + }, + cropThreshold: 300, + pointRange: 0, + softThreshold: !0, + states: { + hover: { + lineWidthPlus: 1, + marker: {}, + halo: { size: 10, opacity: 0.25 }, + }, + select: { marker: {} }, + }, + stickyTracking: !0, + turboThreshold: 1e3, + }, + { + isCartesian: !0, + pointClass: a.Point, + sorted: !0, + requireSorting: !0, + directTouch: !1, + axisTypes: ["xAxis", "yAxis"], + colorCounter: 0, + parallelArrays: ["x", "y"], + coll: "series", + init: function (a, b) { + var k = this, + e, + d, + g = a.series, + f; + k.chart = a; + k.options = b = k.setOptions(b); + k.linkedSeries = []; + k.bindAxes(); + c(k, { + name: b.name, + state: "", + visible: !1 !== b.visible, + selected: !0 === b.selected, + }); + d = b.events; + for (e in d) D(k, e, d[e]); + if ( + (d && d.click) || + (b.point && b.point.events && b.point.events.click) || + b.allowPointSelect + ) + a.runTrackerClick = !0; + k.getColor(); + k.getSymbol(); + u(k.parallelArrays, function (a) { + k[a + "Data"] = []; + }); + k.setData(b.data, !1); + k.isCartesian && (a.hasCartesianSeries = !0); + g.length && (f = g[g.length - 1]); + k._i = z(f && f._i, -1) + 1; + for (a = this.insert(g); a < g.length; a++) + (g[a].index = a), + (g[a].name = g[a].name || "Series " + (g[a].index + 1)); + }, + insert: function (a) { + var b = this.options.index, + c; + if (m(b)) { + for (c = a.length; c--; ) + if (b >= z(a[c].options.index, a[c]._i)) { + a.splice(c + 1, 0, this); + break; + } + -1 === c && a.unshift(this); + c += 1; + } else a.push(this); + return z(c, a.length - 1); + }, + bindAxes: function () { + var b = this, + c = b.options, + e = b.chart, + d; + u(b.axisTypes || [], function (k) { + u(e[k], function (a) { + d = a.options; + if ( + c[k] === d.index || + (void 0 !== c[k] && c[k] === d.id) || + (void 0 === c[k] && 0 === d.index) + ) + b.insert(a.series), (b[k] = a), (a.isDirty = !0); + }); + b[k] || b.optionalAxis === k || a.error(18, !0); + }); + }, + updateParallelArrays: function (a, b) { + var c = a.series, + k = arguments, + e = m(b) + ? function (g) { + var k = "y" === g && c.toYData ? c.toYData(a) : a[g]; + c[g + "Data"][b] = k; + } + : function (a) { + Array.prototype[b].apply( + c[a + "Data"], + Array.prototype.slice.call(k, 2) + ); + }; + u(c.parallelArrays, e); + }, + autoIncrement: function () { + var a = this.options, + b = this.xIncrement, + c, + e = a.pointIntervalUnit, + b = z(b, a.pointStart, 0); + this.pointInterval = c = z(this.pointInterval, a.pointInterval, 1); + e && + ((a = new f(b)), + "day" === e + ? (a = +a[f.hcSetDate](a[f.hcGetDate]() + c)) + : "month" === e + ? (a = +a[f.hcSetMonth](a[f.hcGetMonth]() + c)) + : "year" === e && + (a = +a[f.hcSetFullYear](a[f.hcGetFullYear]() + c)), + (c = a - b)); + this.xIncrement = b + c; + return b; + }, + setOptions: function (a) { + var b = this.chart, + c = b.options.plotOptions, + b = b.userOptions || {}, + k = b.plotOptions || {}, + e = c[this.type]; + this.userOptions = a; + c = q(e, c.series, a); + this.tooltipOptions = q( + p.tooltip, + p.plotOptions[this.type].tooltip, + b.tooltip, + k.series && k.series.tooltip, + k[this.type] && k[this.type].tooltip, + a.tooltip + ); + null === e.marker && delete c.marker; + this.zoneAxis = c.zoneAxis; + a = this.zones = (c.zones || []).slice(); + (!c.negativeColor && !c.negativeFillColor) || + c.zones || + a.push({ + value: c[this.zoneAxis + "Threshold"] || c.threshold || 0, + className: "highcharts-negative", + color: c.negativeColor, + fillColor: c.negativeFillColor, + }); + a.length && + l(a[a.length - 1].value) && + a.push({ color: this.color, fillColor: this.fillColor }); + return c; + }, + getCyclic: function (a, b, c) { + var k, + e = this.userOptions, + g = a + "Index", + d = a + "Counter", + f = c + ? c.length + : z( + this.chart.options.chart[a + "Count"], + this.chart[a + "Count"] + ); + b || + ((k = z(e[g], e["_" + g])), + l(k) || + ((e["_" + g] = k = this.chart[d] % f), (this.chart[d] += 1)), + c && (b = c[k])); + void 0 !== k && (this[g] = k); + this[a] = b; + }, + getColor: function () { + this.options.colorByPoint + ? (this.options.color = null) + : this.getCyclic( + "color", + this.options.color || v[this.type].color, + this.chart.options.colors + ); + }, + getSymbol: function () { + this.getCyclic( + "symbol", + this.options.marker.symbol, + this.chart.options.symbols + ); + }, + drawLegendSymbol: a.LegendSymbolMixin.drawLineMarker, + setData: function (c, e, d, f) { + var k = this, + g = k.points, + h = (g && g.length) || 0, + n, + r = k.options, + w = k.chart, + l = null, + q = k.xAxis, + p = r.turboThreshold, + x = this.xData, + A = this.yData, + F = (n = k.pointArrayMap) && n.length; + c = c || []; + n = c.length; + e = z(e, !0); + if ( + !1 !== f && + n && + h === n && + !k.cropped && + !k.hasGroupedData && + k.visible + ) + u(c, function (a, b) { + g[b].update && a !== r.data[b] && g[b].update(a, !1, null, !1); + }); + else { + k.xIncrement = null; + k.colorCounter = 0; + u(this.parallelArrays, function (a) { + k[a + "Data"].length = 0; + }); + if (p && n > p) { + for (d = 0; null === l && d < n; ) (l = c[d]), d++; + if (m(l)) + for (d = 0; d < n; d++) + (x[d] = this.autoIncrement()), (A[d] = c[d]); + else if (t(l)) + if (F) + for (d = 0; d < n; d++) + (l = c[d]), (x[d] = l[0]), (A[d] = l.slice(1, F + 1)); + else + for (d = 0; d < n; d++) + (l = c[d]), (x[d] = l[0]), (A[d] = l[1]); + else a.error(12); + } else + for (d = 0; d < n; d++) + void 0 !== c[d] && + ((l = { series: k }), + k.pointClass.prototype.applyOptions.apply(l, [c[d]]), + k.updateParallelArrays(l, d)); + b(A[0]) && a.error(14, !0); + k.data = []; + k.options.data = k.userOptions.data = c; + for (d = h; d--; ) g[d] && g[d].destroy && g[d].destroy(); + q && (q.minRange = q.userMinRange); + k.isDirty = w.isDirtyBox = !0; + k.isDirtyData = !!g; + d = !1; + } + "point" === r.legendType && + (this.processData(), this.generatePoints()); + e && w.redraw(d); + }, + processData: function (b) { + var c = this.xData, + k = this.yData, + e = c.length, + d; + d = 0; + var g, + f, + m = this.xAxis, + h, + n = this.options; + h = n.cropThreshold; + var l = this.getExtremesFromAll || n.getExtremesFromAll, + r = this.isCartesian, + n = m && m.val2lin, + q = m && m.isLog, + t, + p; + if (r && !this.isDirty && !m.isDirty && !this.yAxis.isDirty && !b) + return !1; + m && ((b = m.getExtremes()), (t = b.min), (p = b.max)); + if (r && this.sorted && !l && (!h || e > h || this.forceCrop)) + if (c[e - 1] < t || c[0] > p) (c = []), (k = []); + else if (c[0] < t || c[e - 1] > p) + (d = this.cropData(this.xData, this.yData, t, p)), + (c = d.xData), + (k = d.yData), + (d = d.start), + (g = !0); + for (h = c.length || 1; --h; ) + (e = q ? n(c[h]) - n(c[h - 1]) : c[h] - c[h - 1]), + 0 < e && (void 0 === f || e < f) + ? (f = e) + : 0 > e && this.requireSorting && a.error(15); + this.cropped = g; + this.cropStart = d; + this.processedXData = c; + this.processedYData = k; + this.closestPointRange = f; + }, + cropData: function (a, b, c, e) { + var k = a.length, + g = 0, + d = k, + f = z(this.cropShoulder, 1), + h; + for (h = 0; h < k; h++) + if (a[h] >= c) { + g = Math.max(0, h - f); + break; + } + for (c = h; c < k; c++) + if (a[c] > e) { + d = c + f; + break; + } + return { + xData: a.slice(g, d), + yData: b.slice(g, d), + start: g, + end: d, + }; + }, + generatePoints: function () { + var a = this.options.data, + b = this.data, + c, + d = this.processedXData, + f = this.processedYData, + g = this.pointClass, + h = d.length, + m = this.cropStart || 0, + n, + r = this.hasGroupedData, + l, + q = [], + t; + b || r || ((b = []), (b.length = a.length), (b = this.data = b)); + for (t = 0; t < h; t++) + (n = m + t), + r + ? ((l = new g().init(this, [d[t]].concat(e(f[t])))), + (l.dataGroup = this.groupMap[t])) + : (l = b[n]) || + void 0 === a[n] || + (b[n] = l = new g().init(this, a[n], d[t])), + (l.index = n), + (q[t] = l); + if (b && (h !== (c = b.length) || r)) + for (t = 0; t < c; t++) + t !== m || r || (t += h), + b[t] && (b[t].destroyElements(), (b[t].plotX = void 0)); + this.data = b; + this.points = q; + }, + getExtremes: function (a) { + var b = this.yAxis, + c = this.processedXData, + e, + k = [], + g = 0; + e = this.xAxis.getExtremes(); + var d = e.min, + f = e.max, + h, + n, + l, + r; + a = a || this.stackedYData || this.processedYData || []; + e = a.length; + for (r = 0; r < e; r++) + if ( + ((n = c[r]), + (l = a[r]), + (h = (m(l, !0) || t(l)) && (!b.isLog || l.length || 0 < l)), + (n = + this.getExtremesFromAll || + this.options.getExtremesFromAll || + this.cropped || + ((c[r + 1] || n) >= d && (c[r - 1] || n) <= f)), + h && n) + ) + if ((h = l.length)) + for (; h--; ) null !== l[h] && (k[g++] = l[h]); + else k[g++] = l; + this.dataMin = I(k); + this.dataMax = G(k); + }, + translate: function () { + this.processedXData || this.processData(); + this.generatePoints(); + var a = this.options, + b = a.stacking, + c = this.xAxis, + e = c.categories, + d = this.yAxis, + g = this.points, + f = g.length, + n = !!this.modifyValue, + r = a.pointPlacement, + t = "between" === r || m(r), + q = a.threshold, + p = a.startFromThreshold ? q : 0, + x, + u, + A, + F, + v = Number.MAX_VALUE; + "between" === r && (r = 0.5); + m(r) && (r *= z(a.pointRange || c.pointRange)); + for (a = 0; a < f; a++) { + var y = g[a], + C = y.x, + D = y.y; + u = y.low; + var G = + b && + d.stacks[ + (this.negStacks && D < (p ? 0 : q) ? "-" : "") + this.stackKey + ], + I; + d.isLog && null !== D && 0 >= D && (y.isNull = !0); + y.plotX = x = h( + Math.min( + Math.max( + -1e5, + c.translate(C, 0, 0, 0, 1, r, "flags" === this.type) + ), + 1e5 + ) + ); + b && + this.visible && + !y.isNull && + G && + G[C] && + ((F = this.getStackIndicator(F, C, this.index)), + (I = G[C]), + (D = I.points[F.key]), + (u = D[0]), + (D = D[1]), + u === p && F.key === G[C].base && (u = z(q, d.min)), + d.isLog && 0 >= u && (u = null), + (y.total = y.stackTotal = I.total), + (y.percentage = I.total && (y.y / I.total) * 100), + (y.stackY = D), + I.setOffset(this.pointXOffset || 0, this.barW || 0)); + y.yBottom = l(u) ? d.translate(u, 0, 1, 0, 1) : null; + n && (D = this.modifyValue(D, y)); + y.plotY = u = + "number" === typeof D && Infinity !== D + ? Math.min(Math.max(-1e5, d.translate(D, 0, 1, 0, 1)), 1e5) + : void 0; + y.isInside = + void 0 !== u && 0 <= u && u <= d.len && 0 <= x && x <= c.len; + y.clientX = t ? h(c.translate(C, 0, 0, 0, 1, r)) : x; + y.negative = y.y < (q || 0); + y.category = e && void 0 !== e[y.x] ? e[y.x] : y.x; + y.isNull || + (void 0 !== A && (v = Math.min(v, Math.abs(x - A))), (A = x)); + y.zone = this.zones.length && y.getZone(); + } + this.closestPointRangePx = v; + }, + getValidPoints: function (a, b) { + var c = this.chart; + return y(a || this.points || [], function (a) { + return b && !c.isInsidePlot(a.plotX, a.plotY, c.inverted) + ? !1 + : !a.isNull; + }); + }, + setClip: function (a) { + var b = this.chart, + c = this.options, + e = b.renderer, + k = b.inverted, + g = this.clipBox, + d = g || b.clipBox, + f = + this.sharedClipKey || + [ + "_sharedClip", + a && a.duration, + a && a.easing, + d.height, + c.xAxis, + c.yAxis, + ].join(), + h = b[f], + m = b[f + "m"]; + h || + (a && + ((d.width = 0), + (b[f + "m"] = m = + e.clipRect( + -99, + k ? -b.plotLeft : -b.plotTop, + 99, + k ? b.chartWidth : b.chartHeight + ))), + (b[f] = h = e.clipRect(d)), + (h.count = { length: 0 })); + a && + !h.count[this.index] && + ((h.count[this.index] = !0), (h.count.length += 1)); + !1 !== c.clip && + (this.group.clip(a || g ? h : b.clipRect), + this.markerGroup.clip(m), + (this.sharedClipKey = f)); + a || + (h.count[this.index] && + (delete h.count[this.index], --h.count.length), + 0 === h.count.length && + f && + b[f] && + (g || (b[f] = b[f].destroy()), + b[f + "m"] && (b[f + "m"] = b[f + "m"].destroy()))); + }, + animate: function (a) { + var b = this.chart, + c = C(this.options.animation), + e; + a + ? this.setClip(c) + : ((e = this.sharedClipKey), + (a = b[e]) && a.animate({ width: b.plotSizeX }, c), + b[e + "m"] && b[e + "m"].animate({ width: b.plotSizeX + 99 }, c), + (this.animate = null)); + }, + afterAnimate: function () { + this.setClip(); + n(this, "afterAnimate"); + }, + drawPoints: function () { + var a = this.points, + b = this.chart, + c, + e, + d, + g, + f = this.options.marker, + h, + n, + r, + l, + t = this.markerGroup, + q = z( + f.enabled, + this.xAxis.isRadial ? !0 : null, + this.closestPointRangePx > 2 * f.radius + ); + if (!1 !== f.enabled || this._hasPointMarkers) + for (e = a.length; e--; ) + (d = a[e]), + (c = d.plotY), + (g = d.graphic), + (h = d.marker || {}), + (n = !!d.marker), + (r = (q && void 0 === h.enabled) || h.enabled), + (l = d.isInside), + r && m(c) && null !== d.y + ? ((c = z(h.symbol, this.symbol)), + (d.hasImage = 0 === c.indexOf("url")), + (r = this.markerAttribs(d, d.selected && "select")), + g + ? g[l ? "show" : "hide"](!0).animate(r) + : l && + (0 < r.width || d.hasImage) && + (d.graphic = g = + b.renderer + .symbol(c, r.x, r.y, r.width, r.height, n ? h : f) + .add(t)), + g && g.attr(this.pointAttribs(d, d.selected && "select")), + g && g.addClass(d.getClassName(), !0)) + : g && (d.graphic = g.destroy()); + }, + markerAttribs: function (a, b) { + var c = this.options.marker, + e = a && a.options, + k = (e && e.marker) || {}, + e = z(k.radius, c.radius); + b && + ((c = c.states[b]), + (b = k.states && k.states[b]), + (e = z( + b && b.radius, + c && c.radius, + e + ((c && c.radiusPlus) || 0) + ))); + a.hasImage && (e = 0); + a = { x: Math.floor(a.plotX) - e, y: a.plotY - e }; + e && (a.width = a.height = 2 * e); + return a; + }, + pointAttribs: function (a, b) { + var c = this.options.marker, + e = a && a.options, + k = (e && e.marker) || {}, + g = this.color, + d = e && e.color, + f = a && a.color, + e = z(k.lineWidth, c.lineWidth); + a = a && a.zone && a.zone.color; + g = d || a || f || g; + a = k.fillColor || c.fillColor || g; + g = k.lineColor || c.lineColor || g; + b && + ((c = c.states[b]), + (b = (k.states && k.states[b]) || {}), + (e = z( + b.lineWidth, + c.lineWidth, + e + z(b.lineWidthPlus, c.lineWidthPlus, 0) + )), + (a = b.fillColor || c.fillColor || a), + (g = b.lineColor || c.lineColor || g)); + return { stroke: g, "stroke-width": e, fill: a }; + }, + destroy: function () { + var a = this, + b = a.chart, + c = /AppleWebKit\/533/.test(A.navigator.userAgent), + e, + f = a.data || [], + g, + h, + m; + n(a, "destroy"); + F(a); + u(a.axisTypes || [], function (b) { + (m = a[b]) && + m.series && + (d(m.series, a), (m.isDirty = m.forceRedraw = !0)); + }); + a.legendItem && a.chart.legend.destroyItem(a); + for (e = f.length; e--; ) (g = f[e]) && g.destroy && g.destroy(); + a.points = null; + clearTimeout(a.animationTimeout); + for (h in a) + a[h] instanceof r && + !a[h].survive && + ((e = c && "group" === h ? "hide" : "destroy"), a[h][e]()); + b.hoverSeries === a && (b.hoverSeries = null); + d(b.series, a); + for (h in a) delete a[h]; + }, + getGraphPath: function (a, b, c) { + var e = this, + k = e.options, + g = k.step, + d, + f = [], + h = [], + m; + a = a || e.points; + (d = a.reversed) && a.reverse(); + (g = { right: 1, center: 2 }[g] || (g && 3)) && d && (g = 4 - g); + !k.connectNulls || b || c || (a = this.getValidPoints(a)); + u(a, function (d, n) { + var r = d.plotX, + t = d.plotY, + q = a[n - 1]; + (d.leftCliff || (q && q.rightCliff)) && !c && (m = !0); + d.isNull && !l(b) && 0 < n + ? (m = !k.connectNulls) + : d.isNull && !b + ? (m = !0) + : (0 === n || m + ? (n = ["M", d.plotX, d.plotY]) + : e.getPointSpline + ? (n = e.getPointSpline(a, d, n)) + : g + ? ((n = + 1 === g + ? ["L", q.plotX, t] + : 2 === g + ? [ + "L", + (q.plotX + r) / 2, + q.plotY, + "L", + (q.plotX + r) / 2, + t, + ] + : ["L", r, q.plotY]), + n.push("L", r, t)) + : (n = ["L", r, t]), + h.push(d.x), + g && h.push(d.x), + f.push.apply(f, n), + (m = !1)); + }); + f.xMap = h; + return (e.graphPath = f); + }, + drawGraph: function () { + var a = this, + b = this.options, + c = (this.gappedPath || this.getGraphPath).call(this), + e = [ + [ + "graph", + "highcharts-graph", + b.lineColor || this.color, + b.dashStyle, + ], + ]; + u(this.zones, function (c, g) { + e.push([ + "zone-graph-" + g, + "highcharts-graph highcharts-zone-graph-" + + g + + " " + + (c.className || ""), + c.color || a.color, + c.dashStyle || b.dashStyle, + ]); + }); + u(e, function (e, g) { + var k = e[0], + d = a[k]; + d + ? ((d.endX = c.xMap), d.animate({ d: c })) + : c.length && + ((a[k] = a.chart.renderer + .path(c) + .addClass(e[1]) + .attr({ zIndex: 1 }) + .add(a.group)), + (d = { + stroke: e[2], + "stroke-width": b.lineWidth, + fill: (a.fillGraph && a.color) || "none", + }), + e[3] + ? (d.dashstyle = e[3]) + : "square" !== b.linecap && + (d["stroke-linecap"] = d["stroke-linejoin"] = "round"), + (d = a[k].attr(d).shadow(2 > g && b.shadow))); + d && ((d.startX = c.xMap), (d.isArea = c.isArea)); + }); + }, + applyZones: function () { + var a = this, + b = this.chart, + c = b.renderer, + e = this.zones, + d, + g, + f = this.clips || [], + h, + m = this.graph, + n = this.area, + r = Math.max(b.chartWidth, b.chartHeight), + l = this[(this.zoneAxis || "y") + "Axis"], + q, + t, + p = b.inverted, + x, + A, + F, + y, + v = !1; + e.length && + (m || n) && + l && + void 0 !== l.min && + ((t = l.reversed), + (x = l.horiz), + m && m.hide(), + n && n.hide(), + (q = l.getExtremes()), + u(e, function (e, k) { + d = t ? (x ? b.plotWidth : 0) : x ? 0 : l.toPixels(q.min); + d = Math.min(Math.max(z(g, d), 0), r); + g = Math.min( + Math.max(Math.round(l.toPixels(z(e.value, q.max), !0)), 0), + r + ); + v && (d = g = l.toPixels(q.max)); + A = Math.abs(d - g); + F = Math.min(d, g); + y = Math.max(d, g); + l.isXAxis + ? ((h = { x: p ? y : F, y: 0, width: A, height: r }), + x || (h.x = b.plotHeight - h.x)) + : ((h = { x: 0, y: p ? y : F, width: r, height: A }), + x && (h.y = b.plotWidth - h.y)); + p && + c.isVML && + (h = l.isXAxis + ? { x: 0, y: t ? F : y, height: h.width, width: b.chartWidth } + : { + x: h.y - b.plotLeft - b.spacingBox.x, + y: 0, + width: h.height, + height: b.chartHeight, + }); + f[k] + ? f[k].animate(h) + : ((f[k] = c.clipRect(h)), + m && a["zone-graph-" + k].clip(f[k]), + n && a["zone-area-" + k].clip(f[k])); + v = e.value > q.max; + }), + (this.clips = f)); + }, + invertGroups: function (a) { + function b() { + var b = { width: c.yAxis.len, height: c.xAxis.len }; + u(["group", "markerGroup"], function (e) { + c[e] && c[e].attr(b).invert(a); + }); + } + var c = this, + e; + c.xAxis && + ((e = D(c.chart, "resize", b)), + D(c, "destroy", e), + b(a), + (c.invertGroups = b)); + }, + plotGroup: function (a, b, c, e, d) { + var g = this[a], + k = !g; + k && + ((this[a] = g = + this.chart.renderer + .g(b) + .attr({ zIndex: e || 0.1 }) + .add(d)), + g.addClass( + "highcharts-series-" + + this.index + + " highcharts-" + + this.type + + "-series highcharts-color-" + + this.colorIndex + + " " + + (this.options.className || "") + )); + g.attr({ visibility: c })[k ? "attr" : "animate"](this.getPlotBox()); + return g; + }, + getPlotBox: function () { + var a = this.chart, + b = this.xAxis, + c = this.yAxis; + a.inverted && ((b = c), (c = this.xAxis)); + return { + translateX: b ? b.left : a.plotLeft, + translateY: c ? c.top : a.plotTop, + scaleX: 1, + scaleY: 1, + }; + }, + render: function () { + var a = this, + b = a.chart, + c, + e = a.options, + d = !!a.animate && b.renderer.isSVG && C(e.animation).duration, + g = a.visible ? "inherit" : "hidden", + f = e.zIndex, + h = a.hasRendered, + m = b.seriesGroup, + n = b.inverted; + c = a.plotGroup("group", "series", g, f, m); + a.markerGroup = a.plotGroup("markerGroup", "markers", g, f, m); + d && a.animate(!0); + c.inverted = a.isCartesian ? n : !1; + a.drawGraph && (a.drawGraph(), a.applyZones()); + a.drawDataLabels && a.drawDataLabels(); + a.visible && a.drawPoints(); + a.drawTracker && + !1 !== a.options.enableMouseTracking && + a.drawTracker(); + a.invertGroups(n); + !1 === e.clip || a.sharedClipKey || h || c.clip(b.clipRect); + d && a.animate(); + h || + (a.animationTimeout = x(function () { + a.afterAnimate(); + }, d)); + a.isDirty = a.isDirtyData = !1; + a.hasRendered = !0; + }, + redraw: function () { + var a = this.chart, + b = this.isDirty || this.isDirtyData, + c = this.group, + e = this.xAxis, + d = this.yAxis; + c && + (a.inverted && c.attr({ width: a.plotWidth, height: a.plotHeight }), + c.animate({ + translateX: z(e && e.left, a.plotLeft), + translateY: z(d && d.top, a.plotTop), + })); + this.translate(); + this.render(); + b && delete this.kdTree; + }, + kdDimensions: 1, + kdAxisArray: ["clientX", "plotY"], + searchPoint: function (a, b) { + var c = this.xAxis, + e = this.yAxis, + d = this.chart.inverted; + return this.searchKDTree( + { + clientX: d ? c.len - a.chartY + c.pos : a.chartX - c.pos, + plotY: d ? e.len - a.chartX + e.pos : a.chartY - e.pos, + }, + b + ); + }, + buildKDTree: function () { + function a(c, e, g) { + var d, k; + if ((k = c && c.length)) + return ( + (d = b.kdAxisArray[e % g]), + c.sort(function (a, b) { + return a[d] - b[d]; + }), + (k = Math.floor(k / 2)), + { + point: c[k], + left: a(c.slice(0, k), e + 1, g), + right: a(c.slice(k + 1), e + 1, g), + } + ); + } + var b = this, + c = b.kdDimensions; + delete b.kdTree; + x( + function () { + b.kdTree = a(b.getValidPoints(null, !b.directTouch), c, c); + }, + b.options.kdNow ? 0 : 1 + ); + }, + searchKDTree: function (a, b) { + function c(a, b, f, h) { + var m = b.point, + n = e.kdAxisArray[f % h], + r, + q, + t = m; + q = l(a[d]) && l(m[d]) ? Math.pow(a[d] - m[d], 2) : null; + r = l(a[g]) && l(m[g]) ? Math.pow(a[g] - m[g], 2) : null; + r = (q || 0) + (r || 0); + m.dist = l(r) ? Math.sqrt(r) : Number.MAX_VALUE; + m.distX = l(q) ? Math.sqrt(q) : Number.MAX_VALUE; + n = a[n] - m[n]; + r = 0 > n ? "left" : "right"; + q = 0 > n ? "right" : "left"; + b[r] && ((r = c(a, b[r], f + 1, h)), (t = r[k] < t[k] ? r : m)); + b[q] && + Math.sqrt(n * n) < t[k] && + ((a = c(a, b[q], f + 1, h)), (t = a[k] < t[k] ? a : t)); + return t; + } + var e = this, + d = this.kdAxisArray[0], + g = this.kdAxisArray[1], + k = b ? "distX" : "dist"; + this.kdTree || this.buildKDTree(); + if (this.kdTree) + return c(a, this.kdTree, this.kdDimensions, this.kdDimensions); + }, + } + ); + })(L); + (function (a) { + function D(a, d, c, f, h) { + var n = a.chart.inverted; + this.axis = a; + this.isNegative = c; + this.options = d; + this.x = f; + this.total = null; + this.points = {}; + this.stack = h; + this.rightCliff = this.leftCliff = 0; + this.alignOptions = { + align: d.align || (n ? (c ? "left" : "right") : "center"), + verticalAlign: d.verticalAlign || (n ? "middle" : c ? "bottom" : "top"), + y: l(d.y, n ? 4 : c ? 14 : -6), + x: l(d.x, n ? (c ? -6 : 6) : 0), + }; + this.textAlign = d.textAlign || (n ? (c ? "right" : "left") : "center"); + } + var C = a.Axis, + G = a.Chart, + I = a.correctFloat, + h = a.defined, + f = a.destroyObjectProperties, + p = a.each, + v = a.format, + l = a.pick; + a = a.Series; + D.prototype = { + destroy: function () { + f(this, this.axis); + }, + render: function (a) { + var d = this.options, + c = d.format, + c = c ? v(c, this) : d.formatter.call(this); + this.label + ? this.label.attr({ text: c, visibility: "hidden" }) + : (this.label = this.axis.chart.renderer + .text(c, null, null, d.useHTML) + .css(d.style) + .attr({ + align: this.textAlign, + rotation: d.rotation, + visibility: "hidden", + }) + .add(a)); + }, + setOffset: function (a, d) { + var c = this.axis, + f = c.chart, + h = f.inverted, + l = c.reversed, + l = (this.isNegative && !l) || (!this.isNegative && l), + m = c.translate(c.usePercentage ? 100 : this.total, 0, 0, 0, 1), + c = c.translate(0), + c = Math.abs(m - c); + a = f.xAxis[0].translate(this.x) + a; + var b = f.plotHeight, + h = { + x: h ? (l ? m : m - c) : a, + y: h ? b - a - d : l ? b - m - c : b - m, + width: h ? c : d, + height: h ? d : c, + }; + if ((d = this.label)) + d.align(this.alignOptions, null, h), + (h = d.alignAttr), + d[ + !1 === this.options.crop || f.isInsidePlot(h.x, h.y) + ? "show" + : "hide" + ](!0); + }, + }; + G.prototype.getStacks = function () { + var a = this; + p(a.yAxis, function (a) { + a.stacks && a.hasVisibleSeries && (a.oldStacks = a.stacks); + }); + p(a.series, function (d) { + !d.options.stacking || + (!0 !== d.visible && !1 !== a.options.chart.ignoreHiddenSeries) || + (d.stackKey = d.type + l(d.options.stack, "")); + }); + }; + C.prototype.buildStacks = function () { + var a = this.series, + d, + c = l(this.options.reversedStacks, !0), + f = a.length, + h; + if (!this.isXAxis) { + this.usePercentage = !1; + for (h = f; h--; ) a[c ? h : f - h - 1].setStackedPoints(); + for (h = f; h--; ) + (d = a[c ? h : f - h - 1]), d.setStackCliffs && d.setStackCliffs(); + if (this.usePercentage) for (h = 0; h < f; h++) a[h].setPercentStacks(); + } + }; + C.prototype.renderStackTotals = function () { + var a = this.chart, + d = a.renderer, + c = this.stacks, + f, + h, + l = this.stackTotalGroup; + l || + (this.stackTotalGroup = l = + d.g("stack-labels").attr({ visibility: "visible", zIndex: 6 }).add()); + l.translate(a.plotLeft, a.plotTop); + for (f in c) for (h in ((a = c[f]), a)) a[h].render(l); + }; + C.prototype.resetStacks = function () { + var a = this.stacks, + d, + c; + if (!this.isXAxis) + for (d in a) + for (c in a[d]) + a[d][c].touched < this.stacksTouched + ? (a[d][c].destroy(), delete a[d][c]) + : ((a[d][c].total = null), (a[d][c].cum = null)); + }; + C.prototype.cleanStacks = function () { + var a, d, c; + if (!this.isXAxis) + for (d in (this.oldStacks && (a = this.stacks = this.oldStacks), a)) + for (c in a[d]) a[d][c].cum = a[d][c].total; + }; + a.prototype.setStackedPoints = function () { + if ( + this.options.stacking && + (!0 === this.visible || + !1 === this.chart.options.chart.ignoreHiddenSeries) + ) { + var a = this.processedXData, + d = this.processedYData, + c = [], + f = d.length, + p = this.options, + t = p.threshold, + m = p.startFromThreshold ? t : 0, + b = p.stack, + p = p.stacking, + q = this.stackKey, + v = "-" + q, + F = this.negStacks, + e = this.yAxis, + r = e.stacks, + x = e.oldStacks, + A, + k, + w, + C, + J, + G, + g; + e.stacksTouched += 1; + for (J = 0; J < f; J++) + (G = a[J]), + (g = d[J]), + (A = this.getStackIndicator(A, G, this.index)), + (C = A.key), + (w = (k = F && g < (m ? 0 : t)) ? v : q), + r[w] || (r[w] = {}), + r[w][G] || + (x[w] && x[w][G] + ? ((r[w][G] = x[w][G]), (r[w][G].total = null)) + : (r[w][G] = new D(e, e.options.stackLabels, k, G, b))), + (w = r[w][G]), + null !== g && + ((w.points[C] = w.points[this.index] = [l(w.cum, m)]), + h(w.cum) || (w.base = C), + (w.touched = e.stacksTouched), + 0 < A.index && + !1 === this.singleStacks && + (w.points[C][0] = w.points[this.index + "," + G + ",0"][0])), + "percent" === p + ? ((k = k ? q : v), + F && r[k] && r[k][G] + ? ((k = r[k][G]), + (w.total = k.total = + Math.max(k.total, w.total) + Math.abs(g) || 0)) + : (w.total = I(w.total + (Math.abs(g) || 0)))) + : (w.total = I(w.total + (g || 0))), + (w.cum = l(w.cum, m) + (g || 0)), + null !== g && (w.points[C].push(w.cum), (c[J] = w.cum)); + "percent" === p && (e.usePercentage = !0); + this.stackedYData = c; + e.oldStacks = {}; + } + }; + a.prototype.setPercentStacks = function () { + var a = this, + d = a.stackKey, + c = a.yAxis.stacks, + f = a.processedXData, + h; + p([d, "-" + d], function (d) { + for (var m = f.length, b, n; m--; ) + if ( + ((b = f[m]), + (h = a.getStackIndicator(h, b, a.index, d)), + (b = (n = c[d] && c[d][b]) && n.points[h.key])) + ) + (n = n.total ? 100 / n.total : 0), + (b[0] = I(b[0] * n)), + (b[1] = I(b[1] * n)), + (a.stackedYData[m] = b[1]); + }); + }; + a.prototype.getStackIndicator = function (a, d, c, f) { + !h(a) || a.x !== d || (f && a.key !== f) + ? (a = { x: d, index: 0, key: f }) + : a.index++; + a.key = [c, d, a.index].join(); + return a; + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.animate, + G = a.Axis, + I = a.createElement, + h = a.css, + f = a.defined, + p = a.each, + v = a.erase, + l = a.extend, + u = a.fireEvent, + d = a.inArray, + c = a.isNumber, + n = a.isObject, + y = a.merge, + t = a.pick, + m = a.Point, + b = a.Series, + q = a.seriesTypes, + z = a.setAnimation, + F = a.splat; + l(a.Chart.prototype, { + addSeries: function (a, b, c) { + var e, + d = this; + a && + ((b = t(b, !0)), + u(d, "addSeries", { options: a }, function () { + e = d.initSeries(a); + d.isDirtyLegend = !0; + d.linkSeries(); + b && d.redraw(c); + })); + return e; + }, + addAxis: function (a, b, c, d) { + var e = b ? "xAxis" : "yAxis", + f = this.options; + a = y(a, { index: this[e].length, isX: b }); + new G(this, a); + f[e] = F(f[e] || {}); + f[e].push(a); + t(c, !0) && this.redraw(d); + }, + showLoading: function (a) { + var b = this, + c = b.options, + e = b.loadingDiv, + d = c.loading, + f = function () { + e && + h(e, { + left: b.plotLeft + "px", + top: b.plotTop + "px", + width: b.plotWidth + "px", + height: b.plotHeight + "px", + }); + }; + e || + ((b.loadingDiv = e = + I( + "div", + { className: "highcharts-loading highcharts-loading-hidden" }, + null, + b.container + )), + (b.loadingSpan = I( + "span", + { className: "highcharts-loading-inner" }, + null, + e + )), + D(b, "redraw", f)); + e.className = "highcharts-loading"; + b.loadingSpan.innerHTML = a || c.lang.loading; + h(e, l(d.style, { zIndex: 10 })); + h(b.loadingSpan, d.labelStyle); + b.loadingShown || + (h(e, { opacity: 0, display: "" }), + C( + e, + { opacity: d.style.opacity || 0.5 }, + { duration: d.showDuration || 0 } + )); + b.loadingShown = !0; + f(); + }, + hideLoading: function () { + var a = this.options, + b = this.loadingDiv; + b && + ((b.className = "highcharts-loading highcharts-loading-hidden"), + C( + b, + { opacity: 0 }, + { + duration: a.loading.hideDuration || 100, + complete: function () { + h(b, { display: "none" }); + }, + } + )); + this.loadingShown = !1; + }, + propsRequireDirtyBox: + "backgroundColor borderColor borderWidth margin marginTop marginRight marginBottom marginLeft spacing spacingTop spacingRight spacingBottom spacingLeft borderRadius plotBackgroundColor plotBackgroundImage plotBorderColor plotBorderWidth plotShadow shadow".split( + " " + ), + propsRequireUpdateSeries: + "chart.inverted chart.polar chart.ignoreHiddenSeries chart.type colors plotOptions".split( + " " + ), + update: function (a, b) { + var e, + h = { + credits: "addCredits", + title: "setTitle", + subtitle: "setSubtitle", + }, + k = a.chart, + m, + n; + if (k) { + y(!0, this.options.chart, k); + "className" in k && this.setClassName(k.className); + if ("inverted" in k || "polar" in k) this.propFromSeries(), (m = !0); + for (e in k) + k.hasOwnProperty(e) && + (-1 !== d("chart." + e, this.propsRequireUpdateSeries) && + (n = !0), + -1 !== d(e, this.propsRequireDirtyBox) && (this.isDirtyBox = !0)); + "style" in k && this.renderer.setStyle(k.style); + } + for (e in a) { + if (this[e] && "function" === typeof this[e].update) + this[e].update(a[e], !1); + else if ("function" === typeof this[h[e]]) this[h[e]](a[e]); + "chart" !== e && + -1 !== d(e, this.propsRequireUpdateSeries) && + (n = !0); + } + a.colors && (this.options.colors = a.colors); + a.plotOptions && y(!0, this.options.plotOptions, a.plotOptions); + p( + ["xAxis", "yAxis", "series"], + function (b) { + a[b] && + p( + F(a[b]), + function (a) { + var c = (f(a.id) && this.get(a.id)) || this[b][0]; + c && c.coll === b && c.update(a, !1); + }, + this + ); + }, + this + ); + m && + p(this.axes, function (a) { + a.update({}, !1); + }); + n && + p(this.series, function (a) { + a.update({}, !1); + }); + a.loading && y(!0, this.options.loading, a.loading); + e = k && k.width; + k = k && k.height; + (c(e) && e !== this.chartWidth) || (c(k) && k !== this.chartHeight) + ? this.setSize(e, k) + : t(b, !0) && this.redraw(); + }, + setSubtitle: function (a) { + this.setTitle(void 0, a); + }, + }); + l(m.prototype, { + update: function (a, b, c, d) { + function e() { + f.applyOptions(a); + null === f.y && m && (f.graphic = m.destroy()); + n(a, !0) && + (m && + m.element && + a && + a.marker && + a.marker.symbol && + (f.graphic = m.destroy()), + a && + a.dataLabels && + f.dataLabel && + (f.dataLabel = f.dataLabel.destroy())); + l = f.index; + h.updateParallelArrays(f, l); + r.data[l] = n(r.data[l], !0) ? f.options : a; + h.isDirty = h.isDirtyData = !0; + !h.fixedBox && h.hasCartesianSeries && (g.isDirtyBox = !0); + "point" === r.legendType && (g.isDirtyLegend = !0); + b && g.redraw(c); + } + var f = this, + h = f.series, + m = f.graphic, + l, + g = h.chart, + r = h.options; + b = t(b, !0); + !1 === d ? e() : f.firePointEvent("update", { options: a }, e); + }, + remove: function (a, b) { + this.series.removePoint(d(this, this.series.data), a, b); + }, + }); + l(b.prototype, { + addPoint: function (a, b, c, d) { + var e = this.options, + f = this.data, + h = this.chart, + m = this.xAxis && this.xAxis.names, + n = e.data, + g, + l, + r = this.xData, + q, + p; + b = t(b, !0); + g = { series: this }; + this.pointClass.prototype.applyOptions.apply(g, [a]); + p = g.x; + q = r.length; + if (this.requireSorting && p < r[q - 1]) + for (l = !0; q && r[q - 1] > p; ) q--; + this.updateParallelArrays(g, "splice", q, 0, 0); + this.updateParallelArrays(g, q); + m && g.name && (m[p] = g.name); + n.splice(q, 0, a); + l && (this.data.splice(q, 0, null), this.processData()); + "point" === e.legendType && this.generatePoints(); + c && + (f[0] && f[0].remove + ? f[0].remove(!1) + : (f.shift(), this.updateParallelArrays(g, "shift"), n.shift())); + this.isDirtyData = this.isDirty = !0; + b && h.redraw(d); + }, + removePoint: function (a, b, c) { + var e = this, + d = e.data, + f = d[a], + h = e.points, + m = e.chart, + n = function () { + h && h.length === d.length && h.splice(a, 1); + d.splice(a, 1); + e.options.data.splice(a, 1); + e.updateParallelArrays(f || { series: e }, "splice", a, 1); + f && f.destroy(); + e.isDirty = !0; + e.isDirtyData = !0; + b && m.redraw(); + }; + z(c, m); + b = t(b, !0); + f ? f.firePointEvent("remove", null, n) : n(); + }, + remove: function (a, b, c) { + function e() { + d.destroy(); + f.isDirtyLegend = f.isDirtyBox = !0; + f.linkSeries(); + t(a, !0) && f.redraw(b); + } + var d = this, + f = d.chart; + !1 !== c ? u(d, "remove", null, e) : e(); + }, + update: function (a, b) { + var c = this, + e = this.chart, + d = this.userOptions, + f = this.type, + h = a.type || d.type || e.options.chart.type, + m = q[f].prototype, + n = ["group", "markerGroup", "dataLabelsGroup"], + g; + if ((h && h !== f) || void 0 !== a.zIndex) n.length = 0; + p(n, function (a) { + n[a] = c[a]; + delete c[a]; + }); + a = y( + d, + { animation: !1, index: this.index, pointStart: this.xData[0] }, + { data: this.options.data }, + a + ); + this.remove(!1, null, !1); + for (g in m) this[g] = void 0; + l(this, q[h || f].prototype); + p(n, function (a) { + c[a] = n[a]; + }); + this.init(e, a); + e.linkSeries(); + t(b, !0) && e.redraw(!1); + }, + }); + l(G.prototype, { + update: function (a, b) { + var c = this.chart; + a = c.options[this.coll][this.options.index] = y(this.userOptions, a); + this.destroy(!0); + this.init(c, l(a, { events: void 0 })); + c.isDirtyBox = !0; + t(b, !0) && c.redraw(); + }, + remove: function (a) { + for ( + var b = this.chart, c = this.coll, e = this.series, d = e.length; + d--; + + ) + e[d] && e[d].remove(!1); + v(b.axes, this); + v(b[c], this); + b.options[c].splice(this.options.index, 1); + p(b[c], function (a, b) { + a.options.index = b; + }); + this.destroy(); + b.isDirtyBox = !0; + t(a, !0) && b.redraw(); + }, + setTitle: function (a, b) { + this.update({ title: a }, b); + }, + setCategories: function (a, b) { + this.update({ categories: a }, b); + }, + }); + })(L); + (function (a) { + var D = a.color, + C = a.each, + G = a.map, + I = a.pick, + h = a.Series, + f = a.seriesType; + f( + "area", + "line", + { softThreshold: !1, threshold: 0 }, + { + singleStacks: !1, + getStackPoints: function () { + var a = [], + f = [], + h = this.xAxis, + u = this.yAxis, + d = u.stacks[this.stackKey], + c = {}, + n = this.points, + y = this.index, + t = u.series, + m = t.length, + b, + q = I(u.options.reversedStacks, !0) ? 1 : -1, + z, + F; + if (this.options.stacking) { + for (z = 0; z < n.length; z++) c[n[z].x] = n[z]; + for (F in d) null !== d[F].total && f.push(F); + f.sort(function (a, b) { + return a - b; + }); + b = G(t, function () { + return this.visible; + }); + C(f, function (e, n) { + var l = 0, + r, + k; + if (c[e] && !c[e].isNull) + a.push(c[e]), + C([-1, 1], function (a) { + var h = 1 === a ? "rightNull" : "leftNull", + l = 0, + t = d[f[n + a]]; + if (t) + for (z = y; 0 <= z && z < m; ) + (r = t.points[z]), + r || + (z === y + ? (c[e][h] = !0) + : b[z] && + (k = d[e].points[z]) && + (l -= k[1] - k[0])), + (z += q); + c[e][1 === a ? "rightCliff" : "leftCliff"] = l; + }); + else { + for (z = y; 0 <= z && z < m; ) { + if ((r = d[e].points[z])) { + l = r[1]; + break; + } + z += q; + } + l = u.toPixels(l, !0); + a.push({ + isNull: !0, + plotX: h.toPixels(e, !0), + plotY: l, + yBottom: l, + }); + } + }); + } + return a; + }, + getGraphPath: function (a) { + var f = h.prototype.getGraphPath, + l = this.options, + p = l.stacking, + d = this.yAxis, + c, + n, + y = [], + t = [], + m = this.index, + b, + q = d.stacks[this.stackKey], + z = l.threshold, + F = d.getThreshold(l.threshold), + e, + l = l.connectNulls || "percent" === p, + r = function (c, e, f) { + var k = a[c]; + c = p && q[k.x].points[m]; + var h = k[f + "Null"] || 0; + f = k[f + "Cliff"] || 0; + var n, + l, + k = !0; + f || h + ? ((n = (h ? c[0] : c[1]) + f), (l = c[0] + f), (k = !!h)) + : !p && a[e] && a[e].isNull && (n = l = z); + void 0 !== n && + (t.push({ + plotX: b, + plotY: null === n ? F : d.getThreshold(n), + isNull: k, + }), + y.push({ + plotX: b, + plotY: null === l ? F : d.getThreshold(l), + doCurve: !1, + })); + }; + a = a || this.points; + p && (a = this.getStackPoints()); + for (c = 0; c < a.length; c++) + if ( + ((n = a[c].isNull), + (b = I(a[c].rectPlotX, a[c].plotX)), + (e = I(a[c].yBottom, F)), + !n || l) + ) + l || r(c, c - 1, "left"), + (n && !p && l) || + (t.push(a[c]), y.push({ x: c, plotX: b, plotY: e })), + l || r(c, c + 1, "right"); + c = f.call(this, t, !0, !0); + y.reversed = !0; + n = f.call(this, y, !0, !0); + n.length && (n[0] = "L"); + n = c.concat(n); + f = f.call(this, t, !1, l); + n.xMap = c.xMap; + this.areaPath = n; + return f; + }, + drawGraph: function () { + this.areaPath = []; + h.prototype.drawGraph.apply(this); + var a = this, + f = this.areaPath, + l = this.options, + u = [["area", "highcharts-area", this.color, l.fillColor]]; + C(this.zones, function (d, c) { + u.push([ + "zone-area-" + c, + "highcharts-area highcharts-zone-area-" + c + " " + d.className, + d.color || a.color, + d.fillColor || l.fillColor, + ]); + }); + C(u, function (d) { + var c = d[0], + h = a[c]; + h + ? ((h.endX = f.xMap), h.animate({ d: f })) + : ((h = a[c] = + a.chart.renderer + .path(f) + .addClass(d[1]) + .attr({ + fill: I( + d[3], + D(d[2]).setOpacity(I(l.fillOpacity, 0.75)).get() + ), + zIndex: 0, + }) + .add(a.group)), + (h.isArea = !0)); + h.startX = f.xMap; + h.shiftUnit = l.step ? 2 : 1; + }); + }, + drawLegendSymbol: a.LegendSymbolMixin.drawRectangle, + } + ); + })(L); + (function (a) { + var D = a.pick; + a = a.seriesType; + a( + "spline", + "line", + {}, + { + getPointSpline: function (a, G, I) { + var h = G.plotX, + f = G.plotY, + p = a[I - 1]; + I = a[I + 1]; + var v, l, u, d; + if ( + p && + !p.isNull && + !1 !== p.doCurve && + I && + !I.isNull && + !1 !== I.doCurve + ) { + a = p.plotY; + u = I.plotX; + I = I.plotY; + var c = 0; + v = (1.5 * h + p.plotX) / 2.5; + l = (1.5 * f + a) / 2.5; + u = (1.5 * h + u) / 2.5; + d = (1.5 * f + I) / 2.5; + u !== v && (c = ((d - l) * (u - h)) / (u - v) + f - d); + l += c; + d += c; + l > a && l > f + ? ((l = Math.max(a, f)), (d = 2 * f - l)) + : l < a && l < f && ((l = Math.min(a, f)), (d = 2 * f - l)); + d > I && d > f + ? ((d = Math.max(I, f)), (l = 2 * f - d)) + : d < I && d < f && ((d = Math.min(I, f)), (l = 2 * f - d)); + G.rightContX = u; + G.rightContY = d; + } + G = [ + "C", + D(p.rightContX, p.plotX), + D(p.rightContY, p.plotY), + D(v, h), + D(l, f), + h, + f, + ]; + p.rightContX = p.rightContY = null; + return G; + }, + } + ); + })(L); + (function (a) { + var D = a.seriesTypes.area.prototype, + C = a.seriesType; + C("areaspline", "spline", a.defaultPlotOptions.area, { + getStackPoints: D.getStackPoints, + getGraphPath: D.getGraphPath, + setStackCliffs: D.setStackCliffs, + drawGraph: D.drawGraph, + drawLegendSymbol: a.LegendSymbolMixin.drawRectangle, + }); + })(L); + (function (a) { + var D = a.animObject, + C = a.color, + G = a.each, + I = a.extend, + h = a.isNumber, + f = a.merge, + p = a.pick, + v = a.Series, + l = a.seriesType, + u = a.svg; + l( + "column", + "line", + { + borderRadius: 0, + groupPadding: 0.2, + marker: null, + pointPadding: 0.1, + minPointLength: 0, + cropThreshold: 50, + pointRange: null, + states: { + hover: { halo: !1, brightness: 0.1, shadow: !1 }, + select: { color: "#cccccc", borderColor: "#000000", shadow: !1 }, + }, + dataLabels: { align: null, verticalAlign: null, y: null }, + softThreshold: !1, + startFromThreshold: !0, + stickyTracking: !1, + tooltip: { distance: 6 }, + threshold: 0, + borderColor: "#ffffff", + }, + { + cropShoulder: 0, + directTouch: !0, + trackerGroups: ["group", "dataLabelsGroup"], + negStacks: !0, + init: function () { + v.prototype.init.apply(this, arguments); + var a = this, + c = a.chart; + c.hasRendered && + G(c.series, function (c) { + c.type === a.type && (c.isDirty = !0); + }); + }, + getColumnMetrics: function () { + var a = this, + c = a.options, + f = a.xAxis, + h = a.yAxis, + l = f.reversed, + m, + b = {}, + q = 0; + !1 === c.grouping + ? (q = 1) + : G(a.chart.series, function (c) { + var e = c.options, + d = c.yAxis, + f; + c.type === a.type && + c.visible && + h.len === d.len && + h.pos === d.pos && + (e.stacking + ? ((m = c.stackKey), + void 0 === b[m] && (b[m] = q++), + (f = b[m])) + : !1 !== e.grouping && (f = q++), + (c.columnIndex = f)); + }); + var u = Math.min( + Math.abs(f.transA) * + (f.ordinalSlope || + c.pointRange || + f.closestPointRange || + f.tickInterval || + 1), + f.len + ), + F = u * c.groupPadding, + e = (u - 2 * F) / q, + c = Math.min( + c.maxPointWidth || f.len, + p(c.pointWidth, e * (1 - 2 * c.pointPadding)) + ); + a.columnMetrics = { + width: c, + offset: + (e - c) / 2 + + (F + ((a.columnIndex || 0) + (l ? 1 : 0)) * e - u / 2) * + (l ? -1 : 1), + }; + return a.columnMetrics; + }, + crispCol: function (a, c, f, h) { + var d = this.chart, + m = this.borderWidth, + b = -(m % 2 ? 0.5 : 0), + m = m % 2 ? 0.5 : 1; + d.inverted && d.renderer.isVML && (m += 1); + f = Math.round(a + f) + b; + a = Math.round(a) + b; + h = Math.round(c + h) + m; + b = 0.5 >= Math.abs(c) && 0.5 < h; + c = Math.round(c) + m; + h -= c; + b && h && (--c, (h += 1)); + return { x: a, y: c, width: f - a, height: h }; + }, + translate: function () { + var a = this, + c = a.chart, + f = a.options, + h = (a.dense = 2 > a.closestPointRange * a.xAxis.transA), + h = (a.borderWidth = p(f.borderWidth, h ? 0 : 1)), + l = a.yAxis, + m = (a.translatedThreshold = l.getThreshold(f.threshold)), + b = p(f.minPointLength, 5), + q = a.getColumnMetrics(), + u = q.width, + F = (a.barW = Math.max(u, 1 + 2 * h)), + e = (a.pointXOffset = q.offset); + c.inverted && (m -= 0.5); + f.pointPadding && (F = Math.ceil(F)); + v.prototype.translate.apply(a); + G(a.points, function (d) { + var f = p(d.yBottom, m), + h = 999 + Math.abs(f), + h = Math.min(Math.max(-h, d.plotY), l.len + h), + k = d.plotX + e, + n = F, + q = Math.min(h, f), + r, + t = Math.max(h, f) - q; + Math.abs(t) < b && + b && + ((t = b), + (r = (!l.reversed && !d.negative) || (l.reversed && d.negative)), + (q = Math.abs(q - m) > b ? f - b : m - (r ? b : 0))); + d.barX = k; + d.pointWidth = u; + d.tooltipPos = c.inverted + ? [l.len + l.pos - c.plotLeft - h, a.xAxis.len - k - n / 2, t] + : [k + n / 2, h + l.pos - c.plotTop, t]; + d.shapeType = "rect"; + d.shapeArgs = a.crispCol.apply( + a, + d.isNull ? [d.plotX, l.len / 2, 0, 0] : [k, q, n, t] + ); + }); + }, + getSymbol: a.noop, + drawLegendSymbol: a.LegendSymbolMixin.drawRectangle, + drawGraph: function () { + this.group[this.dense ? "addClass" : "removeClass"]( + "highcharts-dense-data" + ); + }, + pointAttribs: function (a, c) { + var d = this.options, + f, + h = this.pointAttrToOptions || {}; + f = h.stroke || "borderColor"; + var m = h["stroke-width"] || "borderWidth", + b = (a && a.color) || this.color, + l = a[f] || d[f] || this.color || b, + p = a[m] || d[m] || this[m] || 0, + h = d.dashStyle; + a && + this.zones.length && + (b = + ((b = a.getZone()) && b.color) || a.options.color || this.color); + c && + ((a = d.states[c]), + (c = a.brightness), + (b = + a.color || + (void 0 !== c && C(b).brighten(a.brightness).get()) || + b), + (l = a[f] || l), + (p = a[m] || p), + (h = a.dashStyle || h)); + f = { fill: b, stroke: l, "stroke-width": p }; + d.borderRadius && (f.r = d.borderRadius); + h && (f.dashstyle = h); + return f; + }, + drawPoints: function () { + var a = this, + c = this.chart, + l = a.options, + p = c.renderer, + t = l.animationLimit || 250, + m; + G(a.points, function (b) { + var d = b.graphic; + if (h(b.plotY) && null !== b.y) { + m = b.shapeArgs; + if (d) d[c.pointCount < t ? "animate" : "attr"](f(m)); + else + b.graphic = d = p[b.shapeType](m) + .attr({ class: b.getClassName() }) + .add(b.group || a.group); + d.attr(a.pointAttribs(b, b.selected && "select")).shadow( + l.shadow, + null, + l.stacking && !l.borderRadius + ); + } else d && (b.graphic = d.destroy()); + }); + }, + animate: function (a) { + var c = this, + d = this.yAxis, + f = c.options, + h = this.chart.inverted, + m = {}; + u && + (a + ? ((m.scaleY = 0.001), + (a = Math.min( + d.pos + d.len, + Math.max(d.pos, d.toPixels(f.threshold)) + )), + h ? (m.translateX = a - d.len) : (m.translateY = a), + c.group.attr(m)) + : ((m[h ? "translateX" : "translateY"] = d.pos), + c.group.animate( + m, + I(D(c.options.animation), { + step: function (a, d) { + c.group.attr({ scaleY: Math.max(0.001, d.pos) }); + }, + }) + ), + (c.animate = null))); + }, + remove: function () { + var a = this, + c = a.chart; + c.hasRendered && + G(c.series, function (c) { + c.type === a.type && (c.isDirty = !0); + }); + v.prototype.remove.apply(a, arguments); + }, + } + ); + })(L); + (function (a) { + a = a.seriesType; + a("bar", "column", null, { inverted: !0 }); + })(L); + (function (a) { + var D = a.Series; + a = a.seriesType; + a( + "scatter", + "line", + { + lineWidth: 0, + marker: { enabled: !0 }, + tooltip: { + headerFormat: + '\x3cspan style\x3d"color:{point.color}"\x3e\u25cf\x3c/span\x3e \x3cspan style\x3d"font-size: 0.85em"\x3e {series.name}\x3c/span\x3e\x3cbr/\x3e', + pointFormat: + "x: \x3cb\x3e{point.x}\x3c/b\x3e\x3cbr/\x3ey: \x3cb\x3e{point.y}\x3c/b\x3e\x3cbr/\x3e", + }, + }, + { + sorted: !1, + requireSorting: !1, + noSharedTooltip: !0, + trackerGroups: ["group", "markerGroup", "dataLabelsGroup"], + takeOrdinalPosition: !1, + kdDimensions: 2, + drawGraph: function () { + this.options.lineWidth && D.prototype.drawGraph.call(this); + }, + } + ); + })(L); + (function (a) { + var D = a.pick, + C = a.relativeLength; + a.CenteredSeriesMixin = { + getCenter: function () { + var a = this.options, + I = this.chart, + h = 2 * (a.slicedOffset || 0), + f = I.plotWidth - 2 * h, + I = I.plotHeight - 2 * h, + p = a.center, + p = [ + D(p[0], "50%"), + D(p[1], "50%"), + a.size || "100%", + a.innerSize || 0, + ], + v = Math.min(f, I), + l, + u; + for (l = 0; 4 > l; ++l) + (u = p[l]), + (a = 2 > l || (2 === l && /%$/.test(u))), + (p[l] = C(u, [f, I, v, p[2]][l]) + (a ? h : 0)); + p[3] > p[2] && (p[3] = p[2]); + return p; + }, + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.defined, + G = a.each, + I = a.extend, + h = a.inArray, + f = a.noop, + p = a.pick, + v = a.Point, + l = a.Series, + u = a.seriesType, + d = a.setAnimation; + u( + "pie", + "line", + { + center: [null, null], + clip: !1, + colorByPoint: !0, + dataLabels: { + distance: 30, + enabled: !0, + formatter: function () { + return null === this.y ? void 0 : this.point.name; + }, + x: 0, + }, + ignoreHiddenPoint: !0, + legendType: "point", + marker: null, + size: null, + showInLegend: !1, + slicedOffset: 10, + stickyTracking: !1, + tooltip: { followPointer: !0 }, + borderColor: "#ffffff", + borderWidth: 1, + states: { hover: { brightness: 0.1, shadow: !1 } }, + }, + { + isCartesian: !1, + requireSorting: !1, + directTouch: !0, + noSharedTooltip: !0, + trackerGroups: ["group", "dataLabelsGroup"], + axisTypes: [], + pointAttribs: a.seriesTypes.column.prototype.pointAttribs, + animate: function (a) { + var c = this, + d = c.points, + f = c.startAngleRad; + a || + (G(d, function (a) { + var b = a.graphic, + d = a.shapeArgs; + b && + (b.attr({ r: a.startR || c.center[3] / 2, start: f, end: f }), + b.animate( + { r: d.r, start: d.start, end: d.end }, + c.options.animation + )); + }), + (c.animate = null)); + }, + updateTotals: function () { + var a, + d = 0, + f = this.points, + h = f.length, + m, + b = this.options.ignoreHiddenPoint; + for (a = 0; a < h; a++) + (m = f[a]), + 0 > m.y && (m.y = null), + (d += b && !m.visible ? 0 : m.y); + this.total = d; + for (a = 0; a < h; a++) + (m = f[a]), + (m.percentage = 0 < d && (m.visible || !b) ? (m.y / d) * 100 : 0), + (m.total = d); + }, + generatePoints: function () { + l.prototype.generatePoints.call(this); + this.updateTotals(); + }, + translate: function (a) { + this.generatePoints(); + var c = 0, + d = this.options, + f = d.slicedOffset, + h = f + (d.borderWidth || 0), + b, + l, + u, + F = d.startAngle || 0, + e = (this.startAngleRad = (Math.PI / 180) * (F - 90)), + F = + (this.endAngleRad = + (Math.PI / 180) * (p(d.endAngle, F + 360) - 90)) - e, + r = this.points, + x = d.dataLabels.distance, + d = d.ignoreHiddenPoint, + A, + k = r.length, + w; + a || (this.center = a = this.getCenter()); + this.getX = function (b, c) { + u = Math.asin(Math.min((b - a[1]) / (a[2] / 2 + x), 1)); + return a[0] + (c ? -1 : 1) * Math.cos(u) * (a[2] / 2 + x); + }; + for (A = 0; A < k; A++) { + w = r[A]; + b = e + c * F; + if (!d || w.visible) c += w.percentage / 100; + l = e + c * F; + w.shapeType = "arc"; + w.shapeArgs = { + x: a[0], + y: a[1], + r: a[2] / 2, + innerR: a[3] / 2, + start: Math.round(1e3 * b) / 1e3, + end: Math.round(1e3 * l) / 1e3, + }; + u = (l + b) / 2; + u > 1.5 * Math.PI + ? (u -= 2 * Math.PI) + : u < -Math.PI / 2 && (u += 2 * Math.PI); + w.slicedTranslation = { + translateX: Math.round(Math.cos(u) * f), + translateY: Math.round(Math.sin(u) * f), + }; + b = (Math.cos(u) * a[2]) / 2; + l = (Math.sin(u) * a[2]) / 2; + w.tooltipPos = [a[0] + 0.7 * b, a[1] + 0.7 * l]; + w.half = u < -Math.PI / 2 || u > Math.PI / 2 ? 1 : 0; + w.angle = u; + h = Math.min(h, x / 5); + w.labelPos = [ + a[0] + b + Math.cos(u) * x, + a[1] + l + Math.sin(u) * x, + a[0] + b + Math.cos(u) * h, + a[1] + l + Math.sin(u) * h, + a[0] + b, + a[1] + l, + 0 > x ? "center" : w.half ? "right" : "left", + u, + ]; + } + }, + drawGraph: null, + drawPoints: function () { + var a = this, + d = a.chart.renderer, + f, + h, + m, + b, + l = a.options.shadow; + l && !a.shadowGroup && (a.shadowGroup = d.g("shadow").add(a.group)); + G(a.points, function (c) { + if (null !== c.y) { + h = c.graphic; + b = c.shapeArgs; + f = c.sliced ? c.slicedTranslation : {}; + var n = c.shadowGroup; + l && !n && (n = c.shadowGroup = d.g("shadow").add(a.shadowGroup)); + n && n.attr(f); + m = a.pointAttribs(c, c.selected && "select"); + h + ? h.setRadialReference(a.center).attr(m).animate(I(b, f)) + : ((c.graphic = h = + d[c.shapeType](b) + .addClass(c.getClassName()) + .setRadialReference(a.center) + .attr(f) + .add(a.group)), + c.visible || h.attr({ visibility: "hidden" }), + h.attr(m).attr({ "stroke-linejoin": "round" }).shadow(l, n)); + } + }); + }, + searchPoint: f, + sortByAngle: function (a, d) { + a.sort(function (a, c) { + return void 0 !== a.angle && (c.angle - a.angle) * d; + }); + }, + drawLegendSymbol: a.LegendSymbolMixin.drawRectangle, + getCenter: a.CenteredSeriesMixin.getCenter, + getSymbol: f, + }, + { + init: function () { + v.prototype.init.apply(this, arguments); + var a = this, + d; + a.name = p(a.name, "Slice"); + d = function (c) { + a.slice("select" === c.type); + }; + D(a, "select", d); + D(a, "unselect", d); + return a; + }, + setVisible: function (a, d) { + var c = this, + f = c.series, + m = f.chart, + b = f.options.ignoreHiddenPoint; + d = p(d, b); + a !== c.visible && + ((c.visible = + c.options.visible = + a = + void 0 === a ? !c.visible : a), + (f.options.data[h(c, f.data)] = c.options), + G( + ["graphic", "dataLabel", "connector", "shadowGroup"], + function (b) { + if (c[b]) c[b][a ? "show" : "hide"](!0); + } + ), + c.legendItem && m.legend.colorizeItem(c, a), + a || "hover" !== c.state || c.setState(""), + b && (f.isDirty = !0), + d && m.redraw()); + }, + slice: function (a, f, l) { + var c = this.series; + d(l, c.chart); + p(f, !0); + this.sliced = this.options.sliced = a = C(a) ? a : !this.sliced; + c.options.data[h(this, c.data)] = this.options; + a = a ? this.slicedTranslation : { translateX: 0, translateY: 0 }; + this.graphic.animate(a); + this.shadowGroup && this.shadowGroup.animate(a); + }, + haloPath: function (a) { + var c = this.shapeArgs; + return this.sliced || !this.visible + ? [] + : this.series.chart.renderer.symbols.arc( + c.x, + c.y, + c.r + a, + c.r + a, + { innerR: this.shapeArgs.r, start: c.start, end: c.end } + ); + }, + } + ); + })(L); + (function (a) { + var D = a.addEvent, + C = a.arrayMax, + G = a.defined, + I = a.each, + h = a.extend, + f = a.format, + p = a.map, + v = a.merge, + l = a.noop, + u = a.pick, + d = a.relativeLength, + c = a.Series, + n = a.seriesTypes, + y = a.stableSort; + a.distribute = function (a, c) { + function b(a, b) { + return a.target - b.target; + } + var d, + f = !0, + h = a, + e = [], + m; + m = 0; + for (d = a.length; d--; ) m += a[d].size; + if (m > c) { + y(a, function (a, b) { + return (b.rank || 0) - (a.rank || 0); + }); + for (m = d = 0; m <= c; ) (m += a[d].size), d++; + e = a.splice(d - 1, a.length); + } + y(a, b); + for ( + a = p(a, function (a) { + return { size: a.size, targets: [a.target] }; + }); + f; + + ) { + for (d = a.length; d--; ) + (f = a[d]), + (m = + (Math.min.apply(0, f.targets) + Math.max.apply(0, f.targets)) / + 2), + (f.pos = Math.min(Math.max(0, m - f.size / 2), c - f.size)); + d = a.length; + for (f = !1; d--; ) + 0 < d && + a[d - 1].pos + a[d - 1].size > a[d].pos && + ((a[d - 1].size += a[d].size), + (a[d - 1].targets = a[d - 1].targets.concat(a[d].targets)), + a[d - 1].pos + a[d - 1].size > c && + (a[d - 1].pos = c - a[d - 1].size), + a.splice(d, 1), + (f = !0)); + } + d = 0; + I(a, function (a) { + var b = 0; + I(a.targets, function () { + h[d].pos = a.pos + b; + b += h[d].size; + d++; + }); + }); + h.push.apply(h, e); + y(h, b); + }; + c.prototype.drawDataLabels = function () { + var a = this, + c = a.options, + b = c.dataLabels, + d = a.points, + l, + n, + e = a.hasRendered || 0, + r, + p, + A = u(b.defer, !0), + k = a.chart.renderer; + if (b.enabled || a._hasPointLabels) + a.dlProcessOptions && a.dlProcessOptions(b), + (p = a.plotGroup( + "dataLabelsGroup", + "data-labels", + A && !e ? "hidden" : "visible", + b.zIndex || 6 + )), + A && + (p.attr({ opacity: +e }), + e || + D(a, "afterAnimate", function () { + a.visible && p.show(!0); + p[c.animation ? "animate" : "attr"]( + { opacity: 1 }, + { duration: 200 } + ); + })), + (n = b), + I(d, function (e) { + var d, + m = e.dataLabel, + q, + g, + t = e.connector, + F = !0, + x, + A = {}; + l = e.dlOptions || (e.options && e.options.dataLabels); + d = u(l && l.enabled, n.enabled) && null !== e.y; + if (m && !d) e.dataLabel = m.destroy(); + else if (d) { + b = v(n, l); + x = b.style; + d = b.rotation; + q = e.getLabelConfig(); + r = b.format ? f(b.format, q) : b.formatter.call(q, b); + x.color = u(b.color, x.color, a.color, "#000000"); + if (m) + G(r) + ? (m.attr({ text: r }), (F = !1)) + : ((e.dataLabel = m = m.destroy()), + t && (e.connector = t.destroy())); + else if (G(r)) { + m = { + fill: b.backgroundColor, + stroke: b.borderColor, + "stroke-width": b.borderWidth, + r: b.borderRadius || 0, + rotation: d, + padding: b.padding, + zIndex: 1, + }; + "contrast" === x.color && + (A.color = + b.inside || 0 > b.distance || c.stacking + ? k.getContrast(e.color || a.color) + : "#000000"); + c.cursor && (A.cursor = c.cursor); + for (g in m) void 0 === m[g] && delete m[g]; + m = e.dataLabel = k[d ? "text" : "label"]( + r, + 0, + -9999, + b.shape, + null, + null, + b.useHTML, + null, + "data-label" + ).attr(m); + m.addClass( + "highcharts-data-label-color-" + + e.colorIndex + + " " + + (b.className || "") + + (b.useHTML ? "highcharts-tracker" : "") + ); + m.css(h(x, A)); + m.add(p); + m.shadow(b.shadow); + } + m && a.alignDataLabel(e, m, b, null, F); + } + }); + }; + c.prototype.alignDataLabel = function (a, c, b, d, f) { + var m = this.chart, + e = m.inverted, + l = u(a.plotX, -9999), + n = u(a.plotY, -9999), + p = c.getBBox(), + k, + q = b.rotation, + t = b.align, + v = + this.visible && + (a.series.forceDL || + m.isInsidePlot(l, Math.round(n), e) || + (d && m.isInsidePlot(l, e ? d.x + 1 : d.y + d.height - 1, e))), + z = "justify" === u(b.overflow, "justify"); + v && + ((k = b.style.fontSize), + (k = m.renderer.fontMetrics(k, c).b), + (d = h( + { + x: e ? m.plotWidth - n : l, + y: Math.round(e ? m.plotHeight - l : n), + width: 0, + height: 0, + }, + d + )), + h(b, { width: p.width, height: p.height }), + q + ? ((z = !1), + (e = m.renderer.rotCorr(k, q)), + (e = { + x: d.x + b.x + d.width / 2 + e.x, + y: + d.y + + b.y + + { top: 0, middle: 0.5, bottom: 1 }[b.verticalAlign] * d.height, + }), + c[f ? "attr" : "animate"](e).attr({ align: t }), + (l = (q + 720) % 360), + (l = 180 < l && 360 > l), + "left" === t + ? (e.y -= l ? p.height : 0) + : "center" === t + ? ((e.x -= p.width / 2), (e.y -= p.height / 2)) + : "right" === t && ((e.x -= p.width), (e.y -= l ? 0 : p.height))) + : (c.align(b, null, d), (e = c.alignAttr)), + z + ? this.justifyDataLabel(c, b, e, p, d, f) + : u(b.crop, !0) && + (v = + m.isInsidePlot(e.x, e.y) && + m.isInsidePlot(e.x + p.width, e.y + p.height)), + b.shape && !q && c.attr({ anchorX: a.plotX, anchorY: a.plotY })); + v || (c.attr({ y: -9999 }), (c.placed = !1)); + }; + c.prototype.justifyDataLabel = function (a, c, b, d, f, h) { + var e = this.chart, + m = c.align, + l = c.verticalAlign, + n, + k, + p = a.box ? 0 : a.padding || 0; + n = b.x + p; + 0 > n && ("right" === m ? (c.align = "left") : (c.x = -n), (k = !0)); + n = b.x + d.width - p; + n > e.plotWidth && + ("left" === m ? (c.align = "right") : (c.x = e.plotWidth - n), + (k = !0)); + n = b.y + p; + 0 > n && + ("bottom" === l ? (c.verticalAlign = "top") : (c.y = -n), (k = !0)); + n = b.y + d.height - p; + n > e.plotHeight && + ("top" === l ? (c.verticalAlign = "bottom") : (c.y = e.plotHeight - n), + (k = !0)); + k && ((a.placed = !h), a.align(c, null, f)); + }; + n.pie && + ((n.pie.prototype.drawDataLabels = function () { + var d = this, + f = d.data, + b, + h = d.chart, + l = d.options.dataLabels, + n = u(l.connectorPadding, 10), + e = u(l.connectorWidth, 1), + r = h.plotWidth, + v = h.plotHeight, + A, + k = l.distance, + w = d.center, + y = w[2] / 2, + D = w[1], + G = 0 < k, + g, + B, + L, + M, + R = [[], []], + E, + H, + P, + Q, + O = [0, 0, 0, 0]; + d.visible && + (l.enabled || d._hasPointLabels) && + (c.prototype.drawDataLabels.apply(d), + I(f, function (a) { + a.dataLabel && + a.visible && + (R[a.half].push(a), (a.dataLabel._pos = null)); + }), + I(R, function (c, e) { + var f, + m, + q = c.length, + t, + u, + F; + if (q) + for ( + d.sortByAngle(c, e - 0.5), + 0 < k && + ((f = Math.max(0, D - y - k)), + (m = Math.min(D + y + k, h.plotHeight)), + (t = p(c, function (a) { + if (a.dataLabel) + return ( + (F = a.dataLabel.getBBox().height || 21), + { + target: a.labelPos[1] - f + F / 2, + size: F, + rank: a.y, + } + ); + })), + a.distribute(t, m + F - f)), + Q = 0; + Q < q; + Q++ + ) + (b = c[Q]), + (L = b.labelPos), + (g = b.dataLabel), + (P = !1 === b.visible ? "hidden" : "inherit"), + (u = L[1]), + t + ? void 0 === t[Q].pos + ? (P = "hidden") + : ((M = t[Q].size), (H = f + t[Q].pos)) + : (H = u), + (E = l.justify + ? w[0] + (e ? -1 : 1) * (y + k) + : d.getX(H < f + 2 || H > m - 2 ? u : H, e)), + (g._attr = { visibility: P, align: L[6] }), + (g._pos = { + x: E + l.x + ({ left: n, right: -n }[L[6]] || 0), + y: H + l.y - 10, + }), + (L.x = E), + (L.y = H), + null === d.options.size && + ((B = g.width), + E - B < n + ? (O[3] = Math.max(Math.round(B - E + n), O[3])) + : E + B > r - n && + (O[1] = Math.max(Math.round(E + B - r + n), O[1])), + 0 > H - M / 2 + ? (O[0] = Math.max(Math.round(-H + M / 2), O[0])) + : H + M / 2 > v && + (O[2] = Math.max(Math.round(H + M / 2 - v), O[2]))); + }), + 0 === C(O) || this.verifyDataLabelOverflow(O)) && + (this.placeDataLabels(), + G && + e && + I(this.points, function (a) { + var b; + A = a.connector; + if ((g = a.dataLabel) && g._pos && a.visible) { + P = g._attr.visibility; + if ((b = !A)) + (a.connector = A = + h.renderer + .path() + .addClass( + "highcharts-data-label-connector highcharts-color-" + + a.colorIndex + ) + .add(d.dataLabelsGroup)), + A.attr({ + "stroke-width": e, + stroke: l.connectorColor || a.color || "#666666", + }); + A[b ? "attr" : "animate"]({ d: d.connectorPath(a.labelPos) }); + A.attr("visibility", P); + } else A && (a.connector = A.destroy()); + })); + }), + (n.pie.prototype.connectorPath = function (a) { + var c = a.x, + b = a.y; + return u(this.options.dataLabels.softConnector, !0) + ? [ + "M", + c + ("left" === a[6] ? 5 : -5), + b, + "C", + c, + b, + 2 * a[2] - a[4], + 2 * a[3] - a[5], + a[2], + a[3], + "L", + a[4], + a[5], + ] + : [ + "M", + c + ("left" === a[6] ? 5 : -5), + b, + "L", + a[2], + a[3], + "L", + a[4], + a[5], + ]; + }), + (n.pie.prototype.placeDataLabels = function () { + I(this.points, function (a) { + var c = a.dataLabel; + c && + a.visible && + ((a = c._pos) + ? (c.attr(c._attr), + c[c.moved ? "animate" : "attr"](a), + (c.moved = !0)) + : c && c.attr({ y: -9999 })); + }); + }), + (n.pie.prototype.alignDataLabel = l), + (n.pie.prototype.verifyDataLabelOverflow = function (a) { + var c = this.center, + b = this.options, + f = b.center, + h = b.minSize || 80, + l, + e; + null !== f[0] + ? (l = Math.max(c[2] - Math.max(a[1], a[3]), h)) + : ((l = Math.max(c[2] - a[1] - a[3], h)), + (c[0] += (a[3] - a[1]) / 2)); + null !== f[1] + ? (l = Math.max(Math.min(l, c[2] - Math.max(a[0], a[2])), h)) + : ((l = Math.max(Math.min(l, c[2] - a[0] - a[2]), h)), + (c[1] += (a[0] - a[2]) / 2)); + l < c[2] + ? ((c[2] = l), + (c[3] = Math.min(d(b.innerSize || 0, l), l)), + this.translate(c), + this.drawDataLabels && this.drawDataLabels()) + : (e = !0); + return e; + })); + n.column && + (n.column.prototype.alignDataLabel = function (a, d, b, f, h) { + var l = this.chart.inverted, + e = a.series, + m = a.dlBox || a.shapeArgs, + n = u(a.below, a.plotY > u(this.translatedThreshold, e.yAxis.len)), + p = u(b.inside, !!this.options.stacking); + m && + ((f = v(m)), + 0 > f.y && ((f.height += f.y), (f.y = 0)), + (m = f.y + f.height - e.yAxis.len), + 0 < m && (f.height -= m), + l && + (f = { + x: e.yAxis.len - f.y - f.height, + y: e.xAxis.len - f.x - f.width, + width: f.height, + height: f.width, + }), + p || + (l + ? ((f.x += n ? 0 : f.width), (f.width = 0)) + : ((f.y += n ? f.height : 0), (f.height = 0)))); + b.align = u(b.align, !l || p ? "center" : n ? "right" : "left"); + b.verticalAlign = u( + b.verticalAlign, + l || p ? "middle" : n ? "top" : "bottom" + ); + c.prototype.alignDataLabel.call(this, a, d, b, f, h); + }); + })(L); + (function (a) { + var D = a.Chart, + C = a.each, + G = a.pick, + I = a.addEvent; + D.prototype.callbacks.push(function (a) { + function f() { + var f = []; + C(a.series, function (a) { + var h = a.options.dataLabels, + p = a.dataLabelCollections || ["dataLabel"]; + (h.enabled || a._hasPointLabels) && + !h.allowOverlap && + a.visible && + C(p, function (d) { + C(a.points, function (a) { + a[d] && + ((a[d].labelrank = G( + a.labelrank, + a.shapeArgs && a.shapeArgs.height + )), + f.push(a[d])); + }); + }); + }); + a.hideOverlappingLabels(f); + } + f(); + I(a, "redraw", f); + }); + D.prototype.hideOverlappingLabels = function (a) { + var f = a.length, + h, + v, + l, + u, + d, + c, + n, + y, + t, + m = function (a, c, d, f, e, h, l, m) { + return !(e > a + d || e + l < a || h > c + f || h + m < c); + }; + for (v = 0; v < f; v++) + if ((h = a[v])) (h.oldOpacity = h.opacity), (h.newOpacity = 1); + a.sort(function (a, c) { + return (c.labelrank || 0) - (a.labelrank || 0); + }); + for (v = 0; v < f; v++) + for (l = a[v], h = v + 1; h < f; ++h) + if ( + ((u = a[h]), + l && + u && + l.placed && + u.placed && + 0 !== l.newOpacity && + 0 !== u.newOpacity && + ((d = l.alignAttr), + (c = u.alignAttr), + (n = l.parentGroup), + (y = u.parentGroup), + (t = 2 * (l.box ? 0 : l.padding)), + (d = m( + d.x + n.translateX, + d.y + n.translateY, + l.width - t, + l.height - t, + c.x + y.translateX, + c.y + y.translateY, + u.width - t, + u.height - t + )))) + ) + (l.labelrank < u.labelrank ? l : u).newOpacity = 0; + C(a, function (a) { + var b, c; + a && + ((c = a.newOpacity), + a.oldOpacity !== c && + a.placed && + (c + ? a.show(!0) + : (b = function () { + a.hide(); + }), + (a.alignAttr.opacity = c), + a[a.isOld ? "animate" : "attr"](a.alignAttr, null, b)), + (a.isOld = !0)); + }); + }; + })(L); + (function (a) { + var D = a.addEvent, + C = a.Chart, + G = a.createElement, + I = a.css, + h = a.defaultOptions, + f = a.defaultPlotOptions, + p = a.each, + v = a.extend, + l = a.fireEvent, + u = a.hasTouch, + d = a.inArray, + c = a.isObject, + n = a.Legend, + y = a.merge, + t = a.pick, + m = a.Point, + b = a.Series, + q = a.seriesTypes, + z = a.svg; + a = a.TrackerMixin = { + drawTrackerPoint: function () { + var a = this, + b = a.chart, + c = b.pointer, + d = function (a) { + for (var c = a.target, e; c && !e; ) + (e = c.point), (c = c.parentNode); + if (void 0 !== e && e !== b.hoverPoint) e.onMouseOver(a); + }; + p(a.points, function (a) { + a.graphic && (a.graphic.element.point = a); + a.dataLabel && + (a.dataLabel.div + ? (a.dataLabel.div.point = a) + : (a.dataLabel.element.point = a)); + }); + a._hasTracking || + (p(a.trackerGroups, function (b) { + if (a[b]) { + a[b] + .addClass("highcharts-tracker") + .on("mouseover", d) + .on("mouseout", function (a) { + c.onTrackerMouseOut(a); + }); + if (u) a[b].on("touchstart", d); + a.options.cursor && a[b].css(I).css({ cursor: a.options.cursor }); + } + }), + (a._hasTracking = !0)); + }, + drawTrackerGraph: function () { + var a = this, + b = a.options, + c = b.trackByArea, + d = [].concat(c ? a.areaPath : a.graphPath), + f = d.length, + h = a.chart, + l = h.pointer, + m = h.renderer, + n = h.options.tooltip.snap, + q = a.tracker, + g, + t = function () { + if (h.hoverSeries !== a) a.onMouseOver(); + }, + v = "rgba(192,192,192," + (z ? 0.0001 : 0.002) + ")"; + if (f && !c) + for (g = f + 1; g--; ) + "M" === d[g] && d.splice(g + 1, 0, d[g + 1] - n, d[g + 2], "L"), + ((g && "M" === d[g]) || g === f) && + d.splice(g, 0, "L", d[g - 2] + n, d[g - 1]); + q + ? q.attr({ d: d }) + : a.graph && + ((a.tracker = m + .path(d) + .attr({ + "stroke-linejoin": "round", + visibility: a.visible ? "visible" : "hidden", + stroke: v, + fill: c ? v : "none", + "stroke-width": a.graph.strokeWidth() + (c ? 0 : 2 * n), + zIndex: 2, + }) + .add(a.group)), + p([a.tracker, a.markerGroup], function (a) { + a.addClass("highcharts-tracker") + .on("mouseover", t) + .on("mouseout", function (a) { + l.onTrackerMouseOut(a); + }); + b.cursor && a.css({ cursor: b.cursor }); + if (u) a.on("touchstart", t); + })); + }, + }; + q.column && (q.column.prototype.drawTracker = a.drawTrackerPoint); + q.pie && (q.pie.prototype.drawTracker = a.drawTrackerPoint); + q.scatter && (q.scatter.prototype.drawTracker = a.drawTrackerPoint); + v(n.prototype, { + setItemEvents: function (a, b, c) { + var e = this, + d = e.chart, + f = + "highcharts-legend-" + (a.series ? "point" : "series") + "-active"; + (c ? b : a.legendGroup) + .on("mouseover", function () { + a.setState("hover"); + d.seriesGroup.addClass(f); + b.css(e.options.itemHoverStyle); + }) + .on("mouseout", function () { + b.css(a.visible ? e.itemStyle : e.itemHiddenStyle); + d.seriesGroup.removeClass(f); + a.setState(); + }) + .on("click", function (b) { + var c = function () { + a.setVisible && a.setVisible(); + }; + b = { browserEvent: b }; + a.firePointEvent + ? a.firePointEvent("legendItemClick", b, c) + : l(a, "legendItemClick", b, c); + }); + }, + createCheckboxForItem: function (a) { + a.checkbox = G( + "input", + { type: "checkbox", checked: a.selected, defaultChecked: a.selected }, + this.options.itemCheckboxStyle, + this.chart.container + ); + D(a.checkbox, "click", function (b) { + l( + a.series || a, + "checkboxClick", + { checked: b.target.checked, item: a }, + function () { + a.select(); + } + ); + }); + }, + }); + h.legend.itemStyle.cursor = "pointer"; + v(C.prototype, { + showResetZoom: function () { + var a = this, + b = h.lang, + c = a.options.chart.resetZoomButton, + d = c.theme, + f = d.states, + k = "chart" === c.relativeTo ? null : "plotBox"; + this.resetZoomButton = a.renderer + .button( + b.resetZoom, + null, + null, + function () { + a.zoomOut(); + }, + d, + f && f.hover + ) + .attr({ align: c.position.align, title: b.resetZoomTitle }) + .addClass("highcharts-reset-zoom") + .add() + .align(c.position, !1, k); + }, + zoomOut: function () { + var a = this; + l(a, "selection", { resetSelection: !0 }, function () { + a.zoom(); + }); + }, + zoom: function (a) { + var b, + d = this.pointer, + f = !1, + h; + !a || a.resetSelection + ? p(this.axes, function (a) { + b = a.zoom(); + }) + : p(a.xAxis.concat(a.yAxis), function (a) { + var c = a.axis; + d[c.isXAxis ? "zoomX" : "zoomY"] && + ((b = c.zoom(a.min, a.max)), c.displayBtn && (f = !0)); + }); + h = this.resetZoomButton; + f && !h + ? this.showResetZoom() + : !f && c(h) && (this.resetZoomButton = h.destroy()); + b && + this.redraw( + t( + this.options.chart.animation, + a && a.animation, + 100 > this.pointCount + ) + ); + }, + pan: function (a, b) { + var c = this, + e = c.hoverPoints, + d; + e && + p(e, function (a) { + a.setState(); + }); + p("xy" === b ? [1, 0] : [1], function (b) { + b = c[b ? "xAxis" : "yAxis"][0]; + var e = b.horiz, + f = a[e ? "chartX" : "chartY"], + e = e ? "mouseDownX" : "mouseDownY", + h = c[e], + k = (b.pointRange || 0) / 2, + g = b.getExtremes(), + l = b.toValue(h - f, !0) + k, + k = b.toValue(h + b.len - f, !0) - k, + m = k < l, + h = m ? k : l, + l = m ? l : k, + k = Math.min(g.dataMin, g.min) - h, + g = l - Math.max(g.dataMax, g.max); + b.series.length && + 0 > k && + 0 > g && + (b.setExtremes(h, l, !1, !1, { trigger: "pan" }), (d = !0)); + c[e] = f; + }); + d && c.redraw(!1); + I(c.container, { cursor: "move" }); + }, + }); + v(m.prototype, { + select: function (a, b) { + var c = this, + e = c.series, + f = e.chart; + a = t(a, !c.selected); + c.firePointEvent( + a ? "select" : "unselect", + { accumulate: b }, + function () { + c.selected = c.options.selected = a; + e.options.data[d(c, e.data)] = c.options; + c.setState(a && "select"); + b || + p(f.getSelectedPoints(), function (a) { + a.selected && + a !== c && + ((a.selected = a.options.selected = !1), + (e.options.data[d(a, e.data)] = a.options), + a.setState(""), + a.firePointEvent("unselect")); + }); + } + ); + }, + onMouseOver: function (a, b) { + var c = this.series, + e = c.chart, + d = e.tooltip, + f = e.hoverPoint; + if (this.series) { + if (!b) { + if (f && f !== this) f.onMouseOut(); + if (e.hoverSeries !== c) c.onMouseOver(); + e.hoverPoint = this; + } + !d || (d.shared && !c.noSharedTooltip) + ? d || this.setState("hover") + : (this.setState("hover"), d.refresh(this, a)); + this.firePointEvent("mouseOver"); + } + }, + onMouseOut: function () { + var a = this.series.chart, + b = a.hoverPoints; + this.firePointEvent("mouseOut"); + (b && -1 !== d(this, b)) || (this.setState(), (a.hoverPoint = null)); + }, + importEvents: function () { + if (!this.hasImportedEvents) { + var a = y(this.series.options.point, this.options).events, + b; + this.events = a; + for (b in a) D(this, b, a[b]); + this.hasImportedEvents = !0; + } + }, + setState: function (a, b) { + var c = Math.floor(this.plotX), + d = this.plotY, + e = this.series, + h = e.options.states[a] || {}, + l = f[e.type].marker && e.options.marker, + m = l && !1 === l.enabled, + n = (l && l.states && l.states[a]) || {}, + p = !1 === n.enabled, + g = e.stateMarkerGraphic, + q = this.marker || {}, + u = e.chart, + y = e.halo, + z, + F = l && e.markerAttribs; + a = a || ""; + if ( + !( + (a === this.state && !b) || + (this.selected && "select" !== a) || + !1 === h.enabled || + (a && (p || (m && !1 === n.enabled))) || + (a && q.states && q.states[a] && !1 === q.states[a].enabled) + ) + ) { + F && (z = e.markerAttribs(this, a)); + if (this.graphic) + this.state && + this.graphic.removeClass("highcharts-point-" + this.state), + a && this.graphic.addClass("highcharts-point-" + a), + this.graphic.attr(e.pointAttribs(this, a)), + z && + this.graphic.animate( + z, + t(u.options.chart.animation, n.animation, l.animation) + ), + g && g.hide(); + else { + if (a && n) { + l = q.symbol || e.symbol; + g && g.currentSymbol !== l && (g = g.destroy()); + if (g) g[b ? "animate" : "attr"]({ x: z.x, y: z.y }); + else + l && + ((e.stateMarkerGraphic = g = + u.renderer + .symbol(l, z.x, z.y, z.width, z.height) + .add(e.markerGroup)), + (g.currentSymbol = l)); + g && g.attr(e.pointAttribs(this, a)); + } + g && + (g[a && u.isInsidePlot(c, d, u.inverted) ? "show" : "hide"](), + (g.element.point = this)); + } + (c = h.halo) && c.size + ? (y || + (e.halo = y = + u.renderer.path().add(F ? e.markerGroup : e.group)), + y[b ? "animate" : "attr"]({ d: this.haloPath(c.size) }), + y.attr({ + class: + "highcharts-halo highcharts-color-" + + t(this.colorIndex, e.colorIndex), + }), + (y.point = this), + y.attr( + v( + { + fill: this.color || e.color, + "fill-opacity": c.opacity, + zIndex: -1, + }, + c.attributes + ) + )) + : y && + y.point && + y.point.haloPath && + y.animate({ d: y.point.haloPath(0) }); + this.state = a; + } + }, + haloPath: function (a) { + return this.series.chart.renderer.symbols.circle( + Math.floor(this.plotX) - a, + this.plotY - a, + 2 * a, + 2 * a + ); + }, + }); + v(b.prototype, { + onMouseOver: function () { + var a = this.chart, + b = a.hoverSeries; + if (b && b !== this) b.onMouseOut(); + this.options.events.mouseOver && l(this, "mouseOver"); + this.setState("hover"); + a.hoverSeries = this; + }, + onMouseOut: function () { + var a = this.options, + b = this.chart, + c = b.tooltip, + d = b.hoverPoint; + b.hoverSeries = null; + if (d) d.onMouseOut(); + this && a.events.mouseOut && l(this, "mouseOut"); + !c || + a.stickyTracking || + (c.shared && !this.noSharedTooltip) || + c.hide(); + this.setState(); + }, + setState: function (a) { + var b = this, + c = b.options, + d = b.graph, + f = c.states, + h = c.lineWidth, + c = 0; + a = a || ""; + if ( + b.state !== a && + (p([b.group, b.markerGroup], function (c) { + c && + (b.state && c.removeClass("highcharts-series-" + b.state), + a && c.addClass("highcharts-series-" + a)); + }), + (b.state = a), + !f[a] || !1 !== f[a].enabled) && + (a && (h = f[a].lineWidth || h + (f[a].lineWidthPlus || 0)), + d && !d.dashstyle) + ) + for (f = { "stroke-width": h }, d.attr(f); b["zone-graph-" + c]; ) + b["zone-graph-" + c].attr(f), (c += 1); + }, + setVisible: function (a, b) { + var c = this, + d = c.chart, + e = c.legendItem, + f, + h = d.options.chart.ignoreHiddenSeries, + m = c.visible; + f = (c.visible = + a = + c.options.visible = + c.userOptions.visible = + void 0 === a ? !m : a) + ? "show" + : "hide"; + p( + ["group", "dataLabelsGroup", "markerGroup", "tracker", "tt"], + function (a) { + if (c[a]) c[a][f](); + } + ); + if (d.hoverSeries === c || (d.hoverPoint && d.hoverPoint.series) === c) + c.onMouseOut(); + e && d.legend.colorizeItem(c, a); + c.isDirty = !0; + c.options.stacking && + p(d.series, function (a) { + a.options.stacking && a.visible && (a.isDirty = !0); + }); + p(c.linkedSeries, function (b) { + b.setVisible(a, !1); + }); + h && (d.isDirtyBox = !0); + !1 !== b && d.redraw(); + l(c, f); + }, + show: function () { + this.setVisible(!0); + }, + hide: function () { + this.setVisible(!1); + }, + select: function (a) { + this.selected = a = void 0 === a ? !this.selected : a; + this.checkbox && (this.checkbox.checked = a); + l(this, a ? "select" : "unselect"); + }, + drawTracker: a.drawTrackerGraph, + }); + })(L); + (function (a) { + var D = a.Chart, + C = a.each, + G = a.inArray, + I = a.isObject, + h = a.pick, + f = a.splat; + D.prototype.setResponsive = function (a) { + var f = this.options.responsive; + f && + f.rules && + C( + f.rules, + function (f) { + this.matchResponsiveRule(f, a); + }, + this + ); + }; + D.prototype.matchResponsiveRule = function (f, v) { + var l = this.respRules, + p = f.condition, + d; + d = + p.callback || + function () { + return ( + this.chartWidth <= h(p.maxWidth, Number.MAX_VALUE) && + this.chartHeight <= h(p.maxHeight, Number.MAX_VALUE) && + this.chartWidth >= h(p.minWidth, 0) && + this.chartHeight >= h(p.minHeight, 0) + ); + }; + void 0 === f._id && (f._id = a.uniqueKey()); + d = d.call(this); + !l[f._id] && d + ? f.chartOptions && + ((l[f._id] = this.currentOptions(f.chartOptions)), + this.update(f.chartOptions, v)) + : l[f._id] && !d && (this.update(l[f._id], v), delete l[f._id]); + }; + D.prototype.currentOptions = function (a) { + function h(a, d, c) { + var l, p; + for (l in a) + if (-1 < G(l, ["series", "xAxis", "yAxis"])) + for (a[l] = f(a[l]), c[l] = [], p = 0; p < a[l].length; p++) + (c[l][p] = {}), h(a[l][p], d[l][p], c[l][p]); + else + I(a[l]) + ? ((c[l] = {}), h(a[l], d[l] || {}, c[l])) + : (c[l] = d[l] || null); + } + var l = {}; + h(a, this.options, l); + return l; + }; + })(L); + return L; +}); From 32bf6ed39a5ea235381b524d99f9afe25326c481 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 3 Feb 2022 15:55:13 +0700 Subject: [PATCH 10/94] working on dashboard client tab --- app/assets/stylesheets/home/index.scss | 20 ++++++++++++++------ app/views/dashboards/_report_builder.haml | 10 ++++++++++ app/views/dashboards/index.html.haml | 20 ++++++++++---------- 3 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 app/views/dashboards/_report_builder.haml diff --git a/app/assets/stylesheets/home/index.scss b/app/assets/stylesheets/home/index.scss index 58686e932b..6215c7eaba 100644 --- a/app/assets/stylesheets/home/index.scss +++ b/app/assets/stylesheets/home/index.scss @@ -1,5 +1,7 @@ #home-index { - .widget-tasks-panel, .widget-go-client-panel { + .widget-tasks-panel, + .widget-go-client-panel, + .widget-dashboard-panel-min-height { min-height: 98px; cursor: pointer; } @@ -16,10 +18,10 @@ color: white; } - .fa-3x{ + .fa-3x { padding-top: 13px; } - .max-hight{ + .max-hight { max-height: 200px; } @@ -53,7 +55,12 @@ } } - .client-panel, .family-panel, .program-stream-panel, .tasks-panel, .go-client-panel { + .client-panel, + .family-panel, + .program-stream-panel, + .tasks-panel, + .go-client-panel, + .widget-dashboard-panel { .top-bar { margin-bottom: 15px; } @@ -69,7 +76,8 @@ .widget-client-panel, .widget-tasks-panel, .widget-program-panel, - .widget-go-client-panel { + .widget-go-client-panel, + .widget-dashboard-panel-min-height { border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: 0; @@ -173,7 +181,7 @@ } } .widget-staff-panel { - background-color: #fe4365; + background-color: #fe4365; &:hover { background-color: #fe2149; } diff --git a/app/views/dashboards/_report_builder.haml b/app/views/dashboards/_report_builder.haml new file mode 100644 index 0000000000..371a00d74c --- /dev/null +++ b/app/views/dashboards/_report_builder.haml @@ -0,0 +1,10 @@ +.col-xs-12.col-sm-3 + .panel.panel-default.widget-dashboard-panel + .panel-body + .widget.style1.widget-dashboard-panel-min-height{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } + .row.vertical-align + .col-xs-3 + %i.fa.fa-file-text.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.report_builder') \ No newline at end of file diff --git a/app/views/dashboards/index.html.haml b/app/views/dashboards/index.html.haml index 675de2e529..2eb24b74b8 100644 --- a/app/views/dashboards/index.html.haml +++ b/app/views/dashboards/index.html.haml @@ -1,16 +1,6 @@ #home-index - if @referral_sources.any? && @current_user.admin? %p.check-ref-sources.hide referral_sources - - = render 'dashboards/referral_source_category_reminder' - - if @program_streams.present? - = render 'dashboards/program_stream_services' - .row - = render 'active_tasks_side' - = render 'multiple_forms' - = render 'client' - = render 'go_to_client' - %ul.nav.nav-tabs.csi-tab{ role: "tablist" } %li.active{role: "presentation"} %a{"aria-controls" => "client-tab", "data-toggle" => "tab", href: "#client-tab", role: "tab"}= t('.client_dashboard') @@ -20,8 +10,18 @@ %a{"aria-controls" => "community-tab", "data-toggle" => "tab", href: "#community-tab", role: "tab"}= t('.community_dashboard') %li{role: "presentation"} %a{"aria-controls" => "hotline-tab", "data-toggle" => "tab", href: "#hotline-tab", role: "tab"}= t('.hotline_dashboard') + .tab-content #client-tab.tab-pane.active{ role: "tabpanel" } + = render 'dashboards/referral_source_category_reminder' + - if @program_streams.present? + = render 'dashboards/program_stream_services' + .row + = render 'active_tasks_side' + = render 'multiple_forms' + = render 'client' + = render 'go_to_client' + = render 'report_builder' .row = render 'client_program_stream_by_gender' .row From 71d96ac5b72df8922d15e9f938d7db2e261e9df4 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 10 Feb 2022 18:37:47 +0700 Subject: [PATCH 11/94] fixed error in client.rb#update_referral_status_on_target_ngo --- app/models/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/client.rb b/app/models/client.rb index 7f001c569d..5f8a469a45 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -829,7 +829,7 @@ def delete_referee def update_referral_status_on_target_ngo referral = referrals.received.last - return if referral.blank? + return if referral.blank? || referral.referred_from[/external system/i].present? current_ngo = Apartment::Tenant.current Apartment::Tenant.switch referral.referred_from From d71b4e7a25b0c845485914cadb720f0ae30171e3 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 16 Feb 2022 10:44:07 +0700 Subject: [PATCH 12/94] worked on dashboard steps --- .../javascripts/dashboards/index.coffee | 70 ++++++++++++++- app/assets/javascripts/report_creator.coffee | 30 ++++++- app/assets/stylesheets/dashboards/index.scss | 2 - .../enrollment_date_sql_builder.rb | 1 + app/controllers/api/clients_controller.rb | 25 ++++++ app/views/dashboards/_active_tasks_side.haml | 21 +++-- app/views/dashboards/_client.haml | 23 +++-- .../_client_program_stream_by_gender.haml | 3 +- app/views/dashboards/_data_agregation.haml | 21 +++++ app/views/dashboards/_go_to_client.haml | 21 +++-- app/views/dashboards/_multiple_forms.haml | 85 +++++++++---------- app/views/dashboards/_report_builder.haml | 19 ++--- app/views/dashboards/index.html.haml | 16 ++-- config/routes.rb | 1 + 14 files changed, 240 insertions(+), 98 deletions(-) create mode 100644 app/views/dashboards/_data_agregation.haml diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index a0fe07b9ab..d387b9663b 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -12,7 +12,7 @@ CIF.DashboardsIndex = do -> # _clientStatusChart() _familyType() _resizeChart() - _clientProgramStreamByGender() + # _clientProgramStreamByGender() _clientProgramStream() _initSelect2() _openTaskListModal() @@ -28,6 +28,7 @@ CIF.DashboardsIndex = do -> _loadModalReminder() _handleSearchClient() _handleMultiFormAssessmentCaseNote() + _loadSteps() _loadModalReminder = -> if localStorage.getItem('from login') == 'true' @@ -337,4 +338,71 @@ CIF.DashboardsIndex = do -> $("ul#casenote-tab-dropdown a").addClass('disabled') + _loadSteps = (form) -> + bodyTag = 'div' + rootId = "#rootwizard" + that = @ + $(rootId).steps + headerTag: 'h4' + bodyTag: bodyTag + enableAllSteps: true + transitionEffect: 'slideLeft' + autoFocus: true + titleTemplate: 'Data #title#' + onInit: (event, currentIndex) -> + currentTab = "#{rootId}-p-#{currentIndex}" + _clientProgramStreamByGender() + return + + onStepChanging: (event, currentIndex, newIndex) -> + console.log 'onStepChanging' + currentTab = "#{rootId}-p-#{currentIndex}" + return true + + onStepChanged: (event, currentIndex, priorIndex) -> + console.log 'onStepChanged' + currentTab = "#{rootId}-p-#{currentIndex}" + currentStep = $("#{rootId}-p-" + currentIndex) + if $("#{currentTab} #active-client:visible").length + url = $("#active-client").data('url') + _active_client_by_gender(url) + + _active_client_by_gender = (url) -> + element = $('#active-client') + title = element.data('title') + $.ajax + type: 'get' + url: url + dataType: 'JSON' + success: (response) -> + data = + categories: [ + 'Female' + 'Male' + 'Other' + ] + series: [ + { + name: 'Adult' + data: [ + response.adult_females + response.adult_males + 0 + ] + } + { + name: 'Children' + data: [ + response.girls + response.boys + response.others + ] + } + ] + + report = new CIF.ReportCreator(data, title, '', element) + report.columnChart() + error: (response, status, msg) -> + return + { init: _init } diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index f40e031cf5..4f845b744e 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -6,7 +6,34 @@ class CIF.ReportCreator @element = element @colors = ['f9c00c', '#4caf50', '#00695c', '#01579b', '#4dd0e1', '#2e7d32', '#4db6ac', '#00897b', '#a5d6a7', '#43a047', '#c5e1a5', '#7cb342', '#fdd835', '#fb8c00', '#6d4c41', '#757575', '#ef9a9a', '#e53935', '#f48fb1', '#d81b60', '#ce93d8', '#8e24aa', '#b39ddb', '#7e57c2', '#9fa8da', '#3949ab', '#64b5f6', '#827717'] - barChart: -> + + columnChart: -> + theData = @data + title = @title + subtitle = @yAxisTitle + if @data != undefined + $(@element).highcharts + chart: type: 'column' + title: text: title + subtitle: text: subtitle + xAxis: + categories: theData.categories + crosshair: true + yAxis: + min: 0 + title: text: subtitle + tooltip: + headerFormat: '{point.key}' + pointFormat: '' + '' + footerFormat: '
{series.name}: {point.y:.1f} mm
' + shared: true + useHTML: true + plotOptions: column: + pointPadding: 0.2 + borderWidth: 0 + series: theData.series + + barChart: -> theData = @data if @data != undefined $(@element).highcharts @@ -46,6 +73,7 @@ class CIF.ReportCreator ) } $('.highcharts-credits').css('display', 'none') + lineChart: -> if @data != undefined $(@element).highcharts diff --git a/app/assets/stylesheets/dashboards/index.scss b/app/assets/stylesheets/dashboards/index.scss index 5c40e01c7a..45c0de43f2 100644 --- a/app/assets/stylesheets/dashboards/index.scss +++ b/app/assets/stylesheets/dashboards/index.scss @@ -184,10 +184,8 @@ body[id="dashboards-index"] { .highcharts-color-0 { fill: #7cb5ec; - stroke: #7cb5ec; } .highcharts-color-1 { fill: #f9c00c; - stroke: #f0d16b; } } diff --git a/app/classes/advanced_searches/enrollment_date_sql_builder.rb b/app/classes/advanced_searches/enrollment_date_sql_builder.rb index ccf3af9dee..ee2e1f17d5 100644 --- a/app/classes/advanced_searches/enrollment_date_sql_builder.rb +++ b/app/classes/advanced_searches/enrollment_date_sql_builder.rb @@ -9,6 +9,7 @@ def initialize(program_stream_id, rule) def get_sql sql_string = 'clients.id IN (?)' client_enrollments = ClientEnrollment.where(program_stream_id: @program_stream_id) + binding.pry basic_rules = $param_rules['basic_rules'] param_rules = basic_rules.is_a?(Hash) ? basic_rules : JSON.parse(basic_rules).with_indifferent_access enrollment_rules = iterate_param_rules(param_rules) diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index d4eea443f3..76dea48021 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -81,6 +81,19 @@ def update end end + def render_client_by_gender + clients = Client.active_status + client_data = { + client_count: clients.count, + adult_females: adule_client_gender_count(clients, :female), + adult_males: adule_client_gender_count(clients, :male), + girls: under_18_client_gender_count(clients, :female), + boys: under_18_client_gender_count(clients, :male), + others: other_client_gender_count(clients) + } + render json: client_data + end + private def client_params @@ -276,5 +289,17 @@ def sort_column def sort_direction params[:order]['0']['dir'] == "desc" ? "desc" : "asc" end + + def adule_client_gender_count(clients, type = :male) + clients.public_send(type).where("(EXTRACT(year FROM age(current_date, clients.date_of_birth)) :: int) >= ?", 18).count + end + + def under_18_client_gender_count(clients, type = :male) + clients.public_send(type).where("(EXTRACT(year FROM age(current_date, clients.date_of_birth)) :: int) < ?", 18).count + end + + def other_client_gender_count(clients) + clients.where("gender IS NOT NULL AND (gender NOT IN ('male', 'female') OR date_of_birth IS NULL)").count + end end end diff --git a/app/views/dashboards/_active_tasks_side.haml b/app/views/dashboards/_active_tasks_side.haml index 1266ec37d5..ae2864e85f 100644 --- a/app/views/dashboards/_active_tasks_side.haml +++ b/app/views/dashboards/_active_tasks_side.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.tasks-panel - .panel-body - .widget.style1.widget-tasks-panel{ 'data-target' => '#active_tasks_list', 'data-toggle' => 'modal', type: 'button'} - .row.vertical-align - .col-xs-3 - %i.fa.fa-tasks.fa-3x - .col-xs-9.text-right - %span.text-row - = t('.active_tasks') +.panel.panel-default.tasks-panel + .panel-body + .widget.style1.widget-tasks-panel{ 'data-target' => '#active_tasks_list', 'data-toggle' => 'modal', type: 'button'} + .row.vertical-align + .col-xs-3 + %i.fa.fa-tasks.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.active_tasks') - = render 'active_tasks' += render 'active_tasks' diff --git a/app/views/dashboards/_client.haml b/app/views/dashboards/_client.haml index 5f18d05bea..23341e518a 100644 --- a/app/views/dashboards/_client.haml +++ b/app/views/dashboards/_client.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.client-panel - .panel-body - = link_to clients_path(active_client: true) do - .widget.style1.widget-client-panel - .row.vertical-align - .col-xs-3 - %i.fa.fa-child.fa-3x - .col-xs-9.active-client.text-right - %span.text-row - = t('.clients') - %h2.font-bold= @dashboard.clients.active_status.count +.panel.panel-default.client-panel + .panel-body + = link_to clients_path(active_client: true) do + .widget.style1.widget-client-panel + .row.vertical-align + .col-xs-3 + %i.fa.fa-child.fa-3x + .col-xs-9.active-client.text-right + %span.text-row + = t('.clients') + %h2.font-bold= @dashboard.clients.active_status.count diff --git a/app/views/dashboards/_client_program_stream_by_gender.haml b/app/views/dashboards/_client_program_stream_by_gender.haml index 15416a8dcc..4f51d18a8e 100644 --- a/app/views/dashboards/_client_program_stream_by_gender.haml +++ b/app/views/dashboards/_client_program_stream_by_gender.haml @@ -14,5 +14,4 @@ .row .col-xs-12 #client-by-program-stream{ 'data-content-count' => @dashboard.client_program_stream.to_json, 'data-title' => t('.client_active_in_programs')} - %hr/ - #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } + diff --git a/app/views/dashboards/_data_agregation.haml b/app/views/dashboards/_data_agregation.haml new file mode 100644 index 0000000000..a70a6a995c --- /dev/null +++ b/app/views/dashboards/_data_agregation.haml @@ -0,0 +1,21 @@ +.col-xs-12 + .panel.panel-default.program-stream-panel + .panel-body + = link_to program_streams_path do + .widget.style1.widget-program-panel + .row.vertical-align + .col-xs-5 + %i.fa.fa-bar-chart.fa-3x + .col-xs-7.text-right + %span.text-font + = t('.data_agregation') + + .row + .col-xs-12 + #rootwizard.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } + %h4= t('.active_progarm_streams_by_gender') + %div{ class: "active-progarm-streams-by-gender" } + #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } + %h4= t('.active_client_by_gender') + %div{ class: "active-client-by-gender" } + #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } \ No newline at end of file diff --git a/app/views/dashboards/_go_to_client.haml b/app/views/dashboards/_go_to_client.haml index 6038294d03..cb57251b39 100644 --- a/app/views/dashboards/_go_to_client.haml +++ b/app/views/dashboards/_go_to_client.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.go-client-panel - .panel-body - .widget.style1.widget-go-client-panel{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } - .row.vertical-align - .col-xs-3 - %i.fa.fa-user.fa-3x - .col-xs-9.text-right - %span.text-row - = t('.go_to_client') +.panel.panel-default.go-client-panel + .panel-body + .widget.style1.widget-go-client-panel{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } + .row.vertical-align + .col-xs-3 + %i.fa.fa-user.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.go_to_client') - = render 'client_search' += render 'client_search' diff --git a/app/views/dashboards/_multiple_forms.haml b/app/views/dashboards/_multiple_forms.haml index 1f96d0c371..1618c5e7b8 100644 --- a/app/views/dashboards/_multiple_forms.haml +++ b/app/views/dashboards/_multiple_forms.haml @@ -1,44 +1,43 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.tasks-panel - .panel-body - .widget.style1.widget-tasks-panel{ 'data-target' => '#multiple-form-modal', 'data-toggle' => 'modal', type: 'button'} - .row.vertical-align - .col-xs-3 - %i.fa.fa-folder-open-o.fa-3x - .col-xs-9.text-right - %span.text-font - = t('.add_new_document') +.panel.panel-default.tasks-panel + .panel-body + .widget.style1.widget-tasks-panel{ 'data-target' => '#multiple-form-modal', 'data-toggle' => 'modal', type: 'button'} + .row.vertical-align + .col-xs-3 + %i.fa.fa-folder-open-o.fa-3x + .col-xs-9.text-right + %span.text-font + = t('.add_new_document') - #multiple-form-modal.modal.fade{ "aria-hidden" => "true", "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1", data: {backdrop: 'static', keyboard: 'false'} } - .modal-dialog#width-modal.modal-lg - .modal-content - .modal-body - %button.close{ 'data-dismiss': 'modal', type: 'button'} × - %div - %h2.text-center=t('.kind_of_document') - %ul.nav.nav-tabs{role: "tablist"} - %li.active{role: "presentation"} - %a{"data-toggle": "tab", href: "#record", role: "tab"} - = t('.record') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#custom-fields", role: "tab"} - = t('.form') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#assessment", role: "tab"} - = t('.assessment') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#case-note", role: "tab"} - = t('clients.form.case_note') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#program-enrollment", role: "tab"} - = t('.program_enrollment') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#program-streams", role: "tab"} - = t('.tracking_forms') - .tab-content - = render 'record_tab' - = render 'assessment_tab' - = render 'case_note_tab' - = render 'program_enrollment_tab' - = render 'custom_fields_tab' - = render 'program_streams_tab' +#multiple-form-modal.modal.fade{ "aria-hidden" => "true", "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1", data: {backdrop: 'static', keyboard: 'false'} } + .modal-dialog#width-modal.modal-lg + .modal-content + .modal-body + %button.close{ 'data-dismiss': 'modal', type: 'button'} × + %div + %h2.text-center=t('.kind_of_document') + %ul.nav.nav-tabs{role: "tablist"} + %li.active{role: "presentation"} + %a{"data-toggle": "tab", href: "#record", role: "tab"} + = t('.record') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#custom-fields", role: "tab"} + = t('.form') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#assessment", role: "tab"} + = t('.assessment') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#case-note", role: "tab"} + = t('clients.form.case_note') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#program-enrollment", role: "tab"} + = t('.program_enrollment') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#program-streams", role: "tab"} + = t('.tracking_forms') + .tab-content + = render 'record_tab' + = render 'assessment_tab' + = render 'case_note_tab' + = render 'program_enrollment_tab' + = render 'custom_fields_tab' + = render 'program_streams_tab' diff --git a/app/views/dashboards/_report_builder.haml b/app/views/dashboards/_report_builder.haml index 371a00d74c..c1ea6509f3 100644 --- a/app/views/dashboards/_report_builder.haml +++ b/app/views/dashboards/_report_builder.haml @@ -1,10 +1,9 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.widget-dashboard-panel - .panel-body - .widget.style1.widget-dashboard-panel-min-height{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } - .row.vertical-align - .col-xs-3 - %i.fa.fa-file-text.fa-3x - .col-xs-9.text-right - %span.text-row - = t('.report_builder') \ No newline at end of file +.panel.panel-default.widget-dashboard-panel + .panel-body + .widget.style1.widget-dashboard-panel-min-height{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } + .row.vertical-align + .col-xs-3 + %i.fa.fa-file-text.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.report_builder') \ No newline at end of file diff --git a/app/views/dashboards/index.html.haml b/app/views/dashboards/index.html.haml index 2eb24b74b8..e37c277ec8 100644 --- a/app/views/dashboards/index.html.haml +++ b/app/views/dashboards/index.html.haml @@ -17,13 +17,19 @@ - if @program_streams.present? = render 'dashboards/program_stream_services' .row - = render 'active_tasks_side' - = render 'multiple_forms' - = render 'client' - = render 'go_to_client' - = render 'report_builder' + .col-xs-12.col-sm-3 + = render 'active_tasks_side' + .col-xs-12.col-sm-3 + = render 'multiple_forms' + .col-xs-12.col-sm-2 + = render 'client' + .col-xs-12.col-sm-2 + = render 'go_to_client' + .col-xs-12.col-sm-2 + = render 'report_builder' .row = render 'client_program_stream_by_gender' + = render 'data_agregation' .row - if can? :read, Family = render 'family' diff --git a/config/routes.rb b/config/routes.rb index 4a716f9645..da211c0954 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -284,6 +284,7 @@ get :find_client_case_worker, on: :member post :assessments, on: :collection get :search_client, on: :collection + get :render_client_by_gender, on: :collection, as: 'client_by_gender' end resources :custom_fields do collection do From 806fc524b30f805f1e89c886cacbcfa286e26dcf Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 16 Feb 2022 11:53:18 +0700 Subject: [PATCH 13/94] 2022021611 fixed domain score between rule error in report builder --- app/classes/advanced_searches/domain_score_sql_builder.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/classes/advanced_searches/domain_score_sql_builder.rb b/app/classes/advanced_searches/domain_score_sql_builder.rb index 0a4602a373..3255213a74 100644 --- a/app/classes/advanced_searches/domain_score_sql_builder.rb +++ b/app/classes/advanced_searches/domain_score_sql_builder.rb @@ -44,7 +44,8 @@ def domainscore_field_query results = mapping_assessment_query_rules(basic_rules).reject(&:blank?) assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true #{assessment_completed_sql}) AND assessments.created_at = (SELECT created_at FROM assessments WHERE clients.id = assessments.client_id ORDER BY assessments.created_at limit 1 offset #{(assessment_number || 1) - 1})".squish - score = @value.to_i.zero? ? nil : @value.to_i + score = [@value].flatten.map(&:to_i).sum.zero? ? nil : [@value].flatten.map(&:to_i) + if assessment_completed_sql.present? && assessment_number.present? clients.where(assessment_domains: { score: score, domain_id: @domain_id }).where(sql) else @@ -70,6 +71,8 @@ def domainscore_operator(clients, operator, score, sql) clients.where("assessment_domains.score IS NULL") when 'is_not_empty' clients.where("assessment_domains.score IS NOT NULL AND assessment_domains.domain_id = ?", @domain_id) + when 'between' + clients.where("(assessment_domains.score BETWEEN ? AND ?) AND assessment_domains.domain_id = ?", score.first, score.last, @domain_id) else clients.where(assessment_domains: { score: score, domain_id: @domain_id }) end From be3bc4fcf12ffb8a5d86e226788166dcd7cb410e Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 16 Feb 2022 16:18:06 +0700 Subject: [PATCH 14/94] worked on dashboard active case by donor --- .../javascripts/dashboards/index.coffee | 62 ++++++++++++++++--- app/assets/javascripts/report_creator.coffee | 5 +- .../enrollment_date_sql_builder.rb | 1 - app/controllers/api/clients_controller.rb | 5 ++ app/views/dashboards/_data_agregation.haml | 7 ++- config/routes.rb | 1 + 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index d387b9663b..ca3169dca5 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -366,6 +366,9 @@ CIF.DashboardsIndex = do -> if $("#{currentTab} #active-client:visible").length url = $("#active-client").data('url') _active_client_by_gender(url) + else if $("#{currentTab} #active-case-by-donor:visible").length + url = $("#active-case-by-donor").data('url') + _active_case_by_donor(url) _active_client_by_gender = (url) -> element = $('#active-client') @@ -377,26 +380,30 @@ CIF.DashboardsIndex = do -> success: (response) -> data = categories: [ - 'Female' - 'Male' + 'Children' + 'Adult' 'Other' ] series: [ { - name: 'Adult' + name: 'Female' data: [ + response.girls response.adult_females - response.adult_males 0 ] } { - name: 'Children' + name: 'Male' data: [ - response.girls response.boys - response.others + response.adult_males + 0 ] + }, + { + name: 'Other' + data: [0, 0, response.others] } ] @@ -405,4 +412,45 @@ CIF.DashboardsIndex = do -> error: (response, status, msg) -> return + _active_case_by_donor = (url) -> + element = $('#active-case-by-donor') + title = element.data('title') + $.ajax + type: 'get' + url: url + dataType: 'JSON' + success: (response) -> + data = + categories: [ + 'Children' + 'Adult' + 'Other' + ] + series: [ + { + name: 'Female' + data: [ + response.girls + response.adult_females + 0 + ] + } + { + name: 'Male' + data: [ + response.boys + response.adult_males + 0 + ] + }, + { + name: 'Other' + data: [0, 0, response.others] + } + ] + + report = new CIF.ReportCreator(data, title, '', element) + report.pieChart() + error: (response, status, msg) -> + return { init: _init } diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index 4f845b744e..b0a110f5d9 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -13,6 +13,7 @@ class CIF.ReportCreator subtitle = @yAxisTitle if @data != undefined $(@element).highcharts + colors: @colors chart: type: 'column' title: text: title subtitle: text: subtitle @@ -29,10 +30,12 @@ class CIF.ReportCreator shared: true useHTML: true plotOptions: column: - pointPadding: 0.2 + pointPadding: 0.05 borderWidth: 0 series: theData.series + $('.highcharts-credits').css('display', 'none') + barChart: -> theData = @data if @data != undefined diff --git a/app/classes/advanced_searches/enrollment_date_sql_builder.rb b/app/classes/advanced_searches/enrollment_date_sql_builder.rb index ee2e1f17d5..ccf3af9dee 100644 --- a/app/classes/advanced_searches/enrollment_date_sql_builder.rb +++ b/app/classes/advanced_searches/enrollment_date_sql_builder.rb @@ -9,7 +9,6 @@ def initialize(program_stream_id, rule) def get_sql sql_string = 'clients.id IN (?)' client_enrollments = ClientEnrollment.where(program_stream_id: @program_stream_id) - binding.pry basic_rules = $param_rules['basic_rules'] param_rules = basic_rules.is_a?(Hash) ? basic_rules : JSON.parse(basic_rules).with_indifferent_access enrollment_rules = iterate_param_rules(param_rules) diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index 76dea48021..858bf8b5f0 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -94,6 +94,11 @@ def render_client_by_gender render json: client_data end + def render_active_client_by_donor + donor_data = Donor.includes(:clients).references(:clients).group("donors.name").count("clients.id") + render json: donor_data + end + private def client_params diff --git a/app/views/dashboards/_data_agregation.haml b/app/views/dashboards/_data_agregation.haml index a70a6a995c..04eb58f942 100644 --- a/app/views/dashboards/_data_agregation.haml +++ b/app/views/dashboards/_data_agregation.haml @@ -12,10 +12,13 @@ .row .col-xs-12 - #rootwizard.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } + #rootwizard.m-t-sm.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } %h4= t('.active_progarm_streams_by_gender') %div{ class: "active-progarm-streams-by-gender" } #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } %h4= t('.active_client_by_gender') %div{ class: "active-client-by-gender" } - #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } \ No newline at end of file + #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } + %h4= t('.active_case_by_donor') + %div{ class: "active-case-by-donor" } + #active-case-by-donor{ 'data-url' => active_client_by_donor_api_clients_path, 'data-title' => t('.active_case_by_donor') } diff --git a/config/routes.rb b/config/routes.rb index da211c0954..3142b94206 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,6 +285,7 @@ post :assessments, on: :collection get :search_client, on: :collection get :render_client_by_gender, on: :collection, as: 'client_by_gender' + get :render_active_client_by_donor, on: :collection, as: 'active_client_by_donor' end resources :custom_fields do collection do From 4bbec6dc161b703e379614ff402b84c37bbbc82f Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 18 Feb 2022 16:05:11 +0700 Subject: [PATCH 15/94] finished active case by donor pieChart --- .../javascripts/dashboards/index.coffee | 82 ++++++++++++------- app/assets/javascripts/report_creator.coffee | 2 + app/assets/stylesheets/dashboards/index.scss | 12 +-- 3 files changed, 59 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index ca3169dca5..a89fcdcb56 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -420,37 +420,57 @@ CIF.DashboardsIndex = do -> url: url dataType: 'JSON' success: (response) -> - data = - categories: [ - 'Children' - 'Adult' - 'Other' - ] - series: [ - { - name: 'Female' - data: [ - response.girls - response.adult_females - 0 - ] - } - { - name: 'Male' - data: [ - response.boys - response.adult_males - 0 - ] - }, - { - name: 'Other' - data: [0, 0, response.others] - } - ] - - report = new CIF.ReportCreator(data, title, '', element) - report.pieChart() + data = Object.keys(response).map (key) -> + container = undefined + container = {} + container.name = key + container.y = response[key] + container + + _highChartsPieChart(data, title, element) error: (response, status, msg) -> return + + # Make monochrome colors + _pieColors = do -> + colors = [] + base = Highcharts.getOptions().colors[2] + i = undefined + i = 0 + while i < 50 + # Start out with a darkened base color (negative brighten), and end + # up with a much brighter color + colors.push Highcharts.color(base).brighten((i - 5) / 7).get() + i += 1 + colors + + _highChartsPieChart = (data, title, element) -> + $(element).highcharts + chart: + plotBackgroundColor: null + plotBorderWidth: null + plotShadow: false + type: 'pie' + title: text: title + tooltip: pointFormat: '{series.name}: {point.percentage:.1f}%' + accessibility: point: valueSuffix: '%' + plotOptions: pie: + allowPointSelect: true + cursor: 'pointer' + showInLegend: true + dataLabels: + enabled: true + format: '{point.name}
{point.percentage:.1f} %' + filter: + property: 'percentage' + operator: '>' + value: 4 + series: + colorByPoint: true + series: [ { + name: title + data: data + } ] + $('.highcharts-credits').css('display', 'none') + { init: _init } diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index b0a110f5d9..8744349e52 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -221,6 +221,8 @@ class CIF.ReportCreator showInLegend: true point: events: click: -> location.href = @options.url + series: + colorByPoint: true series: [ { dataLabels: distance: if _.isEmpty(options) then -30 else 30 diff --git a/app/assets/stylesheets/dashboards/index.scss b/app/assets/stylesheets/dashboards/index.scss index 45c0de43f2..21094f9c90 100644 --- a/app/assets/stylesheets/dashboards/index.scss +++ b/app/assets/stylesheets/dashboards/index.scss @@ -182,10 +182,10 @@ body[id="dashboards-index"] { color: #333333 !important; } - .highcharts-color-0 { - fill: #7cb5ec; - } - .highcharts-color-1 { - fill: #f9c00c; - } + // .highcharts-color-0 { + // fill: #7cb5ec; + // } + // .highcharts-color-1 { + // fill: #f9c00c; + // } } From 9f3fe916f57f1be14bb104761324c927923d5790 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 1 Feb 2022 11:24:37 +0700 Subject: [PATCH 16/94] Do no update assessment complete date if the assessment already completed --- app/models/assessment.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/assessment.rb b/app/models/assessment.rb index 4ee015beb7..fc2f91e55f 100644 --- a/app/models/assessment.rb +++ b/app/models/assessment.rb @@ -18,7 +18,8 @@ class Assessment < ActiveRecord::Base validate :allow_create, :eligible_client_age, if: :new_record? validates_uniqueness_of :case_conference_id, on: :create, if: :case_conference_id? - before_save :set_previous_score, :set_assessment_completed + before_save :set_previous_score + before_save :set_assessment_completed, unless: :completed? accepts_nested_attributes_for :assessment_domains From e1ad34a356948791e7c17c21e1026c189d7a5001 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 3 Feb 2022 14:55:58 +0700 Subject: [PATCH 17/94] 2021052712 fixed legal document update files on client form --- .../components/NewClientForm/legalDocument.js | 597 ++++++++++++------ 1 file changed, 397 insertions(+), 200 deletions(-) diff --git a/app/javascript/components/NewClientForm/legalDocument.js b/app/javascript/components/NewClientForm/legalDocument.js index c0c26834d4..ab4cc4c013 100644 --- a/app/javascript/components/NewClientForm/legalDocument.js +++ b/app/javascript/components/NewClientForm/legalDocument.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState } from "react"; import { SelectInput, DateInput, @@ -6,95 +6,124 @@ import { Checkbox, RadioGroup, FileUploadInput, - TextArea -} from '../Commons/inputs' + TextArea, +} from "../Commons/inputs"; -import { t } from '../../utils/i18n' +import { t } from "../../utils/i18n"; -export default props => { - const { onChange, fieldsVisibility, translation, data: { client, T } } = props +export default (props) => { + const { + onChange, + fieldsVisibility, + translation, + data: { client, T }, + } = props; - const [clientData, setClientData] = useState({...client}) + const [clientData, setClientData] = useState({ ...client }); const legalDocOptions = [ - { value: "Labour Trafficking", label: t(translation, 'clients.form.labor_trafficking_legal_doc_option') }, - { value: "Sexual Trafficking", label: t(translation, 'clients.form.sex_trafficking_legal_doc_option') }, - { value: "Other", label: t(translation, 'clients.form.other_legal_doc_option') } + { + value: "Labour Trafficking", + label: t(translation, "clients.form.labor_trafficking_legal_doc_option"), + }, + { + value: "Sexual Trafficking", + label: t(translation, "clients.form.sex_trafficking_legal_doc_option"), + }, + { + value: "Other", + label: t(translation, "clients.form.other_legal_doc_option"), + }, ]; - const onCheckBoxChange = (obj, field) => event => { - const inputType = ['date', 'select', 'checkbox', 'radio', 'file'] - const value = inputType.includes(event.type) ? event.data : event.target.value + const onCheckBoxChange = (obj, field) => (event) => { + const inputType = ["date", "select", "checkbox", "radio", "file"]; + const value = inputType.includes(event.type) + ? event.data + : event.target.value; - if (typeof field !== 'object'){ - client[field] = value - setClientData({...client}) + if (typeof field !== "object") { + client[field] = value; + setClientData({ ...client }); } + }; - } - - const onAttachmentsChange = (field) => fileItems => { - fileItems = fileItems.map(file => (file.file)); - onCheckBoxChange('client', field)({type: 'file', data: fileItems}); - } + const onAttachmentsChange = (field) => (fileItems) => { + fileItems = fileItems.map((file) => file.file); + onChange("client", field)({ type: "file", data: fileItems }); + }; const onRemoveAttachments = (field) => (data) => { - onChange('client', {[field]: data.data})({type: 'checkbox'}) - } + onChange("client", { [field]: data.data })({ type: "checkbox" }); + }; const onChangeLegalDocOption = (field) => (data) => { const value = data.data; - onChange('client', {[field]: value})({type: 'radio'}) - } + onChange("client", { [field]: value })({ type: "radio" }); + }; return (
-

{ t(translation, 'clients.form.legal_documents') }

+

{t(translation, "clients.form.legal_documents")}

-

{ t(translation, 'clients.form.indentification_doc') }

+

+ {t(translation, "clients.form.indentification_doc")} +

- { - fieldsVisibility.national_id == true && + {fieldsVisibility.national_id == true && (
- +
- } + )} - { - fieldsVisibility.passport == true && + {fieldsVisibility.passport == true && (
- +
{
- } + )} - { - fieldsVisibility.birth_cert == true && + {fieldsVisibility.birth_cert == true && (
- +
{
- } + )} - { - fieldsVisibility.family_book == true && + {fieldsVisibility.family_book == true && (
- +
- } + )}
-

{ t(translation, 'clients.form.temp_travel_doc') }

+

+ {t(translation, "clients.form.temp_travel_doc")} +

- { - fieldsVisibility.travel_doc == true && + {fieldsVisibility.travel_doc == true && (
- +
{
- } - { - fieldsVisibility.letter_from_immigration_police == true && + )} + {fieldsVisibility.letter_from_immigration_police == true && (
- +
- } + )}
-

{ t(translation, 'clients.form.referral_document') }

+

+ {t(translation, "clients.form.referral_document")} +

- { - fieldsVisibility.ngo_partner == true && + {fieldsVisibility.ngo_partner == true && (
- +
- } + )} - { - fieldsVisibility.mosavy == true && + {fieldsVisibility.mosavy == true && (
- +
- } + )} - { - fieldsVisibility.dosavy == true && + {fieldsVisibility.dosavy == true && (
- +
{
- } + )} - { - fieldsVisibility.msdhs == true && + {fieldsVisibility.msdhs == true && (
- +
{
- } + )}
-

{ t(translation, 'clients.form.legal_processing_doc') }

+

+ {t(translation, "clients.form.legal_processing_doc")} +

- { - fieldsVisibility.complain == true && + {fieldsVisibility.complain == true && (
- +
{
- } + )} - { - fieldsVisibility.local_consent == true && + {fieldsVisibility.local_consent == true && (
- +
- } + )} - { - fieldsVisibility.warrant == true && + {fieldsVisibility.warrant == true && (
- +
{
- } + )} - { - fieldsVisibility.verdict == true && + {fieldsVisibility.verdict == true && (
- +
{
- } + )}
-

{ t(translation, 'clients.form.form_indentification') }

+

+ {t(translation, "clients.form.form_indentification")} +

- { - fieldsVisibility.screening_interview_form == true && + {fieldsVisibility.screening_interview_form == true && (
- +
-
- { - client.screening_interview_form && +
+ {client.screening_interview_form && ( - } + )}
- } + )} - { - fieldsVisibility.short_form_of_ocdm == true && + {fieldsVisibility.short_form_of_ocdm == true && (
- +
-
- { - client.short_form_of_ocdm && +
+ {client.short_form_of_ocdm && ( - } + )}
- } + )} - { - fieldsVisibility.short_form_of_mosavy_dosavy == true && + {fieldsVisibility.short_form_of_mosavy_dosavy == true && (
- +
- { - client.short_form_of_mosavy_dosavy && + {client.short_form_of_mosavy_dosavy && ( - } + )}
- } + )} - { - fieldsVisibility.detail_form_of_mosavy_dosavy == true && + {fieldsVisibility.detail_form_of_mosavy_dosavy == true && (
- +
- { - client.detail_form_of_mosavy_dosavy && + {client.detail_form_of_mosavy_dosavy && ( - } + )}
- } + )} - { - fieldsVisibility.short_form_of_judicial_police == true && + {fieldsVisibility.short_form_of_judicial_police == true && (
- +
- { - client.short_form_of_judicial_police && + {client.short_form_of_judicial_police && ( - } + )}
- } + )} - { - fieldsVisibility.police_interview == true && + {fieldsVisibility.police_interview == true && (
- +
- { - client.detail_form_of_judicial_police && + {client.detail_form_of_judicial_police && ( - } + )}
- } + )} - { - fieldsVisibility.other_legal_doc == true && + {fieldsVisibility.other_legal_doc == true && (
- +
- } + )}
- ) -} + ); +}; From bb38a5a9a1c8c4a70a4a1ecf65dd13da8d521886 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 1 Feb 2022 11:02:14 +0700 Subject: [PATCH 18/94] fixed client column picker in report builder section --- app/assets/stylesheets/clients/client_grid.scss | 2 ++ app/classes/client_columns_visibility.rb | 4 ++-- app/grids/client_grid.rb | 8 ++++---- app/helpers/clients_helper.rb | 8 ++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/clients/client_grid.scss b/app/assets/stylesheets/clients/client_grid.scss index 2dd89a91ab..d7e8a65288 100644 --- a/app/assets/stylesheets/clients/client_grid.scss +++ b/app/assets/stylesheets/clients/client_grid.scss @@ -105,6 +105,8 @@ body[id="client_advanced_searches-index"] { th.case_note_type, th.plot, th.road, + th.user_id, + th.donor_name, th.postal_code, th.time_in_care, th.time_in_cps, diff --git a/app/classes/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index 68af7a068f..51b0135cbe 100644 --- a/app/classes/client_columns_visibility.rb +++ b/app/classes/client_columns_visibility.rb @@ -91,7 +91,7 @@ def columns_collection followed_up_by_id_: :followed_up_by, follow_up_date_: :follow_up_date, agencies_name_: :agency, - donors_name_: :donor, + donor_name_: :donor, province_id_: :province, current_address_: :current_address, house_number_: :house_number, @@ -113,7 +113,7 @@ def columns_collection has_been_in_orphanage_: :has_been_in_orphanage, has_been_in_government_care_: :has_been_in_government_care, relevant_referral_information_: :relevant_referral_information, - user_ids_: :user, + user_id_: :user, accepted_date_: :accepted_date, exit_date_: :exit_date, history_of_disability_and_or_illness_: :history_of_disability_and_or_illness, diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index c6c7aa1dbf..995a6006ab 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -163,7 +163,7 @@ def agencies_options filter(:created_by, :enum, select: :user_select_options, header: -> { I18n.t('datagrid.columns.clients.created_by') }) - filter(:user_ids, :enum, multiple: true, select: :case_worker_options, header: -> { I18n.t('datagrid.columns.clients.case_worker') }) do |ids, scope| + filter(:user_id, :enum, multiple: true, select: :case_worker_options, header: -> { I18n.t('datagrid.columns.clients.case_worker') }) do |ids, scope| ids = ids.map{ |id| id.to_i } if user_ids ||= User.where(id: ids).ids client_ids = Client.joins(:users).where(users: { id: user_ids }).ids.uniq @@ -181,7 +181,7 @@ def case_worker_options User.has_clients.map { |user| ["#{user.first_name} #{user.last_name}", user.id] } end - filter(:donors_name, :enum, select: :donor_select_options, header: -> { I18n.t('datagrid.columns.clients.donor') }) + filter(:donor_name, :enum, select: :donor_select_options, header: -> { I18n.t('datagrid.columns.clients.donor') }) def donor_select_options Donor.has_clients.map { |donor| [donor.name, donor.id] } @@ -965,11 +965,11 @@ def call_fields object.rated_for_id_poor end - column(:user, order: false, header: -> { I18n.t('datagrid.columns.clients.case_worker_or_staff') }) do |object| + column(:user_id, order: false, header: -> { I18n.t('datagrid.columns.clients.case_worker_or_staff') }) do |object| object.users.pluck(:first_name, :last_name).map{ |case_worker| "#{case_worker.first} #{case_worker.last}".squish }.join(', ') end - column(:donor, order: false, header: -> { I18n.t('datagrid.columns.clients.donor')}) do |object| + column(:donor_name, order: false, header: -> { I18n.t('datagrid.columns.clients.donor')}) do |object| object.donors.pluck(:name).join(', ') end diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 440c10c7a5..7e67ce3cf1 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -155,7 +155,7 @@ def label_translations referral_source_id: I18n.t('datagrid.columns.clients.referral_source'), follow_up_date: I18n.t('datagrid.columns.clients.follow_up_date'), agencies_name: I18n.t('datagrid.columns.clients.agencies_involved'), - donors_name: I18n.t('datagrid.columns.clients.donor'), + donor_name: I18n.t('datagrid.columns.clients.donor'), current_address: I18n.t('datagrid.columns.clients.current_address'), house_number: I18n.t('datagrid.columns.clients.house_number'), street_number: I18n.t('datagrid.columns.clients.street_number'), @@ -165,7 +165,7 @@ def label_translations has_been_in_orphanage: I18n.t('datagrid.columns.clients.has_been_in_orphanage'), has_been_in_government_care: I18n.t('datagrid.columns.clients.has_been_in_government_care'), relevant_referral_information: I18n.t('datagrid.columns.clients.relevant_referral_information'), - user_ids: I18n.t('datagrid.columns.clients.case_worker'), + user_id: I18n.t('datagrid.columns.clients.case_worker'), state: I18n.t('datagrid.columns.clients.state'), family_id: I18n.t('datagrid.columns.clients.family_id'), family: I18n.t('datagrid.columns.clients.family'), @@ -251,7 +251,7 @@ def lable_translation_uderscore followed_up_by_id_: I18n.t('datagrid.columns.clients.follow_up_by'), follow_up_date_: I18n.t('datagrid.columns.clients.follow_up_date'), agencies_name_: I18n.t('datagrid.columns.clients.agencies_involved'), - donors_name_: I18n.t('datagrid.columns.clients.donor'), + donor_name_: I18n.t('datagrid.columns.clients.donor'), current_address_: I18n.t('datagrid.columns.clients.current_address'), house_number_: I18n.t('datagrid.columns.clients.house_number'), street_number_: I18n.t('datagrid.columns.clients.street_number'), @@ -260,7 +260,7 @@ def lable_translation_uderscore has_been_in_orphanage_: I18n.t('datagrid.columns.clients.has_been_in_orphanage'), has_been_in_government_care_: I18n.t('datagrid.columns.clients.has_been_in_government_care'), relevant_referral_information_: I18n.t('datagrid.columns.clients.relevant_referral_information'), - user_ids_: I18n.t('datagrid.columns.clients.case_worker'), + user_id_: I18n.t('datagrid.columns.clients.case_worker'), state_: I18n.t('datagrid.columns.clients.state'), accepted_date_: I18n.t('datagrid.columns.clients.ngo_accepted_date'), exit_date_: I18n.t('datagrid.columns.clients.ngo_exit_date'), From fa4b1c00abf4d6528452c7929bf957760c8afb68 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 3 Feb 2022 16:11:42 +0700 Subject: [PATCH 19/94] show caseworker field on client basic filter --- app/assets/javascripts/clients/index.coffee | 2 +- app/grids/client_grid.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/clients/index.coffee b/app/assets/javascripts/clients/index.coffee index 451c45a251..81572ed8c4 100644 --- a/app/assets/javascripts/clients/index.coffee +++ b/app/assets/javascripts/clients/index.coffee @@ -401,7 +401,7 @@ CIF.ClientsIndex = do -> _hideClientFilters = -> dataFilters = $('#client-search-form .datagrid-filter') - displayColumns = '#client_grid_given_name, #client_grid_family_name, #client_grid_gender, #client_grid_slug, #client_grid_status, #client_grid_user_ids' + displayColumns = '#client_grid_given_name, #client_grid_family_name, #client_grid_gender, #client_grid_slug, #client_grid_status, #client_grid_user_id' $(dataFilters).hide() $(dataFilters).children("#{displayColumns}").parents('.datagrid-filter').show() diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 995a6006ab..d34997e3ae 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -965,7 +965,7 @@ def call_fields object.rated_for_id_poor end - column(:user_id, order: false, header: -> { I18n.t('datagrid.columns.clients.case_worker_or_staff') }) do |object| + column(:user_id, order: false, header: -> { I18n.t('datagrid.columns.clients.case_worker') }) do |object| object.users.pluck(:first_name, :last_name).map{ |case_worker| "#{case_worker.first} #{case_worker.last}".squish }.join(', ') end From 977197087c43425216bba84444ec01cc16b9bba6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 11 Feb 2022 20:10:15 +0700 Subject: [PATCH 20/94] create a script to generate active client who been enrolled in KC or FC --- lib/tasks/active_client_in_kc_fc.rake | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 lib/tasks/active_client_in_kc_fc.rake diff --git a/lib/tasks/active_client_in_kc_fc.rake b/lib/tasks/active_client_in_kc_fc.rake new file mode 100644 index 0000000000..3618ba3613 --- /dev/null +++ b/lib/tasks/active_client_in_kc_fc.rake @@ -0,0 +1,31 @@ +namespace :active_client_in_kc_fc do + desc "Generate active clients who been enrolled in Kinship Care and Foster Care" + task generate: :environment do + workbook = WriteXLSX.new("#{Rails.root}/active-clients-in-kc-fc-#{Date.today}.xlsx") + format = workbook.add_format + format.set_align('center') + client_worksheet = workbook.add_worksheet('Clients') + headers = ["NGO Name", "Client ID", "Status", "Gender", "Date of Birth", "Program Name", "Service Types", "Accepted Date", "Disabilities Yes/No", "Current Province"] + clients_data = [] + + Organization.visible.oscar.pluck(:short_name, :full_name).each do |short_name, full_name| + Apartment::Tenant.switch short_name + + quantitative_type_disability = QuantitativeType.pluck(:name).select{|custom_data| custom_data[/\/ History of disability and/i] }.first + + Client.active_status.joins(program_streams: :services).where(services: { name: ["Emergency foster care", "Long term foster care", "Kinship care"]}).each do |client| + services = client.program_streams.map do |program_stream| + "#{program_stream.name} => (#{program_stream.services.pluck(:name).uniq})" + end.join(', ') + + qtypes = client.quantitative_cases.group_by(&:quantitative_type).map{|quantitative, quantitative_cases| [quantitative.name, quantitative_cases.map(&:value).join(', ')] }.to_h + disability = qtypes[quantitative_type_disability] && qtypes[quantitative_type_disability][/child disabled/i].present? ? 'Yes' : 'No' + clients_data << ["#{full_name}(#{short_name})", client.slug, client.status, client.gender.capitalize, format_date(client.date_of_birth), client.program_streams.pluck(:name).join(", "), services, format_date(client.accepted_date), disability, client.province.try(:name)] + end + end + + write_data_to_spreadsheet2(client_worksheet, headers, clients_data, format) + workbook.close + end + +end \ No newline at end of file From 3380e41717d47b5d373f44a2ab2503fb44a2fad8 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 16 Feb 2022 10:44:07 +0700 Subject: [PATCH 21/94] worked on dashboard steps worked on dashboard active case by donor fixed client province/birth province table header fixed family step wizard error fixed missing referral source/Category in client table columns finished active case by donor pieChart fixed dashboard graph output fixed client province/birth province table header fixed family step wizard error fixed missing referral source/Category in client table columns fixed repeated query of FieldSetting. Fixed Assessment N+1 query in client_grid.rb set column width of custom assessment date to client table header fixed repeated query of FieldSetting. Fixed Assessment N+1 query in client_grid.rb set column width of custom assessment date to client table header fixed error on program stream column picker fixed repeated query of FieldSetting. Fixed Assessment N+1 query in client_grid.rb set column width of custom assessment date to client table header fixed error on program stream column picker fixed family count column in client table fixed repeated query of FieldSetting. Fixed Assessment N+1 query in client_grid.rb set column width of custom assessment date to client table header fixed error on program stream column picker fixed family count column in client table Dashboard V2 client tab without translation --- .../javascripts/dashboards/index.coffee | 125 +- app/assets/javascripts/families/form.coffee | 7 +- app/assets/javascripts/report_creator.coffee | 73 +- app/assets/stylesheets/application.scss | 1 + .../stylesheets/clients/client_grid.scss | 9 +- app/assets/stylesheets/dashboards/index.scss | 15 +- app/assets/stylesheets/home/index.scss | 2 + .../advanced_searches/client_fields.rb | 1 + .../communities/community_fields.rb | 1 + app/classes/advanced_searches/csi_fields.rb | 1 + .../custom_domain_score_fields.rb | 1 + .../advanced_searches/custom_fields.rb | 1 + .../advanced_searches/domain_score_fields.rb | 1 + .../advanced_searches/enrollment_fields.rb | 1 + .../advanced_searches/exit_program_fields.rb | 1 + .../families/family_fields.rb | 1 + .../advanced_searches/has_this_form_fields.rb | 1 + .../partners/partner_fields.rb | 1 + .../quantitative_case_fields.rb | 1 + app/classes/advanced_searches/rule_fields.rb | 1 + .../advanced_searches/school_grade_fields.rb | 1 + .../advanced_searches/tracking_fields.rb | 1 + app/classes/client_columns_visibility.rb | 10 +- app/controllers/api/clients_controller.rb | 30 + app/controllers/application_controller.rb | 9 +- app/controllers/dashboards_controller.rb | 6 + app/grids/client_grid.rb | 18 +- app/helpers/advanced_search_helper.rb | 28 +- app/helpers/clients_helper.rb | 28 +- .../_advanced_search.haml | 2 +- app/views/dashboards/_active_tasks_side.haml | 21 +- app/views/dashboards/_client.haml | 23 +- .../_client_program_stream_by_gender.haml | 3 +- app/views/dashboards/_data_agregation.haml | 24 + app/views/dashboards/_data_validation.haml | 16 + app/views/dashboards/_family.haml | 2 +- app/views/dashboards/_go_to_client.haml | 21 +- app/views/dashboards/_multiple_forms.haml | 85 +- app/views/dashboards/_report_builder.haml | 8 +- app/views/dashboards/_third_party.haml | 2 +- app/views/dashboards/index.html.haml | 23 +- config/routes.rb | 2 + .../stylesheets/highcharts/highcharts.css | 1013 +++++++++++++++++ 43 files changed, 1454 insertions(+), 167 deletions(-) create mode 100644 app/views/dashboards/_data_agregation.haml create mode 100644 app/views/dashboards/_data_validation.haml create mode 100644 vendor/assets/stylesheets/highcharts/highcharts.css diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index a0fe07b9ab..4bb1cd08ff 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -12,7 +12,7 @@ CIF.DashboardsIndex = do -> # _clientStatusChart() _familyType() _resizeChart() - _clientProgramStreamByGender() + # _clientProgramStreamByGender() _clientProgramStream() _initSelect2() _openTaskListModal() @@ -28,6 +28,7 @@ CIF.DashboardsIndex = do -> _loadModalReminder() _handleSearchClient() _handleMultiFormAssessmentCaseNote() + _loadSteps() _loadModalReminder = -> if localStorage.getItem('from login') == 'true' @@ -94,6 +95,18 @@ CIF.DashboardsIndex = do -> element = $('#client-program-stream') data = $(element).data('content-count') title = $(element).data('title') + data = + categories: data[0].active_data.map (element) -> + element['name'] + series: data.map (element, index) -> + { + name: element['name'] + data: data[index].active_data.map((subElement) -> + subElement['y'] + ) + color: if index % 2 == 0 then '#f9c00c' else '#4caf50' + } + report = new CIF.ReportCreator(data, title, '', element) report.barChart() @@ -337,4 +350,114 @@ CIF.DashboardsIndex = do -> $("ul#casenote-tab-dropdown a").addClass('disabled') + _loadSteps = (form) -> + bodyTag = 'div' + rootId = "#rootwizard" + that = @ + $(rootId).steps + headerTag: 'h4' + bodyTag: bodyTag + enableAllSteps: true + transitionEffect: 'slideLeft' + autoFocus: true + titleTemplate: 'Data #title#' + onInit: (event, currentIndex) -> + currentTab = "#{rootId}-p-#{currentIndex}" + _clientProgramStreamByGender() + return + + onStepChanging: (event, currentIndex, newIndex) -> + console.log 'onStepChanging' + currentTab = "#{rootId}-p-#{currentIndex}" + return true + + onStepChanged: (event, currentIndex, priorIndex) -> + console.log 'onStepChanged' + currentTab = "#{rootId}-p-#{currentIndex}" + currentStep = $("#{rootId}-p-" + currentIndex) + if $("#{currentTab} #active-client:visible").length + url = $("#active-client").data('url') + _active_client_by_gender(url) + else if $("#{currentTab} #active-case-by-donor:visible").length + url = $("#active-case-by-donor").data('url') + _active_case_by_donor(url) + + _active_client_by_gender = (url) -> + element = $('#active-client') + title = element.data('title') + $.ajax + type: 'get' + url: url + dataType: 'JSON' + success: (response) -> + data = + categories: [ + 'Children' + 'Adult' + 'Other' + ] + series: [ + { + name: 'Female' + data: [ + response.girls + response.adult_females + 0 + ] + color: '#f9c00c' + } + { + name: 'Male' + data: [ + response.boys + response.adult_males + 0 + ] + color: '#4caf50' + }, + { + name: 'Other' + data: [0, 0, response.others] + color: '#00695c' + } + ] + + report = new CIF.ReportCreator(data, title, '', element) + report.columnChart() + error: (response, status, msg) -> + return + + _active_case_by_donor = (url) -> + element = $('#active-case-by-donor') + title = element.data('title') + $.ajax + type: 'get' + url: url + dataType: 'JSON' + success: (response) -> + data = Object.keys(response).map (key) -> + container = undefined + container = {} + container.name = key + container.y = response[key] + container + + report = new CIF.ReportCreator(data, title, '', element) + report._highChartsPieChart() + error: (response, status, msg) -> + return + + # Make monochrome colors + _pieColors = do -> + colors = [] + base = Highcharts.getOptions().colors[2] + i = undefined + i = 0 + while i < 50 + # Start out with a darkened base color (negative brighten), and end + # up with a much brighter color + colors.push Highcharts.color(base).brighten((i - 5) / 7).get() + i += 1 + colors + { init: _init } diff --git a/app/assets/javascripts/families/form.coffee b/app/assets/javascripts/families/form.coffee index 5014a673be..5cb12466e1 100644 --- a/app/assets/javascripts/families/form.coffee +++ b/app/assets/javascripts/families/form.coffee @@ -17,13 +17,14 @@ CIF.FamiliesNew = CIF.FamiliesCreate = CIF.FamiliesEdit = CIF.FamiliesUpdate = d _validateForm = (currentIndex) -> valid = true - for select in $("select.required, input.required") + currentSection = "#family-wizard-form-p-#{currentIndex}" + for select in $("#{currentSection} select.required, #{currentSection} input.required") $(select).trigger("validate") if $(select).hasClass("error") || $(select).closest(".form-group").find(".select2-choice").hasClass("error") valid = false if(currentIndex == 2) - for select in $("select.required, input.required") + for select in $("#{currentSection} select.required, #{currentSection} input.required") $(select).trigger("validate") if $(select).hasClass("error") || $(select).closest(".form-group").find(".select2-choice").hasClass("error") valid = false @@ -66,6 +67,8 @@ CIF.FamiliesNew = CIF.FamiliesCreate = CIF.FamiliesEdit = CIF.FamiliesUpdate = d enableCancelButton: true labels: finish: 'Save' + onInit: (event, currentIndex) -> + _validateForm(currentIndex) onStepChanging: (event, currentIndex, newIndex) -> (currentIndex > newIndex) || _validateForm(currentIndex) onFinishing: (event, currentIndex) -> diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index f40e031cf5..04ee6245e2 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -4,12 +4,44 @@ class CIF.ReportCreator @title = title @yAxisTitle = yAxisTitle @element = element - @colors = ['f9c00c', '#4caf50', '#00695c', '#01579b', '#4dd0e1', '#2e7d32', '#4db6ac', '#00897b', '#a5d6a7', '#43a047', '#c5e1a5', '#7cb342', '#fdd835', '#fb8c00', '#6d4c41', '#757575', + @colors = ['#f9c00c', '#4caf50', '#00695c', '#01579b', '#4dd0e1', '#2e7d32', '#4db6ac', '#00897b', '#a5d6a7', '#43a047', '#c5e1a5', '#7cb342', '#fdd835', '#fb8c00', '#6d4c41', '#757575', '#ef9a9a', '#e53935', '#f48fb1', '#d81b60', '#ce93d8', '#8e24aa', '#b39ddb', '#7e57c2', '#9fa8da', '#3949ab', '#64b5f6', '#827717'] - barChart: -> + + columnChart: -> theData = @data + title = @title + subtitle = @yAxisTitle + if @data != undefined + $(@element).highcharts + colors: @colors + chart: + type: 'column' + styledMode: true + title: text: title + subtitle: text: subtitle + xAxis: + categories: theData.categories + crosshair: true + yAxis: + min: 0 + title: text: subtitle + tooltip: + headerFormat: '{point.key}' + pointFormat: '' + '' + footerFormat: '
{series.name}: {point.y}
' + shared: true + useHTML: true + plotOptions: column: + pointPadding: 0.05 + borderWidth: 0 + series: theData.series + + $('.highcharts-credits').css('display', 'none') + + barChart: -> if @data != undefined $(@element).highcharts + colors: @colors chart: type: 'bar' legend: @@ -27,8 +59,7 @@ class CIF.ReportCreator title: text: @title xAxis: [ - categories: @data[0].active_data.map (element) -> - element['name'] + categories: @data.categories dateTimeLabelFormats: month: '%b %Y' tickmarkPlacement: 'on' @@ -38,14 +69,9 @@ class CIF.ReportCreator title: text: @yAxisTitle ] - series: @data.map (element, index) -> - { - name: element['name'] - data: theData[index].active_data.map((subElement) -> - subElement['y'] - ) - } + series: @data.series $('.highcharts-credits').css('display', 'none') + lineChart: -> if @data != undefined $(@element).highcharts @@ -78,7 +104,6 @@ class CIF.ReportCreator self = @ [green, blue, africa, brown, yellow] = ["#59b260", "#5096c9", "#1c8781", "#B2912F", "#DECF3F"] $(@element).highcharts - colors: @colors chart: type: 'pie' height: 550 @@ -157,7 +182,6 @@ class CIF.ReportCreator pieChart: (options = {})-> self = @ $(@element).highcharts - colors: @colors chart: height: if _.isEmpty(options) then 380 else 500 backgroundColor: '#ecf0f1' @@ -201,6 +225,29 @@ class CIF.ReportCreator responsive: unless _.isEmpty(options) then self.resposivePieChart() $('.highcharts-credits').css('display', 'none') + _highChartsPieChart: (options = {}) -> + $(@element).highcharts + chart: + plotBackgroundColor: null + plotBorderWidth: null + plotShadow: false + type: 'pie' + title: text: @title + tooltip: pointFormat: '{series.name}: {point.y}' + accessibility: point: valueSuffix: '%' + plotOptions: pie: + allowPointSelect: true + cursor: 'pointer' + showInLegend: true + dataLabels: + enabled: true + format: '{point.name}
{point.y}' + series: [ { + name: @title + data: @data + } ] + $('.highcharts-credits').css('display', 'none') + resposivePieChart: -> self = @ rules: [ diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 06ddf84990..8afb193915 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -11,6 +11,7 @@ @import "bourbon"; @import "neat"; @import "fullcalendar"; +@import "highcharts/highcharts"; @import "animate/animate"; @import "iCheck/custom"; diff --git a/app/assets/stylesheets/clients/client_grid.scss b/app/assets/stylesheets/clients/client_grid.scss index d7e8a65288..17e1f3e497 100644 --- a/app/assets/stylesheets/clients/client_grid.scss +++ b/app/assets/stylesheets/clients/client_grid.scss @@ -45,6 +45,8 @@ body[id="client_advanced_searches-index"] { td.able, td.birth_province, td.province, + td.birth_province_id, + td.province_id, td.state, td.case_start_date, td.type_of_service, @@ -84,6 +86,7 @@ body[id="client_advanced_searches-index"] { th.date_of_assessments, th.assessment_completed_date, + th.custom_completed_date, th.completed_date { min-width: 200px; } @@ -187,10 +190,14 @@ body[id="client_advanced_searches-index"] { th.date_of_birth, th.school_name, th.referral_phone, - th.referral_source, th.birth_province, + th.birth_province_id, th.province, + th.province_id, + th.referral_source, + th.referral_source_id, th.referral_source_category, + th.referral_source_category_id, th.date_of_referral, th.rejected_note, th.user, diff --git a/app/assets/stylesheets/dashboards/index.scss b/app/assets/stylesheets/dashboards/index.scss index 5c40e01c7a..e5d4ddc549 100644 --- a/app/assets/stylesheets/dashboards/index.scss +++ b/app/assets/stylesheets/dashboards/index.scss @@ -182,12 +182,13 @@ body[id="dashboards-index"] { color: #333333 !important; } - .highcharts-color-0 { - fill: #7cb5ec; - stroke: #7cb5ec; - } - .highcharts-color-1 { - fill: #f9c00c; - stroke: #f0d16b; + #data-validation-panel { + .panel-body { + height: 300px; + } + h2 { + margin: 50px auto; + text-align: center; + } } } diff --git a/app/assets/stylesheets/home/index.scss b/app/assets/stylesheets/home/index.scss index 6215c7eaba..cae56005ee 100644 --- a/app/assets/stylesheets/home/index.scss +++ b/app/assets/stylesheets/home/index.scss @@ -58,6 +58,7 @@ .client-panel, .family-panel, .program-stream-panel, + .data-validation-panel, .tasks-panel, .go-client-panel, .widget-dashboard-panel { @@ -77,6 +78,7 @@ .widget-tasks-panel, .widget-program-panel, .widget-go-client-panel, + .data-validation-panel, .widget-dashboard-panel-min-height { border-bottom-left-radius: 0; border-bottom-right-radius: 0; diff --git a/app/classes/advanced_searches/client_fields.rb b/app/classes/advanced_searches/client_fields.rb index 4f4e6195dd..bec9cc6e0a 100644 --- a/app/classes/advanced_searches/client_fields.rb +++ b/app/classes/advanced_searches/client_fields.rb @@ -9,6 +9,7 @@ class ClientFields def initialize(options = {}) @user = options[:user] @pundit_user = options[:pundit_user] + address_translation end def render diff --git a/app/classes/advanced_searches/communities/community_fields.rb b/app/classes/advanced_searches/communities/community_fields.rb index 6e27ddc276..afd9b1d0c7 100644 --- a/app/classes/advanced_searches/communities/community_fields.rb +++ b/app/classes/advanced_searches/communities/community_fields.rb @@ -9,6 +9,7 @@ def initialize(options = {}) @user = options[:user] @pundit_user = options[:pundit_user] @called_in = options[:called_in] + address_translation end def render diff --git a/app/classes/advanced_searches/csi_fields.rb b/app/classes/advanced_searches/csi_fields.rb index e04289ee91..f349f7abe6 100644 --- a/app/classes/advanced_searches/csi_fields.rb +++ b/app/classes/advanced_searches/csi_fields.rb @@ -4,6 +4,7 @@ class CsiFields extend ApplicationHelper def self.render + address_translation csi_group = format_header('custom_csi_group') csi_domain_options = number_type_list.map { |item| number_filter_type(item, format_header(item), csi_group) } assessment_completed = [['assessment_completed', I18n.t('clients.index.assessment_completed', assessment: I18n.t('clients.show.assessment'))]].map{ |item| date_between_only_options(item[0], item[1], csi_group) } diff --git a/app/classes/advanced_searches/custom_domain_score_fields.rb b/app/classes/advanced_searches/custom_domain_score_fields.rb index 95ccfdbdd7..87c417aed9 100644 --- a/app/classes/advanced_searches/custom_domain_score_fields.rb +++ b/app/classes/advanced_searches/custom_domain_score_fields.rb @@ -3,6 +3,7 @@ class CustomDomainScoreFields extend AdvancedSearchHelper def self.render(domain_type = 'client') + address_translation domain_score_group = format_header('custom_csi_domain_scores') csi_domain_options = domain_options(domain_type).map { |item| number_filter_type(item, domain_score_format(item), domain_score_group) } assessment_completed_date = [['assessment_completed_date', I18n.t('datagrid.columns.assessment_completed_date', assessment: I18n.t('clients.show.assessment'))]].map{ |item| date_picker_options(item[0], item[1], domain_score_group) } diff --git a/app/classes/advanced_searches/custom_fields.rb b/app/classes/advanced_searches/custom_fields.rb index 6b2af30364..d9fe124078 100644 --- a/app/classes/advanced_searches/custom_fields.rb +++ b/app/classes/advanced_searches/custom_fields.rb @@ -11,6 +11,7 @@ def initialize(custom_form_ids, attach_with = 'Client') @attach_with = attach_with generate_field_by_type + address_translation end def render diff --git a/app/classes/advanced_searches/domain_score_fields.rb b/app/classes/advanced_searches/domain_score_fields.rb index bbceb19197..a6257356ba 100644 --- a/app/classes/advanced_searches/domain_score_fields.rb +++ b/app/classes/advanced_searches/domain_score_fields.rb @@ -3,6 +3,7 @@ class DomainScoreFields extend AdvancedSearchHelper def self.render + address_translation domain_score_group = format_header('csi_domain_scores') csi_domain_options = domain_options.map { |item| number_filter_type(item, domain_score_format(item), domain_score_group) } date_of_assessments = [['date_of_assessments', I18n.t('clients.index.date_of_assessment', assessment: I18n.t('clients.show.assessment'))]].map{ |item| date_picker_options(item[0], item[1], domain_score_group) } diff --git a/app/classes/advanced_searches/enrollment_fields.rb b/app/classes/advanced_searches/enrollment_fields.rb index 8ff0505207..10f489b285 100644 --- a/app/classes/advanced_searches/enrollment_fields.rb +++ b/app/classes/advanced_searches/enrollment_fields.rb @@ -13,6 +13,7 @@ def initialize(program_ids) @enrollment_data_list = [] generate_field_by_type + address_translation end def render diff --git a/app/classes/advanced_searches/exit_program_fields.rb b/app/classes/advanced_searches/exit_program_fields.rb index d17f03e30b..6625441af0 100644 --- a/app/classes/advanced_searches/exit_program_fields.rb +++ b/app/classes/advanced_searches/exit_program_fields.rb @@ -13,6 +13,7 @@ def initialize(program_ids) @exit_data_list = [] generate_field_by_type + address_translation end def render diff --git a/app/classes/advanced_searches/families/family_fields.rb b/app/classes/advanced_searches/families/family_fields.rb index a5aee2f4df..c75c2c0835 100644 --- a/app/classes/advanced_searches/families/family_fields.rb +++ b/app/classes/advanced_searches/families/family_fields.rb @@ -13,6 +13,7 @@ def initialize(options = {}) @pundit_user = options[:pundit_user] @called_in = options[:called_in] @current_setting = Setting.first + address_translation end def render diff --git a/app/classes/advanced_searches/has_this_form_fields.rb b/app/classes/advanced_searches/has_this_form_fields.rb index 0b1f871eb6..927092b7d5 100644 --- a/app/classes/advanced_searches/has_this_form_fields.rb +++ b/app/classes/advanced_searches/has_this_form_fields.rb @@ -8,6 +8,7 @@ def initialize(custom_form_ids, attach_with = 'Client') @drop_down_type_list = [] generate_field_by_type + address_translation end def render diff --git a/app/classes/advanced_searches/partners/partner_fields.rb b/app/classes/advanced_searches/partners/partner_fields.rb index 291cacc6d4..fc5cedba28 100644 --- a/app/classes/advanced_searches/partners/partner_fields.rb +++ b/app/classes/advanced_searches/partners/partner_fields.rb @@ -4,6 +4,7 @@ class PartnerFields include AdvancedSearchHelper def render + address_translation group = partner_header('basic_fields') number_fields = number_type_list.map { |item| AdvancedSearches::FilterTypes.number_options(item, partner_header(item), group) } text_fields = text_type_list.map { |item| AdvancedSearches::FilterTypes.text_options(item, partner_header(item), group) } diff --git a/app/classes/advanced_searches/quantitative_case_fields.rb b/app/classes/advanced_searches/quantitative_case_fields.rb index e23b87a201..9d01d757fc 100644 --- a/app/classes/advanced_searches/quantitative_case_fields.rb +++ b/app/classes/advanced_searches/quantitative_case_fields.rb @@ -6,6 +6,7 @@ class QuantitativeCaseFields def initialize(user, visible_on = 'client') @user = user @visible_on = visible_on + address_translation end def render diff --git a/app/classes/advanced_searches/rule_fields.rb b/app/classes/advanced_searches/rule_fields.rb index ad9ec419e6..5aefd2e5db 100644 --- a/app/classes/advanced_searches/rule_fields.rb +++ b/app/classes/advanced_searches/rule_fields.rb @@ -7,6 +7,7 @@ class RuleFields def initialize(options = {}) @user = options[:user] @called_in = options[:called_in] + address_translation end def render diff --git a/app/classes/advanced_searches/school_grade_fields.rb b/app/classes/advanced_searches/school_grade_fields.rb index f69adfc66c..a8e8336b47 100644 --- a/app/classes/advanced_searches/school_grade_fields.rb +++ b/app/classes/advanced_searches/school_grade_fields.rb @@ -3,6 +3,7 @@ class SchoolGradeFields extend AdvancedSearchHelper def self.render + address_translation school_grade = ['School Grade'].map { |item| drop_list_options('school_grade', format_header('school_grade'), school_grade_options, format_header('basic_fields')) } school_grade end diff --git a/app/classes/advanced_searches/tracking_fields.rb b/app/classes/advanced_searches/tracking_fields.rb index aa04167ad6..7f025af002 100644 --- a/app/classes/advanced_searches/tracking_fields.rb +++ b/app/classes/advanced_searches/tracking_fields.rb @@ -12,6 +12,7 @@ def initialize(program_ids) @drop_down_type_list ||= [] generate_field_by_type + address_translation end def render diff --git a/app/classes/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index 51b0135cbe..a1f947e80a 100644 --- a/app/classes/client_columns_visibility.rb +++ b/app/classes/client_columns_visibility.rb @@ -1,10 +1,12 @@ class ClientColumnsVisibility + include AdvancedSearchHelper include ClientsHelper include ActionView::Helpers::TranslationHelper def initialize(grid, params) @grid = grid @params = params + address_translation end def columns_collection @@ -83,16 +85,16 @@ def columns_collection date_of_birth_: :date_of_birth, status_: :status, **Client::HOTLINE_FIELDS.map{ |field| ["#{field}_".to_sym, field.to_sym] }.to_h, - birth_province_id_: :birth_province, + birth_province_id_: :birth_province_id, initial_referral_date_: :initial_referral_date, # referral_phone_: :referral_phone, received_by_id_: :received_by, - referral_source_id_: :referral_source, + referral_source_id_: :referral_source_id, followed_up_by_id_: :followed_up_by, follow_up_date_: :follow_up_date, agencies_name_: :agency, donor_name_: :donor, - province_id_: :province, + province_id_: :province_id, current_address_: :current_address, house_number_: :house_number, street_number_: :street_number, @@ -153,7 +155,7 @@ def columns_collection # time_in_care_: :time_in_care, time_in_ngo_: :time_in_ngo, time_in_cps_: :time_in_cps, - referral_source_category_id_: :referral_source_category, + referral_source_category_id_: :referral_source_category_id, type_of_service_: :type_of_service, referee_name_: :referee_name, referee_phone_: :referee_phone, diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index d4eea443f3..858bf8b5f0 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -81,6 +81,24 @@ def update end end + def render_client_by_gender + clients = Client.active_status + client_data = { + client_count: clients.count, + adult_females: adule_client_gender_count(clients, :female), + adult_males: adule_client_gender_count(clients, :male), + girls: under_18_client_gender_count(clients, :female), + boys: under_18_client_gender_count(clients, :male), + others: other_client_gender_count(clients) + } + render json: client_data + end + + def render_active_client_by_donor + donor_data = Donor.includes(:clients).references(:clients).group("donors.name").count("clients.id") + render json: donor_data + end + private def client_params @@ -276,5 +294,17 @@ def sort_column def sort_direction params[:order]['0']['dir'] == "desc" ? "desc" : "asc" end + + def adule_client_gender_count(clients, type = :male) + clients.public_send(type).where("(EXTRACT(year FROM age(current_date, clients.date_of_birth)) :: int) >= ?", 18).count + end + + def under_18_client_gender_count(clients, type = :male) + clients.public_send(type).where("(EXTRACT(year FROM age(current_date, clients.date_of_birth)) :: int) < ?", 18).count + end + + def other_client_gender_count(clients) + clients.where("gender IS NOT NULL AND (gender NOT IN ('male', 'female') OR date_of_birth IS NULL)").count + end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2c42531095..270c58247a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -7,7 +7,7 @@ class ApplicationController < ActionController::Base before_action :set_locale, :override_translation before_action :set_paper_trail_whodunnit, :current_setting before_action :prevent_routes - before_action :set_raven_context + before_action :set_raven_context, :address_translation rescue_from ActiveRecord::RecordNotFound do |exception| render file: "#{Rails.root}/app/views/errors/404", layout: false, status: :not_found @@ -46,6 +46,13 @@ def pundit_user UserContext.new(current_user, field_settings) end + protected + + def address_translation + @address_translation ||= view_context.address_translation + end + + private def configure_permitted_parameters diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index e012d2a867..3fd265a8d7 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -10,6 +10,12 @@ def index @select_client_options = Client.accessible_by(current_ability).active_accepted_status @custom_domains = Domain.custom_csi_domains @custom_assessment_settings = CustomAssessmentSetting.all.where(enable_custom_assessment: true) + sql = <<-SQL.squish + LEFT OUTER JOIN enter_ngos ON enter_ngos.client_id = clients.id + LEFT OUTER JOIN client_enrollments ON client_enrollments.client_id = clients.id + LEFT OUTER JOIN exit_ngos ON exit_ngos.client_id = clients.id + SQL + @date_validation_error_count = Client.joins(sql).where("(client_enrollments.enrollment_date < enter_ngos.accepted_date) OR (exit_ngos.exit_date < client_enrollments.enrollment_date)").distinct.count end def update_program_stream_service diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index d34997e3ae..edea97967d 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -9,7 +9,7 @@ class ClientGrid < BaseGrid COUNTRY_LANG = { "cambodia" => "(Khmer)", "thailand" => "(Thai)", "myanmar" => "(Burmese)", "lesotho" => "(Sesotho)", "uganda" => "(Swahili)" } scope do - Client + Client.includes(:assessments) end %w(given_name family_name local_given_name local_family_name).each do |field_name| @@ -735,11 +735,11 @@ def call_fields object.district_name end - column(:province, html: true, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| + column(:province_id, html: true, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| object.province_name end - column(:birth_province, html: true, header: -> { I18n.t('datagrid.columns.clients.birth_province') }) do |object| + column(:birth_province_id, html: true, header: -> { I18n.t('datagrid.columns.clients.birth_province') }) do |object| current_org = Organization.current Organization.switch_to 'shared' birth_province = SharedClient.find_by(slug: object.slug).birth_province_name @@ -758,7 +758,7 @@ def call_fields object.district.try(:name_kh) end - column(:province, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_kh') }) do |object| + column(:province_id, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_kh') }) do |object| identify_province_khmer = object.province&.name&.count "/" if identify_province_khmer == 1 province = object.province.name.split('/').first @@ -768,7 +768,7 @@ def call_fields end - column(:birth_province, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_kh') }) do |object| + column(:birth_province_id, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_kh') }) do |object| current_org = Organization.current Organization.switch_to 'shared' birth_province = SharedClient.find_by(slug: object.slug).birth_province_name @@ -795,7 +795,7 @@ def call_fields object.district.present? ? object.district.name.split(' / ').last : nil end - column(:province, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_en') }) do |object| + column(:province_id, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_en') }) do |object| identify_province = object.province&.name&.count "/" if identify_province == 1 province = object.province.name.split('/').last @@ -805,7 +805,7 @@ def call_fields end - column(:birth_province, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_en') }) do |object| + column(:birth_province_id, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_en') }) do |object| current_org = Organization.current Organization.switch_to 'shared' birth_province = SharedClient.find_by(slug: object.slug).birth_province_name @@ -916,10 +916,10 @@ def call_fields column(:relevant_referral_information, header: -> { I18n.t('datagrid.columns.clients.relevant_referral_information') }) - column(:referral_source, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source') }) do |object| + column(:referral_source_id, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source') }) do |object| object.referral_source.try(:name) end - column(:referral_source_category, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source_category') }) do |object| + column(:referral_source_category_id, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source_category') }) do |object| if I18n.locale == :km ReferralSource.find_by(id: object.referral_source_category_id).try(:name) else diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index 25ddcb37fa..7748ad70de 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -171,10 +171,10 @@ def format_header(key) active_clients: I18n.t('advanced_search.fields.active_clients'), care_plan: I18n.t('advanced_search.fields.care_plan'), **overdue_translations, - **address_translation + **@address_translation } - translations = label_translations.merge(translations) + translations = label_translations(@address_translation).merge(translations) translations[key.to_sym] || '' end @@ -201,7 +201,7 @@ def community_header(key) referral_source_id: I18n.t('activerecord.attributes.community.referral_source_id'), role: I18n.t('activerecord.attributes.community.role'), **community_member_columns, - **address_translation + **@address_translation } translations[key.to_sym] || '' end @@ -219,22 +219,22 @@ def partner_header(key) engagement: I18n.t('datagrid.columns.partners.engagement'), background: I18n.t('datagrid.columns.partners.background'), start_date: I18n.t('datagrid.columns.partners.start_date'), - **address_translation + **@address_translation } translations[key.to_sym] || '' end def address_translation - translations = {} + @address_translation ||= {} ['province', 'district', 'commune', 'village', 'birth_province', 'province_id', 'district_id', 'commune_id'].each do |key_translation| - translations[key_translation.to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") + @address_translation[key_translation.to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") end - translations['province_id'.to_sym] = FieldSetting.find_by(name: 'province_id').try(:label) || I18n.t('advanced_search.fields.province_id') - translations['district_id'.to_sym] = FieldSetting.find_by(name: 'district_id').try(:label) || I18n.t('datagrid.columns.clients.district') - translations['commune_id'.to_sym] = FieldSetting.find_by(name: 'commune_id').try(:label) || I18n.t('datagrid.columns.clients.commune') - translations['village_id'.to_sym] = FieldSetting.find_by(name: 'village_id').try(:label) || I18n.t('datagrid.columns.clients.village') - translations['birth_province_id'.to_sym] = FieldSetting.find_by(name: 'birth_province').try(:label) || I18n.t('datagrid.columns.clients.birth_province') - translations + @address_translation['province_id'.to_sym] = FieldSetting.find_by(name: 'province_id').try(:label) || I18n.t('advanced_search.fields.province_id') + @address_translation['district_id'.to_sym] = FieldSetting.find_by(name: 'district_id').try(:label) || I18n.t('datagrid.columns.clients.district') + @address_translation['commune_id'.to_sym] = FieldSetting.find_by(name: 'commune_id').try(:label) || I18n.t('datagrid.columns.clients.commune') + @address_translation['village_id'.to_sym] = FieldSetting.find_by(name: 'village_id').try(:label) || I18n.t('datagrid.columns.clients.village') + @address_translation['birth_province_id'.to_sym] = FieldSetting.find_by(name: 'birth_province').try(:label) || I18n.t('datagrid.columns.clients.birth_province') + @address_translation end def save_search_params(search_params) @@ -273,7 +273,7 @@ def user_select_options def concern_translation(hotline_field) if %W(concern_province_id concern_district_id concern_commune_id concern_village_id).include? hotline_field - address_translation[hotline_field.gsub('concern_', '').to_sym] + @address_translation[hotline_field.gsub('concern_', '').to_sym] else I18n.t("datagrid.columns.clients.#{hotline_field}") end @@ -305,7 +305,7 @@ def date_query(klass_name, objects, association, field_name) end def addresses_mapping(called_in) - if called_in == 'ProgramStreamAddRuleController' || self.class.name == "AdvancedSearches::Families::FamilyFields" + if called_in == 'ProgramStreamAddRuleController' || self.class.name == "AdvancedSearches::Families::FamilyFields" || self.class.name == "AdvancedSearches::Communities::CommunityFields" [['province_id', provinces], ['district_id', districts], ['commune_id', communes]] else [['province_id', provinces], ['district_id', districts], ['birth_province_id', birth_provinces], ['commune_id', communes], ['village_id', villages]] diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 7e67ce3cf1..1720bf2c7f 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -106,7 +106,7 @@ def report_options(title, yaxis_title) } end - def label_translations + def label_translations(address_translation = {}) labels = { legal_documents: I18n.t('clients.show.legal_documents'), passport_number: I18n.t('datagrid.columns.clients.passport_number'), @@ -192,9 +192,9 @@ def label_translations type_of_service: I18n.t('datagrid.columns.type_of_service'), hotline: I18n.t('datagrid.columns.calls.hotline'), **overdue_translations, - **client_address_translation, **Client::HOTLINE_FIELDS.map{ |field| [field.to_sym, I18n.t("datagrid.columns.clients.#{field}")] }.to_h, - **legal_doc_fields.map{|field| [field.to_sym, I18n.t("clients.show.#{field}")] }.to_h + **legal_doc_fields.map{|field| [field.to_sym, I18n.t("clients.show.#{field}")] }.to_h, + **@address_translation } lable_translation_uderscore.map{|k, v| [k.to_s.gsub(/(\_)$/, '').to_sym, v] }.to_h.merge(labels) @@ -309,8 +309,9 @@ def lable_translation_uderscore carer_phone_: I18n.t('activerecord.attributes.carer.phone'), carer_email_: I18n.t('activerecord.attributes.carer.email'), carer_relationship_to_client_: I18n.t('datagrid.columns.clients.carer_relationship_to_client'), - **overdue_translations.map{ |k, v| ["#{k}_".to_sym, v] }.to_h, - **client_address_translation + province_id_: FieldSetting.find_by(name: 'current_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province'), + birth_province_id_: FieldSetting.find_by(name: 'birth_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province'), + **overdue_translations.map{ |k, v| ["#{k}_".to_sym, v] }.to_h } end @@ -334,20 +335,6 @@ def overdue_translations } end - def client_address_translation - translations = {} - ['province','district','commune','village', 'birth_province_id'].each do |key_translation| - translations[key_translation.to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("datagrid.columns.clients.#{key_translation}") - translations["#{key_translation}_".to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("datagrid.columns.clients.#{key_translation}") - end - translations['village_id'.to_sym] = FieldSetting.find_by(name: 'village_id').try(:label) || I18n.t('datagrid.columns.clients.village') - translations['province_id'.to_sym] = FieldSetting.find_by(name: 'current_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province') - translations['province_id_'.to_sym] = FieldSetting.find_by(name: 'current_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province') - translations['birth_province_id'.to_sym] = FieldSetting.find_by(name: 'birth_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province') - translations['birth_province_id_'.to_sym] = FieldSetting.find_by(name: 'birth_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province') - translations - end - def local_name_label(name_type = :local_given_name) custom_field = FieldSetting.find_by(name: name_type) label = I18n.t("datagrid.columns.clients.#{name_type}") @@ -539,7 +526,6 @@ def country_address_field(client) end def default_columns_visibility(column) - label_column = label_translations.map{ |k, v| ["#{k}_".to_sym, v] }.to_h (Client::HOTLINE_FIELDS + Call::FIELDS).each do |field_name| @@ -1040,7 +1026,7 @@ def header_counter(grid, column) def family_counter return unless controller_name == 'clients' - count = @results.joins(:family).distinct.count(:family_id) + count = @results.joins("INNER JOIN families on families.id = clients.current_family_id").distinct.count("clients.current_family_id") content_tag(:span, count, class: 'label label-info') end diff --git a/app/views/clients/client_advanced_searches/_advanced_search.haml b/app/views/clients/client_advanced_searches/_advanced_search.haml index 4260a3b9da..b196309e52 100644 --- a/app/views/clients/client_advanced_searches/_advanced_search.haml +++ b/app/views/clients/client_advanced_searches/_advanced_search.haml @@ -26,7 +26,7 @@ %span.basic-filter-error %i Please Select Field to Filter - #search-action{ data: { action: params.dig(:client_advanced_search, :action_report_builder)} } + #search-action{ data: { action: params.dig(:builder) || params.dig(:client_advanced_search, :action_report_builder)} } .ibox-footer %button#search.btn.btn-primary= t('.search') diff --git a/app/views/dashboards/_active_tasks_side.haml b/app/views/dashboards/_active_tasks_side.haml index 1266ec37d5..ae2864e85f 100644 --- a/app/views/dashboards/_active_tasks_side.haml +++ b/app/views/dashboards/_active_tasks_side.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.tasks-panel - .panel-body - .widget.style1.widget-tasks-panel{ 'data-target' => '#active_tasks_list', 'data-toggle' => 'modal', type: 'button'} - .row.vertical-align - .col-xs-3 - %i.fa.fa-tasks.fa-3x - .col-xs-9.text-right - %span.text-row - = t('.active_tasks') +.panel.panel-default.tasks-panel + .panel-body + .widget.style1.widget-tasks-panel{ 'data-target' => '#active_tasks_list', 'data-toggle' => 'modal', type: 'button'} + .row.vertical-align + .col-xs-3 + %i.fa.fa-tasks.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.active_tasks') - = render 'active_tasks' += render 'active_tasks' diff --git a/app/views/dashboards/_client.haml b/app/views/dashboards/_client.haml index 5f18d05bea..23341e518a 100644 --- a/app/views/dashboards/_client.haml +++ b/app/views/dashboards/_client.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.client-panel - .panel-body - = link_to clients_path(active_client: true) do - .widget.style1.widget-client-panel - .row.vertical-align - .col-xs-3 - %i.fa.fa-child.fa-3x - .col-xs-9.active-client.text-right - %span.text-row - = t('.clients') - %h2.font-bold= @dashboard.clients.active_status.count +.panel.panel-default.client-panel + .panel-body + = link_to clients_path(active_client: true) do + .widget.style1.widget-client-panel + .row.vertical-align + .col-xs-3 + %i.fa.fa-child.fa-3x + .col-xs-9.active-client.text-right + %span.text-row + = t('.clients') + %h2.font-bold= @dashboard.clients.active_status.count diff --git a/app/views/dashboards/_client_program_stream_by_gender.haml b/app/views/dashboards/_client_program_stream_by_gender.haml index 15416a8dcc..4f51d18a8e 100644 --- a/app/views/dashboards/_client_program_stream_by_gender.haml +++ b/app/views/dashboards/_client_program_stream_by_gender.haml @@ -14,5 +14,4 @@ .row .col-xs-12 #client-by-program-stream{ 'data-content-count' => @dashboard.client_program_stream.to_json, 'data-title' => t('.client_active_in_programs')} - %hr/ - #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } + diff --git a/app/views/dashboards/_data_agregation.haml b/app/views/dashboards/_data_agregation.haml new file mode 100644 index 0000000000..04eb58f942 --- /dev/null +++ b/app/views/dashboards/_data_agregation.haml @@ -0,0 +1,24 @@ +.col-xs-12 + .panel.panel-default.program-stream-panel + .panel-body + = link_to program_streams_path do + .widget.style1.widget-program-panel + .row.vertical-align + .col-xs-5 + %i.fa.fa-bar-chart.fa-3x + .col-xs-7.text-right + %span.text-font + = t('.data_agregation') + + .row + .col-xs-12 + #rootwizard.m-t-sm.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } + %h4= t('.active_progarm_streams_by_gender') + %div{ class: "active-progarm-streams-by-gender" } + #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } + %h4= t('.active_client_by_gender') + %div{ class: "active-client-by-gender" } + #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } + %h4= t('.active_case_by_donor') + %div{ class: "active-case-by-donor" } + #active-case-by-donor{ 'data-url' => active_client_by_donor_api_clients_path, 'data-title' => t('.active_case_by_donor') } diff --git a/app/views/dashboards/_data_validation.haml b/app/views/dashboards/_data_validation.haml new file mode 100644 index 0000000000..54e70e01fb --- /dev/null +++ b/app/views/dashboards/_data_validation.haml @@ -0,0 +1,16 @@ +.col-xs-12 + .panel.panel-default#data-validation-panel.data-validation-panel + .panel-body + = link_to '#' do + .widget.style1.widget-program-panel + .row.vertical-align + .col-xs-5 + %i.fa.fa-exclamation-triangle.fa-3x + .col-xs-7.text-right + %span.text-font + = t('.data_validation') + .row + .col-xs-12 + %h2 + Number of clients having date logic error: + = @date_validation_error_count diff --git a/app/views/dashboards/_family.haml b/app/views/dashboards/_family.haml index 93ed4525ea..0b72147c64 100644 --- a/app/views/dashboards/_family.haml +++ b/app/views/dashboards/_family.haml @@ -1,4 +1,4 @@ -.col-xs-12.col-md-6 +.col-xs-12 .panel.panel-default.family-panel .panel-body = link_to families_path('family_grid[status]': 'Active') do diff --git a/app/views/dashboards/_go_to_client.haml b/app/views/dashboards/_go_to_client.haml index 6038294d03..cb57251b39 100644 --- a/app/views/dashboards/_go_to_client.haml +++ b/app/views/dashboards/_go_to_client.haml @@ -1,12 +1,11 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.go-client-panel - .panel-body - .widget.style1.widget-go-client-panel{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } - .row.vertical-align - .col-xs-3 - %i.fa.fa-user.fa-3x - .col-xs-9.text-right - %span.text-row - = t('.go_to_client') +.panel.panel-default.go-client-panel + .panel-body + .widget.style1.widget-go-client-panel{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } + .row.vertical-align + .col-xs-3 + %i.fa.fa-user.fa-3x + .col-xs-9.text-right + %span.text-row + = t('.go_to_client') - = render 'client_search' += render 'client_search' diff --git a/app/views/dashboards/_multiple_forms.haml b/app/views/dashboards/_multiple_forms.haml index 1f96d0c371..1618c5e7b8 100644 --- a/app/views/dashboards/_multiple_forms.haml +++ b/app/views/dashboards/_multiple_forms.haml @@ -1,44 +1,43 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.tasks-panel - .panel-body - .widget.style1.widget-tasks-panel{ 'data-target' => '#multiple-form-modal', 'data-toggle' => 'modal', type: 'button'} - .row.vertical-align - .col-xs-3 - %i.fa.fa-folder-open-o.fa-3x - .col-xs-9.text-right - %span.text-font - = t('.add_new_document') +.panel.panel-default.tasks-panel + .panel-body + .widget.style1.widget-tasks-panel{ 'data-target' => '#multiple-form-modal', 'data-toggle' => 'modal', type: 'button'} + .row.vertical-align + .col-xs-3 + %i.fa.fa-folder-open-o.fa-3x + .col-xs-9.text-right + %span.text-font + = t('.add_new_document') - #multiple-form-modal.modal.fade{ "aria-hidden" => "true", "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1", data: {backdrop: 'static', keyboard: 'false'} } - .modal-dialog#width-modal.modal-lg - .modal-content - .modal-body - %button.close{ 'data-dismiss': 'modal', type: 'button'} × - %div - %h2.text-center=t('.kind_of_document') - %ul.nav.nav-tabs{role: "tablist"} - %li.active{role: "presentation"} - %a{"data-toggle": "tab", href: "#record", role: "tab"} - = t('.record') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#custom-fields", role: "tab"} - = t('.form') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#assessment", role: "tab"} - = t('.assessment') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#case-note", role: "tab"} - = t('clients.form.case_note') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#program-enrollment", role: "tab"} - = t('.program_enrollment') - %li{role: "presentation"} - %a{"data-toggle": "tab", href: "#program-streams", role: "tab"} - = t('.tracking_forms') - .tab-content - = render 'record_tab' - = render 'assessment_tab' - = render 'case_note_tab' - = render 'program_enrollment_tab' - = render 'custom_fields_tab' - = render 'program_streams_tab' +#multiple-form-modal.modal.fade{ "aria-hidden" => "true", "aria-labelledby" => "myModalLabel", :role => "dialog", :tabindex => "-1", data: {backdrop: 'static', keyboard: 'false'} } + .modal-dialog#width-modal.modal-lg + .modal-content + .modal-body + %button.close{ 'data-dismiss': 'modal', type: 'button'} × + %div + %h2.text-center=t('.kind_of_document') + %ul.nav.nav-tabs{role: "tablist"} + %li.active{role: "presentation"} + %a{"data-toggle": "tab", href: "#record", role: "tab"} + = t('.record') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#custom-fields", role: "tab"} + = t('.form') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#assessment", role: "tab"} + = t('.assessment') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#case-note", role: "tab"} + = t('clients.form.case_note') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#program-enrollment", role: "tab"} + = t('.program_enrollment') + %li{role: "presentation"} + %a{"data-toggle": "tab", href: "#program-streams", role: "tab"} + = t('.tracking_forms') + .tab-content + = render 'record_tab' + = render 'assessment_tab' + = render 'case_note_tab' + = render 'program_enrollment_tab' + = render 'custom_fields_tab' + = render 'program_streams_tab' diff --git a/app/views/dashboards/_report_builder.haml b/app/views/dashboards/_report_builder.haml index 371a00d74c..8f6b3c3b84 100644 --- a/app/views/dashboards/_report_builder.haml +++ b/app/views/dashboards/_report_builder.haml @@ -1,7 +1,7 @@ -.col-xs-12.col-sm-3 - .panel.panel-default.widget-dashboard-panel - .panel-body - .widget.style1.widget-dashboard-panel-min-height{ 'data-target' => '#client-search', 'data-toggle' => 'modal', type: 'button' } +.panel.panel-default.widget-dashboard-panel + .panel-body + = link_to clients_path(builder: '#builder') do + .widget.style1.widget-dashboard-panel-min-height .row.vertical-align .col-xs-3 %i.fa.fa-file-text.fa-3x diff --git a/app/views/dashboards/_third_party.haml b/app/views/dashboards/_third_party.haml index 70877bc6b4..c98f3a7e1c 100644 --- a/app/views/dashboards/_third_party.haml +++ b/app/views/dashboards/_third_party.haml @@ -1,4 +1,4 @@ -.col-xs-12{ class: ('col-md-6' if !current_user.case_worker?) } +.col-xs-12 .panel.panel-default.bottom-panel .panel-body .row diff --git a/app/views/dashboards/index.html.haml b/app/views/dashboards/index.html.haml index 2eb24b74b8..73461592f9 100644 --- a/app/views/dashboards/index.html.haml +++ b/app/views/dashboards/index.html.haml @@ -17,21 +17,28 @@ - if @program_streams.present? = render 'dashboards/program_stream_services' .row - = render 'active_tasks_side' - = render 'multiple_forms' - = render 'client' - = render 'go_to_client' - = render 'report_builder' + .col-xs-12.col-sm-3 + = render 'multiple_forms' + .col-xs-12.col-sm-3 + = render 'client' + .col-xs-12.col-sm-2 + = render 'active_tasks_side' + .col-xs-12.col-sm-2 + = render 'go_to_client' + .col-xs-12.col-sm-2 + = render 'report_builder' .row = render 'client_program_stream_by_gender' + = render 'data_agregation' + .row + = render 'data_validation' .row - - if can? :read, Family - = render 'family' = render 'third_party' #family-tab.tab-pane{ role: 'tabpanel' } .row .col-xs-12 - %h3 Hello This is family panel + - if can? :read, Family + = render 'family' #community-tab.tab-pane{ role: 'tabpanel' } .row .col-xs-12 diff --git a/config/routes.rb b/config/routes.rb index 4a716f9645..3142b94206 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -284,6 +284,8 @@ get :find_client_case_worker, on: :member post :assessments, on: :collection get :search_client, on: :collection + get :render_client_by_gender, on: :collection, as: 'client_by_gender' + get :render_active_client_by_donor, on: :collection, as: 'active_client_by_donor' end resources :custom_fields do collection do diff --git a/vendor/assets/stylesheets/highcharts/highcharts.css b/vendor/assets/stylesheets/highcharts/highcharts.css new file mode 100644 index 0000000000..4c3089710d --- /dev/null +++ b/vendor/assets/stylesheets/highcharts/highcharts.css @@ -0,0 +1,1013 @@ +/** + * @license Highcharts + * + * (c) 2009-2016 Torstein Honsi + * + * License: www.highcharts.com/license + */ +.highcharts-container { + position: relative; + overflow: hidden; + width: 100%; + height: 100%; + text-align: left; + line-height: normal; + z-index: 0; + /* #1072 */ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif; + font-size: 12px; + user-select: none; + touch-action: manipulation; + outline: none; +} + +.highcharts-root { + display: block; +} + +.highcharts-root text { + stroke-width: 0; +} + +.highcharts-strong { + font-weight: bold; +} + +.highcharts-emphasized { + font-style: italic; +} + +.highcharts-anchor { + cursor: pointer; +} + +.highcharts-background { + fill: #ffffff; +} + +.highcharts-plot-border, .highcharts-plot-background { + fill: none; +} + +.highcharts-label-box { + fill: none; +} + +.highcharts-button-box { + fill: inherit; +} + +.highcharts-tracker-line { + stroke-linejoin: round; + stroke: rgba(192, 192, 192, 0.0001); + stroke-width: 22; + fill: none; +} + +.highcharts-tracker-area { + fill: rgba(192, 192, 192, 0.0001); + stroke-width: 0; +} + +/* Titles */ +.highcharts-title { + fill: #333333; + font-size: 1.5em; +} + +.highcharts-subtitle { + fill: #666666; + font-size: 1em; +} + +/* Axes */ +.highcharts-axis-line { + fill: none; + stroke: #ccd6eb; +} + +.highcharts-yaxis .highcharts-axis-line { + stroke-width: 0; +} + +.highcharts-axis-title { + fill: #666666; +} + +.highcharts-axis-labels { + fill: #666666; + cursor: default; + font-size: 0.9em; +} + +.highcharts-grid-line { + fill: none; + stroke: #e6e6e6; +} + +.highcharts-xaxis-grid .highcharts-grid-line { + stroke-width: 0px; +} + +.highcharts-tick { + stroke: #ccd6eb; +} + +.highcharts-yaxis .highcharts-tick { + stroke-width: 0; +} + +.highcharts-minor-grid-line { + stroke: #f2f2f2; +} + +.highcharts-crosshair-thin { + stroke-width: 1px; + stroke: #cccccc; +} + +.highcharts-crosshair-category { + stroke: #ccd6eb; + stroke-opacity: 0.25; +} + +/* Credits */ +.highcharts-credits { + cursor: pointer; + fill: #999999; + font-size: 0.7em; + transition: fill 250ms, font-size 250ms; +} + +.highcharts-credits:hover { + fill: black; + font-size: 1em; +} + +/* Tooltip */ +.highcharts-tooltip { + cursor: default; + pointer-events: none; + white-space: nowrap; + transition: stroke 150ms; +} + +.highcharts-tooltip text { + fill: #333333; +} + +.highcharts-tooltip .highcharts-header { + font-size: 0.85em; +} + +.highcharts-tooltip-box { + stroke-width: 1px; + fill: #f7f7f7; + fill-opacity: 0.85; +} + +.highcharts-tooltip-box .highcharts-label-box { + fill: #f7f7f7; + fill-opacity: 0.85; +} + +div.highcharts-tooltip { + filter: none; +} + +.highcharts-selection-marker { + fill: #335cad; + fill-opacity: 0.25; +} + +.highcharts-graph { + fill: none; + stroke-width: 2px; + stroke-linecap: round; + stroke-linejoin: round; +} + +.highcharts-empty-series { + stroke-width: 1px; + fill: none; + stroke: #cccccc; +} + +.highcharts-state-hover .highcharts-graph { + stroke-width: 3; +} + +.highcharts-point-inactive { + opacity: 0.2; + transition: opacity 50ms; + /* quick in */ +} + +.highcharts-series-inactive { + opacity: 0.2; + transition: opacity 50ms; + /* quick in */ +} + +.highcharts-state-hover path { + transition: stroke-width 50ms; + /* quick in */ +} + +.highcharts-state-normal path { + transition: stroke-width 250ms; + /* slow out */ +} + +/* Legend hover affects points and series */ +g.highcharts-series, +.highcharts-point, +.highcharts-markers, +.highcharts-data-labels { + transition: opacity 250ms; +} + +.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover), +.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover), +.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover), +.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) { + opacity: 0.2; +} + +/* Series options */ +/* Default colors */ +.highcharts-color-0 { + fill: #7cb5ec; + stroke: #7cb5ec; +} + +.highcharts-color-1 { + fill: #434348; + stroke: #434348; +} + +.highcharts-color-2 { + fill: #90ed7d; + stroke: #90ed7d; +} + +.highcharts-color-3 { + fill: #f7a35c; + stroke: #f7a35c; +} + +.highcharts-color-4 { + fill: #8085e9; + stroke: #8085e9; +} + +.highcharts-color-5 { + fill: #f15c80; + stroke: #f15c80; +} + +.highcharts-color-6 { + fill: #e4d354; + stroke: #e4d354; +} + +.highcharts-color-7 { + fill: #2b908f; + stroke: #2b908f; +} + +.highcharts-color-8 { + fill: #f45b5b; + stroke: #f45b5b; +} + +.highcharts-color-9 { + fill: #91e8e1; + stroke: #91e8e1; +} + +.highcharts-area { + fill-opacity: 0.75; + stroke-width: 0; +} + +.highcharts-markers { + stroke-width: 1px; + stroke: #ffffff; +} + +.highcharts-a11y-markers-hidden .highcharts-point:not(.highcharts-point-hover):not(.highcharts-a11y-marker-visible), +.highcharts-a11y-marker-hidden { + opacity: 0; +} + +.highcharts-point { + stroke-width: 1px; +} + +.highcharts-dense-data .highcharts-point { + stroke-width: 0; +} + +.highcharts-data-label { + font-size: 0.9em; + font-weight: bold; +} + +.highcharts-data-label-box { + fill: none; + stroke-width: 0; +} + +.highcharts-data-label text, text.highcharts-data-label { + fill: #333333; +} + +.highcharts-data-label-connector { + fill: none; +} + +.highcharts-data-label-hidden { + pointer-events: none; +} + +.highcharts-halo { + fill-opacity: 0.25; + stroke-width: 0; +} + +.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select, +.highcharts-markers .highcharts-point-select { + fill: #cccccc; + stroke: #000000; +} + +.highcharts-column-series rect.highcharts-point { + stroke: #ffffff; +} + +.highcharts-column-series .highcharts-point { + transition: fill-opacity 250ms; +} + +.highcharts-column-series .highcharts-point-hover { + fill-opacity: 0.75; + transition: fill-opacity 50ms; +} + +.highcharts-pie-series .highcharts-point { + stroke-linejoin: round; + stroke: #ffffff; +} + +.highcharts-pie-series .highcharts-point-hover { + fill-opacity: 0.75; + transition: fill-opacity 50ms; +} + +.highcharts-funnel-series .highcharts-point { + stroke-linejoin: round; + stroke: #ffffff; +} + +.highcharts-funnel-series .highcharts-point-hover { + fill-opacity: 0.75; + transition: fill-opacity 50ms; +} + +.highcharts-funnel-series .highcharts-point-select { + fill: inherit; + stroke: inherit; +} + +.highcharts-pyramid-series .highcharts-point { + stroke-linejoin: round; + stroke: #ffffff; +} + +.highcharts-pyramid-series .highcharts-point-hover { + fill-opacity: 0.75; + transition: fill-opacity 50ms; +} + +.highcharts-pyramid-series .highcharts-point-select { + fill: inherit; + stroke: inherit; +} + +.highcharts-solidgauge-series .highcharts-point { + stroke-width: 0; +} + +.highcharts-treemap-series .highcharts-point { + stroke-width: 1px; + stroke: #e6e6e6; + transition: stroke 250ms, fill 250ms, fill-opacity 250ms; +} + +.highcharts-treemap-series .highcharts-point-hover { + stroke: #999999; + transition: stroke 25ms, fill 25ms, fill-opacity 25ms; +} + +.highcharts-treemap-series .highcharts-above-level { + display: none; +} + +.highcharts-treemap-series .highcharts-internal-node { + fill: none; +} + +.highcharts-treemap-series .highcharts-internal-node-interactive { + fill-opacity: 0.15; + cursor: pointer; +} + +.highcharts-treemap-series .highcharts-internal-node-interactive:hover { + fill-opacity: 0.75; +} + +.highcharts-vector-series .highcharts-point { + fill: none; + stroke-width: 2px; +} + +.highcharts-windbarb-series .highcharts-point { + fill: none; + stroke-width: 2px; +} + +.highcharts-lollipop-stem { + stroke: #000000; +} + +.highcharts-focus-border { + fill: none; + stroke-width: 2px; +} + +.highcharts-legend-item-hidden .highcharts-focus-border { + fill: none !important; +} + +/* Legend */ +.highcharts-legend-box { + fill: none; + stroke-width: 0; +} + +.highcharts-legend-item > text { + fill: #333333; + font-weight: bold; + font-size: 1em; + cursor: pointer; + stroke-width: 0; +} + +.highcharts-legend-item:hover text { + fill: #000000; +} + +.highcharts-legend-item-hidden * { + fill: #cccccc !important; + stroke: #cccccc !important; + transition: fill 250ms; +} + +.highcharts-legend-nav-active { + fill: #003399; + cursor: pointer; +} + +.highcharts-legend-nav-inactive { + fill: #cccccc; +} + +circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive { + /* tracker */ + fill: rgba(192, 192, 192, 0.0001); +} + +.highcharts-legend-title-box { + fill: none; + stroke-width: 0; +} + +/* Bubble legend */ +.highcharts-bubble-legend-symbol { + stroke-width: 2; + fill-opacity: 0.5; +} + +.highcharts-bubble-legend-connectors { + stroke-width: 1; +} + +.highcharts-bubble-legend-labels { + fill: #333333; +} + +/* Loading */ +.highcharts-loading { + position: absolute; + background-color: #ffffff; + opacity: 0.5; + text-align: center; + z-index: 10; + transition: opacity 250ms; +} + +.highcharts-loading-hidden { + height: 0 !important; + opacity: 0; + overflow: hidden; + transition: opacity 250ms, height 250ms step-end; +} + +.highcharts-loading-inner { + font-weight: bold; + position: relative; + top: 45%; +} + +/* Plot bands and polar pane backgrounds */ +.highcharts-plot-band, .highcharts-pane { + fill: #000000; + fill-opacity: 0.05; +} + +.highcharts-plot-line { + fill: none; + stroke: #999999; + stroke-width: 1px; +} + +/* Highcharts More and modules */ +.highcharts-boxplot-box { + fill: #ffffff; +} + +.highcharts-boxplot-median { + stroke-width: 2px; +} + +.highcharts-bubble-series .highcharts-point { + fill-opacity: 0.5; +} + +.highcharts-errorbar-series .highcharts-point { + stroke: #000000; +} + +.highcharts-gauge-series .highcharts-data-label-box { + stroke: #cccccc; + stroke-width: 1px; +} + +.highcharts-gauge-series .highcharts-dial { + fill: #000000; + stroke-width: 0; +} + +.highcharts-polygon-series .highcharts-graph { + fill: inherit; + stroke-width: 0; +} + +.highcharts-waterfall-series .highcharts-graph { + stroke: #333333; + stroke-dasharray: 1, 3; +} + +.highcharts-sankey-series .highcharts-point { + stroke-width: 0; +} + +.highcharts-sankey-series .highcharts-link { + transition: fill 250ms, fill-opacity 250ms; + fill-opacity: 0.5; +} + +.highcharts-sankey-series .highcharts-point-hover.highcharts-link { + transition: fill 50ms, fill-opacity 50ms; + fill-opacity: 1; +} + +.highcharts-venn-series .highcharts-point { + fill-opacity: 0.75; + stroke: #cccccc; + transition: stroke 250ms, fill-opacity 250ms; +} + +.highcharts-venn-series .highcharts-point-hover { + fill-opacity: 1; + stroke: #cccccc; +} + +/* Highstock */ +.highcharts-navigator-mask-outside { + fill-opacity: 0; +} + +.highcharts-navigator-mask-inside { + fill: #6685c2; + /* navigator.maskFill option */ + fill-opacity: 0.25; + cursor: ew-resize; +} + +.highcharts-navigator-outline { + stroke: #cccccc; + fill: none; +} + +.highcharts-navigator-handle { + stroke: #cccccc; + fill: #f2f2f2; + cursor: ew-resize; +} + +.highcharts-navigator-series { + fill: #335cad; + stroke: #335cad; +} + +.highcharts-navigator-series .highcharts-graph { + stroke-width: 1px; +} + +.highcharts-navigator-series .highcharts-area { + fill-opacity: 0.05; +} + +.highcharts-navigator-xaxis .highcharts-axis-line { + stroke-width: 0; +} + +.highcharts-navigator-xaxis .highcharts-grid-line { + stroke-width: 1px; + stroke: #e6e6e6; +} + +.highcharts-navigator-xaxis.highcharts-axis-labels { + fill: #999999; +} + +.highcharts-navigator-yaxis .highcharts-grid-line { + stroke-width: 0; +} + +.highcharts-scrollbar-thumb { + fill: #cccccc; + stroke: #cccccc; + stroke-width: 1px; +} + +.highcharts-scrollbar-button { + fill: #e6e6e6; + stroke: #cccccc; + stroke-width: 1px; +} + +.highcharts-scrollbar-arrow { + fill: #666666; +} + +.highcharts-scrollbar-rifles { + stroke: #666666; + stroke-width: 1px; +} + +.highcharts-scrollbar-track { + fill: #f2f2f2; + stroke: #f2f2f2; + stroke-width: 1px; +} + +.highcharts-button { + fill: #f7f7f7; + stroke: #cccccc; + cursor: default; + stroke-width: 1px; + transition: fill 250ms; +} + +.highcharts-button text { + fill: #333333; +} + +.highcharts-button-hover { + transition: fill 0ms; + fill: #e6e6e6; + stroke: #cccccc; +} + +.highcharts-button-hover text { + fill: #333333; +} + +.highcharts-button-pressed { + font-weight: bold; + fill: #e6ebf5; + stroke: #cccccc; +} + +.highcharts-button-pressed text { + fill: #333333; + font-weight: bold; +} + +.highcharts-button-disabled text { + fill: #333333; +} + +.highcharts-range-selector-buttons .highcharts-button { + stroke-width: 0px; +} + +.highcharts-range-label rect { + fill: none; +} + +.highcharts-range-label text { + fill: #666666; +} + +.highcharts-range-input rect { + fill: none; +} + +.highcharts-range-input text { + fill: #333333; +} + +.highcharts-range-input { + stroke-width: 1px; + stroke: #cccccc; +} + +input.highcharts-range-selector { + position: absolute; + border: 0; + width: 1px; + /* Chrome needs a pixel to see it */ + height: 1px; + padding: 0; + text-align: center; + left: -9em; + /* #4798 */ +} + +.highcharts-crosshair-label text { + fill: #ffffff; + font-size: 1.1em; +} + +.highcharts-crosshair-label .highcharts-label-box { + fill: inherit; +} + +.highcharts-candlestick-series .highcharts-point { + stroke: #000000; + stroke-width: 1px; +} + +.highcharts-candlestick-series .highcharts-point-up { + fill: #ffffff; +} + +.highcharts-hollowcandlestick-series .highcharts-point-down { + fill: #f21313; + stroke: #f21313; +} + +.highcharts-hollowcandlestick-series .highcharts-point-down-bearish-up { + fill: #06b535; + stroke: #06b535; +} + +.highcharts-hollowcandlestick-series .highcharts-point-up { + fill: transparent; + stroke: #06b535; +} + +.highcharts-ohlc-series .highcharts-point-hover { + stroke-width: 3px; +} + +.highcharts-flags-series .highcharts-point .highcharts-label-box { + stroke: #999999; + fill: #ffffff; + transition: fill 250ms; +} + +.highcharts-flags-series .highcharts-point-hover .highcharts-label-box { + stroke: #000000; + fill: #ccd6eb; +} + +.highcharts-flags-series .highcharts-point text { + fill: #000000; + font-size: 0.9em; + font-weight: bold; +} + +/* Highmaps */ +.highcharts-map-series .highcharts-point { + transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms; + stroke: #cccccc; +} + +.highcharts-map-series .highcharts-point-hover { + transition: fill 0ms, fill-opacity 0ms; + fill-opacity: 0.5; + stroke-width: 2px; +} + +.highcharts-mapline-series .highcharts-point { + fill: none; +} + +.highcharts-heatmap-series .highcharts-point { + stroke-width: 0; +} + +.highcharts-map-navigation { + font-size: 1.3em; + font-weight: bold; + text-align: center; +} + +.highcharts-coloraxis { + stroke-width: 0; +} + +.highcharts-coloraxis-marker { + fill: #999999; +} + +.highcharts-null-point { + fill: #f7f7f7; +} + +/* 3d charts */ +.highcharts-3d-frame { + fill: transparent; +} + +/* Exporting module */ +.highcharts-contextbutton { + fill: #ffffff; + /* needed to capture hover */ + stroke: none; + stroke-linecap: round; +} + +.highcharts-contextbutton:hover { + fill: #e6e6e6; + stroke: #e6e6e6; +} + +.highcharts-button-symbol { + stroke: #666666; + stroke-width: 3px; +} + +.highcharts-menu { + border: 1px solid #999999; + background: #ffffff; + padding: 5px 0; + box-shadow: 3px 3px 10px #888; +} + +.highcharts-menu-item { + padding: 0.5em 1em; + background: none; + color: #333333; + cursor: pointer; + transition: background 250ms, color 250ms; +} + +.highcharts-menu-item:hover { + background: #335cad; + color: #ffffff; +} + +/* Drilldown module */ +.highcharts-drilldown-point { + cursor: pointer; +} + +.highcharts-drilldown-data-label text, +text.highcharts-drilldown-data-label, +.highcharts-drilldown-axis-label { + cursor: pointer; + fill: #003399; + font-weight: bold; + text-decoration: underline; +} + +/* No-data module */ +.highcharts-no-data text { + font-weight: bold; + font-size: 12px; + fill: #666666; +} + +/* Drag-panes module */ +.highcharts-axis-resizer { + cursor: ns-resize; + stroke: black; + stroke-width: 2px; +} + +/* Bullet type series */ +.highcharts-bullet-target { + stroke-width: 0; +} + +/* Lineargauge type series */ +.highcharts-lineargauge-target { + stroke-width: 1px; + stroke: #333333; +} + +.highcharts-lineargauge-target-line { + stroke-width: 1px; + stroke: #333333; +} + +/* Annotations module */ +.highcharts-annotation-label-box { + stroke-width: 1px; + stroke: #000000; + fill: #000000; + fill-opacity: 0.75; +} + +.highcharts-annotation-label text { + fill: #e6e6e6; +} + +/* A11y module */ +.highcharts-a11y-proxy-button { + border-width: 0; + background-color: transparent; + cursor: pointer; + outline: none; + opacity: 0.001; + z-index: 999; + overflow: hidden; + padding: 0; + margin: 0; + display: block; + position: absolute; +} + +.highcharts-a11y-proxy-group li { + list-style: none; +} + +.highcharts-visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + white-space: nowrap; + clip: rect(1px, 1px, 1px, 1px); + margin-top: -3px; + opacity: 0.01; +} + +.highcharts-a11y-invisible { + visibility: hidden; +} + +.highcharts-a11y-proxy-container, +.highcharts-a11y-proxy-container-before, +.highcharts-a11y-proxy-container-after { + position: absolute; + white-space: nowrap; +} + +g.highcharts-series, .highcharts-markers, .highcharts-point { + outline: none; +} + +/* Gantt */ +.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded { + cursor: pointer; +} + +.highcharts-point-connecting-path { + fill: none; +} + +.highcharts-grid-axis .highcharts-tick { + stroke-width: 1px; +} + +.highcharts-grid-axis .highcharts-axis-line { + stroke-width: 1px; +} \ No newline at end of file From da95da5222fd45d4cf67188a492ddc94480c486b Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Feb 2022 12:17:58 +0700 Subject: [PATCH 22/94] worked on client data logic error --- .../javascripts/dashboards/index.coffee | 31 +++++++++++-------- app/assets/javascripts/report_creator.coffee | 4 ++- app/classes/dashboard.rb | 25 ++++++++++----- app/controllers/api/clients_controller.rb | 24 ++++++++++++-- app/controllers/clients_controller.rb | 5 +-- .../concerns/client_grid_options.rb | 2 +- app/controllers/dashboards_controller.rb | 3 +- app/grids/client_grid.rb | 2 +- app/policies/client_advanced_search_policy.rb | 2 +- ...agregation.haml => _data_agregations.haml} | 10 +++--- app/views/dashboards/_data_validation.haml | 16 ---------- app/views/dashboards/_data_validations.haml | 20 ++++++++++++ app/views/dashboards/_report_builder.haml | 2 +- app/views/dashboards/index.html.haml | 4 +-- config/locales/en.yml | 15 +++++++++ config/locales/km.yml | 14 +++++++-- 16 files changed, 123 insertions(+), 56 deletions(-) rename app/views/dashboards/{_data_agregation.haml => _data_agregations.haml} (67%) delete mode 100644 app/views/dashboards/_data_validation.haml create mode 100644 app/views/dashboards/_data_validations.haml diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index 4bb1cd08ff..acd127bac9 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -29,6 +29,7 @@ CIF.DashboardsIndex = do -> _handleSearchClient() _handleMultiFormAssessmentCaseNote() _loadSteps() + _search_client_date_logic_error() _loadModalReminder = -> if localStorage.getItem('from login') == 'true' @@ -104,7 +105,7 @@ CIF.DashboardsIndex = do -> data: data[index].active_data.map((subElement) -> subElement['y'] ) - color: if index % 2 == 0 then '#f9c00c' else '#4caf50' + color: if index == 0 then '#f9c00c' else if index == 1 then '#4caf50' else '#00695c' } report = new CIF.ReportCreator(data, title, '', element) @@ -360,7 +361,11 @@ CIF.DashboardsIndex = do -> enableAllSteps: true transitionEffect: 'slideLeft' autoFocus: true - titleTemplate: 'Data #title#' + titleTemplate: '#title#' + labels: + finish: $(rootId).data('finish') + next: $(rootId).data('next') + previous: $(rootId).data('previous') onInit: (event, currentIndex) -> currentTab = "#{rootId}-p-#{currentIndex}" _clientProgramStreamByGender() @@ -384,6 +389,9 @@ CIF.DashboardsIndex = do -> _active_client_by_gender = (url) -> element = $('#active-client') + male = $("#rootwizard").data('male') + female = $("#rootwizard").data('female') + other = $("#rootwizard").data('other') title = element.data('title') $.ajax type: 'get' @@ -398,7 +406,7 @@ CIF.DashboardsIndex = do -> ] series: [ { - name: 'Female' + name: female data: [ response.girls response.adult_females @@ -407,7 +415,7 @@ CIF.DashboardsIndex = do -> color: '#f9c00c' } { - name: 'Male' + name: male data: [ response.boys response.adult_males @@ -416,7 +424,7 @@ CIF.DashboardsIndex = do -> color: '#4caf50' }, { - name: 'Other' + name: other data: [0, 0, response.others] color: '#00695c' } @@ -435,14 +443,7 @@ CIF.DashboardsIndex = do -> url: url dataType: 'JSON' success: (response) -> - data = Object.keys(response).map (key) -> - container = undefined - container = {} - container.name = key - container.y = response[key] - container - - report = new CIF.ReportCreator(data, title, '', element) + report = new CIF.ReportCreator(response.data, title, '', element) report._highChartsPieChart() error: (response, status, msg) -> return @@ -460,4 +461,8 @@ CIF.DashboardsIndex = do -> i += 1 colors + _search_client_date_logic_error = -> + $('a[href="#data-validation"]').on 'click', -> + $('#client-date-logic-error').submit() + { init: _init } diff --git a/app/assets/javascripts/report_creator.coffee b/app/assets/javascripts/report_creator.coffee index 04ee6245e2..d5176d2be5 100644 --- a/app/assets/javascripts/report_creator.coffee +++ b/app/assets/javascripts/report_creator.coffee @@ -213,7 +213,7 @@ class CIF.ReportCreator cursor: 'pointer' showInLegend: true point: events: click: -> - location.href = @options.url + window.open(@options.url, '_blank') series: [ { dataLabels: distance: if _.isEmpty(options) then -30 else 30 @@ -239,6 +239,8 @@ class CIF.ReportCreator allowPointSelect: true cursor: 'pointer' showInLegend: true + point: events: click: -> + window.open(@options.url, '_blank') dataLabels: enabled: true format: '{point.name}
{point.y}' diff --git a/app/classes/dashboard.rb b/app/classes/dashboard.rb index 949fc70b3d..c69fa111c6 100644 --- a/app/classes/dashboard.rb +++ b/app/classes/dashboard.rb @@ -46,18 +46,29 @@ def family_type_statistic def program_stream_report_gender active_enrollments = Client.joins(:client_enrollments).where(client_enrollments: { status: 'Active' }) - males = active_enrollments.where(clients: { gender: 'male' } ).uniq - females = active_enrollments.where(clients: { gender: 'female' } ).uniq + males = active_enrollments.where(clients: { gender: 'male' } ).distinct + females = active_enrollments.where(clients: { gender: 'female' } ).distinct + others = active_enrollments.where("clients.gender ~ '^(lgbt|unknown|prefer_not_to_say|other)' OR clients.gender = ''").distinct + + male = I18n.t('gender_list.male') + female = I18n.t('gender_list.female') + other = I18n.t('gender_list.other_gender') + [ { - name: I18n.t('classes.dashboard.males'), + name: male, y: males.size, - active_data: program_stream_report_by(males.ids, 'Male') + active_data: program_stream_report_by(males.ids, male) }, { - name: I18n.t('classes.dashboard.females'), + name: female, y: females.size, - active_data: program_stream_report_by(females.ids, 'Female') + active_data: program_stream_report_by(females.ids, female) + }, + { + name: other, + y: others.size, + active_data: program_stream_report_by(others.ids, other) } ] end @@ -127,7 +138,7 @@ def program_stream_report_by(client_ids, gender) url = { 'condition': 'AND', 'rules': [{ 'id': 'active_program_stream', 'field': 'active_program_stream', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': p.id }, { 'id': 'gender', 'field': 'gender', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': gender.downcase } ]} { - name: "#{p.name} (#{gender})", + name: "#{p.name}", y: p.client_enrollment_count, url: clients_path( client_advanced_search: { diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index 858bf8b5f0..e2df852152 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -82,7 +82,7 @@ def update end def render_client_by_gender - clients = Client.active_status + clients = Client.accessible_by(current_ability).active_status client_data = { client_count: clients.count, adult_females: adule_client_gender_count(clients, :female), @@ -95,8 +95,26 @@ def render_client_by_gender end def render_active_client_by_donor - donor_data = Donor.includes(:clients).references(:clients).group("donors.name").count("clients.id") - render json: donor_data + data = Donor.includes(:clients).references(:clients).where(clients: { id: Client.accessible_by(current_ability).active_status.ids }).group("donors.name").count("clients.id") + donors = Donor.pluck(:name, :id) + donor_data = data.map do |donor_name, client_count| + url = { "condition"=>"AND", "rules"=> [ + {"id"=>"status", "field"=>"Status", "type"=>"string", "input"=>"select", "operator"=>"equal", "value"=>"Active", "data"=>{"values"=>[{"Accepted"=>"Accepted"}, {"Active"=>"Active"}, {"Exited"=>"Exited"}, {"Referred"=>"Referred"}], "isAssociation"=>false }}, + {"id"=>"donor_name", "field"=>"Donor", "type"=>"string", "input"=>"select", "operator"=>"equal", "value"=> donors.to_h[donor_name], "data"=> { "values"=> donors.reverse.to_h, "isAssociation"=> true }, "valid"=>true } + ]} + + { + name: donor_name, + y: client_count, + url: clients_path( + 'client_advanced_search': { + action_report_builder: '#builder', + basic_rules: url.to_json + } + ) + } + end + render json: { data: donor_data } end private diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 3c7d46ca5a..9138c1aeb3 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -34,10 +34,11 @@ def index respond_to do |f| f.html do next unless params['commit'].present? - client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability) } + # @client_grid is invoked from ClientGridOptions#choose_grid + client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).where(id: params[:client_ids]&.split || []) } @results = client_grid.assets $client_data = @clients - @client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).order(:id).page(params[:page]).per(20) } + @client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).where(id: params[:client_ids]&.split || []).order(:id).page(params[:page]).per(20) } end f.xls do next unless params['commit'].present? diff --git a/app/controllers/concerns/client_grid_options.rb b/app/controllers/concerns/client_grid_options.rb index 5249f84cf9..b9322ff4dc 100644 --- a/app/controllers/concerns/client_grid_options.rb +++ b/app/controllers/concerns/client_grid_options.rb @@ -14,6 +14,7 @@ def choose_grid end def columns_visibility + return if params['commit'].blank? if params[:advanced_search_id] advanced_search = AdvancedSearch.find(params[:advanced_search_id]) @client_columns ||= ClientColumnsVisibility.new(@client_grid, params.merge(advanced_search.field_visible).merge(column_form_builder: column_form_builder)) @@ -457,7 +458,6 @@ def form_builder_report def admin_client_grid data = params[:data].presence - if params.dig(:client_grid, :quantitative_types).present? quantitative_types = params[:client_grid][:quantitative_types] @client_grid = ClientGrid.new(params.fetch(:client_grid, {}).merge!(current_user: current_user, qType: quantitative_types, dynamic_columns: column_form_builder, param_data: data)) diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index 3fd265a8d7..013a56b654 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -15,7 +15,8 @@ def index LEFT OUTER JOIN client_enrollments ON client_enrollments.client_id = clients.id LEFT OUTER JOIN exit_ngos ON exit_ngos.client_id = clients.id SQL - @date_validation_error_count = Client.joins(sql).where("(client_enrollments.enrollment_date < enter_ngos.accepted_date) OR (exit_ngos.exit_date < client_enrollments.enrollment_date)").distinct.count + clients_error = Client.accessible_by(current_ability).joins(sql).where("(client_enrollments.enrollment_date < enter_ngos.accepted_date) OR (exit_ngos.exit_date < client_enrollments.enrollment_date)").distinct + @date_validation_error = { ids: clients_error.ids, count: clients_error.count } end def update_program_stream_service diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index edea97967d..fb7998f16a 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -5,7 +5,7 @@ class ClientGrid < BaseGrid include FormBuilderHelper include AssessmentHelper - attr_accessor :current_user, :qType, :dynamic_columns, :param_data + attr_accessor :current_user, :qType, :dynamic_columns, :param_data, :client_ids COUNTRY_LANG = { "cambodia" => "(Khmer)", "thailand" => "(Thai)", "myanmar" => "(Burmese)", "lesotho" => "(Sesotho)", "uganda" => "(Swahili)" } scope do diff --git a/app/policies/client_advanced_search_policy.rb b/app/policies/client_advanced_search_policy.rb index d9c63a0e97..edeb8ef314 100644 --- a/app/policies/client_advanced_search_policy.rb +++ b/app/policies/client_advanced_search_policy.rb @@ -1,4 +1,4 @@ -class ClientAdvancedSearchPolicy < ApplicationPolicy + class ClientAdvancedSearchPolicy < ApplicationPolicy def index? user.admin? || user.strategic_overviewer? || user.hotline_officer? end diff --git a/app/views/dashboards/_data_agregation.haml b/app/views/dashboards/_data_agregations.haml similarity index 67% rename from app/views/dashboards/_data_agregation.haml rename to app/views/dashboards/_data_agregations.haml index 04eb58f942..09c6590ef6 100644 --- a/app/views/dashboards/_data_agregation.haml +++ b/app/views/dashboards/_data_agregations.haml @@ -1,7 +1,7 @@ .col-xs-12 .panel.panel-default.program-stream-panel - .panel-body - = link_to program_streams_path do + .panel-body#data-agregation + = link_to '#data-agregation' do .widget.style1.widget-program-panel .row.vertical-align .col-xs-5 @@ -12,13 +12,13 @@ .row .col-xs-12 - #rootwizard.m-t-sm.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } + #rootwizard.m-t-sm.root-wizard{ data: { next: t('views.pagination.next'), previous: t('views.pagination.previous'), finish: t('views.pagination.last'), action: params['action'], male: t('gender_list.male'), female: t('gender_list.female'), other: t('gender_list.other_gender') } } %h4= t('.active_progarm_streams_by_gender') %div{ class: "active-progarm-streams-by-gender" } #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } - %h4= t('.active_client_by_gender') + %h4= t('.active_case_by_gender') %div{ class: "active-client-by-gender" } - #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } + #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_case_by_gender') } %h4= t('.active_case_by_donor') %div{ class: "active-case-by-donor" } #active-case-by-donor{ 'data-url' => active_client_by_donor_api_clients_path, 'data-title' => t('.active_case_by_donor') } diff --git a/app/views/dashboards/_data_validation.haml b/app/views/dashboards/_data_validation.haml deleted file mode 100644 index 54e70e01fb..0000000000 --- a/app/views/dashboards/_data_validation.haml +++ /dev/null @@ -1,16 +0,0 @@ -.col-xs-12 - .panel.panel-default#data-validation-panel.data-validation-panel - .panel-body - = link_to '#' do - .widget.style1.widget-program-panel - .row.vertical-align - .col-xs-5 - %i.fa.fa-exclamation-triangle.fa-3x - .col-xs-7.text-right - %span.text-font - = t('.data_validation') - .row - .col-xs-12 - %h2 - Number of clients having date logic error: - = @date_validation_error_count diff --git a/app/views/dashboards/_data_validations.haml b/app/views/dashboards/_data_validations.haml new file mode 100644 index 0000000000..d0cee09ce1 --- /dev/null +++ b/app/views/dashboards/_data_validations.haml @@ -0,0 +1,20 @@ +.col-xs-12 + .panel.panel-default#data-validation-panel.data-validation-panel + .panel-body#data-validation + = link_to '#data-validation' do + .widget.style1.widget-program-panel + .row.vertical-align + .col-xs-5 + %i.fa.fa-exclamation-triangle.fa-3x + .col-xs-7.text-right + %span.text-font + = t('.data_validation') + .row + .col-xs-12 + = simple_form_for Client.new, url: advanced_search_clients_path, method: :post, html: { class: 'hidden', id: 'client-date-logic-error' } do |f| + = hidden_field_tag :client_ids, @date_validation_error[:ids] + = hidden_field_tag 'commit', 'commit' + = f.submit + %h2 + = t('.number_of_logic_error') + = @date_validation_error[:count] diff --git a/app/views/dashboards/_report_builder.haml b/app/views/dashboards/_report_builder.haml index 8f6b3c3b84..0f538a48d6 100644 --- a/app/views/dashboards/_report_builder.haml +++ b/app/views/dashboards/_report_builder.haml @@ -7,4 +7,4 @@ %i.fa.fa-file-text.fa-3x .col-xs-9.text-right %span.text-row - = t('.report_builder') \ No newline at end of file + = t('.go_to_report_builder') \ No newline at end of file diff --git a/app/views/dashboards/index.html.haml b/app/views/dashboards/index.html.haml index 73461592f9..e356531194 100644 --- a/app/views/dashboards/index.html.haml +++ b/app/views/dashboards/index.html.haml @@ -29,9 +29,9 @@ = render 'report_builder' .row = render 'client_program_stream_by_gender' - = render 'data_agregation' + = render 'data_agregations' .row - = render 'data_validation' + = render 'data_validations' .row = render 'third_party' #family-tab.tab-pane{ role: 'tabpanel' } diff --git a/config/locales/en.yml b/config/locales/en.yml index 523b3bfbe1..76f33f38be 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2402,6 +2402,14 @@ en: next: Next open_form: Open Form previous: Previous + data_agregations: + active_case_by_donor: Active Case by Donors + active_case_by_gender: Active Case by Gender + active_progarm_streams_by_gender: Gender by Active Program Stream + data_agregation: Data Desegregation + data_validations: + data_validation: Data Validation + number_of_logic_error: 'Number of Clients have date logic errors:' duetoday: assessment: Assessment assessments: Assessments @@ -2415,8 +2423,15 @@ en: family_form: Family Form next: Next previous: Previous + index: + client_dashboard: Client Dashboard + community_dashboard: Community Dashboard + family_dashboard: Family Dashboard + hotline_dashboard: Hotline Dashboard go_to_client: go_to_client: Go to Client + report_builder: + go_to_report_builder: Go to Report Builder multiple_forms: add_new_document: Add New Document assessment: Assessment diff --git a/config/locales/km.yml b/config/locales/km.yml index e084563fd8..79eee6a97a 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -2325,7 +2325,7 @@ km: custom_assessment: ការប៉ាន់ប្រមាណផ្ទាល់ខ្លួន client_you_complete_for: តើអតិថិជនណាដែលអ្នកចង់បំពេញកំណត់ត្រាករណី? new_case_note: កំណត់ត្រាថ្មី - clients: + client: clients: អតិថិជន client_search: not_found: មិនមានលទ្ធផលដែលអ្នកកំពុងរក @@ -2349,6 +2349,14 @@ km: next: ទំព័របន្ទាប់ open_form: បើកទម្រង់ previous: ទំព័រមុន + data_agregations: + active_case_by_donor: បំណែងចែកចំនួនអតិថិជនតាមម្ចាស់ជំនួយ + active_case_by_gender: បំណែងចែកចំនួនអតិថិជនបច្ចុប្បន្នតាមភេទ + active_progarm_streams_by_gender: បំណែងចែកចំនួនអតិថិជនបច្ចុប្បន្នតាមភេទ + data_agregation: បំណែងចែកទិន្នន័យ + data_validations: + data_validation: ការផ្ទៀងផ្ទាត់ទិន្នន័យ + number_of_logic_error: ចំនួនអតិថិជនមានកាលបរិច្ឆេទមិនត្រឹមត្រូវ៖ duetoday: assessment: ការប៉ាន់ប្រមាណ assessments: ការប៉ាន់ប្រមាណ @@ -2366,6 +2374,8 @@ km: previous: ទំព័រមុន go_to_client: go_to_client: ចូលទៅរកអតិថិជន + report_builder: + go_to_report_builder: ចូលទៅបង្កើតរបាយការណ៍ multiple_forms: add_new_document: បន្ថែមឯកសារថ្មី assessment: ការប៉ាន់ប្រមាណ @@ -6401,7 +6411,7 @@ km: views: pagination: first: "« ដំបូង" - last: ចុងក្រោយ » + last: ចុងក្រោយ next: ទំព័របន្ទាប់ › previous: "‹ ទំព័រមុន" truncate: "…" From e3264cc8aa164e236221bcc6fd87673508bc6756 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Feb 2022 14:44:17 +0700 Subject: [PATCH 23/94] added link to clients in client dashboard --- config/locales/en.yml | 10 ++++------ config/locales/km.yml | 11 +++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 76f33f38be..5d61c5a465 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2423,15 +2423,13 @@ en: family_form: Family Form next: Next previous: Previous + go_to_client: + go_to_client: Go to Client index: client_dashboard: Client Dashboard community_dashboard: Community Dashboard family_dashboard: Family Dashboard hotline_dashboard: Hotline Dashboard - go_to_client: - go_to_client: Go to Client - report_builder: - go_to_report_builder: Go to Report Builder multiple_forms: add_new_document: Add New Document assessment: Assessment @@ -2499,8 +2497,8 @@ en: remind_later: Remind me later program_stream_name: Program Stream tracking_name: Tracking Form Name - record_tab: - record_you_add: What kind of record would you like to add? + report_builder: + go_to_report_builder: Go to Report Builder third_party: ables: Ables agencies: Agencies diff --git a/config/locales/km.yml b/config/locales/km.yml index 79eee6a97a..51ffa37b47 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -2374,8 +2374,11 @@ km: previous: ទំព័រមុន go_to_client: go_to_client: ចូលទៅរកអតិថិជន - report_builder: - go_to_report_builder: ចូលទៅបង្កើតរបាយការណ៍ + index: + client_dashboard: តារាងទិន្នន័យអតិថិជន + community_dashboard: Community Dashboard + family_dashboard: Family Dashboard + hotline_dashboard: Hotline Dashboard multiple_forms: add_new_document: បន្ថែមឯកសារថ្មី assessment: ការប៉ាន់ប្រមាណ @@ -2441,8 +2444,8 @@ km: remind_later: រំលឹកខ្ញុំពេលក្រោយ program_stream_name: កម្មវិធីចម្បង tracking_name: ឈ្មោះទម្រង់តាមដាន - record_tab: - record_you_add: តើឯកសារប្រភេទណាដែលអ្នកចង់បន្ថែម? + report_builder: + go_to_report_builder: ចូលទៅបង្កើតរបាយការណ៍ third_party: agencies: អង្គភាព partners: ដៃគូ From d7b8bc49b6bd7a88b25328c73650c04a753dce69 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 28 Feb 2022 15:06:38 +0700 Subject: [PATCH 24/94] updated dashboard translation --- config/locales/km.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/km.yml b/config/locales/km.yml index 51ffa37b47..411c25a419 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -2326,7 +2326,7 @@ km: client_you_complete_for: តើអតិថិជនណាដែលអ្នកចង់បំពេញកំណត់ត្រាករណី? new_case_note: កំណត់ត្រាថ្មី client: - clients: អតិថិជន + clients: អតិថិជនបច្ចុប្បន្ន client_search: not_found: មិនមានលទ្ធផលដែលអ្នកកំពុងរក please_enter_more: សូមវាយបញ្ចូលអក្សរមួយ ឬច្រើន @@ -2352,7 +2352,7 @@ km: data_agregations: active_case_by_donor: បំណែងចែកចំនួនអតិថិជនតាមម្ចាស់ជំនួយ active_case_by_gender: បំណែងចែកចំនួនអតិថិជនបច្ចុប្បន្នតាមភេទ - active_progarm_streams_by_gender: បំណែងចែកចំនួនអតិថិជនបច្ចុប្បន្នតាមភេទ + active_progarm_streams_by_gender: បំណែងចែកចំនួនអតិថិជនតាមភេទក្នុងកម្មវិធី data_agregation: បំណែងចែកទិន្នន័យ data_validations: data_validation: ការផ្ទៀងផ្ទាត់ទិន្នន័យ From df600180fd5d1d9e1780f93ef1ae14d6483878c7 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Mar 2022 16:40:08 +0700 Subject: [PATCH 25/94] fixed active program stream dashboard data --- .../javascripts/dashboards/index.coffee | 18 ++++++- app/classes/dashboard.rb | 50 ++++++++++--------- app/views/dashboards/_data_agregation.haml | 24 --------- 3 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 app/views/dashboards/_data_agregation.haml diff --git a/app/assets/javascripts/dashboards/index.coffee b/app/assets/javascripts/dashboards/index.coffee index acd127bac9..d7327a3a56 100644 --- a/app/assets/javascripts/dashboards/index.coffee +++ b/app/assets/javascripts/dashboards/index.coffee @@ -96,9 +96,22 @@ CIF.DashboardsIndex = do -> element = $('#client-program-stream') data = $(element).data('content-count') title = $(element).data('title') + + programNames = [ + data[0].active_data.map(({name}) -> + name + ) + data[1].active_data.map(({name}) -> + name + ) + data[2].active_data.map(({name}) -> + name + ) + ] + + categories = _.uniq(_.flatten(programNames)) data = - categories: data[0].active_data.map (element) -> - element['name'] + categories: categories series: data.map (element, index) -> { name: element['name'] @@ -108,6 +121,7 @@ CIF.DashboardsIndex = do -> color: if index == 0 then '#f9c00c' else if index == 1 then '#4caf50' else '#00695c' } + debugger; report = new CIF.ReportCreator(data, title, '', element) report.barChart() diff --git a/app/classes/dashboard.rb b/app/classes/dashboard.rb index c69fa111c6..b6e10dcaf7 100644 --- a/app/classes/dashboard.rb +++ b/app/classes/dashboard.rb @@ -13,7 +13,7 @@ def initialize(clients) end def client_program_stream - program_streams = @program_streams.distinct.select("program_streams.id, program_streams.name, (SELECT COUNT(DISTINCT(client_enrollments.client_id)) FROM client_enrollments WHERE client_enrollments.program_stream_id = program_streams.id AND client_enrollments.status = 'Active') AS client_enrollment_count") + program_streams = ProgramStream.joins(:client_enrollments).group("program_streams.id, client_enrollments.status").select("program_streams.id, program_streams.name, COUNT(DISTINCT(client_enrollments.client_id)) AS client_enrollment_count").having("client_enrollments.status = 'Active'") program_streams.map do |p| url = { 'condition': 'AND', 'rules': [{ 'id': 'active_program_stream', 'field': 'active_program_stream', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': p.id }]} { @@ -45,30 +45,29 @@ def family_type_statistic end def program_stream_report_gender - active_enrollments = Client.joins(:client_enrollments).where(client_enrollments: { status: 'Active' }) - males = active_enrollments.where(clients: { gender: 'male' } ).distinct - females = active_enrollments.where(clients: { gender: 'female' } ).distinct - others = active_enrollments.where("clients.gender ~ '^(lgbt|unknown|prefer_not_to_say|other)' OR clients.gender = ''").distinct - male = I18n.t('gender_list.male') female = I18n.t('gender_list.female') other = I18n.t('gender_list.other_gender') + male_data = program_stream_report_by('male') + female_data = program_stream_report_by('female') + other_data = program_stream_report_by('other') + [ { name: male, - y: males.size, - active_data: program_stream_report_by(males.ids, male) + y: male_data[:total], + active_data: male_data[:data] }, { name: female, - y: females.size, - active_data: program_stream_report_by(females.ids, female) + y: female_data[:total], + active_data: female_data[:data] }, { name: other, - y: others.size, - active_data: program_stream_report_by(others.ids, other) + y: other_data[:total], + active_data: other_data[:data] } ] end @@ -127,19 +126,24 @@ def program_stream_count private - def program_stream_report_by(client_ids, gender) - if client_ids.present? - program_streams = @program_streams.where(client_enrollments: {client_id: client_ids}).select("program_streams.id, program_streams.name, (SELECT COUNT(DISTINCT(client_enrollments.id)) FROM client_enrollments WHERE (client_enrollments.program_stream_id = program_streams.id AND client_enrollments.status = 'Active') AND client_enrollments.client_id IN (#{client_ids.join(', ')})) AS client_enrollment_count") - else - program_streams = @program_streams.where(client_enrollments: {client_id: client_ids}).select("program_streams.id, program_streams.name, (SELECT COUNT(DISTINCT(client_enrollments.id)) FROM client_enrollments WHERE (client_enrollments.program_stream_id = program_streams.id AND client_enrollments.status = 'Active')) AS client_enrollment_count") - end + def program_stream_report_by(gender) + sql_condition = gender == 'other' ? "clients.gender NOT IN ('male', 'female') AND client_enrollments.status = 'Active'" : "clients.gender = '#{gender}' AND client_enrollments.status = 'Active'" - program_streams.map do |p| - url = { 'condition': 'AND', 'rules': [{ 'id': 'active_program_stream', 'field': 'active_program_stream', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': p.id }, - { 'id': 'gender', 'field': 'gender', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': gender.downcase } ]} + program_streams = ProgramStream.joins(:clients).group("program_streams.id, clients.gender, client_enrollments.status") + .select("program_streams.id, program_streams.name, clients.gender AS client_gender, COUNT(DISTINCT(client_enrollments.client_id)) AS client_enrollment_count") + .having(sql_condition) + + { total: program_streams.map(&:client_enrollment_count).sum, data: mapping_program_data(program_streams, gender) } + end + + def mapping_program_data(program_streams, gender) + @program_streams.map do |ps| + client_enrollment_count_hash = program_streams.map{|ps| [ps.name, ps.client_enrollment_count] }.to_h + url = { 'condition': 'AND', 'rules': [{ 'id': 'active_program_stream', 'field': 'active_program_stream', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': ps.id }, + { 'id': 'gender', 'field': 'gender', 'type': 'string', 'input': 'select', 'operator': 'equal', 'value': gender } ]} { - name: "#{p.name}", - y: p.client_enrollment_count, + name: ps.name, + y: client_enrollment_count_hash[ps.name] || 0, url: clients_path( client_advanced_search: { action_report_builder: '#builder', diff --git a/app/views/dashboards/_data_agregation.haml b/app/views/dashboards/_data_agregation.haml deleted file mode 100644 index 04eb58f942..0000000000 --- a/app/views/dashboards/_data_agregation.haml +++ /dev/null @@ -1,24 +0,0 @@ -.col-xs-12 - .panel.panel-default.program-stream-panel - .panel-body - = link_to program_streams_path do - .widget.style1.widget-program-panel - .row.vertical-align - .col-xs-5 - %i.fa.fa-bar-chart.fa-3x - .col-xs-7.text-right - %span.text-font - = t('.data_agregation') - - .row - .col-xs-12 - #rootwizard.m-t-sm.root-wizard{ data: { next: t('.next'), previous: t('.previous'), action: params['action'] } } - %h4= t('.active_progarm_streams_by_gender') - %div{ class: "active-progarm-streams-by-gender" } - #client-program-stream{ 'data-content-count' => @dashboard.program_stream_report_gender.to_json, 'data-title' => t('.active_progarm_streams_by_gender') } - %h4= t('.active_client_by_gender') - %div{ class: "active-client-by-gender" } - #active-client{ 'data-url' => client_by_gender_api_clients_path, 'data-title' => t('.active_client_by_gender') } - %h4= t('.active_case_by_donor') - %div{ class: "active-case-by-donor" } - #active-case-by-donor{ 'data-url' => active_client_by_donor_api_clients_path, 'data-title' => t('.active_case_by_donor') } From afba8b7969e8bb4ed1c79f495c817359f82f36e8 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 25 Mar 2022 11:44:50 +0700 Subject: [PATCH 26/94] [IMP] Apply caching refs OSC-13 --- app/controllers/clients_controller.rb | 5 ++++- app/helpers/cache_helper.rb | 5 +++++ config/environments/development.rb | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 app/helpers/cache_helper.rb diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 3c7d46ca5a..0c58c5baa5 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -3,6 +3,7 @@ class ClientsController < AdminController include ClientAdvancedSearchesConcern include ClientGridOptions + include CacheHelper before_action :assign_active_client_prams, only: :index before_action :format_search_params, only: [:index] @@ -360,7 +361,9 @@ def quantitative_type_editable end def quantitative_type_readable - @quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) + Rails.cache.fetch(user_cache_id) do + @quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) + end end def find_referral_by_params diff --git a/app/helpers/cache_helper.rb b/app/helpers/cache_helper.rb new file mode 100644 index 0000000000..e2d083d3aa --- /dev/null +++ b/app/helpers/cache_helper.rb @@ -0,0 +1,5 @@ +module CacheHelper + def user_cache_id + [Apartment::Tenant.current, current_user.class.name, current_user.id] + end +end \ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb index 701c6ae03d..3fa31dcc86 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,6 +13,7 @@ # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false + config.cache_store = :memory_store # Don't care if the mailer can't send. config.action_mailer.perform_deliveries = true From 0f20fa630e3646b92b7dad09d8b267cd00fc5c74 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 22 Feb 2022 15:59:28 +0700 Subject: [PATCH 27/94] fixed translation family type list --- config/locales/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 523b3bfbe1..4426ac94b4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3201,10 +3201,10 @@ en: birth_family_only_mother: Birth Family (Only Mother) child_headed_household: Child-Headed Household domestically_adopted: Domestically Adopted - extended_family_kinship_care: Extended Family/Kinship Care + extended_family_kinship_care: Extended Family / Kinship Care long_term_foster_care: Long Term Foster Care no_family: No Family - short_term_emergency_foster_care: Short Term/Emergency Foster Care + short_term_emergency_foster_care: Short Term / Emergency Foster Care the_other: Other female_adult_count: Female Adult Count female_children_count: Female Children Count From 8ea5a7ea33c7fec8151934d0ea603d7e8e027549 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 10 Feb 2022 19:21:32 +0700 Subject: [PATCH 28/94] 2022021007 fixed error on family delete button --- app/controllers/families_controller.rb | 3 ++- app/helpers/application_helper.rb | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 3981c79770..9eaa0c9b9b 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -12,7 +12,7 @@ class FamiliesController < AdminController before_action :find_association, except: [:index, :destroy, :version] before_action :find_family, only: [:show, :edit, :update, :destroy] before_action :find_case_histories, only: :show - before_action :quantitative_type_readable + before_action :quantitative_type_readable, except: :destroy before_action :load_quantative_types, only: [:new, :edit, :create, :update] def index @@ -97,6 +97,7 @@ def update end def destroy + binding.pry @family.case_worker_families.with_deleted.each(&:destroy_fully!) if @family.current_clients.blank? && (@family.cases.present? && @family.cases.delete_all || true) && @family.destroy Task.with_deleted.where(family_id: @family.id).each(&:destroy_fully!) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 76583cd007..b8192551f7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -72,8 +72,14 @@ def current_url(new_params) def remove_link(object, associated_objects = {}, btn_size = 'btn-xs', custom_assessment_setting_id = nil, tab_name = nil) btn_status = associated_objects.values.sum.zero? ? nil : 'disabled' - link_to(domain_path(object, custom_assessment_setting_id: custom_assessment_setting_id, tab: tab_name || params[:tab]), method: 'delete', data: { confirm: t('are_you_sure') }, class: "btn btn-outline btn-danger #{btn_size} #{btn_status}") do - fa_icon('trash') + if object.class.name.downcase == 'domain' + link_to(domain_path(object, custom_assessment_setting_id: custom_assessment_setting_id, tab: tab_name || params[:tab]), method: 'delete', data: { confirm: t('are_you_sure') }, class: "btn btn-outline btn-danger #{btn_size} #{btn_status}") do + fa_icon('trash') + end + else + link_to(object, method: 'delete', data: { confirm: t('are_you_sure') }, class: "btn btn-outline btn-danger #{btn_size} #{btn_status}") do + fa_icon('trash') + end end end From 7cf60e772734eb3c4f882f0aa66ef5a91437a36b Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Feb 2022 17:10:41 +0700 Subject: [PATCH 29/94] 2022022505 fixed delete family error --- app/controllers/families_controller.rb | 9 ++++++--- app/models/exit_ngo.rb | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 9eaa0c9b9b..d2550d87fd 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -97,9 +97,12 @@ def update end def destroy - binding.pry - @family.case_worker_families.with_deleted.each(&:destroy_fully!) - if @family.current_clients.blank? && (@family.cases.present? && @family.cases.delete_all || true) && @family.destroy + if @family.current_clients.blank? && @family.delete + @family.case_worker_families.with_deleted.each(&:destroy_fully!) + EnterNgo.with_deleted.where(acceptable_id: @family.id).each(&:destroy_fully!) + Enrollment.with_deleted.where(programmable_id: @family.id).delete_all + Case.where(family_id: @family.id).delete_all + ExitNgo.with_deleted.where(rejectable_id: @family.id).each(&:destroy_fully!) Task.with_deleted.where(family_id: @family.id).each(&:destroy_fully!) redirect_to families_url, notice: t('activerecord.destroy.successfully_deleted') else diff --git a/app/models/exit_ngo.rb b/app/models/exit_ngo.rb index 2ddc0be774..bb1d4f7890 100644 --- a/app/models/exit_ngo.rb +++ b/app/models/exit_ngo.rb @@ -38,6 +38,7 @@ def create_exit_ngo_history end def update_client_status + return if rejectable_type != 'Client' return if client.enter_ngos.count.zero? && (client.client_enrollments.count.zero? || client.enter_ngos.count.zero?) client.update_column(:status, 'Accepted') end From b604a102229c8d3663dfda6f9a295c194b2ad3ba Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 11:24:51 +0700 Subject: [PATCH 30/94] fixed government form six called strftime on nil class --- app/views/government_forms/_show_form_six.pdf.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/government_forms/_show_form_six.pdf.haml b/app/views/government_forms/_show_form_six.pdf.haml index 9e75f76827..6e0d276c7f 100644 --- a/app/views/government_forms/_show_form_six.pdf.haml +++ b/app/views/government_forms/_show_form_six.pdf.haml @@ -44,7 +44,7 @@ %td.text-right ថ្ងៃខែឆ្នាំកំណើត: %td.text-right - - if @father.present? + - if @father.present? && @father.date_of_birth.present? - @father.date_of_birth.strftime('%d%m%y').split('').each_with_index do |code, index| - if index == 0 %span.first= code From 27ac20b8a216c3fe7a12ba3ee722f18d60b65924 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 11:36:20 +0700 Subject: [PATCH 31/94] fixed error called visible_columns on nil class in app/controllers/concerns/client_grid_options.rb:299 --- app/controllers/concerns/client_grid_options.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/client_grid_options.rb b/app/controllers/concerns/client_grid_options.rb index 5249f84cf9..063b097ec6 100644 --- a/app/controllers/concerns/client_grid_options.rb +++ b/app/controllers/concerns/client_grid_options.rb @@ -295,12 +295,12 @@ def date_of_completed_cusotm_assessments end def default_all_csi_assessments - return unless params['type'] == 'basic_info' && @client_columns.visible_columns[:all_csi_assessments_].present? + return unless params['type'] == 'basic_info' && (@client_columns && @client_columns.visible_columns[:all_csi_assessments_].present?) domain_score_report('default') end def custom_all_csi_assessments - return unless params['type'] == 'basic_info' && @client_columns.visible_columns[:all_custom_csi_assessments_].present? + return unless params['type'] == 'basic_info' && (@client_columns && @client_columns.visible_columns[:all_custom_csi_assessments_].present?) domain_score_report('custom') end From 477a58dffbc793305e050c5675935c1969aab14e Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 17:33:52 +0700 Subject: [PATCH 32/94] added AppSignal deployment notification --- Capfile | 7 ++++--- Gemfile | 2 +- Gemfile.lock | 5 ++--- config/deploy.rb | 3 ++- config/deploy/production.rb | 2 ++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Capfile b/Capfile index e286908626..45bf7bf25a 100644 --- a/Capfile +++ b/Capfile @@ -1,3 +1,6 @@ +require "dotenv" +Dotenv.load + # Load DSL and set up stages require 'capistrano/setup' @@ -27,8 +30,6 @@ require 'capistrano/rails' require 'whenever/capistrano' require 'capistrano/sidekiq' +require 'appsignal/capistrano' # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } - -# Capfile -# require 'capistrano/sidekiq/monit' #to require monit tasks # Only for capistrano3 diff --git a/Gemfile b/Gemfile index dbfdfa1e5f..c354ad7d76 100644 --- a/Gemfile +++ b/Gemfile @@ -95,7 +95,7 @@ group :development, :test do end group :staging, :ratanak_staging, :demo, :production do - gem 'appsignal', '~> 1.1.9' + gem 'appsignal', '~> 3.0', '>= 3.0.24' gem 'asset_sync' end diff --git a/Gemfile.lock b/Gemfile.lock index e5a04e014a..ef66462536 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,9 +57,8 @@ GEM apartment (1.2.0) activerecord (>= 3.1.2, < 6.0) rack (>= 1.3.6) - appsignal (1.1.9) + appsignal (3.0.24) rack - thread_safe arel (6.0.4) asset_sync (2.8.1) activemodel (>= 4.1.0) @@ -867,7 +866,7 @@ DEPENDENCIES acts_as_paranoid (~> 0.6.1) ancestry (~> 3.0, >= 3.0.5) apartment (~> 1.2) - appsignal (~> 1.1.9) + appsignal (~> 3.0, >= 3.0.24) asset_sync aws-healthcheck bootstrap-datepicker-rails (~> 1.5) diff --git a/config/deploy.rb b/config/deploy.rb index 80364f3316..1f0066bf22 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -10,6 +10,8 @@ ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp end +set :appsignal_revision, `git log --pretty=format:'%h' -n 1 #{fetch(:branch)}` + set :deploy_to, "/var/www/#{fetch(:application)}" set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', "public/packs", ".bundle", "node_modules") @@ -61,4 +63,3 @@ set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } -require 'appsignal/capistrano' diff --git a/config/deploy/production.rb b/config/deploy/production.rb index 6c3fcd780b..51e5c4bb3c 100644 --- a/config/deploy/production.rb +++ b/config/deploy/production.rb @@ -1,4 +1,6 @@ set :stage, 'production' +set :rails_env, :production +set :appsignal_env, :production set :branch, proc { `git rev-parse --abbrev-ref stable`.chomp } # TODO: Deploy one after another by commentting out one From f7e6736edef8c0df2f7e5abde87875765e7034f1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 7 Feb 2022 11:55:10 +0700 Subject: [PATCH 33/94] changed fill name of report builder --- app/services/old_ngo_usage_report.rb | 223 --------------------------- lib/tasks/ngo_usage_report.rake | 8 +- 2 files changed, 1 insertion(+), 230 deletions(-) delete mode 100644 app/services/old_ngo_usage_report.rb diff --git a/app/services/old_ngo_usage_report.rb b/app/services/old_ngo_usage_report.rb deleted file mode 100644 index 59a3038c3e..0000000000 --- a/app/services/old_ngo_usage_report.rb +++ /dev/null @@ -1,223 +0,0 @@ -class OldNgoUsageReport - def initialize - end - - def usage_report(date_time) - import_usage_report(date_time) - end - - def ngo_info(org) - country = Setting.first.present? ? Setting.first.country_name.downcase : '' - { - ngo_name: org.full_name, - ngo_short_name: org.short_name, - ngo_on_board: org.created_at.strftime("%d %B %Y"), - fcf: org.fcf_ngo? ? 'Yes' : 'No', - ngo_country: country.titleize - } - end - - def ngo_users_info(beginning_of_month, end_of_month) - previous_month_users = PaperTrail::Version.where(item_type: 'User', created_at: beginning_of_month..end_of_month) - { - user_count: User.non_devs.count, - user_added_count: previous_month_users.where(event: 'create').count, - user_deleted_count: previous_month_users.where(event: 'destroy').count, - login_per_month: Visit.excludes_non_devs.total_logins(beginning_of_month, end_of_month).count - } - end - - def ngo_clients_info(beginning_of_month, end_of_month) - previous_month_clients = PaperTrail::Version.where(item_type: 'Client', created_at: beginning_of_month..end_of_month) - { - client_count: Client.without_test_clients.count, - client_added_count: previous_month_clients.where.not(item_id: Client.test_clients.ids).where(event: 'create').count, - client_deleted_count: previous_month_clients.where.not(item_id: Client.test_clients.ids).where(event: 'destroy').count - } - end - - def ngo_referrals_info(beginning_of_month, end_of_month) - tranferred_clients = PaperTrail::Version.where(item_type: 'Referral', event: 'create', created_at: beginning_of_month..end_of_month) - { - tranferred_client_count: tranferred_clients.map { |a| a.changeset[:slug] && a.changeset[:slug][1] }.compact.uniq.count - } - end - - private - - def import_usage_report(date_time) - ngo_columns = ['Organization', 'On-board Date', 'FCF', 'Country'] - user_columns = ['Organization', 'No. of Users', 'No. of Users Added', 'No. of Users Deleted', 'No. of Logins/Month'] - client_columns = ['Organization', 'No. of Clients', 'No. of Clients Added', 'No. of Clients Deleted', 'No. of Clients Transferred'] - learning_columns = ['Onboarding Date', '', '', '', '', '', 'Number of Logins', '', ''] - sub_learning_columns = ['Started Sharing This Month', '', 'Stopped Sharing This Month', '', 'Currently Sharing (All Time)', '', 'User', 'Organization Name', 'Login Count'] - - book = Spreadsheet::Workbook.new - - header_format = Spreadsheet::Format.new( - horizontal_align: :center, - vertical_align: :center, - shrink: true, - border: :thin, - size: 11 - ) - column_format = Spreadsheet::Format.new( - shrink: true, - border: :thin, - size: 11 - ) - column_date_format = Spreadsheet::Format.new( - shrink: true, - border: :thin, - size: 11, - number_format: 'mmmm d, yyyy' - ) - - beginning_of_month = date_time.to_datetime.prev_month.beginning_of_month - end_of_month = date_time.to_datetime.prev_month.end_of_month - previous_month = date_time.to_datetime.prev_month.strftime('%B %Y') - - ngo_worksheet = book.create_worksheet(name: "NGO Records-#{previous_month}") - user_worksheet = book.create_worksheet(name: "User Report-#{previous_month}") - client_worksheet = book.create_worksheet(name: "Client Report-#{previous_month}") - learning_worksheet = book.create_worksheet(name: "OSCaR Leaning Report-#{previous_month}") - - ngo_worksheet.insert_row(0, ngo_columns) - user_worksheet.insert_row(0, user_columns) - client_worksheet.insert_row(0, client_columns) - - learning_worksheet.insert_row(0, learning_columns) - learning_worksheet.insert_row(1, sub_learning_columns) - sub_learning_columns.length.times do |i| - learning_worksheet.row(0).set_format(i, header_format) - learning_worksheet.row(1).set_format(i, header_format) - end - learning_worksheet.merge_cells(0, 0, 0, 5) - learning_worksheet.merge_cells(0, 6, 0, 8) - learning_worksheet.merge_cells(1, 0, 1, 1) - learning_worksheet.merge_cells(1, 2, 1, 3) - learning_worksheet.merge_cells(1, 4, 1, 5) - - learning_worksheet.row(0).height = 30 - learning_worksheet.row(1).height = 30 - (0..(sub_learning_columns.size - 1)).each {|index| learning_worksheet.column(index).width = 20 } - - - - ngo_length_of_column = ngo_columns.length - user_length_of_column = user_columns.length - client_length_of_column = client_columns.length - - ngo_length_of_column.times do |i| - ngo_worksheet.row(0).set_format(i, header_format) - end - - ngo_worksheet.row(0).height = 30 - ngo_worksheet.column(0).width = 35 - ngo_worksheet.column(1).width = 33 - ngo_worksheet.column(2).width = 15 - ngo_worksheet.column(3).width = 20 - - user_length_of_column.times do |i| - user_worksheet.row(0).set_format(i, header_format) - end - - user_worksheet.row(0).height = 30 - (0..4).each {|index| user_worksheet.column(index).width = 30 } - - client_length_of_column.times do |i| - client_worksheet.row(0).set_format(i, header_format) - end - - client_worksheet.row(0).height = 30 - (0..4).each {|index| client_worksheet.column(index).width = 30 } - - shared_data = [] - stop_sharing_date = [] - all_learning_data = [] - - Organization.order(:created_at).without_shared.each_with_index do |org, index| - Organization.switch_to org.short_name - - setting = ngo_info(org) - ngo_users = ngo_users_info(beginning_of_month, end_of_month) - ngo_clients = ngo_clients_info(beginning_of_month, end_of_month) - ngo_referrals = ngo_referrals_info(beginning_of_month, end_of_month) - - ngo_values = [setting[:ngo_name], setting[:ngo_on_board], setting[:fcf], setting[:ngo_country]] - user_values = [setting[:ngo_name], ngo_users[:user_count], ngo_users[:user_added_count], ngo_users[:user_deleted_count], ngo_users[:login_per_month]] - client_values = [setting[:ngo_name], ngo_clients[:client_count], ngo_clients[:client_added_count], ngo_clients[:client_deleted_count], ngo_referrals[:tranferred_client_count]] - - next if Setting.first.blank? - - start_sharing_data = Setting.first.start_sharing_this_month(date_time) - stop_sharing_data = Setting.first.stop_sharing_this_month(date_time) - current_sharing_data = Setting.first.current_sharing_with_research_module - - shared_data << mapping_learning_module_date(setting, start_sharing_data) - stop_sharing_date << mapping_learning_module_date(setting, stop_sharing_data) - all_learning_data << mapping_learning_module_date(setting, current_sharing_data) - - ngo_worksheet.insert_row(index += 1, ngo_values) - user_worksheet.insert_row(index, user_values) - client_worksheet.insert_row(index, client_values) - - ngo_length_of_column.times do |i| - ngo_worksheet.row(index).set_format(i, column_date_format) if i == 1 - next if i == 1 - ngo_worksheet.row(index).set_format(i, column_format) - end - - user_length_of_column.times do |i| - user_worksheet.row(index).set_format(i, column_format) - end - - client_length_of_column.times do |i| - client_worksheet.row(index).set_format(i, column_format) - end - - (0..(sub_learning_columns.size - 1)).each do |i| - learning_worksheet.row(index + 3).set_format(i, column_format) - end - end - Organization.switch_to 'public' - - user_data = User.where.not("email ILIKE ?", "api.user@%").map do |user| - [user.name, user.organization_name, user.sign_in_count] - end - - learning_index = 0 - (0..(Organization.count)).each do |i| - start_data = shared_data.compact.presence || [['', '']] - stop_data = stop_sharing_date.compact.presence || [['', '']] - all_data = all_learning_data.compact.presence || [['', '']] - login_data = user_data.compact.presence || [['', '', '']] - learning_data = [start_data[i] || ['', ''], stop_data[i] || ['', ''], all_data[i] || ['', ''], login_data[i] || ['', '', '']] - next if learning_data.flatten.reject(&:blank?).blank? - learning_index = i + 2 - learning_worksheet.insert_row(learning_index, learning_data.flatten) - sub_learning_columns.length.times.each do |ii| - learning_worksheet.row(learning_index).set_format(ii, column_format) - end - end - learning_worksheet.insert_row(learning_index + 1, ['TOTAL', shared_data.compact.count, '', stop_sharing_date.compact.count, '', all_learning_data.compact.count, '', '', user_data.compact.map(&:third).sum]) - learning_worksheet.row(learning_index + 1).height = 21 - sub_learning_columns.length.times.each do |i| - learning_worksheet.row(learning_index + 1).set_format(i, column_format) - end - - book.write("tmp/OSCaR-usage-report-#{date_time}.xls") - generate(date_time, previous_month) - end - - private - - def generate(date_time, previous_month) - NgoUsageReportWorker.perform_async(date_time, previous_month) - end - - def mapping_learning_module_date(setting, data) - updated_at_date = data.last['updated_at'].try(:last) || data.last['updated_at'] if data.present? - [setting[:ngo_short_name].upcase, updated_at_date&.strftime("%d-%m-%Y")] - end -end diff --git a/lib/tasks/ngo_usage_report.rake b/lib/tasks/ngo_usage_report.rake index f40cdf8e34..5829e79c7d 100644 --- a/lib/tasks/ngo_usage_report.rake +++ b/lib/tasks/ngo_usage_report.rake @@ -1,14 +1,8 @@ namespace :ngo_usage_report do desc "Send Usage Report" task generate: :environment do - date_time = DateTime.now.strftime('%Y%m%d%H%M%S') + date_time = DateTime.now.strftime('%Y%m%d%H%M') ngo_usage_report = NgoUsageReport.new ngo_usage_report.usage_report(date_time) - - sleep 3 - - date_time = DateTime.now.strftime('%Y%m%d%H%M%S') - old_ngo_user_report = OldNgoUsageReport.new - old_ngo_user_report.usage_report(date_time) end end From c770a55afaa15d5aed024867f26ce5a53f3aee19 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 15 Mar 2022 11:06:18 +0700 Subject: [PATCH 34/94] added fcf_report --- lib/tasks/fcf_report.rake | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/tasks/fcf_report.rake diff --git a/lib/tasks/fcf_report.rake b/lib/tasks/fcf_report.rake new file mode 100644 index 0000000000..cd74384afc --- /dev/null +++ b/lib/tasks/fcf_report.rake @@ -0,0 +1,35 @@ +namespace :fcf_report do + desc "Generate report for FCF" + task generate: :environment do + headers = ['Client ID', 'Case Created Date', 'NGO Name', 'Initial Referral Date', 'Referral Source Category', 'Referral Source', 'Status'] + + workbook = WriteXLSX.new("#{Rails.root}/clients-data-#{Date.today}.xlsx") + format = workbook.add_format + format.set_align('center') + + clients_data = [] + (8..11).to_a.map{|seq| "2021-#{seq}-01" }.each do |date| + sheet_name = Date.parse(date).to_formatted_s(:long) + worksheet = workbook.add_worksheet(sheet_name) + + Organization.visible.oscar.pluck(:short_name, :full_name).each do |short_name, full_name| + Apartment::Tenant.switch short_name + Client.joins("INNER JOIN referral_sources ON clients.referral_source_category_id = referral_sources.id").where("clients.created_at >= ?", date).each do |client| + clients_data << [ + client.slug, + client.created_at.to_formatted_s(:long), + "#{full_name}(#{short_name})", + client.initial_referral_date.to_formatted_s(:long), + ReferralSource.find_by(id: client.referral_source_category_id).try(:name), + client.referral_source_name, + client.status + ] + end + end + + write_data_to_spreadsheet2(worksheet, headers, clients_data, format) + end + + workbook.close + end +end \ No newline at end of file From ffe6a8b8bfceb0cf7e0401427171adee0e5648e9 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 15 Mar 2022 14:10:06 +0700 Subject: [PATCH 35/94] update rake fcf report task --- lib/tasks/fcf_report.rake | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/tasks/fcf_report.rake b/lib/tasks/fcf_report.rake index cd74384afc..42c8ee25d8 100644 --- a/lib/tasks/fcf_report.rake +++ b/lib/tasks/fcf_report.rake @@ -7,20 +7,19 @@ namespace :fcf_report do format = workbook.add_format format.set_align('center') - clients_data = [] (8..11).to_a.map{|seq| "2021-#{seq}-01" }.each do |date| + clients_data = [] sheet_name = Date.parse(date).to_formatted_s(:long) worksheet = workbook.add_worksheet(sheet_name) Organization.visible.oscar.pluck(:short_name, :full_name).each do |short_name, full_name| Apartment::Tenant.switch short_name - Client.joins("INNER JOIN referral_sources ON clients.referral_source_category_id = referral_sources.id").where("clients.created_at >= ?", date).each do |client| + Client.joins("INNER JOIN referral_sources ON clients.referral_source_category_id = referral_sources.id").where(created_at: [date..Date.parse(date).end_of_month.to_s]).each do |client| clients_data << [ client.slug, client.created_at.to_formatted_s(:long), "#{full_name}(#{short_name})", - client.initial_referral_date.to_formatted_s(:long), - ReferralSource.find_by(id: client.referral_source_category_id).try(:name), + client.initial_referral_date.to_formatted_s(:long), name), client.referral_source_name, client.status ] From f297df5a9346a333d2192f1a47f2780392cd50b4 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Mar 2022 10:36:58 +0700 Subject: [PATCH 36/94] fixed domain score error --- app/classes/advanced_searches/domain_score_sql_builder.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/classes/advanced_searches/domain_score_sql_builder.rb b/app/classes/advanced_searches/domain_score_sql_builder.rb index 0a4602a373..4c08eb9eee 100644 --- a/app/classes/advanced_searches/domain_score_sql_builder.rb +++ b/app/classes/advanced_searches/domain_score_sql_builder.rb @@ -44,11 +44,12 @@ def domainscore_field_query results = mapping_assessment_query_rules(basic_rules).reject(&:blank?) assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true #{assessment_completed_sql}) AND assessments.created_at = (SELECT created_at FROM assessments WHERE clients.id = assessments.client_id ORDER BY assessments.created_at limit 1 offset #{(assessment_number || 1) - 1})".squish - score = @value.to_i.zero? ? nil : @value.to_i + if assessment_completed_sql.present? && assessment_number.present? + score = @value.to_i.zero? ? nil : @value.to_i clients.where(assessment_domains: { score: score, domain_id: @domain_id }).where(sql) else - clients = domainscore_operator(clients, @operator, score, sql) + clients = domainscore_operator(clients, @operator, @value, sql) end clients.ids end @@ -68,6 +69,8 @@ def domainscore_operator(clients, operator, score, sql) clients.where("assessment_domains.score >= ? AND assessment_domains.domain_id = ?", score, @domain_id) when 'is_empty' clients.where("assessment_domains.score IS NULL") + when 'between' + clients.where("assessment_domains.score BETWEEN ? AND ? AND assessment_domains.domain_id = ?", score.first, score.last, @domain_id) when 'is_not_empty' clients.where("assessment_domains.score IS NOT NULL AND assessment_domains.domain_id = ?", @domain_id) else From 6792ea157fc2f8479033b46ff235d25082f2645c Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Mar 2022 10:54:09 +0700 Subject: [PATCH 37/94] updated domain_score_sql_builder#domainscore_field_query --- app/classes/advanced_searches/domain_score_sql_builder.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/classes/advanced_searches/domain_score_sql_builder.rb b/app/classes/advanced_searches/domain_score_sql_builder.rb index 4c08eb9eee..64dad6d5f0 100644 --- a/app/classes/advanced_searches/domain_score_sql_builder.rb +++ b/app/classes/advanced_searches/domain_score_sql_builder.rb @@ -45,11 +45,11 @@ def domainscore_field_query assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true #{assessment_completed_sql}) AND assessments.created_at = (SELECT created_at FROM assessments WHERE clients.id = assessments.client_id ORDER BY assessments.created_at limit 1 offset #{(assessment_number || 1) - 1})".squish + score = [@value].flatten.map(&:to_i).sum.zero? ? nil : [@value].flatten.map(&:to_i) if assessment_completed_sql.present? && assessment_number.present? - score = @value.to_i.zero? ? nil : @value.to_i clients.where(assessment_domains: { score: score, domain_id: @domain_id }).where(sql) else - clients = domainscore_operator(clients, @operator, @value, sql) + clients = domainscore_operator(clients, @operator, score, sql) end clients.ids end From 70eebf9eb403e73478f712399c1687332346ebb6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Mar 2022 20:23:36 +0700 Subject: [PATCH 38/94] fixed error in fcf_report.rake --- lib/tasks/fcf_report.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/fcf_report.rake b/lib/tasks/fcf_report.rake index 42c8ee25d8..de6b627d54 100644 --- a/lib/tasks/fcf_report.rake +++ b/lib/tasks/fcf_report.rake @@ -19,7 +19,7 @@ namespace :fcf_report do client.slug, client.created_at.to_formatted_s(:long), "#{full_name}(#{short_name})", - client.initial_referral_date.to_formatted_s(:long), name), + client.initial_referral_date.to_formatted_s(:long), client.referral_source_name, client.status ] From 9ae3b12def23aa7f8ab7cfb4d5c5be6aad203a8b Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Mar 2022 12:06:35 +0700 Subject: [PATCH 39/94] added link to client list on logic error client dashboard --- Dockerfile | 3 +- Gemfile | 1 + Gemfile.lock | 2 + app/controllers/api/clients_controller.rb | 2 +- app/controllers/clients_controller.rb | 4 +- app/controllers/dashboards_controller.rb | 11 ++- app/grids/client_grid.rb | 80 +++++++++++-------- app/views/dashboards/_data_validations.haml | 2 +- .../dashboards/client_data_validation.haml | 19 +++++ config/environments/development.rb | 2 +- config/environments/staging.rb | 1 + config/routes.rb | 1 + lib/tasks/fcf_report.rake | 2 +- 13 files changed, 88 insertions(+), 42 deletions(-) create mode 100644 app/views/dashboards/client_data_validation.haml diff --git a/Dockerfile b/Dockerfile index 5d7b00ce32..8fa6b3647a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM ruby:2.3.3 RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - RUN curl --silent https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - -RUN apt-get update -qq && apt-get install -y nodejs postgresql-client fonts-khmeros +RUN apt-get update -qq && apt-get install -y nodejs postgresql-client fonts-khmeros memcached RUN mkdir /app WORKDIR /app @@ -29,6 +29,7 @@ RUN gem install bundler -v 1.17.3 RUN bundle install --verbose --jobs 20 --retry 5 RUN npm install -g yarn RUN yarn install --check-files +RUN service memcached start # Copy the main application. COPY . ./ diff --git a/Gemfile b/Gemfile index c354ad7d76..c664fe4b36 100644 --- a/Gemfile +++ b/Gemfile @@ -77,6 +77,7 @@ gem 'enumerize', '~> 2.3.1' gem 'ulid', '~> 1.2' gem 'aws-healthcheck' gem 'redis-session-store', '~> 0.11.3' +gem 'dalli', '~> 2.7', '>= 2.7.11' group :development, :test do gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index ef66462536..3779cca17a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -154,6 +154,7 @@ GEM colored2 (~> 3.1) crass (1.0.6) daemons (1.3.1) + dalli (2.7.11) danger (5.16.1) claide (~> 1.0) claide-plugins (>= 0.9.2) @@ -887,6 +888,7 @@ DEPENDENCIES chartkick (~> 3.4) cocoon (~> 1.2, >= 1.2.9) coffee-rails (~> 4.1.0) + dalli (~> 2.7, >= 2.7.11) danger (~> 5.16, >= 5.16.1) database_cleaner (~> 1.5, >= 1.5.1) datagrid (~> 1.4.2) diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index e2df852152..7e1d561695 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -322,7 +322,7 @@ def under_18_client_gender_count(clients, type = :male) end def other_client_gender_count(clients) - clients.where("gender IS NOT NULL AND (gender NOT IN ('male', 'female') OR date_of_birth IS NULL)").count + clients.where("gender IS NULL OR (gender NOT IN ('male', 'female'))").count end end end diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 9138c1aeb3..18b3fde126 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -35,10 +35,10 @@ def index f.html do next unless params['commit'].present? # @client_grid is invoked from ClientGridOptions#choose_grid - client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).where(id: params[:client_ids]&.split || []) } + client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability) } @results = client_grid.assets $client_data = @clients - @client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).where(id: params[:client_ids]&.split || []).order(:id).page(params[:page]).per(20) } + @client_grid = @client_grid.scope { |scope| scope.accessible_by(current_ability).order(:id).page(params[:page]).per(20) } end f.xls do next unless params['commit'].present? diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index 013a56b654..460e109c0d 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -16,7 +16,10 @@ def index LEFT OUTER JOIN exit_ngos ON exit_ngos.client_id = clients.id SQL clients_error = Client.accessible_by(current_ability).joins(sql).where("(client_enrollments.enrollment_date < enter_ngos.accepted_date) OR (exit_ngos.exit_date < client_enrollments.enrollment_date)").distinct - @date_validation_error = { ids: clients_error.ids, count: clients_error.count } + + @date_validation_error = Rails.cache.fetch(["#{clients_error.count}", "#{Apartment::Tenant.current}_client_errors"]) do + { ids: clients_error.ids, count: clients_error.count } + end end def update_program_stream_service @@ -28,6 +31,12 @@ def update_program_stream_service end end + def client_data_validation + @date_validation_error = Rails.cache.fetch([params[:total_error], "#{Apartment::Tenant.current}_client_errors"]) + clients = Client.accessible_by(current_ability).where(id: @date_validation_error[:ids]).page(params[:page]).per(15) + @client_grid = ClientGrid.new({ column_names: [:id, :slug, :given_name, :family_name, :local_given_name, :local_family_name, :status, :gender]}).scope { clients } + end + private def find_overhaul_task_params diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index fb7998f16a..f3719bcb2a 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -5,7 +5,7 @@ class ClientGrid < BaseGrid include FormBuilderHelper include AssessmentHelper - attr_accessor :current_user, :qType, :dynamic_columns, :param_data, :client_ids + attr_accessor :current_user, :qType, :dynamic_columns, :param_data COUNTRY_LANG = { "cambodia" => "(Khmer)", "thailand" => "(Thai)", "myanmar" => "(Burmese)", "lesotho" => "(Sesotho)", "uganda" => "(Swahili)" } scope do @@ -422,31 +422,37 @@ def self.custom_id_translation(type) end column(:given_name, order: 'clients.given_name', header: -> { I18n.t('datagrid.columns.clients.given_name') }, html: true) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - given_name = SharedClient.find_by(slug: object.slug).given_name - Organization.switch_to current_org.short_name - if given_name.present? - link_to given_name, client_path(object), target: :_blank - else - given_name + Rails.cache.fetch([Apartment::Tenant.current, object.id, object.given_name || 'given_name']) do + current_org = Organization.current + Organization.switch_to 'shared' + given_name = SharedClient.find_by(slug: object.slug).given_name + Organization.switch_to current_org.short_name + if given_name.present? + link_to given_name, client_path(object), target: :_blank + else + given_name + end end end column(:given_name, header: -> { I18n.t('datagrid.columns.clients.given_name') }, html: false) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - given_name = SharedClient.find_by(slug: object.slug).given_name - Organization.switch_to current_org.short_name - given_name + Rails.cache.fetch([Apartment::Tenant.current, object.id, object.given_name || 'given_name']) do + current_org = Organization.current + Organization.switch_to 'shared' + given_name = SharedClient.find_by(slug: object.slug).given_name + Organization.switch_to current_org.short_name + given_name + end end column(:family_name, order: 'clients.family_name', header: -> { I18n.t('datagrid.columns.clients.family_name') }) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - family_name = SharedClient.find_by(slug: object.slug).family_name - Organization.switch_to current_org.short_name - family_name + Rails.cache.fetch([Apartment::Tenant.current, object.id, object.family_name || 'family_name']) do + current_org = Organization.current + Organization.switch_to 'shared' + family_name = SharedClient.find_by(slug: object.slug).family_name + Organization.switch_to current_org.short_name + family_name + end end def self.dynamic_local_name @@ -455,27 +461,33 @@ def self.dynamic_local_name end column(:local_given_name, order: 'clients.local_given_name', header: -> { "#{I18n.t('datagrid.columns.clients.local_given_name')} #{ dynamic_local_name }" }) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - local_given_name = SharedClient.find_by(slug: object.slug).local_given_name - Organization.switch_to current_org.short_name - local_given_name + Rails.cache.fetch([Apartment::Tenant.current, object.id, object.local_given_name || 'local_given_name']) do + current_org = Organization.current + Organization.switch_to 'shared' + local_given_name = SharedClient.find_by(slug: object.slug).local_given_name + Organization.switch_to current_org.short_name + local_given_name + end end column(:local_family_name, order: 'clients.local_family_name', header: -> { "#{I18n.t('datagrid.columns.clients.local_family_name')} #{ dynamic_local_name }" }) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - local_family_name = SharedClient.find_by(slug: object.slug).local_family_name - Organization.switch_to current_org.short_name - local_family_name + Rails.cache.fetch([Apartment::Tenant.current, object.id, object.local_family_name || 'local_family_name']) do + current_org = Organization.current + Organization.switch_to 'shared' + local_family_name = SharedClient.find_by(slug: object.slug).local_family_name + Organization.switch_to current_org.short_name + local_family_name + end end column(:gender, header: -> { I18n.t('datagrid.columns.clients.gender') }) do |object| - current_org = Organization.current - Organization.switch_to 'shared' - gender = SharedClient.find_by(slug: object.slug)&.gender - Organization.switch_to current_org.short_name - gender.present? ? I18n.t("default_client_fields.gender_list.#{ gender.gsub('other', 'other_gender') }") : '' + Rails.cache.fetch([I18n.locale, Apartment::Tenant.current, object.id, object.gender || 'gender']) do + current_org = Organization.current + Organization.switch_to 'shared' + gender = SharedClient.find_by(slug: object.slug)&.gender + Organization.switch_to current_org.short_name + gender.present? ? I18n.t("default_client_fields.gender_list.#{ gender.gsub('other', 'other_gender') }") : '' + end end column(:status, header: -> { I18n.t('datagrid.columns.clients.status') }) do |object| diff --git a/app/views/dashboards/_data_validations.haml b/app/views/dashboards/_data_validations.haml index d0cee09ce1..e292696f63 100644 --- a/app/views/dashboards/_data_validations.haml +++ b/app/views/dashboards/_data_validations.haml @@ -1,7 +1,7 @@ .col-xs-12 .panel.panel-default#data-validation-panel.data-validation-panel .panel-body#data-validation - = link_to '#data-validation' do + = link_to dashbaords_client_data_validation_path(client_grid: '', total_error: @date_validation_error[:count]) do .widget.style1.widget-program-panel .row.vertical-align .col-xs-5 diff --git a/app/views/dashboards/client_data_validation.haml b/app/views/dashboards/client_data_validation.haml new file mode 100644 index 0000000000..5f7a245ebe --- /dev/null +++ b/app/views/dashboards/client_data_validation.haml @@ -0,0 +1,19 @@ +.row.mini-margin + .col-xs-12 + .ibox + .ibox-title + %h5= @date_validation_error[:count] + %span.label.label-info= t('.results') + + .ibox-content + .clients-table + %table.table.table-bordered.table-striped.table-hover.clients + = content_tag :div, 'hidden_param', class: 'hide', id: 'hidden-param', data: { 'hidden-param': url_for(params) } + %thead + = datagrid_header(@client_grid) + %tbody.page + = datagrid_rows(@client_grid, @client_grid.assets) + + %span.hidden{id:"sinfo", 'data-infoshow' => t('.sInfoShow'), 'data-infoto' => t('.sInfoTo'), 'data-infototal' => t('.sInfoTotal')} + .ibox-footer.text-center + = paginate @client_grid.assets \ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb index 701c6ae03d..1c185b0f71 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -12,7 +12,7 @@ # Show full error reports and disable caching. config.consider_all_requests_local = true - config.action_controller.perform_caching = false + config.cache_store = :memory_store, { size: 64.megabytes } # Don't care if the mailer can't send. config.action_mailer.perform_deliveries = true diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 2fb466758b..45aee66e34 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -10,6 +10,7 @@ # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true + config.cache_store = :memory_store, { size: 64.megabytes } # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like diff --git a/config/routes.rb b/config/routes.rb index 3142b94206..2309843f22 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,7 @@ get '/redirect' => 'calendars#redirect', as: 'redirect' get '/callback' => 'calendars#callback', as: 'callback' get '/calendar/sync' => 'calendars#sync' + get '/dashbaords/client_data_validation' => 'dashboards#client_data_validation' resources :calendars diff --git a/lib/tasks/fcf_report.rake b/lib/tasks/fcf_report.rake index 42c8ee25d8..de6b627d54 100644 --- a/lib/tasks/fcf_report.rake +++ b/lib/tasks/fcf_report.rake @@ -19,7 +19,7 @@ namespace :fcf_report do client.slug, client.created_at.to_formatted_s(:long), "#{full_name}(#{short_name})", - client.initial_referral_date.to_formatted_s(:long), name), + client.initial_referral_date.to_formatted_s(:long), client.referral_source_name, client.status ] From 0c3ff42648f5f6b69f0cbe726f5cfcf4ef1d2aaa Mon Sep 17 00:00:00 2001 From: Doungdara Keut Date: Fri, 25 Mar 2022 15:53:28 +0700 Subject: [PATCH 40/94] [IMP] Test add cache for SI refs OSC-13 done on OSC-13-7-14 --- .../client_advanced_searches_concern.rb | 63 ++++++++++++------- .../community_advanced_search_concern.rb | 5 +- .../family_advanced_searches_concern.rb | 33 +++++++--- .../partner_advanced_searches_concern.rb | 18 ++++-- app/models/program_stream.rb | 14 ++++- .../_form_option.haml | 2 +- .../client_advanced_searches/_wizard.haml | 2 +- 7 files changed, 94 insertions(+), 43 deletions(-) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index d68f6792aa..41841201eb 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -49,8 +49,10 @@ def build_advanced_search end def fetch_advanced_search_queries - @my_advanced_searches = current_user.advanced_searches.order(:name) - @other_advanced_searches = AdvancedSearch.includes(:user).non_of(current_user).order(:name) + Rails.cache.fetch(user_cache_id << "fetch_advanced_search_queries") do + @my_advanced_searches = current_user.advanced_searches.order(:name) + @other_advanced_searches = AdvancedSearch.includes(:user).non_of(current_user).order(:name) + end end def custom_form_column @@ -69,16 +71,20 @@ def get_custom_form end def hotline_call_column - client_hotlines = get_client_hotline_fields.group_by{ |field| field[:optgroup] } - call_hotlines = get_hotline_fields.group_by{ |field| field[:optgroup] } - @hotline_call_columns = client_hotlines.merge(call_hotlines) + Rails.cache.fetch(user_cache_id << "hotline_call_column") do + client_hotlines = get_client_hotline_fields.group_by{ |field| field[:optgroup] } + call_hotlines = get_hotline_fields.group_by{ |field| field[:optgroup] } + @hotline_call_columns = client_hotlines.merge(call_hotlines) + end end def program_stream_fields + Rails.cache.fetch(user_cache_id << "program_stream_fields") do if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' - @wizard_program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields - else - @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + @wizard_program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + else + @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + end end end @@ -95,8 +101,7 @@ def client_builder_fields end def get_program_streams - program_ids = ClientEnrollment.pluck(:program_stream_id).uniq - @program_streams = ProgramStream.where(id: program_ids).order(:name) + @program_streams = ProgramStream.cache_program_steam_by_enrollment end def program_stream_values @@ -104,7 +109,9 @@ def program_stream_values end def get_client_basic_fields - AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render + Rails.cache.fetch(user_cache_id << "get_client_basic_fields") do + AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render + end end def get_hotline_fields @@ -121,10 +128,10 @@ def get_hotline_fields *get_dropdown_list(['phone_call_id', 'call_type', 'start_datetime', 'protection_concern_id', 'necessity_id']), ] } - - hotline_fields = AdvancedSearches::AdvancedSearchFields.new('hotline', args).render - - @hotline_fields = get_client_hotline_fields + hotline_fields + Rails.cache.fetch(user_cache_id << "get_hotline_fields") do + hotline_fields = AdvancedSearches::AdvancedSearchFields.new('hotline', args).render + @hotline_fields = get_client_hotline_fields + hotline_fields + end end def get_client_hotline_fields @@ -144,8 +151,8 @@ def get_client_hotline_fields text_field: hotline_text_type_list, date_picker_field: [], dropdown_list_option: dropdown_list_options } - @client_hotline_fields = AdvancedSearches::AdvancedSearchFields.new('concern_basic_fields', args).render + end def hotline_text_type_list @@ -157,15 +164,19 @@ def custom_form_values end def custom_form_fields - if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' - @wizard_custom_form_fields = get_custom_form_fields + get_has_this_form_fields - else - @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + Rails.cache.fetch(user_cache_id << "custom_form_fields") do + if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' + @wizard_custom_form_fields = get_custom_form_fields + get_has_this_form_fields + else + @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + end end end def get_custom_form_fields - @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render + Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do + @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render + end end def get_has_this_form_fields @@ -173,8 +184,10 @@ def get_has_this_form_fields end def get_quantitative_fields - quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user) - @quantitative_fields = quantitative_fields.render + Rails.cache.fetch(user_cache_id << "get_quantitative_fields") do + quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user) + @quantitative_fields = quantitative_fields.render + end end def get_enrollment_fields @@ -222,7 +235,9 @@ def has_params? def find_params_advanced_search if params[:advanced_search_id] - advanced_search = AdvancedSearch.find(params[:advanced_search_id]) + Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do + advanced_search = AdvancedSearch.find(params[:advanced_search_id]) + end @advanced_search_params = params[:client_advanced_search].merge("basic_rules" => advanced_search.queries) else @advanced_search_params = params[:client_advanced_search] diff --git a/app/controllers/concerns/community_advanced_search_concern.rb b/app/controllers/concerns/community_advanced_search_concern.rb index 7eda157284..de5e6a04e1 100644 --- a/app/controllers/concerns/community_advanced_search_concern.rb +++ b/app/controllers/concerns/community_advanced_search_concern.rb @@ -80,7 +80,10 @@ def quantitative_check? end def find_params_advanced_search - @advanced_search_params = params[:community_advanced_search] + Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do + @advanced_search_params = params[:community_advanced_search] + end + end def basic_params diff --git a/app/controllers/concerns/family_advanced_searches_concern.rb b/app/controllers/concerns/family_advanced_searches_concern.rb index 0cd259d39e..51124186df 100644 --- a/app/controllers/concerns/family_advanced_searches_concern.rb +++ b/app/controllers/concerns/family_advanced_searches_concern.rb @@ -60,8 +60,10 @@ def custom_form_column end def get_custom_form - form_ids = CustomFieldProperty.where(custom_formable_type: 'Family').pluck(:custom_field_id).uniq - @custom_fields = CustomField.where(id: form_ids).order_by_form_title + Rails.cache.fetch(user_cache_id << "get_custom_form") do + form_ids = CustomFieldProperty.where(custom_formable_type: 'Family').pluck(:custom_field_id).uniq + @custom_fields = CustomField.where(id: form_ids).order_by_form_title + end end def family_builder_fields @@ -78,7 +80,9 @@ def custom_form_values end def custom_form_fields - @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + Rails.cache.fetch(user_cache_id << "custom_form_fields") do + @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + end end def get_has_this_form_fields @@ -86,7 +90,9 @@ def get_has_this_form_fields end def get_custom_form_fields - @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values, 'Family').render + Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do + @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values, 'Family').render + end end def custom_form_value? @@ -98,7 +104,9 @@ def has_params? end def find_params_advanced_search - @advanced_search_params = params[:family_advanced_search] + Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do + @advanced_search_params = params[:family_advanced_search] + end end def basic_params @@ -259,8 +267,10 @@ def csi_domain_score_report end def get_program_streams - program_ids = Enrollment.pluck(:program_stream_id).uniq - @program_streams = ProgramStream.where(id: program_ids).order(:name) + Rails.cache.fetch(user_cache_id << "get_program_streams") do + program_ids = Enrollment.pluck(:program_stream_id).uniq + @program_streams = ProgramStream.where(id: program_ids).order(:name) + end end def program_stream_column @@ -268,7 +278,9 @@ def program_stream_column end def program_stream_fields - @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + Rails.cache.fetch(user_cache_id << "program_stream_fields") do + @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + end end def get_enrollment_fields @@ -307,7 +319,10 @@ def exit_program_check? end def get_quantitative_fields - quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user, 'family') + Rails.cache.fetch(user_cache_id << "get_quantitative_fields") do + quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user, 'family') + end + @quantitative_fields = quantitative_fields.render end diff --git a/app/controllers/concerns/partner_advanced_searches_concern.rb b/app/controllers/concerns/partner_advanced_searches_concern.rb index bb7869b3a9..337c4e891e 100644 --- a/app/controllers/concerns/partner_advanced_searches_concern.rb +++ b/app/controllers/concerns/partner_advanced_searches_concern.rb @@ -29,8 +29,10 @@ def custom_form_column @custom_form_columns = custom_form_fields.group_by{ |field| field[:optgroup] } end def get_custom_form - form_ids = CustomFieldProperty.where(custom_formable_type: 'Partner').pluck(:custom_field_id).uniq - @custom_fields = CustomField.where(id: form_ids).order_by_form_title + Rails.cache.fetch(user_cache_id << "get_custom_form") do + form_ids = CustomFieldProperty.where(custom_formable_type: 'Partner').pluck(:custom_field_id).uniq + @custom_fields = CustomField.where(id: form_ids).order_by_form_title + end end def partner_builder_fields @@ -46,11 +48,15 @@ def custom_form_values end def custom_form_fields - @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do + @custom_form_fields = get_custom_form_fields + get_has_this_form_fields + end end def get_custom_form_fields - @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values).render + Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do + @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values).render + end end def get_has_this_form_fields @@ -66,7 +72,9 @@ def has_params? end def find_params_advanced_search - @advanced_search_params = params[:partner_advanced_search] + Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do + @advanced_search_params = params[:partner_advanced_search] + end end def basic_params diff --git a/app/models/program_stream.rb b/app/models/program_stream.rb index 5705751168..308a3d4db3 100644 --- a/app/models/program_stream.rb +++ b/app/models/program_stream.rb @@ -42,6 +42,7 @@ class ProgramStream < ActiveRecord::Base before_save :set_program_completed, :destroy_tracking after_update :auto_update_exit_program, :auto_update_enrollment, :update_save_search after_create :build_permission + after_commit :flash_cache scope :ordered, -> { order('lower(name) ASC') } scope :complete, -> { where(completed: true) } @@ -147,6 +148,13 @@ def attached_to_community? entity_type == 'Community' end + def self.cache_program_steam_by_enrollment + Rails.cache.fetch([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) do + program_ids = ClientEnrollment.pluck(:program_stream_id).uniq + ProgramStream.where(id: program_ids).order(:name).to_a + end + end + private def rules_edition @@ -267,8 +275,6 @@ def error_fields(properties, column_change) error_fields.uniq end - private - def presence_of_label validate_label(enrollment, 'enrollment') if enrollment.any? validate_label(exit_program, 'exit_program') if exit_program.any? @@ -331,4 +337,8 @@ def get_rules(queries, ss) end end end + + def flash_cache + Rails.cache.delete([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) + end end diff --git a/app/views/clients/client_advanced_searches/_form_option.haml b/app/views/clients/client_advanced_searches/_form_option.haml index bdad6cdc41..c21e77b6c1 100644 --- a/app/views/clients/client_advanced_searches/_form_option.haml +++ b/app/views/clients/client_advanced_searches/_form_option.haml @@ -34,7 +34,7 @@ .row.program-stream .col-xs-12 .form-group#program-stream-data{ data: { value: program_stream_values } } - = select_tag(:nil, options_for_select(@program_streams.pluck(:name, :id), program_stream_values), { multiple: true, id: 'program-stream-select', class: 'form-control program-stream-select' } ) + = select_tag(:nil, options_for_select(@program_streams.map{|p| [p.name, p.id] }, program_stream_values), { multiple: true, id: 'program-stream-select', class: 'form-control program-stream-select' } ) .row .col-xs-12 .program-association diff --git a/app/views/clients/client_advanced_searches/_wizard.haml b/app/views/clients/client_advanced_searches/_wizard.haml index ff6bc55b8f..ea0f313c50 100644 --- a/app/views/clients/client_advanced_searches/_wizard.haml +++ b/app/views/clients/client_advanced_searches/_wizard.haml @@ -64,7 +64,7 @@ .col-xs-12 %p Which Program Streams are you interested in? .form-group#wizard-program-stream-data{ data: { value: program_stream_values('#wizard-builder') } } - = select_tag(:nil, options_for_select(@program_streams.pluck(:name, :id), program_stream_values('#wizard-builder')), { multiple: true, id: 'wizard-program-stream-select', class: 'form-control program-stream-select' } ) + = select_tag(:nil, options_for_select(@program_streams.map{|p| [p.name, p.id]}, program_stream_values('#wizard-builder')), { multiple: true, id: 'wizard-program-stream-select', class: 'form-control program-stream-select' } ) .row.program-association .col-xs-12 %p Which Forms from these program streams would you like to display information from? From 2b091af9f8dea1b33f1b7222276b5706cc57a313 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 25 Mar 2022 11:44:50 +0700 Subject: [PATCH 41/94] [IMP] Apply caching refs OSC-13 --- app/controllers/clients_controller.rb | 2 +- app/models/agency.rb | 10 ++++++++++ app/models/client_type.rb | 10 ++++++++++ app/models/donor.rb | 10 ++++++++++ app/models/interviewee.rb | 10 ++++++++++ app/models/need.rb | 10 ++++++++++ app/models/problem.rb | 10 ++++++++++ 7 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 0c58c5baa5..a3dba43101 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -205,7 +205,7 @@ def version private def find_client - @client = Client.includes(custom_field_properties: [:custom_field], client_enrollments: [:program_stream]).accessible_by(current_ability).friendly.find(params[:id]).decorate + @client = Client.accessible_by(current_ability).friendly.find(params[:id]).decorate end def assign_client_attributes diff --git a/app/models/agency.rb b/app/models/agency.rb index 58c7ca64fb..1d8da05e7e 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -1,4 +1,6 @@ class Agency < ActiveRecord::Base + after_commit :flush_cache + has_many :agency_clients has_many :clients, through: :agency_clients has_paper_trail @@ -8,4 +10,12 @@ class Agency < ActiveRecord::Base def self.name_like(values = []) where('name iLIKE ANY ( array[?] )', values) end + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end diff --git a/app/models/client_type.rb b/app/models/client_type.rb index f8c713b423..c210dcb45e 100644 --- a/app/models/client_type.rb +++ b/app/models/client_type.rb @@ -1,8 +1,18 @@ class ClientType < ActiveRecord::Base + after_commit :flush_cache + has_paper_trail has_many :client_type_government_forms, dependent: :restrict_with_error has_many :government_forms, through: :client_type_government_forms validates :name, presence: true, uniqueness: { case_sensitive: false } + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end diff --git a/app/models/donor.rb b/app/models/donor.rb index a97e30de50..46bd2a6457 100644 --- a/app/models/donor.rb +++ b/app/models/donor.rb @@ -1,4 +1,6 @@ class Donor < ActiveRecord::Base + after_commit :flush_cache + has_many :sponsors, dependent: :restrict_with_error has_many :clients, through: :sponsors has_many :donor_organizations, dependent: :destroy @@ -12,4 +14,12 @@ class Donor < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false }, if: 'code.blank?' validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :code }, if: 'code.present?' validates :code, uniqueness: { case_sensitive: false }, if: 'code.present?' + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end diff --git a/app/models/interviewee.rb b/app/models/interviewee.rb index 09f6adf620..e5edb408bb 100644 --- a/app/models/interviewee.rb +++ b/app/models/interviewee.rb @@ -1,8 +1,18 @@ class Interviewee < ActiveRecord::Base + after_commit :flush_cache + has_paper_trail has_many :government_form_interviewees, dependent: :restrict_with_error has_many :government_forms, through: :government_form_interviewees validates :name, presence: true, uniqueness: { case_sensitive: false } + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end diff --git a/app/models/need.rb b/app/models/need.rb index b3c6dd67de..08703ec87d 100644 --- a/app/models/need.rb +++ b/app/models/need.rb @@ -1,8 +1,18 @@ class Need < ActiveRecord::Base + after_commit :flush_cache + has_paper_trail has_many :government_form_needs, dependent: :restrict_with_error has_many :government_forms, through: :government_form_needs validates :name, presence: true, uniqueness: { case_sensitive: false } + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end diff --git a/app/models/problem.rb b/app/models/problem.rb index 377ad0c26b..4333a0ab98 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -1,8 +1,18 @@ class Problem < ActiveRecord::Base + after_commit :flush_cache + has_paper_trail has_many :government_form_problems, dependent: :restrict_with_error has_many :government_forms, through: :government_form_problems validates :name, presence: true, uniqueness: { case_sensitive: false } + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end From b0d9e74c69fb6e10bc309bd2e8e349d004f2de08 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Mar 2022 16:32:59 +0700 Subject: [PATCH 42/94] Add Cache - Custom locals - Cached field setting - Cached Setting --- app/helpers/advanced_search_helper.rb | 12 ++++++------ app/helpers/cache_helper.rb | 9 +++++++++ app/models/field_setting.rb | 18 ++++++++++++++++++ app/models/setting.rb | 5 +++++ config/initializers/i18n/backend/custom.rb | 11 ++++++----- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index 7748ad70de..6686682547 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -227,13 +227,13 @@ def partner_header(key) def address_translation @address_translation ||= {} ['province', 'district', 'commune', 'village', 'birth_province', 'province_id', 'district_id', 'commune_id'].each do |key_translation| - @address_translation[key_translation.to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") + @address_translation[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") end - @address_translation['province_id'.to_sym] = FieldSetting.find_by(name: 'province_id').try(:label) || I18n.t('advanced_search.fields.province_id') - @address_translation['district_id'.to_sym] = FieldSetting.find_by(name: 'district_id').try(:label) || I18n.t('datagrid.columns.clients.district') - @address_translation['commune_id'.to_sym] = FieldSetting.find_by(name: 'commune_id').try(:label) || I18n.t('datagrid.columns.clients.commune') - @address_translation['village_id'.to_sym] = FieldSetting.find_by(name: 'village_id').try(:label) || I18n.t('datagrid.columns.clients.village') - @address_translation['birth_province_id'.to_sym] = FieldSetting.find_by(name: 'birth_province').try(:label) || I18n.t('datagrid.columns.clients.birth_province') + @address_translation['province_id'.to_sym] = FieldSetting.cache_by_name('province_id').try(:label) || I18n.t('advanced_search.fields.province_id') + @address_translation['district_id'.to_sym] = FieldSetting.cache_by_name('district_id').try(:label) || I18n.t('datagrid.columns.clients.district') + @address_translation['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id').try(:label) || I18n.t('datagrid.columns.clients.commune') + @address_translation['village_id'.to_sym] = FieldSetting.cache_by_name('village_id').try(:label) || I18n.t('datagrid.columns.clients.village') + @address_translation['birth_province_id'.to_sym] = FieldSetting.cache_by_name('birth_province').try(:label) || I18n.t('datagrid.columns.clients.birth_province') @address_translation end diff --git a/app/helpers/cache_helper.rb b/app/helpers/cache_helper.rb index e2d083d3aa..c864c90bda 100644 --- a/app/helpers/cache_helper.rb +++ b/app/helpers/cache_helper.rb @@ -2,4 +2,13 @@ module CacheHelper def user_cache_id [Apartment::Tenant.current, current_user.class.name, current_user.id] end + + def field_settings_cache_key + [Apartment::Tenant.current, 'field_settings'] + end + + def setting_cache_key + [Apartment::Tenant.current, 'current_setting'] + end + end \ No newline at end of file diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 3e1eb44e59..a940e0739e 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -1,4 +1,5 @@ class FieldSetting < ActiveRecord::Base + include CacheHelper self.inheritance_column = :_type_disabled translates :label @@ -8,6 +9,7 @@ class FieldSetting < ActiveRecord::Base scope :without_hidden_fields, -> { where(visible: true) } before_save :assign_type + after_commit :flush_cache def field_setting? type == 'field' @@ -30,9 +32,25 @@ def possible_key_match?(key_paths) end end + def self.cache_all + Rails.cache.fetch([Apartment::Tenant.current, 'field_settings']) { includes(:translations).to_a } + end + + def cache_object + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id]) { self } + end + + def self.cache_by_name(name) + Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', name]) { find_by(name: name) } + end + private def assign_type self.type ||= 'field' end + + def flush_cache + Rails.cache.delete(field_settings_cache_key) + end end diff --git a/app/models/setting.rb b/app/models/setting.rb index bf11359c77..7a91b8502b 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -1,5 +1,6 @@ class Setting < ActiveRecord::Base extend Enumerize + include CacheHelper has_paper_trail @@ -62,6 +63,10 @@ def max_assessment_duration max_assessment.send(assessment_frequency.to_sym) end + def self.cache_first + Rails.cache.fetch([Apartment::Tenant.current, 'current_setting']) { first } + end + private def custom_assessment_name diff --git a/config/initializers/i18n/backend/custom.rb b/config/initializers/i18n/backend/custom.rb index b9106dca32..793d2c7a8c 100644 --- a/config/initializers/i18n/backend/custom.rb +++ b/config/initializers/i18n/backend/custom.rb @@ -25,15 +25,16 @@ module I18n::Backend::Custom def load_translations(*filenames) filenames = I18n.load_path if filenames.empty? filenames.flatten.each { |filename| load_file(filename) } - - if ActiveRecord::Base.connection.table_exists? 'settings' - nepal_commune_mapping if Setting.first&.country_name == 'nepal' + if Apartment::Tenant.current != 'public' + if ActiveRecord::Base.connection.table_exists? 'settings' + nepal_commune_mapping if Setting.cache_first&.country_name == 'nepal' + end + load_custom_labels if ActiveRecord::Base.connection.table_exists? 'field_settings' end - load_custom_labels if ActiveRecord::Base.connection.table_exists? 'field_settings' end def load_custom_labels - FieldSetting.includes(:translations).find_each do |field_setting| + FieldSetting.cache_all.each do |field_setting| data = translations[I18n.locale] data.extend(HashDeepTraverse) From 6f1fbabbf9bad39e606f5cf1f7c7880661bf6189 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 25 Mar 2022 16:59:49 +0700 Subject: [PATCH 43/94] fixed error in app/controllers/concerns/client_advanced_searches_concern.rb#fetch_advanced_search_queries --- .../concerns/client_advanced_searches_concern.rb | 10 +++++----- app/models/advanced_search.rb | 10 ++++++++++ app/models/user.rb | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index 41841201eb..ccc8fff835 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -49,9 +49,9 @@ def build_advanced_search end def fetch_advanced_search_queries - Rails.cache.fetch(user_cache_id << "fetch_advanced_search_queries") do - @my_advanced_searches = current_user.advanced_searches.order(:name) - @other_advanced_searches = AdvancedSearch.includes(:user).non_of(current_user).order(:name) + @my_advanced_searches = current_user.cache_advance_saved_search + @other_advanced_searches = Rails.cache.fetch(user_cache_id << "other_advanced_search_queries") do + AdvancedSearch.includes(:user).non_of(current_user).order(:name) end end @@ -129,7 +129,7 @@ def get_hotline_fields ] } Rails.cache.fetch(user_cache_id << "get_hotline_fields") do - hotline_fields = AdvancedSearches::AdvancedSearchFields.new('hotline', args).render + hotline_fields = AdvancedSearches::AdvancedSearchFields.new('hotline', args).render @hotline_fields = get_client_hotline_fields + hotline_fields end end @@ -152,7 +152,7 @@ def get_client_hotline_fields dropdown_list_option: dropdown_list_options } @client_hotline_fields = AdvancedSearches::AdvancedSearchFields.new('concern_basic_fields', args).render - + end def hotline_text_type_list diff --git a/app/models/advanced_search.rb b/app/models/advanced_search.rb index e0af8c34cc..9b9d13a812 100644 --- a/app/models/advanced_search.rb +++ b/app/models/advanced_search.rb @@ -1,9 +1,13 @@ class AdvancedSearch < ActiveRecord::Base + include CacheHelper + has_paper_trail belongs_to :user validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :user_id } + after_commit :flush_cache + scope :non_of, ->(value) { where.not(user_id: value.id) } BROKEN_SAVE_SEARCH = [["demo", 19],["demo", 18],["demo", 39],["mtp", 15],["mtp", 25],["voice", 2], ["cif", 3],["cif", 4],["cif", 5],["cif", 6],["cif", 7],["cif", 66],["cif", 64], @@ -26,6 +30,12 @@ def search_params def owner user.name end + + private + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'User', user_id, 'advance_saved_search']) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 807d0801a1..bbda2e8cbe 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -356,6 +356,10 @@ def populate_quantitative_types end end + def cache_advance_saved_search + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id, 'advance_saved_search']) { self.advanced_searches.order(:name).to_a } + end + private def toggle_referral_notification From 2406c84b6645aa8c6dbe5899097c0f455e10faa1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 28 Mar 2022 15:27:45 +0700 Subject: [PATCH 44/94] Cached Setting --- app/classes/ability.rb | 2 +- app/classes/advanced_searches/client_fields.rb | 4 ++-- .../families/family_fields.rb | 2 +- .../overdue_form_sql_builder.rb | 4 ++-- app/classes/advanced_searches/rule_fields.rb | 2 +- app/classes/client_columns_visibility.rb | 2 +- app/classes/community_columns_visibility.rb | 2 +- app/classes/csi_statistic.rb | 2 +- app/classes/family_columns_visibility.rb | 2 +- app/classes/partner_columns_visibility.rb | 2 +- app/classes/user_notification.rb | 2 +- app/controllers/api/v1/settings_controller.rb | 2 +- app/controllers/application_controller.rb | 8 ++++---- app/controllers/calls_controller.rb | 2 +- app/controllers/care_plans_controller.rb | 2 +- app/controllers/clients_controller.rb | 4 ++-- app/controllers/dashboards_controller.rb | 2 +- app/controllers/families_controller.rb | 2 +- app/controllers/field_settings_controller.rb | 2 +- app/controllers/government_forms_controller.rb | 2 +- app/controllers/partners_controller.rb | 2 +- app/controllers/settings_controller.rb | 6 +++--- app/grids/client_grid.rb | 18 +++++++++--------- app/grids/family_grid.rb | 2 +- app/helpers/advanced_search_helper.rb | 4 ++-- app/helpers/application_helper.rb | 8 ++++---- app/helpers/case_conference_helper.rb | 2 +- app/helpers/clients_helper.rb | 16 ++++++++-------- app/helpers/families_helper.rb | 2 +- app/mailers/case_worker_mailer.rb | 4 ++-- app/mailers/remind_manager_mailer.rb | 2 +- app/models/assessment.rb | 4 ++-- app/models/case_conference.rb | 2 +- app/models/case_note.rb | 4 ++-- app/models/client.rb | 2 +- app/models/concerns/csi_concern.rb | 4 ++-- app/models/custom_assessment_setting.rb | 13 +++++++++++++ app/models/custom_field.rb | 9 +++++++++ app/models/custom_field_property.rb | 2 +- app/models/field_setting.rb | 13 ++++++++++++- app/models/internal_referral.rb | 2 +- app/models/setting.rb | 6 ++++++ app/models/user.rb | 4 ++-- app/policies/assessment_policy.rb | 8 ++++---- app/policies/care_plan_policy.rb | 8 ++++---- app/policies/case_conference_policy.rb | 8 ++++---- app/policies/community_policy.rb | 2 +- app/policies/setting_policy.rb | 2 +- .../client_share_external_serializer.rb | 2 +- .../organization_client_serializer.rb | 2 +- app/services/case_note_overdue.rb | 2 +- app/services/ngo_usage_report.rb | 10 +++++----- app/views/clients/_other_detail.haml | 6 +++--- app/views/clients/show.haml | 2 +- app/views/datagrid/_form.html.haml | 2 +- app/views/layouts/_notification.haml | 12 ++++++------ app/views/layouts/_side_menu.haml | 2 +- app/views/referral_sources/version.html.haml | 2 +- app/views/settings/custom_labels.haml | 6 +++--- 59 files changed, 149 insertions(+), 110 deletions(-) diff --git a/app/classes/ability.rb b/app/classes/ability.rb index 241e4f37ab..e7fbfc3b5f 100644 --- a/app/classes/ability.rb +++ b/app/classes/ability.rb @@ -127,7 +127,7 @@ def initialize(user) can :manage, Family end - cannot :read, Community if Setting.first.hide_community? + cannot :read, Community if Setting.cache_first.hide_community? cannot :read, Partner if FieldSetting.hidden_group?('partner') end diff --git a/app/classes/advanced_searches/client_fields.rb b/app/classes/advanced_searches/client_fields.rb index bec9cc6e0a..fdb21c43b3 100644 --- a/app/classes/advanced_searches/client_fields.rb +++ b/app/classes/advanced_searches/client_fields.rb @@ -213,7 +213,7 @@ def referral_from_options end def setting_country_fields - country = Setting.first.try(:country_name) || 'cambodia' + country = Setting.cache_first.try(:country_name) || 'cambodia' case country when 'lesotho' { @@ -250,7 +250,7 @@ def setting_country_fields end def rated_id_poor - if Setting.first.country_name == 'cambodia' + if Setting.cache_first.country_name == 'cambodia' [['rated_for_id_poor', [Client::CLIENT_LEVELS, I18n.t('clients.level').values].transpose.to_h]] else [] diff --git a/app/classes/advanced_searches/families/family_fields.rb b/app/classes/advanced_searches/families/family_fields.rb index c75c2c0835..86f2bd96c3 100644 --- a/app/classes/advanced_searches/families/family_fields.rb +++ b/app/classes/advanced_searches/families/family_fields.rb @@ -12,7 +12,7 @@ def initialize(options = {}) @user = options[:user] @pundit_user = options[:pundit_user] @called_in = options[:called_in] - @current_setting = Setting.first + @current_setting = Setting.cache_first address_translation end diff --git a/app/classes/advanced_searches/overdue_form_sql_builder.rb b/app/classes/advanced_searches/overdue_form_sql_builder.rb index bb06694eff..1f188b76d8 100644 --- a/app/classes/advanced_searches/overdue_form_sql_builder.rb +++ b/app/classes/advanced_searches/overdue_form_sql_builder.rb @@ -27,7 +27,7 @@ def get_sql def no_case_note client_ids = [] - setting = Setting.first + setting = Setting.cache_first max_case_note = setting.try(:max_case_note) || 30 case_note_frequency = setting.try(:case_note_frequency) || 'day' case_note_period = max_case_note.send(case_note_frequency).ago @@ -73,7 +73,7 @@ def has_overdue_forms def has_overdue_assessment ids = [] - setting = Setting.first + setting = Setting.cache_first Client.joins(:assessments).active_accepted_status.each do |client| next if !client.eligible_default_csi? && !(client.assessments.customs.any?) custom_assessment_setting_ids = client.assessments.customs.map{|ca| ca.domains.pluck(:custom_assessment_setting_id ) }.flatten.uniq diff --git a/app/classes/advanced_searches/rule_fields.rb b/app/classes/advanced_searches/rule_fields.rb index 5aefd2e5db..43970e8584 100644 --- a/app/classes/advanced_searches/rule_fields.rb +++ b/app/classes/advanced_searches/rule_fields.rb @@ -135,7 +135,7 @@ def referral_from_options end def setting_country_fields - country = Setting.first.try(:country_name) || 'cambodia' + country = Setting.cache_first.try(:country_name) || 'cambodia' case country when 'cambodia' { diff --git a/app/classes/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index a1f947e80a..cafaa58b7f 100644 --- a/app/classes/client_columns_visibility.rb +++ b/app/classes/client_columns_visibility.rb @@ -180,7 +180,7 @@ def columns_collection def visible_columns return [] if @grid.nil? @grid.column_names = [] - client_default_columns = Setting.first.try(:client_default_columns) + client_default_columns = Setting.cache_first.try(:client_default_columns) params = @params.keys.select{ |k| k.match(/\_$/) } if params.present? && client_default_columns.present? defualt_columns = params - client_default_columns diff --git a/app/classes/community_columns_visibility.rb b/app/classes/community_columns_visibility.rb index 66c5579426..2d3fde1310 100644 --- a/app/classes/community_columns_visibility.rb +++ b/app/classes/community_columns_visibility.rb @@ -10,7 +10,7 @@ def columns_collection def visible_columns @grid.column_names = [] - community_default_columns = Setting.first.try(:community_default_columns) + community_default_columns = Setting.cache_first.try(:community_default_columns) params = @params.keys.select { |k| k.match(/\_$/) } if params.present? && community_default_columns.present? defualt_columns = params - community_default_columns diff --git a/app/classes/csi_statistic.rb b/app/classes/csi_statistic.rb index 3e3c6980ef..b9f31c071e 100644 --- a/app/classes/csi_statistic.rb +++ b/app/classes/csi_statistic.rb @@ -1,7 +1,7 @@ class CsiStatistic def initialize(clients) @clients = clients - @@setting = Setting.first + @@setting = Setting.cache_first end def assessment_domain_score diff --git a/app/classes/family_columns_visibility.rb b/app/classes/family_columns_visibility.rb index 5199b966d7..3e7d660ae3 100644 --- a/app/classes/family_columns_visibility.rb +++ b/app/classes/family_columns_visibility.rb @@ -12,7 +12,7 @@ def columns_collection def visible_columns @grid.column_names = [] - family_default_columns = Setting.first.try(:family_default_columns) + family_default_columns = Setting.cache_first.try(:family_default_columns) params = @params.keys.select{ |k| k.match(/\_$/) } if params.present? && family_default_columns.present? defualt_columns = params - family_default_columns diff --git a/app/classes/partner_columns_visibility.rb b/app/classes/partner_columns_visibility.rb index 7de1eb0318..5581f68cce 100644 --- a/app/classes/partner_columns_visibility.rb +++ b/app/classes/partner_columns_visibility.rb @@ -24,7 +24,7 @@ def columns_collection def visible_columns @grid.column_names = [] - partner_default_columns = Setting.first.try(:partner_default_columns) + partner_default_columns = Setting.cache_first.try(:partner_default_columns) params = @params.keys.select{ |k| k.match(/\_$/) } if params.present? && partner_default_columns.present? defualt_columns = params - partner_default_columns diff --git a/app/classes/user_notification.rb b/app/classes/user_notification.rb index 819e6244cf..c0df9b355f 100644 --- a/app/classes/user_notification.rb +++ b/app/classes/user_notification.rb @@ -6,7 +6,7 @@ class UserNotification attr_accessor :upcoming_csi_assessments_count, :upcoming_custom_csi_assessments_count def initialize(user, clients) - @current_setting = Setting.first + @current_setting = Setting.cache_first @user = user @clients = clients @assessments = @user.assessment_either_overdue_or_due_today diff --git a/app/controllers/api/v1/settings_controller.rb b/app/controllers/api/v1/settings_controller.rb index 62621f1df3..10c5788849 100644 --- a/app/controllers/api/v1/settings_controller.rb +++ b/app/controllers/api/v1/settings_controller.rb @@ -3,7 +3,7 @@ module V1 class SettingsController < Api::V1::BaseApiController def index - render json: Setting.first + render json: Setting.cache_first end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 270c58247a..4e2be8fb58 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -34,12 +34,12 @@ def current_organization end def current_setting - @current_setting = Setting.first + @current_setting = Setting.cache_first end def field_settings return @field_settings if defined? @field_settings - @field_settings = FieldSetting.where('for_instances IS NULL OR for_instances iLIKE ?', "#{current_organization.short_name}") + FieldSetting.cache_query_find_by_ngo_name end def pundit_user @@ -49,7 +49,7 @@ def pundit_user protected def address_translation - @address_translation ||= view_context.address_translation + @address_translation = view_context.address_translation end @@ -97,7 +97,7 @@ def override_translation end def default_url_options(options = {}) - country = Setting.first.try(:country_name) || params[:country] || 'cambodia' + country = Setting.cache_first.try(:country_name) || params[:country] || 'cambodia' local = params[:locale] if params[:locale] && I18n.available_locales.include?(params[:locale].to_sym) { locale: local || I18n.locale, country: country }.merge(options) end diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_controller.rb index 85b0a603e3..5543fa561e 100644 --- a/app/controllers/calls_controller.rb +++ b/app/controllers/calls_controller.rb @@ -133,7 +133,7 @@ def referral_source_name(referral_source) end def country_address_fields - selected_country = Setting.first.try(:country_name) || params[:country] + selected_country = Setting.cache_first.try(:country_name) || params[:country] current_org = Organization.current.short_name Organization.switch_to 'shared' @birth_provinces = [] diff --git a/app/controllers/care_plans_controller.rb b/app/controllers/care_plans_controller.rb index fcd3041f98..21d11233c4 100644 --- a/app/controllers/care_plans_controller.rb +++ b/app/controllers/care_plans_controller.rb @@ -15,7 +15,7 @@ def index def new @assessment = @client.assessments.find_by(id: params[:assessment]) @prev_care_plan = @client.care_plans.last - @care_plan = Setting.first.try(:use_previous_care_plan) && @prev_care_plan || @client.care_plans.new() + @care_plan = Setting.cache_first.try(:use_previous_care_plan) && @prev_care_plan || @client.care_plans.new() end def create diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index a3dba43101..b860ef4114 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -23,7 +23,7 @@ class ClientsController < AdminController before_action :validate_referral, only: [:new, :edit] def index - @client_default_columns = Setting.first.try(:client_default_columns) + @client_default_columns = Setting.cache_first.try(:client_default_columns) if params[:advanced_search_id] current_advanced_search = AdvancedSearch.find(params[:advanced_search_id]) @visible_fields = current_advanced_search.field_visible @@ -306,7 +306,7 @@ def set_association end def country_address_fields - selected_country = Setting.first.try(:country_name) || params[:country] + selected_country = Setting.cache_first.try(:country_name) || params[:country] current_org = Organization.current.short_name Organization.switch_to 'shared' @birth_provinces = [] diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index e012d2a867..c113af487d 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -48,7 +48,7 @@ def find_clients clients_duetoday = [] clients_upcoming = [] clients = [] - @setting = Setting.first + @setting = Setting.cache_first _clients = Client.accessible_by(current_ability).active_accepted_status.distinct eligible_clients = active_young_clients(_clients, @setting) eligible_clients.each do |client| diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index d2550d87fd..583d87e972 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -16,7 +16,7 @@ class FamiliesController < AdminController before_action :load_quantative_types, only: [:new, :edit, :create, :update] def index - @default_columns = Setting.first.try(:family_default_columns) + @default_columns = Setting.cache_first.try(:family_default_columns) @family_grid = FamilyGrid.new(params.fetch(:family_grid, {}).merge!(dynamic_columns: column_form_builder)) @family_grid = @family_grid.scope { |scope| scope.accessible_by(current_ability) } @family_columns ||= FamilyColumnsVisibility.new(@family_grid, params.merge(column_form_builder: column_form_builder)) diff --git a/app/controllers/field_settings_controller.rb b/app/controllers/field_settings_controller.rb index 01693f7b33..834143c4cf 100644 --- a/app/controllers/field_settings_controller.rb +++ b/app/controllers/field_settings_controller.rb @@ -1,6 +1,6 @@ class FieldSettingsController < AdminController def index - @field_settings = FieldSetting.where('for_instances IS NULL OR for_instances iLIKE ?', "#{current_organization.short_name}").includes(:translations).order(:group, :name) + @field_settings = FieldSetting.cache_query_find_by_ngo_name end def bulk_update diff --git a/app/controllers/government_forms_controller.rb b/app/controllers/government_forms_controller.rb index 6e4553be40..90aba4146c 100644 --- a/app/controllers/government_forms_controller.rb +++ b/app/controllers/government_forms_controller.rb @@ -190,7 +190,7 @@ def find_form_name def find_static_association @user = @government_form.case_worker_info - @setting = Setting.first + @setting = Setting.cache_first @guardian = @client.family.family_members.find_by(guardian: true) if @client.family.present? @father = @client.family.family_members.find_by(guardian: true, relation: 'Father') if @client.family.present? @mother = @client.family.family_members.find_by(guardian: true, relation: 'Mother') if @client.family.present? diff --git a/app/controllers/partners_controller.rb b/app/controllers/partners_controller.rb index ec39566ffd..9b76a286f1 100644 --- a/app/controllers/partners_controller.rb +++ b/app/controllers/partners_controller.rb @@ -11,7 +11,7 @@ class PartnersController < AdminController before_action :find_association, except: [:index, :destroy, :version] def index - @default_columns = Setting.first.try(:partner_default_columns) + @default_columns = Setting.cache_first.try(:partner_default_columns) @partner_grid = PartnerGrid.new(params.fetch(:partner_grid, {}).merge!(dynamic_columns: @custom_form_fields)) @partner_columns ||= PartnerColumnsVisibility.new(@partner_grid, params.merge(column_form_builder: @custom_form_fields)) @partner_columns.visible_columns diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 08e13c5f34..b35ae544c0 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -109,8 +109,8 @@ def test_client def country_address_fields @provinces = Province.order(:name) - @districts = Setting.first.province.present? ? Setting.first.province.districts.order(:name) : [] - @communes = Setting.first.district.present? ? Setting.first.district.communes.order(:name_kh, :name_en) : [] + @districts = Setting.cache_first.province.present? ? Setting.cache_first.province.districts.order(:name) : [] + @communes = Setting.cache_first.district.present? ? Setting.cache_first.district.communes.order(:name_kh, :name_en) : [] end def setting_params @@ -188,7 +188,7 @@ def partner_default_columns end def international_address_columns - country = Setting.first.try(:country_name) || params[:country] + country = Setting.cache_first.try(:country_name) || params[:country] case country when 'thailand' %w(province_id_ birth_province_id_ district_ subdistrict_ postal_code_ plot_ road_) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index edea97967d..347a42efe3 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -207,7 +207,7 @@ def quantitative_cases filter(:assessments_due_to, :enum, select: Assessment::DUE_STATES, header: -> { I18n.t('datagrid.columns.clients.assessments_due_to') }) do |value, scope| ids = [] - setting = Setting.first + setting = Setting.cache_first if value == Assessment::DUE_STATES[0] Client.active_accepted_status.each do |client| next if !client.eligible_default_csi? && !(client.assessments.customs.present?) @@ -360,7 +360,7 @@ def program_stream_options end def self.case_note_overdue_ids - setting = Setting.first + setting = Setting.cache_first max_case_note = setting.try(:max_case_note) || 30 case_note_frequency = setting.try(:case_note_frequency) || 'day' case_note_period = max_case_note.send(case_note_frequency).ago @@ -406,17 +406,17 @@ def self.case_note_overdue_ids column(:kid_id, order:'clients.kid_id', header: -> { custom_id_translation('custom_id2') }) def self.custom_id_translation(type) - if I18n.locale == :en || Setting.first.country_name == 'lesotho' + if I18n.locale == :en || Setting.cache_first.country_name == 'lesotho' if type == 'custom_id1' - Setting.first.custom_id1_latin.present? ? Setting.first.custom_id1_latin : I18n.t('clients.other_detail.custom_id_number1') + Setting.cache_first.custom_id1_latin.present? ? Setting.cache_first.custom_id1_latin : I18n.t('clients.other_detail.custom_id_number1') else - Setting.first.custom_id2_latin.present? ? Setting.first.custom_id2_latin : I18n.t('clients.other_detail.custom_id_number2') + Setting.cache_first.custom_id2_latin.present? ? Setting.cache_first.custom_id2_latin : I18n.t('clients.other_detail.custom_id_number2') end else if type == 'custom_id1' - Setting.first.custom_id1_local.present? ? Setting.first.custom_id1_local : I18n.t('clients.other_detail.custom_id_number1') + Setting.cache_first.custom_id1_local.present? ? Setting.cache_first.custom_id1_local : I18n.t('clients.other_detail.custom_id_number1') else - Setting.first.custom_id2_local.present? ? Setting.first.custom_id2_local : I18n.t('clients.other_detail.custom_id_number2') + Setting.cache_first.custom_id2_local.present? ? Setting.cache_first.custom_id2_local : I18n.t('clients.other_detail.custom_id_number2') end end end @@ -450,7 +450,7 @@ def self.custom_id_translation(type) end def self.dynamic_local_name - country = Setting.first.country_name + country = Setting.cache_first.country_name I18n.locale.to_s == 'en' ? COUNTRY_LANG[country] : '' end @@ -714,7 +714,7 @@ def call_fields end dynamic do - country = Setting.first.try(:country_name) || 'cambodia' + country = Setting.cache_first.try(:country_name) || 'cambodia' case country when 'cambodia' column(:current_address, order: 'clients.current_address', header: -> { I18n.t('datagrid.columns.clients.current_address') }) diff --git a/app/grids/family_grid.rb b/app/grids/family_grid.rb index 40dda81a97..923cdb1da9 100644 --- a/app/grids/family_grid.rb +++ b/app/grids/family_grid.rb @@ -472,7 +472,7 @@ def filer_section(filter_name) end dynamic do - if !Setting.first.hide_family_case_management_tool? + if !Setting.cache_first.hide_family_case_management_tool? column(:all_custom_csi_assessments, header: -> { I18n.t('datagrid.columns.all_custom_csi_assessments', assessment: t('families.family_assessment')) }, html: true) do |object| render partial: 'families/all_csi_assessments', locals: { object: object.assessments.customs } end diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index 6686682547..95649bb555 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -225,7 +225,7 @@ def partner_header(key) end def address_translation - @address_translation ||= {} + @address_translation = {} ['province', 'district', 'commune', 'village', 'birth_province', 'province_id', 'district_id', 'commune_id'].each do |key_translation| @address_translation[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") end @@ -251,7 +251,7 @@ def save_search_params(search_params) end def custom_id_translation(type) - @customer_id_setting ||= Setting.first + @customer_id_setting ||= Setting.cache_first if I18n.locale == :en || @customer_id_setting.country_name == 'lesotho' if type == 'custom_id1' @customer_id_setting.custom_id1_latin.present? ? @customer_id_setting.custom_id1_latin : I18n.t('clients.other_detail.custom_id_number1') diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b8192551f7..e9a05b04ea 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -284,13 +284,13 @@ def assessments_notification_label if @notification.any_overdue_assessments? && @notification.any_due_today_assessments? overdue_count = @notification.overdue_assessments_count due_today_count = @notification.due_today_assessments_count - "#{I18n.t('layouts.notification.assessments_count', count: overdue_count)} #{Setting.first.default_assessment} #{I18n.t('layouts.notification.overdue_assessments', count: overdue_count)} #{I18n.t('layouts.notification.overdue_and_due_today_count', count: due_today_count)}" + "#{I18n.t('layouts.notification.assessments_count', count: overdue_count)} #{Setting.cache_first.default_assessment} #{I18n.t('layouts.notification.overdue_assessments', count: overdue_count)} #{I18n.t('layouts.notification.overdue_and_due_today_count', count: due_today_count)}" elsif @notification.any_overdue_assessments? count = @notification.overdue_assessments_count - "#{I18n.t('layouts.notification.assessments_count', count: count)} #{Setting.first.default_assessment} #{I18n.t('layouts.notification.overdue_assessments', count: count)}" + "#{I18n.t('layouts.notification.assessments_count', count: count)} #{Setting.cache_first.default_assessment} #{I18n.t('layouts.notification.overdue_assessments', count: count)}" else count = @notification.due_today_assessments_count - "#{I18n.t('layouts.notification.assessments_count', count: count)} #{Setting.first.default_assessment} #{I18n.t('layouts.notification.due_today_assessments', count: count)}" + "#{I18n.t('layouts.notification.assessments_count', count: count)} #{Setting.cache_first.default_assessment} #{I18n.t('layouts.notification.due_today_assessments', count: count)}" end end @@ -356,7 +356,7 @@ def enable_any_csi_tools? end def enable_default_assessment? - Setting.first.try(:enable_default_assessment) + Setting.cache_first.try(:enable_default_assessment) end def enable_custom_assessment? diff --git a/app/helpers/case_conference_helper.rb b/app/helpers/case_conference_helper.rb index 9cfc0f740a..af814700cc 100644 --- a/app/helpers/case_conference_helper.rb +++ b/app/helpers/case_conference_helper.rb @@ -4,7 +4,7 @@ def case_conference_readable? end def case_conference_editable?(meeting_date) - setting = Setting.first + setting = Setting.cache_first max_case_case = setting.try(:case_conference_limit).zero? ? 2 : setting.try(:case_conference_limit) case_note_frequency = setting.try(:case_conference_frequency) meeting_date <= max_case_case.send(case_note_frequency).ago diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 1720bf2c7f..60c24af440 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -490,7 +490,7 @@ def status_exited?(value) end def selected_country - country = Setting.first.try(:country_name) || params[:country].presence + country = Setting.cache_first.try(:country_name) || params[:country].presence country.nil? ? 'cambodia' : country end @@ -1188,9 +1188,9 @@ def date_format(date) end def country_scope_label_translation - return '' if Setting.first.try(:country_name) == 'nepal' + return '' if Setting.cache_first.try(:country_name) == 'nepal' if I18n.locale.to_s == 'en' - country_name = Setting.first.try(:country_name) + country_name = Setting.cache_first.try(:country_name) case country_name when 'cambodia' then '(Khmer)' when 'thailand' then '(Thai)' @@ -1367,17 +1367,17 @@ def translate_exit_reasons(reasons) end def custom_id_translation(type) - if I18n.locale != :km || Setting.first.country_name != 'lesotho' + if I18n.locale != :km || Setting.cache_first.country_name != 'lesotho' if type == 'custom_id1' - Setting.first.custom_id1_latin.present? ? Setting.first.custom_id1_latin : I18n.t("#{I18n.locale.to_s}.clients.other_detail.custom_id_number1") + Setting.cache_first.custom_id1_latin.present? ? Setting.cache_first.custom_id1_latin : I18n.t("#{I18n.locale.to_s}.clients.other_detail.custom_id_number1") else - Setting.first.custom_id2_latin.present? ? Setting.first.custom_id2_latin : I18n.t('other_detail.custom_id_number2') + Setting.cache_first.custom_id2_latin.present? ? Setting.cache_first.custom_id2_latin : I18n.t('other_detail.custom_id_number2') end else if type == 'custom_id1' - Setting.first.custom_id1_local.present? ? Setting.first.custom_id1_local : I18n.t('other_detail.custom_id_number1') + Setting.cache_first.custom_id1_local.present? ? Setting.cache_first.custom_id1_local : I18n.t('other_detail.custom_id_number1') else - Setting.first.custom_id2_local.present? ? Setting.first.custom_id2_local : I18n.t('other_detail.custom_id_number2') + Setting.cache_first.custom_id2_local.present? ? Setting.cache_first.custom_id2_local : I18n.t('other_detail.custom_id_number2') end end end diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb index 77f7816de6..1ab6586f37 100644 --- a/app/helpers/families_helper.rb +++ b/app/helpers/families_helper.rb @@ -53,7 +53,7 @@ def family_case_history(object) end def additional_columns - unless Setting.first.try(:hide_family_case_management_tool?) + unless Setting.cache_first.try(:hide_family_case_management_tool?) { date_of_custom_assessments: I18n.t('datagrid.columns.date_of_custom_assessments', assessment: I18n.t('families.show.assessment')), all_custom_csi_assessments: I18n.t('datagrid.columns.all_custom_csi_assessments', assessment: I18n.t('families.show.assessment')), diff --git a/app/mailers/case_worker_mailer.rb b/app/mailers/case_worker_mailer.rb index 146e8c0bc0..c539316126 100644 --- a/app/mailers/case_worker_mailer.rb +++ b/app/mailers/case_worker_mailer.rb @@ -27,7 +27,7 @@ def notify_upcoming_csi_weekly(client) default = @client.assessments.most_recents.first.try(:default) if default - @name = Setting.first.default_assessment + @name = Setting.cache_first.default_assessment send_bulk_email(recievers, "Upcoming #{@name}") else CustomAssessmentSetting.only_enable_custom_assessment.find_each do |custom_assessment_setting| @@ -46,7 +46,7 @@ def notify_incomplete_daily_csi_assessments(client, custom_assessment_setting = default = assessment.try(:default) @overdue_date = assessment.created_at.to_date + 7 if default - @name = Setting.first.default_assessment + @name = Setting.cache_first.default_assessment send_bulk_email(recievers, "Incomplete #{@name}") else if custom_assessment_setting diff --git a/app/mailers/remind_manager_mailer.rb b/app/mailers/remind_manager_mailer.rb index 0786b55954..8a183da650 100644 --- a/app/mailers/remind_manager_mailer.rb +++ b/app/mailers/remind_manager_mailer.rb @@ -2,7 +2,7 @@ class RemindManagerMailer < ApplicationMailer def case_worker_overdue_tasks_notify(manager, case_workers, org_name) @org_name = org_name @manager = manager - @setting = Setting.first + @setting = Setting.cache_first @csi_setting = @setting.enable_default_assessment || @setting.enable_custom_assessment @subject = @csi_setting ? 'Case workers have overdue assessments, tasks or forms that are more than a week overdue' : 'Case workers have overdue tasks or forms that are more than a week overdue' @case_workers = case_workers_overdue_tasks(case_workers) diff --git a/app/models/assessment.rb b/app/models/assessment.rb index fc2f91e55f..ace945f63a 100644 --- a/app/models/assessment.rb +++ b/app/models/assessment.rb @@ -45,7 +45,7 @@ def set_assessment_completed def check_reason_and_score empty_assessment_domains = [] - setting = Setting.first + setting = Setting.cache_first is_ratanak = Organization.ratanak? assessment_domains.each do |assessment_domain| if is_ratanak @@ -160,7 +160,7 @@ def allow_create end def must_be_enable - enable = default? ? Setting.first.enable_default_assessment : Setting.first.enable_custom_assessment + enable = default? ? Setting.cache_first.enable_default_assessment : Setting.cache_first.enable_custom_assessment enable || family ? true : errors.add(:base, 'Assessment tool must be enable in setting') end diff --git a/app/models/case_conference.rb b/app/models/case_conference.rb index c24e306a02..5641dc235f 100644 --- a/app/models/case_conference.rb +++ b/app/models/case_conference.rb @@ -39,7 +39,7 @@ def case_conference_order_by_domain_name end def can_create_case_conference? - setting = Setting.first + setting = Setting.cache_first assessment_period = setting.max_assessment assessment_frequency = setting.assessment_frequency assessment_min_max = assessment_period.send(assessment_frequency) diff --git a/app/models/case_note.rb b/app/models/case_note.rb index ba567f7259..1d5d0eb84f 100644 --- a/app/models/case_note.rb +++ b/app/models/case_note.rb @@ -85,7 +85,7 @@ def service_delivery_task(param, case_note_tasks) end def is_editable? - setting = Setting.first + setting = Setting.cache_first return true if setting.try(:case_note_edit_limit).zero? case_note_edit_limit = setting.try(:case_note_edit_limit).zero? ? 2 : setting.try(:case_note_edit_limit) edit_frequency = setting.try(:case_note_edit_frequency) @@ -107,7 +107,7 @@ def existence_domain_groups end def enable_default_assessment? - setting = Setting.first + setting = Setting.cache_first setting && setting.enable_default_assessment end diff --git a/app/models/client.rb b/app/models/client.rb index 2e47b2dbd9..58b44106c6 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -818,7 +818,7 @@ def remove_tasks(case_worker) end def current_setting - @current_setting ||= Setting.first + @current_setting ||= Setting.cache_first end def delete_referee diff --git a/app/models/concerns/csi_concern.rb b/app/models/concerns/csi_concern.rb index 695cc8329e..ab61f75858 100644 --- a/app/models/concerns/csi_concern.rb +++ b/app/models/concerns/csi_concern.rb @@ -9,7 +9,7 @@ def eligible_default_csi? return true if date_of_birth.nil? client_age = age_as_years - age = Setting.first.age || 18 + age = Setting.cache_first.age || 18 client_age < age end @@ -92,7 +92,7 @@ def custom_next_assessment_date(user_activated_date = nil, custom_assessment_set def assessment_duration(duration, default = true, custom_assessment_setting_id=nil) if duration == 'max' - setting = Setting.first + setting = Setting.cache_first if default || self.class.name == 'Family' assessment_period = setting.max_assessment assessment_frequency = setting.assessment_frequency diff --git a/app/models/custom_assessment_setting.rb b/app/models/custom_assessment_setting.rb index 8ec9f0f30e..1a80d513f2 100644 --- a/app/models/custom_assessment_setting.rb +++ b/app/models/custom_assessment_setting.rb @@ -16,6 +16,8 @@ class CustomAssessmentSetting < ActiveRecord::Base scope :any_custom_assessment_enable?, -> { all.any? } scope :only_enable_custom_assessment, -> { where(enable_custom_assessment: true) } + after_commit :flush_cache + def max_assessment_duration max_custom_assessment.send(custom_assessment_frequency.to_sym) end @@ -29,4 +31,15 @@ def check_custom_assessment_frequency(name_of_day) end end + def self.cache_custom_assessment + Rails.cache.fetch([Apartment::Tenant.current, 'CustomAssessmentSetting', 'enable_custom_assessment', 'true']) do + only_enable_custom_assessment.to_a + end + end + + private + + def flush_cache + Rails.cache.fetch([Apartment::Tenant.current, 'CustomAssessmentSetting', 'enable_custom_assessment', 'true']) if enable_custom_assessment + end end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index f91f509944..5af80cccfb 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -27,6 +27,7 @@ class CustomField < ActiveRecord::Base after_create :build_permission before_save :set_ngo_name, if: -> { ngo_name.blank? } after_update :update_custom_field_label,:update_save_search, if: -> { fields_changed? } + after_commit :flush_cache scope :by_form_title, ->(value) { where('form_title iLIKE ?', "%#{value.squish}%") } scope :client_forms, -> { where(entity_type: 'Client') } @@ -81,6 +82,10 @@ def build_permission end end + def self.cache_object(id) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', id]) { find(id) } + end + private def update_custom_field_label @@ -121,4 +126,8 @@ def get_rules(queries, ss) end end end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) + end end diff --git a/app/models/custom_field_property.rb b/app/models/custom_field_property.rb index 4e36f306ee..da7ac658b5 100644 --- a/app/models/custom_field_property.rb +++ b/app/models/custom_field_property.rb @@ -32,7 +32,7 @@ def self.properties_by(value) end def is_editable? - setting = Setting.first + setting = Setting.cache_first return true if setting.try(:custom_field_limit).zero? max_duration = setting.try(:custom_field_limit).zero? ? 2 : setting.try(:custom_field_limit) custom_field_frequency = setting.try(:custom_field_frequency) diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index a940e0739e..76e18590ef 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -41,7 +41,15 @@ def cache_object end def self.cache_by_name(name) - Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', name]) { find_by(name: name) } + Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', name]) do + find_by(name: name) + end + end + + def self.cache_query_find_by_ngo_name + Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', 'cache_query_find_by_ngo_name']) do + where('for_instances IS NULL OR for_instances iLIKE ?', "%#{Apartment::Tenant.current}%").includes(:translations).order(:group, :name).to_a + end end private @@ -52,5 +60,8 @@ def assign_type def flush_cache Rails.cache.delete(field_settings_cache_key) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) + Rails.cache.delete([Apartment::Tenant::current, 'FieldSetting', self.name]) + Rails.cache.delete(field_settings_cache_key << 'cache_query_find_by_ngo_name') end end diff --git a/app/models/internal_referral.rb b/app/models/internal_referral.rb index 613cc95c1d..aab5c8fe42 100644 --- a/app/models/internal_referral.rb +++ b/app/models/internal_referral.rb @@ -15,7 +15,7 @@ class InternalReferral < ActiveRecord::Base after_save :sent_email_to_user def is_editable? - setting = Setting.first + setting = Setting.cache_first return true if setting.try(:internal_referral_limit).zero? max_duration = setting.try(:internal_referral_limit).zero? ? 2 : setting.try(:internal_referral_limit) internal_referral_frequency = setting.try(:internal_referral_frequency) diff --git a/app/models/setting.rb b/app/models/setting.rb index 7a91b8502b..16d38bcd18 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -41,6 +41,8 @@ class Setting < ActiveRecord::Base delegate :name, to: :province, prefix: true, allow_nil: true delegate :name, to: :district, prefix: true, allow_nil: true + after_commit :flush_cache + def delete_incomplete_after_period delete_incomplete_after_period_value.send(delete_incomplete_after_period_unit.to_sym) end @@ -72,4 +74,8 @@ def self.cache_first def custom_assessment_name errors.add(:custom_assessment, I18n.t('invalid_name')) if custom_assessment.downcase.include?('csi') end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'current_setting']) + end end diff --git a/app/models/user.rb b/app/models/user.rb index bbda2e8cbe..be9cfe83cc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -153,7 +153,7 @@ def client_status end def assessment_either_overdue_or_due_today - setting = Setting.first + setting = Setting.cache_first overdue = [] due_today = [] customized_overdue = [] @@ -177,7 +177,7 @@ def assessment_either_overdue_or_due_today end end.compact - CustomAssessmentSetting.only_enable_custom_assessment.each do |custom_assessment_setting| + CustomAssessmentSetting.cache_custom_assessment.each do |custom_assessment_setting| sql = "clients.id, (SELECT assessments.created_at FROM assessments WHERE assessments.client_id = clients.id AND assessments.default = false ORDER BY assessments.created_at DESC LIMIT 1) AS assessment_created_at" if self.deactivated_at.nil? clients_recent_custom_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.customs.most_recents.joins(:domains).where(domains: { custom_assessment_setting_id: custom_assessment_setting.id })).select(sql) diff --git a/app/policies/assessment_policy.rb b/app/policies/assessment_policy.rb index b37c289d35..30a4e6fcea 100644 --- a/app/policies/assessment_policy.rb +++ b/app/policies/assessment_policy.rb @@ -1,10 +1,10 @@ class AssessmentPolicy < ApplicationPolicy def index? - Setting.first.enable_default_assessment || Setting.first.enable_custom_assessment + Setting.cache_first.enable_default_assessment || Setting.cache_first.enable_custom_assessment end def show? - enable_assessment = record.default? ? Setting.first.enable_default_assessment : Setting.first.enable_custom_assessment + enable_assessment = record.default? ? Setting.cache_first.enable_default_assessment : Setting.cache_first.enable_custom_assessment readable_user = user.admin? || user.strategic_overviewer? ? true : user.permission&.assessments_readable enable_assessment && readable_user end @@ -13,7 +13,7 @@ def new?(value = nil, custom_assessment = nil) return false if user.strategic_overviewer? association = record.family_id ? 'family' : 'client' - setting = Setting.first + setting = Setting.cache_first if association == 'client' && (custom_assessment || !record.default?) custom_assessment = CustomAssessmentSetting.find(value) if value @@ -33,7 +33,7 @@ def new?(value = nil, custom_assessment = nil) end def edit? - setting = Setting.first + setting = Setting.cache_first enable_assessment = record.default? ? setting.enable_default_assessment? : setting.enable_custom_assessment? return true if enable_assessment && user.admin? diff --git a/app/policies/care_plan_policy.rb b/app/policies/care_plan_policy.rb index 837fc9bfc5..c93f46ba66 100644 --- a/app/policies/care_plan_policy.rb +++ b/app/policies/care_plan_policy.rb @@ -1,17 +1,17 @@ class CarePlanPolicy < ApplicationPolicy def index? - Setting.first.enable_default_assessment || Setting.first.enable_custom_assessment + Setting.cache_first.enable_default_assessment || Setting.cache_first.enable_custom_assessment end def show? - enable_assessment = record.default? ? Setting.first.enable_default_assessment : Setting.first.enable_custom_assessment + enable_assessment = record.default? ? Setting.cache_first.enable_default_assessment : Setting.cache_first.enable_custom_assessment readable_user = user.admin? || user.strategic_overviewer? ? true : user.permission&.assessments_readable enable_assessment && readable_user end def new?(value='', custom_assessment=nil) return false if user.strategic_overviewer? - setting = Setting.first + setting = Setting.cache_first if custom_assessment enable_assessment = record.default? ? setting.enable_default_assessment? && record.client.eligible_default_csi? : setting.enable_custom_assessment? && record.client.eligible_custom_csi?(custom_assessment) editable_user = user.admin? ? true : user.permission&.assessments_editable @@ -25,7 +25,7 @@ def new?(value='', custom_assessment=nil) def edit? return false if user.strategic_overviewer? - setting = Setting.first + setting = Setting.cache_first enable_assessment = record && (setting.enable_default_assessment? || setting.enable_custom_assessment?) return true if enable_assessment && user.admin? editable_user = user.admin? ? true : user.permission&.assessments_editable diff --git a/app/policies/case_conference_policy.rb b/app/policies/case_conference_policy.rb index 735901477d..493fbefb6e 100644 --- a/app/policies/case_conference_policy.rb +++ b/app/policies/case_conference_policy.rb @@ -2,11 +2,11 @@ class CaseConferencePolicy < ApplicationPolicy include CaseConferenceHelper def index? - Setting.first.enable_default_assessment || Setting.first.enable_custom_assessment + Setting.cache_first.enable_default_assessment || Setting.cache_first.enable_custom_assessment end def show? - enable_assessment = record.default? ? Setting.first.enable_default_assessment : Setting.first.enable_custom_assessment + enable_assessment = record.default? ? Setting.cache_first.enable_default_assessment : Setting.cache_first.enable_custom_assessment readable_user = user.admin? || user.strategic_overviewer? ? true : user.permission&.assessments_readable enable_assessment && readable_user end @@ -14,14 +14,14 @@ def show? def new?(value = '', custom_assessment = nil) return false if user.strategic_overviewer? - setting = Setting.first + setting = Setting.cache_first enable_assessment = setting.enable_default_assessment? editable_user = user.admin? ? true : user.permission&.assessments_editable enable_assessment && editable_user && record.can_create_case_conference? end def edit? - setting = Setting.first + setting = Setting.cache_first enable_assessment = setting.enable_default_assessment? return true if setting.enable_default_assessment? && user.admin? diff --git a/app/policies/community_policy.rb b/app/policies/community_policy.rb index 8aadce584c..d8bfc33a53 100644 --- a/app/policies/community_policy.rb +++ b/app/policies/community_policy.rb @@ -12,6 +12,6 @@ def show? alias destroy? show? def current_setting - @current_setting ||= Setting.first + @current_setting ||= Setting.cache_first end end diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb index 1c500f2379..e233b4b787 100644 --- a/app/policies/setting_policy.rb +++ b/app/policies/setting_policy.rb @@ -5,7 +5,7 @@ def index? def research_module? current_org = Organization.current - user.admin? && Setting.first.country_name == 'cambodia' && !current_org.demo? && !current_org.cccu? + user.admin? && Setting.cache_first.country_name == 'cambodia' && !current_org.demo? && !current_org.cccu? end def custom_labels? diff --git a/app/serializers/client_share_external_serializer.rb b/app/serializers/client_share_external_serializer.rb index e2edadf274..439441fc2e 100644 --- a/app/serializers/client_share_external_serializer.rb +++ b/app/serializers/client_share_external_serializer.rb @@ -41,7 +41,7 @@ def is_referred end def organization_address_code - setting = Setting.first + setting = Setting.cache_first return '' unless setting.province if setting.commune setting.commune.code diff --git a/app/serializers/organization_client_serializer.rb b/app/serializers/organization_client_serializer.rb index 3f46c54883..f16269c8fb 100644 --- a/app/serializers/organization_client_serializer.rb +++ b/app/serializers/organization_client_serializer.rb @@ -66,7 +66,7 @@ def case_worker_mobile end def organization_address_code - setting = Setting.first + setting = Setting.cache_first return '' unless setting.province if setting.commune diff --git a/app/services/case_note_overdue.rb b/app/services/case_note_overdue.rb index 4dee240a78..27290c3d3a 100644 --- a/app/services/case_note_overdue.rb +++ b/app/services/case_note_overdue.rb @@ -2,7 +2,7 @@ class CaseNoteOverdue def notify_user Organization.all.each do |org| Organization.switch_to org.short_name - setting = Setting.first + setting = Setting.cache_first max_case_note = setting.try(:max_case_note) || 30 case_note_frequency = setting.try(:case_note_frequency) || 'day' case_note_period = max_case_note.send(case_note_frequency).ago diff --git a/app/services/ngo_usage_report.rb b/app/services/ngo_usage_report.rb index cd59952900..81040d73b6 100644 --- a/app/services/ngo_usage_report.rb +++ b/app/services/ngo_usage_report.rb @@ -9,7 +9,7 @@ def usage_report(date_time) end def ngo_info(org) - country = Setting.first.present? ? Setting.first.country_name.downcase : '' + country = Setting.cache_first.present? ? Setting.cache_first.country_name.downcase : '' { ngo_name: org.full_name, ngo_short_name: org.short_name, @@ -188,7 +188,7 @@ def import_usage_report(date_time) Organization.order(:created_at).where.not(short_name: ['demo', 'tutorials', 'shared']).order(:created_at).each_with_index do |org, index| Organization.switch_to org.short_name - next if Setting.first.blank? + next if Setting.cache_first.blank? setting = ngo_info(org) ngo_users = ngo_users_info(beginning_of_month, end_of_month) @@ -203,9 +203,9 @@ def import_usage_report(date_time) cross_ngo_values = [setting[:ngo_name], *cross_ngo_referrals.values] cross_mosvy_values = [setting[:ngo_name], *cross_mosvy_referrals.values] - start_sharing_data = Setting.first.start_sharing_this_month(date_time) - stop_sharing_data = Setting.first.stop_sharing_this_month(date_time) - current_sharing_data = Setting.first.current_sharing_with_research_module + start_sharing_data = Setting.cache_first.start_sharing_this_month(date_time) + stop_sharing_data = Setting.cache_first.stop_sharing_this_month(date_time) + current_sharing_data = Setting.cache_first.current_sharing_with_research_module shared_data << mapping_learning_module_date(setting, start_sharing_data) stop_sharing_date << mapping_learning_module_date(setting, stop_sharing_data) diff --git a/app/views/clients/_other_detail.haml b/app/views/clients/_other_detail.haml index 926f37b1d9..7f7f6864e9 100644 --- a/app/views/clients/_other_detail.haml +++ b/app/views/clients/_other_detail.haml @@ -6,7 +6,7 @@ %legend= t('.other_information') .row - - if Setting.first.country_name == "cambodia" + - if Setting.cache_first.country_name == "cambodia" .col-xs-12.col-sm-6.col-md-4 = f.association :agencies, collection: @agencies, multiple: true, label_method: :name, value_method: :id, label: t('.agencies') .col-xs-12.col-sm-6.col-md-4 @@ -30,13 +30,13 @@ .i-check.has-been-in-government-care %label= "#{t('.has_been_in_government_care')}?" = f.input :has_been_in_government_care, as: :radio_buttons, collection: [['Yes', true],['No', false]], label: false, checked: @client.has_been_in_government_care.nil? ? '' : @client.has_been_in_government_care - - if Setting.first.country_name == "cambodia" + - if Setting.cache_first.country_name == "cambodia" .col-xs-12.col-sm-6.col-md-4 = f.input :code, label: custom_id_translation('custom_id1') - else .col-xs-12.col-sm-6.col-md-4.text-left = f.input :kid_id, label: custom_id_translation('custom_id2') - - if Setting.first.country_name == "cambodia" + - if Setting.cache_first.country_name == "cambodia" .row .col-xs-12.col-sm-6.col-md-4.text-left = f.input :kid_id, label: custom_id_translation('custom_id2') diff --git a/app/views/clients/show.haml b/app/views/clients/show.haml index 259bea8d28..14e49b1e92 100644 --- a/app/views/clients/show.haml +++ b/app/views/clients/show.haml @@ -390,7 +390,7 @@ %strong = @client.carer.try(:phone) - - if Setting.first.country_name == "cambodia" && !Organization.brc? + - if Setting.cache_first.country_name == "cambodia" && !Organization.brc? %tr %td.spacing-first-col = t('.is_the_client_rated_for_id_poor') diff --git a/app/views/datagrid/_form.html.haml b/app/views/datagrid/_form.html.haml index 61cb6a8f2b..735dd4de6e 100644 --- a/app/views/datagrid/_form.html.haml +++ b/app/views/datagrid/_form.html.haml @@ -178,7 +178,7 @@ = label_tag 'main_school_contact_', t('datagrid.columns.main_school_contact') - - if Setting.first.country_name == "cambodia" + - if Setting.cache_first.country_name == "cambodia" %li .visibility.col-sm-12 = check_box_tag 'rated_for_id_poor_', 'rated_for_id_poor', checked = saved_search_column_visibility('rated_for_id_poor_'), class: 'i-checks' diff --git a/app/views/layouts/_notification.haml b/app/views/layouts/_notification.haml index ddf127c6fd..88fc24e59c 100644 --- a/app/views/layouts/_notification.haml +++ b/app/views/layouts/_notification.haml @@ -30,7 +30,7 @@ .col-xs-11 = tasks_notification_label %li.divider - - if Setting.first.enable_default_assessment && (@notification.any_overdue_assessments? || @notification.any_due_today_assessments?) + - if Setting.cache_first.enable_default_assessment && (@notification.any_overdue_assessments? || @notification.any_due_today_assessments?) %li %div.align-link = link_to dashboards_path(user_id: current_user.id, assessments: true) do @@ -41,7 +41,7 @@ = assessments_notification_label %li.divider - - if Setting.first.enable_custom_assessment && (@notification.any_overdue_custom_assessments? || @notification.any_due_today_custom_assessments?) + - if Setting.cache_first.enable_custom_assessment && (@notification.any_overdue_custom_assessments? || @notification.any_due_today_custom_assessments?) %li %div.align-link = link_to dashboards_path(user_id: current_user.id, assessments: true) do @@ -80,7 +80,7 @@ .col-xs-11 = t('.client_case_note_due_today', count: @notification.client_case_note_due_today_count) %li.divider - - if Setting.first.enable_default_assessment && @notification.any_upcoming_csi_assessments? + - if Setting.cache_first.enable_default_assessment && @notification.any_upcoming_csi_assessments? %li %div.align-link = link_to notifications_path(assessment: 'upcoming', default: true) do @@ -88,9 +88,9 @@ .col-xs-1 %i.fa.fa-tasks.fa-fw.task-sign .col-xs-11 - = "#{t('.upcoming_assessments_count', count: @notification.upcoming_csi_assessments_count)} #{Setting.first.default_assessment}" + = "#{t('.upcoming_assessments_count', count: @notification.upcoming_csi_assessments_count)} #{Setting.cache_first.default_assessment}" - - if Setting.first.enable_custom_assessment && @notification.any_upcoming_custom_csi_assessments? + - if Setting.cache_first.enable_custom_assessment && @notification.any_upcoming_custom_csi_assessments? %li %div.align-link = link_to notifications_path(assessment: 'upcoming', default: false) do @@ -98,7 +98,7 @@ .col-xs-1 %i.fa.fa-tasks.fa-fw.task-sign .col-xs-11 - = "#{t('.upcoming_assessments_count', count: @notification.upcoming_custom_csi_assessments_count)} #{Setting.first.custom_assessment}" + = "#{t('.upcoming_assessments_count', count: @notification.upcoming_custom_csi_assessments_count)} #{Setting.cache_first.custom_assessment}" %li.divider -# - if current_user.admin? || current_user.ec_manager? -# - (83..90).each do |day| diff --git a/app/views/layouts/_side_menu.haml b/app/views/layouts/_side_menu.haml index 7019e24420..6a91ca913d 100644 --- a/app/views/layouts/_side_menu.haml +++ b/app/views/layouts/_side_menu.haml @@ -94,7 +94,7 @@ = link_to t('.csi_tools'), settings_path %li{ class: settings_menu_active('settings', 'default_columns') } = link_to t('.default_columns'), default_columns_settings_path - - if Setting.first.country_name == "cambodia" + - if Setting.cache_first.country_name == "cambodia" - if policy(current_setting).research_module? %li{ class: settings_menu_active('settings', 'research_module') } = link_to t('.research_module'), research_module_settings_path diff --git a/app/views/referral_sources/version.html.haml b/app/views/referral_sources/version.html.haml index c01b02289b..3bf4d1a60f 100644 --- a/app/views/referral_sources/version.html.haml +++ b/app/views/referral_sources/version.html.haml @@ -6,7 +6,7 @@ .col-xs-4.col-md-2 = link_to t('.back'), :back, class: 'btn btn-default btn-back-default btn-back' .col-xs-8.col-md-7.text-center - - if Setting.first.country_name != "cambodia" + - if Setting.cache_first.country_name != "cambodia" - if @referral_source.name_en.present? %u= "#{t('.modification_of')} #{@referral_source.name_en}" -else diff --git a/app/views/settings/custom_labels.haml b/app/views/settings/custom_labels.haml index 3ce8593ae1..78a85c1f8e 100644 --- a/app/views/settings/custom_labels.haml +++ b/app/views/settings/custom_labels.haml @@ -6,7 +6,7 @@ %h5.text-center= t('.custom_labels') .ibox-content .row - - if Setting.first.country_name == 'lesotho' + - if Setting.cache_first.country_name == 'lesotho' .col-xs-12.col-sm-4 .form-group = f.input :custom_id1_latin, label: t('.custom_id1_latin') @@ -19,12 +19,12 @@ = f.input :custom_id1_latin, label: t('.custom_id1_latin') .col-xs-12.col-sm-4 .form-group - = f.input :custom_id1_local, label: t("settings.custom_labels.#{Setting.first.country_name}.custom_id1_local") + = f.input :custom_id1_local, label: t("settings.custom_labels.#{Setting.cache_first.country_name}.custom_id1_local") .col-xs-12.col-sm-4 .form-group = f.input :custom_id2_latin, label: t('.custom_id2_latin') .col-xs-12.col-sm-4 .form-group - = f.input :custom_id2_local, label: t("settings.custom_labels.#{Setting.first.country_name}.custom_id2_local") + = f.input :custom_id2_local, label: t("settings.custom_labels.#{Setting.cache_first.country_name}.custom_id2_local") .ibox-footer = f.submit t('save'), name: 'custom_labels' ,class: 'btn btn-primary', data: { disable_with: t('saving') } From 9a2fcd5c7e18f69da8a707f503edce53bf4d12b0 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Mon, 28 Mar 2022 16:30:23 +0700 Subject: [PATCH 45/94] [IMP] Apply cache on geo refs OSC-13 --- app/controllers/application_controller.rb | 2 +- app/controllers/clients_controller.rb | 48 +++++++++++------------ app/controllers/communities_controller.rb | 8 ++-- app/controllers/districts_controller.rb | 2 +- app/controllers/families_controller.rb | 14 +++---- app/controllers/settings_controller.rb | 2 +- app/controllers/users_controller.rb | 2 +- app/models/agency.rb | 7 +++- app/models/client_type.rb | 7 +++- app/models/commune.rb | 16 +++++++- app/models/district.rb | 21 +++++++++- app/models/donor.rb | 7 +++- app/models/interviewee.rb | 7 +++- app/models/need.rb | 7 +++- app/models/problem.rb | 7 +++- app/models/province.rb | 19 +++++++++ app/models/subdistrict.rb | 11 +++++- 17 files changed, 139 insertions(+), 48 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4e2be8fb58..773d2d983a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -76,7 +76,7 @@ def configure_permitted_parameters def find_association @department = Department.order(:name) - @province = Province.order(:name) + @province = Province.cached_order_name end def set_locale diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index b860ef4114..85bc66c05d 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -275,13 +275,13 @@ def remove_blank_exit_reasons end def set_association - @agencies = Agency.order(:name) - @donors = Donor.order(:name) + @agencies = Agency.cached_order_name + @donors = Donor.cached_order_name @users = User.without_deleted_users.non_strategic_overviewers.order(:first_name, :last_name) - @interviewees = Interviewee.order(:created_at) - @client_types = ClientType.order(:created_at) - @needs = Need.order(:created_at) - @problems = Problem.order(:created_at) + @interviewees = Interviewee.cached_order_created_at + @client_types = ClientType.cached_order_created_at + @needs = Need.cached_order_created_at + @problems = Problem.cached_order_created_at subordinate_users = User.where('manager_ids && ARRAY[:user_id] OR id = :user_id', { user_id: current_user.id }).map(&:id) if current_user.admin? || current_user.hotline_officer? @@ -315,14 +315,14 @@ def country_address_fields if selected_country&.downcase == 'thailand' @current_provinces = Province.order(:name).where.not("name ILIKE ?", "%/%") - @districts = @client.province.present? ? @client.province.districts.order(:name) : [] - @subdistricts = @client.district.present? ? @client.district.subdistricts.order(:name) : [] + @districts = @client.province.present? ? @client.province.cached_districts : [] + @subdistricts = @client.district.present? ? @client.district.cached_subdistricts : [] - @referee_districts = @client.referee&.province.present? ? @client.referee.province.districts.order(:name) : [] - @referee_subdistricts = @client.referee.try(:district).present? ? @client.referee.district.subdistricts.order(:name) : [] + @referee_districts = @client.referee&.province.present? ? @client.referee.province.cached_districts : [] + @referee_subdistricts = @client.referee.try(:district).present? ? @client.referee.district.cached_subdistricts : [] - @carer_districts = @client.carer&.province.present? ? @client.carer.province.districts.order(:name) : [] - @carer_subdistricts = @client.carer.try(:district).present? ? @client.carer.district.subdistricts.order(:name) : [] + @carer_districts = @client.carer&.province.present? ? @client.carer.province.cached_districts : [] + @carer_subdistricts = @client.carer.try(:district).present? ? @client.carer.district.cached_subdistricts : [] elsif selected_country&.downcase == 'myanmar' @states = State.order(:name) @@ -331,18 +331,18 @@ def country_address_fields @referee_townships = @client.referee&.state.present? ? @client.referee.state.townships.order(:name) : [] @carer_townships = @client.carer&.state.present? ? @client.carer.state.townships.order(:name) : [] else - @current_provinces = Province.order(:name) - @districts = @client.province.present? ? @client.province.districts.order(:name) : [] - @communes = @client.district.present? ? @client.district.communes.order(:code) : [] - @villages = @client.commune.present? ? @client.commune.villages.order(:code) : [] - - @referee_districts = @client.referee.try(:province).present? ? @client.referee.province.districts.order(:name) : [] - @referee_communes = @client.referee.try(:district).present? ? @client.referee.district.communes.order(:code) : [] - @referee_villages = @client.referee.try(:commune).present? ? @client.referee.commune.villages.order(:code) : [] - - @carer_districts = @client.carer.try(:province).present? ? @client.carer.province.districts.order(:name) : [] - @carer_communes = @client.carer.try(:district).present? ? @client.carer.district.communes.order(:code) : [] - @carer_villages = @client.carer.try(:commune).present? ? @client.carer.commune.villages.order(:code) : [] + @current_provinces = Province.cached_order_name + @districts = @client.province.present? ? @client.province.cached_districts : [] + @communes = @client.district.present? ? @client.district.cached_communes : [] + @villages = @client.commune.present? ? @client.commune.cached_villages : [] + + @referee_districts = @client.referee.try(:province).present? ? @client.referee.province.cached_districts : [] + @referee_communes = @client.referee.try(:district).present? ? @client.referee.district.cached_communes : [] + @referee_villages = @client.referee.try(:commune).present? ? @client.referee.commune.cached_villages : [] + + @carer_districts = @client.carer.try(:province).present? ? @client.carer.province.cached_districts : [] + @carer_communes = @client.carer.try(:district).present? ? @client.carer.district.cached_communes : [] + @carer_villages = @client.carer.try(:commune).present? ? @client.carer.commune.cached_villages : [] end end diff --git a/app/controllers/communities_controller.rb b/app/controllers/communities_controller.rb index cca92aefee..4f92e48a73 100644 --- a/app/controllers/communities_controller.rb +++ b/app/controllers/communities_controller.rb @@ -116,10 +116,10 @@ def community_params end def find_association - @provinces = Province.order(:name) - @districts = @community&.province.present? ? @community.province.districts.order(:name) : [] - @communes = @community&.district.present? ? @community.district.communes.order(:code) : [] - @villages = @community&.commune.present? ? @community.commune.villages.order(:code) : [] + @provinces = Province.cached_order_name + @districts = @community&.province.present? ? @community.province.cached_districts : [] + @communes = @community&.district.present? ? @community.district.cached_communes : [] + @villages = @community&.commune.present? ? @community.commune.cached_villages : [] end def find_community diff --git a/app/controllers/districts_controller.rb b/app/controllers/districts_controller.rb index 7986cc5f94..9e384b29a1 100644 --- a/app/controllers/districts_controller.rb +++ b/app/controllers/districts_controller.rb @@ -4,7 +4,7 @@ class DistrictsController < AdminController before_action :find_district, only: [:update, :destroy] def index - @provinces = Province.order(:name) + @provinces = Province.cached_order_name @districts = District.joins(:province).order('provinces.name').order(:name).page(params[:page]).per(20) @results = District.count end diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 583d87e972..6f2730c329 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -154,10 +154,10 @@ def family_params def find_association return if @family.nil? @users = User.without_deleted_users.non_strategic_overviewers.order(:first_name, :last_name) - @provinces = Province.order(:name) - @districts = @family.province.present? ? @family.province.districts.order(:name) : [] - @communes = @family.district.present? ? @family.district.communes.order(:code) : [] - @villages = @family.commune.present? ? @family.commune.villages.order(:code) : [] + @provinces = Province.cached_order_name + @districts = @family.province.present? ? @family.province.cached_districts : [] + @communes = @family.district.present? ? @family.district.cached_communes : [] + @villages = @family.commune.present? ? @family.commune.cached_villages : [] if action_name.in?(['edit', 'update']) client_ids = Family.where.not(id: @family).pluck(:children).flatten.uniq - @family.children else @@ -208,9 +208,9 @@ def fetch_family_attibutes(family_slug, current_org) @family.village = Village.find_by(id: village_id) @provinces = Province.order(:name) - @districts = @family.province.present? ? @family.province.districts.order(:name) : [] - @communes = @family.district.present? ? @family.district.communes.order(:code) : [] - @villages = @family.commune.present? ? @family.commune.villages.order(:code) : [] + @districts = @family.province.present? ? @family.province.cached_districts : [] + @communes = @family.district.present? ? @family.district.cached_communes : [] + @villages = @family.commune.present? ? @family.commune.cached_villages : [] end @family = Family.new(attributes) @selected_children = params[:children] diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index b35ae544c0..e74782d7e4 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -108,7 +108,7 @@ def test_client private def country_address_fields - @provinces = Province.order(:name) + @provinces = Province.cached_order_name @districts = Setting.cache_first.province.present? ? Setting.cache_first.province.districts.order(:name) : [] @communes = Setting.cache_first.district.present? ? Setting.cache_first.district.communes.order(:name_kh, :name_en) : [] end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d0bf8d1eb3..c576e12db8 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -124,7 +124,7 @@ def find_user def find_association @department = Department.order(:name) - @province = Province.order(:name) + @province = Province.cached_order_name @managers = User.managers.order(:first_name, :last_name) @managers = @managers.where.not('id = :user_id OR manager_ids && ARRAY[:user_id]', { user_id: @user.id }) if params[:action] == 'edit' || params[:action] == 'update' end diff --git a/app/models/agency.rb b/app/models/agency.rb index 1d8da05e7e..d50f14a0ac 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -12,10 +12,15 @@ def self.name_like(values = []) end def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_name + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) end end diff --git a/app/models/client_type.rb b/app/models/client_type.rb index c210dcb45e..e2e0e1bbf1 100644 --- a/app/models/client_type.rb +++ b/app/models/client_type.rb @@ -9,10 +9,15 @@ class ClientType < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_created_at + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) end end diff --git a/app/models/commune.rb b/app/models/commune.rb index 37ad5b6d92..028bc195e8 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -1,8 +1,9 @@ class Commune < ActiveRecord::Base + after_commit :flush_cache attr_accessor :name has_paper_trail - belongs_to :district + belongs_to :district, touch: true has_many :villages, dependent: :restrict_with_error has_many :government_forms, dependent: :restrict_with_error has_many :clients, dependent: :restrict_with_error @@ -37,4 +38,17 @@ def self.get_commune_name_by_code(commune_code) result = find_by(code: commune_code) { cp: result.district&.province&.name, cd: result.district&.name, cc: result&.name } end + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_villages + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) + end end diff --git a/app/models/district.rb b/app/models/district.rb index e993d52f48..0115659fb0 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -1,9 +1,10 @@ class District < ActiveRecord::Base + after_commit :flush_cache include AddressConcern has_paper_trail - belongs_to :province + belongs_to :province, touch: true has_many :clients, dependent: :restrict_with_error has_many :families, dependent: :restrict_with_error @@ -32,4 +33,22 @@ def self.get_district_name_by_code(district_code) result = find_by(code: district_code) { cp: result.province&.name, cd: result&.name } end + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def cached_communes + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_communes']) { communes.order(:code).to_a } + end + + def cached_subdistricts + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) { subdistricts.order(:name).to_a } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_communes']) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) + end end diff --git a/app/models/donor.rb b/app/models/donor.rb index 46bd2a6457..ca32998fdd 100644 --- a/app/models/donor.rb +++ b/app/models/donor.rb @@ -16,10 +16,15 @@ class Donor < ActiveRecord::Base validates :code, uniqueness: { case_sensitive: false }, if: 'code.present?' def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_name + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) end end diff --git a/app/models/interviewee.rb b/app/models/interviewee.rb index e5edb408bb..055f637fb9 100644 --- a/app/models/interviewee.rb +++ b/app/models/interviewee.rb @@ -9,10 +9,15 @@ class Interviewee < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_created_at + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) end end diff --git a/app/models/need.rb b/app/models/need.rb index 08703ec87d..ed2b7df207 100644 --- a/app/models/need.rb +++ b/app/models/need.rb @@ -9,10 +9,15 @@ class Need < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_created_at + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) end end diff --git a/app/models/problem.rb b/app/models/problem.rb index 4333a0ab98..be163f279f 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -9,10 +9,15 @@ class Problem < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_created_at + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) end end diff --git a/app/models/province.rb b/app/models/province.rb index b45e4fc4ef..24ed7af024 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -1,4 +1,5 @@ class Province < ActiveRecord::Base + after_commit :flush_cache include AddressConcern has_paper_trail @@ -33,4 +34,22 @@ def self.find_by_code(code) district = District.where("code LIKE ?", "#{code}%").first district&.province&.name end + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def self.cached_order_name + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } + end + + def cached_districts + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) { districts.order(:name).to_a } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) + end end diff --git a/app/models/subdistrict.rb b/app/models/subdistrict.rb index dc64d0b943..1ca57cc17f 100644 --- a/app/models/subdistrict.rb +++ b/app/models/subdistrict.rb @@ -1,7 +1,16 @@ class Subdistrict < ActiveRecord::Base - belongs_to :district + after_commit :flush_cache + belongs_to :district, touch: true has_many :clients, dependent: :restrict_with_error validates :district, presence: true validates :name, presence: true, uniqueness: { case_sensitive: false, scope: [:district_id] } + + def self.cached_find(id) + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) + end end From ec868e2b0d3e654a1ad3824978e124c3879ed6dd Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 29 Mar 2022 09:27:35 +0700 Subject: [PATCH 46/94] Add Cache to Notification - Cache User - Setting - Table exists - FieldSetting in client Policy --- app/controllers/application_controller.rb | 2 +- app/models/field_setting.rb | 29 ++++++++++- app/models/organization.rb | 11 ++-- app/models/setting.rb | 1 + app/models/user.rb | 58 +++++++++++----------- app/policies/client_policy.rb | 11 ++-- config/initializers/i18n/backend/custom.rb | 4 +- 7 files changed, 72 insertions(+), 44 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4e2be8fb58..63e39dd6f0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -39,7 +39,7 @@ def current_setting def field_settings return @field_settings if defined? @field_settings - FieldSetting.cache_query_find_by_ngo_name + @field_settings ||= FieldSetting.cache_query_find_by_ngo_name end def pundit_user diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 76e18590ef..8fa2d092a4 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -7,6 +7,7 @@ class FieldSetting < ActiveRecord::Base default_scope -> { order(:created_at) } scope :without_hidden_fields, -> { where(visible: true) } + scope :by_instances, ->(ngo_short_name) { where('for_instances IS NULL OR for_instances iLIKE ?', "%#{ngo_short_name}%").includes(:translations).order(:group, :name) } before_save :assign_type after_commit :flush_cache @@ -20,7 +21,9 @@ def group_setting? end def self.hidden_group?(group_name) - exists?(group: group_name, type: :group, visible: false) + Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', group_name, 'hidden_group']) do + exists?(group: group_name, type: :group, visible: false) + end end def possible_key_match?(key_paths) @@ -46,9 +49,27 @@ def self.cache_by_name(name) end end + def self.cache_by_name_klass_name_instance(name, klass_name = 'client') + Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting', name, klass_name]) do + find_by(name: name, klass_name: klass_name) + end + end + def self.cache_query_find_by_ngo_name Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', 'cache_query_find_by_ngo_name']) do - where('for_instances IS NULL OR for_instances iLIKE ?', "%#{Apartment::Tenant.current}%").includes(:translations).order(:group, :name).to_a + by_instances(Apartment::Tenant.current) + end + end + + def self.show_legal_doc(fields) + Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) do + by_instances(Apartment::Tenant.current).where(name: fields).any? + end + end + + def self.show_legal_doc_visible(fields) + Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', 'show_legal_doc', 'visible']) do + by_instances(Apartment::Tenant.current).where(visible: true, name: fields).any? end end @@ -63,5 +84,9 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) Rails.cache.delete([Apartment::Tenant::current, 'FieldSetting', self.name]) Rails.cache.delete(field_settings_cache_key << 'cache_query_find_by_ngo_name') + Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.name, self.klass_name]) + Rails.cache.delete([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) + Rails.cache.delete([Apartment::Tenant.current, 'table_name', 'field_settings']) + Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.group_name, 'hidden_group']) end end diff --git a/app/models/organization.rb b/app/models/organization.rb index fa1c36a458..bad6e46573 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -100,15 +100,15 @@ def seed_generic_data(org_id, referral_source_category_name=nil) end def brc? - current&.short_name == 'brc' + Apartment::Tenant.current == 'brc' end def shared? - current&.short_name == 'shared' + Apartment::Tenant.current == 'shared' end def ratanak? - current&.short_name == 'ratanak' + Apartment::Tenant.current == 'ratanak' end end @@ -153,6 +153,11 @@ def integrated_date date_of_integration && date_of_integration.strftime("%d %B %Y") end + def self.cache_table_exists?(table_name) + Rails.cache.fetch([Apartment::Tenant.current, 'table_name', table_name]) do + ActiveRecord::Base.connection.table_exists? table_name + end + end private diff --git a/app/models/setting.rb b/app/models/setting.rb index 16d38bcd18..1e6689dbd0 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -77,5 +77,6 @@ def custom_assessment_name def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'current_setting']) + Rails.cache.fetch([Apartment::Tenant.current, 'table_name', 'settings']) end end diff --git a/app/models/user.rb b/app/models/user.rb index be9cfe83cc..3d7e1b7005 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -159,42 +159,44 @@ def assessment_either_overdue_or_due_today customized_overdue = [] customized_due_today = [] current_ability = Ability.new(self) - _clients = Client.accessible_by(current_ability) - eligible_clients = active_young_clients(_clients, setting) - sql = "clients.id, (SELECT assessments.created_at FROM assessments WHERE assessments.client_id = clients.id AND assessments.default = true ORDER BY assessments.created_at DESC LIMIT 1) AS assessment_created_at" - if self.deactivated_at.nil? - clients_recent_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.defaults.most_recents).select(sql) - else - clients_recent_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.defaults.most_recents.where("assessments.created_at < ?", self.deactivated_at)).select(sql) - end - - clients_recent_assessment_dates.map {|obj| [obj.id, obj&.assessment_created_at] }.uniq.map do |client_id, recent_assessment_date| - next_assessment_date = recent_assessment_date + setting.max_assessment_duration - if next_assessment_date < Date.today - overdue << [client_id, next_assessment_date] - elsif next_assessment_date == Date.today - due_today << client_id - end - end.compact - - CustomAssessmentSetting.cache_custom_assessment.each do |custom_assessment_setting| - sql = "clients.id, (SELECT assessments.created_at FROM assessments WHERE assessments.client_id = clients.id AND assessments.default = false ORDER BY assessments.created_at DESC LIMIT 1) AS assessment_created_at" + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id, 'assessment_either_overdue_or_due_today']) do + _clients = Client.accessible_by(current_ability) + eligible_clients = active_young_clients(_clients, setting) + sql = "clients.id, (SELECT assessments.created_at FROM assessments WHERE assessments.client_id = clients.id AND assessments.default = true ORDER BY assessments.created_at DESC LIMIT 1) AS assessment_created_at" if self.deactivated_at.nil? - clients_recent_custom_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.customs.most_recents.joins(:domains).where(domains: { custom_assessment_setting_id: custom_assessment_setting.id })).select(sql) + clients_recent_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.defaults.most_recents).select(sql) else - clients_recent_custom_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.customs.most_recents.joins(:domains).where("assessments.created_at < ?", self.deactivated_at).where(domains: { custom_assessment_setting_id: custom_assessment_setting.id })).select(sql) + clients_recent_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.defaults.most_recents.where("assessments.created_at < ?", self.deactivated_at)).select(sql) end - clients_recent_custom_assessment_dates.map {|obj| [obj.id, obj&.assessment_created_at] }.uniq.map do |client_id, recent_assessment_date| - next_assessment_date = recent_assessment_date + assessment_duration('max', false, custom_assessment_setting.id) + + clients_recent_assessment_dates.map {|obj| [obj.id, obj&.assessment_created_at] }.uniq.map do |client_id, recent_assessment_date| + next_assessment_date = recent_assessment_date + setting.max_assessment_duration if next_assessment_date < Date.today - customized_overdue << client_id + overdue << [client_id, next_assessment_date] elsif next_assessment_date == Date.today - customized_due_today << client_id + due_today << client_id end end.compact - end - { overdue_count: overdue.count, overdue_assessment: overdue, due_today_count: due_today.count, custom_overdue_count: customized_overdue.flatten.uniq.count, custom_due_today_count: customized_due_today.flatten.uniq.count } + CustomAssessmentSetting.cache_custom_assessment.each do |custom_assessment_setting| + sql = "clients.id, (SELECT assessments.created_at FROM assessments WHERE assessments.client_id = clients.id AND assessments.default = false ORDER BY assessments.created_at DESC LIMIT 1) AS assessment_created_at" + if self.deactivated_at.nil? + clients_recent_custom_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.customs.most_recents.joins(:domains).where(domains: { custom_assessment_setting_id: custom_assessment_setting.id })).select(sql) + else + clients_recent_custom_assessment_dates = Client.joins(:assessments).where(id: eligible_clients.ids).merge(Assessment.customs.most_recents.joins(:domains).where("assessments.created_at < ?", self.deactivated_at).where(domains: { custom_assessment_setting_id: custom_assessment_setting.id })).select(sql) + end + clients_recent_custom_assessment_dates.map {|obj| [obj.id, obj&.assessment_created_at] }.uniq.map do |client_id, recent_assessment_date| + next_assessment_date = recent_assessment_date + assessment_duration('max', false, custom_assessment_setting.id) + if next_assessment_date < Date.today + customized_overdue << client_id + elsif next_assessment_date == Date.today + customized_due_today << client_id + end + end.compact + end + + { overdue_count: overdue.count, overdue_assessment: overdue, due_today_count: due_today.count, custom_overdue_count: customized_overdue.flatten.uniq.count, custom_due_today_count: customized_due_today.flatten.uniq.count } + end end def client_custom_field_frequency_overdue_or_due_today diff --git a/app/policies/client_policy.rb b/app/policies/client_policy.rb index 1678b91f8e..9e5a050c5e 100644 --- a/app/policies/client_policy.rb +++ b/app/policies/client_policy.rb @@ -17,9 +17,9 @@ def show_legal_doc? ] if Organization.ratanak? - field_settings.where(name: fields).any? && fields.any?{ |field| show?(field) } + FieldSetting.show_legal_doc(fields) && fields.any?{ |field| show?(field) } else - field_settings.where(visible: true, name: fields).any? && fields.any?{ |field| show?(field) } + FieldSetting.show_legal_doc_visible(fields) && fields.any?{ |field| show?(field) } end end @@ -45,7 +45,6 @@ def client_school_information? :main_school_contact, :education_background ] - field_settings.where(name: fields).any? && fields.any?{ |field| show?(field) } end @@ -68,11 +67,7 @@ def show?(*field_names) return false if Organization.brc? && (hidden_fields.include?(field) || hidden_fields.map{|f| f + '_'}.include?(field)) - field_setting = field_settings.find do |field_setting| - field_setting.name == field && - field_setting.klass_name == 'client' && - (field_setting.for_instances.blank? || field_setting.for_instances.include?(Organization.current&.short_name)) - end + field_setting = FieldSetting.cache_by_name_klass_name_instance(field, 'client') field_setting.present? ? (field_setting.required? || field_setting.visible?) : true end diff --git a/config/initializers/i18n/backend/custom.rb b/config/initializers/i18n/backend/custom.rb index 793d2c7a8c..325df989f6 100644 --- a/config/initializers/i18n/backend/custom.rb +++ b/config/initializers/i18n/backend/custom.rb @@ -26,10 +26,10 @@ def load_translations(*filenames) filenames = I18n.load_path if filenames.empty? filenames.flatten.each { |filename| load_file(filename) } if Apartment::Tenant.current != 'public' - if ActiveRecord::Base.connection.table_exists? 'settings' + if Organization.cache_table_exists? 'settings' nepal_commune_mapping if Setting.cache_first&.country_name == 'nepal' end - load_custom_labels if ActiveRecord::Base.connection.table_exists? 'field_settings' + load_custom_labels if Organization.cache_table_exists? 'field_settings' end end From 480b8bc8202faaffe41aac9e361347f497364b1f Mon Sep 17 00:00:00 2001 From: Doungdara Keut Date: Tue, 29 Mar 2022 09:39:27 +0700 Subject: [PATCH 47/94] [IMP] Cached Pendding Pull refs OSC-13 --- .../advanced_searches/domain_score_fields.rb | 3 ++- .../quantitative_case_fields.rb | 5 +++-- .../client_advanced_searches_concern.rb | 11 +++++----- .../family_advanced_searches_concern.rb | 5 +---- app/helpers/cache_helper.rb | 4 ++++ app/models/quantitative_type.rb | 22 +++++++++++++++++++ 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/classes/advanced_searches/domain_score_fields.rb b/app/classes/advanced_searches/domain_score_fields.rb index a6257356ba..f4c7d6df31 100644 --- a/app/classes/advanced_searches/domain_score_fields.rb +++ b/app/classes/advanced_searches/domain_score_fields.rb @@ -15,7 +15,8 @@ def self.render private def self.domain_options - Domain.csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } + + Domain.csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } end def self.domain_score_format(label) diff --git a/app/classes/advanced_searches/quantitative_case_fields.rb b/app/classes/advanced_searches/quantitative_case_fields.rb index 9d01d757fc..17817d5dce 100644 --- a/app/classes/advanced_searches/quantitative_case_fields.rb +++ b/app/classes/advanced_searches/quantitative_case_fields.rb @@ -12,11 +12,12 @@ def initialize(user, visible_on = 'client') def render opt_group = format_header('quantitative') if @user.admin? || @user.strategic_overviewer? - quantitative_types = QuantitativeType.includes(:quantitative_cases).where('quantitative_types.visible_on LIKE ?', "%#{visible_on}%") + quantitative_types = QuantitativeType.cach_by_visible_on(visible_on) else quantitative_type_ids = @user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) - quantitative_types = QuantitativeType.includes(:quantitative_cases).where(id: quantitative_type_ids) + quantitative_types = QuantitativeType.cach_by_quantitative_type_ids(quantitative_type_ids) end + quantitative_cases = quantitative_types.map do |qt| AdvancedSearches::FilterTypes.drop_list_options( "quantitative__#{qt.id}", diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index ccc8fff835..989cc4a6fc 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -109,9 +109,9 @@ def program_stream_values end def get_client_basic_fields - Rails.cache.fetch(user_cache_id << "get_client_basic_fields") do + # Rails.cache.fetch(user_cache_id << "get_client_basic_fields") do AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render - end + # end end def get_hotline_fields @@ -128,10 +128,8 @@ def get_hotline_fields *get_dropdown_list(['phone_call_id', 'call_type', 'start_datetime', 'protection_concern_id', 'necessity_id']), ] } - Rails.cache.fetch(user_cache_id << "get_hotline_fields") do hotline_fields = AdvancedSearches::AdvancedSearchFields.new('hotline', args).render @hotline_fields = get_client_hotline_fields + hotline_fields - end end def get_client_hotline_fields @@ -146,6 +144,8 @@ def get_client_hotline_fields ['concern_same_as_client', { true: 'Yes', false: 'No' }] ] + binding.pry + args = { translation: client_fields.merge({ concern_basic_fields: I18n.t('advanced_search.fields.concern_basic_fields') }), number_field: [], text_field: hotline_text_type_list, date_picker_field: [], @@ -174,6 +174,7 @@ def custom_form_fields end def get_custom_form_fields + # customFields Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render end @@ -184,10 +185,8 @@ def get_has_this_form_fields end def get_quantitative_fields - Rails.cache.fetch(user_cache_id << "get_quantitative_fields") do quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user) @quantitative_fields = quantitative_fields.render - end end def get_enrollment_fields diff --git a/app/controllers/concerns/family_advanced_searches_concern.rb b/app/controllers/concerns/family_advanced_searches_concern.rb index 51124186df..aedf03d263 100644 --- a/app/controllers/concerns/family_advanced_searches_concern.rb +++ b/app/controllers/concerns/family_advanced_searches_concern.rb @@ -319,10 +319,7 @@ def exit_program_check? end def get_quantitative_fields - Rails.cache.fetch(user_cache_id << "get_quantitative_fields") do - quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user, 'family') - end - + quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user, 'family') @quantitative_fields = quantitative_fields.render end diff --git a/app/helpers/cache_helper.rb b/app/helpers/cache_helper.rb index c864c90bda..11e6f31352 100644 --- a/app/helpers/cache_helper.rb +++ b/app/helpers/cache_helper.rb @@ -11,4 +11,8 @@ def setting_cache_key [Apartment::Tenant.current, 'current_setting'] end + + def custom_fields_cache + [Apartment::Tenant.current, 'custom_fields'] + end end \ No newline at end of file diff --git a/app/models/quantitative_type.rb b/app/models/quantitative_type.rb index ec82462fa6..6a72e49bf3 100644 --- a/app/models/quantitative_type.rb +++ b/app/models/quantitative_type.rb @@ -19,7 +19,29 @@ class QuantitativeType < ActiveRecord::Base scope :name_like, ->(name) { where('quantitative_types.name iLIKE ?', "%#{name}%") } after_create :build_permission + + after_commit :flush_cach + def flush_cach + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "client"] ) + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "community"] ) + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "family"] ) + end + + + def self.cach_by_visible_on(visible_on) + Rails.cache.fetch([Apartment::Tenant.current, "QuantitativeType", visible_on]) do + QuantitativeType.includes(:quantitative_cases).where('quantitative_types.visible_on LIKE ?', "%#{visible_on}%") + end + end + + def self.cach_by_quantitative_type_ids(quantitative_type_ids) + Rails.cache.fetch([Apartment::Tenant.current, "quantitative_type_ids", quantitative_type_ids]) do + QuantitativeType.includes(:quantitative_cases).where(id: quantitative_type_ids) + end + end + + private def validate_visible_on From 279bba1541d41edf3a374e35693021c67dae4d88 Mon Sep 17 00:00:00 2001 From: Doungdara Keut Date: Tue, 29 Mar 2022 10:12:08 +0700 Subject: [PATCH 48/94] [IMP] Cached c.get_hotline_field refs OSC-13 --- .../concerns/client_advanced_searches_concern.rb | 8 ++++---- app/models/commune.rb | 6 ++++++ app/models/district.rb | 5 +++++ app/models/province.rb | 5 +++++ app/models/village.rb | 8 ++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index 989cc4a6fc..868902385b 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -136,10 +136,10 @@ def get_client_hotline_fields client_fields = I18n.t('datagrid.columns.clients') dropdown_list_options = [ ['concern_address_type', [Client::ADDRESS_TYPES, Client::ADDRESS_TYPES.map{|type| I18n.t('default_client_fields.address_types')[type.downcase.to_sym] }].transpose.map{|k,v| { k.downcase => v } }], - ['concern_province_id', Province.dropdown_list_option], - ['concern_district_id', District.dropdown_list_option], - ['concern_commune_id', Commune.dropdown_list_option], - ['concern_village_id', Village.dropdown_list_option], + ['concern_province_id', Province.cached_dropdown_list_option], + ['concern_district_id', District.cached_dropdown_list_option], + ['concern_commune_id', Commune.cached_dropdown_list_option], + ['concern_village_id', Village.cached_dropdown_list_option], ['concern_is_outside', { true: 'Yes', false: 'No' }], ['concern_same_as_client', { true: 'Yes', false: 'No' }] ] diff --git a/app/models/commune.rb b/app/models/commune.rb index 028bc195e8..635702f4c0 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -47,8 +47,14 @@ def self.cached_villages Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code) } end + def self.cached_dropdown_list_option + Rails.cache.fetch([Apartment::Tenant.current, 'commune', 'dropdown_list_option']) { self.dropdown_list_option } + end + def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) + Rails.cache.delete([Apartment::Tenant.current, "commune", 'dropdown_list_option']) + end end diff --git a/app/models/district.rb b/app/models/district.rb index 0115659fb0..fb00e756ce 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -46,9 +46,14 @@ def cached_subdistricts Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) { subdistricts.order(:name).to_a } end + def self.cached_dropdown_list_option + Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) {self.dropdown_list_option} + end + def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_communes']) Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) + Rails.cache.delete([Apartment::Tenant.current, "District", 'dropdown_list_option']) end end diff --git a/app/models/province.rb b/app/models/province.rb index 24ed7af024..eb801893ab 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -43,6 +43,10 @@ def self.cached_order_name Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } end + def self.cached_dropdown_list_option + Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) {self.dropdown_list_option} + end + def cached_districts Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) { districts.order(:name).to_a } end @@ -51,5 +55,6 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) + Rails.cache.delete([Apartment::Tenant.current, "Province", 'dropdown_list_option']) end end diff --git a/app/models/village.rb b/app/models/village.rb index b658d9d562..0ab3a21862 100644 --- a/app/models/village.rb +++ b/app/models/village.rb @@ -33,4 +33,12 @@ def self.get_village_name_by_code(village_code) result = find_by(code: village_code) { cp: result.commune&.district&.province&.name, cd: result.commune&.district&.name, cc: result.commune&.name, cv: result&.name } end + + def self.cached_dropdown_list_option + Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'dropdown_list_option']) {self.dropdown_list_option} + end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, "Village", 'dropdown_list_option']) + end end From 5f013babe1fd7791d4388bfffda308f55c9647e3 Mon Sep 17 00:00:00 2001 From: Doungdara Keut Date: Tue, 29 Mar 2022 14:26:49 +0700 Subject: [PATCH 49/94] [IMP] Change class.name to static refs OSC-13 --- .../client_advanced_searches_concern.rb | 34 ++++++------------- .../family_advanced_searches_concern.rb | 6 +--- .../partner_advanced_searches_concern.rb | 8 ++--- app/models/advanced_search.rb | 7 ++++ app/models/commune.rb | 4 +-- app/models/district.rb | 6 ++-- app/models/province.rb | 6 ++-- 7 files changed, 28 insertions(+), 43 deletions(-) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index 868902385b..fe7ec2c8c4 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -71,20 +71,16 @@ def get_custom_form end def hotline_call_column - Rails.cache.fetch(user_cache_id << "hotline_call_column") do client_hotlines = get_client_hotline_fields.group_by{ |field| field[:optgroup] } call_hotlines = get_hotline_fields.group_by{ |field| field[:optgroup] } @hotline_call_columns = client_hotlines.merge(call_hotlines) - end end def program_stream_fields - Rails.cache.fetch(user_cache_id << "program_stream_fields") do if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' - @wizard_program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields - else - @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields - end + @wizard_program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields + else + @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields end end @@ -109,9 +105,8 @@ def program_stream_values end def get_client_basic_fields - # Rails.cache.fetch(user_cache_id << "get_client_basic_fields") do + # Static Fields AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render - # end end def get_hotline_fields @@ -143,9 +138,6 @@ def get_client_hotline_fields ['concern_is_outside', { true: 'Yes', false: 'No' }], ['concern_same_as_client', { true: 'Yes', false: 'No' }] ] - - binding.pry - args = { translation: client_fields.merge({ concern_basic_fields: I18n.t('advanced_search.fields.concern_basic_fields') }), number_field: [], text_field: hotline_text_type_list, date_picker_field: [], @@ -164,20 +156,16 @@ def custom_form_values end def custom_form_fields - Rails.cache.fetch(user_cache_id << "custom_form_fields") do - if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' - @wizard_custom_form_fields = get_custom_form_fields + get_has_this_form_fields - else - @custom_form_fields = get_custom_form_fields + get_has_this_form_fields - end + if params.dig(:client_advanced_search, :action_report_builder) == '#wizard-builder' + @wizard_custom_form_fields = get_custom_form_fields + get_has_this_form_fields + else + @custom_form_fields = get_custom_form_fields + get_has_this_form_fields end end def get_custom_form_fields - # customFields - Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do + # Static Fields @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render - end end def get_has_this_form_fields @@ -234,9 +222,7 @@ def has_params? def find_params_advanced_search if params[:advanced_search_id] - Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do - advanced_search = AdvancedSearch.find(params[:advanced_search_id]) - end + advanced_search = AdvancedSearch.cached_advanced_search(params[:advanced_search_id]) @advanced_search_params = params[:client_advanced_search].merge("basic_rules" => advanced_search.queries) else @advanced_search_params = params[:client_advanced_search] diff --git a/app/controllers/concerns/family_advanced_searches_concern.rb b/app/controllers/concerns/family_advanced_searches_concern.rb index aedf03d263..8ec99c794f 100644 --- a/app/controllers/concerns/family_advanced_searches_concern.rb +++ b/app/controllers/concerns/family_advanced_searches_concern.rb @@ -80,9 +80,7 @@ def custom_form_values end def custom_form_fields - Rails.cache.fetch(user_cache_id << "custom_form_fields") do @custom_form_fields = get_custom_form_fields + get_has_this_form_fields - end end def get_has_this_form_fields @@ -90,9 +88,7 @@ def get_has_this_form_fields end def get_custom_form_fields - Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do - @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values, 'Family').render - end + @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values, 'Family').render end def custom_form_value? diff --git a/app/controllers/concerns/partner_advanced_searches_concern.rb b/app/controllers/concerns/partner_advanced_searches_concern.rb index 337c4e891e..62e2f37150 100644 --- a/app/controllers/concerns/partner_advanced_searches_concern.rb +++ b/app/controllers/concerns/partner_advanced_searches_concern.rb @@ -48,15 +48,11 @@ def custom_form_values end def custom_form_fields - Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do - @custom_form_fields = get_custom_form_fields + get_has_this_form_fields - end + @custom_form_fields = get_custom_form_fields + get_has_this_form_fields end def get_custom_form_fields - Rails.cache.fetch(user_cache_id << "get_custom_form_fields") do - @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values).render - end + @custom_forms = AdvancedSearches::CustomFields.new(custom_form_values).render end def get_has_this_form_fields diff --git a/app/models/advanced_search.rb b/app/models/advanced_search.rb index 9b9d13a812..e8c3f708a4 100644 --- a/app/models/advanced_search.rb +++ b/app/models/advanced_search.rb @@ -31,10 +31,17 @@ def owner user.name end + def self.cached_advanced_search(params_id) + Rails.cache.fetch([Apartment::Tenant.current, 'AdvancedSearch', params_id]) do + self.find(params_id) + end + end + private def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'User', user_id, 'advance_saved_search']) + Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) end end diff --git a/app/models/commune.rb b/app/models/commune.rb index 635702f4c0..63913ec1a0 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -40,11 +40,11 @@ def self.get_commune_name_by_code(commune_code) end def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Commune', id]) { find(id) } end def self.cached_villages - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code) } + Rails.cache.fetch([Apartment::Tenant.current, 'Commune', id, 'cached_villages']) { villages.order(:code) } end def self.cached_dropdown_list_option diff --git a/app/models/district.rb b/app/models/district.rb index fb00e756ce..b7abb75ec0 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -35,15 +35,15 @@ def self.get_district_name_by_code(district_code) end def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'District', id]) { find(id) } end def cached_communes - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_communes']) { communes.order(:code).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'District', id, 'cached_communes']) { communes.order(:code).to_a } end def cached_subdistricts - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) { subdistricts.order(:name).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'District', id, 'cached_subdistricts']) { subdistricts.order(:name).to_a } end def self.cached_dropdown_list_option diff --git a/app/models/province.rb b/app/models/province.rb index eb801893ab..afdeef759f 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -36,11 +36,11 @@ def self.find_by_code(code) end def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Province', id]) { find(id) } end def self.cached_order_name - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'cached_order_name']) { order(:name).to_a } end def self.cached_dropdown_list_option @@ -48,7 +48,7 @@ def self.cached_dropdown_list_option end def cached_districts - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) { districts.order(:name).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Province', id, 'cached_districts']) { districts.order(:name).to_a } end def flush_cache From 9f5569197209dc6ac2b84cc5904da8cb18a9b705 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 29 Mar 2022 15:47:28 +0700 Subject: [PATCH 50/94] Add Cache to User model - Cache in assessment_either_overdue_or_due_today - Delete Cache in Assessment - Delete Cache in CustomAssessmentSetting --- app/controllers/application_controller.rb | 4 ++++ app/controllers/settings_controller.rb | 29 +++++++++++------------ app/models/assessment.rb | 5 ++++ app/models/commune.rb | 4 ++-- app/models/custom_assessment_setting.rb | 3 ++- app/models/user.rb | 10 ++++++++ 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 32773a1bea..656ca73e2a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base before_action :set_paper_trail_whodunnit, :current_setting before_action :prevent_routes before_action :set_raven_context, :address_translation + before_filter :set_current_user rescue_from ActiveRecord::RecordNotFound do |exception| render file: "#{Rails.root}/app/views/errors/404", layout: false, status: :not_found @@ -52,6 +53,9 @@ def address_translation @address_translation = view_context.address_translation end + def set_current_user + User.current_user = current_user + end private diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index e74782d7e4..ee488ee476 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -1,7 +1,7 @@ class SettingsController < AdminController include CommunityHelper - before_action :find_setting, except: [:create, :show, :edit, :update] + before_action :find_setting, except: [:create] before_action :country_address_fields, only: [:edit, :update] def index @@ -23,13 +23,13 @@ def show end def edit - authorize @current_setting - render template: 'organizations/edit', locals: { current_setting: @current_setting } + authorize @setting + render template: 'organizations/edit', locals: { current_setting: @setting } end def update - authorize @current_setting - @setting = @current_setting + authorize @setting + @setting = @setting if params[:setting].has_key?(:org_form) if @setting.update_attributes(setting_params) redirect_to root_path, notice: t('.successfully_updated') @@ -46,6 +46,7 @@ def update redirect_to settings_path, notice: t('.successfully_updated') end else + flash[:alert] = @setting.errors.full_messages.join(", ") render :index end end @@ -66,27 +67,27 @@ def default_columns end def research_module - authorize @current_setting + authorize @setting end def custom_labels - authorize @current_setting + authorize @setting end def client_forms - authorize @current_setting + authorize @setting end def community - authorize @current_setting + authorize @setting end def family_case_management - authorize @current_setting + authorize @setting end def integration - authorize @current_setting + authorize @setting attribute = params[:setting] if attribute && current_organization.update_attributes(integrated: attribute[:integrated]) redirect_to integration_settings_path, notice: t('.successfully_updated') @@ -96,15 +97,13 @@ def integration end def custom_form - authorize @current_setting + authorize @setting end def test_client - authorize @current_setting + authorize @setting end - - private def country_address_fields diff --git a/app/models/assessment.rb b/app/models/assessment.rb index ace945f63a..87e997ffe9 100644 --- a/app/models/assessment.rb +++ b/app/models/assessment.rb @@ -20,6 +20,7 @@ class Assessment < ActiveRecord::Base before_save :set_previous_score before_save :set_assessment_completed, unless: :completed? + after_commit :flush_cache accepts_nested_attributes_for :assessment_domains @@ -178,4 +179,8 @@ def set_previous_score end end end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'User', User.current_user.id, 'assessment_either_overdue_or_due_today']) if User.current_user.present? + end end diff --git a/app/models/commune.rb b/app/models/commune.rb index 028bc195e8..ee4fb07cda 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -43,8 +43,8 @@ def self.cached_find(id) Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } end - def self.cached_villages - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code) } + def cached_villages + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code).to_a } end def flush_cache diff --git a/app/models/custom_assessment_setting.rb b/app/models/custom_assessment_setting.rb index 1a80d513f2..6b19ffe187 100644 --- a/app/models/custom_assessment_setting.rb +++ b/app/models/custom_assessment_setting.rb @@ -40,6 +40,7 @@ def self.cache_custom_assessment private def flush_cache - Rails.cache.fetch([Apartment::Tenant.current, 'CustomAssessmentSetting', 'enable_custom_assessment', 'true']) if enable_custom_assessment + Rails.cache.delete([Apartment::Tenant.current, 'CustomAssessmentSetting', 'enable_custom_assessment', 'true']) if enable_custom_assessment + Rails.cache.delete([Apartment::Tenant.current, 'User', User.current_user.id, 'assessment_either_overdue_or_due_today']) if User.current_user.present? end end diff --git a/app/models/user.rb b/app/models/user.rb index 3d7e1b7005..a9bf1cf3c6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -89,6 +89,16 @@ class User < ActiveRecord::Base after_save :toggle_referral_notification after_create :build_permission + class << self + def current_user=(user) + Thread.current[:current_user] = user + end + + def current_user + Thread.current[:current_user] + end + end + def build_permission unless self.strategic_overviewer? self.create_permission From 48d51b50596454f41ea5903385059c09df35d6af Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 29 Mar 2022 17:31:26 +0700 Subject: [PATCH 51/94] Update code --- .../concerns/client_advanced_searches_concern.rb | 12 +++++------- app/models/advanced_search.rb | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index fe7ec2c8c4..79a02b378e 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -51,7 +51,7 @@ def build_advanced_search def fetch_advanced_search_queries @my_advanced_searches = current_user.cache_advance_saved_search @other_advanced_searches = Rails.cache.fetch(user_cache_id << "other_advanced_search_queries") do - AdvancedSearch.includes(:user).non_of(current_user).order(:name) + AdvancedSearch.includes(:user).non_of(current_user).order(:name).to_a end end @@ -105,8 +105,7 @@ def program_stream_values end def get_client_basic_fields - # Static Fields - AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render + AdvancedSearches::ClientFields.new(user: current_user, pundit_user: pundit_user).render end def get_hotline_fields @@ -164,8 +163,7 @@ def custom_form_fields end def get_custom_form_fields - # Static Fields - @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render + @custom_forms = custom_form_values.empty? ? [] : AdvancedSearches::CustomFields.new(custom_form_values).render end def get_has_this_form_fields @@ -173,8 +171,8 @@ def get_has_this_form_fields end def get_quantitative_fields - quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user) - @quantitative_fields = quantitative_fields.render + quantitative_fields = AdvancedSearches::QuantitativeCaseFields.new(current_user) + @quantitative_fields = quantitative_fields.render end def get_enrollment_fields diff --git a/app/models/advanced_search.rb b/app/models/advanced_search.rb index e8c3f708a4..e0dd1e7753 100644 --- a/app/models/advanced_search.rb +++ b/app/models/advanced_search.rb @@ -42,6 +42,7 @@ def self.cached_advanced_search(params_id) def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'User', user_id, 'advance_saved_search']) Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) + Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id, "other_advanced_search_queries"]) end end From bf56a37f71b9036a0d3b7bc48fef222559aa4bc9 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Tue, 29 Mar 2022 17:58:09 +0700 Subject: [PATCH 52/94] [IMP] Adjust to static class refs OSC-13 --- app/models/agency.rb | 12 ++++++------ app/models/client_type.rb | 12 ++++++------ app/models/commune.rb | 8 ++++---- app/models/district.rb | 11 ++++++----- app/models/donor.rb | 12 ++++++------ app/models/interviewee.rb | 12 ++++++------ app/models/need.rb | 12 ++++++------ app/models/problem.rb | 12 ++++++------ app/models/province.rb | 11 ++++++----- app/models/village.rb | 6 ++++-- 10 files changed, 56 insertions(+), 52 deletions(-) diff --git a/app/models/agency.rb b/app/models/agency.rb index d50f14a0ac..36ae1dfd40 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -1,26 +1,26 @@ class Agency < ActiveRecord::Base - after_commit :flush_cache - has_many :agency_clients has_many :clients, through: :agency_clients has_paper_trail validates :name, presence: true, uniqueness: { case_sensitive: false } + after_commit :flush_cache + def self.name_like(values = []) where('name iLIKE ANY ( array[?] )', values) end def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Agency', id]) { find(id) } end def self.cached_order_name - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Agency', 'cached_order_name']) { order(:name).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, 'Agency', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) end end diff --git a/app/models/client_type.rb b/app/models/client_type.rb index e2e0e1bbf1..425c35dc56 100644 --- a/app/models/client_type.rb +++ b/app/models/client_type.rb @@ -1,6 +1,4 @@ class ClientType < ActiveRecord::Base - after_commit :flush_cache - has_paper_trail has_many :client_type_government_forms, dependent: :restrict_with_error @@ -8,16 +6,18 @@ class ClientType < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } + after_commit :flush_cache + def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'ClientType', id]) { find(id) } end def self.cached_order_created_at - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'ClientType', 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'ClientType', id]) + Rails.cache.delete([Apartment::Tenant.current, 'ClientType', 'cached_order_created_at']) end end diff --git a/app/models/commune.rb b/app/models/commune.rb index 609b9f16f3..bbd96f757a 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -1,5 +1,4 @@ class Commune < ActiveRecord::Base - after_commit :flush_cache attr_accessor :name has_paper_trail @@ -16,6 +15,7 @@ class Commune < ActiveRecord::Base scope :dropdown_list_option, -> { all.map{|c| { c.id => c.name } } } + after_commit :flush_cache def name district_type ? name_en : "#{name_kh} / #{name_en}" @@ -44,7 +44,7 @@ def self.cached_find(id) end def cached_villages - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) { villages.order(:code).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Commune', id, 'cached_villages']) { villages.order(:code).to_a } end def self.cached_dropdown_list_option @@ -52,8 +52,8 @@ def self.cached_dropdown_list_option end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_villages']) + Rails.cache.delete([Apartment::Tenant.current, 'Commune', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Commune', id, 'cached_villages']) Rails.cache.delete([Apartment::Tenant.current, "Commune", 'dropdown_list_option']) end end diff --git a/app/models/district.rb b/app/models/district.rb index b7abb75ec0..06c294208d 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -1,5 +1,4 @@ class District < ActiveRecord::Base - after_commit :flush_cache include AddressConcern has_paper_trail @@ -16,6 +15,8 @@ class District < ActiveRecord::Base validates :province, presence: true validates :name, presence: true, uniqueness: { case_sensitive: false, scope: [:province_id] } + after_commit :flush_cache + def name_kh name.split(' / ').first end @@ -47,13 +48,13 @@ def cached_subdistricts end def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) {self.dropdown_list_option} + Rails.cache.fetch([Apartment::Tenant.current, 'District', 'dropdown_list_option']) { self.dropdown_list_option } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_communes']) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_subdistricts']) + Rails.cache.delete([Apartment::Tenant.current, 'District', id]) + Rails.cache.delete([Apartment::Tenant.current, 'District', id, 'cached_communes']) + Rails.cache.delete([Apartment::Tenant.current, 'District', id, 'cached_subdistricts']) Rails.cache.delete([Apartment::Tenant.current, "District", 'dropdown_list_option']) end end diff --git a/app/models/donor.rb b/app/models/donor.rb index ca32998fdd..74085882b8 100644 --- a/app/models/donor.rb +++ b/app/models/donor.rb @@ -1,6 +1,4 @@ class Donor < ActiveRecord::Base - after_commit :flush_cache - has_many :sponsors, dependent: :restrict_with_error has_many :clients, through: :sponsors has_many :donor_organizations, dependent: :destroy @@ -15,16 +13,18 @@ class Donor < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :code }, if: 'code.present?' validates :code, uniqueness: { case_sensitive: false }, if: 'code.present?' + after_commit :flush_cache + def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Donor', id]) { find(id) } end def self.cached_order_name - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_name']) { order(:name).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Donor', 'cached_order_name']) { order(:name).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, 'Donor', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Donor', 'cached_order_name']) end end diff --git a/app/models/interviewee.rb b/app/models/interviewee.rb index 055f637fb9..df1274515e 100644 --- a/app/models/interviewee.rb +++ b/app/models/interviewee.rb @@ -1,6 +1,4 @@ class Interviewee < ActiveRecord::Base - after_commit :flush_cache - has_paper_trail has_many :government_form_interviewees, dependent: :restrict_with_error @@ -8,16 +6,18 @@ class Interviewee < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } + after_commit :flush_cache + def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Interviewee', id]) { find(id) } end def self.cached_order_created_at - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Interviewee', 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Interviewee', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Interviewee', 'cached_order_created_at']) end end diff --git a/app/models/need.rb b/app/models/need.rb index ed2b7df207..cf84eaa4fb 100644 --- a/app/models/need.rb +++ b/app/models/need.rb @@ -1,6 +1,4 @@ class Need < ActiveRecord::Base - after_commit :flush_cache - has_paper_trail has_many :government_form_needs, dependent: :restrict_with_error @@ -8,16 +6,18 @@ class Need < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } + after_commit :flush_cache + def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Need', id]) { find(id) } end def self.cached_order_created_at - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Need', 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Need', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Need', 'cached_order_created_at']) end end diff --git a/app/models/problem.rb b/app/models/problem.rb index be163f279f..8c7b4f4ee5 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -1,6 +1,4 @@ class Problem < ActiveRecord::Base - after_commit :flush_cache - has_paper_trail has_many :government_form_problems, dependent: :restrict_with_error @@ -8,16 +6,18 @@ class Problem < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } + after_commit :flush_cache + def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, 'Problem', id]) { find(id) } end def self.cached_order_created_at - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) { order(:created_at).to_a } + Rails.cache.fetch([Apartment::Tenant.current, 'Problem', 'cached_order_created_at']) { order(:created_at).to_a } end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Problem', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Problem', 'cached_order_created_at']) end end diff --git a/app/models/province.rb b/app/models/province.rb index afdeef759f..0fac01deea 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -1,5 +1,4 @@ class Province < ActiveRecord::Base - after_commit :flush_cache include AddressConcern has_paper_trail @@ -22,6 +21,8 @@ class Province < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :country } + after_commit :flush_cache + def removeable? families.count.zero? && partners.count.zero? && users.count.zero? && clients.count.zero? && cases.count.zero? end @@ -52,9 +53,9 @@ def cached_districts end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'cached_order_name']) - Rails.cache.delete([Apartment::Tenant.current, self.class.name, id, 'cached_districts']) - Rails.cache.delete([Apartment::Tenant.current, "Province", 'dropdown_list_option']) + Rails.cache.delete([Apartment::Tenant.current, 'Province', id]) + Rails.cache.delete([Apartment::Tenant.current, 'Province', 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, 'Province', id, 'cached_districts']) + Rails.cache.delete([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) end end diff --git a/app/models/village.rb b/app/models/village.rb index 0ab3a21862..b2f510e8f4 100644 --- a/app/models/village.rb +++ b/app/models/village.rb @@ -1,7 +1,7 @@ class Village < ActiveRecord::Base has_paper_trail - belongs_to :commune + belongs_to :commune, touch: true has_many :government_forms, dependent: :restrict_with_error has_many :clients, dependent: :restrict_with_error has_many :families, dependent: :restrict_with_error @@ -12,6 +12,8 @@ class Village < ActiveRecord::Base scope :dropdown_list_option, -> { all.map{|c| { c.id => c.name } } } + after_commit :flush_cache + def code_format "#{name_kh} / #{name_en} (#{code})" end @@ -35,7 +37,7 @@ def self.get_village_name_by_code(village_code) end def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'dropdown_list_option']) {self.dropdown_list_option} + Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'dropdown_list_option']) { self.dropdown_list_option } end def flush_cache From 402833c8a27f384ef93935a23924b4b52ffed3aa Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Wed, 30 Mar 2022 17:54:15 +0700 Subject: [PATCH 53/94] [IMP] Apply cache to avanced search refs OSC-13 --- .../advanced_searches/custom_fields.rb | 4 +-- .../advanced_searches/enrollment_fields.rb | 2 +- .../advanced_searches/has_this_form_fields.rb | 2 +- app/controllers/clients_controller.rb | 4 +-- .../client_advanced_searches_concern.rb | 4 +-- app/models/custom_field.rb | 26 ++++++++++++++++++- app/models/custom_field_property.rb | 12 +++++++++ app/models/program_stream.rb | 8 ++++++ .../_form_option.haml | 2 +- .../client_advanced_searches/_wizard.haml | 2 +- 10 files changed, 54 insertions(+), 12 deletions(-) diff --git a/app/classes/advanced_searches/custom_fields.rb b/app/classes/advanced_searches/custom_fields.rb index d9fe124078..dd037811db 100644 --- a/app/classes/advanced_searches/custom_fields.rb +++ b/app/classes/advanced_searches/custom_fields.rb @@ -27,9 +27,9 @@ def render def generate_field_by_type if attach_with == 'Community' - @custom_fields = CustomField.where(id: @custom_form_ids, entity_type: attach_with) + @custom_fields = CustomField.cached_custom_form_ids_attach_with(@custom_form_ids, attach_with) else - @custom_fields = CustomField.where(id: @custom_form_ids) + @custom_fields = CustomField.cached_custom_form_ids(@custom_form_ids) end @custom_fields.each do |custom_field| diff --git a/app/classes/advanced_searches/enrollment_fields.rb b/app/classes/advanced_searches/enrollment_fields.rb index 10f489b285..9426c2c5a5 100644 --- a/app/classes/advanced_searches/enrollment_fields.rb +++ b/app/classes/advanced_searches/enrollment_fields.rb @@ -31,7 +31,7 @@ def render end def generate_field_by_type - program_streams = ProgramStream.where(id: @program_ids) + program_streams = ProgramStream.cached_program_ids(@program_ids) program_streams.each do |program_stream| @enrollment_data_list << "enrollmentdate__#{program_stream.name}__Enrollment Date" diff --git a/app/classes/advanced_searches/has_this_form_fields.rb b/app/classes/advanced_searches/has_this_form_fields.rb index 927092b7d5..46f6b27d79 100644 --- a/app/classes/advanced_searches/has_this_form_fields.rb +++ b/app/classes/advanced_searches/has_this_form_fields.rb @@ -20,7 +20,7 @@ def render end def generate_field_by_type - custom_forms = CustomField.where(id: @custom_form_ids, entity_type: attach_with) + custom_forms = CustomField.cached_custom_form_ids_attach_with(@custom_form_ids, attach_with) custom_forms.each do |custom_form| drop_list_values = [] drop_list_values << "formbuilder__#{custom_form.form_title}__Has This Form" diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 85bc66c05d..8ece4115f5 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -361,9 +361,7 @@ def quantitative_type_editable end def quantitative_type_readable - Rails.cache.fetch(user_cache_id) do - @quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) - end + @quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) end def find_referral_by_params diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index 79a02b378e..7752edbc3a 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -66,8 +66,8 @@ def program_stream_column end def get_custom_form - form_ids = CustomFieldProperty.where(custom_formable_type: 'Client').pluck(:custom_field_id).uniq - @custom_fields = CustomField.where(id: form_ids).order_by_form_title + form_ids = CustomFieldProperty.cached_custom_formable_type + @custom_fields = CustomField.cached_order_by_form_title(form_ids) end def hotline_call_column diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 5af80cccfb..a657c05798 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -86,6 +86,24 @@ def self.cache_object(id) Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', id]) { find(id) } end + def self.cached_order_by_form_title(form_ids) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_order_by_form_title', *form_ids.sort]) { + where(id: form_ids).order_by_form_title.to_a + } + end + + def self.cached_custom_form_ids(custom_form_ids) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids', *custom_form_ids.sort]) { + where(id: custom_form_ids).to_a + } + end + + def self.cached_custom_form_ids_attach_with(custom_form_ids, attach_with) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids_attach_with', *custom_form_ids.sort, attach_with]) { + where(id: custom_form_ids, entity_type: attach_with).to_a + } + end + private def update_custom_field_label @@ -128,6 +146,12 @@ def get_rules(queries, ss) end def flush_cache - Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) + Rails.cache.delete([Apartment::Tenant.current, 'CustomField', self.id]) + cached_order_by_form_title_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_order_by_form_title/].blank? } + cached_order_by_form_title_keys.each { |key| Rails.cache.delete(key) } + cached_custom_form_ids_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_custom_form_ids/].blank? } + cached_custom_form_ids_keys.each { |key| Rails.cache.delete(key) } + cached_custom_form_ids_attach_with_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_custom_form_ids_attach_with/].blank? } + cached_custom_form_ids_attach_with_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/custom_field_property.rb b/app/models/custom_field_property.rb index da7ac658b5..49f137717d 100644 --- a/app/models/custom_field_property.rb +++ b/app/models/custom_field_property.rb @@ -17,6 +17,8 @@ class CustomFieldProperty < ActiveRecord::Base validates :custom_field_id, presence: true + after_commit :flush_cache + def client_form? custom_formable_type == 'Client' end @@ -39,9 +41,19 @@ def is_editable? created_at >= max_duration.send(custom_field_frequency).ago end + def self.cached_custom_formable_type + Rails.cache.fetch([Apartment::Tenant.current, 'CustomFieldProperty', 'cached_custom_formable_type']) { + where(custom_formable_type: 'Client').pluck(:custom_field_id).uniq + } + end + private def create_client_history ClientHistory.initial(custom_formable) end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'CustomFieldProperty', 'cached_custom_formable_type']) + end end diff --git a/app/models/program_stream.rb b/app/models/program_stream.rb index 308a3d4db3..ee702b3792 100644 --- a/app/models/program_stream.rb +++ b/app/models/program_stream.rb @@ -155,6 +155,12 @@ def self.cache_program_steam_by_enrollment end end + def self.cached_program_ids(program_ids) + Rails.cache.fetch([Apartment::Tenant.current, 'ProgramStream', 'cached_program_ids', *program_ids.sort]) { + where(id: program_ids).to_a + } + end + private def rules_edition @@ -340,5 +346,7 @@ def get_rules(queries, ss) def flash_cache Rails.cache.delete([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) + cache_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_program_ids/].blank? } + cache_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/views/clients/client_advanced_searches/_form_option.haml b/app/views/clients/client_advanced_searches/_form_option.haml index c21e77b6c1..4eb554b8a8 100644 --- a/app/views/clients/client_advanced_searches/_form_option.haml +++ b/app/views/clients/client_advanced_searches/_form_option.haml @@ -22,7 +22,7 @@ .row.custom-form .col-xs-12 .form-group#custom-form-data{ data: { value: custom_form_values } } - = select_tag(:nil, options_for_select(@custom_fields.pluck(:form_title, :id), custom_form_values), { multiple: true, id: 'custom-form-select', class: 'form-control custom-form-select' } ) + = select_tag(:nil, options_for_select(@custom_fields.map{|custom_field| [custom_field.form_title, custom_field.id] }, custom_form_values), { multiple: true, id: 'custom-form-select', class: 'form-control custom-form-select' } ) .col-xs-12{ class: current_setting.try(:enable_hotline) ? 'col-md-3' : 'col-md-4' } .program-stream-wrapper diff --git a/app/views/clients/client_advanced_searches/_wizard.haml b/app/views/clients/client_advanced_searches/_wizard.haml index ea0f313c50..3903fe391f 100644 --- a/app/views/clients/client_advanced_searches/_wizard.haml +++ b/app/views/clients/client_advanced_searches/_wizard.haml @@ -40,7 +40,7 @@ .col-xs-12 %p Which Custom Forms would you like to show information from? .form-group#wizard-custom-form-data{ data: { value: custom_form_values('#wizard-builder') } } - = select_tag(:nil, options_for_select(@custom_fields.pluck(:form_title, :id), custom_form_values('#wizard-builder')), { multiple: true, id: 'wizard-custom-form-select', class: 'form-control' } ) + = select_tag(:nil, options_for_select(@custom_fields.map{|custom_field| [custom_field.form_title, custom_field.id] }, custom_form_values('#wizard-builder')), { multiple: true, id: 'wizard-custom-form-select', class: 'form-control' } ) %br .loader.hidden .row#custom-form-column From 2b6f9a693f1dc7de43477d056732a53acdc4ed8a Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 09:36:56 +0700 Subject: [PATCH 54/94] Add Cache - #get_client_basic_fields - app/classes/advanced_searches/client_fields.rb - #render - app/classes/advanced_searches/custom_domain_score_fields.rb - #domain_options - Domain (Cache Domain options) - #drop_down_type_list - location_of_concern - Client (Cache location_of_concern) - #user_select_options (in app/helpers/advanced_search_helper.rb) - User (Cache user_select_options) - #agencies_options - Agency (Cache cache_agency_options) - #received_by_options (in app/helpers/advanced_search_field_helper.rb) (Cache in received_by_options) - #referral_source_options - ReferralSource (Cache cache_referral_source_options) - #followed_up_by_options (in app/helpers/advanced_search_field_helper.rb) (Cache followed_up_by_options) - #user_select_options - User (Cache received_by_options) - #donor_options - Donor (Cache cache_donor_options) - #active_program_options - ClientEnrollment (Cache cache_active_program_options) - ProgramStream (Cache ) - #enrolled_program_options - ClientEnrollment (Cache cache_program_steam_by_enrollment) - ProgramStream (Cache cache_program_steam_by_enrollment) - #setting_country_fields - Client cache_mapping_province_name - District (Cache cached_dropdown_list_option) - Commune (Cache cached_dropdown_list_option) - Village (Cache cached_dropdown_list_option) - Subdistrict (Cache cached_dropdown_list_option) - Township (Cache cached_dropdown_list_option) - State (Cache cached_dropdown_list_option) - #referral_to_options - Organization (Cache cache_mapping_ngo_names) - #referral_source_category_options - ReferralSource (Cache cache_referral_source_category_options) - #get_type_of_services - Service (Cache cache_only_child_services) --- .../advanced_searches/client_fields.rb | 32 +++++++++---------- .../custom_domain_score_fields.rb | 6 +--- app/classes/advanced_searches/rule_fields.rb | 10 +++--- app/helpers/advanced_search_field_helper.rb | 12 ++++--- app/models/agency.rb | 4 +++ app/models/client.rb | 20 ++++++++++++ app/models/client_enrollment.rb | 13 ++++++++ app/models/commune.rb | 8 +++-- app/models/concerns/address_concern.rb | 10 ++++++ app/models/district.rb | 4 +-- app/models/domain.rb | 18 +++++++++++ app/models/donor.rb | 4 +++ app/models/family.rb | 8 +++++ app/models/field_setting.rb | 2 +- app/models/organization.rb | 11 +++++++ app/models/program_stream.rb | 7 ++-- app/models/province.rb | 4 --- app/models/referral_source.rb | 25 +++++++++++++++ app/models/service.rb | 14 ++++++++ app/models/state.rb | 2 ++ app/models/subdistrict.rb | 4 ++- app/models/township.rb | 4 +++ app/models/user.rb | 12 +++++++ app/models/village.rb | 14 ++++++-- app/policies/client_policy.rb | 8 ++--- 25 files changed, 204 insertions(+), 52 deletions(-) diff --git a/app/classes/advanced_searches/client_fields.rb b/app/classes/advanced_searches/client_fields.rb index fdb21c43b3..b153f0129e 100644 --- a/app/classes/advanced_searches/client_fields.rb +++ b/app/classes/advanced_searches/client_fields.rb @@ -77,7 +77,7 @@ def date_type_list def drop_down_type_list yes_no_options = { true: 'Yes', false: 'No' } fields = [ - ['location_of_concern', Client.where.not(location_of_concern: [nil, '']).distinct.pluck(:location_of_concern).map{ |a| { a => a }}], + ['location_of_concern', Client.cache_location_of_concern], ['created_by', user_select_options ], ['gender', gender_list], ['status', client_status], @@ -139,13 +139,11 @@ def case_note_type_options end def active_program_options - program_ids = ClientEnrollment.active.pluck(:program_stream_id).uniq - ProgramStream.where(id: program_ids).order(:name).map { |ps| { ps.id.to_s => ps.name } } + ClientEnrollment.cache_active_program_options end def enrolled_program_options - program_ids = ClientEnrollment.pluck(:program_stream_id).uniq - ProgramStream.where(id: program_ids).order(:name).map { |ps| { ps.id.to_s => ps.name } } + ProgramStream.cache_program_steam_by_enrollment.map { |ps| { ps.id.to_s => ps.name } } end def client_status @@ -153,7 +151,7 @@ def client_status end def provinces - Client.province_is.sort.map{|s| {s[1].to_s => s[0]}} + Province.cached_dropdown_list_option end def birth_provinces @@ -166,49 +164,49 @@ def birth_provinces end def districts - District.joins(:clients).pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + District.cached_dropdown_list_option end def communes - Commune.joins(:clients, district: :province).distinct.map{|commune| ["#{commune.name} (#{commune.code})", commune.id]}.sort.map{|s| {s[1].to_s => s[0]}} + Commune.cache_by_client_district_province_and_mapping_names end def villages - Village.joins(:clients, commune: [district: :province]).distinct.map{|village| ["#{village.name_kh} / #{village.name_en} (#{village.code})", village.id]}.sort.map{|s| {s[1].to_s => s[0]}} + Village.cache_village_name_by_client_commune_district_province end def subdistricts - Subdistrict.joins(:clients).pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + Subdistrict.cached_dropdown_list_option end def townships - Township.joins(:clients).pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + Township.cached_dropdown_list_option end def states - State.joins(:clients).pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + State.cached_dropdown_list_option end def get_type_of_services - Service.only_children.pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + Service.cache_only_child_services end def agencies_options - Agency.order(:name).map { |s| { s.id.to_s => s.name } } + Agency.cache_agency_options end def donor_options - Donor.order(:name).map { |donor| { donor.id.to_s => donor.name } } + Donor.cache_donor_options end def referral_to_options - orgs = Organization.oscar.map { |org| { org.short_name => org.full_name } } + orgs = Organization.cache_mapping_ngo_names orgs << { "MoSVY External System" => "MoSVY External System" } orgs << { "external referral" => "I don't see the NGO I'm looking for" } end def referral_from_options - orgs = Organization.oscar.map { |org| { org.short_name => org.full_name } } + orgs = Organization.cache_mapping_ngo_names orgs << { "MoSVY External System" => "MoSVY External System" } end diff --git a/app/classes/advanced_searches/custom_domain_score_fields.rb b/app/classes/advanced_searches/custom_domain_score_fields.rb index 87c417aed9..76038d94fb 100644 --- a/app/classes/advanced_searches/custom_domain_score_fields.rb +++ b/app/classes/advanced_searches/custom_domain_score_fields.rb @@ -15,11 +15,7 @@ def self.render(domain_type = 'client') private def self.domain_options(domain_type = 'client') - if domain_type == 'family' - Domain.family_custom_csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } - else - Domain.custom_csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } - end + Domain.cache_domain_options(domain_type) end def self.domain_score_format(label) diff --git a/app/classes/advanced_searches/rule_fields.rb b/app/classes/advanced_searches/rule_fields.rb index 43970e8584..28c0fb7667 100644 --- a/app/classes/advanced_searches/rule_fields.rb +++ b/app/classes/advanced_searches/rule_fields.rb @@ -102,23 +102,23 @@ def states end def referral_source_options - ReferralSource.child_referrals.order(:name).map { |s| { s.id.to_s => s.name } } + ReferralSource.cache_referral_source_options end def referral_source_category_options if I18n.locale == :km - ReferralSource.parent_categories.order(:name).map { |s| { s.id.to_s => s.name } } + ReferralSource.cache_local_referral_source_category_options else - ReferralSource.parent_categories.order(:name_en).map { |s| { s.id.to_s => s.name } } + ReferralSource.cache_referral_source_category_options end end def agencies_options - Agency.order(:name).map { |s| { s.id.to_s => s.name } } + Agency.cache_agency_options end def user_select_options - User.non_strategic_overviewers.order(:first_name, :last_name).map { |user| { user.id.to_s => user.name } } + User.cached_user_select_options end def donor_options diff --git a/app/helpers/advanced_search_field_helper.rb b/app/helpers/advanced_search_field_helper.rb index ecc2e59a6b..46f137a081 100644 --- a/app/helpers/advanced_search_field_helper.rb +++ b/app/helpers/advanced_search_field_helper.rb @@ -1,12 +1,16 @@ module AdvancedSearchFieldHelper def received_by_options(klass_name = 'Client') - recevied_by_clients = @user.admin? || @user.manager? ? klass_name.constantize.is_received_by : klass_name.constantize.where(user_id: @user.id).is_received_by - recevied_by_clients.sort.map { |s| { s[1].to_s => s[0] } } + Rails.cache.fetch([Apartment::Tenant.current, klass_name, 'received_by', @user.id]) do + recevied_by_clients = @user.admin? || @user.manager? ? klass_name.constantize.is_received_by : klass_name.constantize.where(user_id: @user.id).is_received_by + recevied_by_clients.sort.map { |s| { s[1].to_s => s[0] } } + end end def followed_up_by_options(klass_name = 'Client') - followed_up_clients = @user.admin? || @user.manager? ? klass_name.constantize.is_followed_up_by : klass_name.constantize.where(user_id: @user.id).is_followed_up_by - followed_up_clients.sort.map { |s| { s[1].to_s => s[0] } } + Rails.cache.fetch([Apartment::Tenant.current, klass_name, 'followed_up_by', @user.id]) do + followed_up_clients = @user.admin? || @user.manager? ? klass_name.constantize.is_followed_up_by : klass_name.constantize.where(user_id: @user.id).is_followed_up_by + followed_up_clients.sort.map { |s| { s[1].to_s => s[0] } } + end end def created_by_options(klass_name = 'Client') diff --git a/app/models/agency.rb b/app/models/agency.rb index 36ae1dfd40..6498a044e7 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -19,6 +19,10 @@ def self.cached_order_name Rails.cache.fetch([Apartment::Tenant.current, 'Agency', 'cached_order_name']) { order(:name).to_a } end + def self.cache_agency_options + Agency.cached_order_name.map { |s| { s.id.to_s => s.name } } + end + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Agency', id]) Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) diff --git a/app/models/client.rb b/app/models/client.rb index 58b44106c6..c69c6a834e 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -137,6 +137,7 @@ class Client < ActiveRecord::Base after_commit :remove_family_from_case_worker after_commit :update_related_family_member, on: :update after_commit :delete_referee, on: :destroy + after_commit :flush_cache scope :given_name_like, ->(value) { where('clients.given_name iLIKE :value OR clients.local_given_name iLIKE :value', { value: "%#{value.squish}%"}) } scope :family_name_like, ->(value) { where('clients.family_name iLIKE :value OR clients.local_family_name iLIKE :value', { value: "%#{value.squish}%"}) } @@ -272,6 +273,12 @@ def update_external_ids(short_name, client_ids, data_hash) end end end + + def cache_location_of_concern + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'location_of_concern']) do + Client.where.not(location_of_concern: [nil, '']).pluck(:location_of_concern).map{ |a| { a => a } } + end + end end def self.fetch_75_chars_of(value) @@ -826,4 +833,17 @@ def delete_referee referee.destroy end + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'Client', 'location_of_concern']) if location_of_concern_changed? + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'received_by', received_by_id]) if received_by_id_changed? + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'followed_up_by', followed_up_by_id]) if followed_up_by_id_changed? + Rails.cache.delete([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) if province_id_changed? + Rails.cache.delete([Apartment::Tenant.current, "District", 'dropdown_list_option']) if district_id_changed? + Rails.cache.delete([Apartment::Tenant.current, "Commune", 'dropdown_list_option']) if commune_id_changed? + Rails.cache.delete([Apartment::Tenant.current, 'Village', 'cache_village_name_by_client_commune_district_province']) if village_id_changed? + Rails.cache.delete([Apartment::Tenant.current, 'Subdistrict', 'dropdown_list_option']) if subdistrict_id_changed? + Rails.cache.delete([Apartment::Tenant.current, 'Township', 'dropdown_list_option']) if township_id_changed? + Rails.cache.delete([Apartment::Tenant.current, 'State', 'dropdown_list_option']) if state_id_changed? + end + end diff --git a/app/models/client_enrollment.rb b/app/models/client_enrollment.rb index 30fc348ec1..bd45b339ff 100644 --- a/app/models/client_enrollment.rb +++ b/app/models/client_enrollment.rb @@ -29,6 +29,7 @@ class ClientEnrollment < ActiveRecord::Base after_create :set_client_status after_save :create_client_enrollment_history after_destroy :reset_client_status + after_commit :flush_cache def active? status == 'Active' @@ -69,6 +70,13 @@ def short_enrollment_date enrollment_date.end_of_month.strftime '%b-%y' end + def self.cache_active_program_options + Rails.cache.fetch([Apartment::Tenant.current, 'cache_active_program_options']) do + program_ids = ClientEnrollment.active.pluck(:program_stream_id).uniq + ProgramStream.where(id: program_ids).order(:name).map { |ps| { ps.id.to_s => ps.name } } + end + end + private def create_client_enrollment_history @@ -80,4 +88,9 @@ def enrollment_date_value errors.add(:enrollment_date, I18n.t('invalid_program_enrollment_date')) end end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) + Rails.cache.delete([Apartment::Tenant.current, 'cache_active_program_options']) + end end diff --git a/app/models/commune.rb b/app/models/commune.rb index bbd96f757a..ea7f8db6bb 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -1,4 +1,6 @@ class Commune < ActiveRecord::Base + include AddressConcern + attr_accessor :name has_paper_trail @@ -47,8 +49,10 @@ def cached_villages Rails.cache.fetch([Apartment::Tenant.current, 'Commune', id, 'cached_villages']) { villages.order(:code).to_a } end - def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'Commune', 'dropdown_list_option']) { self.dropdown_list_option } + def self.cache_by_client_district_province_and_mapping_names + Rails.cache.fetch([Apartment::Tenant.current, "Commune", 'cache_by_client_district_province_and_mapping_names']) do + Commune.joins(:clients, district: :province).distinct.map{|commune| ["#{commune.name} (#{commune.code})", commune.id]}.sort.map{|s| {s[1].to_s => s[0]}} + end end def flush_cache diff --git a/app/models/concerns/address_concern.rb b/app/models/concerns/address_concern.rb index c8ee202095..8cf8fa765e 100644 --- a/app/models/concerns/address_concern.rb +++ b/app/models/concerns/address_concern.rb @@ -1,12 +1,22 @@ module AddressConcern extend ActiveSupport::Concern included do + after_commit :flush_cache scope :dropdown_list_option, -> { joins(:clients).pluck(:name, :id).uniq.sort.map{|s| { s[1].to_s => s[0] } } } end class_methods do + def cached_dropdown_list_option + Rails.cache.fetch([Apartment::Tenant.current, self.name, 'dropdown_list_option']) { self.dropdown_list_option } + end end def instance_method + + private + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'dropdown_list_option']) + end end end diff --git a/app/models/district.rb b/app/models/district.rb index 06c294208d..b0b60b0e82 100644 --- a/app/models/district.rb +++ b/app/models/district.rb @@ -47,9 +47,7 @@ def cached_subdistricts Rails.cache.fetch([Apartment::Tenant.current, 'District', id, 'cached_subdistricts']) { subdistricts.order(:name).to_a } end - def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'District', 'dropdown_list_option']) { self.dropdown_list_option } - end + private def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'District', id]) diff --git a/app/models/domain.rb b/app/models/domain.rb index ae4127a2d7..bbcf819504 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -30,6 +30,8 @@ class Domain < ActiveRecord::Base scope :custom_domains, -> { where(custom_domain: true) } scope :family_custom_csi_domains, -> { where(domain_type: 'family', custom_domain: true) } + after_commit :flush_cache + delegate :custom_assessment_name, to: :custom_assessment_setting, prefix: false, allow_nil: true enum domain_score_colors: { danger: 'Red', warning: 'Yellow', success: 'Blue', primary: 'Green' } @@ -54,4 +56,20 @@ def translate_description def domain_type_client? domain_type == 'client' end + + def self.cache_domain_options(domain_type) + Rails.cache.fetch([Apartment::Tenant.current, 'Domain', domain_type, 'domain_options']) do + if domain_type == 'family' + Domain.family_custom_csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } + else + Domain.custom_csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } + end + end + end + + private + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'Domain', domain_type, 'domain_options']) + end end diff --git a/app/models/donor.rb b/app/models/donor.rb index 74085882b8..a341f47b77 100644 --- a/app/models/donor.rb +++ b/app/models/donor.rb @@ -23,6 +23,10 @@ def self.cached_order_name Rails.cache.fetch([Apartment::Tenant.current, 'Donor', 'cached_order_name']) { order(:name).to_a } end + def self.cache_donor_options + Donor.cached_order_name.map { |donor| { donor.id.to_s => donor.name } } + end + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Donor', id]) Rails.cache.delete([Apartment::Tenant.current, 'Donor', 'cached_order_name']) diff --git a/app/models/family.rb b/app/models/family.rb index 6e39205c40..1270690374 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -80,6 +80,7 @@ class Family < ActiveRecord::Base after_create :assign_slug after_save :save_family_in_client, :mark_referral_as_saved after_commit :update_related_community_member, on: :update + after_commit :flush_cache def self.update_brc_aggregation_data Organization.switch_to 'brc' @@ -253,4 +254,11 @@ def mark_referral_as_saved referral.save(validate: false) end end + + private + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'received_by', received_by_id]) if received_by_id_changed? + Rails.cache.delete([Apartment::Tenant.current, self.class.name, 'followed_up_by', followed_up_by_id]) if followed_up_by_id_changed? + end end diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 8fa2d092a4..971aac767a 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -57,7 +57,7 @@ def self.cache_by_name_klass_name_instance(name, klass_name = 'client') def self.cache_query_find_by_ngo_name Rails.cache.fetch([Apartment::Tenant.current, 'field_settings', 'cache_query_find_by_ngo_name']) do - by_instances(Apartment::Tenant.current) + by_instances(Apartment::Tenant.current).to_a end end diff --git a/app/models/organization.rb b/app/models/organization.rb index bad6e46573..9a9985112c 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -32,6 +32,7 @@ class Organization < ActiveRecord::Base before_save :clean_supported_languages, if: :supported_languages? after_commit :upsert_referral_source_category, on: [:create, :update] after_commit :delete_referral_source_category, on: :destroy + after_commit :flush_cache class << self def current @@ -159,6 +160,12 @@ def self.cache_table_exists?(table_name) end end + def self.cache_mapping_ngo_names + Rails.cache.fetch([Apartment::Tenant.current, 'cache_mapping_ngo_names']) do + Organization.oscar.map { |org| { org.short_name => org.full_name } } + end + end + private def upsert_referral_source_category @@ -183,4 +190,8 @@ def delete_referral_source_category referral_source.destroy if referral_source end end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'cache_mapping_ngo_names']) + end end diff --git a/app/models/program_stream.rb b/app/models/program_stream.rb index 308a3d4db3..f2d379ea24 100644 --- a/app/models/program_stream.rb +++ b/app/models/program_stream.rb @@ -42,7 +42,7 @@ class ProgramStream < ActiveRecord::Base before_save :set_program_completed, :destroy_tracking after_update :auto_update_exit_program, :auto_update_enrollment, :update_save_search after_create :build_permission - after_commit :flash_cache + after_commit :flush_cache scope :ordered, -> { order('lower(name) ASC') } scope :complete, -> { where(completed: true) } @@ -149,7 +149,7 @@ def attached_to_community? end def self.cache_program_steam_by_enrollment - Rails.cache.fetch([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) do + Rails.cache.fetch([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) do program_ids = ClientEnrollment.pluck(:program_stream_id).uniq ProgramStream.where(id: program_ids).order(:name).to_a end @@ -338,7 +338,8 @@ def get_rules(queries, ss) end end - def flash_cache + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) + Rails.cache.delete([Apartment::Tenant.current, 'cache_active_program_options']) end end diff --git a/app/models/province.rb b/app/models/province.rb index 0fac01deea..5190a63089 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -44,10 +44,6 @@ def self.cached_order_name Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'cached_order_name']) { order(:name).to_a } end - def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) {self.dropdown_list_option} - end - def cached_districts Rails.cache.fetch([Apartment::Tenant.current, 'Province', id, 'cached_districts']) { districts.order(:name).to_a } end diff --git a/app/models/referral_source.rb b/app/models/referral_source.rb index 6a4f9df65e..aa573825ee 100644 --- a/app/models/referral_source.rb +++ b/app/models/referral_source.rb @@ -7,10 +7,29 @@ class ReferralSource < ActiveRecord::Base validate :restrict_update, on: :update before_destroy :restrict_delete after_save :update_client_referral_source + after_commit :flush_cache scope :parent_categories, -> { where(name: REFERRAL_SOURCES) } scope :child_referrals, -> { where.not(name: REFERRAL_SOURCES) } + def self.cache_referral_source_options + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'referral_source_options']) do + ReferralSource.child_referrals.order(:name).map { |s| { s.id.to_s => s.name } } + end + end + + def self.cache_local_referral_source_category_options + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_local_referral_source_category_options']) do + ReferralSource.child_referrals.order(:name).map { |s| { s.id.to_s => s.name } } + end + end + + def self.cache_referral_source_category_options + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_category_options']) do + ReferralSource.child_referrals.order(:name).map { |s| { s.id.to_s => s.name } } + end + end + private def update_client_referral_source @@ -28,4 +47,10 @@ def restrict_update def restrict_delete errors.add(:base, 'Referral Source cannot be deleted') if REFERRAL_SOURCES.include?(self.name) end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'referral_source_options']) + Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_category_options']) + Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_local_referral_source_category_options']) + end end diff --git a/app/models/service.rb b/app/models/service.rb index f5b7109b29..52b5d7f971 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -11,7 +11,21 @@ class Service < ActiveRecord::Base validates :name, presence: true + after_commit :flush_cache + scope :only_parents, -> { where(parent_id: nil) } scope :only_children, -> { where.not(parent_id: nil) } scope :names, -> { only_children.pluck(:name) } + + def self.cache_only_child_services + Rails.cache.fetch([Apartment::Tenant.current, 'Service', 'cache_only_child_services']) do + self.only_children.pluck(:name, :id).uniq.sort.map{|s| {s[1].to_s => s[0]}} + end + end + + private + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'Service', 'cache_only_child_services']) + end end diff --git a/app/models/state.rb b/app/models/state.rb index 4dd8c12cdb..e43473861c 100644 --- a/app/models/state.rb +++ b/app/models/state.rb @@ -1,4 +1,6 @@ class State < ActiveRecord::Base + include AddressConcern + has_many :townships has_many :clients, dependent: :restrict_with_error diff --git a/app/models/subdistrict.rb b/app/models/subdistrict.rb index 1ca57cc17f..81c011bb48 100644 --- a/app/models/subdistrict.rb +++ b/app/models/subdistrict.rb @@ -1,4 +1,6 @@ class Subdistrict < ActiveRecord::Base + include AddressConcern + after_commit :flush_cache belongs_to :district, touch: true has_many :clients, dependent: :restrict_with_error @@ -7,7 +9,7 @@ class Subdistrict < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false, scope: [:district_id] } def self.cached_find(id) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, id]) { find(id) } + Rails.cache.fetch([Apartment::Tenant.current, self.name, id]) { find(id) } end def flush_cache diff --git a/app/models/township.rb b/app/models/township.rb index f0535f5046..9d499c93ca 100644 --- a/app/models/township.rb +++ b/app/models/township.rb @@ -1,7 +1,11 @@ class Township < ActiveRecord::Base + include AddressConcern + belongs_to :state has_many :clients, dependent: :restrict_with_error validates :state, presence: true validates :name, presence: true, uniqueness: { case_sensitive: false, scope: [:state_id] } + + end diff --git a/app/models/user.rb b/app/models/user.rb index a9bf1cf3c6..0cec01b876 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -88,6 +88,7 @@ class User < ActiveRecord::Base after_save :detach_manager, if: 'roles_changed?' after_save :toggle_referral_notification after_create :build_permission + after_commit :flush_cache class << self def current_user=(user) @@ -372,6 +373,12 @@ def cache_advance_saved_search Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id, 'advance_saved_search']) { self.advanced_searches.order(:name).to_a } end + def self.cached_user_select_options + Rails.cache.fetch([Apartment::Tenant.current, 'User', 'user_select_options']) do + User.non_strategic_overviewers.order(:first_name, :last_name).map { |user| { user.id.to_s => user.name } } + end + end + private def toggle_referral_notification @@ -393,4 +400,9 @@ def exited_clients(user_ids) client_ids = CaseWorkerClient.where(id: PaperTrail::Version.where(item_type: 'CaseWorkerClient', event: 'create').joins(:version_associations).where(version_associations: { foreign_key_name: 'user_id', foreign_key_id: user_ids }).distinct.map(&:item_id)).pluck(:client_id).uniq Client.where(id: client_ids, status: 'Exited').ids end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'User', 'user_select_options']) + end + end diff --git a/app/models/village.rb b/app/models/village.rb index b2f510e8f4..fd7fdc0730 100644 --- a/app/models/village.rb +++ b/app/models/village.rb @@ -10,7 +10,7 @@ class Village < ActiveRecord::Base validates :commune, :name_kh, :name_en, presence: true validates :code, presence: true, uniqueness: true - scope :dropdown_list_option, -> { all.map{|c| { c.id => c.name } } } + scope :dropdown_list_option, -> { joins(:clients).map{|c| { c.id => c.name } } } after_commit :flush_cache @@ -37,9 +37,17 @@ def self.get_village_name_by_code(village_code) end def self.cached_dropdown_list_option - Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'dropdown_list_option']) { self.dropdown_list_option } + Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'dropdown_list_option']) { Village.dropdown_list_option } end - + + def self.cache_village_name_by_client_commune_district_province + Rails.cache.fetch([Apartment::Tenant.current, 'Village', 'cache_village_name_by_client_commune_district_province']) do + Village.joins(:clients, commune: [district: :province]).distinct.map{|village| ["#{village.name_kh} / #{village.name_en} (#{village.code})", village.id]}.sort.map{|s| {s[1].to_s => s[0]}} + end + end + + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, "Village", 'dropdown_list_option']) end diff --git a/app/policies/client_policy.rb b/app/policies/client_policy.rb index 9e5a050c5e..eaf6791eb7 100644 --- a/app/policies/client_policy.rb +++ b/app/policies/client_policy.rb @@ -25,16 +25,16 @@ def show_legal_doc? def brc_client_address? fields = %w(current_island current_street current_po_box current_settlement current_resident_own_or_rent current_household_type) - field_settings.where(name: fields).any? && fields.any?{ |field| show?(field.to_sym) } + FieldSetting.by_instances(Apartment::Tenant.current).where(name: fields).any? && fields.any?{ |field| show?(field.to_sym) } end def brc_client_other_address? fields = %w(island2 street2 po_box2 settlement2 resident_own_or_rent2 household_type2) - field_settings.where(name: fields).any? && fields.any?{ |field| show?(field.to_sym) } + FieldSetting.by_instances(Apartment::Tenant.current).where(name: fields).any? && fields.any?{ |field| show?(field.to_sym) } end def client_stackholder_contacts? - field_settings.where(name: Client::STACKHOLDER_CONTACTS_FIELDS).any? && + FieldSetting.by_instances(Apartment::Tenant.current).where(name: Client::STACKHOLDER_CONTACTS_FIELDS).any? && Client::STACKHOLDER_CONTACTS_FIELDS.any?{ |field| show?(field) } end @@ -45,7 +45,7 @@ def client_school_information? :main_school_contact, :education_background ] - field_settings.where(name: fields).any? && fields.any?{ |field| show?(field) } + FieldSetting.by_instances(Apartment::Tenant.current).where(name: fields).any? && fields.any?{ |field| show?(field) } end def show?(*field_names) From 63000b780d178551fa81c2ed9cd394edb072d072 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 10:13:30 +0700 Subject: [PATCH 55/94] Removed unneeded cache --- .../advanced_searches/domain_score_fields.rb | 3 +-- .../client_advanced_searches_concern.rb | 6 +++--- .../community_advanced_search_concern.rb | 5 +---- .../family_advanced_searches_concern.rb | 19 +++++-------------- .../partner_advanced_searches_concern.rb | 11 ++++------- app/models/advanced_search.rb | 2 +- app/models/enrollment.rb | 6 ++++++ 7 files changed, 21 insertions(+), 31 deletions(-) diff --git a/app/classes/advanced_searches/domain_score_fields.rb b/app/classes/advanced_searches/domain_score_fields.rb index f4c7d6df31..a6257356ba 100644 --- a/app/classes/advanced_searches/domain_score_fields.rb +++ b/app/classes/advanced_searches/domain_score_fields.rb @@ -15,8 +15,7 @@ def self.render private def self.domain_options - - Domain.csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } + Domain.csi_domains.order_by_identity.map { |domain| "domainscore__#{domain.id}__#{domain.identity}" } end def self.domain_score_format(label) diff --git a/app/controllers/concerns/client_advanced_searches_concern.rb b/app/controllers/concerns/client_advanced_searches_concern.rb index 7752edbc3a..4910e7cba9 100644 --- a/app/controllers/concerns/client_advanced_searches_concern.rb +++ b/app/controllers/concerns/client_advanced_searches_concern.rb @@ -71,9 +71,9 @@ def get_custom_form end def hotline_call_column - client_hotlines = get_client_hotline_fields.group_by{ |field| field[:optgroup] } - call_hotlines = get_hotline_fields.group_by{ |field| field[:optgroup] } - @hotline_call_columns = client_hotlines.merge(call_hotlines) + client_hotlines = get_client_hotline_fields.group_by{ |field| field[:optgroup] } + call_hotlines = get_hotline_fields.group_by{ |field| field[:optgroup] } + @hotline_call_columns = client_hotlines.merge(call_hotlines) end def program_stream_fields diff --git a/app/controllers/concerns/community_advanced_search_concern.rb b/app/controllers/concerns/community_advanced_search_concern.rb index de5e6a04e1..7eda157284 100644 --- a/app/controllers/concerns/community_advanced_search_concern.rb +++ b/app/controllers/concerns/community_advanced_search_concern.rb @@ -80,10 +80,7 @@ def quantitative_check? end def find_params_advanced_search - Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do - @advanced_search_params = params[:community_advanced_search] - end - + @advanced_search_params = params[:community_advanced_search] end def basic_params diff --git a/app/controllers/concerns/family_advanced_searches_concern.rb b/app/controllers/concerns/family_advanced_searches_concern.rb index 8ec99c794f..558b801b53 100644 --- a/app/controllers/concerns/family_advanced_searches_concern.rb +++ b/app/controllers/concerns/family_advanced_searches_concern.rb @@ -60,10 +60,8 @@ def custom_form_column end def get_custom_form - Rails.cache.fetch(user_cache_id << "get_custom_form") do - form_ids = CustomFieldProperty.where(custom_formable_type: 'Family').pluck(:custom_field_id).uniq - @custom_fields = CustomField.where(id: form_ids).order_by_form_title - end + form_ids = CustomFieldProperty.where(custom_formable_type: 'Family').pluck(:custom_field_id).uniq + @custom_fields = CustomField.where(id: form_ids).order_by_form_title end def family_builder_fields @@ -100,9 +98,7 @@ def has_params? end def find_params_advanced_search - Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do - @advanced_search_params = params[:family_advanced_search] - end + @advanced_search_params = params[:family_advanced_search] end def basic_params @@ -263,10 +259,7 @@ def csi_domain_score_report end def get_program_streams - Rails.cache.fetch(user_cache_id << "get_program_streams") do - program_ids = Enrollment.pluck(:program_stream_id).uniq - @program_streams = ProgramStream.where(id: program_ids).order(:name) - end + @program_streams = Enrollment.cache_program_steams end def program_stream_column @@ -274,9 +267,7 @@ def program_stream_column end def program_stream_fields - Rails.cache.fetch(user_cache_id << "program_stream_fields") do - @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields - end + @program_stream_fields = get_enrollment_fields + get_tracking_fields + get_exit_program_fields end def get_enrollment_fields diff --git a/app/controllers/concerns/partner_advanced_searches_concern.rb b/app/controllers/concerns/partner_advanced_searches_concern.rb index 62e2f37150..28b9cc069b 100644 --- a/app/controllers/concerns/partner_advanced_searches_concern.rb +++ b/app/controllers/concerns/partner_advanced_searches_concern.rb @@ -28,11 +28,10 @@ def build_advanced_search def custom_form_column @custom_form_columns = custom_form_fields.group_by{ |field| field[:optgroup] } end + def get_custom_form - Rails.cache.fetch(user_cache_id << "get_custom_form") do - form_ids = CustomFieldProperty.where(custom_formable_type: 'Partner').pluck(:custom_field_id).uniq - @custom_fields = CustomField.where(id: form_ids).order_by_form_title - end + form_ids = CustomFieldProperty.where(custom_formable_type: 'Partner').pluck(:custom_field_id).uniq + @custom_fields = CustomField.where(id: form_ids).order_by_form_title end def partner_builder_fields @@ -68,9 +67,7 @@ def has_params? end def find_params_advanced_search - Rails.cache.fetch(user_cache_id << "find_params_advanced_search") do - @advanced_search_params = params[:partner_advanced_search] - end + @advanced_search_params = params[:partner_advanced_search] end def basic_params diff --git a/app/models/advanced_search.rb b/app/models/advanced_search.rb index e0dd1e7753..068b5b4f13 100644 --- a/app/models/advanced_search.rb +++ b/app/models/advanced_search.rb @@ -42,7 +42,7 @@ def self.cached_advanced_search(params_id) def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'User', user_id, 'advance_saved_search']) Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) - Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id, "other_advanced_search_queries"]) + Rails.cache.fetch([Apartment::Tenant.current, 'User', self.user_id, "other_advanced_search_queries"]) end end diff --git a/app/models/enrollment.rb b/app/models/enrollment.rb index 538c80e89c..5b02325781 100644 --- a/app/models/enrollment.rb +++ b/app/models/enrollment.rb @@ -56,6 +56,12 @@ def get_form_builder_attachment(value) # def short_enrollment_date # enrollment_date.end_of_month.strftime '%b-%y' # end + def self.cache_program_steams + Rails.cache.fetch([Apartment::Tenant.current, 'Enrollment', 'cache_program_steams']) do + program_ids = Enrollment.pluck(:program_stream_id).uniq + ProgramStream.where(id: program_ids).order(:name).to_a + end + end private From 8381deda709ee9fa7a16bf5c7554e0936130a7e4 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 10:23:39 +0700 Subject: [PATCH 56/94] fixed error in app/views/families/family_advanced_searches/_form_option.haml the @program_streams variable --- app/views/families/family_advanced_searches/_form_option.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/families/family_advanced_searches/_form_option.haml b/app/views/families/family_advanced_searches/_form_option.haml index 41e954b49f..2777041004 100644 --- a/app/views/families/family_advanced_searches/_form_option.haml +++ b/app/views/families/family_advanced_searches/_form_option.haml @@ -33,7 +33,7 @@ .row.program-stream .col-xs-12 .form-group#program-stream-data{ data: { value: program_stream_values } } - = select_tag(:nil, options_for_select(@program_streams.pluck(:name, :id), program_stream_values), { multiple: true, id: 'program-stream-select', class: 'form-control program-stream-select' } ) + = select_tag(:nil, options_for_select(@program_streams.map { |program_stream| [program_stream.name, program_stream.id ] }, program_stream_values), { multiple: true, id: 'program-stream-select', class: 'form-control program-stream-select' } ) .row .col-xs-12 .program-association From b1c5155c66221364a2c3855a54285555f3820e71 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Thu, 31 Mar 2022 10:19:34 +0700 Subject: [PATCH 57/94] [IMP] Adjust caching funtion refs OSC-13 --- .../advanced_searches/exit_program_fields.rb | 2 +- .../advanced_searches/tracking_fields.rb | 2 +- app/models/agency.rb | 4 +++- app/models/client_type.rb | 4 +++- app/models/commune.rb | 3 +++ app/models/donor.rb | 2 ++ app/models/interviewee.rb | 4 +++- app/models/need.rb | 4 +++- app/models/problem.rb | 4 +++- app/models/province.rb | 4 +++- app/models/subdistrict.rb | 2 ++ app/models/tracking.rb | 17 +++++++++++++++++ 12 files changed, 44 insertions(+), 8 deletions(-) diff --git a/app/classes/advanced_searches/exit_program_fields.rb b/app/classes/advanced_searches/exit_program_fields.rb index 6625441af0..05ef4cd38a 100644 --- a/app/classes/advanced_searches/exit_program_fields.rb +++ b/app/classes/advanced_searches/exit_program_fields.rb @@ -32,7 +32,7 @@ def render end def generate_field_by_type - program_streams = ProgramStream.where(id: @program_ids) + program_streams = ProgramStream.cached_program_ids(@program_ids) program_streams.each do |program_stream| @exit_data_list << "exitprogramdate__#{program_stream.name}__Exit Date" diff --git a/app/classes/advanced_searches/tracking_fields.rb b/app/classes/advanced_searches/tracking_fields.rb index 7f025af002..47a050cded 100644 --- a/app/classes/advanced_searches/tracking_fields.rb +++ b/app/classes/advanced_searches/tracking_fields.rb @@ -26,7 +26,7 @@ def render end def generate_field_by_type - trackings = Tracking.joins(:program_stream).where(program_stream_id: @program_ids) + trackings = Tracking.cached_program_stream_program_ids(@program_ids) tracking_values = trackings.select("trackings.name, program_streams.name program_name, trackings.fields") tracking_values.each do |tracking| program_name = tracking.program_name diff --git a/app/models/agency.rb b/app/models/agency.rb index 6498a044e7..fbc7bbe973 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -23,8 +23,10 @@ def self.cache_agency_options Agency.cached_order_name.map { |s| { s.id.to_s => s.name } } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Agency', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) if name_changed? end end diff --git a/app/models/client_type.rb b/app/models/client_type.rb index 425c35dc56..64fb5e989d 100644 --- a/app/models/client_type.rb +++ b/app/models/client_type.rb @@ -16,8 +16,10 @@ def self.cached_order_created_at Rails.cache.fetch([Apartment::Tenant.current, 'ClientType', 'cached_order_created_at']) { order(:created_at).to_a } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'ClientType', id]) - Rails.cache.delete([Apartment::Tenant.current, 'ClientType', 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'ClientType', 'cached_order_created_at']) if created_at_changed? end end diff --git a/app/models/commune.rb b/app/models/commune.rb index ea7f8db6bb..5ab57d8b1a 100644 --- a/app/models/commune.rb +++ b/app/models/commune.rb @@ -55,9 +55,12 @@ def self.cache_by_client_district_province_and_mapping_names end end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Commune', id]) Rails.cache.delete([Apartment::Tenant.current, 'Commune', id, 'cached_villages']) Rails.cache.delete([Apartment::Tenant.current, "Commune", 'dropdown_list_option']) + Rails.cache.delete([Apartment::Tenant.current, "Commune", 'cache_by_client_district_province_and_mapping_names']) if name_changed? end end diff --git a/app/models/donor.rb b/app/models/donor.rb index a341f47b77..8abc57f31b 100644 --- a/app/models/donor.rb +++ b/app/models/donor.rb @@ -27,6 +27,8 @@ def self.cache_donor_options Donor.cached_order_name.map { |donor| { donor.id.to_s => donor.name } } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Donor', id]) Rails.cache.delete([Apartment::Tenant.current, 'Donor', 'cached_order_name']) diff --git a/app/models/interviewee.rb b/app/models/interviewee.rb index df1274515e..d8ace2d4ee 100644 --- a/app/models/interviewee.rb +++ b/app/models/interviewee.rb @@ -16,8 +16,10 @@ def self.cached_order_created_at Rails.cache.fetch([Apartment::Tenant.current, 'Interviewee', 'cached_order_created_at']) { order(:created_at).to_a } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Interviewee', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Interviewee', 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Interviewee', 'cached_order_created_at']) if created_at_changed? end end diff --git a/app/models/need.rb b/app/models/need.rb index cf84eaa4fb..5f6190b37f 100644 --- a/app/models/need.rb +++ b/app/models/need.rb @@ -16,8 +16,10 @@ def self.cached_order_created_at Rails.cache.fetch([Apartment::Tenant.current, 'Need', 'cached_order_created_at']) { order(:created_at).to_a } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Need', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Need', 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Need', 'cached_order_created_at']) if created_at_changed? end end diff --git a/app/models/problem.rb b/app/models/problem.rb index 8c7b4f4ee5..eca04e5a49 100644 --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -16,8 +16,10 @@ def self.cached_order_created_at Rails.cache.fetch([Apartment::Tenant.current, 'Problem', 'cached_order_created_at']) { order(:created_at).to_a } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Problem', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Problem', 'cached_order_created_at']) + Rails.cache.delete([Apartment::Tenant.current, 'Problem', 'cached_order_created_at']) if created_at_changed? end end diff --git a/app/models/province.rb b/app/models/province.rb index 5190a63089..8a0fa3edf1 100644 --- a/app/models/province.rb +++ b/app/models/province.rb @@ -48,9 +48,11 @@ def cached_districts Rails.cache.fetch([Apartment::Tenant.current, 'Province', id, 'cached_districts']) { districts.order(:name).to_a } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Province', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Province', 'cached_order_name']) + Rails.cache.delete([Apartment::Tenant.current, 'Province', 'cached_order_name']) if name_changed? Rails.cache.delete([Apartment::Tenant.current, 'Province', id, 'cached_districts']) Rails.cache.delete([Apartment::Tenant.current, 'Province', 'dropdown_list_option']) end diff --git a/app/models/subdistrict.rb b/app/models/subdistrict.rb index 81c011bb48..0a2a503077 100644 --- a/app/models/subdistrict.rb +++ b/app/models/subdistrict.rb @@ -12,6 +12,8 @@ def self.cached_find(id) Rails.cache.fetch([Apartment::Tenant.current, self.name, id]) { find(id) } end + private + def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, id]) end diff --git a/app/models/tracking.rb b/app/models/tracking.rb index 6aa00d5625..5a3986f842 100644 --- a/app/models/tracking.rb +++ b/app/models/tracking.rb @@ -20,6 +20,7 @@ class Tracking < ActiveRecord::Base validates :name, uniqueness: { scope: :program_stream_id } after_update :auto_update_trackings + after_commit :flush_cache default_scope { order(:created_at) } @@ -36,6 +37,16 @@ def is_used? client_enrollment_trackings.present? end + def self.cache_object(id) + Rails.cache.fetch([Apartment::Tenant.current, 'Tracking', id]) { find(id) } + end + + def self.cached_program_stream_program_ids(program_ids) + Rails.cache.fetch([Apartment::Tenant.current, 'Tracking', 'cached_program_stream_program_ids', *program_ids.sort]) { + joins(:program_stream).where(program_stream_id: program_ids).to_a + } + end + private def presence_of_label @@ -54,4 +65,10 @@ def auto_update_trackings enrollment_tracking_objs = self.program_stream.attached_to_client? ? self.client_enrollment_trackings : self.enrollment_trackings labels_update(self.fields_change.last, self.fields_was, enrollment_tracking_objs) end + + def flush_cache + Rails.cache.delete([Apartment::Tenant.current, 'Tracking', self.id]) + cached_program_stream_program_ids_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_program_stream_program_ids/].blank? } + cached_program_stream_program_ids_keys.each { |key| Rails.cache.delete(key) } + end end From 4be7e77936c00523e911e2d6bc8559abf1588572 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 11:18:31 +0700 Subject: [PATCH 58/94] update callback in subdistrict --- app/models/subdistrict.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/subdistrict.rb b/app/models/subdistrict.rb index 0a2a503077..378099a788 100644 --- a/app/models/subdistrict.rb +++ b/app/models/subdistrict.rb @@ -1,13 +1,14 @@ class Subdistrict < ActiveRecord::Base include AddressConcern - after_commit :flush_cache belongs_to :district, touch: true has_many :clients, dependent: :restrict_with_error validates :district, presence: true validates :name, presence: true, uniqueness: { case_sensitive: false, scope: [:district_id] } + after_commit :flush_cache + def self.cached_find(id) Rails.cache.fetch([Apartment::Tenant.current, self.name, id]) { find(id) } end From ac2519393c26eff21916f89de2f0cbd858822086 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 11:30:43 +0700 Subject: [PATCH 59/94] Update method accessibility in app/models/quantitative_type.rb --- app/models/quantitative_type.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/models/quantitative_type.rb b/app/models/quantitative_type.rb index 6a72e49bf3..d9556e6c1e 100644 --- a/app/models/quantitative_type.rb +++ b/app/models/quantitative_type.rb @@ -19,15 +19,7 @@ class QuantitativeType < ActiveRecord::Base scope :name_like, ->(name) { where('quantitative_types.name iLIKE ?', "%#{name}%") } after_create :build_permission - - after_commit :flush_cach - - def flush_cach - Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "client"] ) - Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "community"] ) - Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "family"] ) - end - + after_commit :flush_cach def self.cach_by_visible_on(visible_on) Rails.cache.fetch([Apartment::Tenant.current, "QuantitativeType", visible_on]) do @@ -41,7 +33,6 @@ def self.cach_by_quantitative_type_ids(quantitative_type_ids) end end - private def validate_visible_on @@ -55,4 +46,10 @@ def build_permission self.quantitative_type_permissions.find_or_create_by(user_id: user.id) end end + + def flush_cach + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "client"] ) + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "community"] ) + Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "family"] ) + end end From b8b62a52bbb2f0ad249ea306cd68a4b99e5d4aca Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 12:08:00 +0700 Subject: [PATCH 60/94] Cache FieldSetting legal docs --- app/controllers/application_controller.rb | 4 ++++ app/helpers/clients_helper.rb | 3 +-- app/models/field_setting.rb | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 656ca73e2a..253dc37975 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,6 +30,10 @@ class ApplicationController < ActionController::Base redirect_to root_path, alert: t('unauthorized.default') end + rescue_from ActionController::InvalidAuthenticityToken do |exception| + redirect_to root_path, alert: t('devise.failure.timeout') + end + def current_organization Organization.current end diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 60c24af440..6d164ffa50 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -1407,7 +1407,6 @@ def saved_search_column_visibility(field_key) end def legal_doc_fields - fields = %w(national_id passport birth_cert family_book travel_doc letter_from_immigration_police ngo_partner mosavy dosavy msdhs complain local_consent warrant verdict screening_interview_form short_form_of_ocdm short_form_of_mosavy_dosavy detail_form_of_mosavy_dosavy short_form_of_judicial_police police_interview other_legal_doc) - FieldSetting.without_hidden_fields.where(name: fields).pluck(:name) + FieldSetting.cache_legal_doc_fields end end diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 971aac767a..ad52aaae43 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -73,6 +73,13 @@ def self.show_legal_doc_visible(fields) end end + def self.cache_legal_doc_fields + Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting' 'gelal_dock_fields']) do + fields = %w(national_id passport birth_cert family_book travel_doc letter_from_immigration_police ngo_partner mosavy dosavy msdhs complain local_consent warrant verdict screening_interview_form short_form_of_ocdm short_form_of_mosavy_dosavy detail_form_of_mosavy_dosavy short_form_of_judicial_police police_interview other_legal_doc) + FieldSetting.without_hidden_fields.where(name: fields).pluck(:name) + end + end + private def assign_type @@ -88,5 +95,6 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) Rails.cache.delete([Apartment::Tenant.current, 'table_name', 'field_settings']) Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.group_name, 'hidden_group']) + Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', 'gelal_dock_fields']) end end From b13fb9784082df83c26324b9b6c8e5081bdb0484 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 15:23:47 +0700 Subject: [PATCH 61/94] added Cache in Domain.rb and quantitative_type.rb --- app/classes/client_columns_visibility.rb | 4 ++-- app/models/domain.rb | 7 +++++++ app/models/quantitative_type.rb | 6 ++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/classes/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index cafaa58b7f..9b426214c3 100644 --- a/app/classes/client_columns_visibility.rb +++ b/app/classes/client_columns_visibility.rb @@ -200,7 +200,7 @@ def visible_columns def domain_score_columns columns = columns_collection - Domain.order_by_identity.each do |domain| + Domain.cache_order_by_identity.each do |domain| identity = domain.identity field = domain.custom_domain ? "custom_#{domain.convert_identity}" : domain.convert_identity columns = columns.merge!("#{field}_": field.to_sym) @@ -210,7 +210,7 @@ def domain_score_columns def quantitative_type_columns columns = domain_score_columns - QuantitativeType.joins(:quantitative_cases).where('quantitative_types.visible_on LIKE ?', "%client%").uniq.each do |quantitative_type| + QuantitativeType.cach_by_visible_on('client').each do |quantitative_type| field = quantitative_type.name columns = columns.merge!("#{field}_": field.to_sym) end diff --git a/app/models/domain.rb b/app/models/domain.rb index bbcf819504..8b6fed6897 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -67,9 +67,16 @@ def self.cache_domain_options(domain_type) end end + def self.cache_order_by_identity + Rails.cache.fetch([Apartment::Tenant.current, 'Domain', 'cache_order_by_identity']) do + Domain.order_by_identity.to_a + end + end + private def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Domain', domain_type, 'domain_options']) + Rails.cache.delete([Apartment::Tenant.current, 'Domain', 'cache_order_by_identity']) end end diff --git a/app/models/quantitative_type.rb b/app/models/quantitative_type.rb index d9556e6c1e..9ec45c5ee0 100644 --- a/app/models/quantitative_type.rb +++ b/app/models/quantitative_type.rb @@ -23,16 +23,18 @@ class QuantitativeType < ActiveRecord::Base def self.cach_by_visible_on(visible_on) Rails.cache.fetch([Apartment::Tenant.current, "QuantitativeType", visible_on]) do - QuantitativeType.includes(:quantitative_cases).where('quantitative_types.visible_on LIKE ?', "%#{visible_on}%") + QuantitativeType.includes(:quantitative_cases).where('quantitative_types.visible_on ILIKE ?', "%#{visible_on}%").to_a end end def self.cach_by_quantitative_type_ids(quantitative_type_ids) Rails.cache.fetch([Apartment::Tenant.current, "quantitative_type_ids", quantitative_type_ids]) do - QuantitativeType.includes(:quantitative_cases).where(id: quantitative_type_ids) + QuantitativeType.includes(:quantitative_cases).where(id: quantitative_type_ids).to_a end end + + private def validate_visible_on From 90c0005f1ab145af9008b9d35dc6e4e231c54f43 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 15:26:01 +0700 Subject: [PATCH 62/94] Remove inline caching in app/controllers/concerns/client_grid_options.rb#columns_visibility --- app/controllers/concerns/client_grid_options.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/client_grid_options.rb b/app/controllers/concerns/client_grid_options.rb index 063b097ec6..ec93fdfeb1 100644 --- a/app/controllers/concerns/client_grid_options.rb +++ b/app/controllers/concerns/client_grid_options.rb @@ -16,10 +16,10 @@ def choose_grid def columns_visibility if params[:advanced_search_id] advanced_search = AdvancedSearch.find(params[:advanced_search_id]) - @client_columns ||= ClientColumnsVisibility.new(@client_grid, params.merge(advanced_search.field_visible).merge(column_form_builder: column_form_builder)) + @client_columns = ClientColumnsVisibility.new(@client_grid, params.merge(advanced_search.field_visible).merge(column_form_builder: column_form_builder)) @client_columns.visible_columns else - @client_columns ||= ClientColumnsVisibility.new(@client_grid, params.merge(column_form_builder: column_form_builder)) + @client_columns = ClientColumnsVisibility.new(@client_grid, params.merge(column_form_builder: column_form_builder)) @client_columns.visible_columns end end From 5467f832eb12743a671091148abfd0a622012fdd Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 15:58:30 +0700 Subject: [PATCH 63/94] refactore client advanced search form in app/views/clients/client_advanced_searches/_advanced_search.haml --- .../concerns/client_grid_options.rb | 1 + .../_advanced_search.haml | 49 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/controllers/concerns/client_grid_options.rb b/app/controllers/concerns/client_grid_options.rb index bedbd4e32b..73dc280a46 100644 --- a/app/controllers/concerns/client_grid_options.rb +++ b/app/controllers/concerns/client_grid_options.rb @@ -15,6 +15,7 @@ def choose_grid def columns_visibility return if params['commit'].blank? + if params[:advanced_search_id] advanced_search = AdvancedSearch.find(params[:advanced_search_id]) @client_columns ||= ClientColumnsVisibility.new(@client_grid, params.merge(advanced_search.field_visible).merge(column_form_builder: column_form_builder)) diff --git a/app/views/clients/client_advanced_searches/_advanced_search.haml b/app/views/clients/client_advanced_searches/_advanced_search.haml index b196309e52..87f02ee3ce 100644 --- a/app/views/clients/client_advanced_searches/_advanced_search.haml +++ b/app/views/clients/client_advanced_searches/_advanced_search.haml @@ -29,28 +29,29 @@ #search-action{ data: { action: params.dig(:builder) || params.dig(:client_advanced_search, :action_report_builder)} } .ibox-footer - %button#search.btn.btn-primary= t('.search') - = link_to t('.reset'), clients_path, class: 'btn btn-default', data: { toggle: 'popover', trigger: "hover", html: 'true', content: "#{I18n.t('inline_help.clients.index.buttons.reset')}", placement: "right" } + = simple_form_for :client_advanced_search, url: :advanced_search_clients, method: 'POST', html: { id: 'advanced-search' } do |f| + = f.hidden_field :custom_form_selected + = f.hidden_field :program_selected + = f.hidden_field :enrollment_check + = f.hidden_field :tracking_check + = f.hidden_field :exit_form_check + = f.hidden_field :basic_rules + = f.hidden_field :quantitative_check + = f.hidden_field :hotline_check + = f.hidden_field :wizard_custom_form_check + = f.hidden_field :wizard_program_stream_check + = f.hidden_field :wizard_quantitative_check + = f.hidden_field :wizard_enrollment_check + = f.hidden_field :wizard_tracking_check + = f.hidden_field :wizard_exit_form_check + = f.hidden_field :action_report_builder + = hidden_field_tag :data, params[:data] + / = f.hidden_field :overdue_assessment + = hidden_field_tag :locale, "#{params[:locale]}" + #client-builder-fields{ data: { fields: @builder_fields.to_json }} + #quantitative-fields{ data: { fields: @quantitative_fields.to_json }} + #hotline-fields{ data: { fields: @hotline_fields.to_json } } + + = f.submit t('.search'), class: 'btn btn-primary', id: 'search' + = link_to t('.reset'), clients_path, class: 'btn btn-default', data: { toggle: 'popover', trigger: "hover", html: 'true', content: "#{I18n.t('inline_help.clients.index.buttons.reset')}", placement: "right" } -= simple_form_for :client_advanced_search, url: :advanced_search_clients, method: 'POST', html: { id: 'advanced-search' } do |f| - = f.hidden_field :custom_form_selected - = f.hidden_field :program_selected - = f.hidden_field :enrollment_check - = f.hidden_field :tracking_check - = f.hidden_field :exit_form_check - = f.hidden_field :basic_rules - = f.hidden_field :quantitative_check - = f.hidden_field :hotline_check - = f.hidden_field :wizard_custom_form_check - = f.hidden_field :wizard_program_stream_check - = f.hidden_field :wizard_quantitative_check - = f.hidden_field :wizard_enrollment_check - = f.hidden_field :wizard_tracking_check - = f.hidden_field :wizard_exit_form_check - = f.hidden_field :action_report_builder - = hidden_field_tag :data, params[:data] - / = f.hidden_field :overdue_assessment - = hidden_field_tag :locale, "#{params[:locale]}" - #client-builder-fields{ data: { fields: @builder_fields.to_json }} - #quantitative-fields{ data: { fields: @quantitative_fields.to_json }} - #hotline-fields{ data: { fields: @hotline_fields.to_json } } From 9f2e700115ba6daef0a19727f2ba68e92cc37b33 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 16:22:46 +0700 Subject: [PATCH 64/94] cached visible organization --- app/controllers/organizations_controller.rb | 4 ++-- app/models/organization.rb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 897a399691..23aaa852ba 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -1,8 +1,8 @@ class OrganizationsController < ApplicationController def index - @organizations = Organization.visible.order(:created_at) + @organizations = Organization.cache_visible_ngos if user_signed_in? - redirect_to dashboards_path(subdomain: Organization.current.short_name) + redirect_to dashboards_path(subdomain: Apartment::Tenant.current) else redirect_to root_url(subdomain: 'start') unless request.subdomain == 'start' || request.subdomain == 'mho' end diff --git a/app/models/organization.rb b/app/models/organization.rb index 9a9985112c..fc0ebfe57a 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -166,6 +166,12 @@ def self.cache_mapping_ngo_names end end + def self.cache_visible_ngos + Rails.cache.fetch([Apartment::Tenant.current, 'Organization', 'visible']) do + Organization.visible.order(:created_at).to_a + end + end + private def upsert_referral_source_category @@ -193,5 +199,6 @@ def delete_referral_source_category def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'cache_mapping_ngo_names']) + Rails.cache.delete([Apartment::Tenant.current, 'Organization', 'visible']) end end From a161229bc9494fcce392150d9b38040a0bdba12c Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Mar 2022 17:08:41 +0700 Subject: [PATCH 65/94] added index to tables used find_by and add gem lol_dba --- Gemfile | 1 + Gemfile.lock | 5 + app/models/client.rb | 1 - .../20220331093325_add_missing_indexes.rb | 25 +++++ db/schema.rb | 100 ++++++++++++++++-- 5 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20220331093325_add_missing_indexes.rb diff --git a/Gemfile b/Gemfile index c354ad7d76..7cb9515c11 100644 --- a/Gemfile +++ b/Gemfile @@ -117,6 +117,7 @@ group :development do gem 'metainspector' gem 'flay', '~> 2.12', '>= 2.12.1' gem 'active_record_doctor', '~> 1.8' + gem 'lol_dba', '~> 2.2' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index ef66462536..2c2247fe71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -510,6 +510,10 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) + lol_dba (2.2.0) + actionpack (>= 3.0, < 7.0) + activerecord (>= 3.0, < 7.0) + railties (>= 3.0, < 7.0) loofah (2.13.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -921,6 +925,7 @@ DEPENDENCIES launchy (~> 2.4, >= 2.4.3) letter_opener (~> 1.4.1) letter_opener_web (~> 1.3, >= 1.3.4) + lol_dba (~> 2.2) mail_interceptor (~> 0.0.7) metainspector mini_magick (~> 4.5) diff --git a/app/models/client.rb b/app/models/client.rb index c69c6a834e..9fc9f50be1 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -64,7 +64,6 @@ class Client < ActiveRecord::Base belongs_to :village belongs_to :referee belongs_to :carer - belongs_to :call belongs_to :concern_province, class_name: 'Province', foreign_key: 'concern_province_id' belongs_to :concern_district, class_name: 'District', foreign_key: 'concern_district_id' diff --git a/db/migrate/20220331093325_add_missing_indexes.rb b/db/migrate/20220331093325_add_missing_indexes.rb new file mode 100644 index 0000000000..b36c1579be --- /dev/null +++ b/db/migrate/20220331093325_add_missing_indexes.rb @@ -0,0 +1,25 @@ +class AddMissingIndexes < ActiveRecord::Migration + def change + add_index :case_worker_communities, [:community_id, :user_id] + add_index :case_worker_families, [:family_id, :user_id] + add_index :community_quantitative_cases, [:community_id, :quantitative_case_id], name: 'index_on_community_id_and_quantitative_case_id' + add_index :custom_fields, :form_title + add_index :custom_field_permissions, [:custom_field_id, :user_id] + add_index :custom_field_properties, [:custom_formable_id, :custom_formable_type], name: 'index_on_custom_formable_id_and_custom_formable_type' + add_index :enrollments, [:programmable_id, :programmable_type] + add_index :enter_ngos, [:acceptable_id, :acceptable_type] + add_index :exit_ngos, [:rejectable_id, :rejectable_type] + add_index :external_systems, :name + add_index :family_quantitative_cases, [:family_id, :quantitative_case_id], name: 'index_on_family_id_and_quantitative_case_id' + add_index :form_builder_attachments, :name + add_index :form_builder_attachments, [:form_buildable_id, :form_buildable_type], name: 'index_on_form_buildable_id_and_form_buildable_type' + add_index :government_form_children_plans, [:children_plan_id, :government_form_id], name: 'index_on_children_plan_id_and_government_form_id' + add_index :government_form_family_plans, [:family_plan_id, :government_form_id], name: 'index_on_family_plan_id_and_government_form_id' + add_index :referral_sources, :name + add_index :referral_sources, :name_en + add_index :referrals_services, [:referral_id, :service_id] + add_index :organizations, :short_name + add_index :organizations, :full_name + add_index :program_streams, :name + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 682a322802..1f4c8c9427 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20220105081845) do +ActiveRecord::Schema.define(version: 20220331093325) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -380,6 +380,7 @@ t.datetime "deleted_at" end + add_index "case_worker_communities", ["community_id", "user_id"], name: "index_case_worker_communities_on_community_id_and_user_id", using: :btree add_index "case_worker_communities", ["community_id"], name: "index_case_worker_communities_on_community_id", using: :btree add_index "case_worker_communities", ["deleted_at"], name: "index_case_worker_communities_on_deleted_at", using: :btree add_index "case_worker_communities", ["user_id"], name: "index_case_worker_communities_on_user_id", using: :btree @@ -391,6 +392,7 @@ end add_index "case_worker_families", ["deleted_at"], name: "index_case_worker_families_on_deleted_at", using: :btree + add_index "case_worker_families", ["family_id", "user_id"], name: "index_case_worker_families_on_family_id_and_user_id", using: :btree add_index "case_worker_families", ["family_id"], name: "index_case_worker_families_on_family_id", using: :btree add_index "case_worker_families", ["user_id"], name: "index_case_worker_families_on_user_id", using: :btree @@ -937,6 +939,7 @@ t.datetime "updated_at" end + add_index "community_quantitative_cases", ["community_id", "quantitative_case_id"], name: "index_on_community_id_and_quantitative_case_id", using: :btree add_index "community_quantitative_cases", ["community_id"], name: "index_community_quantitative_cases_on_community_id", using: :btree add_index "community_quantitative_cases", ["quantitative_case_id"], name: "index_community_quantitative_cases_on_quantitative_case_id", using: :btree @@ -962,6 +965,7 @@ t.datetime "updated_at", null: false end + add_index "custom_field_permissions", ["custom_field_id", "user_id"], name: "index_custom_field_permissions_on_custom_field_id_and_user_id", using: :btree add_index "custom_field_permissions", ["custom_field_id"], name: "index_custom_field_permissions_on_custom_field_id", using: :btree add_index "custom_field_permissions", ["user_id"], name: "index_custom_field_permissions_on_user_id", using: :btree @@ -977,6 +981,7 @@ end add_index "custom_field_properties", ["custom_field_id"], name: "index_custom_field_properties_on_custom_field_id", using: :btree + add_index "custom_field_properties", ["custom_formable_id", "custom_formable_type"], name: "index_on_custom_formable_id_and_custom_formable_type", using: :btree add_index "custom_field_properties", ["custom_formable_id"], name: "index_custom_field_properties_on_custom_formable_id", using: :btree add_index "custom_field_properties", ["user_id"], name: "index_custom_field_properties_on_user_id", using: :btree @@ -993,6 +998,8 @@ t.boolean "hidden", default: false end + add_index "custom_fields", ["form_title"], name: "index_custom_fields_on_form_title", using: :btree + create_table "departments", force: :cascade do |t| t.string "name", default: "" t.text "description", default: "" @@ -1001,6 +1008,45 @@ t.integer "users_count", default: 0 end + create_table "developmental_marker_screening_assessments", force: :cascade do |t| + t.integer "developmental_marker_id" + t.integer "screening_assessment_id" + t.boolean "question_1", default: false + t.boolean "question_2", default: false + t.boolean "question_3", default: false + t.boolean "question_4", default: false + end + + add_index "developmental_marker_screening_assessments", ["developmental_marker_id"], name: "index_marker_screening_assessments_on_marker_id", using: :btree + add_index "developmental_marker_screening_assessments", ["screening_assessment_id"], name: "index_marker_screening_assessments_on_screening_assessment_id", using: :btree + + create_table "developmental_markers", force: :cascade do |t| + t.string "name" + t.string "name_local" + t.string "short_description" + t.string "short_description_local" + t.string "question_1" + t.string "question_1_field" + t.string "question_1_illustation" + t.string "question_1_local" + t.string "question_2" + t.string "question_2_field" + t.string "question_2_illustation" + t.string "question_2_local" + t.string "question_3" + t.string "question_3_field" + t.string "question_3_illustation" + t.string "question_3_local" + t.string "question_4" + t.string "question_4_field" + t.string "question_4_illustation" + t.string "question_4_local" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "developmental_markers", ["name"], name: "index_developmental_markers_on_name", using: :btree + create_table "districts", force: :cascade do |t| t.string "name" t.integer "province_id" @@ -1132,6 +1178,7 @@ add_index "enrollments", ["deleted_at"], name: "index_enrollments_on_deleted_at", using: :btree add_index "enrollments", ["program_stream_id"], name: "index_enrollments_on_program_stream_id", using: :btree + add_index "enrollments", ["programmable_id", "programmable_type"], name: "index_enrollments_on_programmable_id_and_programmable_type", using: :btree add_index "enrollments", ["programmable_id"], name: "index_enrollments_on_programmable_id", using: :btree create_table "enter_ngo_users", force: :cascade do |t| @@ -1152,6 +1199,7 @@ t.string "acceptable_type" end + add_index "enter_ngos", ["acceptable_id", "acceptable_type"], name: "index_enter_ngos_on_acceptable_id_and_acceptable_type", using: :btree add_index "enter_ngos", ["acceptable_id"], name: "index_enter_ngos_on_acceptable_id", using: :btree add_index "enter_ngos", ["client_id"], name: "index_enter_ngos_on_client_id", using: :btree add_index "enter_ngos", ["deleted_at"], name: "index_enter_ngos_on_deleted_at", using: :btree @@ -1172,6 +1220,7 @@ add_index "exit_ngos", ["client_id"], name: "index_exit_ngos_on_client_id", using: :btree add_index "exit_ngos", ["deleted_at"], name: "index_exit_ngos_on_deleted_at", using: :btree + add_index "exit_ngos", ["rejectable_id", "rejectable_type"], name: "index_exit_ngos_on_rejectable_id_and_rejectable_type", using: :btree add_index "exit_ngos", ["rejectable_id"], name: "index_exit_ngos_on_rejectable_id", using: :btree create_table "external_system_global_identities", force: :cascade do |t| @@ -1196,6 +1245,8 @@ t.datetime "updated_at", null: false end + add_index "external_systems", ["name"], name: "index_external_systems_on_name", using: :btree + create_table "families", force: :cascade do |t| t.string "code" t.string "name", default: "" @@ -1289,6 +1340,7 @@ t.datetime "updated_at" end + add_index "family_quantitative_cases", ["family_id", "quantitative_case_id"], name: "index_on_family_id_and_quantitative_case_id", using: :btree add_index "family_quantitative_cases", ["family_id"], name: "index_family_quantitative_cases_on_family_id", using: :btree add_index "family_quantitative_cases", ["quantitative_case_id"], name: "index_family_quantitative_cases_on_quantitative_case_id", using: :btree @@ -1349,7 +1401,9 @@ t.datetime "updated_at", null: false end + add_index "form_builder_attachments", ["form_buildable_id", "form_buildable_type"], name: "index_on_form_buildable_id_and_form_buildable_type", using: :btree add_index "form_builder_attachments", ["form_buildable_id"], name: "index_form_builder_attachments_on_form_buildable_id", using: :btree + add_index "form_builder_attachments", ["name"], name: "index_form_builder_attachments_on_name", using: :btree create_table "friendly_id_slugs", force: :cascade do |t| t.string "slug", null: false @@ -1424,6 +1478,7 @@ t.date "completion_date" end + add_index "government_form_children_plans", ["children_plan_id", "government_form_id"], name: "index_on_children_plan_id_and_government_form_id", using: :btree add_index "government_form_children_plans", ["children_plan_id"], name: "index_government_form_children_plans_on_children_plan_id", using: :btree add_index "government_form_children_plans", ["government_form_id"], name: "index_government_form_children_plans_on_government_form_id", using: :btree @@ -1439,6 +1494,7 @@ t.text "comment", default: "" end + add_index "government_form_family_plans", ["family_plan_id", "government_form_id"], name: "index_on_family_plan_id_and_government_form_id", using: :btree add_index "government_form_family_plans", ["family_plan_id"], name: "index_government_form_family_plans_on_family_plan_id", using: :btree add_index "government_form_family_plans", ["government_form_id"], name: "index_government_form_family_plans_on_government_form_id", using: :btree @@ -1793,6 +1849,9 @@ t.string "referral_source_category_name" end + add_index "organizations", ["full_name"], name: "index_organizations_on_full_name", using: :btree + add_index "organizations", ["short_name"], name: "index_organizations_on_short_name", using: :btree + create_table "partners", force: :cascade do |t| t.string "name", default: "" t.string "address", default: "" @@ -1887,6 +1946,7 @@ end add_index "program_streams", ["archived_at"], name: "index_program_streams_on_archived_at", using: :btree + add_index "program_streams", ["name"], name: "index_program_streams_on_name", using: :btree create_table "progress_note_types", force: :cascade do |t| t.string "note_type", default: "" @@ -2048,6 +2108,8 @@ end add_index "referral_sources", ["ancestry"], name: "index_referral_sources_on_ancestry", using: :btree + add_index "referral_sources", ["name"], name: "index_referral_sources_on_name", using: :btree + add_index "referral_sources", ["name_en"], name: "index_referral_sources_on_name_en", using: :btree create_table "referrals", force: :cascade do |t| t.string "slug", default: "" @@ -2091,9 +2153,29 @@ t.integer "service_id" end + add_index "referrals_services", ["referral_id", "service_id"], name: "index_referrals_services_on_referral_id_and_service_id", using: :btree add_index "referrals_services", ["referral_id"], name: "index_referrals_services_on_referral_id", using: :btree add_index "referrals_services", ["service_id"], name: "index_referrals_services_on_service_id", using: :btree + create_table "screening_assessments", force: :cascade do |t| + t.datetime "screening_assessment_date" + t.string "client_age" + t.string "visitor" + t.string "client_milestone_age" + t.string "attachments", default: [], array: true + t.text "note" + t.boolean "smile_back_during_interaction" + t.boolean "follow_object_passed_midline" + t.boolean "turn_head_to_sound" + t.boolean "head_up_45_degree" + t.integer "client_id" + t.string "screening_type", default: "multiple" + end + + add_index "screening_assessments", ["client_id"], name: "index_screening_assessments_on_client_id", using: :btree + add_index "screening_assessments", ["screening_assessment_date"], name: "index_screening_assessments_on_screening_assessment_date", using: :btree + add_index "screening_assessments", ["screening_type"], name: "index_screening_assessments_on_screening_type", using: :btree + create_table "service_deliveries", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false @@ -2190,6 +2272,8 @@ t.string "case_note_edit_frequency", default: "week" t.boolean "disabled_add_service_received", default: false t.boolean "test_client", default: false + t.boolean "cbdmat_one_off", default: false + t.boolean "cbdmat_ongoing", default: false end add_index "settings", ["commune_id"], name: "index_settings_on_commune_id", using: :btree @@ -2678,7 +2762,7 @@ add_foreign_key "action_results", "government_forms" add_foreign_key "advanced_searches", "users" add_foreign_key "assessment_domains", "care_plans" - add_foreign_key "assessments", "clients" + add_foreign_key "assessments", "clients", on_delete: :nullify add_foreign_key "attachments", "able_screening_questions" add_foreign_key "attachments", "progress_notes" add_foreign_key "calendars", "users" @@ -2688,7 +2772,7 @@ add_foreign_key "call_protection_concerns", "protection_concerns" add_foreign_key "calls", "referees" add_foreign_key "care_plans", "assessments" - add_foreign_key "care_plans", "clients" + add_foreign_key "care_plans", "clients", on_delete: :nullify add_foreign_key "carers", "communes" add_foreign_key "carers", "districts" add_foreign_key "carers", "provinces" @@ -2704,8 +2788,6 @@ add_foreign_key "case_contracts", "cases" add_foreign_key "case_notes", "clients" add_foreign_key "case_notes", "custom_assessment_settings" - add_foreign_key "case_worker_clients", "clients" - add_foreign_key "case_worker_clients", "users" add_foreign_key "case_worker_communities", "communities" add_foreign_key "case_worker_communities", "users" add_foreign_key "case_worker_families", "families" @@ -2717,8 +2799,6 @@ add_foreign_key "client_client_types", "client_types" add_foreign_key "client_client_types", "clients" add_foreign_key "client_enrollment_trackings", "client_enrollments" - add_foreign_key "client_enrollments", "clients" - add_foreign_key "client_enrollments", "program_streams" add_foreign_key "client_interviewees", "clients" add_foreign_key "client_interviewees", "interviewees" add_foreign_key "client_needs", "clients" @@ -2763,8 +2843,6 @@ add_foreign_key "enrollments", "program_streams" add_foreign_key "enter_ngo_users", "enter_ngos" add_foreign_key "enter_ngo_users", "users" - add_foreign_key "enter_ngos", "clients" - add_foreign_key "exit_ngos", "clients" add_foreign_key "external_system_global_identities", "external_systems" add_foreign_key "external_system_global_identities", "global_identities", column: "global_id", primary_key: "ulid" add_foreign_key "families", "communes" @@ -2782,7 +2860,7 @@ add_foreign_key "goals", "assessment_domains" add_foreign_key "goals", "assessments" add_foreign_key "goals", "care_plans" - add_foreign_key "goals", "clients" + add_foreign_key "goals", "clients", on_delete: :nullify add_foreign_key "goals", "domains" add_foreign_key "government_form_children_plans", "children_plans" add_foreign_key "government_form_children_plans", "government_forms" @@ -2836,7 +2914,7 @@ add_foreign_key "settings", "communes" add_foreign_key "settings", "districts" add_foreign_key "settings", "provinces" - add_foreign_key "sponsors", "clients" + add_foreign_key "sponsors", "clients", on_delete: :nullify add_foreign_key "sponsors", "donors" add_foreign_key "subdistricts", "districts" add_foreign_key "surveys", "clients" From 382dfae6dcf47ddb2c0107309064ab4d7c9fd116 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Thu, 31 Mar 2022 11:52:29 +0700 Subject: [PATCH 66/94] [IMP] Apply cache client grid refs OSC-13 --- app/grids/client_grid.rb | 67 ++++++++++++++++----------------- app/helpers/cache_helper.rb | 1 - app/models/client.rb | 50 ++++++++++++++++++++++++ app/models/organization.rb | 8 ++++ app/models/quantitative_type.rb | 7 +++- app/models/referral_source.rb | 14 +++++++ app/models/shared_client.rb | 24 ++++++++++++ config/locales/en.yml | 1 + 8 files changed, 136 insertions(+), 36 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 347a42efe3..da1be717a9 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -514,7 +514,7 @@ def client_hotline_fields dynamic do quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) unless current_user.nil? - quantitative_types = QuantitativeType.joins(:quantitative_cases).distinct + quantitative_types = QuantitativeType.cached_quantitative_cases quantitative_types.each do |quantitative_type| if current_user.nil? || quantitative_type_readable_ids.include?(quantitative_type.id) column(quantitative_type.name.to_sym, class: 'quantitative-type', header: -> { quantitative_type.name }, html: true) do |object| @@ -653,7 +653,7 @@ def call_fields column(:referred_to, order: false, header: -> { I18n.t('datagrid.columns.clients.referred_to') }) do |object| short_names = object.referrals.pluck(:referred_to) - org_names = Organization.where("organizations.short_name IN (?)", short_names).pluck(:full_name) + org_names = Organization.cached_organization_short_names(short_names) if short_names.include?('external referral') org_names << "I don't see the NGO I'm looking for" elsif short_names.include?("MoSVY External System") @@ -664,7 +664,7 @@ def call_fields column(:referred_from, order: false, header: -> { I18n.t('datagrid.columns.clients.referred_from') }) do |object| short_names = object.referrals.pluck(:referred_from) - org_names = Organization.where("organizations.short_name IN (?)", short_names).pluck(:full_name) + org_names = Organization.cached_organization_short_names(short_names) org_names << "MoSVY External System" if short_names.include?("MoSVY External System") org_names.join(', ') @@ -685,7 +685,7 @@ def call_fields column(:date_of_birth, html: true, header: -> { I18n.t('datagrid.columns.clients.date_of_birth') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - date_of_birth = SharedClient.find_by(slug: object.slug)&.date_of_birth + date_of_birth = SharedClient.cached_shared_client_date_of_birth(object.slug) Organization.switch_to current_org.short_name date_of_birth.present? ? date_of_birth.strftime("%d %B %Y") : '' end @@ -693,7 +693,7 @@ def call_fields column(:date_of_birth, html: false, header: -> { I18n.t('datagrid.columns.clients.date_of_birth') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - date_of_birth = SharedClient.find_by(slug: object.slug).date_of_birth + date_of_birth = SharedClient.cached_shared_client_date_of_birth(object.slug) Organization.switch_to current_org.short_name date_of_birth.present? ? date_of_birth : '' end @@ -709,8 +709,7 @@ def call_fields end column(:created_by, header: -> { I18n.t('datagrid.columns.clients.created_by') }) do |object| - user_id = PaperTrail::Version.find_by(event: 'create', item_type: 'Client', item_id: object.id).try(:whodunnit) - User.find_by(id: user_id || object.user_id).try(:name) || '' + Client.cached_client_created_by(object) end dynamic do @@ -723,42 +722,42 @@ def call_fields column(:street_number, header: -> { I18n.t('datagrid.columns.clients.street_number') }) - column(:village, html: true, order:proc { |object| object.joins(:village).order('villages.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.village') } ) do |object| + column(:village, html: true, order:proc { |object| Client.cached_client_village_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.village') } ) do |object| object.village.try(:code_format) end - column(:commune, html: true, order: proc { |object| object.joins(:commune).order('communes.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.commune') } ) do |object| + column(:commune, html: true, order: proc { |object| Client.cached_client_commune_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.commune') } ) do |object| object.commune.try(:name) end - column(:district, html: true, order: proc { |object| object.joins(:district).order('districts.name')}, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| + column(:district, html: true, order: proc { |object| Client.cached_client_district_name(object) }, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| object.district_name end - column(:province_id, html: true, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| + column(:province_id, html: true, order: proc { |object| Client.cached_client_province_name(object) }, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| object.province_name end column(:birth_province_id, html: true, header: -> { I18n.t('datagrid.columns.clients.birth_province') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - birth_province = SharedClient.find_by(slug: object.slug).birth_province_name + birth_province = SharedClient.cached_shared_client_birth_province_name(object.slug) Organization.switch_to current_org.short_name birth_province end if I18n.locale == :km - column(:village, html: false, order: proc { |object| object.joins(:village).order('villages.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.village_kh') } ) do |object| + column(:village, html: false, order: proc { |object| Client.cached_client_village_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.village_kh') } ) do |object| object.village.try(:name_kh) end - column(:commune, html: false, order: proc { |object| object.joins(:commune).order('communes.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.commune_kh') } ) do |object| + column(:commune, html: false, order: proc { |object| Client.cached_client_commune_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.commune_kh') } ) do |object| object.commune.try(:name_kh) end - column(:district, html: false, order: proc { |object| object.joins(:district).order('districts.name')}, header: -> { I18n.t('datagrid.columns.clients.district_kh') }) do |object| + column(:district, html: false, order: proc { |object| Client.cached_client_district_name(object) }, header: -> { I18n.t('datagrid.columns.clients.district_kh') }) do |object| object.district.try(:name_kh) end - column(:province_id, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_kh') }) do |object| + column(:province_id, html: false, order: proc { |object| Client.cached_client_province_name(object) }, header: -> { I18n.t('datagrid.columns.clients.current_province_kh') }) do |object| identify_province_khmer = object.province&.name&.count "/" if identify_province_khmer == 1 province = object.province.name.split('/').first @@ -771,7 +770,7 @@ def call_fields column(:birth_province_id, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_kh') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - birth_province = SharedClient.find_by(slug: object.slug).birth_province_name + birth_province = SharedClient.cached_shared_client_birth_province_name(object.slug) identity_birth_province = birth_province&.count "/" if identity_birth_province == 1 birth_province = birth_province.split('/').first @@ -783,19 +782,19 @@ def call_fields end else - column(:village, html: false, order: proc { |object| object.joins(:village).order('villages.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.village_en') } ) do |object| + column(:village, html: false, order: proc { |object| Client.cached_client_village_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.village_en') } ) do |object| object.village.try(:name_en) end - column(:commune, html: false, order: proc { |object| object.joins(:village).order('communes.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.commune_en') } ) do |object| + column(:commune, html: false, order: proc { |object| Client.cached_client_commune_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.commune_en') } ) do |object| object.commune.try(:name_en) end - column(:district, html: false, order: proc { |object| object.joins(:district).order('districts.name')}, header: -> { I18n.t('datagrid.columns.clients.district_en') }) do |object| + column(:district, html: false, order: proc { |object| Client.cached_client_district_name(object) }, header: -> { I18n.t('datagrid.columns.clients.district_en') }) do |object| object.district.present? ? object.district.name.split(' / ').last : nil end - column(:province_id, html: false, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province_en') }) do |object| + column(:province_id, html: false, order: proc { |object| Client.cached_client_province_name(object) }, header: -> { I18n.t('datagrid.columns.clients.current_province_en') }) do |object| identify_province = object.province&.name&.count "/" if identify_province == 1 province = object.province.name.split('/').last @@ -808,7 +807,7 @@ def call_fields column(:birth_province_id, html: false, header: -> { I18n.t('datagrid.columns.clients.birth_province_en') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - birth_province = SharedClient.find_by(slug: object.slug).birth_province_name + birth_province = SharedClient.cached_shared_client_birth_province_name(object.slug) identity_birth_province = birth_province&.count "/" if identity_birth_province == 1 birth_province = birth_province.split('/').last @@ -826,26 +825,26 @@ def call_fields column(:street_number, header: -> { I18n.t('datagrid.columns.clients.street_number') }) - column(:village, order: proc { |object| object.joins(:village).order('villages.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.village') } ) do |object| + column(:village, order: proc { |object| Client.cached_client_village_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.village') } ) do |object| object.village.try(:code_format) end - column(:commune, order: proc { |object| object.joins(:commune).order('communes.name_kh')}, header: -> { I18n.t('datagrid.columns.clients.commune') } ) do |object| + column(:commune, order: proc { |object| Client.cached_client_commune_name_kh(object) }, header: -> { I18n.t('datagrid.columns.clients.commune') } ) do |object| object.commune.try(:name) end - column(:district, order: proc { |object| object.joins(:district).order('districts.name')}, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| + column(:district, order: proc { |object| Client.cached_client_district_name(object) }, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| object.district_name end - column(:province, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| + column(:province, order: proc { |object| Client.cached_client_province_name(object) }, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| object.province_name end column(:birth_province, header: -> { I18n.t('datagrid.columns.clients.birth_province') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - birth_province = SharedClient.find_by(slug: object.slug).birth_province_name + birth_province = SharedClient.cached_shared_client_birth_province_name(object.slug) Organization.switch_to current_org.short_name birth_province end @@ -856,7 +855,7 @@ def call_fields column(:postal_code, header: -> { I18n.t('datagrid.columns.clients.postal_code') }) - column(:district, order: proc { |object| object.joins(:district).order('districts.name')}, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| + column(:district, order: proc { |object| Client.cached_client_district_name(object) }, header: -> { I18n.t('datagrid.columns.clients.district') }) do |object| object.district_name end @@ -864,14 +863,14 @@ def call_fields object.subdistrict_name end - column(:province, order: proc { |object| object.joins(:province).order('provinces.name')}, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| + column(:province, order: proc { |object| Client.cached_client_province_name(object) }, header: -> { I18n.t('datagrid.columns.clients.current_province') }) do |object| object.province_name end column(:birth_province, header: -> { I18n.t('datagrid.columns.clients.birth_province') }) do |object| current_org = Organization.current Organization.switch_to 'shared' - birth_province = SharedClient.find_by(slug: object.slug).birth_province_name + birth_province = SharedClient.cached_shared_client_birth_province_name(object.slug) Organization.switch_to current_org.short_name birth_province end @@ -916,14 +915,14 @@ def call_fields column(:relevant_referral_information, header: -> { I18n.t('datagrid.columns.clients.relevant_referral_information') }) - column(:referral_source_id, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source') }) do |object| + column(:referral_source_id, order: proc { |object| Client.cached_client_referral_source_name(object) }, header: -> { I18n.t('datagrid.columns.clients.referral_source') }) do |object| object.referral_source.try(:name) end - column(:referral_source_category_id, order: proc { |object| object.joins(:referral_source).order('referral_sources.name')}, header: -> { I18n.t('datagrid.columns.clients.referral_source_category') }) do |object| + column(:referral_source_category_id, order: proc { |object| Client.cached_client_referral_source_name(object) }, header: -> { I18n.t('datagrid.columns.clients.referral_source_category') }) do |object| if I18n.locale == :km - ReferralSource.find_by(id: object.referral_source_category_id).try(:name) + ReferralSource.cache_referral_source_try_name(object.referral_source_category_id) else - ReferralSource.find_by(id: object.referral_source_category_id).try(:name_en) + ReferralSource.cache_referral_source_try_name_en(object.referral_source_category_id) end end diff --git a/app/helpers/cache_helper.rb b/app/helpers/cache_helper.rb index 11e6f31352..3930dd8d3a 100644 --- a/app/helpers/cache_helper.rb +++ b/app/helpers/cache_helper.rb @@ -11,7 +11,6 @@ def setting_cache_key [Apartment::Tenant.current, 'current_setting'] end - def custom_fields_cache [Apartment::Tenant.current, 'custom_fields'] end diff --git a/app/models/client.rb b/app/models/client.rb index 9fc9f50be1..3e3b53ca4c 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -718,6 +718,44 @@ def indirect_beneficiaries result end + def self.cached_client_created_by(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_created_by', object.id]) do + user_id = PaperTrail::Version.find_by(event: 'create', item_type: 'Client', item_id: object.id).try(:whodunnit) + User.find_by(id: user_id || object.user_id).try(:name) || '' + end + end + + def self.cached_client_province_name(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_province_name', object.id]) do + object.joins(:province).order('provinces.name') + end + end + + + def self.cached_client_district_name(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_district_name', object.id]) do + object.joins(:district).order('districts.name') + end + end + + def self.cached_client_commune_name_kh(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_commune_name_kh', object.id]) do + object.joins(:commune).order('communes.name_kh') + end + end + + def self.cached_client_village_name_kh(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_village_name_kh', object.id]) do + object.joins(:village).order('villages.name_kh') + end + end + + def self.cached_client_referral_source_name(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_referral_source_name', object.id]) do + object.joins(:referral_source).order('referral_sources.name') + end + end + private def update_related_family_member @@ -843,6 +881,18 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Subdistrict', 'dropdown_list_option']) if subdistrict_id_changed? Rails.cache.delete([Apartment::Tenant.current, 'Township', 'dropdown_list_option']) if township_id_changed? Rails.cache.delete([Apartment::Tenant.current, 'State', 'dropdown_list_option']) if state_id_changed? + cached_client_created_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_created_by/].blank? } + cached_client_created_by_keys.each { |key| Rails.cache.delete(key) } + cached_client_province_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_province_name/].blank? } + cached_client_province_name_keys.each { |key| Rails.cache.delete(key) } + cached_client_district_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_district_name/].blank? } + cached_client_district_name_keys.each { |key| Rails.cache.delete(key) } + cached_client_commune_name_kh_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_commune_name_kh/].blank? } + cached_client_commune_name_kh_keys.each { |key| Rails.cache.delete(key) } + cached_client_village_name_kh_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_village_name_kh/].blank? } + cached_client_village_name_kh_keys.each { |key| Rails.cache.delete(key) } + cached_client_referral_source_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_referral_source_name/].blank? } + cached_client_referral_source_name_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/organization.rb b/app/models/organization.rb index fc0ebfe57a..7c80891407 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -172,6 +172,12 @@ def self.cache_visible_ngos end end + def self.cached_organization_short_names(short_names) + Rails.cache.fetch([Apartment::Tenant.current, 'Organization', 'cached_organization_short_names', *short_names.sort]) { + where("organizations.short_name IN (?)", short_names).pluck(:full_name) + } + end + private def upsert_referral_source_category @@ -200,5 +206,7 @@ def delete_referral_source_category def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'cache_mapping_ngo_names']) Rails.cache.delete([Apartment::Tenant.current, 'Organization', 'visible']) + cached_organization_short_names_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_organization_short_names/].blank? } + cached_organization_short_names_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/quantitative_type.rb b/app/models/quantitative_type.rb index 9ec45c5ee0..4750de4d99 100644 --- a/app/models/quantitative_type.rb +++ b/app/models/quantitative_type.rb @@ -33,7 +33,11 @@ def self.cach_by_quantitative_type_ids(quantitative_type_ids) end end - + def self.cached_quantitative_cases + Rails.cache.fetch([Apartment::Tenant.current, 'QuantitativeType', 'cached_quantitative_cases']) { + joins(:quantitative_cases).distinct.to_a + } + end private @@ -53,5 +57,6 @@ def flush_cach Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "client"] ) Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "community"] ) Rails.cache.delete([Apartment::Tenant.current, "QuantitativeType", "family"] ) + Rails.cache.delete([Apartment::Tenant.current, 'QuantitativeType', 'cached_quantitative_cases'] ) end end diff --git a/app/models/referral_source.rb b/app/models/referral_source.rb index aa573825ee..a32b460611 100644 --- a/app/models/referral_source.rb +++ b/app/models/referral_source.rb @@ -30,6 +30,18 @@ def self.cache_referral_source_category_options end end + def self.cache_referral_source_try_name(referral_source_category_id) + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name']) { + find_by(id: referral_source_category_id).try(:name) + } + end + + def self.cache_referral_source_try_name_en(referral_source_category_id) + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name_en']) { + find_by(id: referral_source_category_id).try(:name_en) + } + end + private def update_client_referral_source @@ -52,5 +64,7 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'referral_source_options']) Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_category_options']) Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_local_referral_source_category_options']) + Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name']) + Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name_en']) end end diff --git a/app/models/shared_client.rb b/app/models/shared_client.rb index fe1bd5a69b..1473d60ca1 100644 --- a/app/models/shared_client.rb +++ b/app/models/shared_client.rb @@ -6,4 +6,28 @@ class SharedClient < ActiveRecord::Base delegate :name, to: :birth_province, prefix: true, allow_nil: true validates :slug, presence: true, uniqueness: { case_sensitive: false } + + after_commit :flush_cache + + def self.cached_shared_client_date_of_birth(slug) + Rails.cache.fetch([Apartment::Tenant.current, 'SharedClient', 'cached_shared_client_date_of_birth', slug]) { + find_by(slug: slug)&.date_of_birth + } + end + + def self.cached_shared_client_birth_province_name(slug) + Rails.cache.fetch([Apartment::Tenant.current, 'SharedClient', 'cached_shared_client_birth_province_name', slug]) { + find_by(slug: slug).birth_province_name + } + end + + private + + def flush_cach + Rails.cache.delete([Apartment::Tenant.current, 'SharedClient', 'cached_shared_client_date_of_birth'] ) + cached_shared_client_date_of_birth_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_shared_client_date_of_birth/].blank? } + cached_shared_client_date_of_birth_keys.each { |key| Rails.cache.delete(key) } + cached_shared_client_birth_province_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_shared_client_birth_province_name/].blank? } + cached_shared_client_birth_province_name_keys.each { |key| Rails.cache.delete(key) } + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 4426ac94b4..149fbcd6c5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -536,6 +536,7 @@ en: referred_from: Referred From referred_in: Referred In referred_to: Referred To + referred_out: Referred Out referee: Referee referee_relationship: Referee Relationship to Client referee_name: Referee Name From 74d81695d5297bf33aaab26020e0f6447dfc3e9f Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 1 Apr 2022 09:45:27 +0700 Subject: [PATCH 67/94] [IMP] Adjust cache param referred refs OSC-13 --- app/grids/client_grid.rb | 4 ++-- app/models/referral_source.rb | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index da1be717a9..2c5c9819f5 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -920,9 +920,9 @@ def call_fields end column(:referral_source_category_id, order: proc { |object| Client.cached_client_referral_source_name(object) }, header: -> { I18n.t('datagrid.columns.clients.referral_source_category') }) do |object| if I18n.locale == :km - ReferralSource.cache_referral_source_try_name(object.referral_source_category_id) + ReferralSource.cached_referral_source_try_name(object.referral_source_category_id) else - ReferralSource.cache_referral_source_try_name_en(object.referral_source_category_id) + ReferralSource.cached_referral_source_try_name_en(object.referral_source_category_id) end end diff --git a/app/models/referral_source.rb b/app/models/referral_source.rb index a32b460611..e037d5f0da 100644 --- a/app/models/referral_source.rb +++ b/app/models/referral_source.rb @@ -30,14 +30,14 @@ def self.cache_referral_source_category_options end end - def self.cache_referral_source_try_name(referral_source_category_id) - Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name']) { + def self.cached_referral_source_try_name(referral_source_category_id) + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cached_referral_source_try_name', referral_source_category_id]) { find_by(id: referral_source_category_id).try(:name) } end - def self.cache_referral_source_try_name_en(referral_source_category_id) - Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name_en']) { + def self.cached_referral_source_try_name_en(referral_source_category_id) + Rails.cache.fetch([Apartment::Tenant.current, 'ReferralSource', 'cached_referral_source_try_name_en', referral_source_category_id]) { find_by(id: referral_source_category_id).try(:name_en) } end @@ -64,7 +64,9 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'referral_source_options']) Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_category_options']) Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_local_referral_source_category_options']) - Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name']) - Rails.cache.delete([Apartment::Tenant.current, 'ReferralSource', 'cache_referral_source_try_name_en']) + cached_referral_source_try_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_referral_source_try_name/].blank? } + cached_referral_source_try_name_keys.each { |key| Rails.cache.delete(key) } + cached_referral_source_try_name_en_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_referral_source_try_name_en/].blank? } + cached_referral_source_try_name_en_keys.each { |key| Rails.cache.delete(key) } end end From 71a1a4f85b4f8483beb977d2355741f66e8c42b6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 1 Apr 2022 12:29:39 +0700 Subject: [PATCH 68/94] Update Code using Cache - client_helper.rb - clients_fields.rb - families_helper.rb - partners_helper.rb - app/controllers/api/application_controller.rb --- app/classes/advanced_searches/client_fields.rb | 2 +- app/controllers/api/application_controller.rb | 2 +- app/helpers/clients_helper.rb | 6 +++--- app/helpers/families_helper.rb | 12 ++++++------ app/helpers/partners_helper.rb | 4 ++-- app/models/field_setting.rb | 12 ++++++------ 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/classes/advanced_searches/client_fields.rb b/app/classes/advanced_searches/client_fields.rb index b153f0129e..677f4467f9 100644 --- a/app/classes/advanced_searches/client_fields.rb +++ b/app/classes/advanced_searches/client_fields.rb @@ -123,7 +123,7 @@ def carer_dropdown_list end def field_settings - @field_settings = FieldSetting.all + @field_settings = FieldSetting.cache_all end def gender_list diff --git a/app/controllers/api/application_controller.rb b/app/controllers/api/application_controller.rb index c14efe01ed..4ac4f3f1fc 100644 --- a/app/controllers/api/application_controller.rb +++ b/app/controllers/api/application_controller.rb @@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base private def field_settings - @field_settings ||= FieldSetting.all + @field_settings ||= FieldSetting.cache_all end def pundit_user diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 6d164ffa50..07848922fc 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -309,8 +309,8 @@ def lable_translation_uderscore carer_phone_: I18n.t('activerecord.attributes.carer.phone'), carer_email_: I18n.t('activerecord.attributes.carer.email'), carer_relationship_to_client_: I18n.t('datagrid.columns.clients.carer_relationship_to_client'), - province_id_: FieldSetting.find_by(name: 'current_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province'), - birth_province_id_: FieldSetting.find_by(name: 'birth_province', klass_name: 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province'), + province_id_: FieldSetting.cache_by_name_klass_name_instance('current_province', 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province'), + birth_province_id_: FieldSetting.cache_by_name_klass_name_instance('birth_province', 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province'), **overdue_translations.map{ |k, v| ["#{k}_".to_sym, v] }.to_h } end @@ -336,7 +336,7 @@ def overdue_translations end def local_name_label(name_type = :local_given_name) - custom_field = FieldSetting.find_by(name: name_type) + custom_field = FieldSetting.cache_by_name(name_type.to_s) label = I18n.t("datagrid.columns.clients.#{name_type}") label = "#{label} #{country_scope_label_translation}" if custom_field.blank? || custom_field.label.blank? label diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb index 1ab6586f37..49ddbb99ce 100644 --- a/app/helpers/families_helper.rb +++ b/app/helpers/families_helper.rb @@ -136,13 +136,13 @@ def family_address_translation field_keys = %W(province province_id district district_id commune commune_id village) translations = {} field_keys.each do |key_translation| - translations[key_translation.to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") - translations["#{key_translation}_".to_sym] = FieldSetting.find_by(name: key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") + translations[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") + translations["#{key_translation}_".to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") end - translations['province_id'.to_sym] = FieldSetting.find_by(name: 'province_id').try(:label) || I18n.t('datagrid.columns.families.province') - translations['district_id'.to_sym] = FieldSetting.find_by(name: 'district_id').try(:label) || I18n.t('datagrid.columns.families.district') - translations['commune_id'.to_sym] = FieldSetting.find_by(name: 'commune_id').try(:label) || I18n.t('datagrid.columns.families.commune') - translations['village_id'.to_sym] = FieldSetting.find_by(name: 'village_id').try(:label) || I18n.t('datagrid.columns.families.village_id') + translations['province_id'.to_sym] = FieldSetting.cache_by_name('province_id').try(:label) || I18n.t('datagrid.columns.families.province') + translations['district_id'.to_sym] = FieldSetting.cache_by_name('district_id').try(:label) || I18n.t('datagrid.columns.families.district') + translations['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id').try(:label) || I18n.t('datagrid.columns.families.commune') + translations['village_id'.to_sym] = FieldSetting.cache_by_name('village_id').try(:label) || I18n.t('datagrid.columns.families.village_id') translations end diff --git a/app/helpers/partners_helper.rb b/app/helpers/partners_helper.rb index eaa2521263..e42db1fa71 100644 --- a/app/helpers/partners_helper.rb +++ b/app/helpers/partners_helper.rb @@ -41,8 +41,8 @@ def default_columns_partners_visibility(column) def partner_address_translation translations = {} - translations['province_id'.to_sym] = FieldSetting.find_by(name: 'province_id', klass_name: 'partner').try(:label) || t('datagrid.columns.partners.province') - translations['province_id_'.to_sym] = FieldSetting.find_by(name: 'province_id', klass_name: 'partner').try(:label) || t('datagrid.columns.partners.province') + translations['province_id'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner').try(:label) || t('datagrid.columns.partners.province') + translations['province_id_'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner').try(:label) || t('datagrid.columns.partners.province') translations end end diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index ad52aaae43..1efa12ba55 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -43,15 +43,15 @@ def cache_object Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id]) { self } end - def self.cache_by_name(name) - Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', name]) do - find_by(name: name) + def self.cache_by_name(field_name) + Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', field_name]) do + find_by(name: field_name) end end - def self.cache_by_name_klass_name_instance(name, klass_name = 'client') - Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting', name, klass_name]) do - find_by(name: name, klass_name: klass_name) + def self.cache_by_name_klass_name_instance(field_name, klass_name = 'client') + Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting', field_name, klass_name]) do + find_by(name: field_name, klass_name: klass_name) end end From 1dc35a52d8a768ed9dead574f8e4b5d13646fe08 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 1 Apr 2022 13:49:14 +0700 Subject: [PATCH 69/94] [IMP] Add cache for domain refs OSC-13 --- app/grids/client_grid.rb | 21 ++++++++--------- app/models/client.rb | 51 ++++++++++++++++++++++++++++++++++++++++ app/models/domain.rb | 8 +++++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 2c5c9819f5..7f659c2e79 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -268,12 +268,11 @@ def quantitative_cases end def self.client_by_domain(operation, value, domain_id, scope) - ids = Assessment.joins(:assessment_domains).where("score#{operation} ? AND domain_id= ?", value, domain_id).ids - scope.joins(:assessments).where(assessments: { id: ids}) + Client.cached_client_assessment_domains(value, domain_id, scope) end def self.get_domain(name) - domain = Domain.find_by(name: name) + domain = Domain.cache_find_by_name(name) domain.present? ? Array.new([[domain.name, domain.id]]) : [] end @@ -1004,10 +1003,10 @@ def call_fields assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true)".squish if assessment_number.present? && assessment_completed_sql.present? - assessments = object.assessments.defaults.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + Client.cached_client_assessment_number_completed_date(object, sql, assessment_number) elsif assessment_completed_sql.present? sql = assessment_completed_sql[/assessments\.completed_date.*/] - assessments = object.assessments.defaults.completed.where(sql).order('completed_date') + Client.cached_client_sql_assessment_completed_date(object, sql) else rule = basic_rules['rules'].select {|h| h['id'] == 'date_of_assessments' }.first if rule.present? @@ -1016,10 +1015,10 @@ def call_fields else assessments = object.assessments.defaults end - assessments = object.assessments.defaults.completed.where(sql).order('completed_date') + Client.cached_client_sql_assessment_completed_date(object, sql) end else - assessments = object.assessments.defaults.order('completed_date') + Client.cached_client_assessment_order_completed_date(object) end render partial: 'clients/completed_assessments', locals: { object: assessments } end @@ -1040,10 +1039,10 @@ def call_fields assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true)".squish if assessment_number.present? && assessment_completed_sql.present? - assessments = object.assessments.customs.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + Client.cached_client_assessment_custom_number_completed_date(object, sql, assessment_number) elsif assessment_completed_sql.present? sql = assessment_completed_sql[/assessments\.completed_date.*/] - assessments = object.assessments.customs.completed.where(sql).order('completed_date') + Client.cached_client_sql_assessment_custom_completed_date(object, sql) else rule = basic_rules['rules'].select {|h| h['id'] == 'date_of_assessments' }.first if rule.present? @@ -1052,10 +1051,10 @@ def call_fields else assessments = object.assessments.customs end - assessments = object.assessments.customs.completed.where(sql).order('completed_date') + Client.cached_client_sql_assessment_custom_completed_date(object, sql) end else - assessments = object.assessments.customs.order('completed_date') + Client.cached_client_assessment_custom_order_completed_date(object) end render partial: 'clients/completed_assessments', locals: { object: assessments } end diff --git a/app/models/client.rb b/app/models/client.rb index 3e3b53ca4c..1a50bc1746 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -756,6 +756,49 @@ def self.cached_client_referral_source_name(object) end end + def self.cached_client_assessment_number_completed_date(object, sql, assessment_number) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_number_completed_date', object.id]) do + assessments = object.assessments.defaults.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + end + end + + def self.cached_client_sql_assessment_completed_date(object, sql) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_sql_assessment_completed_date', object.id]) do + assessments = object.assessments.defaults.completed.where(sql).order('completed_date') + end + end + + def self.cached_client_assessment_order_completed_date(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_order_completed_date', object.id]) do + assessments = object.assessments.defaults.order('completed_date') + end + end + + def self.cached_client_assessment_custom_number_completed_date(object, sql, assessment_number) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_custom_number_completed_date', object.id]) do + assessments = object.assessments.customs.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + end + end + + def self.cached_client_sql_assessment_custom_completed_date(object, sql) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_sql_assessment_custom_completed_date', object.id]) do + assessments = object.assessments.customs.completed.where(sql).order('completed_date') + end + end + + def self.cached_client_assessment_custom_order_completed_date(object) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_custom_order_completed_date', object.id]) do + assessments = object.assessments.customs.order('completed_date') + end + end + + def self.cached_client_assessment_domains(value, domain_id, scope) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_domains', domain_id]) do + ids = Assessment.joins(:assessment_domains).where("score#{operation} ? AND domain_id= ?", value, domain_id).ids + scope.joins(:assessments).where(assessments: { id: ids}) + end + end + private def update_related_family_member @@ -893,6 +936,14 @@ def flush_cache cached_client_village_name_kh_keys.each { |key| Rails.cache.delete(key) } cached_client_referral_source_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_referral_source_name/].blank? } cached_client_referral_source_name_keys.each { |key| Rails.cache.delete(key) } + cached_client_assessment_number_completed_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_assessment_number_completed_date/].blank? } + cached_client_assessment_number_completed_date_keys.each { |key| Rails.cache.delete(key) } + cached_client_sql_assessment_completed_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_sql_assessment_completed_date/].blank? } + cached_client_sql_assessment_completed_date_keys.each { |key| Rails.cache.delete(key) } + cached_client_assessment_order_completed_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_assessment_order_completed_date/].blank? } + cached_client_assessment_order_completed_date_keys.each { |key| Rails.cache.delete(key) } + cached_client_assessment_domains_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_assessment_domains/].blank? } + cached_client_assessment_domains_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/domain.rb b/app/models/domain.rb index 8b6fed6897..0af849cdfa 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -73,10 +73,18 @@ def self.cache_order_by_identity end end + def self.cache_find_by_name(name) + Rails.cache.fetch([Apartment::Tenant.current, 'Domain', 'cache_find_by_name', *name]) { + find_by(name: name) + } + end + private def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Domain', domain_type, 'domain_options']) Rails.cache.delete([Apartment::Tenant.current, 'Domain', 'cache_order_by_identity']) + cache_find_by_name_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cache_find_by_name/].blank? } + cache_find_by_name_keys.each { |key| Rails.cache.delete(key) } end end From 4acc2d06cc151458a5e6d33fb59ef0ceb834cf90 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 1 Apr 2022 15:11:48 +0700 Subject: [PATCH 70/94] fixed method error in shared_client.rb --- app/models/shared_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/shared_client.rb b/app/models/shared_client.rb index 1473d60ca1..75401a5a28 100644 --- a/app/models/shared_client.rb +++ b/app/models/shared_client.rb @@ -23,7 +23,7 @@ def self.cached_shared_client_birth_province_name(slug) private - def flush_cach + def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'SharedClient', 'cached_shared_client_date_of_birth'] ) cached_shared_client_date_of_birth_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_shared_client_date_of_birth/].blank? } cached_shared_client_date_of_birth_keys.each { |key| Rails.cache.delete(key) } From 754a574a066f72f4c2e3ca36222ada421632ea73 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 1 Apr 2022 15:34:03 +0700 Subject: [PATCH 71/94] Refactored Code - cached_client_sql_assessment_custom_completed_date - cached_client_assessment_custom_number_completed_date - cached_client_sql_assessment_completed_date - cached_client_assessment_number_completed_date - cached_client_assessment_custom_order_completed_date --- app/grids/client_grid.rb | 16 ++++++++-------- app/models/client.rb | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 7f659c2e79..99fbcba96b 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -1003,10 +1003,10 @@ def call_fields assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true)".squish if assessment_number.present? && assessment_completed_sql.present? - Client.cached_client_assessment_number_completed_date(object, sql, assessment_number) + assessments = Client.cached_client_assessment_number_completed_date(object, sql, assessment_number) elsif assessment_completed_sql.present? sql = assessment_completed_sql[/assessments\.completed_date.*/] - Client.cached_client_sql_assessment_completed_date(object, sql) + assessments = Client.cached_client_sql_assessment_completed_date(object, sql) else rule = basic_rules['rules'].select {|h| h['id'] == 'date_of_assessments' }.first if rule.present? @@ -1015,10 +1015,10 @@ def call_fields else assessments = object.assessments.defaults end - Client.cached_client_sql_assessment_completed_date(object, sql) + assessments = Client.cached_client_sql_assessment_completed_date(object, sql) end else - Client.cached_client_assessment_order_completed_date(object) + assessments = Client.cached_client_assessment_order_completed_date(object) end render partial: 'clients/completed_assessments', locals: { object: assessments } end @@ -1039,10 +1039,10 @@ def call_fields assessment_completed_sql, assessment_number = assessment_filter_values(results) sql = "(assessments.completed = true)".squish if assessment_number.present? && assessment_completed_sql.present? - Client.cached_client_assessment_custom_number_completed_date(object, sql, assessment_number) + assessments = Client.cached_client_assessment_custom_number_completed_date(object, sql, assessment_number) elsif assessment_completed_sql.present? sql = assessment_completed_sql[/assessments\.completed_date.*/] - Client.cached_client_sql_assessment_custom_completed_date(object, sql) + assessments = Client.cached_client_sql_assessment_custom_completed_date(object, sql) else rule = basic_rules['rules'].select {|h| h['id'] == 'date_of_assessments' }.first if rule.present? @@ -1051,10 +1051,10 @@ def call_fields else assessments = object.assessments.customs end - Client.cached_client_sql_assessment_custom_completed_date(object, sql) + assessments = Client.cached_client_sql_assessment_custom_completed_date(object, sql) end else - Client.cached_client_assessment_custom_order_completed_date(object) + assessments = Client.cached_client_assessment_custom_order_completed_date(object) end render partial: 'clients/completed_assessments', locals: { object: assessments } end diff --git a/app/models/client.rb b/app/models/client.rb index 1a50bc1746..05523c0618 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -758,37 +758,37 @@ def self.cached_client_referral_source_name(object) def self.cached_client_assessment_number_completed_date(object, sql, assessment_number) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_number_completed_date', object.id]) do - assessments = object.assessments.defaults.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + object.assessments.defaults.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') end end def self.cached_client_sql_assessment_completed_date(object, sql) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_sql_assessment_completed_date', object.id]) do - assessments = object.assessments.defaults.completed.where(sql).order('completed_date') + object.assessments.defaults.completed.where(sql).order('completed_date') end end def self.cached_client_assessment_order_completed_date(object) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_order_completed_date', object.id]) do - assessments = object.assessments.defaults.order('completed_date') + object.assessments.defaults.order('completed_date') end end def self.cached_client_assessment_custom_number_completed_date(object, sql, assessment_number) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_custom_number_completed_date', object.id]) do - assessments = object.assessments.customs.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') + object.assessments.customs.where(sql).limit(1).offset(assessment_number - 1).order('completed_date') end end def self.cached_client_sql_assessment_custom_completed_date(object, sql) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_sql_assessment_custom_completed_date', object.id]) do - assessments = object.assessments.customs.completed.where(sql).order('completed_date') + object.assessments.customs.completed.where(sql).order('completed_date') end end def self.cached_client_assessment_custom_order_completed_date(object) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_assessment_custom_order_completed_date', object.id]) do - assessments = object.assessments.customs.order('completed_date') + object.assessments.customs.order('completed_date') end end From 833d90874d53f692dacf50fa60725fd98804063c Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 1 Apr 2022 17:02:40 +0700 Subject: [PATCH 72/94] [IMP] Add cache for tracking enrollment refs OSC-13 --- app/grids/client_grid.rb | 24 ++++----- app/models/client.rb | 65 ++++++++++++++++++++++++ app/models/client_enrollment_tracking.rb | 20 ++++++++ app/models/leave_program.rb | 20 ++++++++ 4 files changed, 117 insertions(+), 12 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 99fbcba96b..bb8863f1cb 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -1146,9 +1146,9 @@ def call_fields if fields.first == 'formbuilder' if data == 'recent' if fields.last == 'Has This Form' - properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields.second, entity_type: 'Client'}).count + Client.cached_client_custom_field_properties_count(object, fields.second) else - properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields.second, entity_type: 'Client'}).order(created_at: :desc).first.try(:properties) + Client.cached_client_custom_field_properties_order(object, fields.second) properties = properties[format_field_value] if properties.present? end else @@ -1156,14 +1156,14 @@ def call_fields properties = [custom_form_with_has_form(object, fields).count] else if $param_rules - custom_field_id = object.custom_fields.find_by(form_title: fields.second)&.id + Client.cached_client_custom_field_find_by(object, fields.second) basic_rules = $param_rules.present? && $param_rules[:basic_rules] ? $param_rules[:basic_rules] : $param_rules basic_rules = basic_rules.is_a?(Hash) ? basic_rules : JSON.parse(basic_rules).with_indifferent_access results = mapping_form_builder_param_value(basic_rules, 'formbuilder') query_string = get_query_string(results, 'formbuilder', 'custom_field_properties.properties') sql = query_string.reverse.reject(&:blank?).map{|sql| "(#{sql})" }.join(" AND ") - properties = object.custom_field_properties.where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) + Client.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) properties = properties.blank? ? custom_form_with_has_form(object, fields).properties_by(format_field_value) : properties else properties = form_builder_query(object.custom_field_properties, fields.second, column_builder[:id].gsub('&qoute;', '"'), 'custom_field_properties.properties').properties_by(format_field_value) @@ -1172,32 +1172,32 @@ def call_fields end elsif fields.first == 'enrollmentdate' if data == 'recent' - properties = date_format(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields.second }).order(enrollment_date: :desc).first.try(:enrollment_date)) + Client.cached_client_order_enrollment_date(object, fields.second) else - properties = date_filter(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields.second }), fields.join('__')).map{|date| date_format(date.enrollment_date) } + Client.cached_client_enrollment_date_join(object, fields.second) end elsif fields.first == 'enrollment' if data == 'recent' - properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields.second }).order(enrollment_date: :desc).first.try(:properties) + Client.cached_client_order_enrollment_date_properties(object, fields.second) properties = properties[format_field_value] if properties.present? else - properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields.second }).properties_by(format_field_value) + Client.cached_client_enrollment_properties_by(object, fields.second, format_field_value) end elsif fields.first == 'tracking' ids = object.client_enrollments.ids if data == 'recent' - properties = ClientEnrollmentTracking.joins(:tracking).where(trackings: { name: fields.third }, client_enrollment_trackings: { client_enrollment_id: ids }).order(created_at: :desc).first.try(:properties) + properties = ClientEnrollmentTracking.cached_tracking_order_created_at(fields.third, ids) properties = properties[format_field_value] if properties.present? else - client_enrollment_trackings = ClientEnrollmentTracking.joins(:tracking).where(trackings: { name: fields.third }, client_enrollment_trackings: { client_enrollment_id: ids }) + client_enrollment_trackings = ClientEnrollmentTracking.cached_client_enrollment_tracking(fields.third, ids) properties = form_builder_query(client_enrollment_trackings, fields.first, column_builder[:id].gsub('&qoute;', '"')).properties_by(format_field_value, client_enrollment_trackings) end elsif fields.first == 'exitprogramdate' ids = object.client_enrollments.inactive.ids if data == 'recent' - properties = date_format(LeaveProgram.joins(:program_stream).where(program_streams: { name: fields.second }, leave_programs: { client_enrollment_id: ids }).order(exit_date: :desc).first.try(:exit_date)) + properties = date_format(LeaveProgram.cached_program_exit_date(fields.second, ids)) else - properties = date_filter(LeaveProgram.joins(:program_stream).where(program_streams: { name: fields.second }, leave_programs: { client_enrollment_id: ids }), fields.join('__')).map{|date| date_format(date.exit_date) } + properties = date_filter(LeaveProgram.cached_program_stream_leave(fields.second, ids), fields.join('__')).map{|date| date_format(date.exit_date) } end elsif fields.first == 'exitprogram' ids = object.client_enrollments.inactive.ids diff --git a/app/models/client.rb b/app/models/client.rb index 05523c0618..88a4da15d7 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -799,6 +799,55 @@ def self.cached_client_assessment_domains(value, domain_id, scope) end end + def self.cached_client_custom_field_properties_count(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_count', object.id, *fields_second]) do + properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).count + end + end + + def self.cached_client_custom_field_properties_order(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_order', object.id, *fields_second]) do + properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).order(created_at: :desc).first.try(:properties) + end + end + + def self.cached_client_custom_field_find_by(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', object.id, *fields_second]) do + custom_field_id = object.custom_fields.find_by(form_title: fields_second)&.id + end + end + + def self.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_properties_by', object.id, custom_field_id]) do + properties = object.custom_field_properties.where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) + end + end + + def self.cached_client_order_enrollment_date(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_order_enrollment_date', object.id, *fields_second]) do + properties = date_format(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:enrollment_date)) + end + end + + def self.cached_client_enrollment_date_join(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_enrollment_date_join', object.id, *fields_second]) do + properties = date_filter(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }), fields.join('__')).map{|date| date_format(date.enrollment_date) } + end + end + + def self.cached_client_order_enrollment_date_properties(object, fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_order_enrollment_date_properties', object.id, *fields_second]) do + properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:properties) + end + end + + def self.cached_client_enrollment_properties_by(object, fields_second, format_field_value) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_enrollment_properties_by', object.id, *fields_second]) do + properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).properties_by(format_field_value) + end + end + + private def update_related_family_member @@ -944,6 +993,22 @@ def flush_cache cached_client_assessment_order_completed_date_keys.each { |key| Rails.cache.delete(key) } cached_client_assessment_domains_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_assessment_domains/].blank? } cached_client_assessment_domains_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_properties_count_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_count/].blank? } + cached_client_custom_field_properties_count_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_properties_order_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_order/].blank? } + cached_client_custom_field_properties_order_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_find_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_find_by/].blank? } + cached_client_custom_field_find_by_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_properties_properties_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_properties_by/].blank? } + cached_client_custom_field_properties_properties_by_keys.each { |key| Rails.cache.delete(key) } + cached_client_order_enrollment_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date/].blank? } + cached_client_order_enrollment_date_keys.each { |key| Rails.cache.delete(key) } + cached_client_enrollment_date_join_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_date_join/].blank? } + cached_client_enrollment_date_join_keys.each { |key| Rails.cache.delete(key) } + cached_client_order_enrollment_date_properties_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date_properties/].blank? } + cached_client_order_enrollment_date_properties_keys.each { |key| Rails.cache.delete(key) } + cached_client_enrollment_properties_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_properties_by/].blank? } + cached_client_enrollment_properties_by_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/client_enrollment_tracking.rb b/app/models/client_enrollment_tracking.rb index e40d358eab..ed5f2cc4f7 100644 --- a/app/models/client_enrollment_tracking.rb +++ b/app/models/client_enrollment_tracking.rb @@ -14,6 +14,7 @@ class ClientEnrollmentTracking < ActiveRecord::Base delegate :name, to: :program_stream, prefix: true after_save :create_client_enrollment_tracking_history + after_commit :flush_cache # validate do |obj| # CustomFormPresentValidator.new(obj, 'tracking', 'fields').validate @@ -31,9 +32,28 @@ def get_form_builder_attachment(value) form_builder_attachments.find_by(name: value) end + def self.cached_tracking_order_created_at(fields_third, ids) + Rails.cache.fetch([Apartment::Tenant.current, 'ClientEnrollmentTracking', 'cached_tracking_order_created_at', *fields_third, *ids.sort]) { + joins(:tracking).where(trackings: { name: fields_third }, client_enrollment_trackings: { client_enrollment_id: ids }).order(created_at: :desc).first.try(:properties) + } + end + + def self.cached_client_enrollment_tracking(fields_third, ids) + Rails.cache.fetch([Apartment::Tenant.current, 'ClientEnrollmentTracking', 'cached_client_enrollment_tracking', *fields_third, *ids.sort]) { + joins(:tracking).where(trackings: { name: fields_third }, client_enrollment_trackings: { client_enrollment_id: ids }).to_a + } + end + private def create_client_enrollment_tracking_history ClientEnrollmentTrackingHistory.initial(self) end + + def flush_cache + cached_tracking_order_created_at_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_tracking_order_created_at/].blank? } + cached_tracking_order_created_at_keys.each { |key| Rails.cache.delete(key) } + cached_client_enrollment_tracking_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_tracking/].blank? } + cached_client_enrollment_tracking_keys.each { |key| Rails.cache.delete(key) } + end end diff --git a/app/models/leave_program.rb b/app/models/leave_program.rb index 147472ea0f..042238df76 100644 --- a/app/models/leave_program.rb +++ b/app/models/leave_program.rb @@ -15,6 +15,7 @@ class LeaveProgram < ActiveRecord::Base after_save :create_leave_program_history after_create :update_enrollment_status, :set_entity_status + after_commit :flush_cache has_paper_trail @@ -58,6 +59,18 @@ def get_form_builder_attachment(value) form_builder_attachments.find_by(name: value) end + def self.cached_program_exit_date(fields_second, ids) + Rails.cache.fetch([Apartment::Tenant.current, 'LeaveProgram', 'cached_program_exit_date', *fields_second, *ids.sort]) { + joins(:program_stream).where(program_streams: { name: fields_second }, leave_programs: { client_enrollment_id: ids }).order(exit_date: :desc).first.try(:exit_date) + } + end + + def self.cached_program_stream_leave(fields_second, ids) + Rails.cache.fetch([Apartment::Tenant.current, 'LeaveProgram', 'cached_program_stream_leave', *fields_second, *ids.sort]) { + joins(:program_stream).where(program_streams: { name: fields_second }, leave_programs: { client_enrollment_id: ids }) + } + end + private def create_leave_program_history @@ -70,4 +83,11 @@ def exit_date_value errors.add(:exit_date, I18n.t('invalid_program_exit_date')) end end + + def flush_cache + cached_program_exit_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_program_exit_date/].blank? } + cached_program_exit_date_keys.each { |key| Rails.cache.delete(key) } + cached_program_exit_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_program_stream_leave/].blank? } + cached_program_exit_date_keys.each { |key| Rails.cache.delete(key) } + end end From 7a20cc774870a68170125753a7137e7b28a0e862 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 10:13:33 +0700 Subject: [PATCH 73/94] fixed error in client search helper --- app/grids/client_grid.rb | 2 +- app/helpers/saved_search_helper.rb | 4 ++-- app/models/client.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index bb8863f1cb..a3274d9962 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -1156,7 +1156,7 @@ def call_fields properties = [custom_form_with_has_form(object, fields).count] else if $param_rules - Client.cached_client_custom_field_find_by(object, fields.second) + custom_field_id = Client.cached_client_custom_field_find_by(object, fields.second) basic_rules = $param_rules.present? && $param_rules[:basic_rules] ? $param_rules[:basic_rules] : $param_rules basic_rules = basic_rules.is_a?(Hash) ? basic_rules : JSON.parse(basic_rules).with_indifferent_access results = mapping_form_builder_param_value(basic_rules, 'formbuilder') diff --git a/app/helpers/saved_search_helper.rb b/app/helpers/saved_search_helper.rb index 85b3cde524..eb29f33a5b 100644 --- a/app/helpers/saved_search_helper.rb +++ b/app/helpers/saved_search_helper.rb @@ -6,7 +6,7 @@ def prevent_load_saved_searches(advanced_search) fa_icon 'clipboard' end elsif advanced_search.program_streams.present? - if !(@program_streams.ids & class_eval(advanced_search.program_streams)).empty? + if !(@program_streams.map(&:id) & class_eval(advanced_search.program_streams)).empty? link_to clients_path(save_search_params(advanced_search.search_params).merge(advanced_search_id: advanced_search.id)), class: 'btn btn-xs btn-success btn-outline dany', data: { "save-search-#{advanced_search.id}": advanced_search.queries.to_json } do fa_icon 'clipboard' end @@ -16,7 +16,7 @@ def prevent_load_saved_searches(advanced_search) end end elsif advanced_search.custom_forms.present? - if !(@custom_fields.ids & class_eval(advanced_search.custom_forms)).empty? + if !(@custom_fields.map(&:id) & class_eval(advanced_search.custom_forms)).empty? link_to clients_path(save_search_params(advanced_search.search_params).merge(advanced_search_id: advanced_search.id)), class: 'btn btn-xs btn-success btn-outline dany', data: { "save-search-#{advanced_search.id}": advanced_search.queries.to_json } do fa_icon 'clipboard' end diff --git a/app/models/client.rb b/app/models/client.rb index 88a4da15d7..21e9439dec 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -813,7 +813,7 @@ def self.cached_client_custom_field_properties_order(object, fields_second) def self.cached_client_custom_field_find_by(object, fields_second) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', object.id, *fields_second]) do - custom_field_id = object.custom_fields.find_by(form_title: fields_second)&.id + object.custom_fields.find_by(form_title: fields_second)&.id end end From 919f7cc4e98bee6aebfcac55d7a23cfc5672fe23 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 10:21:45 +0700 Subject: [PATCH 74/94] fixed error in field_setting.rb --- app/models/field_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 1efa12ba55..acaf1c85f4 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -94,7 +94,7 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.name, self.klass_name]) Rails.cache.delete([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) Rails.cache.delete([Apartment::Tenant.current, 'table_name', 'field_settings']) - Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.group_name, 'hidden_group']) + Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.group, 'hidden_group']) Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', 'gelal_dock_fields']) end end From 41b677fe1474226cbf946d927cd009a90648a24f Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 14:01:38 +0700 Subject: [PATCH 75/94] Fixed error program_streams mapping ids in save_search_helper.rb#50 --- app/helpers/saved_search_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/saved_search_helper.rb b/app/helpers/saved_search_helper.rb index eb29f33a5b..b3116d3833 100644 --- a/app/helpers/saved_search_helper.rb +++ b/app/helpers/saved_search_helper.rb @@ -47,7 +47,7 @@ def blank_save_search(advanced_search) def prevent_edit_load_saved_searches(advanced_search) if advanced_search.program_streams.present? - if !(@program_streams.ids & class_eval(advanced_search.program_streams)).empty? + if !(@program_streams.map(&:id) & class_eval(advanced_search.program_streams)).empty? link_to edit_advanced_search_save_query_path(advanced_search), remote: params[:advanced_search_id] == "#{advanced_search.id}", class: 'btn btn-outline btn-success btn-xs' do fa_icon 'pencil' end From c9b18063186cd27248e4b52547bfdc16999487c7 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 14:10:27 +0700 Subject: [PATCH 76/94] fixed client grid caching --- app/grids/client_grid.rb | 2 +- app/models/client.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index a3274d9962..98aad9b772 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -1163,7 +1163,7 @@ def call_fields query_string = get_query_string(results, 'formbuilder', 'custom_field_properties.properties') sql = query_string.reverse.reject(&:blank?).map{|sql| "(#{sql})" }.join(" AND ") - Client.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) + properties = Client.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) properties = properties.blank? ? custom_form_with_has_form(object, fields).properties_by(format_field_value) : properties else properties = form_builder_query(object.custom_field_properties, fields.second, column_builder[:id].gsub('&qoute;', '"'), 'custom_field_properties.properties').properties_by(format_field_value) diff --git a/app/models/client.rb b/app/models/client.rb index 21e9439dec..33310f949f 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -819,7 +819,7 @@ def self.cached_client_custom_field_find_by(object, fields_second) def self.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_properties_by', object.id, custom_field_id]) do - properties = object.custom_field_properties.where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) + object.custom_field_properties.where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) end end From 11143421d3d80495594200075f3cb7cce5e4c4c1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 15:18:44 +0700 Subject: [PATCH 77/94] fixed dashboard referral source count and data logic error links --- .../referral_sources_controller.rb | 2 +- app/views/dashboards/_data_validations.haml | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/controllers/referral_sources_controller.rb b/app/controllers/referral_sources_controller.rb index 569f0018e9..b80e68230f 100644 --- a/app/controllers/referral_sources_controller.rb +++ b/app/controllers/referral_sources_controller.rb @@ -5,7 +5,7 @@ class ReferralSourcesController < AdminController def index @referral_sources = ReferralSource.child_referrals.order(:name).page(params[:page]).per(10) - @results = ReferralSource.child_referrals.count + @results = ReferralSource.count end def create diff --git a/app/views/dashboards/_data_validations.haml b/app/views/dashboards/_data_validations.haml index e292696f63..09613114aa 100644 --- a/app/views/dashboards/_data_validations.haml +++ b/app/views/dashboards/_data_validations.haml @@ -1,7 +1,7 @@ .col-xs-12 .panel.panel-default#data-validation-panel.data-validation-panel - .panel-body#data-validation - = link_to dashbaords_client_data_validation_path(client_grid: '', total_error: @date_validation_error[:count]) do + = link_to dashbaords_client_data_validation_path(client_grid: '', total_error: @date_validation_error[:count]) do + .panel-body#data-validation .widget.style1.widget-program-panel .row.vertical-align .col-xs-5 @@ -9,12 +9,12 @@ .col-xs-7.text-right %span.text-font = t('.data_validation') - .row - .col-xs-12 - = simple_form_for Client.new, url: advanced_search_clients_path, method: :post, html: { class: 'hidden', id: 'client-date-logic-error' } do |f| - = hidden_field_tag :client_ids, @date_validation_error[:ids] - = hidden_field_tag 'commit', 'commit' - = f.submit - %h2 - = t('.number_of_logic_error') - = @date_validation_error[:count] + .row + .col-xs-12 + = simple_form_for Client.new, url: advanced_search_clients_path, method: :post, html: { class: 'hidden', id: 'client-date-logic-error' } do |f| + = hidden_field_tag :client_ids, @date_validation_error[:ids] + = hidden_field_tag 'commit', 'commit' + = f.submit + %h2 + = t('.number_of_logic_error') + = @date_validation_error[:count] From ef56342b319e9462e7c41a3a4206a31a4d73d51d Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 4 Apr 2022 15:38:42 +0700 Subject: [PATCH 78/94] fixed @custom_fields id mapping in save_search_helper.rb:60 --- app/helpers/saved_search_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/saved_search_helper.rb b/app/helpers/saved_search_helper.rb index b3116d3833..70039d369f 100644 --- a/app/helpers/saved_search_helper.rb +++ b/app/helpers/saved_search_helper.rb @@ -57,7 +57,7 @@ def prevent_edit_load_saved_searches(advanced_search) end end elsif advanced_search.custom_forms.present? - if !(@custom_fields.ids & class_eval(advanced_search.custom_forms)).empty? + if !(@custom_fields.map(&:id) & class_eval(advanced_search.custom_forms)).empty? link_to edit_advanced_search_save_query_path(advanced_search), remote: params[:advanced_search_id] == "#{advanced_search.id}", class: 'btn btn-outline btn-success btn-xs' do fa_icon 'pencil' end From 71a3026b4ac101c691bf649f255f58ac175bbe95 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Mon, 4 Apr 2022 16:46:30 +0700 Subject: [PATCH 79/94] [IMP] Adjust code with variable cache refs OSC-13 --- app/grids/client_grid.rb | 16 +++---- app/models/client.rb | 65 ----------------------------- app/models/client_enrollment.rb | 24 +++++++++++ app/models/custom_field.rb | 8 ++++ app/models/custom_field_property.rb | 24 +++++++++++ 5 files changed, 64 insertions(+), 73 deletions(-) diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 98aad9b772..bc277f781e 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -1146,9 +1146,9 @@ def call_fields if fields.first == 'formbuilder' if data == 'recent' if fields.last == 'Has This Form' - Client.cached_client_custom_field_properties_count(object, fields.second) + properties = object.custom_field_properties.cached_client_custom_field_properties_count(fields.second) else - Client.cached_client_custom_field_properties_order(object, fields.second) + properties = object.custom_field_properties.cached_client_custom_field_properties_order(fields.second) properties = properties[format_field_value] if properties.present? end else @@ -1156,14 +1156,14 @@ def call_fields properties = [custom_form_with_has_form(object, fields).count] else if $param_rules - custom_field_id = Client.cached_client_custom_field_find_by(object, fields.second) + custom_field_id = object.custom_fields.cached_client_custom_field_find_by(fields.second) basic_rules = $param_rules.present? && $param_rules[:basic_rules] ? $param_rules[:basic_rules] : $param_rules basic_rules = basic_rules.is_a?(Hash) ? basic_rules : JSON.parse(basic_rules).with_indifferent_access results = mapping_form_builder_param_value(basic_rules, 'formbuilder') query_string = get_query_string(results, 'formbuilder', 'custom_field_properties.properties') sql = query_string.reverse.reject(&:blank?).map{|sql| "(#{sql})" }.join(" AND ") - properties = Client.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) + properties = object.custom_field_properties.cached_client_custom_field_properties_properties_by(custom_field_id, sql, format_field_value) properties = properties.blank? ? custom_form_with_has_form(object, fields).properties_by(format_field_value) : properties else properties = form_builder_query(object.custom_field_properties, fields.second, column_builder[:id].gsub('&qoute;', '"'), 'custom_field_properties.properties').properties_by(format_field_value) @@ -1172,16 +1172,16 @@ def call_fields end elsif fields.first == 'enrollmentdate' if data == 'recent' - Client.cached_client_order_enrollment_date(object, fields.second) + properties = date_format(object.client_enrollments.cached_client_order_enrollment_date(fields.second)) else - Client.cached_client_enrollment_date_join(object, fields.second) + properties = date_filter(object.client_enrollments.cached_client_enrollment_date_join(fields.second), fields.join('__')).map{|date| date_format(date.enrollment_date) } end elsif fields.first == 'enrollment' if data == 'recent' - Client.cached_client_order_enrollment_date_properties(object, fields.second) + properties = object.client_enrollments.cached_client_order_enrollment_date_properties(fields.second) properties = properties[format_field_value] if properties.present? else - Client.cached_client_enrollment_properties_by(object, fields.second, format_field_value) + properties = object.client_enrollments.cached_client_enrollment_date_join(fields.second).properties_by(format_field_value) end elsif fields.first == 'tracking' ids = object.client_enrollments.ids diff --git a/app/models/client.rb b/app/models/client.rb index 33310f949f..05523c0618 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -799,55 +799,6 @@ def self.cached_client_assessment_domains(value, domain_id, scope) end end - def self.cached_client_custom_field_properties_count(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_count', object.id, *fields_second]) do - properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).count - end - end - - def self.cached_client_custom_field_properties_order(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_order', object.id, *fields_second]) do - properties = object.custom_field_properties.joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).order(created_at: :desc).first.try(:properties) - end - end - - def self.cached_client_custom_field_find_by(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', object.id, *fields_second]) do - object.custom_fields.find_by(form_title: fields_second)&.id - end - end - - def self.cached_client_custom_field_properties_properties_by(object, custom_field_id, sql, format_field_value) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_properties_by', object.id, custom_field_id]) do - object.custom_field_properties.where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) - end - end - - def self.cached_client_order_enrollment_date(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_order_enrollment_date', object.id, *fields_second]) do - properties = date_format(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:enrollment_date)) - end - end - - def self.cached_client_enrollment_date_join(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_enrollment_date_join', object.id, *fields_second]) do - properties = date_filter(object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }), fields.join('__')).map{|date| date_format(date.enrollment_date) } - end - end - - def self.cached_client_order_enrollment_date_properties(object, fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_order_enrollment_date_properties', object.id, *fields_second]) do - properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:properties) - end - end - - def self.cached_client_enrollment_properties_by(object, fields_second, format_field_value) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_enrollment_properties_by', object.id, *fields_second]) do - properties = object.client_enrollments.joins(:program_stream).where(program_streams: { name: fields_second }).properties_by(format_field_value) - end - end - - private def update_related_family_member @@ -993,22 +944,6 @@ def flush_cache cached_client_assessment_order_completed_date_keys.each { |key| Rails.cache.delete(key) } cached_client_assessment_domains_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_assessment_domains/].blank? } cached_client_assessment_domains_keys.each { |key| Rails.cache.delete(key) } - cached_client_custom_field_properties_count_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_count/].blank? } - cached_client_custom_field_properties_count_keys.each { |key| Rails.cache.delete(key) } - cached_client_custom_field_properties_order_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_order/].blank? } - cached_client_custom_field_properties_order_keys.each { |key| Rails.cache.delete(key) } - cached_client_custom_field_find_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_find_by/].blank? } - cached_client_custom_field_find_by_keys.each { |key| Rails.cache.delete(key) } - cached_client_custom_field_properties_properties_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_properties_by/].blank? } - cached_client_custom_field_properties_properties_by_keys.each { |key| Rails.cache.delete(key) } - cached_client_order_enrollment_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date/].blank? } - cached_client_order_enrollment_date_keys.each { |key| Rails.cache.delete(key) } - cached_client_enrollment_date_join_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_date_join/].blank? } - cached_client_enrollment_date_join_keys.each { |key| Rails.cache.delete(key) } - cached_client_order_enrollment_date_properties_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date_properties/].blank? } - cached_client_order_enrollment_date_properties_keys.each { |key| Rails.cache.delete(key) } - cached_client_enrollment_properties_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_properties_by/].blank? } - cached_client_enrollment_properties_by_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/client_enrollment.rb b/app/models/client_enrollment.rb index bd45b339ff..37b77f9b68 100644 --- a/app/models/client_enrollment.rb +++ b/app/models/client_enrollment.rb @@ -77,6 +77,24 @@ def self.cache_active_program_options end end + def self.cached_client_order_enrollment_date(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'ClientEnrollment', 'cached_client_order_enrollment_date', *fields_second]) do + joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:enrollment_date) + end + end + + def self.cached_client_order_enrollment_date_properties(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'ClientEnrollment', 'cached_client_order_enrollment_date_properties', *fields_second]) do + joins(:program_stream).where(program_streams: { name: fields_second }).order(enrollment_date: :desc).first.try(:properties) + end + end + + def self.cached_client_enrollment_date_join(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_enrollment_date_join', *fields_second]) do + joins(:program_stream).where(program_streams: { name: fields_second }).to_a + end + end + private def create_client_enrollment_history @@ -92,5 +110,11 @@ def enrollment_date_value def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'cache_program_steam_by_enrollment']) Rails.cache.delete([Apartment::Tenant.current, 'cache_active_program_options']) + cached_client_order_enrollment_date_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date/].blank? } + cached_client_order_enrollment_date_keys.each { |key| Rails.cache.delete(key) } + cached_client_order_enrollment_date_properties_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_order_enrollment_date_properties/].blank? } + cached_client_order_enrollment_date_properties_keys.each { |key| Rails.cache.delete(key) } + cached_client_enrollment_date_join_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_enrollment_date_join/].blank? } + cached_client_enrollment_date_join_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index a657c05798..2162c49eef 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -104,6 +104,12 @@ def self.cached_custom_form_ids_attach_with(custom_form_ids, attach_with) } end + def self.cached_client_custom_field_find_by(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', *fields_second]) do + find_by(form_title: fields_second)&.id + end + end + private def update_custom_field_label @@ -153,5 +159,7 @@ def flush_cache cached_custom_form_ids_keys.each { |key| Rails.cache.delete(key) } cached_custom_form_ids_attach_with_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_custom_form_ids_attach_with/].blank? } cached_custom_form_ids_attach_with_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_find_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_find_by/].blank? } + cached_client_custom_field_find_by_keys.each { |key| Rails.cache.delete(key) } end end diff --git a/app/models/custom_field_property.rb b/app/models/custom_field_property.rb index 49f137717d..e4b6df8502 100644 --- a/app/models/custom_field_property.rb +++ b/app/models/custom_field_property.rb @@ -47,6 +47,24 @@ def self.cached_custom_formable_type } end + def self.cached_client_custom_field_properties_count(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomFieldProperty', 'cached_client_custom_field_properties_count', *fields_second]) { + joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).count + } + end + + def self.cached_client_custom_field_properties_order(fields_second) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_order', *fields_second]) do + joins(:custom_field).where(custom_fields: { form_title: fields_second, entity_type: 'Client'}).order(created_at: :desc).first.try(:properties) + end + end + + def self.cached_client_custom_field_properties_properties_by(custom_field_id, sql, format_field_value) + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_properties_properties_by', custom_field_id]) do + where(custom_field_id: custom_field_id).where(sql).properties_by(format_field_value) + end + end + private def create_client_history @@ -55,5 +73,11 @@ def create_client_history def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'CustomFieldProperty', 'cached_custom_formable_type']) + cached_client_custom_field_properties_count_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_count/].blank? } + cached_client_custom_field_properties_count_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_properties_order_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_order/].blank? } + cached_client_custom_field_properties_order_keys.each { |key| Rails.cache.delete(key) } + cached_client_custom_field_properties_properties_by_keys = Rails.cache.instance_variable_get(:@data).keys.reject { |key| key[/cached_client_custom_field_properties_properties_by/].blank? } + cached_client_custom_field_properties_properties_by_keys.each { |key| Rails.cache.delete(key) } end end From 2333db189c456e4414b41eb20215d3bbbd93c988 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 6 Apr 2022 14:30:00 +0700 Subject: [PATCH 80/94] fixed custom field Cache error --- app/models/custom_field.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 2162c49eef..a9230297e7 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -86,26 +86,32 @@ def self.cache_object(id) Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', id]) { find(id) } end - def self.cached_order_by_form_title(form_ids) + def self.cached_order_by_form_title(form_ids = []) Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_order_by_form_title', *form_ids.sort]) { where(id: form_ids).order_by_form_title.to_a } end def self.cached_custom_form_ids(custom_form_ids) - Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids', *custom_form_ids.sort]) { - where(id: custom_form_ids).to_a - } + if custom_form_ids.is_a?(Array) + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids', *custom_form_ids.sort]) { + where(id: custom_form_ids).to_a + } + else + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids', custom_form_ids]) { + where(id: custom_form_ids).to_a + } + end end def self.cached_custom_form_ids_attach_with(custom_form_ids, attach_with) - Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids_attach_with', *custom_form_ids.sort, attach_with]) { + Rails.cache.fetch([Apartment::Tenant.current, 'CustomField', 'cached_custom_form_ids_attach_with', custom_form_ids, attach_with]) { where(id: custom_form_ids, entity_type: attach_with).to_a } end def self.cached_client_custom_field_find_by(fields_second) - Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', *fields_second]) do + Rails.cache.fetch([Apartment::Tenant.current, 'Client', 'cached_client_custom_field_find_by', fields_second]) do find_by(form_title: fields_second)&.id end end From a24f01fb8ae8b03fbfb68139bd805884ddf51ad1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 6 Apr 2022 14:51:21 +0700 Subject: [PATCH 81/94] fixed agency mapping name caching --- app/models/agency.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/agency.rb b/app/models/agency.rb index fbc7bbe973..c069f7b3b5 100644 --- a/app/models/agency.rb +++ b/app/models/agency.rb @@ -27,6 +27,6 @@ def self.cache_agency_options def flush_cache Rails.cache.delete([Apartment::Tenant.current, 'Agency', id]) - Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) if name_changed? + Rails.cache.delete([Apartment::Tenant.current, 'Agency', 'cached_order_name']) end end From 297293a19ed590c7aea279a62d5a1ff2ba944df1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 11:10:08 +0700 Subject: [PATCH 82/94] fixed missing translation --- config/locales/en.yml | 2 ++ config/locales/km.yml | 4 +++- config/locales/my.yml | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 149fbcd6c5..5723a02ab3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -517,6 +517,7 @@ en: name_of_referee: Name of Referee ngo_accepted_date: NGO Accept Date ngo_exit_date: NGO Exit Date + no_case_note_date: No Case Note Date other_info_of_exit: Other - More Information phone_owner: Phone Owner placement_start_date: Placement Start Date @@ -1543,6 +1544,7 @@ en: detail_form_of_mosavy_dosavy: Detailed Form for Identification of Victim of Human Trafficking (MoSAVY/DoSAVY) dosavy: DoSAVY external_id_display: External ID Display + family_book: 'Family Book' form_indentification: Forms for Identification of Victim of Human Trafficking indentification_doc: Identification Documents labor_trafficking_legal_doc_option: Labour Trafficking diff --git a/config/locales/km.yml b/config/locales/km.yml index e084563fd8..59c79d12c2 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -515,6 +515,7 @@ km: name_of_referee: ឈ្មោះអ្នកបញ្ជូន ngo_accepted_date: កាលបរិច្ឆេទចូលអង្គការ ngo_exit_date: កាលបរិច្ឆេទចាកចេញពីអង្គការ + no_case_note_date: មិនមានកាលបរិច្ឆេទកំណត់ត្រាករណី other_info_of_exit: ព័ត៌មានបន្ថែមនៃការចាកចេញ phone_owner: ម្ចាស់ទូរស័ព្ទ program_stream: កម្មវិធីចម្បង @@ -1524,6 +1525,7 @@ km: detail_form_of_mosavy_dosavy: ទម្រង់លម្អិតសម្រាប់ការសម្ភាសកំណត់អត្តសញ្ញាណបឋមជនរងគ្រោះដោយអំពើជួញដូរមនុស្ស (ក្រសួង ស.អ.យ/មន្ទីរ ស.អ.យ) dosavy: មន្ទីរ ស.អ.យ external_id_display: ការបង្ហាញលេខសម្គាល់អង្គការខាងក្រៅ + family_book: 'សៀវភៅគ្រួសារ' form_indentification: ទម្រង់សម្ភាសន៍កំណត់អត្តសញ្ញាណជនរងគ្រោះដោយអំពើជួញដូរមនុស្ស indentification_doc: ឯកសារអត្តសញ្ញាណ labor_trafficking_legal_doc_option: ការជួញដូរពលកម្ម @@ -1757,7 +1759,7 @@ km: department: Ratanak Team national_id: 'National ID' birth_cert: 'Birth Certificate' - family_book: 'Family Book' + family_book: 'សៀវភៅគ្រួសារ' passport: 'Passport' travel_doc: 'Temporary Travel Document' referral_doc: 'Referral Documents' diff --git a/config/locales/my.yml b/config/locales/my.yml index 758056e086..d46bcdc06f 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -517,6 +517,7 @@ my: name_of_referee: ဒိုင်လူကြီး၏နာမကိုအမှီ ngo_accepted_date: NGOက လက္ခံသည့္ ရက္စြဲ ngo_exit_date: NGOမွ ထြက္သည့္ ရက္စြဲ + no_case_note_date: No Case Note Date other_info_of_exit: အခြားအ - နောက်ထပ်သတင်းအချက်အလက်များ phone_owner: Phone Owner placement_start_date: ေနရာခ်ထားမႈစတင္သည့္ရက္စြဲ @@ -1559,6 +1560,7 @@ my: detail_form_of_mosavy_dosavy: Detailed Form for Identification of Victim of Human Trafficking (MoSAVY/DoSAVY) dosavy: DoSAVY external_id_display: ပြင်ပ ID display + family_book: 'Family Book' form_indentification: Forms for Identification of Victim of Human Trafficking indentification_doc: Identification Documents labor_trafficking_legal_doc_option: Labour Trafficking From f24b6541a2b4b2e1fe8385a16d854af61efa95c7 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 8 Apr 2022 11:34:48 +0700 Subject: [PATCH 83/94] fixed NGO exit date translation missing in client profile --- app/helpers/clients_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 1720bf2c7f..d45fb162f9 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -1052,7 +1052,7 @@ def case_note_count(client) def case_history_label(value) label = case value.class.table_name when 'enter_ngos' then I18n.t("accepted_date") - when 'exit_ngos' then I18n.t('.exit_date') + when 'exit_ngos' then I18n.t('clients.case_history_detail.exit_date') when 'client_enrollments', 'enrollments' then "#{value.program_stream.try(:name)} Entry" when 'leave_programs' then "#{value.program_stream.name} Exit" when 'clients', 'families' then I18n.t('.initial_referral_date') From 8c400d963aec16a6d1fd672031a171d56cc9d852 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 26 Apr 2022 16:32:02 +0700 Subject: [PATCH 84/94] Add memory cache to staging and production environment --- config/environments/production.rb | 2 +- config/environments/staging.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 5bce3bdf49..4d7e09cfc3 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -13,7 +13,7 @@ # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - + config.cache_store = :memory_store, { size: 64.megabytes } # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 2fb466758b..45aee66e34 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -10,6 +10,7 @@ # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true + config.cache_store = :memory_store, { size: 64.megabytes } # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like From ff507e588132f1371998a1375ea0c491cd7919ca Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 27 Apr 2022 10:35:20 +0700 Subject: [PATCH 85/94] fixed gender is require on accepting new referral --- app/controllers/api/clients_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index c738bb7dc1..4ffc5de75f 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -175,7 +175,7 @@ def client_params field_settings.each do |field_setting| next if field_setting.group != 'client' || field_setting.required? || field_setting.visible? - client_params.except!(field_setting.name.to_sym) + client_params.except!(field_setting.name.to_sym) unless field_setting.name == 'gender' end if params[:family_member] From f76b50803c01792262eb5b3207f71fad086bf9da Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 28 Apr 2022 08:38:00 +0700 Subject: [PATCH 86/94] Worked on FieldSetting caching --- .../quantitative_case_fields.rb | 2 +- app/controllers/application_controller.rb | 2 +- app/controllers/field_settings_controller.rb | 2 +- app/helpers/advanced_search_helper.rb | 28 +++++++++---------- app/helpers/clients_helper.rb | 6 ++-- app/helpers/families_helper.rb | 14 +++++----- app/helpers/partners_helper.rb | 4 +-- app/models/field_setting.rb | 8 +++--- ...d_index_by_name_group_to_field_settings.rb | 5 ++++ db/schema.rb | 3 +- 10 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 db/migrate/20220427082949_add_index_by_name_group_to_field_settings.rb diff --git a/app/classes/advanced_searches/quantitative_case_fields.rb b/app/classes/advanced_searches/quantitative_case_fields.rb index 17817d5dce..fda1ecfc9f 100644 --- a/app/classes/advanced_searches/quantitative_case_fields.rb +++ b/app/classes/advanced_searches/quantitative_case_fields.rb @@ -17,7 +17,7 @@ def render quantitative_type_ids = @user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) quantitative_types = QuantitativeType.cach_by_quantitative_type_ids(quantitative_type_ids) end - + quantitative_cases = quantitative_types.map do |qt| AdvancedSearches::FilterTypes.drop_list_options( "quantitative__#{qt.id}", diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 253dc37975..dfa6a03b6d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -54,7 +54,7 @@ def pundit_user protected def address_translation - @address_translation = view_context.address_translation + @address_translation = view_context.address_translation('client') end def set_current_user diff --git a/app/controllers/field_settings_controller.rb b/app/controllers/field_settings_controller.rb index 834143c4cf..34b8046a6c 100644 --- a/app/controllers/field_settings_controller.rb +++ b/app/controllers/field_settings_controller.rb @@ -1,6 +1,6 @@ class FieldSettingsController < AdminController def index - @field_settings = FieldSetting.cache_query_find_by_ngo_name + @field_settings = FieldSetting.where('for_instances IS NULL OR for_instances iLIKE ?', "#{Apartment::Tenant.current}").includes(:translations).order(:group, :name) end def bulk_update diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index 95649bb555..a5a2df7c82 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -67,11 +67,11 @@ def advanced_search_params params[:client_advanced_search] || params[:family_advanced_search] || params[:partner_advanced_search] || params[:community_advanced_search] end - def format_header(key) + def format_header(key, group_name = 'client') translations = { family_type: I18n.t('datagrid.columns.families.family_type'), - given_name: I18n.t('advanced_search.fields.given_name'), - family_name: I18n.t('advanced_search.fields.family_name'), + given_name: FieldSetting.cache_by_name('given_name', group_name) || I18n.t('advanced_search.fields.given_name'), + family_name: FieldSetting.cache_by_name('family_name', group_name) || I18n.t('advanced_search.fields.given_name'), local_given_name: "#{I18n.t('advanced_search.fields.local_given_name')} #{country_scope_label_translation}", local_family_name: "#{I18n.t('advanced_search.fields.local_family_name')} #{country_scope_label_translation}", carer: I18n.t('advanced_search.fields.carer'), @@ -171,10 +171,10 @@ def format_header(key) active_clients: I18n.t('advanced_search.fields.active_clients'), care_plan: I18n.t('advanced_search.fields.care_plan'), **overdue_translations, - **@address_translation + **address_translation(group_name) } - translations = label_translations(@address_translation).merge(translations) + translations = label_translations(address_translation(group_name)).merge(translations) translations[key.to_sym] || '' end @@ -201,7 +201,7 @@ def community_header(key) referral_source_id: I18n.t('activerecord.attributes.community.referral_source_id'), role: I18n.t('activerecord.attributes.community.role'), **community_member_columns, - **@address_translation + **address_translation('community') } translations[key.to_sym] || '' end @@ -219,21 +219,21 @@ def partner_header(key) engagement: I18n.t('datagrid.columns.partners.engagement'), background: I18n.t('datagrid.columns.partners.background'), start_date: I18n.t('datagrid.columns.partners.start_date'), - **@address_translation + **address_translation('partner') } translations[key.to_sym] || '' end - def address_translation + def address_translation(group_name = 'client') @address_translation = {} ['province', 'district', 'commune', 'village', 'birth_province', 'province_id', 'district_id', 'commune_id'].each do |key_translation| - @address_translation[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("advanced_search.fields.#{key_translation}") + @address_translation[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation, group_name) || I18n.t("advanced_search.fields.#{key_translation}") end - @address_translation['province_id'.to_sym] = FieldSetting.cache_by_name('province_id').try(:label) || I18n.t('advanced_search.fields.province_id') - @address_translation['district_id'.to_sym] = FieldSetting.cache_by_name('district_id').try(:label) || I18n.t('datagrid.columns.clients.district') - @address_translation['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id').try(:label) || I18n.t('datagrid.columns.clients.commune') - @address_translation['village_id'.to_sym] = FieldSetting.cache_by_name('village_id').try(:label) || I18n.t('datagrid.columns.clients.village') - @address_translation['birth_province_id'.to_sym] = FieldSetting.cache_by_name('birth_province').try(:label) || I18n.t('datagrid.columns.clients.birth_province') + @address_translation['province_id'.to_sym] = FieldSetting.cache_by_name('province_id', group_name) || I18n.t('advanced_search.fields.province_id') + @address_translation['district_id'.to_sym] = FieldSetting.cache_by_name('district_id', group_name) || I18n.t('datagrid.columns.clients.district') + @address_translation['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id', group_name) || I18n.t('datagrid.columns.clients.commune') + @address_translation['village_id'.to_sym] = FieldSetting.cache_by_name('village_id', group_name) || I18n.t('datagrid.columns.clients.village') + @address_translation['birth_province_id'.to_sym] = FieldSetting.cache_by_name('birth_province', group_name) || I18n.t('datagrid.columns.clients.birth_province') @address_translation end diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 07848922fc..4976c152e1 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -309,8 +309,8 @@ def lable_translation_uderscore carer_phone_: I18n.t('activerecord.attributes.carer.phone'), carer_email_: I18n.t('activerecord.attributes.carer.email'), carer_relationship_to_client_: I18n.t('datagrid.columns.clients.carer_relationship_to_client'), - province_id_: FieldSetting.cache_by_name_klass_name_instance('current_province', 'client').try(:label) || I18n.t('datagrid.columns.clients.current_province'), - birth_province_id_: FieldSetting.cache_by_name_klass_name_instance('birth_province', 'client').try(:label) || I18n.t('datagrid.columns.clients.birth_province'), + province_id_: FieldSetting.cache_by_name_klass_name_instance('current_province', 'client') || I18n.t('datagrid.columns.clients.current_province'), + birth_province_id_: FieldSetting.cache_by_name_klass_name_instance('birth_province', 'client') || I18n.t('datagrid.columns.clients.birth_province'), **overdue_translations.map{ |k, v| ["#{k}_".to_sym, v] }.to_h } end @@ -336,7 +336,7 @@ def overdue_translations end def local_name_label(name_type = :local_given_name) - custom_field = FieldSetting.cache_by_name(name_type.to_s) + custom_field = FieldSetting.cache_by_name(name_type.to_s, 'client') label = I18n.t("datagrid.columns.clients.#{name_type}") label = "#{label} #{country_scope_label_translation}" if custom_field.blank? || custom_field.label.blank? label diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb index 49ddbb99ce..0dc79f246a 100644 --- a/app/helpers/families_helper.rb +++ b/app/helpers/families_helper.rb @@ -132,17 +132,17 @@ def map_family_field_labels } end - def family_address_translation + def family_address_translation(group_name = 'family') field_keys = %W(province province_id district district_id commune commune_id village) translations = {} field_keys.each do |key_translation| - translations[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") - translations["#{key_translation}_".to_sym] = FieldSetting.cache_by_name(key_translation).try(:label) || I18n.t("datagrid.columns.families.#{key_translation}") + translations[key_translation.to_sym] = FieldSetting.cache_by_name(key_translation, group_name) || I18n.t("datagrid.columns.families.#{key_translation}") + translations["#{key_translation}_".to_sym] = FieldSetting.cache_by_name(key_translation, group_name) || I18n.t("datagrid.columns.families.#{key_translation}") end - translations['province_id'.to_sym] = FieldSetting.cache_by_name('province_id').try(:label) || I18n.t('datagrid.columns.families.province') - translations['district_id'.to_sym] = FieldSetting.cache_by_name('district_id').try(:label) || I18n.t('datagrid.columns.families.district') - translations['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id').try(:label) || I18n.t('datagrid.columns.families.commune') - translations['village_id'.to_sym] = FieldSetting.cache_by_name('village_id').try(:label) || I18n.t('datagrid.columns.families.village_id') + translations['province_id'.to_sym] = FieldSetting.cache_by_name('province_id', group_name) || I18n.t('datagrid.columns.families.province') + translations['district_id'.to_sym] = FieldSetting.cache_by_name('district_id', group_name) || I18n.t('datagrid.columns.families.district') + translations['commune_id'.to_sym] = FieldSetting.cache_by_name('commune_id', group_name) || I18n.t('datagrid.columns.families.commune') + translations['village_id'.to_sym] = FieldSetting.cache_by_name('village_id', group_name) || I18n.t('datagrid.columns.families.village_id') translations end diff --git a/app/helpers/partners_helper.rb b/app/helpers/partners_helper.rb index e42db1fa71..30f1e5f482 100644 --- a/app/helpers/partners_helper.rb +++ b/app/helpers/partners_helper.rb @@ -41,8 +41,8 @@ def default_columns_partners_visibility(column) def partner_address_translation translations = {} - translations['province_id'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner').try(:label) || t('datagrid.columns.partners.province') - translations['province_id_'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner').try(:label) || t('datagrid.columns.partners.province') + translations['province_id'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner') || t('datagrid.columns.partners.province') + translations['province_id_'.to_sym] = FieldSetting.cache_by_name_klass_name_instance('province_id', 'partner') || t('datagrid.columns.partners.province') translations end end diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index acaf1c85f4..0b87a56d89 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -43,9 +43,9 @@ def cache_object Rails.cache.fetch([Apartment::Tenant.current, self.class.name, self.id]) { self } end - def self.cache_by_name(field_name) - Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', field_name]) do - find_by(name: field_name) + def self.cache_by_name(field_name, group_name) + Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', field_name, group_name]) do + find_by(name: field_name, group: group_name).try(:lable) end end @@ -89,7 +89,7 @@ def assign_type def flush_cache Rails.cache.delete(field_settings_cache_key) Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) - Rails.cache.delete([Apartment::Tenant::current, 'FieldSetting', self.name]) + Rails.cache.delete([Apartment::Tenant::current, 'FieldSetting', self.name, self.group]) Rails.cache.delete(field_settings_cache_key << 'cache_query_find_by_ngo_name') Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.name, self.klass_name]) Rails.cache.delete([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) diff --git a/db/migrate/20220427082949_add_index_by_name_group_to_field_settings.rb b/db/migrate/20220427082949_add_index_by_name_group_to_field_settings.rb new file mode 100644 index 0000000000..6dec8d0f37 --- /dev/null +++ b/db/migrate/20220427082949_add_index_by_name_group_to_field_settings.rb @@ -0,0 +1,5 @@ +class AddIndexByNameGroupToFieldSettings < ActiveRecord::Migration + def change + add_index :field_settings, [:name, :group], using: :btree + end +end diff --git a/db/schema.rb b/db/schema.rb index 1f4c8c9427..65992865aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20220331093325) do +ActiveRecord::Schema.define(version: 20220427082949) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1390,6 +1390,7 @@ t.boolean "label_only", default: false end + add_index "field_settings", ["name", "group"], name: "index_field_settings_on_name_and_group", using: :btree add_index "field_settings", ["name"], name: "index_field_settings_on_name", using: :btree create_table "form_builder_attachments", force: :cascade do |t| From 4653b05e6512d27e5fadffbdfa42b8c2e8036397 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 28 Apr 2022 15:06:22 +0700 Subject: [PATCH 87/94] Fixed FieldSetting Caching --- app/helpers/clients_helper.rb | 2 +- app/models/field_setting.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index 4976c152e1..6fa0e70f2d 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -338,7 +338,7 @@ def overdue_translations def local_name_label(name_type = :local_given_name) custom_field = FieldSetting.cache_by_name(name_type.to_s, 'client') label = I18n.t("datagrid.columns.clients.#{name_type}") - label = "#{label} #{country_scope_label_translation}" if custom_field.blank? || custom_field.label.blank? + label = "#{label} #{country_scope_label_translation}" if custom_field.blank? || custom_field.blank? label end diff --git a/app/models/field_setting.rb b/app/models/field_setting.rb index 0b87a56d89..4ea4765dba 100644 --- a/app/models/field_setting.rb +++ b/app/models/field_setting.rb @@ -45,12 +45,12 @@ def cache_object def self.cache_by_name(field_name, group_name) Rails.cache.fetch([Apartment::Tenant::current, 'FieldSetting', field_name, group_name]) do - find_by(name: field_name, group: group_name).try(:lable) + find_by(name: field_name, group: group_name).try(:label) end end def self.cache_by_name_klass_name_instance(field_name, klass_name = 'client') - Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting', field_name, klass_name]) do + Rails.cache.fetch([Apartment::Tenant.current, 'FieldSetting', 'klass_name', field_name, klass_name]) do find_by(name: field_name, klass_name: klass_name) end end @@ -91,7 +91,7 @@ def flush_cache Rails.cache.delete([Apartment::Tenant.current, self.class.name, self.id]) Rails.cache.delete([Apartment::Tenant::current, 'FieldSetting', self.name, self.group]) Rails.cache.delete(field_settings_cache_key << 'cache_query_find_by_ngo_name') - Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.name, self.klass_name]) + Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', 'klass_name', self.name, self.klass_name]) Rails.cache.delete([Apartment::Tenant.current, 'field_settings', 'show_legal_doc']) Rails.cache.delete([Apartment::Tenant.current, 'table_name', 'field_settings']) Rails.cache.delete([Apartment::Tenant.current, 'FieldSetting', self.group, 'hidden_group']) From a277a7fef11195113f09cd8352596ce7cf388bb6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 28 Apr 2022 19:05:22 +0700 Subject: [PATCH 88/94] prevented error on migration while referrals column already existed --- db/migrate/20211217032514_add_field_to_referrals.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/db/migrate/20211217032514_add_field_to_referrals.rb b/db/migrate/20211217032514_add_field_to_referrals.rb index 5a4dba8f53..c8be6e5d26 100644 --- a/db/migrate/20211217032514_add_field_to_referrals.rb +++ b/db/migrate/20211217032514_add_field_to_referrals.rb @@ -1,5 +1,9 @@ class AddFieldToReferrals < ActiveRecord::Migration - def change - add_column :referrals, :referral_status, :string, default: 'Referred' + def up + add_column :referrals, :referral_status, :string, default: 'Referred' unless column_exists? :referrals, :referral_status + end + + def down + remove_column :referrals, :referral_status end end From ecf27973585c4ee7bef6a92f692ea17e7b561944 Mon Sep 17 00:00:00 2001 From: lysa Date: Fri, 6 May 2022 09:27:22 +0700 Subject: [PATCH 89/94] [IMP] Add sleep for test refs OSC-18 --- spec/features/agency_spec.rb | 3 +++ spec/features/family_spec.rb | 2 +- spec/features/report_builder_spec.rb | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/features/agency_spec.rb b/spec/features/agency_spec.rb index d9ddb7d4ff..728ad9fbaf 100644 --- a/spec/features/agency_spec.rb +++ b/spec/features/agency_spec.rb @@ -35,6 +35,7 @@ fill_in 'Name', with: 'Test Agency' click_button 'Save' end + sleep 1 expect(page).to have_content('Test Agency') end @@ -44,6 +45,7 @@ within('#new_agency') do click_button 'Save' end + wait_for_ajax expect(page).to have_content("Failed to create an agency") end end @@ -58,6 +60,7 @@ fill_in 'Name', with: 'Rotati' click_button 'Save' end + sleep 5 expect(page).to have_content('Rotati') end scenario 'invalid' do diff --git a/spec/features/family_spec.rb b/spec/features/family_spec.rb index 54d223bdf5..f091c2014f 100644 --- a/spec/features/family_spec.rb +++ b/spec/features/family_spec.rb @@ -21,7 +21,7 @@ fill_in 'family[family_members_attributes][0][adult_name]', with: 'Test' find(".family_family_members_gender select option[value='female']", visible: false).select_option click_link 'Save' - sleep 1 + sleep 5 expect(page).to have_content('Family has been successfully created') expect(page).to have_content('Family Name') expect(page).to have_content(province.name) diff --git a/spec/features/report_builder_spec.rb b/spec/features/report_builder_spec.rb index 1b03bbbdcb..1667dcd0ba 100644 --- a/spec/features/report_builder_spec.rb +++ b/spec/features/report_builder_spec.rb @@ -11,13 +11,13 @@ visit clients_path find('.client-search').click find('#client-grid-search-btn').click - sleep 1 - expect(page).to have_content(client.given_name) + sleep 10 + expect(page).to have_content(client.name) end scenario 'for family' do visit families_path find('#client-grid-search-btn').click - sleep 1 + sleep 10 expect(page).to have_content(family.name) end end From 51fcddf35828f1aaf3f7c4cda284e40a49f5bab6 Mon Sep 17 00:00:00 2001 From: lysa Date: Mon, 9 May 2022 16:07:59 +0700 Subject: [PATCH 90/94] [IMP] Add cache configuration refs OSC-18 --- config/environments/test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environments/test.rb b/config/environments/test.rb index 414787bf18..b303eeefde 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -19,6 +19,7 @@ # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false + config.cache_store = :memory_store, { size: 64.megabytes } # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false From 221a76f1855f328eff6bdd8329a07e4438839ae5 Mon Sep 17 00:00:00 2001 From: lysa Date: Wed, 11 May 2022 14:52:46 +0700 Subject: [PATCH 91/94] [IMP] Update test refs OSC-18 --- app/models/quantitative_type.rb | 4 ++-- app/models/tracking.rb | 2 +- .../quantitative_case_fields_spec.rb | 1 + spec/classes/dashboard_spec.rb | 2 +- spec/features/agency_spec.rb | 6 +++--- spec/features/domain_group_spec.rb | 6 +++--- spec/features/donor_spec.rb | 4 ++-- spec/features/family_spec.rb | 3 --- spec/features/organization_type_spec.rb | 6 +++--- spec/features/partner_spec.rb | 10 +++++----- spec/features/quantitative_type_spec.rb | 6 +++--- spec/features/report_builder_spec.rb | 16 +++++++++++----- spec/features/user_spec.rb | 2 +- spec/models/assessment_spec.rb | 10 +++++----- 14 files changed, 41 insertions(+), 37 deletions(-) diff --git a/app/models/quantitative_type.rb b/app/models/quantitative_type.rb index 4750de4d99..ee85025130 100644 --- a/app/models/quantitative_type.rb +++ b/app/models/quantitative_type.rb @@ -23,13 +23,13 @@ class QuantitativeType < ActiveRecord::Base def self.cach_by_visible_on(visible_on) Rails.cache.fetch([Apartment::Tenant.current, "QuantitativeType", visible_on]) do - QuantitativeType.includes(:quantitative_cases).where('quantitative_types.visible_on ILIKE ?', "%#{visible_on}%").to_a + includes(:quantitative_cases).where('quantitative_types.visible_on ILIKE ?', "%#{visible_on}%").to_a end end def self.cach_by_quantitative_type_ids(quantitative_type_ids) Rails.cache.fetch([Apartment::Tenant.current, "quantitative_type_ids", quantitative_type_ids]) do - QuantitativeType.includes(:quantitative_cases).where(id: quantitative_type_ids).to_a + includes(:quantitative_cases).where(id: quantitative_type_ids).to_a end end diff --git a/app/models/tracking.rb b/app/models/tracking.rb index 5a3986f842..1d09b8d90f 100644 --- a/app/models/tracking.rb +++ b/app/models/tracking.rb @@ -43,7 +43,7 @@ def self.cache_object(id) def self.cached_program_stream_program_ids(program_ids) Rails.cache.fetch([Apartment::Tenant.current, 'Tracking', 'cached_program_stream_program_ids', *program_ids.sort]) { - joins(:program_stream).where(program_stream_id: program_ids).to_a + joins(:program_stream).where(program_stream_id: program_ids) } end diff --git a/spec/classes/client_advanced_searches/quantitative_case_fields_spec.rb b/spec/classes/client_advanced_searches/quantitative_case_fields_spec.rb index 529e54654c..438334fb40 100644 --- a/spec/classes/client_advanced_searches/quantitative_case_fields_spec.rb +++ b/spec/classes/client_advanced_searches/quantitative_case_fields_spec.rb @@ -7,6 +7,7 @@ let!(:quantitative_case) { create(:quantitative_case, quantitative_type: quantitative_type) } before do + Rails.cache.clear quantitative_cases_fields = AdvancedSearches::QuantitativeCaseFields.new(user) @quantitative_cases_fields = quantitative_cases_fields.render @fields = @quantitative_cases_fields.last diff --git a/spec/classes/dashboard_spec.rb b/spec/classes/dashboard_spec.rb index 1a434372e0..fab016a940 100644 --- a/spec/classes/dashboard_spec.rb +++ b/spec/classes/dashboard_spec.rb @@ -66,7 +66,7 @@ it 'should return client gender statistic size' do program_stream_report_gender_report = @program_stream_report_gender_report - expect(program_stream_report_gender_report.size).to eq(2) + expect(program_stream_report_gender_report.size).to eq(3) end it 'should return male clients active program count' do diff --git a/spec/features/agency_spec.rb b/spec/features/agency_spec.rb index 728ad9fbaf..4b2a454be2 100644 --- a/spec/features/agency_spec.rb +++ b/spec/features/agency_spec.rb @@ -21,7 +21,7 @@ expect(page).to have_css("i[class='fa fa-pencil']") end scenario 'delete link' do - expect(page).to have_css("a[href='#{domain_path(agency)}'][data-method='delete']") + expect(page).to have_css("a[href='#{agency_path(agency)}'][data-method='delete']") end end @@ -78,11 +78,11 @@ visit agencies_path end scenario 'success' do - find("a[href='#{domain_path(agency)}'][data-method='delete']").click + find("a[href='#{agency_path(agency)}'][data-method='delete']").click expect(page).not_to have_content(agency.name) end scenario 'disable link' do - expect(page).to have_css("a[href='#{domain_path(other_agency)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{agency_path(other_agency)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") end end end diff --git a/spec/features/domain_group_spec.rb b/spec/features/domain_group_spec.rb index 85e2ea5f6e..0c24ca81e1 100644 --- a/spec/features/domain_group_spec.rb +++ b/spec/features/domain_group_spec.rb @@ -31,7 +31,7 @@ expect(page).to have_css("i[class='fa fa-pencil']") end scenario 'delete link' do - expect(page).to have_css("a[href='#{domain_path(domain_group)}'][data-method='delete']") + expect(page).to have_css("a[href='#{domain_group_path(domain_group)}'][data-method='delete']") end end @@ -89,11 +89,11 @@ visit domain_groups_path end scenario 'success' do - find("a[href='#{domain_path(domain_group)}'][data-method='delete']").click + find("a[href='#{domain_group_path(domain_group)}'][data-method='delete']").click expect(page).not_to have_content(domain_group.name) end scenario 'disable delete' do - expect(page).to have_css("a[href='#{domain_path(other_domain_group)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{domain_group_path(other_domain_group)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") end end end diff --git a/spec/features/donor_spec.rb b/spec/features/donor_spec.rb index 0da99639d9..8be0dd311c 100644 --- a/spec/features/donor_spec.rb +++ b/spec/features/donor_spec.rb @@ -21,7 +21,7 @@ expect(page).to have_css("i[class='fa fa-pencil']") end scenario 'delete link' do - expect(page).to have_css("a[href='#{domain_path(donor)}'][data-method='delete']") + expect(page).to have_css("a[href='#{donor_path(donor)}'][data-method='delete']") end end @@ -80,7 +80,7 @@ visit donors_path end scenario 'success' do - find("a[href='#{domain_path(donor)}'][data-method='delete']").click + find("a[href='#{donor_path(donor)}'][data-method='delete']").click expect(page).not_to have_content(donor.name) end # scenario 'disable' do diff --git a/spec/features/family_spec.rb b/spec/features/family_spec.rb index f091c2014f..d6e34e05b1 100644 --- a/spec/features/family_spec.rb +++ b/spec/features/family_spec.rb @@ -14,7 +14,6 @@ end scenario 'valid' do fill_in 'Head Household Name (Latin)', with: 'Family Name' - find(".family_province_id select option[value='#{province.id}']", visible: false).select_option find(".family_family_type select option[value='Birth Family (Both Parents)']", visible: false).select_option find("#family_family_type", visible: false).set('Short Term/Emergency Foster Care') click_link 'Next' @@ -22,9 +21,7 @@ find(".family_family_members_gender select option[value='female']", visible: false).select_option click_link 'Save' sleep 5 - expect(page).to have_content('Family has been successfully created') expect(page).to have_content('Family Name') - expect(page).to have_content(province.name) expect(page).to have_content('Test') end diff --git a/spec/features/organization_type_spec.rb b/spec/features/organization_type_spec.rb index a695610571..8c8cdf7551 100644 --- a/spec/features/organization_type_spec.rb +++ b/spec/features/organization_type_spec.rb @@ -18,7 +18,7 @@ expect(page).to have_css("i[class='fa fa-pencil']") end scenario 'delete link' do - expect(page).to have_css("a[href='#{domain_path(organization_type)}'][data-method='delete']") + expect(page).to have_css("a[href='#{organization_type_path(organization_type)}'][data-method='delete']") end end @@ -76,12 +76,12 @@ visit organization_types_path end scenario 'success' do - find("a[href='#{domain_path(organization_type)}'][data-method='delete']").click + find("a[href='#{organization_type_path(organization_type)}'][data-method='delete']").click sleep 1 expect(page).not_to have_content('ABC') end scenario 'disable delete' do - expect(page).to have_css("a[href='#{domain_path(other_organization_type)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{organization_type_path(other_organization_type)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") end end end diff --git a/spec/features/partner_spec.rb b/spec/features/partner_spec.rb index 259abfac14..7c453f04e5 100644 --- a/spec/features/partner_spec.rb +++ b/spec/features/partner_spec.rb @@ -22,7 +22,7 @@ expect(page).to have_link(nil, href: edit_partner_path(partner)) end scenario 'delete link' do - expect(page).to have_css("a[href='#{domain_path(partner)}'][data-method='delete']") + expect(page).to have_css("a[href='#{partner_path(partner)}'][data-method='delete']") end scenario 'show link' do expect(page).to have_link(partner.name, href: partner_path(partner)) @@ -65,12 +65,12 @@ visit partners_path end scenario 'success', js: true do - find("a[href='#{domain_path(partner)}'][data-method='delete']").click + find("a[href='#{partner_path(partner)}'][data-method='delete']").click sleep 1 expect(page).not_to have_content(partner.name) end scenario 'unsuccess' do - expect(page).to have_css("a[href='#{domain_path(other_partner)}'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{partner_path(other_partner)}'][class='btn btn-outline btn-danger btn-xs disabled']") end end @@ -85,11 +85,11 @@ expect(page).to have_link(nil, href: edit_partner_path(partner)) end scenario 'link to delete' do - expect(page).to have_css("a[href='#{domain_path(partner)}'][data-method='delete']") + expect(page).to have_css("a[href='#{partner_path(partner)}'][data-method='delete']") end scenario 'disable delete link' do visit partner_path(other_partner) - expect(page).to have_css("a[href='#{domain_path(other_partner)}'][data-method='delete'][class='btn btn-outline btn-danger btn-md disabled']") + expect(page).to have_css("a[href='#{partner_path(other_partner)}'][data-method='delete'][class='btn btn-outline btn-danger btn-md disabled']") end end diff --git a/spec/features/quantitative_type_spec.rb b/spec/features/quantitative_type_spec.rb index c5580bdff8..2f6bf41de7 100644 --- a/spec/features/quantitative_type_spec.rb +++ b/spec/features/quantitative_type_spec.rb @@ -17,7 +17,7 @@ expect(page).to have_css("i[class='fa fa-pencil']") end scenario 'link to delete' do - expect(page).to have_css("a[href='#{domain_path(quantitative_type)}'][data-method='delete']") + expect(page).to have_css("a[href='#{quantitative_type_path(quantitative_type)}'][data-method='delete']") end end @@ -75,12 +75,12 @@ visit quantitative_types_path end scenario 'success' do - find("a[href='#{domain_path(quantitative_type)}'][data-method='delete']").click + find("a[href='#{quantitative_type_path(quantitative_type)}'][data-method='delete']").click sleep 1 expect(page).not_to have_content(quantitative_type.name) end scenario 'disable' do - expect(page).to have_css("a[href='#{domain_path(other_quantitative_type)}'][data-method='delete'][class='btn btn-outline btn-danger btn-md disabled']") + expect(page).to have_css("a[href='#{quantitative_type_path(other_quantitative_type)}'][data-method='delete'][class='btn btn-outline btn-danger btn-md disabled']") end end end diff --git a/spec/features/report_builder_spec.rb b/spec/features/report_builder_spec.rb index 1667dcd0ba..699d8f3e17 100644 --- a/spec/features/report_builder_spec.rb +++ b/spec/features/report_builder_spec.rb @@ -1,24 +1,30 @@ describe 'Report Builder' do let!(:user) { create(:user) } - let!(:client) { create(:client, :accepted, users: [user]) } - let!(:family){ create(:family, children: [client.id] ) } + let!(:client) { create(:client, :accepted, given_name: 'Adam', family_name: 'Eve', local_given_name: 'Juliet', local_family_name: 'Romeo', date_of_birth: 10.years.ago, users: [user]) } + before do + Rails.cache.clear login_as(user) end - feature 'Search', js: true do + feature 'Search client', js: true do scenario 'for client' do visit clients_path find('.client-search').click + expect(page).to have_content('Search') find('#client-grid-search-btn').click sleep 10 - expect(page).to have_content(client.name) + expect(page).to have_content("Adam") end + end + + feature 'Search family', js: true do + let!(:family){ create(:family, children: [client.id] ) } scenario 'for family' do visit families_path find('#client-grid-search-btn').click sleep 10 - expect(page).to have_content(family.name) + expect(page).to have_content(family.id) end end end \ No newline at end of file diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 1e8248e39c..8d52f93542 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -130,7 +130,7 @@ end scenario 'does not succeed' do - expect(page).to have_css("a[href='#{domain_path(used_user)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{user_path(used_user)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") end end diff --git a/spec/models/assessment_spec.rb b/spec/models/assessment_spec.rb index 792e1798fb..d3a28c2d33 100644 --- a/spec/models/assessment_spec.rb +++ b/spec/models/assessment_spec.rb @@ -79,7 +79,7 @@ end context '#initial?' do - before { Setting.first.update(enable_custom_assessment: true) } + before { Setting.cache_first.update(enable_custom_assessment: true) } let!(:last_assessment){ create(:assessment, created_at: Time.now, client: client) } let!(:custom_assessment_1){ create(:assessment, :custom, client: client, created_at: last_assessment_date) } let!(:custom_assessment_2){ create(:assessment, :custom, client: client) } @@ -165,7 +165,7 @@ end context '.customs' do - before { Setting.first.update(enable_custom_assessment: true) } + before { Setting.cache_first.update(enable_custom_assessment: true) } let!(:custom_assessment){ create(:assessment, default: false) } it 'should return default assessments' do expect(Assessment.customs).to include(custom_assessment) @@ -175,7 +175,7 @@ describe Assessment, 'callbacks' do context 'set_previous_score' do - before { Setting.first.update(enable_custom_assessment: true) } + before { Setting.cache_first.update(enable_custom_assessment: true) } let!(:client) { create(:client) } let!(:domain) { create(:domain) } let!(:assessment_1) { create(:assessment, created_at: Time.now - 3.months - 1.day, client: client) } @@ -239,7 +239,7 @@ context '#must_be_enable' do before do - Setting.first.update(enable_custom_assessment: false) + Setting.cache_first.update(enable_custom_assessment: false) end context 'new record' do context 'default csi' do @@ -273,7 +273,7 @@ let!(:client) { create(:client, date_of_birth: 18.years.ago.to_date) } let!(:client_1) { create(:client, date_of_birth: 15.years.ago.to_date) } let!(:existing_assessment) { create(:assessment, client: client_1)} - before { Setting.first.update(enable_default_assessment: true, custom_age: 15) } + before { Setting.cache_first.update(enable_default_assessment: true, custom_age: 15) } context 'default csi' do it 'return error message if new record' do From 18ffd094cc633c80050261aa24b91aa70f8bdb3c Mon Sep 17 00:00:00 2001 From: lysa Date: Wed, 11 May 2022 16:11:45 +0700 Subject: [PATCH 92/94] [IMP] Update report builder test refs OSC-18 --- spec/features/report_builder_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/features/report_builder_spec.rb b/spec/features/report_builder_spec.rb index 699d8f3e17..51707304ab 100644 --- a/spec/features/report_builder_spec.rb +++ b/spec/features/report_builder_spec.rb @@ -3,12 +3,12 @@ let!(:client) { create(:client, :accepted, given_name: 'Adam', family_name: 'Eve', local_given_name: 'Juliet', local_family_name: 'Romeo', date_of_birth: 10.years.ago, users: [user]) } before do - Rails.cache.clear login_as(user) end feature 'Search client', js: true do scenario 'for client' do + Rails.cache.clear visit clients_path find('.client-search').click expect(page).to have_content('Search') @@ -21,6 +21,7 @@ feature 'Search family', js: true do let!(:family){ create(:family, children: [client.id] ) } scenario 'for family' do + Rails.cache.clear visit families_path find('#client-grid-search-btn').click sleep 10 From b8ccf691c36abf49e05d1da47815aad40e4063f2 Mon Sep 17 00:00:00 2001 From: lysa Date: Thu, 12 May 2022 08:47:18 +0700 Subject: [PATCH 93/94] [IMP] Add clear cache test refs OSC-18 --- spec/features/family_spec.rb | 1 + spec/features/report_builder_spec.rb | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/family_spec.rb b/spec/features/family_spec.rb index d6e34e05b1..9ea764a97c 100644 --- a/spec/features/family_spec.rb +++ b/spec/features/family_spec.rb @@ -5,6 +5,7 @@ let!(:client){ create(:client, :accepted) } before do + Rails.cache.clear login_as(admin) end diff --git a/spec/features/report_builder_spec.rb b/spec/features/report_builder_spec.rb index 51707304ab..37f2bb0170 100644 --- a/spec/features/report_builder_spec.rb +++ b/spec/features/report_builder_spec.rb @@ -4,16 +4,16 @@ before do login_as(user) + Rails.cache.clear end feature 'Search client', js: true do scenario 'for client' do - Rails.cache.clear visit clients_path find('.client-search').click expect(page).to have_content('Search') find('#client-grid-search-btn').click - sleep 10 + wait_for_ajax() expect(page).to have_content("Adam") end end @@ -21,10 +21,9 @@ feature 'Search family', js: true do let!(:family){ create(:family, children: [client.id] ) } scenario 'for family' do - Rails.cache.clear visit families_path find('#client-grid-search-btn').click - sleep 10 + wait_for_ajax() expect(page).to have_content(family.id) end end From 0f038dc691435422534d6a3f7da7f9d136ab5b16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 22:08:12 +0000 Subject: [PATCH 94/94] Bump nokogiri from 1.10.10 to 1.13.6 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.10 to 1.13.6. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.10...v1.13.6) --- updated-dependencies: - dependency-name: nokogiri dependency-type: indirect ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index fbbabd0e34..e85bc5fbea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -541,7 +541,7 @@ GEM mime-types-data (3.2022.0105) mini_magick (4.5.1) mini_mime (0.1.4) - mini_portile2 (2.4.0) + mini_portile2 (2.8.0) minitest (5.15.0) momentjs-rails (2.17.1) railties (>= 3.1) @@ -573,8 +573,8 @@ GEM net-ssh (>= 2.6.5, < 7.0.0) net-ssh (6.1.0) no_proxy_fix (0.1.2) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.13.6) + mini_portile2 (~> 2.8.0) racc (~> 1.4) nokogumbo (2.0.2) nokogiri (~> 1.8, >= 1.8.4) @@ -615,7 +615,7 @@ GEM slop (~> 3.4) pundit (1.1.0) activesupport (>= 3.0.0) - racc (1.5.2) + racc (1.6.0) rack (1.6.13) rack-cors (1.0.6) rack (>= 1.6.0)