From e24e6239ae2b10dff743781c43ff010bf607b649 Mon Sep 17 00:00:00 2001 From: Darren Jensen Date: Tue, 1 Dec 2020 13:43:45 +0700 Subject: [PATCH 0001/1114] update identifier and readme for fargate deployment --- app/views/layouts/_identifier.haml | 3 +-- aws/ecs/README.md | 2 +- aws/ecs/deploy-oscar-staging-ecs.bash | 10 ---------- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/app/views/layouts/_identifier.haml b/app/views/layouts/_identifier.haml index 93bb6c11e7..85adb7c9f2 100644 --- a/app/views/layouts/_identifier.haml +++ b/app/views/layouts/_identifier.haml @@ -1,4 +1,3 @@ .staging-identifier .message-staging.text-center - This is NOT the production server! - = Socket.gethostname \ No newline at end of file + This is not the production server!! \ No newline at end of file diff --git a/aws/ecs/README.md b/aws/ecs/README.md index 6c7277408e..74ddd144b0 100644 --- a/aws/ecs/README.md +++ b/aws/ecs/README.md @@ -6,7 +6,7 @@ This readme is about running OSCaR in AWS ECR Fargate as Docker Container instan There are two steps: -1. Run `cap staging deploy` as usual. +1. Run `cap staging deploy` as usual (if using Docker start the container first `docker run -v ~/.ssh:/root/.ssh -it --entrypoint bash oscar-staging:latest`). 1. SSH into the staging build server, cd into the `current` folder and run `./aws/ecs/deploy-oscar-staging-ecs.bash`. ## (Optional) check the contents of the image on the server diff --git a/aws/ecs/deploy-oscar-staging-ecs.bash b/aws/ecs/deploy-oscar-staging-ecs.bash index 01d3afd958..0789f951cd 100755 --- a/aws/ecs/deploy-oscar-staging-ecs.bash +++ b/aws/ecs/deploy-oscar-staging-ecs.bash @@ -1,13 +1,3 @@ -# NOTE These rake tasks should have been run already! :) -# precompile rails assets -#RAILS_ENV=staging bundle exec rake assets:precompile - -# compile webpack assets -#RAILS_ENV=staging bundle exec rake webpacker:compile - -# migrate database -#RAILS_ENV=staging bundle exec rake db:migrate - # load env vars for docker commands cd /var/www/oscar-web/current export $(cat .env | sed 's/#.*//g' | xargs) From 3971c0cf59cb8dffaf0926f411feb8db2ab360f5 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 1 Dec 2020 16:08:04 +0700 Subject: [PATCH 0002/1114] added travis build badge --- README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4d7d8628b0..12342f8218 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ ### Open Source Case-management and Record-keeping. +[![Build Status](https://travis-ci.com/DevZep/oscar-web.svg?branch=staging)](https://travis-ci.com/DevZep/oscar-web) + ### Requirements -* Docker Desktop (latest stable version for your platform) +- Docker Desktop (latest stable version for your platform) ### Getting Started @@ -24,7 +26,7 @@ See the project [Makefile](./Makefile) for a list of all the available commands. Once the containers have fired up open a web browser and navigate to [http://localhost:3000](http://localhost:3000) to open the app. To login, click on the 'dev' organizations logo (there should only be the one logo) and the username (email) is any of the users (listed in the 'users' sheet) of the [lib/devdata/dev_tenant.xlsx](lib/devdata/dev_tenant.xlsx) spreadsheet with the password set to `123456789`. -*NOTE* If this is the first time you have run this you may need to stop the containers and run it again! +_NOTE_ If this is the first time you have run this you may need to stop the containers and run it again! ## Debugging using Pry @@ -38,7 +40,7 @@ Now when your code runs and gets to the `binding.pry` line it will halt and a Pr When you have finished dubugging just type `exit` in the Pry REPL session as you normally would. Keep this terminal attached for convenience if you need to use Pry again. -NOTE: To detach the tty __without also terminating the Rails container__, you need to use the escape sequence __Ctrl+P__ followed by __Ctrl+Q__. +NOTE: To detach the tty **without also terminating the Rails container**, you need to use the escape sequence **Ctrl+P** followed by **Ctrl+Q**. ## Troubleshooting @@ -168,15 +170,15 @@ oscar_history_development client_histories > db.client_histories.find()[0] { - "_id" : ObjectId("5ef2fec9c245050001d8244f"), - "tenant" : "dev", - "object" : { - "id" : 11, - "code" : "", - "given_name" : "Darren", - "family_name" : "Jensen", - "gender" : "male", - "date_of_birth" : null, + "_id" : ObjectId("5ef2fec9c245050001d8244f"), + "tenant" : "dev", + "object" : { + "id" : 11, + "code" : "", + "given_name" : "Darren", + "family_name" : "Jensen", + "gender" : "male", + "date_of_birth" : null, ETC..... ``` @@ -227,7 +229,7 @@ Check the local [Makefile](./Makefile) for a complete list of available commands The Docker Compose file contains a pgAdmin service. After `docker-compose up` spins up all the services, its possible to connect to pgAdmin at [http://localhost:5050/](http://localhost:5050/). The pgAdmin username and password are in the `pgadmin` services definition in [docker-compose.yml](./docker-compose.yml). To connect to the oscar database, simply expand the OSCaR Server Group in the top left and click on the database. You will be asked to enter the db password (123456789). -Note, if you only started the 'core' services and you want to fire up pgAdmin service too, then simply run the following command at your local terminal (note this will also startup the `db` service (Postgres) if it is not running): +Note, if you only started the 'core' services and you want to fire up pgAdmin service too, then simply run the following command at your local terminal (note this will also startup the `db` service (Postgres) if it is not running): ``` docker-compose up pgadmin From ce89727976a6895db8f37aa9daa014b9c34b9ff8 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 7 Dec 2020 09:08:41 +0700 Subject: [PATCH 0003/1114] pulled from staging --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index be85223bae..4ee375ed59 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -17,6 +17,7 @@ enable_extension "plpgsql" enable_extension "hstore" enable_extension "pgcrypto" + enable_extension "uuid-ossp" create_table "able_screening_questions", force: :cascade do |t| t.string "question" From f71554671008277722e2fcc0c0490a3eea5e6a9e Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 7 Dec 2020 09:49:06 +0700 Subject: [PATCH 0004/1114] udated Dockerfile to use bundler 2.1.4 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c263692c40..84fcabad53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN phantomjs --version # will be cached unless changes to one of those two files # are made. COPY Gemfile Gemfile.lock ./ -RUN gem install bundler -v 1.17.3 +RUN gem install bundler -v 2.1.4 RUN bundle install --verbose --jobs 20 --retry 5 RUN npm install -g yarn RUN yarn install --check-files @@ -40,4 +40,4 @@ ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. -CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] \ No newline at end of file +CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] From 031a1b3c2fb554e13c44c1425e3a8b7f49b4803a Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 7 Dec 2020 10:05:51 +0700 Subject: [PATCH 0005/1114] Reverted back bundler version --- Dockerfile | 2 +- Gemfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 84fcabad53..02a0933759 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN phantomjs --version # will be cached unless changes to one of those two files # are made. COPY Gemfile Gemfile.lock ./ -RUN gem install bundler -v 2.1.4 +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 diff --git a/Gemfile b/Gemfile index 123bb6199d..6d0960e1e1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' +gem 'bundler', '1.17.3' gem 'rails', '4.2.2' gem 'pg', '~> 0.18.4' gem 'jquery-rails' From 13010f770c7c88b3fc2143909fd1a376b9cc4196 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 7 Dec 2020 10:19:55 +0700 Subject: [PATCH 0006/1114] Reverted back bundler version --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 75aa7ea460..bcd39e3d07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -858,6 +858,7 @@ DEPENDENCIES bourbon (~> 4.2) browser (~> 2.1) bullet (= 5.4.3) + bundler (= 1.17.3) cancancan (~> 1.13, >= 1.13.1) capistrano (= 3.9.0) capistrano-foreman @@ -958,4 +959,4 @@ DEPENDENCIES x-editable-rails (~> 1.5, >= 1.5.5.1) BUNDLED WITH - 2.1.4 + 1.17.3 From a828021f7b6abd9affa3499e6a2f1a199d3cfa5f Mon Sep 17 00:00:00 2001 From: Vibol Teav Date: Wed, 9 Dec 2020 13:20:03 +0700 Subject: [PATCH 0007/1114] added missing show data --- app/views/families/show.html.haml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index b09a4f9280..643a8c50f9 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -58,6 +58,15 @@ .panel-body.text-center{ style: 'min-height: 50px; background-color: yellow;' } %h3 Case Management Tool Set +- if @family.enrollments.active.count > 0 + = link_to family_enrolled_programs_path(@family), data: { toggle: "popover", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } do + .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.enrolled_program_streams') +- else + .btn.btn-primary.small-btn-margin.btn-sm.disabled.btn-fit{ data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } } + = t('.enrolled_program_streams') += link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do + .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') + .ibox.mini-margin .ibox-title %h5= "#{t('.general_info')} #{@family.name}" From 179be278fa200b232d339192fdfeacf8fe8adf3d Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 11 Dec 2020 10:13:44 +0700 Subject: [PATCH 0008/1114] fixed confict in app/views/families/show.html.haml --- app/views/families/show.html.haml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index 23a406ba9e..bc52c9fff5 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -27,12 +27,19 @@ - if current_organization.available_for_referral? %input#family-id{ value: "#{@family.id}", type: 'hidden' } .btn-group.small-btn-margin - %button.btn-sm.btn.btn-warning.dropdown-toggle.btn-fit#add-referral-btn{ data: { toggle: "dropdown", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.referral_form')}", placement: "auto" }, class: ('disabled' if @family.family_referrals.empty?) } + %button.btn-sm.btn.btn-warning.dropdown-toggle.btn-fit{ data: { toggle: "dropdown", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.referral_form')}", placement: "auto" }, class: ('disabled' if @family.family_referrals.empty?) } = t('.referral_forms') %span.caret %ul.dropdown-menu.btn-fit.scrollable-dropdown-menu.referral-forms %li= link_to t('.referred_to'), family_family_referrals_path(@family, referral_type: 'referred_to') %li= link_to t('.referred_from'), family_family_referrals_path(@family, referral_type: 'referred_from') + .btn-group.small-btn-margin + %button.btn-sm.btn.btn-warning.dropdown-toggle.btn-fit#add-referral-btn{ data: { toggle: "dropdown", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.refer_family')}", placement: "auto" } } + = t('.refer_family') + %span.caret + %ul.dropdown-menu.btn-fit.scrollable-dropdown-menu.referral-forms + - ngos.each do |value, key| + %li= link_to value, new_family_family_referral_url(@family, ngo: key, external_ngo_name: key == 'external referral' && value), :class => 'target-ngo', :value => "#{key}", :id => "#{key}" - if @family.enrollments.active.count > 0 = link_to family_enrolled_programs_path(@family), data: { toggle: "popover", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } do @@ -43,7 +50,6 @@ = link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') - %div{ class: "col-md-9"} %table.table.small.m-b-xs#main-info %tbody From 7c3f2395a841c2a9fac86aaba2687430c77bb633 Mon Sep 17 00:00:00 2001 From: Pirun Seng Date: Tue, 15 Dec 2020 13:15:28 +0700 Subject: [PATCH 0009/1114] removes duplicated family program stream buttons --- app/views/families/show.html.haml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index 352a3757e2..1bdc36add1 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -116,16 +116,6 @@ = link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') - -- if @family.enrollments.active.count > 0 - = link_to family_enrolled_programs_path(@family), data: { toggle: "popover", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } do - .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.enrolled_program_streams') -- else - .btn.btn-primary.small-btn-margin.btn-sm.disabled.btn-fit{ data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } } - = t('.enrolled_program_streams') -= link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do - .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') - .ibox.mini-margin .ibox-title %h5= "#{t('.general_info')} #{@family.name}" From 504be127c9520e8140686692b971850c69376968 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 17 Dec 2020 15:25:40 +0700 Subject: [PATCH 0010/1114] modified Dockerfile to include vim and config/initializers/sidekiq.rb --- Dockerfile | 2 +- config/initializers/sidekiq.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 02a0933759..2c5b557b12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ruby:2.3.3 -RUN apt-get update -qq && apt-get install -y nodejs postgresql-client +RUN apt-get update -qq && apt-get install -y nodejs postgresql-client vim RUN mkdir /app WORKDIR /app diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 7bec7f05d0..e7e1ed7880 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,9 +1,9 @@ Sidekiq.default_worker_options = { retry: 3, backtrace: true } Sidekiq.configure_server do |config| - config.redis = { url: 'redis://localhost:6379/0' } + config.redis = { url: (ENV["REDIS_URL"] || 'redis://localhost:6379/0') } end Sidekiq.configure_client do |config| - config.redis = { url: 'redis://localhost:6379/0' } -end \ No newline at end of file + config.redis = { url: (ENV["REDIS_URL"] || 'redis://localhost:6379/0') } +end From 2291edc07610dcd1aa1e33d81f187ed6440190cf Mon Sep 17 00:00:00 2001 From: JaneSoo Date: Fri, 18 Dec 2020 20:39:28 +0100 Subject: [PATCH 0011/1114] change bundler version --- Gemfile.lock | 393 +++++++++++++++++++++++++++------------------------ 1 file changed, 207 insertions(+), 186 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bcd39e3d07..40410258c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -21,9 +21,12 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.1) - active_model_serializers (0.9.4) - activemodel (>= 3.2) - active_record_union (1.2.0) + active_model_serializers (0.10.12) + actionpack (>= 4.1, < 6.2) + activemodel (>= 4.1, < 6.2) + case_transform (>= 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.3) + active_record_union (1.3.0) activerecord (>= 4.0) activejob (4.2.2) activesupport (= 4.2.2) @@ -41,28 +44,32 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - acts_as_paranoid (0.6.1) + acts_as_paranoid (0.6.3) activerecord (>= 4.2, < 7.0) activesupport (>= 4.2, < 7.0) - addressable (2.4.0) - airbrussh (1.3.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) - ancestry (3.0.5) - activerecord (>= 3.2.0) + aliyun-sdk (0.8.0) + nokogiri (~> 1.6) + rest-client (~> 2.0) + ancestry (3.2.1) + activerecord (>= 4.2.0) apartment (1.2.0) activerecord (>= 3.1.2, < 6.0) rack (>= 1.3.6) appsignal (1.1.9) rack thread_safe - arel (6.0.3) - asset_sync (2.8.1) + arel (6.0.4) + asset_sync (2.13.0) activemodel (>= 4.1.0) fog-core mime-types (>= 2.99) unf - ast (2.3.0) - autoprefixer-rails (6.3.3.1) + ast (2.4.1) + autoprefixer-rails (9.8.6) execjs autosize-rails (1.18.17) rails (>= 3.1) @@ -73,28 +80,28 @@ GEM babel-source (>= 4.0, < 6) execjs (~> 2.0) bbcoder (1.1.1) - bcrypt (3.1.13) - bootstrap-datepicker-rails (1.5.0) + bcrypt (3.1.16) + bootstrap-datepicker-rails (1.9.0.1) railties (>= 3.0) - bootstrap-sass (3.3.6) + bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) - bourbon (4.2.7) + bourbon (4.3.4) sass (~> 3.4) thor (~> 0.19) - browser (2.1.0) - bson (4.2.1) + browser (2.7.1) + bson (4.11.1) builder (3.2.4) bullet (5.4.3) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) - cancancan (1.13.1) + cancancan (1.17.0) capistrano (3.9.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.5.0) + capistrano-bundler (1.6.0) capistrano (~> 3.1) capistrano-foreman (1.4.0) capistrano (~> 3.1) @@ -121,11 +128,13 @@ GEM activemodel (>= 4.0.0) activesupport (>= 4.0.0) mime-types (>= 1.16) + case_transform (0.2) + activesupport caxlsx (2.0.2) htmlentities (~> 4.3.1) nokogiri (>= 1.4.1) rubyzip (~> 1.2) - chartkick (3.4.0) + chartkick (3.4.2) choice (0.2.0) chronic (0.10.2) claide (1.0.3) @@ -134,18 +143,18 @@ GEM nap open4 (~> 1.3) cliver (0.3.2) - cocoon (1.2.9) - coderay (1.1.1) + cocoon (1.2.15) + coderay (1.1.3) coffee-rails (4.1.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.1.x) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) + coffee-script-source (1.12.2) colored2 (3.1.2) concurrent-ruby (1.1.7) - connection_pool (2.2.0) + connection_pool (2.2.3) cork (0.3.0) colored2 (~> 3.1) crass (1.0.6) @@ -162,14 +171,14 @@ GEM no_proxy_fix octokit (~> 4.7) terminal-table (~> 1) - database_cleaner (1.5.1) - datagrid (1.4.2) + database_cleaner (1.8.5) + datagrid (1.4.4) rails (>= 3.2.22.2) - db_text_search (0.2.0) + db_text_search (0.2.2) activerecord (>= 4.1.15, < 6.0) - declarative (0.0.9) + declarative (0.0.20) declarative-option (0.1.0) - devise (3.5.6) + devise (3.5.10) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) @@ -202,20 +211,20 @@ GEM enumerize (2.3.1) activesupport (>= 3.2) erubis (2.7.0) - ethon (0.8.1) + ethon (0.12.0) ffi (>= 1.3.0) eventmachine (1.2.7) - excon (0.73.0) - execjs (2.6.0) - factory_girl (4.5.0) + excon (0.78.1) + execjs (2.7.0) + factory_girl (4.9.0) activesupport (>= 3.0.0) - factory_girl_rails (4.6.0) - factory_girl (~> 4.5.0) + factory_girl_rails (4.9.0) + factory_girl (~> 4.9.0) railties (>= 3.0.0) - faraday (0.12.1) + faraday (0.17.3) multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) http-cookie (~> 1.0.0) faraday-encoding (0.0.5) faraday @@ -223,9 +232,9 @@ GEM faraday (~> 0.8) faraday_middleware (0.14.0) faraday (>= 0.7.4, < 1.0) - fastimage (2.1.7) + fastimage (2.2.0) ffaker (2.1.0) - ffi (1.9.10) + ffi (1.13.1) fission (0.5.0) CFPropertyList (~> 2.2) flay (2.12.1) @@ -267,7 +276,8 @@ GEM fog-xml (~> 0.1.1) ipaddress (~> 0.5) json (>= 1.8, < 2.0) - fog-aliyun (0.3.5) + fog-aliyun (0.3.19) + aliyun-sdk (~> 0.8.0) fog-core fog-json ipaddress (~> 0.8) @@ -313,7 +323,7 @@ GEM fog-core fog-json fog-xml - fog-internet-archive (0.0.1) + fog-internet-archive (0.0.2) fog-core fog-json fog-xml @@ -370,7 +380,7 @@ GEM fog-voxel (0.1.0) fog-core fog-xml - fog-vsphere (3.3.0) + fog-vsphere (3.5.0) fog-core rbvmomi (>= 1.9, < 3) fog-xenserver (1.0.0) @@ -380,8 +390,8 @@ GEM fog-xml (0.1.3) fog-core nokogiri (>= 1.5.11, < 2.0.0) - font-awesome-rails (4.7.0.0) - railties (>= 3.2, < 5.1) + font-awesome-rails (4.7.0.5) + railties (>= 3.2, < 6.1) foreman (0.78.0) thor (~> 0.19.1) formatador (0.2.5) @@ -391,32 +401,30 @@ GEM jquery-rails (>= 4.0.5, < 5.0.0) jquery-ui-rails (>= 5.0.2) momentjs-rails (>= 2.9.0) - gemoji (2.1.0) + gemoji (3.0.1) git (1.7.0) rchardet (~> 1.8) github-markdown (0.6.9) - globalid (0.3.6) - activesupport (>= 4.1.0) + globalid (0.4.2) + activesupport (>= 4.2.0) globalize (5.1.0) activemodel (>= 4.2, < 5.2) activerecord (>= 4.2, < 5.2) request_store (~> 1.0) - google-api-client (0.10.3) - addressable (~> 2.3) - googleauth (~> 0.5) - httpclient (~> 2.7) - hurley (~> 0.1) - memoist (~> 0.11) - mime-types (>= 1.6) + google-api-client (0.32.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.5, < 0.10.0) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - googleauth (0.5.1) - faraday (~> 0.9) - jwt (~> 1.4) - logging (~> 2.0) - memoist (~> 0.12) + signet (~> 0.10) + googleauth (0.9.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) haml (4.0.7) tilt @@ -426,13 +434,13 @@ GEM haml (>= 4.0.6, < 5.0) html2haml (>= 1.0.1) railties (>= 4.0.1) - hashie (3.4.3) - html-pipeline (2.4.2) + hashie (4.1.0) + html-pipeline (2.14.0) activesupport (>= 2) nokogiri (>= 1.4) html-pipeline-vimeo (0.1.1) html-pipeline (~> 2.0) - html-pipeline-youtube (0.1.3) + html-pipeline-youtube (0.1.4) html-pipeline (~> 2.0) html2haml (2.2.0) erubis (~> 2.7.0) @@ -440,33 +448,30 @@ GEM nokogiri (>= 1.6.0) ruby_parser (~> 3.5) htmlentities (4.3.4) + http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) - hurley (0.2) i18n (0.9.5) concurrent-ruby (~> 1.0) - inline_svg (1.7.1) + inline_svg (1.7.2) activesupport (>= 3.0) nokogiri (>= 1.6) ipaddress (0.8.3) - jbuilder (2.4.1) - activesupport (>= 3.0.0, < 5.1) - multi_json (~> 1.2) + jbuilder (2.9.1) + activesupport (>= 4.2.0) jquery-datatables-rails (3.4.0) actionpack (>= 3.1) jquery-rails railties (>= 3.1) sass-rails - jquery-rails (4.1.0) - rails-dom-testing (~> 1.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) - jquery-validation-rails (1.13.1) - railties (>= 3.2, < 5.0) - thor (~> 0.14) + jquery-validation-rails (1.19.0) jquery_query_builder-rails (0.5.0) activesupport json (>= 1.8.3) @@ -475,7 +480,8 @@ GEM json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) - jwt (1.5.6) + jsonapi-renderer (0.2.2) + jwt (2.2.2) kaminari (1.2.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.1) @@ -497,18 +503,14 @@ GEM actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) - loofah (2.7.0) + loofah (2.8.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) + mail (2.7.1) + mini_mime (>= 0.1.1) mail_interceptor (0.0.7) activesupport - memoist (0.15.0) + memoist (0.16.2) metainspector (5.4.0) addressable (~> 2.4) faraday (~> 0.9) @@ -519,19 +521,21 @@ GEM fastimage (~> 2.0) nesty (~> 1.0) nokogiri (~> 1.6) - method_source (0.8.2) - mime-types (2.99.1) - mini_magick (4.5.1) - mini_mime (0.1.4) + method_source (1.0.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.1104) + mini_magick (4.11.0) + mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) momentjs-rails (2.17.1) railties (>= 3.1) - mongo (2.4.1) - bson (>= 4.2.1, < 5.0.0) - mongoid (5.2.1) + mongo (2.14.0) + bson (>= 4.8.2, < 5.0.0) + mongoid (5.4.1) activemodel (~> 4.0) - mongo (>= 2.4.1, < 3.0.0) + mongo (>= 2.5.1, < 3.0.0) origin (~> 2.3) tzinfo (>= 0.3.37) mongoid-compatibility (0.5.1) @@ -544,41 +548,42 @@ GEM rspec-core (~> 3.3) rspec-expectations (~> 3.3) rspec-mocks (~> 3.3) - multi_json (1.14.1) - multipart-post (2.0.0) + multi_json (1.15.0) + multipart-post (2.1.1) nap (1.1.0) - neat (1.8.0) + neat (1.9.1) sass (>= 3.3) thor (~> 0.19) nesty (1.0.2) - net-scp (2.0.0) - net-ssh (>= 2.6.5, < 6.0.0) - net-ssh (5.2.0) + net-scp (3.0.0) + net-ssh (>= 2.6.5, < 7.0.0) + net-ssh (6.1.0) + netrc (0.11.0) no_proxy_fix (0.1.2) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - nokogumbo (2.0.2) + nokogumbo (2.0.4) nokogiri (~> 1.8, >= 1.8.4) octokit (4.19.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - omniauth (1.3.1) - hashie (>= 1.2, < 4) - rack (>= 1.0, < 3) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) open4 (1.3.4) - optimist (3.0.0) - origin (2.3.0) + optimist (3.0.1) + origin (2.3.1) orm_adapter (0.5.0) - os (0.9.6) - paper_trail (5.2.2) + os (1.1.1) + paper_trail (5.2.3) activerecord (>= 3.0, < 6.0) request_store (~> 1.1) - parser (2.3.3.1) - ast (~> 2.2) + parser (2.7.2.0) + ast (~> 2.4.1) path_expander (1.1.0) pg (0.18.4) phantomjs (2.1.1.0) - phony (2.15.15) + phony (2.18.18) phony_rails (0.12.13) activesupport (>= 3.0) phony (~> 2.12) @@ -587,20 +592,20 @@ GEM cliver (~> 0.3.1) multi_json (~> 1.0) websocket-driver (>= 0.2.0) - powerpack (0.1.1) - proxies (0.2.1) - pry (0.10.3) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) + powerpack (0.1.3) + proxies (0.2.3) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) pundit (1.1.0) activesupport (>= 3.0.0) rack (1.6.13) rack-cors (1.0.6) rack (>= 1.6.0) - rack-mini-profiler (1.0.0) + rack-mini-profiler (1.1.6) rack (>= 1.2.0) - rack-protection (1.5.3) + rack-protection (1.5.5) rack rack-proxy (0.6.5) rack @@ -623,14 +628,14 @@ GEM activesupport (>= 4.2.0, < 5.0) nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-erd (1.4.6) - activerecord (>= 3.2) - activesupport (>= 3.2) + rails-erd (1.6.0) + activerecord (>= 4.2) + activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - rails-timeago (2.14.0) + rails-timeago (2.19.0) actionpack (>= 3.1) activesupport (>= 3.1) railties (4.2.2) @@ -638,49 +643,60 @@ GEM activesupport (= 4.2.2) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.1) + rainbow (2.2.2) + rake rake (13.0.1) - rb-gravatar (1.0.5) + rb-fsevent (0.10.4) + rb-gravatar (1.0.6) + rb-inotify (0.10.1) + ffi (~> 1.0) rbvmomi (2.4.1) builder (~> 3.0) json (>= 1.8) nokogiri (~> 1.5) optimist (~> 3.0) rchardet (1.8.0) - react-rails (2.6.0) + react-rails (2.6.1) babel-transpiler (>= 0.7.0) connection_pool execjs railties (>= 3.2) tilt - redis (3.2.2) + redis (3.3.5) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) - request_store (1.3.0) - responders (2.1.1) - railties (>= 4.2.0, < 5.1) - retriable (3.0.2) - roo (2.3.2) + request_store (1.5.0) + rack (>= 1.4) + responders (2.4.1) + actionpack (>= 4.2.0, < 6.0) + railties (>= 4.2.0, < 6.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retriable (3.1.2) + roo (2.8.3) nokogiri (~> 1) - rubyzip (~> 1.1, < 2.0.0) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) + rubyzip (>= 1.3.0, < 3.0.0) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) rspec-activemodel-mocks (1.1.0) activemodel (>= 3.0) activesupport (>= 3.0) rspec-mocks (>= 2.99, < 4.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.3) + rspec-core (3.10.0) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) + rspec-support (~> 3.10.0) rspec-rails (4.0.1) actionpack (>= 4.2) activesupport (>= 4.2) @@ -692,28 +708,33 @@ GEM rspec-sidekiq (3.1.0) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.9.4) + rspec-support (3.10.0) rubocop (0.47.1) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-graphviz (1.2.2) - ruby-ole (1.2.12) - ruby-progressbar (1.8.1) - ruby_parser (3.14.2) + ruby-graphviz (1.2.4) + ruby-ole (1.2.12.2) + ruby-progressbar (1.10.1) + ruby_parser (3.15.0) sexp_processor (~> 4.9) - rubyzip (1.2.0) - s3 (0.3.24) - proxies (~> 0.2.0) + rubyzip (1.3.0) + s3 (0.3.29) + addressable + proxies sanitize (5.2.1) crass (~> 1.0.2) nokogiri (>= 1.8.0) nokogumbo (~> 2.0) - sass (3.4.21) - sass-rails (5.0.4) - railties (>= 4.0.0, < 5.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -725,55 +746,55 @@ GEM thor (~> 0.14) sentry-raven (2.13.0) faraday (>= 0.7.6, < 1.0) - sexp_processor (4.14.1) - shoulda-matchers (3.1.1) - activesupport (>= 4.0.0) + sexp_processor (4.15.1) + shoulda-matchers (4.0.1) + activesupport (>= 4.2.0) shoulda-whenever (0.0.2) - sidekiq (4.1.0) + sidekiq (4.1.4) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) redis (~> 3.2, >= 3.2.1) - signet (0.7.3) + sinatra (>= 1.4.7) + signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) - jwt (~> 1.5) + jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simple_form (3.2.1) - actionpack (> 4, < 5.1) - activemodel (> 4, < 5.1) - sinatra (1.4.7) + simple_form (3.5.1) + actionpack (> 4, < 5.2) + activemodel (> 4, < 5.2) + sinatra (1.4.8) rack (~> 1.5) rack-protection (~> 1.4) tilt (>= 1.3, < 3) - slop (3.6.0) sorbet-rails (0.4.0) - spreadsheet (1.1.3) + spreadsheet (1.1.9) ruby-ole (>= 1.0) - sprockets (3.5.2) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-es6 (0.9.1) + sprockets-es6 (0.9.2) babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.0.4) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.18.2) + sshkit (1.21.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) sysrandom (1.0.5) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) text (1.3.1) - thin (1.7.2) + thin (1.8.0) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.19.1) + thor (0.19.4) thread_safe (0.3.6) - thredded (0.6.2) + thredded (0.6.3) active_record_union (>= 1.2.0) autoprefixer-rails autosize-rails @@ -800,43 +821,43 @@ GEM sprockets-es6 tilt (2.0.10) timecop (0.8.1) - tinymce-rails (4.5.6) + tinymce-rails (4.5.7) railties (>= 3.1.1) - typhoeus (1.0.1) - ethon (>= 0.8.0) - tzinfo (1.2.7) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (1.2.9) thread_safe (~> 0.1) uber (0.1.0) - uglifier (2.7.2) - execjs (>= 0.3.0) - json (>= 1.8.0) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) ulid (1.2.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.6) - unicode-display_width (1.1.3) + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) uniform_notifier (1.10.0) - warden (1.2.6) + warden (1.2.7) rack (>= 1.0) webpacker (4.0.7) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - websocket-driver (0.7.0) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) whenever (0.9.7) chronic (>= 0.6.3) where-or (0.1.6) - wicked_pdf (1.0.6) + wicked_pdf (1.4.0) + activesupport wkhtmltopdf-binary-edge (0.12.3.0) - write_xlsx (0.85.7) + write_xlsx (0.85.11) rubyzip (>= 1.0.0) zip-zip x-editable-rails (1.5.5.1) railties - xml-simple (1.1.5) - xmlrpc (0.3.0) + xml-simple (1.1.7) + xmlrpc (0.3.1) xpath (2.1.0) nokogiri (~> 1.3) zip-zip (0.3) From 9bae794ce835d12cbac2903ab8914950bc8f2dbc Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 31 Dec 2020 14:40:53 +0700 Subject: [PATCH 0012/1114] fixed has_one family community_member association --- app/controllers/families_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 980923c839..64f4c0c9fc 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -42,7 +42,7 @@ def new else @family = Family.new @family.family_members.new - @family.community_member.new + @family.community_member = CommunityMember.new @selected_children = params[:children] end end From 6f0dd7c2a93fa2f2dbb9e1fc10e320d4ae5ef331 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 5 Jan 2021 10:55:06 +0700 Subject: [PATCH 0013/1114] removed end keyword miss-matched in goals_controller.rb --- app/controllers/goals_controller.rb | 80 ++--------------------------- 1 file changed, 4 insertions(+), 76 deletions(-) diff --git a/app/controllers/goals_controller.rb b/app/controllers/goals_controller.rb index dd303b4665..cf02f33d8d 100644 --- a/app/controllers/goals_controller.rb +++ b/app/controllers/goals_controller.rb @@ -1,81 +1,11 @@ class GoalsController < AdminController - #commented because not in use - - # load_and_authorize_resource - # before_action :find_care_plan - # before_action :authorize_client, only: [:new, :create] - # before_action :find_goal, only: [:edit, :update, :destroy] - - # def index - # @goals = @care_plan.goals - # end - - # def create - # @goal = @care_plan.goals.new(goal_params) - # respond_to do |format| - # if @goal.save - # format.json { render json: @goal.to_json, status: 200 } - # format.html { redirect_to client_care_plans_path(@care_plan), notice: t('.successfully_created') } - # else - # format.html { render :new } - # format.json { render json: @goal.errors, status: 422 } - # end - # end - # end - - # def edit - # end - - # def update - # if @goal.update_attributes(goal_params) - # redirect_to client_care_plans_path(@care_plan), notice: t('.successfully_updated') - # else - # render :edit - # end - # end - - # def destroy - # respond_to do |format| - # if @goal.destroy - # msg = { status: 'ok', message: t('.successfully_deleted') } - # format.json { render json: msg } - # format.html { redirect_to client_care_plans_path(@client), notice: t('.successfully_deleted') } - # else - # format.json { render json: @goal.errors, status: 422 } - # format.html { redirect_to client_care_plans_path(@client), alert: t('.failed_delete') } - # end - # end - # end - - # private - - # def find_client - # @client = Client.accessible_by(current_ability).friendly.find(params[:client_id]) - # end - - # def goal_params - # params.require(:goal).permit(:domain_id, :description, :assessment_domain_id, :assessment_id) - # end - - # def encode32hex(str) - # str.gsub(/\G(.{5})|(.{1,4}\z)/mn) do - # full = $1; frag = $2 - # n, c = (full || frag.ljust(5, "\0")).unpack("NC") - # full = ((n << 8) | c).to_s(32).rjust(8, "0") - # if frag - # full[0, (frag.length*8+4).div(5)].ljust(8, "=") - # else - # full - # end - # end - end - - HEX = '[0-9a-v]' + HEX = '[0-9a-v]'.freeze def decode32hex(str) str.gsub(/\G\s*(#{HEX}{8}|#{HEX}{7}=|#{HEX}{5}={3}|#{HEX}{4}={4}|#{HEX}{2}={6}|(\S))/imno) do - raise "invalid base32" if $2 + raise 'invalid base32' if $2 + s = $1 - s.tr("=", "0").to_i(32).divmod(256).pack("NC")[0,(s.count("^=")*5).div(8)] + s.tr('=', '0').to_i(32).divmod(256).pack("NC")[0,(s.count("^=")*5).div(8)] end end @@ -86,6 +16,4 @@ def find_goal def authorize_client authorize @client, :create? end - end - \ No newline at end of file From f8076a64753fb6acdd17d3a7ae30147fadd96bc3 Mon Sep 17 00:00:00 2001 From: Vibol T Date: Wed, 6 Jan 2021 09:23:09 +0700 Subject: [PATCH 0014/1114] fixed valiation --- app/models/family.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/family.rb b/app/models/family.rb index 2b8272c7ca..753e75f48f 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -63,11 +63,11 @@ class Family < ActiveRecord::Base validates :family_type, presence: true, inclusion: { in: TYPES } - validates :received_by_id, :initial_referral_date, :case_worker_ids, :referral_source_category_id, presence: true, if: :case_management_record? + validates :received_by_id, :initial_referral_date, :referral_source_category_id, presence: true, if: :case_management_record? validates :code, uniqueness: { case_sensitive: false }, if: 'code.present?' validates :status, inclusion: { in: STATUSES } validate :client_must_only_belong_to_a_family - validates :case_worker_ids, presence: true, on: :update, unless: :exit_ngo? + validates :case_worker_ids, presence: true, on: :update, if: -> { !exit_ngo? && case_management_record? } after_create :assign_slug after_save :save_family_in_client, :mark_referral_as_saved From ebf895b0135a58256847a394c9924aa044f896f9 Mon Sep 17 00:00:00 2001 From: Vibol T Date: Wed, 6 Jan 2021 14:42:57 +0700 Subject: [PATCH 0015/1114] fixed family member count --- app/models/family.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/family.rb b/app/models/family.rb index a163d3f50d..3e885c5058 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -80,7 +80,7 @@ def self.update_brc_aggregation_data end def member_count - brc? ? family_members.count : (male_adult_count.to_i + female_adult_count.to_i + male_children_count.to_i + female_children_count.to_i) + family_members.count end def to_select2 From 36cb8147a894d1eff7e27f733d6b17c6bc97c88c Mon Sep 17 00:00:00 2001 From: JaneSoo Date: Wed, 6 Jan 2021 21:39:51 +0100 Subject: [PATCH 0016/1114] change field for referee phone --- app/controllers/families_controller.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 1a824ce761..e9843bbc89 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -135,7 +135,6 @@ def find_referral_by_params def fetch_family_attibutes(family_slug, current_org) attributes = Family.find_by(slug: family_slug).try(:attributes) - referee_phone_number = @family_referral.referral_phone if attributes.present? province_name = Province.find_by(id: attributes['province_id']).try(:name) @@ -149,7 +148,7 @@ def fetch_family_attibutes(family_slug, current_org) village_id = Village.find_by(code: village_code).try(:id) commune_id = Commune.find_by(code: commune_code).try(:id) - attributes = attributes.slice('name', 'name_en', 'house', 'street', 'slug', 'initial_referral_date').merge!({province_id: province_id, district_id: district_id, commune_id: commune_id, village_id: village_id, referee_phone_number: referee_phone_number}) + attributes = attributes.slice('name', 'name_en', 'house', 'street', 'slug', 'initial_referral_date', 'referee_phone_number').merge!({province_id: province_id, district_id: district_id, commune_id: commune_id, village_id: village_id}) @family.province = Province.find_by(id: province_id) @family.district = District.find_by(id: district_id) @family.commune = Commune.find_by(id: commune_id) From 375a13dc6abc6687a35a9eef9925ea1586ab59de Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 8 Jan 2021 09:08:30 +0700 Subject: [PATCH 0017/1114] added gem ruby-ole --- Gemfile | 1 + Gemfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 6d0960e1e1..1b5d947ec0 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,7 @@ gem 'carrierwave', '~> 1.1.0' gem 'mini_magick', '~> 4.5' gem 'chartkick', '~> 3.4' gem 'font-awesome-rails', '~> 4.7' +gem 'ruby-ole', '~> 1.2', '>= 1.2.12.2' gem 'spreadsheet', '~> 1.1.3' gem 'apartment', '~> 1.2' gem 'dropzonejs-rails', '~> 0.7.3' diff --git a/Gemfile.lock b/Gemfile.lock index 790874348c..6d4aa547a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -426,7 +426,7 @@ GEM haml (>= 4.0.6, < 5.0) html2haml (>= 1.0.1) railties (>= 4.0.1) - hashie (3.4.3) + hashie (4.1.0) html-pipeline (2.4.2) activesupport (>= 2) nokogiri (>= 1.4) @@ -700,7 +700,7 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-graphviz (1.2.2) - ruby-ole (1.2.12) + ruby-ole (1.2.12.2) ruby-progressbar (1.8.1) ruby_parser (3.14.2) sexp_processor (~> 4.9) @@ -930,6 +930,7 @@ DEPENDENCIES rspec-rails (~> 4.0.0) rspec-sidekiq (~> 3.0, >= 3.0.3) rubocop (~> 0.47.1) + ruby-ole (~> 1.2, >= 1.2.12.2) s3 sass-rails (~> 5.0) select2-rails (~> 3.5.9.3) From 3e9d187fc05ab8117eb427a6a38e2f031ee5cc00 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 8 Jan 2021 14:13:28 +0700 Subject: [PATCH 0018/1114] bundle update spreadsheet --- Gemfile.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile.lock b/Gemfile.lock index 6d4aa547a9..d3c1a0cbd1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -749,6 +749,7 @@ GEM ruby-ole (>= 1.0) sorbet-rails (0.4.0) spreadsheet (1.1.9) + ruby-ole (>= 1.0) sprockets (3.5.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) From efa26866edf336910cf14f017ade055dcda902ff Mon Sep 17 00:00:00 2001 From: Vibol T Date: Mon, 11 Jan 2021 16:23:57 +0700 Subject: [PATCH 0019/1114] fixed duplicate data --- app/views/families/show.html.haml | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index a064797477..f67706a4d4 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -141,37 +141,6 @@ = link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') - .ibox.mini-margin - .ibox-title - %h5= "#{t('.general_info')} #{@family.name}" - .ibox-tools - %a.collapse-link - .btn.btn-outline.btn-primary - %i.fa.fa-chevron-up - - if can? :manage, Family - %small - = link_to edit_family_path(@family) do - .btn.btn-outline.btn-success - %i.fa.fa-pencil - = remove_link(@family, { family_case: @results || 0 }, 'btn-md') - - .ibox-content - .row.family-summary - .col-sm-12 - %table.table.table-bordered - %tr - %td.spacing-first-col - = t('.code') - %td - %strong - = @family.code - %tr - %td.spacing-first-col - = t('.case_history') - %td - %strong - = family_case_history(@family) - .ibox.mini-margin .ibox-title %h5= "#{t('.general_info')} #{@family.name}" From 41b5791592aba6d8d6e06d52d7e5a4646c812ae8 Mon Sep 17 00:00:00 2001 From: Vibol T Date: Tue, 12 Jan 2021 13:18:25 +0700 Subject: [PATCH 0020/1114] fixed jobs --- app/models/client.rb | 2 +- app/models/community_member.rb | 5 +++-- app/models/family.rb | 2 +- app/models/family_member.rb | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/client.rb b/app/models/client.rb index ac283db11d..54742ca9e8 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -685,7 +685,7 @@ def self.get_address_by_code(the_address_code) private def update_related_family_member - FamilyMember.delay.update_client_relevant_data(family_member.id) if family_member.present? + FamilyMember.delay.update_client_relevant_data(family_member.id, Apartment::Tenant.current) if family_member.present? end def create_client_history diff --git a/app/models/community_member.rb b/app/models/community_member.rb index 09242de7ea..48ed8c1a19 100644 --- a/app/models/community_member.rb +++ b/app/models/community_member.rb @@ -16,8 +16,9 @@ class CommunityMember < ActiveRecord::Base after_commit :save_family_data, if: :persisted? - def self.update_family_relevant_data(family_member_id) - find(family_member_id).save_family_data + def self.update_family_relevant_data(community_member_id, org_name) + Organization.switch_to(org_name) + find(community_member_id).save_family_data end def is_family diff --git a/app/models/family.rb b/app/models/family.rb index 7a2cb089c9..0ba7fa4291 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -199,7 +199,7 @@ def case_management_record? private def update_related_community_member - CommunityMember.delay.update_family_relevant_data(community_member.id) if community_member.present? + CommunityMember.delay.update_family_relevant_data(community_member.id, Apartment::Tenant.current) if community_member.present? end def assign_status diff --git a/app/models/family_member.rb b/app/models/family_member.rb index cb2373514d..fe228ef55a 100644 --- a/app/models/family_member.rb +++ b/app/models/family_member.rb @@ -16,7 +16,8 @@ class FamilyMember < ActiveRecord::Base validates :client_id, uniqueness: { scope: :family_id }, if: :client_id? - def self.update_client_relevant_data(family_member_id) + def self.update_client_relevant_data(family_member_id, org_name) + Organization.switch_to(org_name) find(family_member_id).save_client_data end From 16ce33473e7b1f72c19c78202e8738c7c03eb7d3 Mon Sep 17 00:00:00 2001 From: Vibol T Date: Tue, 12 Jan 2021 15:22:20 +0700 Subject: [PATCH 0021/1114] fixed community form --- app/assets/javascripts/communities/form.coffee | 5 ++--- app/assets/javascripts/families/form.coffee | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/communities/form.coffee b/app/assets/javascripts/communities/form.coffee index d19101ea60..ece728be3c 100644 --- a/app/assets/javascripts/communities/form.coffee +++ b/app/assets/javascripts/communities/form.coffee @@ -29,7 +29,7 @@ CIF.CommunitiesNew = CIF.CommunitiesCreate = CIF.CommunitiesEdit = CIF.Communiti allowedFileExtensions: ['jpg', 'png', 'jpeg', 'doc', 'docx', 'xls', 'xlsx', 'pdf'] _initWizardForm = -> - window.savingCommunity == false + window.savingCommunity = false $("#community-wizard-form").steps headerTag: 'h3' @@ -47,8 +47,7 @@ CIF.CommunitiesNew = CIF.CommunitiesCreate = CIF.CommunitiesEdit = CIF.Communiti if window.savingCommunity == false $("#community-form").submit() window.savingCommunity = true - else - false + return true onCanceled: -> result = confirm('Are you sure?') if result diff --git a/app/assets/javascripts/families/form.coffee b/app/assets/javascripts/families/form.coffee index 5b3a4070fa..3c7265de21 100644 --- a/app/assets/javascripts/families/form.coffee +++ b/app/assets/javascripts/families/form.coffee @@ -61,8 +61,7 @@ CIF.FamiliesNew = CIF.FamiliesCreate = CIF.FamiliesEdit = CIF.FamiliesUpdate = d if window.savingFamily == false $("#family-form").submit() window.savingFamily = true - else - false + return true onCanceled: -> result = confirm('Are you sure?') if result From 64e7799ddd97a6636f9fbce90b041a67fe534e46 Mon Sep 17 00:00:00 2001 From: Vibol T Date: Fri, 15 Jan 2021 14:14:10 +0700 Subject: [PATCH 0022/1114] fixed sync data family/community --- app/models/client.rb | 2 +- app/models/family.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/client.rb b/app/models/client.rb index 54742ca9e8..ef3f86ad7f 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -685,7 +685,7 @@ def self.get_address_by_code(the_address_code) private def update_related_family_member - FamilyMember.delay.update_client_relevant_data(family_member.id, Apartment::Tenant.current) if family_member.present? + FamilyMember.delay.update_client_relevant_data(family_member.id, Apartment::Tenant.current) if family_member.present? && family_member.persisted? end def create_client_history diff --git a/app/models/family.rb b/app/models/family.rb index 0ba7fa4291..f44674cf54 100644 --- a/app/models/family.rb +++ b/app/models/family.rb @@ -199,7 +199,7 @@ def case_management_record? private def update_related_community_member - CommunityMember.delay.update_family_relevant_data(community_member.id, Apartment::Tenant.current) if community_member.present? + CommunityMember.delay.update_family_relevant_data(community_member.id, Apartment::Tenant.current) if community_member.present? && community_member.persisted? end def assign_status From 8a5ab5670010372a2a5238061cee03f0e09c7f7c Mon Sep 17 00:00:00 2001 From: JaneSoo Date: Sat, 16 Jan 2021 13:50:59 +0100 Subject: [PATCH 0023/1114] find address by name instead of code --- app/controllers/families_controller.rb | 12 +++--- db/schema.rb | 53 ++++++++++++-------------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/app/controllers/families_controller.rb b/app/controllers/families_controller.rb index 7e313d4bb6..ae949e5efa 100644 --- a/app/controllers/families_controller.rb +++ b/app/controllers/families_controller.rb @@ -172,15 +172,15 @@ def fetch_family_attibutes(family_slug, current_org) if attributes.present? province_name = Province.find_by(id: attributes['province_id']).try(:name) - district_code = District.find_by(id: attributes['district_id']).try(:code) - village_code = Village.find_by(id: attributes['village_id']).try(:code) - commune_code = Commune.find_by(id: attributes['commune_id']).try(:code) + district_name = District.find_by(id: attributes['district_id'], province_id: attributes['province_id']).try(:name) + commune_name_en = Commune.find_by(id: attributes['commune_id'], district_id: attributes['district_id']).try(:name_en) + village_name_en = Village.find_by(id: attributes['village_id'], commune_id: attributes['commune_id']).try(:name_en) Organization.switch_to current_org.short_name province_id = Province.find_by(name: province_name).try(:id) - district_id = District.find_by(code: district_code).try(:id) - village_id = Village.find_by(code: village_code).try(:id) - commune_id = Commune.find_by(code: commune_code).try(:id) + district_id = District.find_by(name: district_name, province_id: province_id).try(:id) + commune_id = Commune.find_by(name_en: commune_name_en, district_id: district_id).try(:id) + village_id = Village.find_by(name_en: village_name_en, commune_id: commune_id).try(:id) attributes = attributes.slice('name', 'name_en', 'house', 'street', 'slug').merge!({province_id: province_id, district_id: district_id, commune_id: commune_id, village_id: village_id}) diff --git a/db/schema.rb b/db/schema.rb index 8ddddb63a8..097e1d8ece 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -105,9 +105,8 @@ create_table "ar_internal_metadata", primary_key: "key", force: :cascade do |t| t.string "value" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "environment" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false end create_table "assessment_domains", force: :cascade do |t| @@ -679,9 +678,9 @@ t.string "other_agency_name" t.string "other_representative_name" t.string "other_agency_phone" - t.string "locality" t.string "national_id_number" t.string "passport_number" + t.string "locality" end add_index "clients", ["commune_id"], name: "index_clients_on_commune_id", using: :btree @@ -1047,8 +1046,8 @@ t.string "id_poor" t.text "relevant_information" t.string "referee_phone_number" - t.string "documents", default: [], array: true t.string "slug", default: "" + t.string "documents", default: [], array: true end add_index "families", ["commune_id"], name: "index_families_on_commune_id", using: :btree @@ -1969,6 +1968,10 @@ add_index "tasks", ["deleted_at"], name: "index_tasks_on_deleted_at", using: :btree add_index "tasks", ["taskable_type", "taskable_id"], name: "index_tasks_on_taskable_type_and_taskable_id", using: :btree + create_table "test_tables", force: :cascade do |t| + t.string "test" + end + create_table "thredded_categories", force: :cascade do |t| t.integer "messageboard_id", null: false t.string "name", limit: 191, null: false @@ -1985,24 +1988,22 @@ t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "position", null: false end - add_index "thredded_messageboard_groups", ["name"], name: "index_thredded_messageboard_group_on_name", unique: true, using: :btree - create_table "thredded_messageboards", force: :cascade do |t| - t.string "name", limit: 191, null: false + t.string "name", limit: 255, null: false t.string "slug", limit: 191 t.text "description" t.integer "topics_count", default: 0 t.integer "posts_count", default: 0 + t.boolean "closed", default: false, null: false t.integer "last_topic_id" t.integer "messageboard_group_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "position", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end + add_index "thredded_messageboards", ["closed"], name: "index_thredded_messageboards_on_closed", using: :btree add_index "thredded_messageboards", ["messageboard_group_id"], name: "index_thredded_messageboards_on_messageboard_group_id", using: :btree add_index "thredded_messageboards", ["slug"], name: "index_thredded_messageboards_on_slug", using: :btree @@ -2065,7 +2066,6 @@ t.string "hash_id", limit: 191, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "last_post_at" end add_index "thredded_private_topics", ["hash_id"], name: "index_thredded_private_topics_on_hash_id", using: :btree @@ -2103,7 +2103,6 @@ t.integer "moderation_state", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "last_post_at" end add_index "thredded_topics", ["hash_id"], name: "index_thredded_topics_on_hash_id", using: :btree @@ -2129,23 +2128,21 @@ add_index "thredded_user_details", ["user_id"], name: "index_thredded_user_details_on_user_id", using: :btree create_table "thredded_user_messageboard_preferences", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "messageboard_id", null: false - t.boolean "follow_topics_on_mention", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "followed_topic_emails", default: true, null: false + t.integer "user_id", null: false + t.integer "messageboard_id", null: false + t.boolean "notify_on_mention", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "thredded_user_messageboard_preferences", ["user_id", "messageboard_id"], name: "thredded_user_messageboard_preferences_user_id_messageboard_id", unique: true, using: :btree create_table "thredded_user_preferences", force: :cascade do |t| - t.integer "user_id", null: false - t.boolean "follow_topics_on_mention", default: true, null: false - t.boolean "notify_on_message", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "followed_topic_emails", default: true, null: false + t.integer "user_id", null: false + t.boolean "notify_on_mention", default: true, null: false + t.boolean "notify_on_message", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "thredded_user_preferences", ["user_id"], name: "index_thredded_user_preferences_on_user_id", using: :btree @@ -2275,10 +2272,10 @@ t.integer "item_id", null: false t.string "event", null: false t.string "whodunnit" + t.text "object" t.datetime "created_at" + t.text "object_changes" t.integer "transaction_id" - t.jsonb "object" - t.jsonb "object_changes" end add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree From aac906cc8d124490ecf35a98a7d1622ebe9bd1c9 Mon Sep 17 00:00:00 2001 From: Darren Jensen Date: Tue, 19 Jan 2021 19:52:09 +0700 Subject: [PATCH 0024/1114] update README and added some changes to test deployment in new aws account --- app/javascript/components/NewClientForm/admin.js | 1 + app/views/layouts/_identifier.haml | 2 +- aws/ecs/README.md | 14 +++++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/NewClientForm/admin.js b/app/javascript/components/NewClientForm/admin.js index 1df6bbd605..f8d0ecf6db 100644 --- a/app/javascript/components/NewClientForm/admin.js +++ b/app/javascript/components/NewClientForm/admin.js @@ -28,6 +28,7 @@ export default (props) => {
+ DADOU Date: Tue, 19 Jan 2021 20:05:54 +0700 Subject: [PATCH 0025/1114] remove test --- app/javascript/components/NewClientForm/admin.js | 1 - app/views/layouts/_identifier.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/components/NewClientForm/admin.js b/app/javascript/components/NewClientForm/admin.js index f8d0ecf6db..1df6bbd605 100644 --- a/app/javascript/components/NewClientForm/admin.js +++ b/app/javascript/components/NewClientForm/admin.js @@ -28,7 +28,6 @@ export default (props) => {
- DADOU Date: Wed, 20 Jan 2021 10:09:57 +0700 Subject: [PATCH 0026/1114] updated staging ip address in cap staging config --- .gitignore | 3 +++ config/deploy/staging.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0389887173..dabb211b19 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ NOTES.md REVISION GIT_REVISION sorbet + +## AWS +aws/ecs/private/* \ No newline at end of file diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 271747ec3b..284d771b34 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -10,7 +10,7 @@ set :stage, 'staging' set :branch, proc { `git rev-parse --abbrev-ref staging`.chomp } -server '52.220.217.164', user: 'deployer', roles: %w{app web db} +server '3.0.131.11', user: 'deployer', roles: %w{app web db} # role-based syntax # ================== From 5eeee904de6434914e0c44d8a52e35627ca547b4 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 21 Jan 2021 14:06:26 +0700 Subject: [PATCH 0027/1114] removed log limit --- config/environments/staging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 2fb466758b..6a0f462d95 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -62,7 +62,7 @@ # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - config.logger = ActiveSupport::Logger.new(config.paths['log'].first, 1, 50 * 1024 * 1024) + # Use a different cache store in production. # config.cache_store = :mem_cache_store # Ignore bad email addresses and do not raise email delivery errors. From ab43e6697ab2ddd240baf808d05e085dd8af97ad Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 27 Jan 2021 16:25:51 +0700 Subject: [PATCH 0028/1114] removed log retation line from config/environments/production.rb --- config/environments/production.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 978318e8dc..3431ab73cc 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -75,7 +75,6 @@ # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - config.logger = ActiveSupport::Logger.new(config.paths['log'].first, 1, 50 * 1024 * 1024) # Use a different cache store in production. # config.cache_store = :mem_cache_store From a7772bd8ce20a13b474bc28d523ca4d5bf8dab6b Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 27 Jan 2021 16:39:44 +0700 Subject: [PATCH 0029/1114] removed log retation line from config/environments/development.rb --- config/environments/development.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index b62173f155..f1d1fdffdd 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -32,7 +32,6 @@ # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - config.logger = ActiveSupport::Logger.new(config.paths['log'].first, 1, 50 * 1024 * 1024) # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load From 3d4bbfa7ce16abbc92397de9eade14562a4da73f Mon Sep 17 00:00:00 2001 From: Darren Jensen Date: Wed, 27 Jan 2021 20:03:58 +0700 Subject: [PATCH 0030/1114] added staging rails logger to STDOUT --- config/environments/staging.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 6a0f462d95..477fb96722 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -62,6 +62,8 @@ # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + # DO NOT REMOVE THIS LOGGER :) + config.logger = Logger.new(STDOUT) # Use a different cache store in production. # config.cache_store = :mem_cache_store From d6029395c5580499239bd560f3d74d33718faebf Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 19 Feb 2021 13:40:24 +0700 Subject: [PATCH 0031/1114] removed duplicated add form buttons in family show --- app/views/families/show.html.haml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index c2933fdc9e..c6e4a0a4b9 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -89,16 +89,6 @@ - ngos.each do |value, key| %li= link_to value, new_family_family_referral_url(@family, ngo: key, external_ngo_name: key == 'external referral' && value), :class => 'target-ngo', :value => "#{key}", :id => "#{key}" - - if can? :manage, CustomFieldProperty - .btn-group.small-btn-margin - %button.btn-sm.btn.btn-success.dropdown-toggle.btn-fit{ data: { toggle: "dropdown", trigger: 'hover', html: 'true', content: "#{I18n.t('inline_help.families.show.add_form')}", placement: "auto" }, class: ('disabled' if @free_family_forms.empty?) } - = t('.add_form') - %span.caret - %ul.dropdown-menu.scrollable-dropdown-menu - - @free_family_forms.each do |custom_field| - %li - %p= link_to custom_field.form_title, new_family_custom_field_property_path(@family, custom_field_id: custom_field) - = link_to family_tasks_path(@family), data: { toggle: "popover", trigger: "hover", content: "#{I18n.t('inline_help.clients.show.tasks')}", placement: "auto" } do %span.btn-sm.btn.btn-info.small-btn-margin.btn-fit %strong.count-margin= @family.tasks.incomplete.count From 2269f93a92028fe7603eb26c9c8a27ab568daeb9 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 26 Feb 2021 16:06:08 +0700 Subject: [PATCH 0032/1114] ran migration for staging --- db/schema.rb | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 4fc016f436..b18c9a5e03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -105,8 +105,9 @@ create_table "ar_internal_metadata", primary_key: "key", force: :cascade do |t| t.string "value" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "environment" end create_table "assessment_domains", force: :cascade do |t| @@ -697,9 +698,9 @@ t.string "other_agency_name" t.string "other_representative_name" t.string "other_agency_phone" + t.string "locality" t.string "national_id_number" t.string "passport_number" - t.string "locality" end add_index "clients", ["commune_id"], name: "index_clients_on_commune_id", using: :btree @@ -2005,7 +2006,7 @@ create_table "tasks", force: :cascade do |t| t.string "name", default: "" - t.date "expected_date" + t.date "completion_date" t.datetime "remind_at" t.boolean "completed", default: false t.integer "user_id" @@ -2021,7 +2022,7 @@ t.datetime "deleted_at" t.integer "goal_id" t.integer "family_id" - t.datetime "completion_date" + t.datetime "expected_date" t.string "domain_group_identity" end @@ -2032,10 +2033,6 @@ add_index "tasks", ["goal_id"], name: "index_tasks_on_goal_id", using: :btree add_index "tasks", ["taskable_type", "taskable_id"], name: "index_tasks_on_taskable_type_and_taskable_id", using: :btree - create_table "test_tables", force: :cascade do |t| - t.string "test" - end - create_table "thredded_categories", force: :cascade do |t| t.integer "messageboard_id", null: false t.string "name", limit: 191, null: false @@ -2052,22 +2049,24 @@ t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "position", null: false end + add_index "thredded_messageboard_groups", ["name"], name: "index_thredded_messageboard_group_on_name", unique: true, using: :btree + create_table "thredded_messageboards", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", limit: 191, null: false t.string "slug", limit: 191 t.text "description" t.integer "topics_count", default: 0 t.integer "posts_count", default: 0 - t.boolean "closed", default: false, null: false t.integer "last_topic_id" t.integer "messageboard_group_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.integer "position", null: false end - add_index "thredded_messageboards", ["closed"], name: "index_thredded_messageboards_on_closed", using: :btree add_index "thredded_messageboards", ["messageboard_group_id"], name: "index_thredded_messageboards_on_messageboard_group_id", using: :btree add_index "thredded_messageboards", ["slug"], name: "index_thredded_messageboards_on_slug", using: :btree @@ -2130,6 +2129,7 @@ t.string "hash_id", limit: 191, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "last_post_at" end add_index "thredded_private_topics", ["hash_id"], name: "index_thredded_private_topics_on_hash_id", using: :btree @@ -2167,6 +2167,7 @@ t.integer "moderation_state", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "last_post_at" end add_index "thredded_topics", ["hash_id"], name: "index_thredded_topics_on_hash_id", using: :btree @@ -2192,21 +2193,23 @@ add_index "thredded_user_details", ["user_id"], name: "index_thredded_user_details_on_user_id", using: :btree create_table "thredded_user_messageboard_preferences", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "messageboard_id", null: false - t.boolean "notify_on_mention", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", null: false + t.integer "messageboard_id", null: false + t.boolean "follow_topics_on_mention", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "followed_topic_emails", default: true, null: false end add_index "thredded_user_messageboard_preferences", ["user_id", "messageboard_id"], name: "thredded_user_messageboard_preferences_user_id_messageboard_id", unique: true, using: :btree create_table "thredded_user_preferences", force: :cascade do |t| - t.integer "user_id", null: false - t.boolean "notify_on_mention", default: true, null: false - t.boolean "notify_on_message", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", null: false + t.boolean "follow_topics_on_mention", default: true, null: false + t.boolean "notify_on_message", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "followed_topic_emails", default: true, null: false end add_index "thredded_user_preferences", ["user_id"], name: "index_thredded_user_preferences_on_user_id", using: :btree From 51f8e3eece71284b52d7cbc00b5abf2d03161d38 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 1 Mar 2021 09:13:14 +0700 Subject: [PATCH 0033/1114] added memcached in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2c5b557b12..d4384d2193 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ruby:2.3.3 -RUN apt-get update -qq && apt-get install -y nodejs postgresql-client vim +RUN apt-get update -qq && apt-get install -y nodejs postgresql-client memcached vim RUN mkdir /app WORKDIR /app From 1e12b86a8e8c72eebacc839965276f4c034eed9b Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 2 Mar 2021 14:32:51 +0700 Subject: [PATCH 0034/1114] reverted the config file in staging --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 8b6bab6f99..35e3e9c3b6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,7 +26,7 @@ class Application < Rails::Application # Autoload path config.enable_dependency_loading = true config.autoload_paths << "#{Rails.root}/lib" - config.autoload_paths += Dir[Rails.root.join('app/classes/**/*')] + config.autoload_paths << Rails.root.join('app/classes/**/*') # Override rails template engine: erb to haml config.generators do |g| From 0435624e335f10f609b1f7c2c93ee2d33c52bf07 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 2 Mar 2021 15:34:49 +0700 Subject: [PATCH 0035/1114] removed session redis --- Gemfile | 2 -- Gemfile.lock | 21 --------------------- config/initializers/session_store.rb | 11 +---------- 3 files changed, 1 insertion(+), 33 deletions(-) diff --git a/Gemfile b/Gemfile index 5ea056e05a..1b5d947ec0 100644 --- a/Gemfile +++ b/Gemfile @@ -77,8 +77,6 @@ gem 'globalize', '~> 5.1.0' gem 'enumerize', '~> 2.3.1' gem 'ulid', '~> 1.2' gem 'aws-healthcheck' -gem 'redis-rails', '~> 5.0', '>= 5.0.2' -gem 'redis-session-store', '~> 0.11.3' group :development, :test do gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index 1900bdc929..d3c1a0cbd1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -654,25 +654,6 @@ GEM railties (>= 3.2) tilt redis (3.2.2) - redis-actionpack (5.1.0) - actionpack (>= 4.0, < 7) - redis-rack (>= 1, < 3) - redis-store (>= 1.1.0, < 2) - redis-activesupport (5.2.0) - activesupport (>= 3, < 7) - redis-store (>= 1.3, < 2) - redis-rack (2.0.6) - rack (>= 1.5, < 3) - redis-store (>= 1.2, < 2) - redis-rails (5.0.2) - redis-actionpack (>= 5.0, < 6) - redis-activesupport (>= 5.0, < 6) - redis-store (>= 1.2, < 2) - redis-session-store (0.11.3) - actionpack (>= 3, < 7) - redis (>= 3, < 5) - redis-store (1.6.0) - redis (>= 2.2, < 5) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) @@ -945,8 +926,6 @@ DEPENDENCIES rails (= 4.2.2) rails-erd react-rails (~> 2.6.0) - redis-rails (~> 5.0, >= 5.0.2) - redis-session-store (~> 0.11.3) roo (~> 2.2) rspec-activemodel-mocks (~> 1.1) rspec-rails (~> 4.0.0) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 96db348bb4..0ec2adf1a4 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,13 +1,4 @@ # Be sure to restart your server when you modify this file. -# Rails.application.config.session_store :cookie_store, key: '_cif-web_session' +Rails.application.config.session_store :cookie_store, key: '_cif-web_session' # Rails.application.config.session_store :mem_cache_store, key: "_cif-web_session-#{Rails.env}" -Rails.application.config.session_store :redis_session_store, { - key: "_cif-web_session-#{Rails.env}", - redis: { - expire_after: 120.minutes, # cookie expiration - ttl: 120.minutes, # Redis expiration, defaults to 'expire_after' - key_prefix: 'cif:session:', - url: "#{ENV['REDIS_URL']}/0", - } -} From f01f44b93a7ed10dc07c5c6c55bc11e2e8e179e7 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 2 Mar 2021 16:17:09 +0700 Subject: [PATCH 0036/1114] removed family field file duplicated --- .../advanced_searches/family_fields.rb | 65 ------------------- 1 file changed, 65 deletions(-) delete mode 100644 app/classes/advanced_searches/family_fields.rb diff --git a/app/classes/advanced_searches/family_fields.rb b/app/classes/advanced_searches/family_fields.rb deleted file mode 100644 index 7b8b66a952..0000000000 --- a/app/classes/advanced_searches/family_fields.rb +++ /dev/null @@ -1,65 +0,0 @@ -module AdvancedSearches - class FamilyFields - include AdvancedSearchHelper - - def render - group = family_header('basic_fields') - number_fields = number_type_list.map { |item| AdvancedSearches::FilterTypes.number_options(item, family_header(item), group) } - text_fields = text_type_list.map { |item| AdvancedSearches::FilterTypes.text_options(item, family_header(item), group) } - date_picker_fields = date_type_list.map { |item| AdvancedSearches::FilterTypes.date_picker_options(item, family_header(item), group) } - drop_list_fields = drop_down_type_list.map { |item| AdvancedSearches::FilterTypes.drop_list_options(item.first, family_header(item.first), item.last, group) } - - search_fields = text_fields + drop_list_fields + number_fields + date_picker_fields - - search_fields.sort_by { |f| f[:label].downcase }.select do |field| - field_name = field[:id] - field_name = 'member_count' if field_name.to_s.include?('significant_family_member_count') - policy(Family).show?(field_name.to_sym) - end - end - - private - - def current_user - @pundit_user - end - - def number_type_list - ['significant_family_member_count', 'household_income', 'female_children_count', 'male_children_count', 'female_adult_count', 'male_adult_count'] - end - - def text_type_list - ['code', 'name', 'address', 'caregiver_information', 'case_history'] - end - - def date_type_list - ['contract_date', 'active_families'] - end - - def drop_down_type_list - [ - ['family_type', family_type_options], - ['status', status_options], - ['province_id', provinces], - ['dependable_income', { yes: 'Yes', no: 'No' }], - ['client_id', clients] - ] - end - - def family_type_options - Family.mapping_family_type_translation.to_h - end - - def status_options - Family::STATUSES - end - - def provinces - Family.joins(:province).pluck('provinces.name', 'provinces.id').uniq.sort.map{|s| {s[1].to_s => s[0]}} - end - - def clients - Client.joins(:families).order('lower(clients.given_name)').pluck('clients.given_name, clients.family_name, clients.id').uniq.map{|s| { s[2].to_s => "#{s[0]} #{s[1]}" } } - end - end -end From 5b3d192dd726385009307b52780b9332163d0e99 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 2 Mar 2021 16:52:22 +0700 Subject: [PATCH 0037/1114] removed html tag from family datagrid field :anme --- app/grids/family_grid.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/grids/family_grid.rb b/app/grids/family_grid.rb index ad8c28b695..d284e5507c 100644 --- a/app/grids/family_grid.rb +++ b/app/grids/family_grid.rb @@ -203,18 +203,18 @@ def filer_section(filter_name) column(:code, header: -> { I18n.t('datagrid.columns.families.code') }) - column(:name, html: true, order: 'LOWER(name)', header: -> { I18n.t('datagrid.columns.families.name') }) do |object| - link_to entity_name(object), family_path(object) + column(:name, order: 'LOWER(name)', header: -> { I18n.t('datagrid.columns.families.name') }) do |object| + format(object.name) do |value| + link_to entity_name(object), family_path(object) if value.present? + end end column(:name_en, order: 'LOWER(name_en)', header: -> { I18n.t('datagrid.columns.families.name_en') }) do |object| - format(object.name_en) do |value| link_to value, family_path(object) if value.present? end end - column(:name, html: false, header: -> { I18n.t('datagrid.columns.families.name') }) column(:family_type, header: -> { I18n.t('datagrid.columns.families.family_type') }) do |object| object.family_type From a7c710bd06329d1b58401dc06c3206e2a0f06aad Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 2 Mar 2021 17:25:53 +0700 Subject: [PATCH 0038/1114] removed library memcached from Dockerfile --- Dockerfile | 2 +- app/grids/family_grid.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index d4384d2193..2c5b557b12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ruby:2.3.3 -RUN apt-get update -qq && apt-get install -y nodejs postgresql-client memcached vim +RUN apt-get update -qq && apt-get install -y nodejs postgresql-client vim RUN mkdir /app WORKDIR /app diff --git a/app/grids/family_grid.rb b/app/grids/family_grid.rb index ad8c28b695..d284e5507c 100644 --- a/app/grids/family_grid.rb +++ b/app/grids/family_grid.rb @@ -203,18 +203,18 @@ def filer_section(filter_name) column(:code, header: -> { I18n.t('datagrid.columns.families.code') }) - column(:name, html: true, order: 'LOWER(name)', header: -> { I18n.t('datagrid.columns.families.name') }) do |object| - link_to entity_name(object), family_path(object) + column(:name, order: 'LOWER(name)', header: -> { I18n.t('datagrid.columns.families.name') }) do |object| + format(object.name) do |value| + link_to entity_name(object), family_path(object) if value.present? + end end column(:name_en, order: 'LOWER(name_en)', header: -> { I18n.t('datagrid.columns.families.name_en') }) do |object| - format(object.name_en) do |value| link_to value, family_path(object) if value.present? end end - column(:name, html: false, header: -> { I18n.t('datagrid.columns.families.name') }) column(:family_type, header: -> { I18n.t('datagrid.columns.families.family_type') }) do |object| object.family_type From eefdec3921a6a2e3b07f1e5cdba5b34fb1e20188 Mon Sep 17 00:00:00 2001 From: JaneSoo Date: Tue, 2 Mar 2021 21:47:42 +0100 Subject: [PATCH 0039/1114] add missing family header counter and column --- app/helpers/families_helper.rb | 1 + app/views/datagrid/_head.html.haml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb index c50460172d..864b323b39 100644 --- a/app/helpers/families_helper.rb +++ b/app/helpers/families_helper.rb @@ -121,6 +121,7 @@ def map_family_field_labels program_streams: I18n.t('datagrid.columns.families.program_streams'), program_enrollment_date: I18n.t('datagrid.columns.clients.program_enrollment_date'), program_exit_date: I18n.t('datagrid.columns.clients.program_exit_date'), + direct_beneficiaries: I18n.t('datagrid.columns.families.direct_beneficiaries'), **additional_columns } end diff --git a/app/views/datagrid/_head.html.haml b/app/views/datagrid/_head.html.haml index a7b5032b7e..64a6536696 100644 --- a/app/views/datagrid/_head.html.haml +++ b/app/views/datagrid/_head.html.haml @@ -11,6 +11,8 @@ %th{ class: datagrid_column_classes(grid, column), title: column.header, data: { header: header_classes(grid, column).parameterize } } - if grid.instance_of?(ClientGrid) = header_counter(grid, column) + - elsif grid.instance_of?(FamilyGrid) + = family_header_counter(grid, column) - else = column.header.truncate(65) = datagrid_order_for(grid, column, options) if column.supports_order? && options[:order] From 36815d9e5e4573213f71ec10493874483478177e Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 5 Mar 2021 15:33:31 +0700 Subject: [PATCH 0040/1114] merged with 2021030502 --- app/views/datagrid/_form.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/datagrid/_form.html.haml b/app/views/datagrid/_form.html.haml index 6960b72f4d..49872403b3 100644 --- a/app/views/datagrid/_form.html.haml +++ b/app/views/datagrid/_form.html.haml @@ -383,7 +383,8 @@ .visibility.col-sm-12 = check_box_tag 'direct_beneficiaries_', 'direct_beneficiaries', checked = default_setting('direct_beneficiaries_', @default_columns) || params[:direct_beneficiaries_].present?, class: 'i-checks' = label_tag 'direct_beneficiaries_', t('datagrid.columns.families.direct_beneficiaries') - %li.divider.col-xs-12 + + %li.divider.col-xs-12 %li .visibility.col-sm-12 = check_box_tag 'manage_', 'manage', checked = default_setting('manage_', @default_columns) || params[:manage_].present?, class: 'i-checks' From 366e3b4cba40b9d8a2c9d7cc8e24058b9ceb17c7 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 10 Mar 2021 10:25:14 +0700 Subject: [PATCH 0041/1114] fixed family buttons display on family form enabled --- app/views/families/show.html.haml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/views/families/show.html.haml b/app/views/families/show.html.haml index 2fe17508ed..ecf869c8e0 100644 --- a/app/views/families/show.html.haml +++ b/app/views/families/show.html.haml @@ -108,6 +108,15 @@ %span.btn-sm.btn.btn-info.small-btn-margin.btn-fit %strong.count-margin= @family.case_notes.count = t('clients.form.case_note') + - if @family.enrollments.active.count > 0 + = link_to family_enrolled_programs_path(@family), data: { toggle: "popover", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } do + .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.enrolled_program_streams') + - else + .btn.btn-primary.small-btn-margin.btn-sm.disabled.btn-fit{ data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } } + = t('.enrolled_program_streams') + = link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do + .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') + - if !@family.referred? .btn-group.small-btn-margin @@ -128,15 +137,6 @@ %li %p= link_to custom_field.form_title, new_family_custom_field_property_path(@family, custom_field_id: custom_field) - - if @family.enrollments.active.count > 0 - = link_to family_enrolled_programs_path(@family), data: { toggle: "popover", html: 'true', trigger: 'hover', content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } do - .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.enrolled_program_streams') - - else - .btn.btn-primary.small-btn-margin.btn-sm.disabled.btn-fit{ data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.active_program')}", placement: "auto" } } - = t('.enrolled_program_streams') - = link_to family_enrollments_path(@family), data: { toggle: "popover", html: 'true', trigger: "hover", content: "#{I18n.t('inline_help.families.show.enroll_program')}", placement: "auto" } do - .btn.btn-primary.small-btn-margin.btn-sm.btn-fit= t('.program_streams') - .ibox.mini-margin .ibox-title %h5= "#{t('.general_info')} #{@family.name}" From 23ba376b308a607878eacd8911d962319df11f2c Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 25 Mar 2021 15:15:23 +0700 Subject: [PATCH 0042/1114] added functionality remove referee after remove client --- app/controllers/clients_controller.rb | 1 + app/models/client.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 2a0ec57699..d888682296 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -179,6 +179,7 @@ def destroy @client.cases.delete_all @client.tasks.update_all(client_id: nil) @client.tasks.destroy_all + @client.case_worker_clients.each(&:destroy_fully!) @client.reload.destroy end diff --git a/app/models/client.rb b/app/models/client.rb index 388ba814c2..60e5a88b76 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -120,6 +120,7 @@ class Client < ActiveRecord::Base after_commit :remove_family_from_case_worker after_commit :update_related_family_member, on: :update + after_destroy :remove_referee 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}%"}) } @@ -829,4 +830,8 @@ def remove_tasks(case_worker) case_worker.tasks.incomplete.destroy_all end end + + def remove_referee + referee.destroy + end end From af3a307a85bb40a6de1bb43aaa0011b5c38c021a Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 8 Apr 2021 10:56:43 +0700 Subject: [PATCH 0043/1114] fixed save client slug validation --- app/controllers/api/clients_controller.rb | 1 - app/models/client.rb | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index 00829e6ac9..3b032030c6 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -49,7 +49,6 @@ def create client.carer_id = carer.id client_saved = client.save end - if client_saved render json: { slug: client.slug, id: client.id }, status: :ok else diff --git a/app/models/client.rb b/app/models/client.rb index f69e275e07..2e76580796 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -112,6 +112,7 @@ class Client < ActiveRecord::Base validates :global_id, presence: true validates_uniqueness_of :global_id, on: :create + after_initialize :set_slug before_validation :assign_global_id, on: :create before_create :set_country_origin before_update :disconnect_client_user_relation, if: :exiting_ngo? @@ -723,6 +724,12 @@ def indirect_beneficiaries private + def set_slug + short_name = Apartment::Tenant.current + self.slug = generate_random_char + Apartment::Tenant.switch short_name + end + def update_related_family_member FamilyMember.delay.update_client_relevant_data(family_member.id, Apartment::Tenant.current) if family_member.present? && family_member.persisted? end From 2ba1dbbb26351954948b342df6931f10633c8b84 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 8 Apr 2021 14:02:46 +0700 Subject: [PATCH 0044/1114] reverted back the set default slug --- app/models/client.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/models/client.rb b/app/models/client.rb index 2e76580796..f69e275e07 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -112,7 +112,6 @@ class Client < ActiveRecord::Base validates :global_id, presence: true validates_uniqueness_of :global_id, on: :create - after_initialize :set_slug before_validation :assign_global_id, on: :create before_create :set_country_origin before_update :disconnect_client_user_relation, if: :exiting_ngo? @@ -724,12 +723,6 @@ def indirect_beneficiaries private - def set_slug - short_name = Apartment::Tenant.current - self.slug = generate_random_char - Apartment::Tenant.switch short_name - end - def update_related_family_member FamilyMember.delay.update_client_relevant_data(family_member.id, Apartment::Tenant.current) if family_member.present? && family_member.persisted? end From a2ec9adf6a708a07e01d088bdf9d986124d4ee3e Mon Sep 17 00:00:00 2001 From: Darren Jensen Date: Mon, 12 Apr 2021 08:32:00 +0700 Subject: [PATCH 0045/1114] update deploy script readme --- aws/ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/ecs/README.md b/aws/ecs/README.md index 8b616e295e..f9bffd5646 100644 --- a/aws/ecs/README.md +++ b/aws/ecs/README.md @@ -9,7 +9,7 @@ If your interested in how this is setup, there is a tutorial on the AWS website There are two steps: 1. Run `cap staging deploy` as usual (if using Docker start the container first `docker run -v ~/.ssh:/root/.ssh -it --entrypoint bash oscar-staging:latest`). -1. SSH into the staging build server, cd into the `current` folder and run `./aws/ecs/deploy-oscar-staging-ecs.bash`. +1. SSH into the staging build server, cd into the `current` folder and run `./aws/ecs/deploy-oscar-ecs.bash`. ## (Optional) check the contents of the image on the server From c134ec7dcc327fdd0c044d55cb2aaeb11c6c3e26 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 14 May 2021 01:04:43 +0700 Subject: [PATCH 0046/1114] merged with 2021051321 --- app/views/assessments/_assessment_lists.haml | 3 +-- app/views/assessments/_ratanak_assessments.haml | 3 +-- app/views/assessments/index.html.haml | 1 - app/views/assessments/show.html.haml | 3 +-- app/views/{assessments => care_plans}/_incomplete_detail.haml | 0 app/views/care_plans/index.html.haml | 3 ++- app/views/care_plans/show.html.haml | 3 ++- 7 files changed, 7 insertions(+), 9 deletions(-) rename app/views/{assessments => care_plans}/_incomplete_detail.haml (100%) diff --git a/app/views/assessments/_assessment_lists.haml b/app/views/assessments/_assessment_lists.haml index 6e2f889b6a..5986e89cea 100644 --- a/app/views/assessments/_assessment_lists.haml +++ b/app/views/assessments/_assessment_lists.haml @@ -8,8 +8,7 @@ = date_format(assessment.created_at) = "(#{assessment.domains.last.custom_assessment_setting.custom_assessment_name})" if assessment.domains.last&.custom_assessment_setting.present? - = render partial: 'incomplete_detail', locals: { assessment: assessment } - %label{ class: "cursor-pointer #{assessment.completed_label_class}", data: { toggle: :modal, target: "#modal-incomplete-assessment-#{assessment.id}" } } + %label{ class: "cursor-pointer #{assessment.completed_label_class}" } = assessment.completed_status .col-xs-12.col-sm-4 diff --git a/app/views/assessments/_ratanak_assessments.haml b/app/views/assessments/_ratanak_assessments.haml index 53adf3c923..a0a6862843 100644 --- a/app/views/assessments/_ratanak_assessments.haml +++ b/app/views/assessments/_ratanak_assessments.haml @@ -10,8 +10,7 @@ = t('assessments.index.assessment_created_on', assessment: t('clients.show.assessment')) %b = date_format(assessment.created_at) - = render partial: 'incomplete_detail', locals: { assessment: assessment } - %label{ class: "cursor-pointer #{assessment.completed? ? 'label label-primary' : 'label label-danger'}", data: { toggle: :modal, target: "#modal-incomplete-assessment-#{assessment.id}" } }= "#{assessment.completed? ? 'Completed' : 'Incompleted'}" + %label{ class: "cursor-pointer #{assessment.completed? ? 'label label-primary' : 'label label-danger'}" }= "#{assessment.completed? ? 'Completed' : 'Incompleted'}" .col-xs-12.col-sm-4 - if assessment.present? .btn.btn-block.btn-info.button{ class: ('disabled' unless case_conference_readable?) } diff --git a/app/views/assessments/index.html.haml b/app/views/assessments/index.html.haml index cfaae68456..bafc3faf74 100644 --- a/app/views/assessments/index.html.haml +++ b/app/views/assessments/index.html.haml @@ -16,7 +16,6 @@ - if @current_setting.enable_default_assessment && completed_initial_assessment?('defaults') = render 'new_assessment' - - if @current_setting.enable_custom_assessment && completed_initial_assessment?('customs') .row.margin-row - @custom_assessment_settings.each do |css| diff --git a/app/views/assessments/show.html.haml b/app/views/assessments/show.html.haml index 473d07fc40..443dc625d4 100644 --- a/app/views/assessments/show.html.haml +++ b/app/views/assessments/show.html.haml @@ -16,10 +16,9 @@ .ibox-content .row .col-xs-10 - = render partial: 'incomplete_detail', locals: { assessment: @assessment } %p %b= "#{t('.case_plan')} #{@assessment.client.name}" - %label{ class: "cursor-pointer #{@assessment.completed_label_class}", data: { toggle: :modal, target: "#modal-incomplete-assessment-#{@assessment.id}" } } + %label = @assessment.completed_status %br - if @assessment.index_of == 0 diff --git a/app/views/assessments/_incomplete_detail.haml b/app/views/care_plans/_incomplete_detail.haml similarity index 100% rename from app/views/assessments/_incomplete_detail.haml rename to app/views/care_plans/_incomplete_detail.haml diff --git a/app/views/care_plans/index.html.haml b/app/views/care_plans/index.html.haml index b14faf0740..aa9eb60d7a 100644 --- a/app/views/care_plans/index.html.haml +++ b/app/views/care_plans/index.html.haml @@ -22,13 +22,14 @@ = t('.care_plan_created_on', care_plan: t('clients.care_plan')) %b = date_format(assessment.care_plan.created_at) - %span{class: care_plan_label(assessment.care_plan)}= care_plan_status(assessment.care_plan) + %span{ class: "cursor-pointer #{care_plan_label(assessment.care_plan)}", data: { toggle: :modal, target: "#modal-incomplete-assessment-#{assessment.id}" } }= care_plan_status(assessment.care_plan) .col-xs-12.col-sm-4 .btn.btn-block.btn-info.button{ class: ('disabled' unless case_notes_readable?) } - if assessment.care_plan.present? = link_to t('.review_care_plan', care_plan: t('clients.care_plan')), client_care_plan_path(@client, assessment.care_plan.id) - else = link_to t('.new_care_plan', care_plan: t('clients.care_plan')), new_client_care_plan_path(@client, :assessment => assessment.id) + = render partial: 'incomplete_detail', locals: { assessment: assessment } .ibox-footer .row diff --git a/app/views/care_plans/show.html.haml b/app/views/care_plans/show.html.haml index b948578864..c8551e84cf 100644 --- a/app/views/care_plans/show.html.haml +++ b/app/views/care_plans/show.html.haml @@ -15,6 +15,7 @@ .col-xs-10 %p %b= "#{t('.care_plan', care_plan: t('clients.care_plan'))} #{@care_plan.client.name}" + %span{ class: "cursor-pointer #{care_plan_label(@care_plan)}", data: { toggle: :modal, target: "#modal-incomplete-assessment-#{@care_plan.assessment.id}" } }= care_plan_status(@care_plan) %br = "#{t('.care_plan_created_by')} #{whodunnit('CarePlan', @care_plan.id)} #{t('.on_date')} #{date_format(@care_plan.created_at)}" .col-xl-1.button-position @@ -81,4 +82,4 @@ .col-md-2.col-xs-12 = date_format(task.completion_date) - + = render partial: 'incomplete_detail', locals: { assessment: @care_plan.assessment } From e5ab1340f8b067b31b12d34b0074b3e99d418374 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 19 May 2021 16:39:46 +0700 Subject: [PATCH 0047/1114] remove conflict line in schema.rb --- db/schema.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 027c74dee9..15f837ade7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -<<<<<<< HEAD -ActiveRecord::Schema.define(version: 20210514142805) do -======= ActiveRecord::Schema.define(version: 20210519061845) do ->>>>>>> 2021051023-add-case-conference-ratanak # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From ddf957532e2e90e4c970f9ff5ce90ad6f8b6394b Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 20 May 2021 10:37:43 +0700 Subject: [PATCH 0048/1114] removed staging log to stdout in config/environments/staging.rb --- config/environments/staging.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 477fb96722..49f6c87c6e 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -63,7 +63,6 @@ # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # DO NOT REMOVE THIS LOGGER :) - config.logger = Logger.new(STDOUT) # Use a different cache store in production. # config.cache_store = :mem_cache_store From cddfddb0db9f70a5532ec2c100bd0317b4bdc920 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 24 May 2021 10:04:29 +0700 Subject: [PATCH 0049/1114] fixed assessment policy edit care plan --- app/policies/care_plan_policy.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/policies/care_plan_policy.rb b/app/policies/care_plan_policy.rb index c4c179a9b8..e426194340 100644 --- a/app/policies/care_plan_policy.rb +++ b/app/policies/care_plan_policy.rb @@ -2,13 +2,13 @@ class CarePlanPolicy < ApplicationPolicy def index? Setting.first.enable_default_assessment || Setting.first.enable_custom_assessment end - + def show? enable_assessment = record.default? ? Setting.first.enable_default_assessment : Setting.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 @@ -22,17 +22,16 @@ def new?(value='', custom_assessment=nil) enable_assessment && editable_user && !record.client.exit_ngo? && record.client.can_create_assessment?(record.default, value) end end - + def edit? return false if user.strategic_overviewer? setting = Setting.first - enable_assessment = record.default? ? setting.enable_default_assessment? : setting.enable_custom_assessment? + 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 + editable_user = user.admin? ? true : user.permission&.assessments_editable enable_assessment && (editable_user && !record.client.exit_ngo? && Date.current <= record.created_at + 6.days || user.admin?) end - + alias create? new? alias update? edit? end - \ No newline at end of file From b7f9bdd01aac490d39c4403d4e4bf4cf7fc6fe8b Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 27 May 2021 14:02:54 +0700 Subject: [PATCH 0050/1114] worked on added new fields to fields setting --- db/support/field_settings.xlsx | Bin 46028 -> 57404 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/db/support/field_settings.xlsx b/db/support/field_settings.xlsx index e30a6827be7a0e5415ecb4878a750997637af138..ef168e0462cdb07a4d8f65e7c8fe33a6a45919b1 100644 GIT binary patch literal 57404 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 Date: Tue, 8 Jun 2021 11:30:18 +0700 Subject: [PATCH 0051/1114] fixed legal doc translation --- config/locales/en.yml | 25 ++++++++++++++++++++++++- config/locales/km.yml | 1 + config/locales/my.yml | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 65717f5368..482c34d387 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1536,7 +1536,28 @@ en: update: successfully_updated: Enrollment has been successfully updated. clients: - care_plan: Care plan + attr: &LEGAL_DOC_ATTR + complain: Complaint + detail_form_of_judicial_police: Detailed Form for Identification of Victim of Human Trafficking (Judicial Police) + detail_form_of_mosavy_dosavy: Detailed Form for Identification of Victim of Human Trafficking (MoSAVY/DoSAVY) + dosavy: DoSAVY + form_indentification: Forms for Identification of Victim of Human Trafficking + indentification_doc: Identification Documents + labor_trafficking_legal_doc_option: Labour Trafficking + legal_processing_doc: Legal Proceeding Documents + mosavy: MoSAVY + msdhs: MSDHS + ngo_partner: NGO Partner + other_legal_doc_option: Other + referral_document: Referral Documents + sex_trafficking_legal_doc_option: Sexual Trafficking + short_form_of_judicial_police: Short Interview Form for Identification of Victim of Human Trafficking (Judicial Police) + short_form_of_mosavy_dosavy: Short Interview Form for Identification of Victim of Human Trafficking (MoSAVY/DoSAVY) + short_form_of_ocdm: Short Interview Form for Identification of Victim of Human Trafficking (Officials of the Cambodian Diplomatic Mission) + temp_travel_doc: Temporary Travel Documents + travel_doc: Laissez-Passer + verdict: Verdict + warrant: Warran actions: are_you_sure: Are you sure you want to delete? address_forms: @@ -1598,6 +1619,7 @@ en: referral_reasons: Referral Reasons referred_from: Referred from External Organisation referred_to: Referred to External Organisation + care_plan: Care plan client_advanced_searches: advanced_search: add_custom_group: Add Assessment Filter @@ -1743,6 +1765,7 @@ en: reject_client: Reject Client rejected_note: Rejected Note form: + <<: *LEGAL_DOC_ATTR national_id_number: National ID Number passport_number: Passport Number neighbor_name: "Neighbor/Friend: Name" diff --git a/config/locales/km.yml b/config/locales/km.yml index 5ec55f6ae0..df794a3b71 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -1596,6 +1596,7 @@ km: export_to_xls: แž‘แžถแž‰แž…แŸแž‰แž‡แžถแžฏแž€แžŸแžถแžšแž”แŸ’แžšแž—แŸแž‘ XLS book: book: แž”แžŽแŸ’แžแžปแŸ†แž–แŸแžแŸŒแž˜แžถแž“แžขแžแžทแžแžทแž‡แž“ + care_plan: แž‚แž˜แŸ’แžšแŸ„แž„แžšแž€แŸ’แžŸแžถ case_history_detail: accepted_date: แž€แžถแž›แž”แžšแžทแž…แŸ’แž†แŸแž‘แž‘แž‘แžฝแž›แž™แž€แž…แžผแž›แžขแž„แŸ’แž‚แž€แžถแžš client: แžˆแŸ’แž˜แŸ„แŸ‡แžขแžแžทแžแžทแž‡แž“ diff --git a/config/locales/my.yml b/config/locales/my.yml index 9003483d86..9da602433b 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1592,6 +1592,7 @@ my: export_to_xls: XLS แ€กแ€ปแ€–แ€…แ€น แ€‘แ€ฏแ€แ€นแ€›แ€”แ€น book: book: Client Book + care_plan: Care plan case_history_detail: accepted_date: แ€œแ€€แ€บแ€แ€ถแ€แ€ฒแ€ทแ€žแ€Šแ€บแ€”แ€ฑแ€ทแ€…แ€ฝแ€ฒ client: แ€€แ€ฑแ€œแ€ธแ€”แ€ฌแ€™แ€Šแ€น From 6b850abe445fc3a1fcf879c460d124acfb89bd23 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 10:54:57 +0700 Subject: [PATCH 0052/1114] added rake task to correct siemreap province --- lib/tasks/siemreap_province.rake | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/tasks/siemreap_province.rake diff --git a/lib/tasks/siemreap_province.rake b/lib/tasks/siemreap_province.rake new file mode 100644 index 0000000000..0ed23f0cc2 --- /dev/null +++ b/lib/tasks/siemreap_province.rake @@ -0,0 +1,17 @@ +namespace :siemreap_province do + desc "Correct Seam Reap to Siemreap" + task correct: :environment do + Organization.without_shared.pluck(:short_name) do |short_name| + Apartment::Tenant.switch short_nanme + wrong_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").id + correct_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž” / Siemreap").id + if wrong_siem_reap_id + Family.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) + Community.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) + Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").destroy + end + end + puts "Done !!!!!" + end + +end From 293f19dae72e362521a6d828dfd727be9b1b4a84 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 10:55:08 +0700 Subject: [PATCH 0053/1114] added rake task to correct siemreap province --- lib/tasks/client_siemreap_birth_province.rake | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 lib/tasks/client_siemreap_birth_province.rake diff --git a/lib/tasks/client_siemreap_birth_province.rake b/lib/tasks/client_siemreap_birth_province.rake deleted file mode 100644 index 77aa841e81..0000000000 --- a/lib/tasks/client_siemreap_birth_province.rake +++ /dev/null @@ -1,19 +0,0 @@ -namespace :client_siemreap_birth_province do - desc "Correct birht province from Seam Reap to Siemreap" - task correct: :environment do - Organization.switch_to 'shared' - wrong_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").id - correct_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž” / Siemreap").id - if wrong_siem_reap_id - SharedClient.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) - Organization.where.not(short_name: 'shared').pluck(:short_name).each do |short_name| - Organization.switch_to short_name - Client.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) - end - Organization.switch_to 'shared' - Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").delete - end - puts "Done !!!!!" - end - -end From b866a2d22d7a92a81dd0e00bc28ba35420c88182 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 11:25:34 +0700 Subject: [PATCH 0054/1114] fixed rake task update Seam Reap province --- lib/tasks/siemreap_province.rake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/tasks/siemreap_province.rake b/lib/tasks/siemreap_province.rake index 0ed23f0cc2..f8ab333d6a 100644 --- a/lib/tasks/siemreap_province.rake +++ b/lib/tasks/siemreap_province.rake @@ -5,13 +5,14 @@ namespace :siemreap_province do Apartment::Tenant.switch short_nanme wrong_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").id correct_siem_reap_id = Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž” / Siemreap").id - if wrong_siem_reap_id - Family.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) - Community.where(birth_province_id: wrong_siem_reap_id).update_all(birth_province_id: correct_siem_reap_id) - Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").destroy + if wrong_siem_reap_id && correct_siem_reap_id + Family.where(province_id: wrong_siem_reap_id).update_all(province_id: correct_siem_reap_id) + Community.where(province_id: wrong_siem_reap_id).update_all(province_id: correct_siem_reap_id) + User.where(province_id: wrong_siem_reap_id).update_all(province_id: correct_siem_reap_id) + Partner.where(province_id: wrong_siem_reap_id).update_all(province_id: correct_siem_reap_id) + Province.find_by(name: "แžŸแŸ€แž˜แžšแžถแž”/Seam Reap").delete end end puts "Done !!!!!" end - end From d8b6b49f89e5d6afc0dfbc1fbea1d6aad2076589 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 12:53:51 +0700 Subject: [PATCH 0055/1114] added field setting with update --- db/support/field_settings.xlsx | Bin 0 -> 62657 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 db/support/field_settings.xlsx diff --git a/db/support/field_settings.xlsx b/db/support/field_settings.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3ba8c0113b8d4fa9684458b0e9ae63c178780be9 GIT binary patch literal 62657 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 literal 0 HcmV?d00001 From 1ecda3049cc5d4be3a1cfb7d71372d02c85f1aa2 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 15:19:49 +0700 Subject: [PATCH 0056/1114] added field settings file --- db/support/field_settings.xlsx | Bin 0 -> 62632 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 db/support/field_settings.xlsx diff --git a/db/support/field_settings.xlsx b/db/support/field_settings.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d027b55381aef942a1d4cac81bd048c7fa73f552 GIT binary patch literal 62632 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 literal 0 HcmV?d00001 From 1fa9f6adef5742939346494ab843d894f7524390 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Jun 2021 18:44:01 +0700 Subject: [PATCH 0057/1114] added field settings --- db/support/field_settings.xlsx | Bin 0 -> 63195 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 db/support/field_settings.xlsx diff --git a/db/support/field_settings.xlsx b/db/support/field_settings.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f5b45daeea8a16e98bc87bca8a82eaf112e991b7 GIT binary patch literal 63195 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 literal 0 HcmV?d00001 From 76a41998dfd44e4cbaa2f52faef5c3f70e8d6997 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 22 Jun 2021 11:22:10 +0700 Subject: [PATCH 0058/1114] commit git hook --- scripts/install-hooks.bash | 2 +- scripts/pre-push.bash | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install-hooks.bash b/scripts/install-hooks.bash index a8611e85b6..3fd67e4e62 100755 --- a/scripts/install-hooks.bash +++ b/scripts/install-hooks.bash @@ -6,5 +6,5 @@ echo "Installing hooks..." # this command creates symlink to our pre-commit script ln -s ../../scripts/pre-commit.bash $GIT_DIR/hooks/pre-commit ln -s ../../scripts/pre-push.bash $GIT_DIR/hooks/pre-push -ln -s ../../scripts/prevent-merging-with-staging-branch.bash $GIT_DIR/hooks/prevent-merging-with-staging-branch +ln -s ../../scripts/post-checkout.bash $GIT_DIR/hooks/post-checkout echo "Done!" diff --git a/scripts/pre-push.bash b/scripts/pre-push.bash index 4893936607..599facb12d 100755 --- a/scripts/pre-push.bash +++ b/scripts/pre-push.bash @@ -2,7 +2,7 @@ echo "Running pre-push hook" #./scripts/run-brakeman.bash -./scripts/run-tests.bash +# ./scripts/run-tests.bash # $? stores exit value of the last command if [ $? -ne 0 ]; then From 680b90d79a0a690325e0c364d4223517aa65e0f1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 22 Jun 2021 11:34:54 +0700 Subject: [PATCH 0059/1114] update git script prevent checkout from staging --- scripts/prevent-merging-with-staging-branch.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prevent-merging-with-staging-branch.bash b/scripts/prevent-merging-with-staging-branch.bash index e14fa8db60..12bf3900c4 100755 --- a/scripts/prevent-merging-with-staging-branch.bash +++ b/scripts/prevent-merging-with-staging-branch.bash @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash getBranchName() { From 7cfaaa0b0ed3bbede5b41878d0d670c2d00f873c Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 14 Jul 2021 21:30:48 +0700 Subject: [PATCH 0060/1114] update is_editable? in case note --- app/models/case_note.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/case_note.rb b/app/models/case_note.rb index 4b0dbf8209..69fac52c5c 100644 --- a/app/models/case_note.rb +++ b/app/models/case_note.rb @@ -88,6 +88,7 @@ def is_editable? setting = Setting.first 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) + created_at >= case_note_edit_limit.send(edit_frequency).ago end private From 66a12f3315850e71e4355b78f663bc3deff9f30e Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 19 Jul 2021 10:48:08 +0700 Subject: [PATCH 0061/1114] added DEV2_EMAIL to tracking email sending --- app/mailers/admin_mailer.rb | 2 +- app/mailers/case_worker_mailer.rb | 3 ++- app/workers/internal_referral_worker.rb | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index fe37018cef..df627ddfef 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -12,6 +12,6 @@ def case_worker_overdue_tasks_notify(admin, case_workers, org_name) @case_workers = case_workers return unless @case_workers.present? && @admin.task_notify - mail(to: @admin.email, subject: 'Case workers Overdue Task', bcc: ENV['DEV_EMAIL']) + mail(to: @admin.email, subject: 'Case workers Overdue Task', bcc: [ENV['DEV_EMAIL'], ENV['DEV2_EMAIL']]) end end diff --git a/app/mailers/case_worker_mailer.rb b/app/mailers/case_worker_mailer.rb index c3a4589425..12fa3edb67 100644 --- a/app/mailers/case_worker_mailer.rb +++ b/app/mailers/case_worker_mailer.rb @@ -10,13 +10,14 @@ def tasks_due_tomorrow_of(user) def overdue_tasks_notify(user, short_name) @user = user + return if @user.nil? || @user&.disable? || @user&.task_notify == false @overdue_tasks = user.tasks.where(client_id: user.clients.active_accepted_status.ids).overdue_incomplete_ordered @short_name = short_name return unless @overdue_tasks.present? - mail(to: @user.email, subject: 'Overdue Tasks') + mail(to: @user.email, subject: 'Overdue Tasks', bcc: ENV['DEV2_EMAIL']) end def notify_upcoming_csi_weekly(client) diff --git a/app/workers/internal_referral_worker.rb b/app/workers/internal_referral_worker.rb index 1f7395915f..460cb78956 100644 --- a/app/workers/internal_referral_worker.rb +++ b/app/workers/internal_referral_worker.rb @@ -1,6 +1,5 @@ class InternalReferralWorker include Sidekiq::Worker - sidekiq_options queue: 'send_email' def perform(*args) Apartment::Tenant.switch 'ratanak' From a8425be3ba85de29d9b485a48bcdf965030e7bf2 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 19 Jul 2021 16:23:31 +0700 Subject: [PATCH 0062/1114] removed order column picker custom/tracking/Enrollment fields --- app/classes/advanced_searches/custom_fields.rb | 2 -- app/classes/advanced_searches/enrollment_fields.rb | 1 - app/classes/advanced_searches/exit_program_fields.rb | 2 -- app/classes/advanced_searches/tracking_fields.rb | 1 - 4 files changed, 6 deletions(-) diff --git a/app/classes/advanced_searches/custom_fields.rb b/app/classes/advanced_searches/custom_fields.rb index 6b2af30364..df0b2a46ad 100644 --- a/app/classes/advanced_searches/custom_fields.rb +++ b/app/classes/advanced_searches/custom_fields.rb @@ -20,8 +20,6 @@ def render drop_list_fields = @drop_down_type_list.map { |item| AdvancedSearches::FilterTypes.drop_list_options(item.first.gsub('"', '&qoute;'), format_label(item.first) , item.last, format_optgroup(item.first)) } results = text_fields + drop_list_fields + number_fields + date_picker_fields - - results.sort_by { |f| f[:label].downcase } end def generate_field_by_type diff --git a/app/classes/advanced_searches/enrollment_fields.rb b/app/classes/advanced_searches/enrollment_fields.rb index 8ff0505207..6301eed0c3 100644 --- a/app/classes/advanced_searches/enrollment_fields.rb +++ b/app/classes/advanced_searches/enrollment_fields.rb @@ -22,7 +22,6 @@ def render drop_list_fields = @drop_down_type_list.map { |item| AdvancedSearches::FilterTypes.drop_list_options(item.first.gsub('"', '&qoute;'), format_label(item.first) , item.last, format_optgroup(item.first)) } results = text_fields + drop_list_fields + number_fields + date_picker_fields - results.sort_by { |f| f[:label].downcase } @enrollment_data_list.map{ |item|results.unshift AdvancedSearches::FilterTypes.date_picker_options(item.gsub('"', '&qoute;'), format_label(item), format_optgroup(item)) } diff --git a/app/classes/advanced_searches/exit_program_fields.rb b/app/classes/advanced_searches/exit_program_fields.rb index d17f03e30b..93b0d436ae 100644 --- a/app/classes/advanced_searches/exit_program_fields.rb +++ b/app/classes/advanced_searches/exit_program_fields.rb @@ -23,8 +23,6 @@ def render results = text_fields + drop_list_fields + number_fields + date_picker_fields - results.sort_by { |f| f[:label].downcase } - @exit_data_list.map{ |item|results.unshift AdvancedSearches::FilterTypes.date_picker_options(item.gsub('"', '&qoute;'), format_label(item), format_optgroup(item)) } results diff --git a/app/classes/advanced_searches/tracking_fields.rb b/app/classes/advanced_searches/tracking_fields.rb index aa04167ad6..ac59fb05cd 100644 --- a/app/classes/advanced_searches/tracking_fields.rb +++ b/app/classes/advanced_searches/tracking_fields.rb @@ -21,7 +21,6 @@ def render drop_list_fields = @drop_down_type_list.map { |item| AdvancedSearches::FilterTypes.drop_list_options(item.first.gsub('"', '&qoute;'), format_label(item.first) , item.last, format_optgroup(item.first)) } results = text_fields + drop_list_fields + number_fields + date_picker_fields - results.sort_by { |f| f[:label].downcase } end def generate_field_by_type From 13a881d8a00c12592568dae50bb3e9cdeaf81880 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 30 Jul 2021 16:08:15 +0700 Subject: [PATCH 0063/1114] added family members family report builder --- .../advanced_searches/families/family_association_filter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/classes/advanced_searches/families/family_association_filter.rb b/app/classes/advanced_searches/families/family_association_filter.rb index b04d02746e..89a99273ec 100644 --- a/app/classes/advanced_searches/families/family_association_filter.rb +++ b/app/classes/advanced_searches/families/family_association_filter.rb @@ -64,9 +64,9 @@ def family_members when 'not_equal' families = families.joins(:family_members).where.not(family_members: { relation: @value }) when 'is_empty' - families = Family.includes(:family_members).where(family_members: { relation: "" }).references(:family_members) + families = Family.includes(:family_members).references(:family_members).group(:id).having("COUNT(family_members.*) = 0") when 'is_not_empty' - families = Family.includes(:family_members).where.not(family_members: { relation: "" }).references(:family_members) + families = families.joins(:family_members).where.not(family_members: { relation: "" }) end families.ids From f8645b1712afdd11d3d17a72c147b76c5a96ae09 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 30 Jul 2021 16:13:04 +0700 Subject: [PATCH 0064/1114] updated family member filter --- .../advanced_searches/families/family_association_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/classes/advanced_searches/families/family_association_filter.rb b/app/classes/advanced_searches/families/family_association_filter.rb index 89a99273ec..7f29c5fc35 100644 --- a/app/classes/advanced_searches/families/family_association_filter.rb +++ b/app/classes/advanced_searches/families/family_association_filter.rb @@ -62,7 +62,7 @@ def family_members when 'equal' families = families.joins(:family_members).where(family_members: { relation: @value }) when 'not_equal' - families = families.joins(:family_members).where.not(family_members: { relation: @value }) + families = Family.includes(:family_members).references(:family_members).where.not(family_members: { relation: @value }) when 'is_empty' families = Family.includes(:family_members).references(:family_members).group(:id).having("COUNT(family_members.*) = 0") when 'is_not_empty' From d8fe64a6ad9937c59e80b282a0b6a6f6bce7171f Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 3 Sep 2021 14:10:21 +0700 Subject: [PATCH 0065/1114] added exited_client_case_worker rake to schedule --- config/schedule.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/schedule.rb b/config/schedule.rb index 2f809a50bb..c31a20b8a8 100644 --- a/config/schedule.rb +++ b/config/schedule.rb @@ -13,6 +13,7 @@ every :day, at: '00:00 am' do rake 'incompleted_assessment:delete' + rake 'exited_client_case_worker:disattach' end every :month, at: '00:00 am' do From 20a8591ea576f337238196a3fbb64cc58e9073bd Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 15 Sep 2021 21:23:42 +0700 Subject: [PATCH 0066/1114] worked on ngo user sage report, added cross NGO MOSVY report --- app/services/ngo_usage_report.rb | 90 ++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/app/services/ngo_usage_report.rb b/app/services/ngo_usage_report.rb index 582d24fe74..a46507e725 100644 --- a/app/services/ngo_usage_report.rb +++ b/app/services/ngo_usage_report.rb @@ -1,4 +1,6 @@ class NgoUsageReport + include ReferralsHelper + def initialize end @@ -19,20 +21,28 @@ def ngo_info(org) 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) + users = User.non_devs.where(id: previous_month_users.where(event: 'create').pluck(:item_id)) { - 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, + user_count: users.count, + unlock_users: users.where(disable: true).count, + female_users: users.where(gender: 'female').count, + male_users: users.where(gender: 'male').count, + other_users: users.where.not(gender: ['male', 'female']).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) + clients = Client.without_test_clients.where(id: previous_month_clients.where(event: 'create').pluck(:item_id)) { - 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 + client_added_count: clients.count, + new_adult_female_client: adule_client_gender_count(clients, 'female'), + new_adult_male_clients: adule_client_gender_count(clients), + new_female_clients: clients.female.count, + new_male_clients: clients.male.count, + no_gender_dob_clients: other_client_gender_count(clients), + no_dob_clients: clients.where("gender IS NOT NULL AND (gender NOT IN ('male', 'female') AND date_of_birth IS NULL)").count } end @@ -47,8 +57,12 @@ def ngo_referrals_info(beginning_of_month, end_of_month) 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'] + user_columns = ['Organization', 'No. of Users', 'Unlock User', 'No. of Users Added Female', 'No. of Users Added Male', 'No. of Users Added Other', 'No. of Logins/Month'] + client_columns = ['Organization', 'Added new client this month', 'Increased client as adult female', 'Increased client as adult male', 'Increased client as Girl', 'Increased client as Boy', 'Other', 'No DoB'] + cross_ngo_columns = ['Organization', 'No. of Clients referred', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other', 'Agency Name received the case'] + + cross_mosvy_columns_group = ['', '', '', 'NGO to MoSAVY', '', '', '', '', '', 'MoSAVY to NGO', '', '', '', '', ''] + cross_mosvy_columns = ['Organization', 'Sign up date', 'Current Sharing', 'No. of Clients referred', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other', 'No. of Clients received', 'Adult Female', 'Adult Male', 'Other'] 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'] @@ -77,17 +91,23 @@ def import_usage_report(date_time) end_of_month = 1.month.ago.end_of_month previous_month = 1.month.ago.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 = book.create_worksheet(name: "1. NGO Records-#{previous_month}") + user_worksheet = book.create_worksheet(name: "2. User Report-#{previous_month}") + client_worksheet = book.create_worksheet(name: "3. Client Report-#{previous_month}") + cross_ngo_worksheet = book.create_worksheet(name: "4. Cross NGO-NGO Report-#{previous_month}") + cross_mosvy_worksheet = book.create_worksheet(name: "5. Cross NGO-MOSVY Report-#{previous_month}") + learning_worksheet = book.create_worksheet(name: "6. 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) + cross_ngo_worksheet.insert_row(0, cross_ngo_columns) + cross_mosvy_worksheet.insert_row(0, cross_mosvy_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) @@ -107,6 +127,7 @@ def import_usage_report(date_time) ngo_length_of_column = ngo_columns.length user_length_of_column = user_columns.length client_length_of_column = client_columns.length + cross_ngo_column_length = cross_ngo_columns.length ngo_length_of_column.times do |i| ngo_worksheet.row(0).set_format(i, header_format) @@ -123,14 +144,20 @@ def import_usage_report(date_time) end user_worksheet.row(0).height = 30 - (0..4).each {|index| user_worksheet.column(index).width = 30 } + (0..6).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 } + (0..5).each {|index| client_worksheet.column(index).width = 30 } + + cross_ngo_worksheet.row(0).height = 30 + cross_ngo_worksheet.column(0).width = 35 + cross_ngo_worksheet.column(1).width = 35 + cross_ngo_worksheet.column(7).width = 35 + cross_ngo_column_length.times {|i| cross_ngo_worksheet.row(0).set_format(i, header_format) } shared_data = [] stop_sharing_date = [] @@ -143,10 +170,12 @@ def import_usage_report(date_time) 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) + cross_ngo_referrals = cross_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]] + user_values = [setting[:ngo_name], *ngo_users.values] + client_values = [setting[:ngo_name], *ngo_clients.values] + cross_ngo_values = [setting[:ngo_name], *cross_ngo_referrals.values] next if Setting.first.blank? @@ -161,6 +190,7 @@ def import_usage_report(date_time) ngo_worksheet.insert_row(index += 1, ngo_values) user_worksheet.insert_row(index, user_values) client_worksheet.insert_row(index, client_values) + cross_ngo_worksheet.insert_row(index, cross_ngo_values) ngo_length_of_column.times do |i| ngo_worksheet.row(index).set_format(i, column_date_format) if i == 1 @@ -176,6 +206,8 @@ def import_usage_report(date_time) 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 @@ -220,4 +252,30 @@ 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 + + def cross_ngo_referrals_info(beginning_of_month, end_of_month) + referrals = Referral.where(created_at: beginning_of_month..end_of_month).where.not(referred_to: 'MoSVY External System') + clients = Client.where(id: referrals.pluck(:client_id)) + { + number_of_referrals: referrals.count, + adult_females: adule_client_gender_count(clients, :female), + adult_males: adule_client_gender_count(clients, :female), + girls: under_18_client_gender_count(clients, :female), + boys: under_18_client_gender_count(clients), + others: other_client_gender_count(clients), + referred_to: referrals.map{|referral| ngo_hash_mapping[referral.referred_to] }.join(', ') + } + 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')").count + end end From d29544466a980ca3545e50ce9e5e145197908071 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 16 Sep 2021 18:37:34 +0700 Subject: [PATCH 0067/1114] finish cross MOSVY usage report --- app/services/ngo_usage_report.rb | 94 ++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/app/services/ngo_usage_report.rb b/app/services/ngo_usage_report.rb index a46507e725..e3f4c7356b 100644 --- a/app/services/ngo_usage_report.rb +++ b/app/services/ngo_usage_report.rb @@ -15,7 +15,8 @@ def ngo_info(org) 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 + ngo_country: country.titleize, + integrated: org.integrated ? 'Yes' : 'No' } end @@ -61,8 +62,8 @@ def import_usage_report(date_time) client_columns = ['Organization', 'Added new client this month', 'Increased client as adult female', 'Increased client as adult male', 'Increased client as Girl', 'Increased client as Boy', 'Other', 'No DoB'] cross_ngo_columns = ['Organization', 'No. of Clients referred', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other', 'Agency Name received the case'] - cross_mosvy_columns_group = ['', '', '', 'NGO to MoSAVY', '', '', '', '', '', 'MoSAVY to NGO', '', '', '', '', ''] - cross_mosvy_columns = ['Organization', 'Sign up date', 'Current Sharing', 'No. of Clients referred', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other', 'No. of Clients received', 'Adult Female', 'Adult Male', 'Other'] + cross_mosvy_columns_group = ['', '', '', 'NGO to MoSAVY', '', '', '', '', 'MoSAVY to NGO', '', '', '', '', ''] + cross_mosvy_columns = ['Organization', 'Sign up date', 'Current Sharing', 'No. of Clients referred', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other', 'No. of Clients received', 'Adult Female', 'Adult Male', 'Girl', 'Boy', 'Other'] 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'] @@ -73,7 +74,7 @@ def import_usage_report(date_time) vertical_align: :center, shrink: true, border: :thin, - size: 11 + size: 12 ) column_format = Spreadsheet::Format.new( shrink: true, @@ -103,8 +104,30 @@ def import_usage_report(date_time) client_worksheet.insert_row(0, client_columns) cross_ngo_worksheet.insert_row(0, cross_ngo_columns) - cross_mosvy_worksheet.insert_row(0, cross_mosvy_columns) + cross_mosvy_worksheet.insert_row(0, cross_mosvy_columns_group) + cross_mosvy_worksheet.insert_row(1, cross_mosvy_columns) + cross_mosvy_worksheet.merge_cells(0, 3, 0, 7) + cross_mosvy_worksheet.merge_cells(0, 8, 0, 14) + cross_mosvy_worksheet.row(1).height = 30 + + cross_mosvy_columns.length.times do |i| + cross_mosvy_worksheet.row(0).set_format(i, header_format) + cross_mosvy_worksheet.row(1).set_format(i, header_format) + end + + cross_mosvy_worksheet.column(0).width = 35 + cross_mosvy_worksheet.column(1).width = 15 + cross_mosvy_worksheet.column(2).width = 15 + cross_mosvy_worksheet.column(3).width = 20 + cross_mosvy_worksheet.column(4).width = 15 + cross_mosvy_worksheet.column(5).width = 15 + + cross_mosvy_worksheet.column(9).width = 20 + cross_mosvy_worksheet.column(10).width = 15 + cross_mosvy_worksheet.column(11).width = 15 + + # Leaning worksheet ================================== learning_worksheet.insert_row(0, learning_columns) learning_worksheet.insert_row(1, sub_learning_columns) @@ -165,19 +188,20 @@ def import_usage_report(date_time) Organization.order(:created_at).without_shared.each_with_index do |org, index| Organization.switch_to org.short_name + next if Setting.first.blank? - 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) + 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) cross_ngo_referrals = cross_ngo_referrals_info(beginning_of_month, end_of_month) + cross_mosvy_referrals = cross_mosvy_referrals_info(setting, 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.values] - client_values = [setting[:ngo_name], *ngo_clients.values] - cross_ngo_values = [setting[:ngo_name], *cross_ngo_referrals.values] - - next if Setting.first.blank? + ngo_values = [setting[:ngo_name], setting[:ngo_on_board], setting[:fcf], setting[:ngo_country]] + user_values = [setting[:ngo_name], *ngo_users.values] + client_values = [setting[:ngo_name], *ngo_clients.values] + 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) @@ -191,6 +215,7 @@ def import_usage_report(date_time) user_worksheet.insert_row(index, user_values) client_worksheet.insert_row(index, client_values) cross_ngo_worksheet.insert_row(index, cross_ngo_values) + cross_mosvy_worksheet.insert_row(index += 1, cross_mosvy_values) ngo_length_of_column.times do |i| ngo_worksheet.row(index).set_format(i, column_date_format) if i == 1 @@ -267,6 +292,43 @@ def cross_ngo_referrals_info(beginning_of_month, end_of_month) } end + def cross_mosvy_referrals_info(ngo_data, beginning_of_month, end_of_month) + ngo_to_mosvy_hash = ngo_referral_client_info(beginning_of_month, end_of_month) + mosvy_to_ngo_hash = mosvy_referral_client_info(beginning_of_month, end_of_month) + + { + sign_update_date: ngo_data[:ngo_on_board], + current_sharing: ngo_data[:integrated], + }.merge(ngo_to_mosvy_hash.merge(mosvy_to_ngo_hash)) + end + + def ngo_referral_client_info(beginning_of_month, end_of_month) + referrals = Referral.where(created_at: beginning_of_month..end_of_month).where(referred_to: 'MoSVY External System') + clients = Client.where(id: referrals.pluck(:client_id)) + { + ngo_mosvy_referred_client_count: referrals.count, + ngo_mosvy_adult_females: adule_client_gender_count(clients, :female), + ngo_mosvy_adult_males: adule_client_gender_count(clients, :male), + ngo_mosvy_girls: under_18_client_gender_count(clients, :female), + ngo_mosvy_boys: under_18_client_gender_count(clients), + ngo_mosvy_others: other_client_gender_count(clients) + } + end + + def mosvy_referral_client_info(beginning_of_month, end_of_month) + referrals = Referral.where(created_at: beginning_of_month..end_of_month).where(saved: true, referred_from: 'MoSVY External System') + clients = Client.where(id: referrals.pluck(:client_id)) + + { + mosvy_ngo_referred_client_count: referrals.count, + mosvy_ngo_adult_females: adule_client_gender_count(clients, :female), + mosvy_ngo_adult_males: adule_client_gender_count(clients, :male), + mosvy_ngo_girls: under_18_client_gender_count(clients, :female), + mosvy_ngo_boys: under_18_client_gender_count(clients), + mosvy_ngo_others: other_client_gender_count(clients) + } + 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 @@ -276,6 +338,6 @@ 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')").count + clients.where("gender IS NOT NULL AND (gender NOT IN ('male', 'female') OR date_of_birth IS NULL)").count end end From be78b3b61aa1e2c81933cff6e8df24883fcf3d9f Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 17 Sep 2021 09:09:54 +0700 Subject: [PATCH 0068/1114] keep old user report and add new usage report --- app/services/old_ngo_usage_report.rb | 223 +++++++++++++++++++++++++++ lib/tasks/ngo_usage_report.rake | 6 + 2 files changed, 229 insertions(+) create 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 new file mode 100644 index 0000000000..b9b5aead74 --- /dev/null +++ b/app/services/old_ngo_usage_report.rb @@ -0,0 +1,223 @@ +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 = 1.month.ago.beginning_of_month + end_of_month = 1.month.ago.end_of_month + previous_month = 1.month.ago.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 4a9b9024e8..f40cdf8e34 100644 --- a/lib/tasks/ngo_usage_report.rake +++ b/lib/tasks/ngo_usage_report.rake @@ -4,5 +4,11 @@ namespace :ngo_usage_report do date_time = DateTime.now.strftime('%Y%m%d%H%M%S') 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 153fe149f3ae1a27fd529999761fc0c5c4a71b42 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 18 Oct 2021 10:59:16 +0700 Subject: [PATCH 0069/1114] fixed completed assessment table header for family --- app/grids/family_grid.rb | 6 +++--- app/views/datagrid/_family_custom_domain_score.haml | 4 ++-- config/locales/en.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/grids/family_grid.rb b/app/grids/family_grid.rb index f2dc99aa8e..6a0b740978 100644 --- a/app/grids/family_grid.rb +++ b/app/grids/family_grid.rb @@ -477,7 +477,7 @@ def filer_section(filter_name) dynamic do if !Setting.first.hide_family_case_management_tool? - column(:all_custom_csi_assessments, header: -> { I18n.t('datagrid.columns.all_custom_csi_assessments', assessment: t('families.show.assessment')) }, html: true) do |object| + 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 @@ -493,12 +493,12 @@ def filer_section(filter_name) end end - column(:date_of_custom_assessments, header: -> { I18n.t('datagrid.columns.date_of_custom_assessments', assessment: I18n.t('families.show.assessment')) }, html: true) do |object| + column(:date_of_custom_assessments, header: -> { I18n.t('datagrid.columns.date_of_custom_assessments', assessment: I18n.t('families.family_assessment')) }, html: true) do |object| assessments = map_assessment_and_score(object, '', nil) render partial: 'families/assessments', locals: { object: assessments } end - column(:custom_completed_date, header: -> { I18n.t('datagrid.columns.assessment_completed_date', assessment: I18n.t('families.show.assessment')) }, html: true) do |object| + column(:assessment_completed_date, header: -> { I18n.t('datagrid.columns.assessment_completed_date', assessment: I18n.t('families.family_assessment')) }, html: true) do |object| assessments = map_assessment_and_score(object, '', nil) render partial: 'families/assessments/assessment_completed_dates', locals: { object: assessments } end diff --git a/app/views/datagrid/_family_custom_domain_score.haml b/app/views/datagrid/_family_custom_domain_score.haml index 45dbc3a486..c2d2bd4feb 100644 --- a/app/views/datagrid/_family_custom_domain_score.haml +++ b/app/views/datagrid/_family_custom_domain_score.haml @@ -6,8 +6,8 @@ = label_tag 'date_of_custom_assessments_', t('datagrid.columns.date_of_family_assessment') %li .visibility.col-sm-12 - = check_box_tag 'custom_completed_date_', 'completed_date', checked = family_saved_search_column_visibility('custom_completed_date_'), class: 'i-checks' - = label_tag 'custom_completed_date_', t('datagrid.columns.assessment_completed_date', assessment: t('families.show.assessment')) + = check_box_tag 'assessment_completed_date_', 'completed_date', checked = family_saved_search_column_visibility('assessment_completed_date_'), class: 'i-checks' + = label_tag 'assessment_completed_date_', t('datagrid.columns.assessment_completed_date', assessment: t('families.family_assessment')) %li .visibility.col-sm-12 = check_box_tag 'all_custom_csi_assessments_', 'all_custom_csi_assessments', checked = family_saved_search_column_visibility('all_custom_csi_assessments_'), class: 'i-checks' diff --git a/config/locales/en.yml b/config/locales/en.yml index f4f6b84e38..bb1bd4b36c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2664,7 +2664,7 @@ en: datagrid: columns: all_csi_assessments: All CSI %{assessment} - all_custom_csi_assessments: "All Custom %{assessment}" + all_custom_csi_assessments: "All %{assessment}" all_result_framework_assessment: All Result Framework Assessment assessment_completed_date: "%{assessment} Completed Date" assessment_custom_completed_date: "Custom %{assessment} Completed Date" From 215de38862890033fdfd99530c427b17756b9b50 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 22 Oct 2021 17:07:42 +0700 Subject: [PATCH 0070/1114] fixed family assessment created at table header translation --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 47eff8294e..14381d0d92 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2973,7 +2973,7 @@ en: village_kh: Address - Village (Khmer) what3words: What3words date_of_assessments: "%{assessment} Created At" - date_of_custom_assessments: "Custom %{assessment} Created At" + date_of_custom_assessments: "%{assessment} Created At" date_of_family_assessment: Date of Family Assessment exit_circumstance: Exit Circumstance exit_note: NGO Exit Note From 2fb95470fb349e337d4872f05725b3208ad27c92 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 4 Nov 2021 15:06:41 +0700 Subject: [PATCH 0071/1114] fixed upcoming assessment email in client.rb --- app/models/client.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/client.rb b/app/models/client.rb index 1697129675..08aa4051cc 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -578,13 +578,15 @@ def exiting_ngo? end def self.notify_upcoming_csi_assessment + obj = self.new Organization.without_shared.each do |org| Organization.switch_to org.short_name - + current_setting = Setting.first_or_initialize next if !(current_setting.enable_default_assessment) && !(current_setting.enable_custom_assessment?) - clients = active_young_clients(self) - default_clients = clients_have_recent_default_assessments(clients) - custom_assessment_clients = clients_have_recent_custom_assessments(clients) + + clients = obj.active_young_clients(self) + default_clients = obj.clients_have_recent_default_assessments(clients) + custom_assessment_clients = obj.clients_have_recent_custom_assessments(clients) (default_clients + custom_assessment_clients).each do |client| CaseWorkerMailer.notify_upcoming_csi_weekly(client).deliver_now @@ -817,7 +819,7 @@ def remove_tasks(case_worker) end def current_setting - @current_setting ||= Setting.first + @current_setting ||= Setting.first_or_initialize end end From c0678a757a5b5ebe120bd8f7ca8540b84759eed6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 5 Nov 2021 09:52:49 +0700 Subject: [PATCH 0072/1114] added cron to Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3005a96810..8374d1bc3e 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 cron RUN mkdir /app WORKDIR /app @@ -38,4 +38,4 @@ ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. -CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] \ No newline at end of file +CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] From c9be80540e321d179492008d958105f2781c3db9 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 5 Nov 2021 10:12:19 +0700 Subject: [PATCH 0073/1114] removed douplicated key from has in families_helper and advanced_search --- app/helpers/advanced_search_helper.rb | 1 - app/helpers/families_helper.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index aa8f5b57ad..d9665f6e32 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -177,7 +177,6 @@ def family_header(key) def community_header(key) translations = { - initial_referral_date: I18n.t('advanced_search.fields.initial_referral_date'), name: I18n.t('activerecord.attributes.community.name'), name_en: I18n.t('activerecord.attributes.community.name_en'), status: I18n.t('activerecord.attributes.community.status'), diff --git a/app/helpers/families_helper.rb b/app/helpers/families_helper.rb index 2b0203c765..1b37de9b12 100644 --- a/app/helpers/families_helper.rb +++ b/app/helpers/families_helper.rb @@ -116,7 +116,6 @@ def map_family_field_labels case_note_type: I18n.t('advanced_search.fields.case_note_type'), female_children_count: I18n.t('datagrid.columns.families.female_children_count'), female_adult_count: I18n.t('datagrid.columns.families.female_adult_count'), - male_children_count: I18n.t('datagrid.columns.families.male_children_count'), clients: I18n.t('datagrid.columns.families.clients'), client_id: I18n.t('datagrid.columns.families.client'), manage: I18n.t('datagrid.columns.families.manage'), From 01327e1d3ce405c49a421ac3486953b04b582294 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 17 Nov 2021 11:57:17 +0700 Subject: [PATCH 0074/1114] hid NGO cifcp --- app/models/organization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/organization.rb b/app/models/organization.rb index 26618174c6..5d83b0ce6e 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -16,7 +16,7 @@ class Organization < ActiveRecord::Base scope :without_shared, -> { where.not(short_name: 'shared') } scope :exclude_current, -> { where.not(short_name: Organization.current.short_name) } scope :oscar, -> { visible.where(demo: false) } - scope :visible, -> { where.not(short_name: ['cwd', 'myan', 'rok', 'shared', 'my', 'tutorials']) } + scope :visible, -> { where.not(short_name: ['cwd', 'myan', 'rok', 'shared', 'my', 'tutorials', 'cifcp']) } scope :test_ngos, -> { where(short_name: ['demo', 'tutorials']) } scope :cambodian, -> { where(country: 'cambodia') } scope :skip_dup_checking_orgs, -> { where(short_name: ['demo', 'cwd', 'myan', 'rok', 'my']) } From bb83123493690714ce19c48f0fee227f194f7eee Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 1 Dec 2021 08:52:03 +0700 Subject: [PATCH 0075/1114] updated AppSignal name --- config/appsignal.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/appsignal.yml b/config/appsignal.yml index b41d0d5d19..1e104df629 100644 --- a/config/appsignal.yml +++ b/config/appsignal.yml @@ -4,7 +4,7 @@ default: &defaults push_api_key: "<%= ENV['APPSIGNAL_PUSH_API_KEY'] %>" # Your app's name - name: "cif" + name: "OSCaR" ignore_errors: - Apartment::TenantNotFound From 3d3f0a5dca0421434d0a7fbf3bcce7c71f78451a Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 3 Dec 2021 14:19:37 +0700 Subject: [PATCH 0076/1114] 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 0077/1114] 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 0078/1114] 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 0079/1114] 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 0080/1114] 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 56c3838a9794a617e8bdd500d738cd4d07dc3cde Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 5 Jan 2022 14:29:16 +0700 Subject: [PATCH 0081/1114] fixed page break on assess/casenote printable form --- app/views/shared/_printable_assessment.haml | 4 ++-- app/views/shared/_printable_care_plan.haml | 2 +- app/views/shared/_printable_case_note.haml | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/shared/_printable_assessment.haml b/app/views/shared/_printable_assessment.haml index 2a3d1b346e..50b4573311 100644 --- a/app/views/shared/_printable_assessment.haml +++ b/app/views/shared/_printable_assessment.haml @@ -37,9 +37,9 @@ %td= date_format assessment.completed_date - assessment.assessment_domains_in_order.each do |ad| - .row + .row{ style: "page-break-inside: avoid" } .col-xs-12 - %div{class: "panel panel-default"} + %div{ class: "panel panel-default" } .panel-heading %table.table.table-borderless %tbody diff --git a/app/views/shared/_printable_care_plan.haml b/app/views/shared/_printable_care_plan.haml index d671b9b0e3..51ee381b14 100644 --- a/app/views/shared/_printable_care_plan.haml +++ b/app/views/shared/_printable_care_plan.haml @@ -37,7 +37,7 @@ .col-xs-12 - care_plan.assessment.assessment_domains_in_order.each do |ad| - next if ad.goals.empty? - .row + .row{ style: "page-break-inside: avoid" } .col-xs-12 %div{class: "panel panel-#{ad.score_color_class}"} .panel-heading diff --git a/app/views/shared/_printable_case_note.haml b/app/views/shared/_printable_case_note.haml index 0aa28c3585..46b4c850a0 100644 --- a/app/views/shared/_printable_case_note.haml +++ b/app/views/shared/_printable_case_note.haml @@ -8,14 +8,14 @@ %h3.text-center = t('case_notes.case_note_form') .row - .col-xs-6 + .col-xs-7 %table.table.table-borderless.small.m-b-xs %tr %td.spacing-first-col %strong= "#{t('case_notes.client_name')}:" %td= case_note.parent.en_and_local_name %tr - %td + %td{ width: '120' } %strong= "#{t('case_notes.visitation_date')}:" %td= date_format case_note.meeting_date %tr @@ -23,7 +23,7 @@ %strong= "#{t('created_by')}:" %td= whodunnit('CaseNote', case_note.id) - .col-xs-6 + .col-xs-5 %table.table.table-borderless.small.m-b-xs %tr %td @@ -32,7 +32,7 @@ %tr %td %strong= "#{t('case_notes.type')}:" - %td= case_note.interaction_type + %td= type = I18n.t("case_notes.form.type_options.#{case_note.interaction_type.downcase.split(' ').join('_').gsub(/3|other/, '3' => '', 'other' => 'other_option')}") %tr %td %strong= "#{t('case_notes.show.case_note_on')}:" From bef6212494957a86e1b684df01ad2482c82cb9a8 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 10 Jan 2022 10:30:26 +0700 Subject: [PATCH 0082/1114] 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 18ff3a725610a4b03d93e564b59110ac6896aee2 Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Wed, 19 Jan 2022 12:18:26 +0700 Subject: [PATCH 0083/1114] [TASK] Add ci deployment --- .gitignore | 5 ++ ansible.cfg | 6 ++ config/ansible/group_vars/all/vars.yml | 43 +++++++++ config/ansible/group_vars/all/vault.yml | 23 +++++ config/ansible/inventory.yml | 25 ++++++ config/ansible/playbook.yml | 11 +++ config/ansible/roles/build/tasks/main.yml | 32 +++++++ .../ansible/roles/build/templates/Dockerfile | 4 + config/ansible/roles/deploy/tasks/main.yml | 37 ++++++++ .../roles/deploy/templates/ingress.yml.j2 | 18 ++++ .../roles/deploy/templates/mongo.yml.j2 | 56 ++++++++++++ .../roles/deploy/templates/namespace.yml.j2 | 6 ++ .../roles/deploy/templates/postgres.yml.j2 | 57 ++++++++++++ .../ansible/roles/deploy/templates/pvc.yml.j2 | 32 +++++++ .../roles/deploy/templates/redis.yml.j2 | 50 +++++++++++ .../roles/deploy/templates/sidekiq.yml.j2 | 72 +++++++++++++++ .../roles/deploy/templates/webapp.yml.j2 | 90 +++++++++++++++++++ .../roles/deploy/templates/webpack.yml.j2 | 50 +++++++++++ config/jenkins/build.sh | 11 +++ config/jenkins/deploy/demo.sh | 10 +++ config/jenkins/deploy/latest.sh | 10 +++ config/jenkins/deploy/live.sh | 11 +++ config/jenkins/uat/demo.sh | 3 + config/jenkins/uat/latest.sh | 3 + config/jenkins/uat/live.sh | 3 + 25 files changed, 668 insertions(+) create mode 100644 ansible.cfg create mode 100644 config/ansible/group_vars/all/vars.yml create mode 100644 config/ansible/group_vars/all/vault.yml create mode 100644 config/ansible/inventory.yml create mode 100644 config/ansible/playbook.yml create mode 100644 config/ansible/roles/build/tasks/main.yml create mode 100644 config/ansible/roles/build/templates/Dockerfile create mode 100644 config/ansible/roles/deploy/tasks/main.yml create mode 100644 config/ansible/roles/deploy/templates/ingress.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/mongo.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/namespace.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/postgres.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/pvc.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/redis.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/sidekiq.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/webapp.yml.j2 create mode 100644 config/ansible/roles/deploy/templates/webpack.yml.j2 create mode 100755 config/jenkins/build.sh create mode 100755 config/jenkins/deploy/demo.sh create mode 100755 config/jenkins/deploy/latest.sh create mode 100755 config/jenkins/deploy/live.sh create mode 100755 config/jenkins/uat/demo.sh create mode 100755 config/jenkins/uat/latest.sh create mode 100755 config/jenkins/uat/live.sh diff --git a/.gitignore b/.gitignore index 7276d1c5bf..8410f62ef2 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,8 @@ staging.env # aws private aws/ecs/private/ + +# Deployment +k8s-deploy +config/ansible +config/jenkins diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000000..ccf49fc685 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,6 @@ +[defaults] +inventory = config/ansible/inventory.yml +ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host} +callback_whitelist = profile_tasks +force_color = 1 +vault_password_file = ~/ansible-vault/oscar diff --git a/config/ansible/group_vars/all/vars.yml b/config/ansible/group_vars/all/vars.yml new file mode 100644 index 0000000000..82255e5d80 --- /dev/null +++ b/config/ansible/group_vars/all/vars.yml @@ -0,0 +1,43 @@ +--- + +project_id: 'osc' +project_name: 'oscar' +ssh_user: '{{ project_id }}' +k8s_namespace: '{{ project_id }}' +domain_name: 'start.wehost.asia' +rail_env: '' + +image_name: 'hub.web-essentials.co/we/{{ project_id }}' + +ssh_user: '{{ inventory_hostname }}-{{ project_name }}' +kubeconfig: '~/.kube/admin.conf' +workspace: '{{ lookup("env", "WORKSPACE") or lookup("env", "PWD") }}' +tz_path: '/usr/share/zoneinfo/Asia/Phnom_Penh' +nfs_server: '10.10.10.26' +nfs_path: '/volume1/export/{{ stage }}/{{ project_id }}' + +oscar_team_email: team@oscarhq.com +oscar_team_pass: '{{ oscar_team_pass_vault }}' +power_bin_group: power_bi_group + +read_only_db_user: powerbi_dashboard_user +read_only_db_pass: '{{ read_only_db_pass_vault }}' + +redis_ver: 'redis:5.0.13' + +pg_db_ver: 'postgres:12.3' +pg_db_user: '{{ stage }}_{{ project_name | replace("-", "_") }}' +pg_db_name: '{{ stage }}_{{ project_name | replace("-", "_") }}' +pg_db_pass: '{{ pg_db_pass_vault }}' + +mongo_db_ver: 'mongo:4.2.10' +mongo_db_user: '{{ stage }}_history_{{ project_name | replace("-", "_") }}' +mongo_db_name: '{{ stage }}_history_{{ project_name | replace("-", "_") }}' +mongo_db_pass: '{{ mongo_db_pass_vault }}' + +secret_key_base: '{{ secret_key_base_vault }}' +s3_bucket_name: +aws_access_key_id: '{{ aws_access_key_id_vault }}' +aws_secret_access_key: '{{ aws_secret_access_key_vault }}' +fog_region: 'ap-southeast-1' +fog_directory: diff --git a/config/ansible/group_vars/all/vault.yml b/config/ansible/group_vars/all/vault.yml new file mode 100644 index 0000000000..467380c216 --- /dev/null +++ b/config/ansible/group_vars/all/vault.yml @@ -0,0 +1,23 @@ +$ANSIBLE_VAULT;1.1;AES256 +33353032633739653832333462306538363330316661633761626234633765303463316333383335 +3937393433386664636631323332346336616165666233660a353137346137333432643935643632 +36376139323761666635316131343362633762383762616331333132633632323934313062616438 +3739616435633730620a393963613530343133386632656534666138656461666239613730663235 +32313135343861353835393266343334393562373962663836373734393231316232303566633266 +32356639363365306337313739333061316662613131663536626631653332373466633831646236 +31613263656462333565646231616431636463326537623534346135633261333961633763396361 +63626335313962353364633737393930373031303765353163353137613132313830613164646631 +34306366663532336635313037323031326563393862343732326566626262653765366134626631 +35383138396138666135626231636361366335343362306137323938616563626430343338663835 +35333331663139316632363137313932353333326231333332653066626339336364353733396463 +39646462346236616438383933613361323234313332383264633833326535646233306134313032 +66333235306535663162643333373235656666343638663164333962633362346631353532653939 +33653065393235373462663239363136303737643861316239383538633231326135613030343133 +35643237643835366566353361386464643361383333623163653637626264383330366437343330 +36633637666437366265373762363938313031396661643238333264306135386236323633613234 +65646534363830363164653232306461656564633836343764643965353762623439336431346434 +66646465393966313462646261643464343364343132313133636366623635333734336536363832 +62333334663562333338663866363237653865666533363530333438646638663036313161653361 +37346237643261373666616339616164366230623930336635393632396535356533313730396366 +33343734646362633238646665326265383632653037633932336664303330363363346361303463 +3262346537386565646336393332386264313761623936343234 diff --git a/config/ansible/inventory.yml b/config/ansible/inventory.yml new file mode 100644 index 0000000000..a90c462de6 --- /dev/null +++ b/config/ansible/inventory.yml @@ -0,0 +1,25 @@ +--- + +all: + hosts: + build: + ansible_connection: local + + latest: + ansible_connection: local + + demo: + ansible_connection: local + + live: + ansible_host: + ansible_user: + ansible_ssh_private_key_file: ~/.ssh/id_rsa + + children: + oscarservers: + hosts: + latest: + demo: + live: + diff --git a/config/ansible/playbook.yml b/config/ansible/playbook.yml new file mode 100644 index 0000000000..9d86a73d3a --- /dev/null +++ b/config/ansible/playbook.yml @@ -0,0 +1,11 @@ +--- + +- hosts: oscarservers + remote_user: '{{ ssh_user }}' + roles: + - role: build + tags: + - build + - role: deploy + tags: + - deploy diff --git a/config/ansible/roles/build/tasks/main.yml b/config/ansible/roles/build/tasks/main.yml new file mode 100644 index 0000000000..9e342194a4 --- /dev/null +++ b/config/ansible/roles/build/tasks/main.yml @@ -0,0 +1,32 @@ +--- + +- name: Copy other config files + template: + src: '{{ item.src }}' + dest: '{{ item.dest }}' + mode: 0755 + loop: + - { src: 'Dockerfile', dest: '{{ workspace }}/Dockerfile' } + +- name: Run yarn install + shell: yarn install + args: + chdir: '{{ workspace }}' + +- name: Build docker image + shell: 'docker build -t {{ image_name }}/webapp:latest .' + args: + chdir: '{{ workspace }}' + +- name: Tag docker image + shell: 'docker tag {{ image_name }}/webapp:latest {{ image_name }}/webapp:{{ version }}' + args: + chdir: '{{ workspace }}' + +- name: Push docker image to hub.web-essentials.co + shell: 'docker push {{ image_name }}/webapp:{{ item }}' + args: + chdir: '{{ workspace }}' + loop: + - '{{ version }}' + - 'latest' diff --git a/config/ansible/roles/build/templates/Dockerfile b/config/ansible/roles/build/templates/Dockerfile new file mode 100644 index 0000000000..f71a66d0e2 --- /dev/null +++ b/config/ansible/roles/build/templates/Dockerfile @@ -0,0 +1,4 @@ +FROM hub.web-essentials.co/base/ruby:2.3.3 + +WORKDIR /app +COPY . ./ diff --git a/config/ansible/roles/deploy/tasks/main.yml b/config/ansible/roles/deploy/tasks/main.yml new file mode 100644 index 0000000000..657f48ac73 --- /dev/null +++ b/config/ansible/roles/deploy/tasks/main.yml @@ -0,0 +1,37 @@ +--- + +- name: Internal | Create k8s deploy directory + file: + path: '{{ workspace }}/k8s-deploy' + state: directory + mode: 0755 + +- name: Copy deployment files + template: + src: '{{ item }}' + dest: '{{ workspace }}/k8s-deploy/{{ item | basename | regex_replace("\.j2$", "") }}' + with_fileglob: + '../templates/*.j2' + +- name: K8s apply namespace and databases + shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }}' + loop: + - namespace.yml + - pvc.yml + - redis.yml + - postgres.yml + - mongo.yml + +- name: K8s apply po and ing + shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }} --wait=true' + loop: + - webapp.yml + - webpack.yml + - sidekiq.yml + - ingress.yml + +- name: Wait until container ready + shell: | + while [[ $(KUBECONFIG={{ kubeconfig }} kubectl -n {{ k8s_namespace }} get po -l app=app-{{ stage }}-{{ project_id }}-webapp -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]] ; do echo "waiting for pod" && sleep 1 ; done + args: + executable: /bin/bash diff --git a/config/ansible/roles/deploy/templates/ingress.yml.j2 b/config/ansible/roles/deploy/templates/ingress.yml.j2 new file mode 100644 index 0000000000..63ec971e68 --- /dev/null +++ b/config/ansible/roles/deploy/templates/ingress.yml.j2 @@ -0,0 +1,18 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ing-{{ stage }}-{{ project_id }} + namespace: '{{ k8s_namespace }}' +spec: + rules: + - host: {{ domain_name }} + http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: 'svc-{{ stage }}-{{ project_id }}-webapp' + port: + number: 3000 diff --git a/config/ansible/roles/deploy/templates/mongo.yml.j2 b/config/ansible/roles/deploy/templates/mongo.yml.j2 new file mode 100644 index 0000000000..3acc37b35b --- /dev/null +++ b/config/ansible/roles/deploy/templates/mongo.yml.j2 @@ -0,0 +1,56 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-mongo' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-mongo' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-mongo' + spec: + imagePullSecrets: + - name: secret-osc + containers: + - image: {{ mongo_db_ver }} + name: 'app-{{ stage }}-{{ project_id }}-mongo' + imagePullPolicy: Always + ports: + - containerPort: 27017 + env: + - name: MONGO_INITDB_DATABASE + value: {{ mongo_db_name }} + - name: MONGO_INITDB_ROOT_USERNAME + value: {{ mongo_db_user }} + - name: MONGO_INITDB_ROOT_PASSWORD + value: "{{ mongo_db_pass }}" + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /data/db + subPath: mongodata + volumes: + - name: tz-config + hostPath: + path: /usr/share/zoneinfo/Asia/Phnom_Penh + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-mongo' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 27017 + selector: + app: 'app-{{ stage }}-{{ project_id }}-mongo' diff --git a/config/ansible/roles/deploy/templates/namespace.yml.j2 b/config/ansible/roles/deploy/templates/namespace.yml.j2 new file mode 100644 index 0000000000..10054ca81a --- /dev/null +++ b/config/ansible/roles/deploy/templates/namespace.yml.j2 @@ -0,0 +1,6 @@ +--- + +apiVersion: v1 +kind: Namespace +metadata: + name: '{{ k8s_namespace }}' diff --git a/config/ansible/roles/deploy/templates/postgres.yml.j2 b/config/ansible/roles/deploy/templates/postgres.yml.j2 new file mode 100644 index 0000000000..a4a9c80791 --- /dev/null +++ b/config/ansible/roles/deploy/templates/postgres.yml.j2 @@ -0,0 +1,57 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-pg' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-pg' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-pg' + spec: + imagePullSecrets: + - name: secret-osc + containers: + - image: {{ pg_db_ver }} + name: 'app-{{ stage }}-{{ project_id }}-pg' + imagePullPolicy: Always + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: {{ pg_db_name }} + - name: POSTGRES_USER + value: {{ pg_db_user }} + - name: POSTGRES_PASSWORD + value: "{{ pg_db_pass }}" + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /data/postgres + subPath: pgdata + volumes: + - name: tz-config + hostPath: + path: /usr/share/zoneinfo/Asia/Phnom_Penh + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc + +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-pg' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 5432 + selector: + app: 'app-{{ stage }}-{{ project_id }}-pg' diff --git a/config/ansible/roles/deploy/templates/pvc.yml.j2 b/config/ansible/roles/deploy/templates/pvc.yml.j2 new file mode 100644 index 0000000000..08b609fbab --- /dev/null +++ b/config/ansible/roles/deploy/templates/pvc.yml.j2 @@ -0,0 +1,32 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: 'pv-{{ stage }}-{{ project_id }}-osc' + labels: + app: 'pv-{{ stage }}-{{ project_id }}-osc' +spec: + capacity: + storage: 2Gi + accessModes: + - ReadWriteMany + nfs: + server: '{{ nfs_server }}' + path: '{{ nfs_path }}' + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: 'pvc-{{ stage }}-{{ project_id }}-osc' + namespace: '{{ k8s_namespace }}' +spec: + selector: + matchLabels: + app: 'pv-{{ stage }}-{{ project_id }}-osc' + accessModes: + - ReadWriteMany + storageClassName: '' + resources: + requests: + storage: 2Gi diff --git a/config/ansible/roles/deploy/templates/redis.yml.j2 b/config/ansible/roles/deploy/templates/redis.yml.j2 new file mode 100644 index 0000000000..c324126d5a --- /dev/null +++ b/config/ansible/roles/deploy/templates/redis.yml.j2 @@ -0,0 +1,50 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-redis' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-redis' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-redis' + spec: + imagePullSecrets: + - name: secret-osc + containers: + - image: {{ redis_ver }} + name: 'app-{{ stage }}-{{ project_id }}-redis' + imagePullPolicy: Always + ports: + - containerPort: 6379 + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /data + subPath: redisdata + volumes: + - name: tz-config + hostPath: + path: /usr/share/zoneinfo/Asia/Phnom_Penh + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc + +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-redis' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 6379 + selector: + app: 'app-{{ stage }}-{{ project_id }}-redis' \ No newline at end of file diff --git a/config/ansible/roles/deploy/templates/sidekiq.yml.j2 b/config/ansible/roles/deploy/templates/sidekiq.yml.j2 new file mode 100644 index 0000000000..89fc75a587 --- /dev/null +++ b/config/ansible/roles/deploy/templates/sidekiq.yml.j2 @@ -0,0 +1,72 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-sidekiq' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-sidekiq' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-sidekiq' + spec: + imagePullSecrets: + - name: secret-{{ project_id }} + containers: + - image: {{ image_name }}/webapp:{{ version }} + name: 'app-{{ stage }}-{{ project_id }}-sidekiq' + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: RAIL_ENV + value: {{ rail_env }} + - name: SECRET_KEY_BASE + value: {{ secret_key_base }} + - name: DATABASE_HOST + value: svc-{{ stage }}-{{ project_id }}-pg + - name: DATABASE_NAME + value: {{ pg_db_name }} + - name: DATABASE_NAME_TEST + value: {{ pg_db_name }}_test + - name: DATABASE_USER + value: {{ pg_db_user }} + - name: DATABASE_PASSWORD + value: "{{ pg_db_pass }}" + - name: SENDER_EMAIL + value: {{ oscar_team_email }} + - name: REDIS_URL + value: redis://svc-{{ stage }}-{{ project_id }}-redis:6379/0 + - name: S3_BUCKET_NAME + value: {{ s3_bucket_name }} + - name: AWS_ACCESS_KEY_ID + value: {{ aws_access_key_id }} + - name: AWS_SECRET_ACCESS_KEY + value: {{ aws_secret_access_key }} + - name: FOG_REGION + value: {{ fog_region }} + - name: FOG_DIRECTORY + value: {{ fog_directory }} + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + volumes: + - name: tz-config + hostPath: + path: {{ tz_path }} +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-sidekiq' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 3000 + selector: + app: 'app-{{ stage }}-{{ project_id }}-sidekiq' diff --git a/config/ansible/roles/deploy/templates/webapp.yml.j2 b/config/ansible/roles/deploy/templates/webapp.yml.j2 new file mode 100644 index 0000000000..a9b388cf65 --- /dev/null +++ b/config/ansible/roles/deploy/templates/webapp.yml.j2 @@ -0,0 +1,90 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-webapp' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-webapp' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-webapp' + spec: + imagePullSecrets: + - name: secret-{{ project_id }} + containers: + - image: {{ image_name }}/webapp:{{ version }} + name: 'app-{{ stage }}-{{ project_id }}-webapp' + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: RAIL_ENV + value: {{ rail_env }} + - name: SECRET_KEY_BASE + value: {{ secret_key_base }} + - name: DATABASE_HOST + value: svc-{{ stage }}-{{ project_id }}-pg + - name: DATABASE_NAME + value: {{ pg_db_name }} + - name: DATABASE_NAME_TEST + value: {{ pg_db_name }}_test + - name: DATABASE_USER + value: {{ pg_db_user }} + - name: DATABASE_PASSWORD + value: "{{ pg_db_pass }}" + - name: HISTORY_DATABASE_NAME + value: {{ mongo_db_name }} + - name: HISTORY_DATABASE_NAME_TEST + value: {{ mongo_db_name }}_test + - name: HISTORY_DATABASE_HOST + value: svc-{{ stage }}-{{ project_id }}-mongo + - name: OSCAR_TEAM_EMAIL + value: {{ oscar_team_email }} + - name: OSCAR_TEAM_PASSWORD + value: "{{ oscar_team_pass }}" + - name: SENDER_EMAIL + value: {{ oscar_team_email }} + - name: POWER_BI_GROUP + value: {{ power_bin_group }} + - name: READ_ONLY_DATABASE_USER + value: {{ read_only_db_user }} + - name: READ_ONLY_DATABASE_PASSWORD + value: "{{ read_only_db_pass }}" + - name: WEBPACKER_DEV_SERVER_HOST + value: svc-{{ stage }}-{{ project_id }}-webpack + - name: REDIS_URL + value: redis://svc-{{ stage }}-{{ project_id }}-redis:6379/0 + - name: S3_BUCKET_NAME + value: {{ s3_bucket_name }} + - name: AWS_ACCESS_KEY_ID + value: {{ aws_access_key_id }} + - name: AWS_SECRET_ACCESS_KEY + value: {{ aws_secret_access_key }} + - name: FOG_REGION + value: {{ fog_region }} + - name: FOG_DIRECTORY + value: {{ fog_directory }} + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + volumes: + - name: tz-config + hostPath: + path: {{ tz_path }} +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-webapp' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 3000 + selector: + app: 'app-{{ stage }}-{{ project_id }}-webapp' diff --git a/config/ansible/roles/deploy/templates/webpack.yml.j2 b/config/ansible/roles/deploy/templates/webpack.yml.j2 new file mode 100644 index 0000000000..270ce1c903 --- /dev/null +++ b/config/ansible/roles/deploy/templates/webpack.yml.j2 @@ -0,0 +1,50 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: 'app-{{ stage }}-{{ project_id }}-webpack' + namespace: '{{ k8s_namespace }}' + annotations: + reloader.stakater.com/auto: 'true' +spec: + selector: + matchLabels: + app: 'app-{{ stage }}-{{ project_id }}-webpack' + template: + metadata: + labels: + app: 'app-{{ stage }}-{{ project_id }}-webpack' + spec: + imagePullSecrets: + - name: secret-{{ project_id }} + containers: + - image: {{ image_name }}/webapp:{{ version }} + name: 'app-{{ stage }}-{{ project_id }}-webpack' + imagePullPolicy: Always + ports: + - containerPort: 3035 + env: + - name: RAIL_ENV + value: {{ rail_env }} + - name: WEBPACKER_DEV_SERVER_HOST + value: 0.0.0.0 + command: [ "/bin/bash" ] + args: [ "-c", "rm -rf /app/public/packs; /app/bin/webpack-dev-server" ] + volumeMounts: + - name: tz-config + mountPath: /etc/localtime + volumes: + - name: tz-config + hostPath: + path: {{ tz_path }} +--- +apiVersion: v1 +kind: Service +metadata: + name: 'svc-{{ stage }}-{{ project_id }}-webpack' + namespace: '{{ k8s_namespace }}' +spec: + ports: + - port: 3000 + selector: + app: 'app-{{ stage }}-{{ project_id }}-webpack' diff --git a/config/jenkins/build.sh b/config/jenkins/build.sh new file mode 100755 index 0000000000..d2eeb619fb --- /dev/null +++ b/config/jenkins/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +FILE_NAME=$(basename "$0") +STAGE="${FILE_NAME%.*}" + +ansible-playbook config/ansible/playbook.yml -vv \ + -l ${STAGE} \ + -t build \ + -e stage=${STAGE} \ + -e version=${BUILD_ID} \ + -e workspace=${WORKSPACE} diff --git a/config/jenkins/deploy/demo.sh b/config/jenkins/deploy/demo.sh new file mode 100755 index 0000000000..918ef385d2 --- /dev/null +++ b/config/jenkins/deploy/demo.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +FILE_NAME=$(basename "$0") +STAGE="${FILE_NAME%.*}" + +ansible-playbook config/ansible/playbook.yml -t deploy -vv \ + -l ${STAGE} \ + -e stage=${STAGE} \ + -e version=${DEPLOY_ID} \ + -e workspace=${WORKSPACE} diff --git a/config/jenkins/deploy/latest.sh b/config/jenkins/deploy/latest.sh new file mode 100755 index 0000000000..918ef385d2 --- /dev/null +++ b/config/jenkins/deploy/latest.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +FILE_NAME=$(basename "$0") +STAGE="${FILE_NAME%.*}" + +ansible-playbook config/ansible/playbook.yml -t deploy -vv \ + -l ${STAGE} \ + -e stage=${STAGE} \ + -e version=${DEPLOY_ID} \ + -e workspace=${WORKSPACE} diff --git a/config/jenkins/deploy/live.sh b/config/jenkins/deploy/live.sh new file mode 100755 index 0000000000..ac685ee34e --- /dev/null +++ b/config/jenkins/deploy/live.sh @@ -0,0 +1,11 @@ +#!/bin/bash -ex + +exit 0 +FILE_NAME=$(basename "$0") +STAGE="${FILE_NAME%.*}" + +ansible-playbook config/ansible/playbook.yml -t deploy -vv \ + -l ${STAGE} \ + -e stage=${STAGE} \ + -e version=${DEPLOY_ID} \ + -e workspace=${WORKSPACE} diff --git a/config/jenkins/uat/demo.sh b/config/jenkins/uat/demo.sh new file mode 100755 index 0000000000..20abb91f22 --- /dev/null +++ b/config/jenkins/uat/demo.sh @@ -0,0 +1,3 @@ +#!/bin/bash -ex +exit 0 + diff --git a/config/jenkins/uat/latest.sh b/config/jenkins/uat/latest.sh new file mode 100755 index 0000000000..20abb91f22 --- /dev/null +++ b/config/jenkins/uat/latest.sh @@ -0,0 +1,3 @@ +#!/bin/bash -ex +exit 0 + diff --git a/config/jenkins/uat/live.sh b/config/jenkins/uat/live.sh new file mode 100755 index 0000000000..20abb91f22 --- /dev/null +++ b/config/jenkins/uat/live.sh @@ -0,0 +1,3 @@ +#!/bin/bash -ex +exit 0 + From 81c7259e05b39d67c37cbc85d2da739c8155c497 Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Wed, 26 Jan 2022 10:24:15 +0700 Subject: [PATCH 0084/1114] [TAST] Fix inventory hosts playbook --- config/ansible/playbook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/ansible/playbook.yml b/config/ansible/playbook.yml index 9d86a73d3a..72edc0322d 100644 --- a/config/ansible/playbook.yml +++ b/config/ansible/playbook.yml @@ -1,6 +1,6 @@ --- -- hosts: oscarservers +- hosts: all remote_user: '{{ ssh_user }}' roles: - role: build From 95d8b2e63e6908604c8e044e5d22231c79217ce2 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 26 Jan 2022 14:45:51 +0700 Subject: [PATCH 0085/1114] 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 4b8dd6449e11a01f5dd91e4f19b61d8230f441ca Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Fri, 28 Jan 2022 16:53:50 +0700 Subject: [PATCH 0086/1114] [TASK] local server share node modules --- config/ansible/roles/deploy/templates/sidekiq.yml.j2 | 6 ++++++ config/ansible/roles/deploy/templates/webapp.yml.j2 | 6 ++++++ config/ansible/roles/deploy/templates/webpack.yml.j2 | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/config/ansible/roles/deploy/templates/sidekiq.yml.j2 b/config/ansible/roles/deploy/templates/sidekiq.yml.j2 index 89fc75a587..7bccd3ba0d 100644 --- a/config/ansible/roles/deploy/templates/sidekiq.yml.j2 +++ b/config/ansible/roles/deploy/templates/sidekiq.yml.j2 @@ -55,10 +55,16 @@ spec: volumeMounts: - name: tz-config mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /app/node_modules + subPath: nodemodules volumes: - name: tz-config hostPath: path: {{ tz_path }} + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc --- apiVersion: v1 kind: Service diff --git a/config/ansible/roles/deploy/templates/webapp.yml.j2 b/config/ansible/roles/deploy/templates/webapp.yml.j2 index a9b388cf65..9922e15825 100644 --- a/config/ansible/roles/deploy/templates/webapp.yml.j2 +++ b/config/ansible/roles/deploy/templates/webapp.yml.j2 @@ -73,10 +73,16 @@ spec: volumeMounts: - name: tz-config mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /app/node_modules + subPath: nodemodules volumes: - name: tz-config hostPath: path: {{ tz_path }} + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc --- apiVersion: v1 kind: Service diff --git a/config/ansible/roles/deploy/templates/webpack.yml.j2 b/config/ansible/roles/deploy/templates/webpack.yml.j2 index 270ce1c903..54c1cdf090 100644 --- a/config/ansible/roles/deploy/templates/webpack.yml.j2 +++ b/config/ansible/roles/deploy/templates/webpack.yml.j2 @@ -33,10 +33,16 @@ spec: volumeMounts: - name: tz-config mountPath: /etc/localtime + - name: vol-{{ stage }}-{{ project_id }}-osc + mountPath: /app/nodemodules + subPath: node_modules volumes: - name: tz-config hostPath: path: {{ tz_path }} + - name: vol-{{ stage }}-{{ project_id }}-osc + persistentVolumeClaim: + claimName: pvc-{{ stage }}-{{ project_id }}-osc --- apiVersion: v1 kind: Service From 373be81d85aa3ce3ad03d06be448012b2b0c2ee3 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 8 Feb 2022 12:17:17 +0700 Subject: [PATCH 0087/1114] fixed font display wrong type version --- app/assets/fonts/KhmerOSBattambang-Bold.ttf | Bin 0 -> 296840 bytes app/assets/fonts/KhmerOSBattambang-Regular.ttf | Bin 0 -> 308232 bytes app/views/layouts/_fonts.html.erb | 14 ++++++++++++++ 3 files changed, 14 insertions(+) create mode 100755 app/assets/fonts/KhmerOSBattambang-Bold.ttf create mode 100755 app/assets/fonts/KhmerOSBattambang-Regular.ttf diff --git a/app/assets/fonts/KhmerOSBattambang-Bold.ttf b/app/assets/fonts/KhmerOSBattambang-Bold.ttf new file mode 100755 index 0000000000000000000000000000000000000000..ac1221205eb6adc95e8a4b34965eabc0538db405 GIT binary patch literal 296840 zcmeFa349bq_6J_ob0rW$?t_qXa*~ToP9PVMgphCq

Zt2Lucdxdozt$R#Ksju*1X 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

8YlvfDsg0%M{f~O6J!j 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

  • ?0aw@OE*(M2_8}g#H2V@Fo)uASXbxn|kXqb0>3sNwR%_35AsT}jf`{~NkvdT0ng zR>y-v78&+`k*UtV4cwzZr^je4_2Br;Tjp`|QTItmS9e>OZcpvfF)*#g+(Sw*TH|EG zoO}+KXt#Q^3MBenGV(ig0|tD#D6)zRuWk$vNXa#DVw1TyXy3%x3}QXGz78J<%YW6( z`l8owHC^Yet;W25!i1Zby}M#>;eCZ`i2J)m*4Jd<4ku~j2HZ5)m`I`2jM>~cmsSz{+vD97l4JH^y&CO zN3Ww9ZZzq^{o@5Q*>@4SqsJJFkDIZ1F#mgje;`{fMpw{VGRyEk?zU%X>j@vE4|Y&`bN1bpUT~6im!>Oj zES?~n)eaG9Z1O~R`8<8#!{mxAxktTVA@D%zCgrzh(Ix8z^jUJ-xyWZgCGiWtZrft%i?Vz2jV>~FGDtN`jM|FsXs5PM5x z+12d@D_M4jd&Y^do#bKV#3!}uv?I!P(oMVkWS5ij8nOC^Hpt6hO=03@K<@5!FDUFG z7rZNKg}x>5I#-LqYWZ2rL3+YR?}N(<-pF9nq3l@0vhVVDMTnArV*TSJZKie(VnwAr zKf5fExEY1h0_rqN&I6CQMwxhFgPhCNQYbI`Qgq21nXqX^$3TyJRfw_3hKnZcZn8{f zTd_^VMUuDEUowEKW5)7%_AHKU9#3z`5;iUyuGV4(`9ybM%gIwZt}sk1D}gImaEvv? z8LbbqR&i$uk@-VRPicnxV|0n_0@>d@Bbg`HYIk-EmZb;PNizD`WFuR64Ex=Oqf2<} z!X_O;r?Bzi{oR5cD_KHUe5_e#HSVgMHE@8~dyl`3Bmsn2OxRUeyZC)Yx zO5}Go^p16g7cSdlon_UqQ0R9JU)Ir<`xVN=y~@mqainp7{Md|{7dOtF+0xwH`sVE5 zefI}v?=G}pDJ$Y$es9rZo`0#LcbkUNm z?4|JW#c3k*UDbbUYzt_sv;*2T+SedGyC?mB_R-mxuvMS0%>uH7MB!fjw%Wz#^g~a- zebz^<{`M5;8UitYv-r}GqieBKCwaxQ4sW$)VUSAFgq4G8V@oHkrTgb|xnF^q#dnVQ zE)9EKOCX=DtK8llc(TN;m7JP_E#23AW__G9(>}R4ce_5iS?Y+dgz5?Qk38*cWOx09 zcAtS2^8Y!}wb;vNEGz7sgL%?i7Pd!tTky<2HyWmOg+;TtkG%SXEzDK1IIo@1q3ADne;h*_1lTa=}M(Kec7rZtPro52Tv%5nF}@*oJ(q zcD7~2&ie2r+{H3~w&z5~ZL6~~xs@(sGu5__e8(R*c11Bz`kc+Kw6=S!@@h6DwFGkk zwvoUOs$<40y6ss%v&4a7qLiGgJ_Fyh%y-)*hU~UvxWK*8Jp}HTV(s5$+F@|1#hsaW znyo=9`$|mz&zc6n*4DIX8NrlB5*?x1z5i-fm^5(IG+bnoJGZNb3)n4=*NgQ_d0KyM zowh~$bPucx^teXWBeJ`;F7U&NWWI3QM%NYBIayj4*gheBLf_+Wrd$54@qhlZV_*A$ z6EA5KvD-1$cZw}4_ON{L_?>YUq&Tg=5F(NNfLV4uN7 z@r@va%gW2k@0xvXb@jA7$QR!Br@Z`*Nd613y7fgL3kT;-qTPMP;rP=RHS)7A zYRrEj{>}O^W9r#Tb<|j#^zLU6dtXRzjV}y~@gi?B`ZbmfVb8SAyQOaN{2LeEcj1*6 z-Ft6f#PK%+BkCufJ9G1f7Z+^aB;S7@`+Se4_tiEUa4 zYmFaU{E3=#6Y9>>2C(TG3oHwKOUu^=Xv4H|VkhxASXFoyvSFW(h`39!&)h)`d$df1 z6p)*cmHDt@^Md}}@|k-1-e9o&Mxf~H z!PE5eO;i+9(T9o|RGd!5PCW4ydi)*w?-&)gQL!FxSJ6q^)KXSD<%?6Mpp5@^>J&C* z%2;vfN>Lsb<$a>O-&Wo&UihrI5*FolQGQL7_lkPHC(c1Tnl_2Oq%m=(VMlNvQk*kw z5dJgHIYzKW@>~ZWsp|pYMV;q>23E>van(OiH*G6kcu8Yy*6M~`yLVjDR2-dIR%SmhK4Wv!-p#wN zC@v3A7uAXy&R$`?KZD-Ci0a*gw#?kpc*(ZiJ1^NY^MXq*rUsyamu%a$dFS>W7Zlev zY~HhXSHo_3y}`V&=#q;s#>bcK*k(LXdr8v;d(fQS#i&!he(BzZ-77EIyQg7y@zxZ# z&G(B}?cUqa*eJf#pz7GVWA_Ey8=8vGJiM)O#|7IjYS^)-p$ScCD6ZbSdw&BiHSKNM zwtMp~Tt4fPT^Dc09aUpf?cS#SXwZ)0l{-X(w&L-Im3#N>X(+~H>h7`zqXWe|cidf! zPVH^lAwD2`VYHz5qJ}0w3#Gc(U|w0$aB)LJ6WY0X=gx-49T#3?YXX?<3Iqi=Xj>8B za{-RU+Sl<{ga4W!9E&wlHJh-YY9`KuS{agkmx=Rg1UNS0+Hx!`*@h?gVB$#sHQ?Bw z?SiSf0YQ*;xO1`izZmA{6?kGHYTkq2&A7frd}W)sdI@TaAszl|@$_!gO5ZKkqWD@F zep6j9Mp{v7>t2+*(3fJgFsx0-QMJ};1GT{E`!jIW`2Iy^d-q7V%tR|2ad#W~wG+oZ zXwL;WUQ9VC#m|Y!wC1Vv4}TKZNV4dVdpQ_W^DqYW(t1OV6<~br2fc6rq|`u2$iZ3> zWZF>3rs3KMZKO5|Qg93;Qn5A;-stg6X91SOa#voUNeD`w+Z2^-HQu!(FEo6Jht6gHJjW7F9THj|yk%2+uI zvJeZi2#c}`R>`W^EH<0XVRP9$HlHnE)odYK#A?`LR?C*KrR;Qe2CHMs*qQ7sb~dYL z=dk5$1zX8hvDNHcwuY@`>)3j>ft?2yV)MLm+fPhvCG*NY(Kk_9bnDu6YP`hDt0wH$UeocVV`E#vg_FO>;`rt zyNMlQpJ6w%&$3(C=h&_6^XxWuJ8NNIV0W-P*+o-)B#=AFv;? ze`n9I|6tFu|76dxAF)>UJbQut7yB`Lk-fxz!hXvBo4w5bhaF=-V?SrVV83L)V!vj; zVXv^?vRB#f*zehE>~;1B_6GYSdz1Z%y~X~_-e!Mc@36nJciI24_t@Xq-`PJf0s1HV zfPKh5Vjr_McAT}d4i;x8xW*ahT;~Cv!*h8a&*!~(Z{CL&@V>ks@6QMDLOzfW;)8h+ zAHs+7VSG3r!AJ5@d^8`!$MRx6j+gN9d;*`yC-KR=luzMP`7}PA&)_rpX}pY=^B@oL zFpuykui%xuiqGP+`5Zo%&*Ss?0$$A*@{0saJekVW7zsT?6ck_Grm-xN>K7K#{GXDzyDt~}~ zjX%i0&X4eK@Ne>O@rU@s{1N^re~f>dALZZSkMk$^ll_)qvx`G515`Ty`^{Ac{<{1^O}{8#+f{5Sj+ z{#*Vk{~iB5e~rJ+|G?kif8=lSKk>KtpZVMTFZ><;SN<;lU;ZBd8~;212Y;XclYhWJ z*wlg^tJjreZ9UxKTqGNpRaGyH|tyU3-p-Ypl{W;>D%=k z`i1&MdZT`^-lSim@6<2Vcj>$JJ^EgKpMIHsxqgMdU%yg6pf~HE&_AhPrC+Td)IX(P zqkmezR=-ZaUcW)VQNKw)q<==gS^unli~c$NR{it(ZTjtcOYXMDD|T)V=3#^^uc$1O z$8vd8*F*CEuslZOF)EK0@>nU4Rq|LZj|=5-kv!JO<6?QNHI7yC`&IJ$Rr32)^7~ct z`&IJ$Rr32)^7~ct`&IJ$Rr32)^7~ct`&IJ$Rr32)^83~D`_=OM)$;q*^83~D`_=OM z)$;q*^83~D`_=OM)$;q*^83~D`_=OM)$;q*^7{+r_ZQ0VFO=V3D8Ii@es7`t-a`4k zh4Om~<@XlK?=6(yTPVM`P=0Tr{N5t@y+!hSi{$qf$?q+a{aqycyGVY2k^KH5`Ta%m z`-|lF7s>B0lHXq>zrRR+f06utjr@L%{C_5t&*ftQKnAH)k#pDgw#n`okY}0RGn0)lS*}>YOYi@SE`yTRn3(lqmK(K z3_4#_Lts&LR;sU5npf0UD%Dpi)mN(2SE|%ks?=AiRQsz``>RyVRjTGHRdbc9xk}a? zER!{3Zf$feSSD)@mdTofWwPdAnXEaedJ!y>H3!RN&A~ENbGfRyT-98zYA#nbm#dn~ zRn6tH&B1b2bGfRyT-6*@H3wDAi1sx`-k_>EsA>+XnuDt5psG2jY7VNJL#pPGsyU=; z4yl?$s^*ZYIizY1shUHo=8&p6tZELcn!~E*u&Oz%Y7VQK!>Z=6syVD`4y&3Ys^*BQ zIihNgsG1|H=7_2}qH2z)nj@;_h^jfNYL2R!qpIepsyV7^j;flYs^+MwIjU+_{1{aH z7_3k=SE!mRRLvEt<_cAFg{rwi)m))!u25}O{1{aH7*zZitW-5us+tuK1{DtmD^<;k z2ZM?SgNg@(iU)&=2ZM?SgNg@(iU)&=2ZM?SgNg@(iU&iA2SbVnLy8AOiU&iA2SbVn zLy8AOiU&iA2SbVnLy8AOiU&iA2SbVnLy8AOiU&iA2SbVnLy8AOiU&iA2SbVnLy8AO ziU&iA2a!6}U{81^)QN<1Nbz7u@nA^tU`X*`Nbz7u@nA^tU`X*`Nbz7u@nA^tU`X*` zNbz7u@nA^tU`X*`Nbz7u@gOpO$u=t<3@IKADIN?d9t z@T_uzXO$B?tDN9jflpta5^9l@mOxoZwmQ1kY+GcvdIE(_n#! zl9Lf7CnHKuMwFb4C^;EXax$XiWJJlyh?0{LB_|_FPDYfRj3_x7QF1a;p2TH~A`V=Z zcv1Eu@uH-P#EX(D5-&=sNW3ViBJrY{046j^QbppEk}47}N~%b_D5)axqNIw%i;^l5 zFG{LNyeO$6;iBTQ#EX(D5-&QzQ*l}1lTPqdT$cEx6Fe1{B|hl{PsL@4PddRR?IqQeKM-_ z$*9sNqe`EQDt$7l^vS5wC!61~VPezqK8CCjZROypZ zrB6ncJ{eW|WK`*sQKe5tl|C6&`ean;lToElMwLDpRr+L9>61~#gVde}pMocpPA>6rPA+WYHoDc3>w?hzJNq|2%hWCh~@8+K(?1Spo4MYQV8KLN#V;9HBnDw0@17_cY;Yy04*jig;FMbRwQ-69R7} zJbxH;QxG{g4XvGl_MRpJVS-u+ zHAfJlQlV99RR~_0ZMJ2gXp87;kA8p7<}LfSvJ1B~UbdC(Lb;nAxB$}~b}P+!*h5tE z5jzns#rN;ry=ON+NRZ>#lLv_3j55S;LmB3WQAYT^D5LxVlokA&C@cA6D69CB`}dcZ z@uyLi^Jh^8`3opR{HG|x{O2em{1uc@{yNGE{uatg{w~TY{{H^`!7|>4vRv0t2K8K& zA-w=)SRaToq7OqE)yJT$&?lg*)Tg4X(#!Vm50&Xrl;!#yltFzV%85q%?aPDJ-;i0+{XpCZKY7<5A6QW~Oth#Gr~ zc%Q+D^O*>GB5DfZUKoD{wRSN^$dytx(5=(AR5X&ONF@1{QU^{TcmxpJMBfOC5(UnQ zYpI7+>Y^mBW`G@)xuP_9HeZ}mj3WcIs5ELT5a);~e~ka*!wo zi?T?RLqs`Llq823BCMz!A2+FadEEeTBQI?2uyeKD#a-t|FiE^?i zOGP^T7OP|N&mV2s{W?_ zuKs}@599{=1%?F11SSP$2BLv^f!e@Xfz^SHfvth2z`npI0@ns^4zvXB2|N&ZDDZgT z>41#14fGb$n*|0KI1c>YI0Eeg|2f6-SSGIPISX-(PgzfZq4{ci5}Q{!(JV(4PQR|h;e7c zgb}qP?l8VT;tg>vf6THm-yAzgR6l@}Oi#WA;{xME+$2bKFOKd9O#uJoxoTJ`#@0kh`K zYMga=R_m+}W|z*sX0|~cedZL;Icv^ibACU!-`u5hZ=Cz=+_rgT^R|lja^_t%@2B%i z=GV`EQ2fv5e|P@7#@T`~3pN^*eu~Jl-OYx5=Xs3(gi*8vid^Cy&23jx}55u}L2H$)l{VM#h^j9&B7+ ze4#x4P#$HR`r^0b|L@7;N5-)heV~5S_LIjU@;F8wvGPIO56j~md0Zfm%jI!{JU%6l z^7~6Uhxvruw`7jMJyW&Gts0*9e1EzoMU zWe6Bvqisas?}Z4i-G>}K2es>wq4PGZZM#={04t^*)1K6xhD>}Ab<`q%4@YS7S5Yp| zzJhW-vRQC#4xZ)Ey}pEU7S^tE=wf%FtkAxQGK#u5w6r@>hP69ThOnCy_H)#3M_Gn% zap-fOM>!L_cyQ=74=KZ9}#GUsz>fH$I?q}_mWB3j8Y7SiapM~wd^I5Wl| z8q;Z9n+zSG9D3ngvF5>e4%os|3_31hCW4Z#)6+L0XLbu#GTe_|8_(q97|_0q?-0Ej z&(XWa^(S%t5^;U4`2Gl-55^z0LqnhGjYj-&e7y$8UfNo$LR+tG&^7_8-_jn^9!_{~ zDxR^wm)xRM?~lTJLj)|6YfxWiz%aFDd&2h&nyL_YE<%|H_|5>d<^W#vfZfzBR$rVc z;H&}5RJ(H>nSPrglw3!zMj^PI`o{n}f{*$ZM|-cQJw`wopT(1(11;r&s_ufWcMoWC zAgJ@Jc<*bV+hM4OXa(%5eH*{y@n1f$u>t>Y!fybO*$!>!A_SP{0ymF9yLk-1gS8*w zw@7Q#I#832r|q;uGKT?TRQL6u8Vz535Rv1?bAxepgjOutte(X4?0;}25woPb5;ZE? zt-?7&e~5BsptUS;54d0*sPtyk{|xk!4-j)c74hI35vzSG;;5fvzh`ZTKc0!$;4RQT zS`btF0^(ich(!$|PIN0`I1lSbp;f#U;DJ%lA?gDc2ChL%^P$ZR=DGONHg*i<8c}XF z@8pR)KNEMTwC?1KJ3kk9sI>0%5_f(f?oesn=`HU3Qrw}^y3R9bh4ySFjSxq%-ltvf@-oj-~@ zR9bh4=C`po#T_cGJHy4DKZ!e3T6ac>J8y|QR9bgNiaUQ6cc`@Pj1qU=7I&z$?u-_9 z{vz&BY26tk?z|)JP-)#6EAISN+@aFCQ!MVhEACKf-5DqD{I9q}rFEx7+<8yjq0+iT z5?~wqo47-zb!URO^LKHFO6$%bcj1tZpB}|l&m*l1Rakt^cMWzg5QZ4OYg>LeLsFDV@!Pj;|odODG3tN z7_&&PVPd3BmeYQuyd|OMpf(Nq2+c<)pbca@ng(e~3zl%hLlB5zO4Pw z*0y~+$ZMLOQ<3~F^6sqA%=9GizR+HZR^8F}sB zCH>3Lu;!oziPS=x*(S{2w_thLziHp46(u%&PQWNit#x{jY3Jcy`nQ*-c-t?|Q++1fhUQc^S$EIFT>ktFze4jH!sAld0SJ%t zL_Nm5$@}Q;SLVvWW4cHmHt)jW+oi-``BC(a>e>U(IA(0tK4&67-9a%Hp6yTm9oT^Zr>d ztxkfeeJ-G2YJbN6Hxp|#V47H?|DF>}5uu@P#VFhYtNIb`D6H?Tu-m_)y{WyYwIL4$ z`6Ek^KOzc$Ob|GKGwaCSA7RLJoyzkRRdaunWG8yasXPEEtG<1e1_! zV3z5pY{Y8+tFV&)HmuTr5G(MX!s_~;V&(iBSQY;fR=gL$Us;Tm>S3%xUkZQa24FV8 z%QOz0(^~!<&4D+ncJX>a-ooSw(WuBdFp4FRY=a^^9 z&9fEe*-D&=NFBb)JX>v^ook-0G0)bTXY0(f_2$_I^XxqHY@>O0zInFEJlkxZZ86U- zFwbJ5*`?+gv}*a?-R9XI z^K7qqw$D7fOq@aHf+H@2%+(<)O2n9~H0*_-@f_%8@R|ubLMHOjsDFj1U&s7;xppmT zHR2&OB)m!ff*)Jg|0V4~)|L(v{`G)JYWVwu4q?`Se0O1XrHyj3+w$(KIhRx1i$U$= z|4R*RroFCxjAx04)msdW^$QKYRq#>i-Ya%-y6YqQP+X6NsqrPSImVkR#`bK!8nFeWDCHT{j#j&#$ zmLZzJnpKnBbAHmd#+6hrdHJ1N(Ag%gT10y4B*X>GN%^jmt&HYcss^% zm+iG1?Ne6Z&d|-I{>z^>((@S}jV%v(vk& z@SONy+hgc$>vr~U&aAugdSyFuo&U4^Di2=9d4}<*2y!1-&nK5eEN4n0> z`W&O>jJF$V#{6h;_p})~_;z$t%@^9>Og=j`I(LS%|9Pc>$8l!MyP^IBn0H70cF1=} z{dV{V)4)Gj?$}{VdOpS0S$cjl_LAR*RDgHJUI%HcKEH3mz6;+s>&WE$3O8N(zN#aW z?MW7eND7=bBE0xg^h?NRum6*rdtw-o0A z%;N9R-q!vq&V$HwT*g+~H2pEaFi9v_L@{<5Qqvg4(no3(Bfe>6r#F3Tts<5oT^xdH zAMCW9+Gow8~TShTU)-&j&@^PRPD-NtLOw?1JH?q z6U9rGcg;>w@G3YwseDm1V>f(?XA{=JosA3@aQr&QsH^YWpH|;bw4zuR9rX@;84k2< zk7po{pb@uke>O8s#+a`s#!FcIn^?aeYE`{WRr5Iljv^a6`kQ&piu#737Q>RM3CkEm z+$^=BnMz}&ijZ(ddkpR*TkQzE7sUDulDx~sv&MV0q9E1ViL!kKo(#aQ+K6QR@S|wl zu)f2-djnczS@uSVZ;+(s$o>A1&t8*dcLDxX92ERlzMw`cbr<7T6BNtDs)|vvbtEro zMF?3TG~usd>~Wv!Qzut^=9-BAiq0ViX_TZ<)aUrLjaCeKo|UJ?x<1b>F`o9?5}!}Q&kea~`4`6s zf0I)$@to!DN_d0htn(XP<L%S^wRbO3g^Qmh#v|AVs1bgrNJ z{UM&P#$d{OLaPnRgk?o#I?xacoD41N-NaE`!LG~&b~d^-A!0z?>zil+ztO~%pSNzx z=&eH;z4a*GGW1%dRS;F#qn*941wD$0D#Lr4DEI6&5?;tA>qpy5*FsudJ0oF*u|bP= zKDB>8Lk$_@iNA^SMwGcH{w~gGO?$40+M<=-bWTqjtFv>lLl~WZ8DE-?{RF-2sYUq$ zysk*(u$E2G;@Sy(gfZT=9mvqe#I{)+A#QLAKx3oWFeyT>fceVdT6#L(_zia={-q-%AeV~1a zkq;xT;}=ugT@s-$;&f3*(p5Px$rURotkH8YzV#jLaqS7S?5qJ1PIeO{<$AhNS|7C= zAmZt{aZYD=CiQrp%O1de>&ql_u`*fvp7xaXeZ=+uK>K&x?>tu1*Z&Q7jIoWPFx3y) zy^#G32fC|aj0V{8N7g*jmFS1A$9fGW@-ZTaRPfHA2+U3-ciz z^-Mzhm%>U`it()sqv~p{0=mpbXna*z$GZR-g?1rdP7Um5wfMapc~ACe`(+C=$O+YfeJGKg4<+*Qp@i;; z68alT%)n7%7K;*@_fP^;D1kGS#iA?`C8!HmCW#VMfOBNhLx~)ED3Lu6W!d}_Ctmr% zi4&i}@wYgB7ROg{{G2>~9>?F||J!l=J&s?%@iiRpl*cdP_&WZ-8^=H3_$3_Q!0|qL z{4$Pz#Q$H#@l71RhU1@b{5pWFdw8(-8SktcSLQ|*g zhomo4=0mc1P}aj#G18>{r;3u+Al(g~qLxuhsMd}6JBYs{f}iqGGwHaK@jD0DnU2Wg za{Mm9{HYT5=ehW;fj?mpxQKXY=?VPc?Ht=L_0{+e^`3q#NRTukJA`;fyo-C{h-Zzu z7TAvB?SN=mu4q|-XjxRWtWvaWmS`E}m!b?))UpLeJqcR625l#G-ycipJN0W0-o6R- zQRa~aSTogvzEkFr8j*RVR%9MgeJ4J&e(n9WpBufVlIXzbquKYXY<&hiZLpvx@wfp4 zB{8XmQewiQprMVHyd=Dmyqt%>E%@7mzXSNYO7xLNGs4C^{EorBJzBAZ7WKmVp=Z>O zbxz}w@x!kY$9qxEKkXa{49SRy%#4W4)X>TA^ZlHAcJFcf>$DUr}=U&T41rr3o?e3K^<{G`n+!9X$SlWuT=q`00?V^Q0i#~ zT3&Ey(Sz80PsKf1<@R;vy9LY7R?Kk`b3B|mE}{O9p#GOq|3^~)%c%e5)c*={rP9(= z$AxUS<(yP;$dz6>N^29Ym?G(fK$vBrbQvzseD|EdNUEC2Uox_pC8_*9Sd%gbGhT zbRUp79cc^m-Uc=x9%c~gCF5uxPt!g&QX4j5O~^aZYBCd}5njX$w^@iz9>qMjeHan& z7%cA_nBjH^D@`s#4Qz#aLT95TZIy0;b_9cZka|-`0{lbDmmovw%Oh z_x%Vb@w4DB1}z_qiIHdcy1q;$VsxI-$~W7)ZkC9r^gLshSCTg7DP!b~wggL`$Xm2g z%8E%yi;=cd-c1-+WmC&@P)5!`U1tSGb(GdgC~>aE|0Z4);+eAm;fm$WvB#C-$DAMh zzI|Q2@}bhp_lwN8^Mmr_0LBah88ZxG%uvXfVK8HcA&eP{q;*RTt>a2iLu~x%@+J8V z<~W#7s|iQ2b5G82F?^Hp|9G6gzvXb#~Xtu1nxIBXr+sA*|^!xu4X>%p|X!LEFiPB}cpb(PZj23fO7*6UAStnsj#i_wnhe1ohv zH9xIyusyZu%!6GkE1hq6oGWpq^9{1fS4~>wu#|JJ(wT?loYxia4W?RZz3AV=kyQ<# zni6IcHY03KcnV<)!j^=s2u~$!O?Vn%8^Y5G+Y+8Z*pBc_!m|j^CTvf54q*qva|t^V zo=4b;@O;9~gclHYA-s?VWUV z2o%1z7h!KgwCaPBzJ&b<^9j)ki2DVE0|*Ba4k9ch985Tbu!wLd;V{Bt!r_D^gd+$` z2}cr^5tb8H5LOaa5u!&5G>j%3LpYXj9N~Dv34{{~(PEA7PbQo~SWP&Ua2nxs!Wo1! z31<<`CY(b!mvA28e8L5U3keqyE+)k5@K-~)jBq*O3c{6ys|Z&Ut|44YxQ_5C!lwz> z6Fx(@fp8x6F*zDf8t;h`%rD&bj-Z312d{3qbYf&UD=8h9^o4e&nTrNFNMuLJ%Y zusrv7;BNu{19&~~eqed-0PuH#4+3ul{x`5Z_aERVf&UA<1^7*1dG2lC9{~$J+koEz zmgj1Le*%0Mct^G3$c`<*2G>6a&H~;E+!S~la5Lav0iOcA8@L6qJl6{Nx4^A|_W_>< zEYF<|{1WgPz^?(f1D5B`0{#=K${mJ{S0}z~=$K3ET--p6d+!58y7q?*d;4 zEYDp8{BPik6=(+ThHH5)7x*pU?zrC^xCgG~xeo!?0!u4-Yv5kM@?39V@yUIF+W}tz zEYE!eSp4uufja=_1Iu&$fyMV00Cxty3Rs@I8d&`8HNahg2La1-g}~xt2LpEp{v@zG zcP;P*z@Gx{1w0g3o*M=%zO)#)FYxui^4tx;J%Dcn?hiZySe`2dz6^LI@Ic_3f#tbd zfIk9!EAU|83SfDz68K8sD&V2OcK{Crz7zNw;LidN2Oa|~&y5BCB=9)kQsB=6%X4=F zUk7{-a5?ZqV0ms5@D0F|fvbS;1D5CR2fhjT0pKyfQ-S5VX~4GvPX`_k{2;JA_a)#v zfFA;$1T1`%=Y)@U0S^JL2L1%FJSTj;8(8={9a!$mb8`P)V7WgFSbj&Ilizs&Sbk?N zuzX*hlkYzWEZ<+?UCVQF{bgX0*J9v-!1CN6;75SvcjeGX2B`^B7E)8BY@}vL&5=$) zYJt=esTIWd_H z(GN-NA|L5vNd1wnL@GeK3TXh+)kwmlYmkITA4d`%37>`c!e`;V@U;j@_&OBnI;3Gp zpGGQ1x*lmb(hW!@NTUBEkUoP{igXjwNTi#Q%8+hBDo45%sRHRXq)MdQk*bjHKpKT~ zC(>x7&mxUMx(jJ6(&v!IA$=ZcJks4r6Oisfnuzp&NRyE6MVgFsALg~ZV6ri{<#(nT z)=PiUG}ph^zc;7ooqDHfpv}-lO-Jw))Td&*luhS7vePzv#c1Gxa{b&zzeuz_CP)8A|HoW})!pAPUG;zUf6c}EP5q|nrr*+UnM?HB`fZb||EK?FF4c#yJ7jnL zj(*4V(C_MZO;25`YmMlP(!wR}CDHPBNV{+gTC4}d2C|{^cHFk~Jr;5tgwz^mah>0X zF$zM)K`iGuh_x68u@ob3Fb+a(WUu4q!#MDZ@%zb>y&LR-ZtUD2!yq1-5 zP9C)RGJGb(x-UnoP(OU8!?N=+s-Xa%nXuxkAuFL^4ka=dlG&){d2&f))QR+p*XN+o#66;=(<%QMl>aQs ze>UYmm-3%S`Ol~P7gPRADE}JDe;G$^tl-Fvl^nUTiX%5x!%}N7hD=6otfSzW{HJmN!SyB=Qzp4%jm< z5e4h9l>M2sXItvbp*_!~J

    (FQ7dyq&+XDJujg>*U+9<(w;HT0H4*g=QSKTx(>ft zhPiz*_Iv|Jj&5YxyF*5WLiaE#6!UmxSMj|vDpaM;tNVUjym@e*3z$GI7&+k~jIdXt z1#U!QtcKuAlrO$b+)!!m^Z~4ho*rnaHBj)t*+}9879nj!I!HZm^GPJW%|p$j43U+L zn45w~_J^w4Jc)H4yl4X7PsPVCaorh+3CYMwC@3F%B_Vml%E8SelKgdI&MK_krfh8i zA%2#Z1EJiNpUZImGXB?&e0HoJo-|>Yq139JN-HV z2n+E$copP%s%_%<$YPI|yPzz>v1}`7dK1L5B8w4}!V<=#D;STiW<2^7->Eeh=F`UlAO`nH3S%&xR}B^S#Cy-vJyTVwVABbp?KBZ-h@+YbQ zFAKEb9&i8QVwy}HHt0_pI`?%Y69Jv2&(`hrIl6;BS9jFs=}!85-C17%bLr;Ukl2w} zgtYt4L|Tm$o)0gcV*ujkGK_4IcKadp97XgTWr%~&FjinJdMRYJj{Y9atJHUp|L+3Y)n0|1 zfgVjli)*(PK5as>>khfKLCWxk{3JN!^w)Aqu6%yTdGOl*kDSS2dEHeWNNJN&$wPq$D zf}VjfNwV_9YSyuz_M{Z%#nE25{vf|6$Uh7B4+XF6cN`4Lf$XZ2N_gByY;=O}hUHLQ z7pL+*v_DoJatDuDdAOA-gj;r{iW#hd%w!E@7Hc50Sp%8F8pvGo&l}T{mX(T?PFBX1 z7`K*@@nKkNo$RA7W78QLC^j6hW31$(^Z+Eg4u>0;C7L-J{-Ok>gOnWM9$%?5@1VxM zK#hHo8oQGkyNeq8GE0};j7wi->9U`t%Yn4`fM8h_JP-Po@O}RZ>-0cxq|^iXn&ILR|Blrb-vr zeGNLPJrJ^8fxNpi%K4z~xaIc+g!_4-VPxQV=y3nP!>+6`*2RSETSe$ZB)>~c35j~5jU5n{|meBvy z(Elu>|5;A|vx5F-B{c2(YE??LOMFZ-`gq@~AS*HPOZd9J2l>F3K}pwfZpXZwOdTgp zb)2;9aidJTBwxpoeiy0Z zNWaVNsMCmk9RE6wuLqgPLTY8l)P|NG-O>dC-{ack5w{{fRp^lus=b~keF4TMcft-c z>y%rw-Y?OQ?ToF&p@_iMCW4Uw2x4S{{}G$&x|*cDZlGrAtxik&y%~Sl6ZylYm$}UJ zHkX?|<_go-e8luKA2s>rW7z5MN>gC2G6T%jW}vyo3^E@#h2|63!|s#V#qL^DWIknv zn(NFkgBi6L>tkaVq1d(6$DgYyt5p|rWM>gab`IxQ-ZGBm9nG=4<6)yusEMR>GC953 zi^Y6PO8o5DWa2+9S`m)Vf>%Ka$$2nR!($0atjYT!xuj$#HMT-}RJR_zc+_In*DKi{ zHJ0`Dagg01H9jLdSTpq|JywT0H|7lC9X7c-x6C7&fwVQ$HY2hdN7+qcO=vPp!zmnT zJ%{!K^Cr^U%uGm~}hMOBqiMi2?FrP7{<|Z@J z+-%CsEvDSuYAVcarqbMQs>~f`l)2N4HlM|Qt#_HR=5uD8`Meo#?luz)R%}RPc_*CC zly_p35^1?eC1sLJN_NSXcNy8m^I9pC-R0mxyR%oUYD7YtGzNxTJY1e&wyQ#-mV&NS9Z6nuiF*U?JDMWrE$9&xLvVZR@HV# zYW5EZuM%k2`nT)xCrhEDOK^s^WF+fzE^2%m?2{Hbnab)-?uZ)S;9u&FsL|$okm%W| z_-S`Ujc>9#m^-3I8^#>h9Z{oAR#S6F)My*#2m_89ZL<2DJEBHgu_FvPYP88}eeQ@F zZL*4>JEBILtZwLzsL@v92m_89ZL%7qJEBJ02uB!j)M%5{Gu;t2+NA%`9Z{oARvUFk z)M%4cP~8zV+HQ7)0Y{CtvIx_5ON41Fk1%bwMwm7kf9Z~>*}`p(FyN@sRvBU1ZjUf+ zRS~95R^xU@)bwqXBMdldw8`q_?uZ&~qa9(uQKQYS_ihP{9&KalI2S$IWPN;lM31(y zj=8Y_jd!F)kZ-bIg*~FjH`zxcs9K*du!SHp!6|LB7fUG4_Za-(*B}RB5}Il>JK!j_B)fVMoIA2s;s;PuQ980>UnY7ZTk|;Q+#cgo6kR2?rAnAuJ*sN;r(L zm~c2@3E>FBQo@mhWrXE~6@-<9RfMAmM-z@A97{Nka6I7z!ij{F2qzOxA*?2xN;r*h zI^hh$nS`?lXJ2`^cF41bYkvb=1iS-yDDZEAhXKC;Tnzj>;Nie80+#^)9(V-sPT*4D zmw-nC?*c9Z{sVA1@XNpzz}T_8wi0+Za1}6iF0UO0ya#wRFm^Dn9Rs`o(B9+;OW5F>AZFZ z@V|g(0v`mP1&kffYi9$$4m<}KJD=Ci1%3ng5n$|qUi&EUo4}6&zXkkN;J1Ol2K+zZ z$AJ$4e;xQ8;BNrG3yhuEYq2wW?Kgqn1AYP+JEYfQC-(Og@V9|A@OOX>Fm_^puLZ9 z{y8vqRImL7a2w!X0-p~2E8w=kzXm=7_&31qfPV{oCh+fo&jS8E@Y%pG0mDLhXh4J1 z1St!tDN;64Goi^Xb*kpso0QF@(sRIfk(VTq4?HgRQc$`x z(ooKyYQcSUT5%=GA|7t#UXGgHhekxJE-v(R!a zSGf1YZ@Fs?s4-#>P99<_kcS?GG#_a@<>B^Gk(E6sQXW$&k7<<0OwQw8#ChB`oX5SA z^SIY>9`{DhfXd7lImfg6lP1iH)?(vZ>yhsU`FUJ^$phafA|#dUcK{MLs|wkMOl zk?~tWpBHr>h8Bdnc*EJu4%|XrP4}aG+%6PSuH>;?>81XMeZu<~;e92`jP=LF`yjtk zbslxTR|NPj5vJ6^5btCCbp_tanscrW)?f9zp!qX#{*!BMlGD0!5i>HIJGrqz%G!z$uWYz&)9PHNqlU3jb zFc*c)#ZcyA7;{m=T$D2xqnL{^UM`+fzf{}Qudw3BudyoMZ?KcWZ?W_94(yuqd+h!E zlG>&IfL*=+h<)t;r1q#kgZwsL9)xzsW3;Ma{a>Y?CXXFO!s7wlXJsI3S3|f;RuSsD zcY^D8;WG?RJgthct5171ypFH+`6g{2spp*Aqg>Xc=|c+plfnX0IDixmB!z=K+BT`5 zs?F+WfLl<{+p2y6qPlsM2|t{zSK@pzlC5XDJ#}uvd-D07L3~dk-!qu+8N&AzkrP9` zx7k=xYWMC;R_K>Q-i7*-clcL9J(BZa4GdrA@8a9Uc;3E8^a&c(BzVnV77dp-%YHf1 zYH3Z+3#Nfvz0gs-B^r!tRjq21|| z=+cYGj@_Z*6t*K}gFF4f8NL~kAKI3#03Ne=6z&(z+~1u#9!*e&iuN zRxxo$bvW)Ye%v8v__~cd8r+vuchMK>9DR}QsxQ{v^d&l1U#h$79=fOgkj~Q|*1hy) zy0^Yu_t97AzWO7&pZ=)M*B{gU^_99nU!@1=tMx#A4M^YW(I3>+J-Cnd;+Q@g;bjqd zIgj;)`HZv|vHfou+y7QG#$L}DdlO^qE#&%hDdl=HeLa5JU0+f`xdo*q=YD&==vQLW z3q+5{TZ+iaf;e(qi+iu(e_a^sxR^=gSV%d}=B@^FxNpf^?prdC`w}ZJINL!6Q!cJP9<(pnQExuwvVd|~NVzPcTozLR%>jXQ5sx1D5h=OF!YU8p~y2kTGjA^KWfq(7yH z>g)6{{b^mSuh+x%4Z1|%s7L6}=u&-?9;t8EW%?Ffu5Z;9`ZisuZ`W1&4ya7Bai{Py z6L-RGC{!8gJzT9J`M5J>QiL3njXOn-nYdHN?I80<*ekZ-H7Yp>&r=s=qME#9eO>(td zskylE+V~E6hW`;eU*cjfV)-F(U;18Md$aIAhy2ea|GQhxhsR|nF}_cJuZXqHV%9cC zu(m1VG9FW-+1ff0waulfnyszV`OcYI_PCkl$=tT$_-z7>pZTo@($|`rk8sNIS6H+EiO_Ia;g<>W?ch~v$_Lc6qAL}>$fGT&|8G$Dl`f_Jx8iuD_G~pCL79|OCKZ&) zD9U6sWio{_sisV3QYNz~liBPEn4_hvSdfuS!ezzrl#P@d8QCOWZp0e{j=GFS|BLRM zV!M$tH*_+NM|fRxvT90gFs-sYj~Z63zn=3@A(8a14~%_Hck zN>OgSr$#nVvyf8Z15snpGXngDE_00iCh)c`Qn*jw#zWGoT1@^9Cx1)G-x1_*DfwGQ z{+5%!73|Yr<#Ac&7Y2Le<-A#nYGsM`?YmmBt(^tyXZDWk3r^*n)&yz_Y1DHmg)@4d zK0O!yX7oJ4k~EEaE)h*e&(o*p!rzRZC#vW9fpXQgGsxVv?MUx2UUXv^lZ{&>Iy`ng z2N_XqInWgzZBj>dF^SBXv2*!@^Zt^JNrZP+hi4pfOp@4G!l=d@d+PI;T8OL^jNo)t0%yxjOYzLUecst(cPKmee=uSIQa2Z<(Rx>tS!&ZW|Y`Ixi zuYJHqiGE9tf97VONYof?w`q!N|6A#6#R*%C#ETJ?Zp?Z)vaS^7&g#xcvyiq?zW#bS zH+t*kR8zjPYEBhqpUNsmGb!IijQ1Ba-e1CazlQPtGRFHW*b20g@?A&yK1KOHP5G{8 ze^HB&wTXnA^J8TlhQ5@Wvwqh43pHxXXz7>ZhEh~1_mv#NRgFiY@Y7e$P;jmmu zIdJ5@xF+8JVRz&hsI#0a0G6jAxupB(LTe{QM;CmHwYuP6`)5wtqAx)kZ;EUGTdC58 zY;ssi9m6vR!go$+UC{fGDalWuY}K7`IRU zb@tvB@|}ycgEDT*vfa)CpTe?zI(`2P`u^GU{d4I1Yv}uz(f2Q>?_WXRzmmRxHGTgY z`u_Fw{m;<%Z=j4fQpQ_Z0<@_o>5SEj+~H{~XByCh6qEoFvU#aPW^7(HAZy-dp_6ne zA0e9;8E0(XMt|>H)EA2RygD@G5`VqSBgOx+4$YBP&0LC6otPbjb`8u9!b&r1@p+jg z!ETlWdsqu_>rVMGn~m)JYyZrT{~JH^dlUTUGqpj~6oRAljI5g!8}niQkBkMtDwa~8 zNGu)@U8v#My%ik0w}xZ))^qIM7LMK9PF;9`y0DYFu$Q{9kGk**qk>l%6&$P=4`ejL zMgou&dDB>Aa15mdDmYQo2`M!)Iw9r7`_uyoAAxZ|b$%in=b|H(NLULWwk&?atp(Og z^}<>@kKi*9tEw%)?4Q^0DOCT&XRvOiTd5&fyCDxN$Q0}1x)o&nxtF%aj-TcGpy@os zN6C4xhK);FLqk>(YXxrfxX_kM+Pagro}?|$^E&I4{m9r2KhLh6eiH84^#&x*Zf(C? zQBTUleD@!!zFOLm^Vzpu#VI{!y=&=-c;Lb z@+45VaIH$XFHZp2_)_{Za4`S=r|K{0Y5I$Lx_(g4&|lIs^+S4={<5B} zzoO^xnRW7@ms}hDPTcB-R7G8JBRKB6oMlH9%Z{-uJ0`L0n9fx;X0z;=OAgN` zO$*844dn1fkHfA-C|KSndM?i&5!(dWrr9*9{Mjy>t4!0qkmxR7BpmalyhHJC9)~^?5k; zc_j7Ot}i*B`aGHXTum-cBNtcH>2RVN8~k1(oTojXFj||CWWfK#^;Tw?oh*90R4>!t z)XVh~dWHU$Ua7yWSLyHQ)%v@7jsBiqtG}<|gG1wakF2-Cn~dHzR5m8#Z4;6VeCoSM zu8eg4wgUIW*VsNd=WjVeOwZBxt6X}DqB?&o5k(@F-?+~W_f^`MLP{(gd|N{iRv>x^ zwMcu>1KW*V^ia27hYpUHb$Yo8>}Y6Qq0}vpG%swKvh%{GkxMhkrSQ1Ips!5yJQ2<& z=2OGmbupVC5x6og72^tz#Q9`(u0}tc(K*DYk7AYO*tv+l&Iy+?Iw!6|JXNf?%LZ1WW}$2@H2nn%n$^Qf6`9y1HfSIt86HM7V(ZZJ;nD0%awdGdtj z&DYHm^9@sDmYQYen`XIr!mKdgGAqru%_{R9v)X*utO0M@d(sq%ij1U!Io0_^AHYI%9 z2)?b9ZyQPbEAzNkzm=@<=rvt!V2SdlqsN;df68#Le%p!c0dkVmLCr+-e`b=o*Gx9| znJMOeQ*9nFQ_UC5H1kC>-8^V!m@h$Pq(0+x$Hn^b+fHO}K7X&oDsQjEQuY^(VSmv? z_7qKGPtoK|+X*c%PCIR4l1WN7qPLm$ffTe4BrF^8AF+0`1osZ&c3#NmCM%mMda|-@ zj&5drc=TjL=OjEXR@h ztpffyVd){T-*Z^5#GA&H``)$FQ`b6^z9p@ugk? z9tt1d2=Q?=#@FYmG5GXWWAW+3Jw^MPFJZ?px1x))$3*HXneQAO2SiCk^PMkq=b}Nb zRS*BB9^ik(_^#6nAIlGUR_5AuMaDUlaW~31*M1u-mb`=3;ab|3eY?EX(-*7_D|>Ir ze$!&HZl0L5kQTDXsfevr!`b6h#vats>~R{;9;b=uzkNVWMo+5tDn+O@?}AvvcDe9*5Ihh5$KJP)ga8;%KP)$_UL37mTa%`bYX1{bRjBKdU$DpXg2cr+Tygnckv* zuD9x6=;!n=^)~%0yh&KN^->|%`X2|-5cevYrs z*KfzI*YUp&)KAxEEaPS3$JTN8BxFf2)!c)U!0j{~*44y#dvyLV^OOiTsCn*OG%g)B zzW_a7vM=RG+I%H#ew1fVd(~gmKJ{0$!Tb&7!QahVjAr@=Ecc(74f`+D6#k8sQ2v9F zTmMyWs<+hJ*mYEyZUMdY+H!OOvNREC1@$uC+)kPGH<>d(rf{^*9QuLz^aG3G2Nr-X zp>i#$Tu&-Du;#vrHTP|-xo>C9{dsEe&V+nIur(;L^Z44I4!w=lgrHsowZVF$WA9Tv zQpu2FErFiIY17iZk;TzSi;j<;a8LCUQ%_EA-H3n>;oiw7rfvj%(aEhRQSc$M=lqGO zCnvRT=xNAaOnl6}yLaQZjA}Y^-F5-S4NJT+it)xc#v2pNEHhC}GT$@bQ*NiQ_|d?T z?gp*1vGZ67onWzG-0|%Tk*OV!fo%hIBj%;`GnaeUccW9Cu=lUCV<%eywjncU^yIqkLo3o8g{lh zn98HI_6pglv}UFL6YC4Dcem}P>8K5*%}y=fXssE(`Qtqr^=}!zC0(B0q;68(S;q8? z(KkEyEA>`dztuhpc3KgqJl$#u^ZL5u#35+S+N<)&x3U=jcBJ^O{{KD@T-Gz<@YwE| ztn{!@ye;E!3K3tIsbOgKt3>;wtoAj6t$Cx_nm2*1c@x>1H;EFMS|rsV#KYJCsV;sap)su67hXp)O zB0faCk{qtC&WpfR+WA1L>(?oR?IFSw>4)74GTS2!rGIXT5g|>m8i)92yB~_(38jqw zxt#uaJpJ<&`sZr;XWI`ulN_7Hcdzj{c66h@5}R?B=p5;A?cI$V@cV9EA8smUMuln& z@uVR8HPp39x%z42y13YU`6ScDqvOLG^YxRA55aaGH%71oIn#>os1Neae}g5hVa>HoUJGGvfiEg9!c)_{fTlVpP;SS zJN}4=xN8i=j8kgjU89abTHQD|Re)=fX7k**9Tj_d5FZ=l+l$C-r-<;)=a=J~&n^GV zjkk1I_)4jfL&zU7q-UiooC2>J;v`BQ|9i=B6nrZU@Kh*}@?i;*j0f&Up1Qdy(vpho<1OaB$a?HvUqr*aooQw&%iBsTTiA9i z?;5)n-ky*pCirtKynDzzl;NGHd&%nEER18%=$*COWO*k&gdb$yN!j82of|v50Qa6j z+Q+iQjVrzxEtG{E@s-fXPp+Kh+90ggOQcR#|2rTgP$%=xrTleu27HAcx1mxvHg0jV z8zfKdJzxDTwNibXFAI*%cd@_l1fyi}f?$7il00D(o`9+y^l1=?gfMap}!#w;N=5t{NkNo1P zY_sbG{^y`Sr7J$=>JrrrtHfQ3PnGJ4&nSE+54+r7hR+yvIX+`mUv&lc)#-=N1n65n z_F%ag)-(yL>n}}U-j0+qsKw)Odc=#sRecWo?Fsnqk-6#pzv1$Aq$^+bZ?LC8cXloS!{)1Dn~JAdJ_n9#p3P9?O)Oa6-wLTC zQWY*yrfBq~>qGc=F7xijb<3>Fe3p&0Ks8E=sF-u9zf6!2xK%tCrhS91IiV~c-;`t6U^ z2KB7^2?#6ns1rW9GD+e;sRQpPk6oE0@5&{QdXh(Wk3bjs`DK!nx%T6qiLBgR!Tyy! zvyf5?@hZrq^&($7>;kOM-n>Z%(f7?b^?C$h+Ik=cRXGY1{5ga=WuZQx4`7w_gZiKvVmh17s>pOPUDQxB*bG*~%n&m~6`L_;j2dpn znz5?H<4@QgMbqSP{0N>0wF&o=;*0Y-LaEn+V!ls~+c#yavsD@1ol7lkL66ZAWt`M( zTcDh4DWB6NW@vSOOT@;f;<`0HS?V-=nxTwtgY&lPbewloXW&=P!>1W)jGge!^VRun tTdL0Zv{V~^9cZ8#e-s=tH&EUsVQ{Vje8=a>>UqoM2~!6KJci(>mEqD+ z@g7+y_~ThzlKwUm@}!B7TE{zT#>B*sauQ>Bo3(-Fc)X^3Hil5YitZQ05VcyqT^NSC zP!x;kY2lA#iEV+N~-6?HZ}t&Jj8 zswQGVv9hB!msI0ume28{=QqTP^Xxh+>^lF=&dc|8July5l+QcvH?rr2Hqv#Juk+uy z#*aA1o@3wn^F7{aQJwF47rzJhnN{m~J6RjqdycD)N=Knh+)GI3*4{-dCB$A&i0xZ; zUTCwQMfrrask?}KrDtvP6)zI2Vj{8P{_(gU2)F$c$1icLLRp3H-{9Dc<6`M(t5y3D zt7bUxwGr(&L#%-&VpaRlw4pe%cd+ZcJVVSHfBCy~&aBaP|3;k{qf!ytY!~G3j{Zy( z+B)qxXC&mZpM33S?1=LgLcU}7vFAaU{P<%W7o%Ub@^5x63dO|Ev3B8mBD*HMY5xGn zM^JVX(i>hl=XgJkw)qkaY|RWuXh({4-Fko!D=4~iXE%Ip7g&2)+obnN@5J>lWViu_ z434bW*P+Zq8IIrVJ?ML{a!I<+wh6}&`P#)E_c+eIigW01*R$+9aAu2<&pm@htV3JKys@XW!ZVYz#S$j(gc}M`7pKcgMB9ezSA`e|vhvs6ROM zgrnoT0|$M<*?|Yg(E(@2@BWSsob-2pf9J2R??%>xv48xk;@}$-asGQet3Q6#qYloi z@SF1}9A*CG;N=khxm<2BnPfp@m0j_;16;~cx6 zjTa_kx{i+L*m)Fwp53QvvRzO$b=pxN6UADo*gs(Rp|8a#W2EE1-@s9i{s2B!9525n z*U8_H`~PSAA@`T_kiKZncTqR*aNNu8W9J>eT^-r|44#go<9_zrQP?^5-Epn2-|XCf zvptTsUENN{`KynPK3{#mqt88$j(+t#*VlIk4Enpjzw=kub#*Kvi<7IdoHVPpz^4ZP7 zF9u(b&R+TW%KR(yZHGR8{PV+~Kl<07p8tnjGj8wwN106|pP~Z3u+sAn7ZpmCTBG&w z^zt6!Gt}45KOj&S6dV#779J596&({BhvE1?^2dO99e-(fwr81v-_Uy)d?@r2;c>vC z3FQFF1=wXtD6qej&}EdTP|l-V0#QOeMoP}YQi?S32_h@2JYEVvq6NjLH$7xZg z_7F-t$~Umc@Y{16AzpyQ*<6yEv^@%O~rVwq#5NB zA*E?3Cs1&`tR4l&axKbI6x3OccUJhK;JJ$T2^rBq$jEq}eO~t(pDARC$nvIa@jfC7nQ1IN0&4kR1K|vd4DN)ddS)ULx8~4oql8`yg zDEK`W_svBe^HA?RoSS!skomYY$Psl@BLLNpPkKp$~)bRt{`@?ub4pkEJ7~XOC03km*1b58) zggoJma*2>5HH7@6m5`%0LVo%sAy4By&!C=TO@us)I-bXKFEkKx{2M}E#POvygq*;0 zzd)TYe@@6Rj}!7rJt3#?{q*C6ycS5v>$v{wvxK~f_P&L8y^Z5LNrb$+n~?X43HjY| zLe7Q|a;}Y#KSmJp{!@f}P)Nw1n+f?ajgY^*O~{4)gj@t{Kb}L#C%FH!&4hd&PsrcV zwtp-o75*qRvpDizJ#vE_cfOYU5C>2IH7l-?&dXwZhVc<&HLe!#IxJ*-tF7qB5WoU z^h$Rp5xOUk(EDtJ?(-(}{xgI=@IIjr9whXkRze>sB=iS(*Q1vReGJDRDG7bNozN$m zQQ8Q7vIgZKT%a*1pAhGYcpCRV6M=$ykKz7jeNhe( z`W)(f4sCi4&p&TQ!Mi}e^o6$xJ&wAL;~Hq0m!BOZ^u;lRzJ%vbpw6F%puA7$FHrZ( z2MBFLeZNFKC-L4_9w+qFPC{R8CiHYUp})ere`O=|wNr$?zMs%H>Iwa|5~YLCc0Bhc z?t80}(BI^uoG0|{ZG`@|kkEI~hIcLz`YxV5gJ<8vwcou)=xbL$Wg#HcnevbEi(Lm_ma|!)N8lhjt6Z#dN`3i9P8t?w+enP)FMCkv`A@pCk z@89KwUILuH^+rK`-{IPSjuQ$W3BBA#DEvgU6X$L36KcmbaW|n?4ibTkLHV2rbUzV< zZA4H!P6Xv^L{P)Ot7(Mm`4b{|h7iFE-@V(2;8RG1q2Ca}ubv402Z#`W@45ye1ho<& z_&gCp(})l@hX~=QC*pk~M0OA%dIk|hRsAsI6#ENQ$$EkB0>u8 zNyYu?zC_44NQ6v0mt9E&Bks#-BSKyT5%Lcap)d;$T$~$zi3r7b269{|L%kI>L>O^| z2$i_63inmxd=0LT#x=-fVGN!bdxi+(y@_DjO@s-r5n&SQs^3Y3DOw^-EhIuiBN3)O zON3kSeFo0YiXp=6HAI+;gnLdA;ofE<+!sTHR@}c2*Y@L?2k_1Zvxsm2{do8(A{;ak;Rm??hi?<% zF_a(S`j2N2;mKSg{3L=1N72@&&JY1(QFwL^5uV4n7ut#Nv!z6M3HO}%gb2U*k_c@_ zh;S0mzEX+OjM7GgQ)n|QuWkmN5a^@$M-)T@P0ugZ&wvj~l<@Q^PT9&;q?5!jMHX$O zr}42|bq=?C5U^~8Uf~y@)8`51r0S$YPpQt?$A|^=P2qSaId=}Xk|l~4g+|p=xZevJ zX^svL82IOslLe*%TEjk7Pkk(YMFT$OpLizX%KOU6s{5gsM&lid9G#vQeSltpl9$6v zjv_VymBlJ9)}H)R*&n|#Ju=pAIymm#FUmjc7=5solA3wq2uf7XZ9?RU~I!yk!^Jt5lcpAIdVxS^%Q z07w^Id0+hzVD3v|iGk#iYV^q%O?fZ$al8Zh!Er+=S{xjdW0aa~$f0^_l)Bn|6YG`Z zo=loRAAD-DA%4r84Noi?htK7uAzm}`%T|===VdEAci8^8L-Ctu7S*upw~gC2=XP9} zlT*69R6jp6TD4)v@VY677n`=sXHVZZJ2N(BS($!DPU)J`=u_4-;pH8(HXL3we)GHy zPb?mLAI>+H#?H(sT3e#?1df(mc~2FBJ|&R?Qps=>s0q*~0_u*o7BF1o7Ubu!p2`@b z`FTJN$A~_Tcf~_Ol+-1~|2@9m`q3uy@YRoey{d9a?SWQ**4faC}~P#G`X6 zcMMB4`Ul)=&MOt4Z)|>d(U|DuC7HTgW0EUt?1l894GWXA?;4kL7t}pU<|$sJdOkMf zkumSp!ikR2fa_LqoNxkT4f7K;M^LDBz5zkOdO-o23&16ZQD8_Gk)%HNl!hO=3Y} zL9lRQi|3yb|I)g6)0Tzzewg?t)wxf{HkH0u`^qb|FP1iqMPC+P`BGV>_zmL%R2S=~ z_lwPo^~>>7R*4Pwi4F9~eS*GKoJOB)rAI`_KJvWyd%BC9WA_*2=zR5gz+(y3KmW%+ zHt2jdocmV%_c&#Fq6w-fC9~+4LY1%yRKn`c6ROamP4{EgfYpPK9jFJ_3>dz2$2t5# zPJVOceW4nCVb3tiKetj0U#U80Re=9Mr&Z>w&Vw5cg(V#u8l8_ZPkv8tg~xM+7`h$zQ=({80z%uRhOMkmHwwrsdFMzdakZ^0)Rg zzg6y^{e|@x77OlOhW8D{y+LSk9($LbefX+0YQI=KPEcWHfckP9WbGB5AcpqKK`r6#xSBf?$ za)LNq<23pJ^i`3c6Qdj|)`|5-i;)J=Xz?F!P1##5oUy-Txb0E;Q+i4CruY7b&Z4t6 zD?FbU@5y0pnG9I}2W`=i5JCcEgi#4?dUT!D>gW6KRH|kD5-(GYm?eKZV*z`)CB6oXTXYsE(6M8#5H5a9BRTkAXCSvfco##$ zT|Z(-K(+X@uM@vwqLBue=KY1Xij9AncdJdDN`GQw{T_+;ed_2p6;LPYmEV?wR|lx| zIHeesDt3tM4H|U)J-c0;YoQ*r1|1hO6+fg;iZ6>VD`FT4(>ZU^&!*5n(?6r%0=bCJ zzo^Vc&!88u(ZeJ?eUXizbLSK@Fph8!#_0)u55@|`b%^S&(MnI86QlvF%szL{_IQs` z%iucW%GaDH0ImUfROdl;GZbIgK2Ri3%f0l-Vev)r*~eHr7Lt#ZRl@J3cCZo7in7WU ztN6$kEByYx`@}=7t*CDy>KhLpgZikB=6TRO<#=1LBGL9IL4U8<@EEP7ro)mBqbg8+ zO%h2e?$k4>!T;q#!f`pF=Vx+aWSPlizs`^Kt^LF>eT|R)L7}BgpLnE1|ICb;ClZsX zuqrlsabn39o%acPpz+qa^h)pO9Xnb}blUaRcfP-Vy-szsw6@{C+cOK+7er@^A2QVl z{i+iGqIm@EsA3$4=77O>$=bvOgFXfl+RryQDArgI3``mlB*tHmZvZ_92Z4P&aVyXXAfR{Jz~efBXg<`Kl;v7<3#I@g)`@74_iOIGR8xwH!21D zuWF(T!nOPNKL6c|zw~~u`fuWg;zw`%cEqkJe}0>$XJ*o3>S-!_GiS)VuZmx$rgpsd z;t%e>cfnmBJ#|!gK6CGu$M3Gv-!<>)^f;ddd657GV1WUo{3~cVlaw%O$StFiB+oH+ ze=9&wWfXON0mw870gUiNsopPFAE)!PlviwyiC>Zz9vK>%ZPv#XuF>m5L#xV1#E(ge zNf|agdsKZ|iXkN^Xvd~U%=9!p-EOriZ>`HMDQRvjjEpR7ENwCPKbbfo*)(B7Qj)+whq|+vRP1D4)jL)~X+x=OaF&CxWh&C%Z9qEi1>bxBWNGh}>Qc|b%aV6}upDu6+ z)dl7l^ZW|v>3HFQSSps%^B1hvQiEZC8*2yn@La34T}kVVq4reqEKQ?cf0%#N(%xPd z9aCyCTUi4vCpZ1PqrIJaGk0N6xQ!h++$d9*bbQDVM*DF{<|bm~ za*YpaEYr~WSU_K>Qs>4UzOivulSvWN__bJiyrRNv0rUf>OwLamRUMrY6zXY6$X08z z#wLZw`Gixg(~Dy1*Q)Bkt!A6Ho~8+H?OP{LHg_IxZx?>HBxb?(?Ww5&s_MLaHBHgQ z6b(1o{rkTno@KHGhN*I+`f+exZvxCRdIG8EGo3vrKGIAE-vWU$XO8rzTS_3`=_;^X&hY@0OAXg0SS)6=VK7c4$` zyiym`Vm6nyy?kj}_Uobr_ z5nH>1szyZ)_4&dO|H#{g7IX2|t;;Qs-Sb>?X9%Hxh!lHRqr<|! zyY0RvqiMyogryRoRMX(Qx)hx~d^H6Mqd^ytI-fmWoVGQ5a+&{1A{|e(GjE`9D zZ)k=CE{T}I1Y}g4douGF0O9V9IJG91;SMg393uo5JD))*hah+dWGZ|hOsnYnD*to< zRjLtO7!DE6jnx5B(Pj0iX=k6_aocSleYEzLyK8HYtoR`%ciARZ&-NYS=NS;#dCY89 zez&!KeCe{KB@^0Ka^m& zdsjh4@$AX7<{lei4p*qX66k8HS#hN9u}+in*_GJ|hU``B+oA;Z{{8BNMGiRLF>TuR z?bAFx6GAf>Ok{fYhTN{e_zetZWGi)(`E>vXCJ;fy(AL$lY%DXfXHZl4^)&$Kfyj_N zZ=dimpCQjh#-wDeIiO z?MsiECHjzc4-Ya+<|Zn}-{rB;{{YbW$sF7{f9WM97$c#VjK=OB^%?`q86$V1l{jPr z*asNCGR)D(-lRY64tphdy70HYv=JMWP+2x`8pHVT_>S|^Tn8_eslJDL*i!&p&1C&p zpM*Av=4EZr!SuViC1ZFGGa^^UF+Rt}ZF_s?af#n6$MoicjF)xW2)P7vjRUn3I8V## z&9-`RxPfWBCe!i7EtZvy7cZ`CY%eWqX(=spX+_xfL5#QKpK!OO4u2MAPMD4O!`wla zoaJL%(kgnW1CsP|Q`Rb=7Kz?m&ykcPCz4iHgk%NBexzF?>QCG{cqpe4Pm<4gXii>S z023N(AtETT{la89u=K7IzQs ztg~GPw*U(jN~XWm(qg75Ki_oH!eDMSTLAOA_IAtB`G0_JXk_vTbO2uzGr^tnJ*L|_ zy~7t#aHUR|ol_kOdi=E*TRq2Jl}t0ta(AZ6I<$bkNej9#AAer`sJ0R@^r9pTPfi@d z*L~@?5)n|__b3MgRMLHAISa2W*L@W|g*TjzlB$3L-7PC@&XqsRa%zhdOKQ^08y;!lOW$|&B?AX5BY!Mo+=EU}Oj70;k z4%#j!qd*tX5S=(1xMXyChborat%?_&&^P1NPA)U|x7Df;KI%(~+xh9 zzq!-@74d96j4Cc0fKQ=;%dBK*4~+VPWlD3(m7pg>zAQfO3^A>88|hblp{5typ|?x+ zDHnwfb%fK8JMd+&WeQ`r9_5rNGK%73xwksy%&25!)D16gYh?BVFUBQ%*dCHmDH$F; zp;m4j-DPJ?>$WoJRZuJKGA~*WO2&mG_n`N28I#4dahd?5jWf3wk<0Z(aHKuU9I5T{7RPQ@LjL zz0iOg0R`&} zZRSRoBSuo^xqe@{H2IB?1?KYH`iTZ(SfqDq`f#mw*qrRRlt2T`hg(kAr5#ac0pIS| z*UMmpLCe*At3&%w_pc27zGK*dZ|<`Jzmg^tT%(%UTDQN8VfI)cNd#wM@s*0dWU)*@5;0g4_6no zn8ml5g01?nFEYJ+N-{>(vj$*Sb1~bu2($J%j%~F}FtxJDWC_Xf>`*HZ z;xb^2b48eY&4(glOU5zRKiold^j&w>Z+P(I^X=n0S{ml%8Nca?S-6!Mx|||-__V3- zcYdY}n-D!Wy9Dk%hu?+y|CtZD1{}(jqhG_F6!|XwE%abN&i{SQdoVRXhxXxOQJwF@ zKpHz{HlG~me71l03iDYo{>Al~5HiytalWsG=_z6-dd}dosWbeJje!k~fDVVUc$M;V zmE-klEv+5XI7Tu1;>B?h;WeYno9R#RPeavgZ1&WTdii1d(A+vrOw^by7Pw8~$E2m1 zF?$WqpXx`Pfpx1;-5y$CtS?_|fckrW*C5&FD)p?(?}|wQ1_H#_`E_}2;fqZk%U!Qq z8s9t9xRtqG8GYfpkf7LvJxPV-;>Kc50%sO|z&coJGBLZzSB(>y1=T7+R1BWW6ZPqN?;Q{nh7&YBKTlU6Z zQ*SsAVsi=FIh+SA82}Hue*C-SQUd^m=71CKWuJYm=w`kO`v=@c3T2!PGenrS!t|bO zp20;ZnKwa(LGaNjH$L*P_?t(TE-m@Re?DBX+&{n&9bH-$9qrPZaH57-;e?5at%!mn zJamXRSX+YOm^0P1KTjW~J-cCHU@2{Y1)I{7LZuKeA#pMXWCj;5de4lEiAl*5hNq^4 zja{~E&%=Px&pXbqSY8(sQ&xJN5US2k2n@&$4fXYnj`sDHfn~fdP=QaF?H_R8VJL}W zvG4s;4wvTc_!avj4|}CEe`6m6K&#yFL8ASt0Pr~jNg?p~yNB%o^u-lQUG&Nc(%k4C zXqm?Wah;_A(vQve`oiXd+b);K_oy|91q`LoGJ8k(I2_cudQ3>_t!#qH+#WhqZGH_3 zVRulEY)m08L=lW=7<$QkOp4_h7MDgCUSoK6fhOSk#m(`G5%+e` zl;brU6_dC9PJAaNXxEm;0f-pYJCfJw<kD3seI(+_A=F^Zo7wzrL%?V|}fkUkYaqH>iHt0iF zA+F1*7@WtVfTeH=7XCX38k6#`Jy7{5L+_;@H)KW(4G0T~D%)hcFaYGLpLn?^PGtvU zi|+1{*nlwqpm1IK^v`TxT_d6aC(73R!a8Z^dCm!&l9A%K5`k1e1EI2|t#wspcaL~% z$%fkw9a4rdQ_*CYnlX3AZ_DS-AwuU?jrNLKdw_Tnf7Aq=vq7SdjVlPuiOrz;xX!Kc zg(%hGRhc8!Mh6Z1?d1~FbK*ZxjqO`r=ev-vh;wAq$#Sg>t&VzwP`AG% zj`VS2;f%@qhL_einWH@8ixWa}@72Y}hSVh{`We=&w12h!j!`RC&A6o`C_Ja8u{JjB z`R7;BrPFTt#Xow%>{{_I*v|qUr#?Vuj+R%RsR8*2;(Dp&?zj$e>Y z#B|Es!?#!zv$zUtwKm#6Xr&3uZre_W58K|Td24L?cWG8B`WovQSz0Mc{H6IJj-F+s zo@L|4{t18Y4`81F+ssKIzt2~X06U3Wc1dj*-Yg@9o|a$t&7nI|JPgUxlBSza=59Mq zPl)hP^8g2nri141Q%3cOu}!TJ7cw0e1Ep&&OP=#};`(-kK=OGxCwe%GGPNA97AExN z4z+)9bSsPGW&D)CALCpPj>_VbnR}B@1+lO$Ms{2{$|6A;ash0%QQ_3LDbI#cpMuFF zQ<9^ijMJw#*4EaHex$LgDk8j~AUxtY<`rGEU!x5T&C0ARfN-y?8NFli#7WW7Ww1{| zLod&8(KScE-a_VN0>OMs09wSu0TWW$XkuCyI@aAgg#5eRE5_s4A7DH#KHhkDA^z?L z?{cF@9Rh8k@kBcAGBOxabYga-IBKdj67TDTzd7NUuQuA;giv^nKY- zXm0t@q}W~p!b_D-*F*6X&sJ@$oZ3rPXoz5&uhEJI?OX6rFpd=nHi97n@=-G@L>e;^ z<=8GI$$vDvuyEx{208Yr`GNx!A6=&k3mJW4u7a&diM1?Q-HFHC}pWl>SEI~B)!{|4;G1NOrJdo|17qd+N~d+=7XgYDA9N%Zd%Cu!djpE5;7nP|P^ zi1h|k>hddhYF3d9JcYJ)Prr75>t`aivw3~Rh&yBARuqOugoYUx#OsHxip`EqHdl;@ zA8klYt*lBKnLHsmH8woFR$ZP{fKv_QlL7;i#-}xed9RBY7CCGfK6E;$O$^7>_D_x0 z{*czj^Sz{|+aH=dS!)xwO^=G2PLXOzJHJFf4|?3rrGdkXDeJ8uWgiOr2_w3jStZ8V zI#NqY5@G^}l+W5zHo2ab-(6K29XYimFhLiX5S3QBPSYHal^q@sP?BCg*OF>Bk6yDp zR2?uwAD*BaVyyF&+ExWU$V+>f!-;2gNuszHT|*TqbEI7J+!fHiMs54E_Yl3Yx;iaw z?xwV~^z^Npvc^=Dr+5Vf5A{wjE~$%;t1U|pkIFrvJr3wEvKe+vY2X23Wx4H8h z+VHzjlWZbR!}!*PgJouENuN ztgIA+r>?BDaplF9vQq8B#j}m`^YWH78I1)6%YQI#9D4<$ht9?7Rac^6D=Q$NkRf={ zR;xibC44j^uF?BSp#%q{^2xBkoC<3nrf=CNzgNH-w5!BYt$^%bw*EkmoIHk}@A0N* z3<-602?pttwjv1yk)YD2W;on4EOwoLN)y2RledZ4RorLC-jWA>LE?E}f_@sMo+a&a z0C5B$9Cq()h`+L_+r_V+68}Yg%;xIq%^j-7rPlvj+YlO>V;HIEs77WB0B^pF)z4#n z)aB6SJ-G&q7l8Ueb9t^_raEfD1UMQk9`5I@=dHh}GbDr>5{$|Z`fGrS@nFR^@L&eV zOj5>T1YJSX)%5_D{rTrEkfM#Ws;5K{U+(`JRAn&d9ti0nG{B(yzCeHW;Dcy3!xfKp zgWf|KM+O`VR0avAoI~g5I0(g0m8fxim(?J__h^28bY-9?Lvife24 z-#)rV?6@%3cW6xH(4kR2LnEVWB3DCiu?#nXhnQF;2?L{z#W7Sp*dD-PA?9g!$9`d?kIv&zH$%q~{|$mvJvY-^7I8 za$34BxxW~9!*$J9>+$!M^!fZW{5>yyt_SZfS}%Qqn@2?z@y}z&SepAW{E3If!^e)P z6npoH=f(5;_U_$B<7xc9y`7(}UHiosYw^cE71>i8j)7nrrXD-ifV4CXqgfwD8(t(7 z_FFbGMn+S%9sswgqDlyg=$Yo6mmx14u&#cQ!I z#lj03YJIA~N|rb$uJ$TVd@kbi)hCM5|&{e65@l~DGYZEQ1bX|X4A1rOD9H`h`-Q|CtFp`L z^3#HYGioX{>QQCUF+uUEg^6(^XMXgn*PaB60J(ghlQrp)(SPF4GF>VDS4TR*`*VP#RU0gR~BitBZq|uox8v5?6gdv&`y@!v!!o&XO!cdJeXsFU7OxR=$ zP$~kwl^#0#ksuAFS`}4j1zo6`syqaRwmmZ3-#ezRE;d;o8B!XbN;T!#MxUX@#X%uK zx|HYHe zwf*Hbx}L6oxo!UZH-59BIZd7KG#bYZDk3;=?O96Mfwz~MN2Y< zN9$p4^s=F|<>(DRXQ4Mz9HAW00R@6ZIQAcgggiEc*0iwr3*qiQgJ-N3`v)u@18N7C z)m#@C!fYXb#2EO;#`we35@KTn9N=DICk1_+^=leKqrdt}yeM8s*mnBWJMVn;G+lp@ zrd_1##a$Q0vlqo(%E*Vrvk!^y(aeWv+Cwx`l3%?3RP>xp4niG`@%Qt^Kn6EpOiXeq zU1fOrsREyWLHr4LckJ(9dFbH-ljn|#n9$NvRlVhB<^@}}bo}Rm{q()hzo@C9o9J5V z_tMi3wX{_GjM=(%*SPV^mOlBbyLaumPkf!h8g2vCam0|Q`3g%qsFPrcF<=v2MrBYD zWvY+FjyQwqici2<&Um@5&g@ThtYSvi5bsIizjp7oY(4+Xv&)}-CRU|QS+P7ttBS8h zLVf$PWl>QZHbh1(UAB9-_%Fq6bw+RK)(@;+eN-8?X1lfXv(5NZ#FXn*4?nEZmtW)= z_Oult@6S9lZQ32S3rAP4mhhqu0S{HP6<`fgyJhLgbX91=5TC|i$1Du%4iZk;d3qWx zq!WIAj5mj_e|^@h=jd9q`RV7NDD&1R-hKal+XMW?$}l=>>QwP?=VzEG4|^XWIA-Q( zk?DfNjcFc?@#%~P-*om(ngSXE=IjRy#xfUnjxh?EwrYf;L}QH%rk5xWdL{+h7SUxi z{H+Fan|M?_^LFbJdhqbR+FKHJYt$ z&--}1wLN>aH*->-#XX4q4z`&~S#D&A>JvY8xYP@m5Gpy}c+9+gG!IHoH1@X)n6O1MU z)qlVWS?EKKkwBUtiA54!d=i7%IRzj^K0+{|#fFQ5<6HthJ-zOnje3dSVtJek>(}2h z{jn`u%;xBbl0Dl_|LC#D9{qITl35^6)#M#mc4kvMVq{+5bKlP&YM8cmm)TqxlJwer zFP-~$JDqy&3PIyqL6#1jt9>}mOF?szutU&Q_fV07kk}aE;{*xLlHJ6F=2c^bo}L#T zo+f;aT(hm`*#gm1~ zzp^nB)!wf7ygbi)>sIf)a{G~@;t}JfHcXg+#AHWi2=(`Ih1xztjC*Cq;`{qqm!}shNUcGs-LE!XG`*pk++cC~_<& z##_ct@e5EU;R%Le`|by_L{{@c4bdbE?sf#*biXQ)6U~qKIkmw z4E#Xn4D^`J0hvp_y99gX;=Pvh$B&=4>}{_$u}QP7O-)^t0l!ElmW&}$_% zHZV|^Om-@VKFPu?kfvcakEBIA%PUGtnx9BYW9C0jO`ZCT_+DA*M%eH;V_%nSvQ^pu`f>SbsLn6X+#}}8daoW`n<|Slrm0c4(d&A&wt6gBu zHk;vxko&cG@m|r}Y-Sd5&)!v=;1{`IU&sCDWvElmg$LdlDkZAKf?aS-bh+`Jd9CT` zA02M0uJZTG&!2}dmT{RaZH;%f{j9}_av(0;ZT9gA5BKq56Z6J`bhG&8J-cbX_}1>- zsttYl-n-%O4!7ndoX99(MC;J$8PW2^7<5d(#y{HyGz#7cZs3N(X0IDtRh6C|6Qk9p zMJEk07|P0~PPMOt#Shm6GlF437Z?(vqx14+&BU4F>5VH{1$?}8Zsqe(za_)jDts!= zxA|*i3Mg~TopQ>+AP>|amn#u|HW|9os4B=;$wDJI-*&pLEed6RaRwX#ir_Xk&(l)<0e+e z>Gg5d{tIXgiVL47p4(Vj zhTlz1W>Yb){tmpRb0T0@fbm$)W)_$ifyHb{L?;a=_Bw`1)*h7n0v|Zw9P)@+GmQ2% z04B4C6#P~wO$7;}kerv7Z%8aQ8k3hZPcy@$_mI#6{m-AzO$(*op+h7Ol_@I=>q*8M zy@H4280X`bEQ1&IvabsZ&(rJk!@_3HT)e&-a}DX~RRv4-@1GM!DVs$xB&4TL)1~SI zm0G2C^2{0ZG%7dJ`GBQe9!Cfi{@yqOGlkB~nhbbCBak=-Vocp1SbI#}1FNwy6p#ij z)>%Lc7-IJ>c5{eXOifKDW7gO@^WWAp@HE}xVzALpqy5O0FyIPWpMtlaEtbLfIxyzo zeavsnD2JQSa_>GHS-E3neSN|B;?mSSgCQ8;n?Ga52mSlWu~#2b#I;2Oj?Nm3K%oRtaMsT-E}(FWG^A1$5O+O#x{z z@(7OS^b8nFbcNfbYiOt>u!4icmo1;9rN3mUjE!BgGc;zTxw>v~dq!f*;snD18e}y7 zc+y0860E=a)uCf8Eyu7B3FF?Z&AV=0TKd3G3Z_oY3rJ0_yuEVgXZXHKz6Y}T`)Wph*2sC7QF#s zKsh`tU;J`qf=(B4G_>KC(Y0k%73Mv}DE_m!5RT>Dm)FPn`|0DV#GR?Spnc2ZlfH~A z8s_b-R#AVy{VJ8$qidFM*)$U}_8p9AqLOgoAasTeSO|Eh3czQsem)SO^{`QWl|*H@ zRNHE+tLy3}Pc|C2ZZ#SwPp&B6CT=j+)iyScwSFutX=78q^9>w`I7v>UkS zNtsel5Xw{lm5=p@D^k);fp{YEKlw&Q?2*!Zp=I^r#j82#F0Nyw{Ml#h^Ef-(BIK8d zrDG$*bwX?2tcxb2QKIC6g1T?Fn$25VPo8XLNAVObXBRKdf>FX~+@ZtWm@z~VqD>i*w`|) zp?;FtoSb6L$S|A7k1s7_G6X}el&M>CpWY!^Y}7g;aQR4qLy!#>_RnDyV7zq8$Wm%w z$M_>ke*W#Zo3c#{*RJ*P3<`>=$j+LVzQ8nLT64oxLpdhOJ#>UuZt5ss|1HJ2RGWKr{kjC-2vt?xo^oShQb?GR zCL~Yw@+=VBRH{&|ms;=|np+dB3!*Cws_euH-++6|3#l+f8#p&rPkl0zM|gUU3XC0A zP!Zu9q`(|AXlgNV9*zF$83vdh!UY=A&T@?0$Hmr>kc2iasq{WHQPS=Vd5neH_U^B( zg$$;%m>>owFt)HHgm^YwJLEuY%;ey3gsvQ(920B*Br9uneQr+Jlqq42&5;q~iXtQ9 zHXG z+^ssHt3V7huFnjF3{1966^yxJ6=azd$EndDH8f(SwdGowgL&EIqa*Xqu>AFWP@7_-R$7W{- z>4Sr3&ALnB8OUI6|3Kz3JQ9fEU-n?g)(FA@oPwV%Zq4KMY8wj+%bTq(GUF{Qg3mY| z0Se5$k2wHEOJ68K`YhVk`3T0|SHKC69hAfj=#z1_p2HnFi7bM_d~8SrVNwuQJM*XX z`hgHwZp2%F2G|qgVLGeV2|5e!+>LkI9C9D)A}||*v42Pb=_}`GkPJvPW{e=AbGIqV zSa6J9VxM(&Ebkbf_J7x%N7ByL;$rqGo-&Wms4G5X9?!>IX{YH5+(F-uS;el-Gkpuk zk7qHfxMu_IVZI$Sp`VylbssUSou)lWIjhIk%}y+wALgI+)O$H2ANYd3*Y>SkC(~&_ z&0V!JsW$MKRU|<Jj2Z4XJ`6|W(&2F@OWIp1cv z#%`~siJanrEttvLdO5|u&Q=S4;g%h8`P2YydHCcyO}1KzS704)#&?Wx=&}I!E@r`S zXFY)XUUAJYI(L3A#=T#DVI3yd={CRUY<3dk?wnujM10y}kGt3|E$9x9PgjA498u{U zoeZgc1O`g+79295Ia`Pnv@X0@2xmIN)mt`bxLy85y75Q zn9*0rpdljM(^Ei-VNn!31&F5y_^*zq&vmpGS{sZ-9z)MEd$=M4jlmI5FIaj9(#zrQ zES_GRb9EH`w@)zDh3$u7|AfD@E0*3-t=~1p(%WjKdOf3PUy<~TA6&pL?ktv`uLUcq zKe8Gm%>`hj4ajME)oeiBN>|eXw(=g+0SIeMH=7@Whf*opcJJ0a)|ckM#zLoEZukG3 z@3m)tET-ye+4ow-xN$Y}y*lfe&}otT)yp?EV7C*G(s$*};=U=>M6tK)$;Ur`=B#{w zpWdz~mkzAXE8I6VriTN{C)I1(G_O=2*#T9vbk?k;HSk?&EPY}Xwy@lH*up)tI>8Q^ zq>;^Cr>ZIw27J}_9u}_?qopgyGtHcYMQFL%t5x*bIy9-PeawF7X-7c_4#1L1Qw*Kx z+?Ha)_QCo2sR9D3W5BAw2*ixOx9k!GU-$A#b<;+_kTUm-eT?8HbE%>uq!K1#A z@iYv}p}1o_Nc^$OUm)=lmsUinSX&dQO6C?v|271Jl`$Pr<4FaGVP-+h0*kZ8uEG81 z;n|v9=Hbb8ILX7a`@71+>rux*^-^B>_2%LAc)v5fw5ua7^6&(#AcOdIcXC!lP2c$; z{pPF)_Iap#Oy4;fqNe|x4Eu17>ubq1u%8*cTm!*|+)p}xFmxO2M8p&FZm4dn0u3B4Q$PvP6irlfLV^~i#4!q!h6l2 zZ60f&fZQ3ZScCWeOQ8nP(?je>_;|&fyc{p)-kkj4P00&gW6do)7-$^eoVM+v%vboF zwx-Jo$eaw$=Cs=-58xn7X)}Asjyd5$n$xzgyJ>UUHyAgJ{us|6z+9;<cOAbERy)U=Zg@+y1wvN})$%Zj$%++U82_s|I(jl<^-1f7uVG zaWNNZxrV7o^jXqv4kDg+fOC=deS<#?14yqkt9dncIh>ra-jSVbj`SPCYv?Dqm#Cw}T+k+mn+&SvMWJu7Cb@n5+ zS?Ck&2*;GVBht-jY_DzB;8_yz(RGbCw`>WZa)7o($7N141CEdFG!2?DLD9#V;EjV5 zAA5PvO|vC#Fm5Ox&um<|9e3HW|9Wi+xlh+-OIUv~=(a@17xpGw5@_Sb#9ekS{ogVs zgpQ{%9_ORK*JevxJ~zm=1n8lO>n)uBxQc7++wSS?=K3?${C?X9j3X2$%2IXggc_o&&NL_CE*IFEG*&Q{S_D$6W!imYSSawI~{ATTCHpwa97J7y4>xq(c{7l;Fhnk5BIsr3iw(>ZmsyJ+!!w$ zVDUlNF~Vx)`%18`vt1=HBX7krVlKN+kvn8hWs&XD{t@l%@@^A+CC!tYe%^uosJw3x z6)sjy>`5DAv9k60fzw7;mNE+y94cV0~z923TVDFb83|fQKA{jYwlxVLEQyft?2jFbH47-q$f~cflUJ zT<52as*X+x3iUK3WUDnb!h4 zP0_{h{j{BJB<9ALRdL=noUhN}jkQ@N3T9j?FgTw>KR`;F@ndUwOA+@ho|Q#h20Es8 zr2tI6bwYP)3)Yt4QQ|uHNmyNPte#}B&jX00+?)9LJsaC5O+#E~JCa_jYZoj&dAw2= zgw;8C%n~-owYbCVR7u6faRn)26jrSll`K_9_-^U2kZDytR?00h<`g!JIevWXC@-&( zFFCf{mDaLf=aO_K+T}7riJeaCNTW=<0embEcne`p;{yiYDgchL#ewWKj3~->i#se+ z-uRiZOyP|Y*g?tJM#T1T#|F{uuaz&{cPg(pgZGu4;#9Q>9%HRqdjRq?0&W#x83EE@VLAgQp4$PK3PQWb;Q+k zdBb>MR}eiP#W!8>C^urOu0SSuAXczM7?aaZ1&q^+V(HiaIptdVAmU$~1KFe4=KcbS z*C&K9{_Ssgdz6ds2tG@jQ%8tt6IaC|4}jmWr@BxpFuf2VsZYiM8|<-La7Kw#&w0pwM5oOF$e zcJ4$oFF%xExO-PYMe*#(v*sQfVGdWQy%Ol^0VZZxf8laGbn61%(BCT{$8G~34n$@% zU$X+CT+$jAj(nCFEJ7R0*CvpX&jJ;WjbwT;mH}{#`pA$xZ=dimpCQjh#-wD8Vq>dDCyn$^GS3EJgHo4MtU5q|O-P?K8xCok9?%%voeMi)S_;S+HzUKGJHj zZbXOeiK`gPHrq2Upe5=3(*A%Bn~>|Opv~@tAHS=%`I9tzFjgLT*Go@zA4&?QjxO)o zBDvQVfK2-LP*Tf{*1x#{pz6S_W;(HJKjrSt0Oei6zo&(|CMLk=$`m9m|~YtqR*em9{B7?}CsJf6d38Z1;Fz*XsFX zr!0w7O#earK7_FZsBVeWBdts4Cu=y)@{$o!-cyTmpWK)bqL&ct@9AB%Y-!OD&tfKu zx`D_P9VZzB4&BKe07LXZX;Owc;ELt@fSd01GiIfX zW2SYdN%?GhyCW$B%PC{iRtaSM>Srzh;h;U)x0Opw@Y!IgyMcPQI3V(*1#KKF3igu7 z?n7#TUJR3oZ@b-i@#4zHc9z~;TIP-->C*dU+sD?S!$09}#1_Es&D>y8hP2(KBSVr6 zOy6ef#Yp}_E*pASb&@0)be&rb6HxT#Hl1V$UZ=(_8+{IA?nt-wckODo#P4L;;7Qp2 z;B%Osk&_n}z!b7L2AdKZEV%`l>j%I6ST-$}n|v5zu?H9@IOjfPsj-d96lhf9+v3so zJxk5fj$v*44F+h}R`bOcODD3@UCMw$cQ^MIC%PJa5}inP?>OuUjaWVIanW(htCuHb zz&5XB;khp_8R5WOb#SGDSh^BhbBDikYJyd4cDPq03(lD%!PdgKoz@j7W&exhdzN{+ zL{U5w5Na5x$1j+zRl~f8hIohfStcq*c2GBhRRg1^FU7A`EL%1QtfHl9eMy}-SNxc< zcbTXz(DQCNRz5e@*)Fr}F0)xEnf_8sipmH&ny}w=|I1uEwNKrF)wS*N>V_Y*C(o`TwPb}n z7TIhCvwxt0_2~|ASUi0XSuCwN0D@w?+bjN&w+k)i;;mbkTOPaTx#rTsNQEMz>OpCc z^%|jI?e-3Cn=_Xws)(W z?Y;F-cML!%O+uSPIcF+UZ~fBfYEX6iC)vK@*Qg@}NUA*J%e~fV=~_9(eL@+Kj$}5O zR`yhn*gEs>0L`U}WZ#Xo>KneN9j#+(lS>s`rX9wo%3&RjAuGAo>116cNnvuz$yC^q z3S(8}wpT~J$_cMx`jeB(^b*sv+>(02M}1i=vTgmdzOIR%o5elg`K~Cl_ng@@STc74 zj;#NQP>MNY z!WnfAV0gp7)6Rye6Uez^oI8f+UebJb$*%fv1{>pv{*^c3C`Z3DAg5Z1=#X48tsk0TT0{@Opybq+2H-VPM!9ix zmk)4Saq*b=&~P>ZeJzwx+U5FdJ(w2~g?VL{#3AWRjDKf2JXW-8TV&2>TyF8FQS$X8 z;kakpt4U5SIPbY@l&R}b$$2z{EzEn_O{GZ{#eb{P-yAX?TJy@LZEz9M9 zxrV6FCN^?ML!YwI?hkK7tNGQwc$(F0|G*U_d&|suh~~ojS`uGlJ3G1|F!^MlOs@!M zlGfB_o6cbKsD9x*03a(jnoKmU&5Tt6oq&=dLh^WV7g*)e084 z!?oIBbF$-70u3}D+f=)BZD5L9*rgp&hwTlWGexW!W5l1?>Tr;y`&Wi8*uMR$*c6vM zgwr&i_cLP8)fu2^8M6HYQNj+tZJ_Z{Zji3*nsv_*bxxs*Z9V+6ZunupQ{@q^)d6AxDxw3x-W znL|kRVPC`Fu_&*4)}T4*B-@Y4xt_5pJS>HMsN7Kf_%fMJ7=t6QB`?-}KFsC-g*LXV zrVB-pUUI@hBwNs3jRCGGC+eiNPlk*&OTi@CU2|owPjFtK?21xhcFYZ**=Ozrw^u`S zXVxBC=73gAbnR%7{SR25$q|2i4>-e3ia_Rm3$D7dSxDvs>$!@hYmTgTf|Oh9a!X2@`?D`@DY@2|YbRKeV-h&cOR?BkP3M0i z8rv<5-Qt>DQIX(I5Orf?#GT=% zTfRGD$M?%)?Pf-gyM-1^QQf!*^Zt=@V`9&p;Y1oUOh$F<{XceaK(7Axj;Fe5QR(iY ziK$mNJaXL?#%@?lyED`QV-9{N#k=o$^c`VfLCdT1A!7uYoAwD1Y?T*g}hX1!B z^xeVmeuzG?zdP7d-jM8o{?T(I@&=q>!Qp2(!}+K* z-^%GbouDPN6?|IU3G@+-;+F$0cIJ+JVjt@L8w9$=+}W z0|WG?USozw|LAc!b8sEGAI`AlknfU=xk|qCNDVoLm=BEqifuaB*(3cUVbJqRoIze1 zClcHp`6g^_ksCcp?#^f!oUD}o0j(Ugd=+O9cF?xuFOzav4$8XgenIgNXW?23Vm%tOmbmiR@KCmWJ6F&iX4pZPRL>Zh`CX@ zFiy}U`I)70&?S>^aF&c)m_TOMpk&fGf%c%~*f@c&91{thN6Nj~d$Y4|+yVBD&d})} z2R9^l#|b(dd4^mEm2!ISmwD1n%<6FmCtU6he&$dAsJKBnK~9j-llMruLf9#RM_mqX zwvapEzW$7&{vpd_%&$4u$O!=FfBFI6c6vGiR}YT={nzJ#Cc1nSH!JJQ9r#Mi(F}5CSpUH0@_bNp#@v9vOv_R}SxY~(JSZ7u?m)a3MPEnO zS^q%3nxY4ukLDDp-E!EGtA<&H|CMYtw=i&XGurw`l;S4jx;aIq9NWWmDk%@{Az<>q zk_qPy-rPtzy8djr{;_o<^XQymsi$5g<=6c`_TB_Aj^fH2@2VbMmVD^4Bwrfcwk}IO zmn6%&EXjv#BOBi~K1aSS-^SRMLyW;-3<2T@LINR#5JDgj;t)s(A%rCg%O)(#W^(|6 zgb+d!mh2LPbqxPk)m78onx5(IIV6Ao?-RIYG(GC$UG?g{-+T2q&;48`6?SHrH%e7_ z!Pz;|^?9LKEbAe~1j)5~*Mc4zuU%-GgMQ6w^`04ynNT+i6@I}PK`Q$~1+%Qw{)HlP zf?N{_woY9W_(S3kT%J{dW|$N|3YCMwSveXtg`rr^VmvYiC`nieRk5EH2=16dj>#}u z6^CYcJJMB&!TCx&${5xwhGd}}cc!+H*D?|W@`zM3n&DE^D;|Th0eBGAT1XRgPq-KM z!{K$3KzFNXh_H8Rfj$7xmKr*Rl@aqyHb{U%Nu^rS<+K(EwNNFQ(?QDdA)D#-LMBZ* zTLV95`wRo0Rx2dmZ-;BNOWsazMa3FPItony&UHdq+Mj263A_S%-xm^piAjB=Xh`MA zt0RMuGg_6A`ec;XsIu(JHIZaxW3k3JN;Q$jM7AW#VOK;3VMNTTA@w=KKFR_j(XJC~vB6W_&{D^*)qdwuITH&ZqWj6mq zeIwBexxVo+zMi{W>Kn~)WE83!^_d2%!V#@(48~WmceJkYB~@J`aQ5=5Yc%HUs8luT zb9Q7a8bk1v)-%2Y-7fe(MzFWnNY*nNb1gR2jQVVeNhPB`^?9z9*h(2cKw>Z>RKsXY zb-Emi=H5v`2p;|FMSXIU*bA*&B=nZ+7HChD`rFj^l;Rbmh>$#ZobmH%OL zHH}lov>wrz9nr2v)F*uOD-nZGo9l#3Z(fH8Rnal8Lo`9^sO*iUPv9t4AO@kVEXO2w zViV-r!@HT!IU2QxCa4Xu#F$qe>N6u|Rfj=Xqsa~W=hzt69Gc)jv?>mRP&sa*ZKWeah?Ik=r@Um=`gs zFw|#3tf~uxF+uIe$hC!sm>(bA+CpP8M_yT|Pf=JVMX-ZeFzJnwk4`a1(Wxgirm!U+ zJ=|(SeI^*X=n+*C1`%#qwqF6?hSw018fMrnC&V5Z#yDKdHH5~D8JP+~ea^(LdN2rA zw3raNc95VnFN4}aV@{25<)A*xMx<&m2=}N?L5&qzFUa+RWIv@C)C(H(LQ`AiE}z?; zTRJgkOi^)ld_vWt>67!#?&zgYxw>ow#o5bYWsLCA3@oUNG+c+XQE!or%!rV7 zRoTY=&$YF&tik=exhRw;z7u8uhj} zh3P_2(`bgoRu}&Os|O;KjBYKESyF~q z=wnR0xX4O+22O<}p(2N*Zf7r0k6=8bP_tv2Y!MXX7!xnN(wl*4A!&yFGB-=`!YmR= zt%g}LSuMmcCXJPni~id~5`^0`|GvM3Dv`{s=BZ+7(_dqvSe+t2dC`C{ihYtX>^nij zBHxe4G=0L@YR8yJ;lR-Qe-N5bvK^|U`*XJlyMRiOW1mes4%UIq#zYDW!t8r5G?fGr zH}m^ePN7q{1}J6GyE5Jik(`aPNJj`)YJok6giBI0GsyngVDyQfk(x0JIiY4eqrkMZnb|791XWCL2r#(b zK&aF{z+!>)d>}qR^e}hkDHA!M&X{mvL+5-Mrsyc!FsE=8mn7I=6t-NaQj?mMDWi!} ziwb9Jg^7sURG1p#%u~x8_{{`GOvBs;cNvgCkbR?9K%eFayQ70;Gfj}lfNI8c3J;QI zb}cB&&2(W2^PS9Ko=lU*2+(Bw@CMK&UqfigkZlZ+3xa>yM`x|5U|&p=AR53HV={#g zVHr9Snu0855SNjV2wuQUVHMC&59<&rZ)|#ES2sTnniMvshH?70|HjZyP330HIXXXY z7GNJmwyuDlOsRBMHiMulF33-apNe3Xj)$q6K-@gVA%xGgNW~<{WK7j)r<06$Ky5l* z+eyM&kUE+S3Q2d$8TvKWAJVMgSxE>P6Us{J=hFX#6hx52fzJP{W152tWCA7J87!s= zzBJJ2|Ha}QHLDh0xUgt$a`K$-55DDX{NWE9z3BPgKJTT{kR+64?ElaHV}E)`0ze!I za6Lm0jadWsUd|ot63G6o7FW1o<`3w?SPW^{*c1{!uBrK2d*M#kTfs^=~%FD)vaIPuJ9z688S&-eBB549RwEYka?uP@i3{y`iJndm{r2HgeGtl1^MvFojtlo$=Sg((6)|E_*qM!}@GHFRhcVwsUzCTE zE~;`PGUrI#A|uN!NBUXt+F4uzzEMdrfEDDXm*6YGmnJ$S4}3-f90@Y^ryzeON}z6( zTvlq@Su)SMqzO{{l|@6FK@~0(6ciSGx3KWmlh0haTJn1Pq>7661zkIyz0jIDvB&Fe zcm`~%7mDzrrCc%1wivTarE79N=8!Qr~{S8x8#iH4e~qBy1P&S>dR z^s-9EOrY~p$`f>LsBmE<@=jK`5akS&nHeX#Su8_FDDw=@aqSh(gVh?jJeLK2OqiPO zN*OnK(zvnTnQAYcdDSs!T!PB~;``Hc&pBOd);gR&8m2bBl;{|$=x5Cg8N>Z)mX{@Y zwS5BFyKV$?YZh2jSBPG@^WS}spx583ACod`&+b`c$J93z6`g;kyL*;&%`s`#o^IbC zeVfib{A?dswSms)FeV0>I%CdIonh-7IgfWzAU!%Qu5>7ML-tiM8+kF$CrgW;u(N(< z?403BDD2FjAp@DsqjQg(H}ndmLt2b;hvvDIG^Vg{g_M)yd)fDJ->tj7z6P=kedc^o zQ3C+eyl?mP4or)Z{zNGgX-IQr9ZLFxTrHdsz6ul{rCg;2PJoXbVVjWXFe*lwPjh@@ za}ddkZWaK0D{D%ua-$Zw1A1a&)AA^BAWE55Lz2rl5t=t4dD#6z?+E8&%`+g;>1zlP z@dCmJ4~O;N7#+UEBC~7EBWll>UXZ-+3W%)9`K_qte$BHYp$;M}im%nDcwv1#J-yL# zgd@r%8&j3XD@o2!&NUwvNFS*v^;(EUysI*Vh#NrD(MN!s z8SlQ3Qv}hUhQ^+rhQKG7MS16e#L7%mOyDa1H;{!%D2{nX)69l*mKOfe&UyyloX#)`V68gK zh^e9Xu2b`y=Sp9&&hL@VmPVcZG0mt(APcd{wT6}nQX39=Sg-*Ju#c1R{G^SL_T{S;X7 z%Q53^ir1r-6%Wqvk4#xlX?e2!cA7T566M=qG+ z{79l3LU4D4a1SBb$*}e3U_R%Vh#y?Gs0cI6y&RAe!FfI!n2ON+SNrfBZ}FAUMO>I+ z^T?nwf-^Fvc#TjTljmfjuL_RiS?YaFM4IiI5d$QL8FnUJHfWFFEFUc#NGNGw zu{G!!PGW1M{{g{>JP7hI`X~`Id>=_9NpQ}_8eMGw z4JL+2!sU*MF&2fK}0~#l|z1o?4>|@Pm^8;KLiQ5Y_TvVSU>7W7<~a4 z6%)?wMUFNtK!qv+Q_hN>tqkwwR zXX*$eUi2BO%+qSD%adTGFGsA43I2{6(nX)iqkwYJXEevIu|0A)#^w9K>B|!1VuID9 zgmBSk^k|@4f^eGZH1)g44y2@qJn20_vfcj&be}NhyQRfXWQ+RAT;hV>@7;}B(kSsyCI#MVWeI6=t6{R=EPuW^(6gv4wm9v)%dPSe{qlH`v zAqQ*@=a>}|6{o1Q(Z{Tq)8i5A#}S01BZXGcXQ3ab@{%8?0@(6A*nK2qYg*cKJLESe z#318s%+SjLrJ~R0Q9-B#;W@S6Om8_h<(|>SrWmt!WRNNP%!@55B?y-o24T!Oh;t~I zlxL{te7Ry$jM;ekAX4;MJ!)u_AW}eemF``y;7}d_85n&WiZQQ85{07A&6p!lf^d!L zF5pi{jFRI|{>vYuyexJUiKgFt8KY11CF3$fo&=R6jyrit;JA}-fc9S&xD#UoF!HDq zeP)j!;v^U&!Hy4f`qxUe!g}FaA?&phiISMwJ0cw%b?C8QT!x^5egU-B2g5^$4BA#7 zJ}~X3lbDC$TIu7LjklDLXoE(ogWQ`-Z6yn(7(sQQs@gxcBhCgioi5;&9g3yJr4SBH0Gpu+L&b# zq5+1Egy!SGyP;Vopn(emoxazGT7^{NHN|J9!Y)zJ3<wNu|TI1;^b{C z+%5*pxEFF&veDysqRkU4O|Byj#@PR#9QMCnlEXP9IxT)Y!OkkQ;Qzkqt%Yh9iT|cXU)#%xT>KcUC{EewMTf-Zb0uCKOJdzqM*?PV(5?)b{1e zz3>#$4!3*i6tq3QQ1YY=iOtP*wYAe{OfF1K%g(NvF{Q0;D*V)q&(2+xi^kbg+X@Ox ziWgrImojad)3I?(LV96#Vj9qqKLep77+KnF%j2Gn;7;M@9jVmhLc^^xM7Sp3v%@ub zLh`~bNI=ITd#<;)v7tx)oqU*+BMr4ad)1s-1^I1j3v;tq`~GpTrVf7J)4g`Sry?us zS)kogpq&S3*Ccev93*?DC}fd^1Ozo>A`lM#xjp0Hp4jz4ZnJ={KyDx=A-YIp8upom ztyp|`hRfwHDyf&G8QsKagOSWh8JpuNc=`!vc@9d+8QW1@1Z3^3s+ursY@U>yHP#_* zfv;2*C8Okl&g`5jyS+L)d-?JWyPFy-DrU@Rls4XQ!^&)g^7C6;^Yf?ASdw01PqQW1 z5*96AhVD*I$*MqWfR->!(cva>D;`&x0p`?S+N&w>caWvnnLm=f&gM+ibU! zN0h%LQMtU73bx~ia33>-HlS^Ri`+q`{6gOX;XwXi9~&?LJ8Z2g(;vPqAA=j^ z^RMrRz3-G)e80CfFK_3KIrcf;_KqEWl_fnpii_?-6D8^ES1w(->aqTx{`9`bdU_t~ zBR^g*xo7wCHI5VKD(B5BpHP@zt}} zal2;|a*0F|I;jjFmz|X)(vr}`@_bB$TX@1(|Dek5j zGk_R);Csxj+JQQtvrIjym=}@92T>=ig3t4Sby<0YcUhh+UkXqds62U++xNHq#hIB? z9?n^{cK(7!6gMeltmOOe`Wla?``o3W@?V%*J#);MxHvR! z{0W;a>GY=6w|C;W^bT+rU$(Xo8F zB%L}XNz0cvH=Xt!mpT^g+qbAUC#P3h{f9HeqW@%0e%|CMS=r~moRyWGJ@`z{6vToPv=x|h5*mI{WO&veJDgEC3)U*PS^0Jzm`joX3Qd2W>l9Si1OGzgDxd`^ccPq%2 zO_4q!@CiK1#Hq-WMa7BDUGz&C@E0W?yCpI2p$0eZIk;oTLCU@z z9fY0lzDvdnK*^8Ct5;O1mclO(mhdG(q#S6}z&qt}sF-y5ia z9DI32HKA{;

    RQg09y99f}kwqN+`eB-eVnJrvWbfzDMcAA8`)A(ykFvPSZ@w3L-C zTXq196NoLy7Q7jB?!TgQVaM9FMI~!XOV_TQH?Ou9>`eh!%-UMD%v6zU7I4{x^TU5s zRH)oauHE-T%ScR$LrE#+_33G8$dMa2!|on8 z;fQM{!ebIrmlRGzDJ2D-#H3lNldCG-S>w{NoygP<;CU`QZ-GEKz$c?Tvia9kfg%?%BZIe}U$CH~qZh4*p%*xjYj0?<=lWiooH+KbJp1yj$*|zN zf4Mzx;F0OmuV^nXoj7;y#H+SvXEj%6=S-Vgmxa(*axyb;*7o!aJsEcawFOtIBNg#R6)rb4Ah@j@9~6duv#oGDx20;2QOegd=L!HH)|Cp&7BG6%P2%t)Trn48;k@0Ckk zm z#JHs2yliRRtRrjOk8IyIYf?$iiIdgR=I(X7HZQBm$Sf_*gnw$gIZl{H_)P`LAr*Sa zy_66IXU?q$T(6-Yy#XG{nl)(Z)4ey(-O=3*-azxY&6`u=#!edOEGu2Ieam${k957$ zG-G=D)b#Yli}&KB3GmEc0-pXIp1A~`nVpV{l1hwVF5z@fbqrR^){FEhhV_T$m%(_T zaaLBvO=~OQXRYrisL}sHC6F66z%|doH3#9E)LuJ?zb&gRXs`U1Y6{G*4p$x#u;PJp zozo=GW9TC?c64-h`chyVc)qo7N=3yHx0{TG4m0E|7H& zoW79^go)!RUK@-e+Y3-T=Gg}VlPOSa6 zOS3z_^4TjQxR$^)Rc_)IwO_QaiAm3NBZuuQ`4a<&tr;RDBFV1$} zl3AFS-BD7KT6Adt(BH2EMN)1bsR5(3b8qhaX*>&6S0wWs{??>F)*!ZnwrByYjS2Q+me&XrA&G$9e zdG_pCvTWHLZ|Tye=FffMTkD>}ACN%TP#XTAuWu+V?)Ag9ldid<<=F8b4Q7k)Ep6Fd zzI=IYMQcTQVfm7U?HhBOTTX+V3jOR}Z9+YeiI}CL%#5+%DuITidgOaA0-p))o_UPK zUO@yh3%CG}qC5e5z7XF%hxVNvb0}Ohvw;z>1^^~iA+fxGyepR; zSgGcd{7mhF!1iWXAqeTjIst+6OX3S45f3*@Z`;Hcqqui=Az z`#$hBTxf3g!W0%_F90~0d;N9Gj^Fvm*ZUTK(z9x_OZqUbJ>!&j@GakK%a%bt`XpA9c-J;*N$#fV*|X|8$^P*{j<+4mF=?Z}&dD{9yYmN#3G)%$UC5 zvTOtNgtWyMp@)bj;umJkCkCP=xDE01koNaa5|;5NAgRezD>t2$YL~4Iduo6V z0b2?C2{*|jksS?8Jj-!ZPB&=vz19!;9dEB(iE=-@S!4J(Z9~6+KSH+=5*z^n`d;%s z+ubK1*_MNx62Cl<_6qgKfyvC`5(?oS<}HHoRszg;B*h7R-Tr+q=-Vt29wZ&0f zG-=YsU7tHDb?#lgW{qrj!1l8}EjO^-WSrmb5PeJqRU&hxY$u1A7m!8b#!yWpVMrTH z{AA7uW`NN1UM~UY$QB6f3+(ekEU0BO1aOQ#*#?QHVWxYCgh*@)$=o-1!f$fQ%F^T7 zUG8{PnrR1%GZexYi9-L-i`4#>K*Z@VY+T`A5z>bvzIc3slY~EQgoQ8}lFus<&7dN` zyDkTpRi3+_d@Aogi~6Immiyz0?D(dSO7UoPIHyI@oORk!*)jPu+-*w-8eGRvDw58Iy`GusU#jfmW zRNko>516AHCM2apUcl>nt!dN7pO87ZvuGuxHoy=3bOrTUZNinnJ&hmeae0V6b4f(- zVMe0vpd#lgw@@SK!zL=fd&u_GG}wN#X3DeIaN*TDjDPyGcWC#+-u7E=;VvT=RpvO# zl3MBhk%ncmj50qfNdOZ_E5_o;d{v~Kb2RI3JuS}UQ&uQrhdz=d@id=|v>ROcm9?A9 zS%Iz=*f^NLy#9#@Kf|9yx+nG2{9750bPW{D;rN8Xv+8 zWPt{81n_9!N!WS@?4}K>V^}>hwIi}mCwmoiUS-}9H7mhfip=2#lXy?(wVs9FI}G$G z)c%O<82(c2Rt!znI*Bh1_bR@iaY=}e!R7_(BaRW~l6k@3voxKA2dI6NrE!*=`J=0{ z*(gi)35mVYlR@zgdC$>a^){oL4M>NrI9bgE`q>%pDTo&Sw9ofkUvE#3?|t8^{rys3 z@6S;dn3JYJ@#8$A&j4#q8iTOpmHXX7#zIB!Wark5Ybz_3YHv@OKiJr@9o$Y73k`y2 zJNTBYd)jqB*agZJ8V{uWATuxojk4lWL&5m70)!2C@;Anhx6tdpzwh0%XCnxI&(W)A zclb8>{up2v2%_V@zj}LtAsu~vy$^5smG7ru&LHK5XRrBwPmdRsKK+?zdjpvQe)*?6 z>3jv*lOj4RSzB^|NJQk{D4QCtUQg`xHwWHM+XOCS)> z1oL-2lgrh9E}3hnE2}?ambdS$Chc&;5bipg%zdGvbkAs?mG{%re*Jutpi738{&QcD-xqg(n5=J3` zB=JczyK|zC0-YLsukY4ASh@u(hVZ@j`yg%%@lX9RGP1fLbGi~Ea}q#L280Z>>rb7> z8uktClcDD&Defp=q4sXrVLe1%kWs$bPSQCCQkJyGSDJrm z0TT!bji{E|#F_=D4TQA|U^(%xJ<})WlZ6BkDIW66%Mry%8FkVXT{oTSldf)_J_FD( zlL%nimexX-%!3Qa?W4c5*j^^WlNPsa*zjYrXjoCSq|!~025P>*eaarSEL}$NgW|3uby?J4x4Tjf|c=W6FaUTA~^)($Fl;B8}5le|1Lb zdd$B%(~vgUl1$}yRKPxp7QJ49CQ`xEFK|JUhFV;RPY@B2y)G62m(7+r3x!{Unfd5Z zSHqd>8$9Yb__qD;f3V+-tr~j!mVU2qA5f!zom5vNt@kZahsAH}SOQT*5_4m7JMm25 z0m7f&;o)J5cVya5_F0+a&z$KygI5iuJ!NUChm{-&LJ_xKqZbg}R_le#3s^L>*c!0F zQ|!030XAzY3x;b=+i>OS?39(q^!B6jUZ#QS1&bA$ig{jMqV)5XOGR31PS*`L3=6BF z#Fh?lp8OUrK=lxrf9RTl~kGAqrCX0u-H-gQ}qTj!;2}vY!L}#iZ;u2-C zS;_@Iht{;p2~b$&s^Vo+QE-m~<}myW+Z)-J!Y!@4d~SPg>BO8dMa9+e2~~^OB&X+i z5cAvb?Ul%c1_5Kuyru~g_x~iwgQCH=hK|trxQTF|#`#DVpTzrsmoOUxGhxZ(fEbyR zy$clz|JwaW+uPDo-R{lcxhK)+;GEWTF3%`nfcqlwNttMefvn+#eNdo zfQKQ^am$X-&(Yg?^k}D4wW!1U=c`F1cQsln$<&kvt}DsqZ(vJpgXrvD}-JU4dLFS2>+Kc64vhdDNl~DJ3C(;lSCY-lGo#RxBGel_ecMoK)6SK~vkWAG`O! zfqRd2ci$PePoIOxw}3q_*iXf5fJjkSHz8AY_Ea9=@EL&_g;n2n&)XNwC=C5pR&`X~ zJ6-bD-y2%jm3t~9_e9Jd+zkob7i4DSh0Of;I()NFG}YAXLkWM^p%-kc{qrqukF>_o z+Z(rEpCWL{q2Dr^02;msU}6xRfP0WbNV4*|h`SSlD7_>tx?o>TP16YogY^d93e0|R zGtwH#o9XedfWu7Y5dY<3{m_w1>puN5#3fDO zhcCVwh#&Tq&Pkt~dGyrLqo>H6+u7dUnK5lzeS1X(1kv!tg>e2|pZ-Fi_+fv>%@9BQ z$=77ak`aFR>io1+fX(}EEX$mDM|a`$zva)WO-YH5Lusk!6PI^2Y|2*aOp1SSXXKy2~zUNRAIr#Q^7r9S#u>g2{81B;oKJDVu8c3Sv z7heOUFyWJ4**EpwkJf8GKGiePgSJHdy zhkJwX8Dh)^(#>PcF1qd>Ybt|Li?n;D?emcvs}MB4o`DmmEftQm0HEL1}u zqm!-G$umq9K*0RfStOD^9BCFld(iXhWjBYPS>P3FRIj%cxFEXGBG+jCd(3pov&}sJ zt>PI86vE0VVho!~G&?&J|B~*zYsYobg7;1t0iIW%!=puIZoPjm+4 zcBtKw6KHyZPf|Cb&~PgArSP4se>d`{BkSMbYRhaPj#%k30+j z7vjNv1Qz#}j@Ih~;n1Kq(WoK;!^jc-lFXKdu?lOg!{g-KmS4^js%7c643r~DZEE4a zrbx9#v>fENR1!_+tCV-B?c2rue$``ZZ6C1%eh3K-d}V0Yp8*j+unyX9D9k6Tf-!Fd zT0u?BJY&UjbEc526TYZ#2}6X}hlKz&$U3!2B)R{g7wH^9aCM)timOvmG$A64onM5^ zBJ2{&sHk}wAF+nCBT{CFvU^OC!doo@?o=`;<0^>G5xgCsH_hPfR1}Sa*p(UVou76g zkav9EY4Uv<8iGgXnf$4lQg+I?u_=1+yB{jVxh@0}Pa%Yi!;|M0j-4-3J*OtdNIa5Y zh5Q#elyF2V7|SE!6GgO{5@^ih`+>?}c}f2=jNgHRpAVCH7geH!yBubbFB1b&r_ z?NiGGPq$=P!TG5q0Op!M^A(gCv>#zRnNw6I?yLpSpO)W>&uI5!HAA8Jw2p_BMK5~3 zQwQ2-VO2$n4S1eRi|`9$U8OuXAXIgH<>4+B^%a;WNzGemY@naL)NMYl?^F3bXMLy; z;j`>Le^0U38kfopQc%rq;w$*q2xEjLWIprb1T~xul3CNs!jXVKsTN|f4UQNT=X04> zXVEnIH92=ktr&+5{#<#|vnCLP>W7mtp0eF0u>phi4_gTG#et%`2C!1JtScqkT zuE4hGKnTTiK^Fw-cn-}z0Vg$cvY&@maE2N_ieDMV!_aU;DzE@GR8L1HQHLr<%D4o= z$N9iTwV~C-*ogPepoJlj3(6FeFhyq}CqoT&F$;9@svBlgK@Rnh3+gqSS|%ZWqa%~U z?MzUyjDo!hkkD}Lj#;FlimTfAV{5Q^)Es8fFQhbmvzH1fSw;kL6&P|+XWpbsgAf2l z^#jQGMcKa4xsvAKQ(0D*MxHqS2Rs215f3iBh5=$7l%ifFf)*s}1dB-~J{VdNpVA3O z@KR-+(6NkdB+nf$5IZAd8`aOoR^s6EEAR@P<+d7mpy;KHa}@Wh96vy(ar|6J41N96f4JneV#^o|9l7<5cEnSiB)zETr<}1P7Uzr+#j^X3qj5 zl2U+xA)gL>d{RAZ2^*=RhK!4RoW@=NQA8zO%W(?H(1-M~!h0TRdJ!9Y$5 z4VOFy&JU4damdr6f z9|J_CiUsftfJv~)Jb+Yg01t+(xtY!3S$U&{ws=5yq0Lc0!|;`Kg-zMfueZVu3ba%$ ztKoUL&s`yG1O3V*&;oP7$%z`jM&kw;SyJ1{P-x06_})OTAed3b9~hx3I|i(oR))bO zww3teDgjm80#b6%eI1UoBvcEI)>I@msLw-?muK{`nudN4$6xB>Hr3vbRu6aT%=zk> zqnNY?ix97=JUUrz4p-LjP@A$k_j>VAppAu6rf?CQYE=h009~hZ4;^J>^N)G|&^z*UHVM=Yj)2)j}a-K4pJ1iNu~2 z%%@sHI3b9`84doBA8Gz4-Q)o z%1=%m5q*p(sXGgrn_PwM6jH(|*#)^{X7?!`& zeGc$Tf?-F^KUOIC=ram>)bRQAfTSUw6zo35k&fD?RVcCT66Q(wQ9SZ5m)EmJMrhSnLkUDx96%#>aA4~Fid4(n2dKBNy^Gs&=Ndw^K&1xJDu zrU+y^X}W9Nr4Nm%6IN%rfn7!fQs_ha&UF%B(P9@05_OOR-#Umu^VE9Ypr03ABkFaj zBbcGS5Ya2rFw2ZE1?yxt^pGhUvR))BprY|wkTIGIi2_bg!Pk+A%ZhqN-WJcyt`|{j zoi}*Gh|FY9KT>n&@2$5*xDhB#;z_aVuVHnepWY)r^v4+z+!={2dsAf8~U*^8RaZ*7~KH}%M8Vo%PFmK zC;bCg!`{%$A`o1MJ`gepp+ff%v_{4-yAAqMOwyA=FT?Ff=)2#LuylP+^?xx`WF7^Y z$}oR5=@com4$L%9&4fC2M_hmdLe8nn;G$P!nDyU~(;7JlYY6t&3_jH)CN(U+*p@}yIr7p!j7cAr?vf^G%+@2U|Kt_I zAvSYHrw65ZaTlKquU%!oXo8lc-E_DuEeB2+nRc?~{d5KE>u@E+K6`+MvFLkg!Jf0F zuobK=Z>er|aclvItLnjSLNsOlXX`xDXLEyan$McVuZw=KO$!8;(Y)bygLyIv zojXlTk8;e0`6g|`0%0$YVcTS26Hj}=%}&clc-;q z-9k;vC)Ino-y8LjEk<*Q$c02Bo0my*5gi?fa!H`E${t`6L;<#+(# z)pYOxh1c;_GVq4fG-PuLmAZ&~!djC#Yu#hbNL`p6W(|c7;{On5`E)0pM`jBx1UVL2 zA6g5PI-m;-*O4KA(lIy=L@NnM=d=^F$hm4uzzTvY)5d1=!5oU@CJLqEoPmu(B#gOnM2+IgTjw z6SiXa!tFe4CX^g*cVfe(8r4y@9$pUXVG<1^ViW!T#pZ?aWW=40&8&r^)D78?!VJH_ z_AjXgEYlXCG11+ydM2BfND}_C&|Z0{#MlrLl%jc=o|q#3^R2G zO4`U;YMo3GW;!~X5hOYJU{Ir=-DA#JXsBMYJ$xEr=XuiCmB>s4eW9&Mj-?jTgC^3o zRCX-2nKlt7RoQ*ghkB!My;basrC!wLNoX?_ISJjhy8o}n8fj*3HPP96@D&41Rn`-H z1s1ZJLeWblnX6ZmVVbh0-5&X2P z6b@LSUGOejrV#N;L5CUQiKh$Wn$u17tX)YepN$A7-UB_j{-vhHQd8S{n~0hy>0m=2 zt=!EOK5K<`MIkZos<>nH#ZWx0*C{u8BKf-@eRLnPCYsEDm3v9f5GsXpR_IH!T#2l8p%y?2{WZ=uVTMo* zmB8mMaY2i$S6rg0RQ+ij4`cVX)fh zXl+iVaE&Ey7DOgl}2I?wit;01igh zDOxqvn!)jZ^1sIGA;MGA?gdoJazANVn?j@cL$90&lV4S|&1(I$dR89pC%FNecC!_2 zGgASe(nYf!s#QEQGm(gEg4O|Wf61_qwK*&IY^X-pZz@a))#zHofZ=}8wU($){37*Q zVOWnX*K|;{*|O!@~!b>IfnTC|Ic_&`jT~RzO+*I)vM-NVS~n8Sb0X zX*{}Jz|9$Y+9^C&HGx~`6mD|PhS~yKwszVMqmj6Jst)j>j$7t{#%1M6Ab(y0HZ2VB zq!{8)$pD4nDZxG%#8GX$%T)b=8Sx%AU0P{guCdg^=gLZ`&akuwpmH6uzCbloDoX^C z{*6WuS3Ip#ZV(r}=o#$b7OY{q-g0+%4_MVYYxtaagB95|(e*eWTws7mwI;?iZ~zTv z_10SkJ=jh8AaVrZ*NU&9S#FtW(ykV3ZJex9aFU8SDyimXNF~3iu^0Ys;M7WF%p#kL z!e@Fp&@8!--6D~f$+Zk(Bexd@ec#QI_O)pq-^!dE^f!gNW*Wn*2`c*>tiPhXezg=| z*O6cKz6X43=|EU@EMr~)VCsN+2-D#h{n(d@X7nlpp+jR!^Bc{|Yy4)vyaI$1TH$46 z#lb$bunjS6f?JU9O3liIEpf^IO+|&X0pZEIURw0z;y`A_FAS5{&^_+WN$qK=L!A#Y z*#uuJv=pc0_mdA=vH?M-E#67i5fbxcP4g4~O($3wqO-mk!L&)b;lcR6>D7DjGXtme z8t<#bark_7t0n3M#jF zbreAjgONYmZ_v|>L?1{61^Q_JzPI1rzpqbjwoDqx#_J>#bb4%4vmm}59EJ$_2S6~O z^}Vq9Pe<=i8veiwo7zjT;V|)X@%=g$BZNyUugCoMwaQ}tE6Tedml06DSH0#oQcy2+sqVA=vPiDLTJ(}gaGXj?hvf>Qv+IHpx<))L zNr~xdI$PeE!3@R4_q@G#1(MtH#tmxfGb{<)zb*;rPkN-0&R(5(I z$T~B{RPhBYNr1B56EmM(N0-nO)LmATC#u0lI$Kyo(6&&jbdLN9-R2h*@O^*W2m ztC@-qWvU`I9A?@KB;To)N&Vd?WWj@-3Jf}!*%ky!oY`8q?!s)ner+C ziWqk^^bQ)g1bY9$X7`J)8h8r<_f*(hic^&XiquHD=t!d8kH&&wbAYD49Dn(w~L5umV3SZ1~Zth zRStWT5w*pZ>qvMG$Qs&9E$2M(KvS6^jR=kEh8vqn{yLA#SgSuVv*5L5b6Z-V|C4XCNc0bAUog|bWbYhaU>j^GpXUrOxl?T4SZMm=c;7mxUOj5cIn0qEjs zu@7Cy3JbHOGFM2gzo~#R*~}0s*royl7YqmpI!x#XJ%$p56k)w^1}rDZBgU}Py;-V8 zqtU$y>PEHE{iUl7=&i^r>2tpus7-Sc5q7yZO;TvEMna9feZ53i12T(&9k7OeY)rzi zo80$%LQ)I%$z-y*nAAQ-y9>KnqD9(--I#!pHDUkciI7xeHj>p#FA|RMS=!&>D$*)a zvxJlzn)F@KuxIz|CBe3IPD##lY`o`{eG79-j^ELBgb+Yv}xM%eU&VMK+nsNx=SA#Q&qjw zk`(~xwx@VGssTxvyh2}3xqlnjP>bFXapXgy=u8vSRI8cn&dv)vdU`c^#-UbYN}BPI zLNw_$a*`A}+62;57INOIUK5dZlr>90-bliHLs?_8@rDt|VnbO2vD4@=^~q%)M;A1u z7`^-UD zKC^f!(>9Lzh+yMplRft$>mVCvnnIQv$QskfdIQx8{33B($P=G-}wDLW6X`B zou<`}IC&Ntn;O%_@6~NDjz51szIgl4Lw$XHUfaTBt5@G}!|HNZ^-V5S%U1*Ar05pu z!AaI%(dfWwUT(r~z-dgWa2s&;-xykuD9!wR{y4uV`%C{6t`+Nrn~-3C0An9!GAu*ez>t!ks|^$>8G?+WfybXmmFG z{5aQu-e5DCEZT(s>wR1UYLQ+54^cZO=YMtxfO;R191u-%n?e@GAF#Gfx3QL+L2y{N z1ferZ#!dMYUdZ{TYlM3(M^&Pdb287Ggp=WKc^w@#nTgMYm%%b*7{PIX?x^hgIpI6t z!CodgF7sSWn!wvq;eRA2_i)q(WOC$!k;1}TPd;?^a6KBGN)#Ng_W_$zL~L;G0M;xa))9Dnf{R+-U;EW zz@6dg4jCb=-s_A7|1^1&U;zuKuH#E<_}RUo?pjP#T`wT{t+EUjzHGake#G% zkoo>{PV}?FgTT$v%!^v!WnDzu@kWP>vCgs@vru3r}ksx!=RW-Nc?oX-84BnFI4oOB!Q!>(MAFTA16DMmXWY>RW9iNxqq` z$wwvUY=KL#76^N*)95fPb{T9FilycmiT@oG?qXUsa(QkG+yb47en#Ak0St5d)rggf zVqR#>k>u5P(XMjw1yKBI4aEWG&Es~ZLQ@jcxpfO%skE}&QeWRdXLJ-N%9(mYZBgqA z$>VPqZV6b6#Uz(+o)u)_71gZ28Qt)&w4#9wFyw-Io{_Nwa;#}2@dM@=l2jQ>q=6ak z#0HIEf=#kNkaRI25juf1g0}1t?tr;vq%aHSc@%#j2IvMetcf-P!UR*O4P)^nf{~z6 z_{#++VV-3Pbt8$WFvH6cMO_#(n)(ft&jgbpTgZ{bWteAW$`jVgj~TN2V`p@nh*+UK zj9J9iOQg1WIv~TB!j7c!iq>4sfq4o?YuhZ_$>@+g?kL|{Ye|Xet+jmjhr)gtQG58# zTW994u(I!fJ&Dn?sPMT>RuO?ul=jTjq)?N;?gtL$X)B!!=Agbl)v zV#3j3DQ*Sh7X59NqQ&LJi9E+0Up<}{(uBG7Qe;Z2*_o-Lx{jCIM7lFmoj+Ek@PDOn ze{8rP*y>W#+7=e(u2fcBLHm8^uItr3Ria~q6JhsQ@v1=cYqZrDJx%_mtep{TaTFOpZC5HkI(PnV(g<(Y+QXqU#$OUEK{uhsssO}I+90eX@?4qT@BaTOmEGBd^^ z=#(i%u$N3sd0bZ**CF$=@h;yVeVeS*97mJ6G+TbZ!o#y?p8d?zpY&NMPmUUA)jG^= z^Fbjtb1t>cAq$<=!UTO&Ww4r=Zj8~IbK{$I((G{~wgL*VGMt0U@C_6MJ-|j79g*IrOSaXQDNSa(#RtCO$CL#;9%u5=~tczc@?}&1@GE zp*F&9B2TIm(QI|RYW*^NP5R~7a4zWd$J^nW-F75e9811kFNUzUVG)#uLvO19#iea> zBjc8>KquNq;s@q-E>YV}u4iA23AaM5HH~2cu~}Hg?eH%w>&K+b+OvDs*fI5eJw3hM z-Ls@?j!CojcqW(AeowT^Yh;YlTSkySz4T^+BOC`z5FL9DqAtPv29+`&1I#3+1 zr=C&iWX|fvs2upKjx;N`aIpljjNXjz;-MQ$hyoI~`RL8)=&tQ+s+i5d2HWF(eS;@_ zuc31MJ-vnpL$K|=O=LRy&c6xzMHn~Zur7CJu6qb(#vMFUekKx^5xsN zx8@hD|FFrP*W9$ItZY$pQ(4)HJUht;;QoIkBo6*A@g;~pjfH!MuA9ZKRB*z`%Ruka zE7AK)uS|T&_ekfoX`N^pe-)7LcKG)5P`i^#{;D9=DTn?_`#+fKHw)SNp( zn1IR!WLs!^R7eyi2-!lOPzt^8>w!0mg%yzV*bexsW5OwUpRUY|GDMn?0*eaZRb;OT z@JbJq1iT?%_5V%a8*-MwuVnE*(!3$x^?xA#pt@45J}gPqXRE8LzbVZWt2dItPKIPM zRFGjl8TP?99w1-;8~N=uGMp#FTKMZ)@}|FZI+{M?OEYG`=zF7d2AVM=mwt4dj(5}X zt8~0yHQq!2;B)j7Cmpxb@f&o!kDlj;^gR(Cp&0!_K=hp;%!F6@JrdFdH+zqvfA&oF zUKH&9&uyyj#0gV?FY@2UgFGEz?~{ZiA(_2T5hg+(efk|3vV||Q_ee-WN$fp_zx@t- zF9PSEr|%K{{;TZ0@_mG`kSn~;J{N^Fb{-7h&qI^x=NSGt3BAeQV<8LGv-hHqims&Z zMfiRu^0N0xNJMAZd$=ET4SO#N>FjeGe18hMhkkBTo!|G7=|oj9Qef1Z6OUdYG%oJnx4A@+HSP>%n;?3#UD zTQ_a#vVYUwx~Y4+ecs;gZIyPZs=BJu-n@Iaed&&E+YcSIFYP+mb>K*scWKwQ!@D;f z=-9re>wx`=EA1_t4jtOGXY;1+ZFMWU4jkOEx7+Tntg2GKw=dY#eR$J>YwYfFdS3ds zS6r$5w7Xhq}7qR=VuXhYud>f{(fncW*nm=>UAZc<+Hd zo8SmPV|V-E?qhI6JM7DL&>PwcU+-FW_|Ty)JA92FUDzc*fPLSNd+hK~hr4&s3$Uk< z@4&vRs~e~VBR|&V|7330p02KLxX(@d_I2&vv2&N|7KCL`2DVS=g5s4;P%_aa*oAMx zzgGCI8w{gemw?!VP}w6e zdf{jn96b!*+ytL>P>?w}Y8S46*2Q+A1wKCn|7`;B`DS{}ZP0>o1wF$-I5W96F%p$< z)vBPM+hN;#a=(XRJOIx_{-PX4e!t3f$yN2PxyyfNq%4lmqZ00CH(Y-kJkLIOJp`X@ zf!95xpBnx7w1o7~2>iiM!Dqyph!FRWgLyOo=A9(yh?@*M2aJV%SH^=aN)^(;;$;Y# zV8^n+e&q<0gvns{rV7)*3fWd67ULCiDsfIRE;F$ zKu+XBZsb8Vs20_sS*RY(Mh&PDwxnrBEocsEMQx}Z%|-Lje6#>{poM4=T8yqhooESK zimpV<&~mf_twgKPYP1HeMeER2Xg%71Hlj^vGunc@s0(dH+t7Bj1MNh+&~CH`b)&s# zAKH%&po8cTI*g8>tI<((4LXL7qZ6nHokX8O*P_p&Q|LN$8eNZWKxfd6=q7X)-Hgtm zThOiObLcj7JGujX9-T*DK)vWrbQk&}x*L57eHq<@?nPfg_o1(%`_b3X1L*7MLG%su z5c(#17<~&pf*wWRMvtM#(Ra`j=)33w`W|``eIGrAo<=`FKSa-a%`UCnSdI$Xpy^H>g-a~&u@1wt>zoEaQ572+3579r+|DgXxAEAGui|AwY z3A%*((Eu7mL&%3d#R5hcV-eeM9FE5cI1wk|F*q5g;IVid9*-yBRGfy>aR$!B6LA*K z#yNNro{Xp9sdyUB#de&B^Kk(##6`Fmm*DAm1}?>AxExpDN<0%+;c6^l2X6CDSRD1jjzWy;4}C}d=oy4Z^q~FE%;XaIeZ(w z9p8aJkI&;T;9h(uz6*a5-;KY7zl`s}_u{YM`|wxs{rGG60sM9RApQn^2!9hljK75+ z!H?o^DSiR}3w{y* z4EN!m{8v@SFJe_$~Z4 z{saCaeh2>vzl;Bj-@|{w@8iGXzu~{*5Ac8E5Ai?n|KR_{AK`!Ei}+*w3BH8;@cJ1osyI!|742f4m@gKHg<_FdES8AV#TjC$SSFT>6=J10Q>+rJMM-psPSGX0MUPk` z){1rFEU{jkEjEaaVw2b`wup1YR+SxK6xETrX}AH;S9Y&Egi(D|U%n#ckqtafi55+$HW7_lVu%UU8qe zUpycl6c34q#UtX?;!*J$@tAmAJR$aoC&kZ**NUGNPl?xwr^V~V8^kl>jp9w>S@CA^ zoOp|PtN1zbHt}}x4)OEidGQNkZ~V61*X-LaCBV#B?Ww6^uhs08f9_zvJK3v?y}H?} zhrQOY*IM@4%wAjA>m2sl%3j;pYrFhf%dTI`u3yWpU(2pv%dTI`u3yWpU(2pv%dTI` zu3yWpU(2pv%dTI`u3yWpU(2rF%&yln!miiCuGhk@*TSyX!miiCuGhk@*TSyX z!miiCt~ZBWZw|ZO9Cp1q?0R$9^Uh(HSH&&WE*CdLQZ$tyU(Q_CmD| z&24yw9n~XRtsc>8WuguH^BVQZ-NQIrOKIgA_9uF@s@2fSJ&fkfdK75ZNQk?ZiHfI+ zzp3VLB>u+1-#Gaj7k}gCZ#?`>4S&PWT*J>?!_Qp9&s^h>pRvUw%lVvELKc3`8h(`; z|0nz^HT)_y{3^BlDz*G7wfriz{QhhC{nzp{*YY#h@-x@+GuN^+OI7U5u%4D5O{!vN zma5p9r7Cu2sfwLh;!hz}u`^3m?95UXKXWxdb2UG6H9vDTKXWxdb2UG6HM?i2nxDCv zpShZ!S>k7w_?cmkczNcP_?absW{IC!;%AolnI(Q^iJ#fQ&+Oo5cJMPh_?aF2%np8L z2S2lepV`6B?BHj1@-sX6nVtO1PJU)5KeLmc*~!oBWH$OAiM~UmB9)4yIKeLCQ*~8E5;rGn-QR4b2aeb6(_?c_?nYj*1TnD8ZerB$N64ybA z>!8GSP~ti$aUGPn4oX}HC9Z=K*FlNvpu~01!FABVb!5?{po8n6gX^G!>!5?{po8n6gX^G!>!5?{po8n6 zgX^G!>!5?{po8lm^sQs}%yrPgbZZZZZZZ< zTn8aH%E-cX(8+bs$#u}_X5Mv8mFx$$xGQ%HywK4bn1QR()-Y@ z_aSplwHK<<`(~})hs}B)w&;C0NAJT{y${>;K5Wd~`VkDkqX^laABQ&s^Nx05bz zCtci5y11QmaXabacGAV|q>I~07q^oxZYN#bPP(|Aba6ZB;&#$iJxt5yxOB8k`yqP@ z?T1V%v>!65(0<6ILi-_;3hjq{0jRkpCKcLmGO5sh$fQF1A(IO2hfFH8A2O-Xe#oRk z`yrDG&4*mev>!65(0-^#Pp)O!Z|c#LYnk?&di3O4rv0WKJ-L=?zo|!0u4US9>d}*H znf9A{^yFHm`6kyg?T32w;&Yjc&t)z?m$~>{=Hhdii_c{) zK9{-pT;}3)nTyY5EO0y0|~-;{K$I`;#v2PrA52>EiyRi~Ex< z?oYb7Kk4HBq>KBLF78jdxIgLQ{-lfhlP>O0y0|~-;{K$I`;#v2PrA52>EiyRi~Ex< z?oYb7Kk4HBq>KBLF78jdxIgLU{-m4xlWy)$y1AWnb35tgcGAu5q?_AGH@A~+ZYSN` zPP)0BbaOlD=62G}?WCLANjJBXZthRIxj*UV{-m4xlWy)$y175;=KiFc`;%_&PrA83 z>E`~VoBNY)?oYb8Kk4TFB!IDGd3JMu(#`!zH}@yq+@ExFf6~qUNjLW=-Q1sabAQs! z{Yf|XC*9nibaQ{w&HYI?_b1)lpLBD7(#`!zH}@yq+@ExF9fU2R*;8;GbaNeaa~*VZ z9dvUYbaNeaa~*VZ9dvUYbaNeaa~*VZ9dvUYbaNeaa~*VZ9dvUYbaNeaa~*VZ9dvUY zbaNeaa~*VZ9dvUYbaNeaa~*VZ9dvUYbaNeaa~*VZ9dvUYbaNeaa~*VZ9dy@Lr_g-B z92&>xztB)L%{V};l4c?#*PJ;7?0`P~lp-Jy`Q^peDxo$<5Pr7v!XSO!<^Cf@ERBQT zvdJg#UHB&c9*j2n8=L`N#{fX1QosQ5IUtPNz3EUle488>;Oqq5%-@kW^!upL_KxQJ zcf&alM0TqIF+u(+fqoMM>O#g`kk6dqA}@qOp-3neN}%)C3_uH(!QEEC{m!HS7zxmW zPQe8r6OT|M)B<2-mj7PT=)KTq9ntI8Z`yohE84kv_tjg`0T>UW6I)=pgYF<}9`tQ8 z;z|1eCWVjfJ9y|IK1C>pZzLHYd@GC&d>%$8z8gjtz7Iw>eh@|vegwuE`~-}(_^D&Z zs;ls`FjnIiV3hDnFgoxnFgo$CV07WvVRYj^!05s6!B~SofUy>TbnKW^h5KQw76lk3 zF&;*Tm;$3yOoP!SX2a+fr@`nE3t_AgOJS@PtBxIWREcgFtHpX4C9wrYhd2*Lr??nK zm$(c@x3~sIkGK)W8gVO(wc_q$$DCE-0T`>rqcBS1Nf;gCX&9a2Sr}d7Z7{mUUKl;% zmtm|C?}xEgeCXIQSC#l}7^}q#FiPSNK*}&k7?E#4<56^v0O%fq@W})Wk1QtyCM5vc zhe+cXiucI?oKF$R6Okqd9CN}yM2|;^5i*oi0pwQiS{#jJ3W>z7GF?X#2t90oZ6eo@ z=m-P6Ct6FMkc=W7iB=<^9U0^4DC=w@eNS+W2&hFy`D`ilJ)n@`6GCqUlqBN>Is(oZ z-h;lum`=wGI%d*wA|12nNbC>-gcTVl(Qz^zr_d4b#_&1V5EygmXs2Ty9rNi}K*vHl z7SXYojwN)QPRAK^ETv-^9n0xhK}VSfGA6c)enKRKQ_KOtwHJh!h1Z1Fg?EI%2_FIZ z#sQ|w4!Erv^{Hk5uOH~$To&rZ)BSw^Vs&5{0eNR{ExG<*DCtC7}pecH0~+--WK!!~D*0~`_Q}7e)X~?tl(Q+n7~3@V{MZla@5$p33v|%Q%~vlKJe!riuSUKNGX+vYyPYq3>~H1CcZ2fQ$Vc<;%X=k1KEFQy zME)cBe<;YNpT-xo6znW`zF@Ghq43M{Cq>ysO+|MVy;_`6e1Lv3x%h*U?2-c|FHbL? ze(m(PW;D&XuT&_lFTJ+(nKGfQzUmeXZ&B3~ z^c7dVR-GchtKM0Cp1pfV8b{y7OY5a`()*5V#}UT^^b_QeX@{I?@^>K&xRJgSooAgF zTp9AWT$|;it_!aB*->*n{atL{*nFX7f_$u{L6*>#&Fu9Ad%c#u zo@TE%ve$F$^)~i;p1uA-ew|apUe~bKx8&E>t?ad%y&hq&?0l^s$iKH`u-BdJ^*Q$X zEB5*xd;O5Tej>lNgO(9ZYahp6C$iUR>=mjV=y4}|l`-n5y@~z4l)bKFug|bocKx~R zJagH3=GMu_=eDrdjqH^@=UhgYxnE|VKMSw%crlC^V8z?um7wht!2&zMS85X4g@phN zUL~vtfbdQL*B*iW&Q3uM)UCpK=&*Vp^f-A0dQd$jJPS7QAvj07@C_JSg|EZdBs>6P zBkYfcp@QQ+7-tDzfw2zum&D+UeHliN@Ff`Ca4rm9+81GT!mbGz=G{ACl!Pz9SOwR@ z;Lm*?#!BIK7%PO&!B_@&fnkom1;!cBeHnuXcoxQD;U*Z1;4U%Dg=BU+MCbo}cqh+6 zWKJjZS_$|7)!-M-hJDld_kdgQEd)L;;f#xYPQHCJ>=@81+ym9q53=th!mCaA8eE6S zE4hyRxANyt!RLGF=d0=UC&Bv+_($$TfM+Iuu^awjxOywRCJC#BHNsk9ov;z8`l#@2 z;W5qMmBM$_e>eP&g8lsz_`8Xe7Q@fLuZ)1h51TLZkD4bMv? z)aZ(pl+FThnVen!9N}IY0eoD9r7yN-3wZ<2Bhy+IREqDms|wQc`4w**8^7j4!}{r zh~7f|fIqGTZ186AK6(LD`x4+?eSk%k04KT?Fr0Ub7r?7{&xUPNz=!Cx?X;bSJ52;{ zE(6EImA0YRU|dDVt^Okk^vHkGBV<&LB+?_lqDRQ697&=_{)-+VqjF>nJ@RXMgpA6O zWP0Q`^avT1BgAvxhJH(rkWo1@mLB;XJwit1$T)iBb$W!1%8~K($Q$$s8I>aw=#e++ z5i%-AQt6T3(<5Y5j-=5eZ_y)URF0(6BX83qWK@n2x!8vOK#!17Ig&|_{E;3ZqjH33 z_cjD;Zr~3Yl_Od7$e-vDGAc)i%x^>Q(j#P4j^xlIf2K#ss2rI@kGw~ZkWo1@nI8EI zJwit1$P{|yeR_n9%8{w`$Y1FZGAc)=(IbDON64rg$)!jBPLGgLIbx?rKA=a)s2s_o zNB)~0A)|66pC0*;9wDQ0gjj%W=pXb58I>c2^vM6vBV<&L6wxF9OOKFIIZ{lId_<3s zQ8`jVkNlG!A)|6+Iz4ic9wDQ0WClI*F+D;?ze573|1DNJM14*&XAvK7 zMHpw*o2k_2CU&d==4h+;8_vwDzAo|IWDlzz?m#Oo#IxN9>-Wu29`>~G15#0<;%6bu zqU3J%{tiJWPNTnG8sx97@;ul*5^fd4JTI?#0`D>$%0=tm2)7HFw-T}BqJpxB~v0JGBP7G zB}F7O^MlY#4MqOH^P4;Oeu6tkLptMkh*VI zywA3)D%8*ty<1v$uRvY?$~t~U>Ng_CQnUkz9DDISw%(L{>7J_1ks4x91|IBhi*FO8 zdvd+fow{VDWOJz(t?sX;p#E7O-ltAOedTp1c;=*b`0y-B@j%Zft6L5etr34E+_n$g+aUEzVFAgT)okA~FqkE2+8*>(sDUb+GPM4v`mq|Mq8SKFhF zHuj#BzZvsJ>&u3}T9z~2Q`CXV5{~>0)H%EVEK4z+S!z}bt^j8jpWOGfCGLX5%KIg}}`muBT zST{d*t{>~}$IkO(J^a|`{a8;w_60u%uh#qS7yTHlr*{px-HSn|y%;3n#rpWM3;bAL zKX#!X>*vQV@?)ib>`Q*EzaRUu9~<%n^lj=9v2 z4fbP~`LQ8>>~cRg)Q^45j}7x}o$Y!jE0!$Iu$&(R{5R z8|lZc^JAm@*!6yFv>*Gr9~&~6&+Y6I}f`Lbi5IKGxVCV9icAv(Z2r@zCQ=`=Rw%*L;5nLtX5e3CiV-dv~?XJ z^kCJNeLnvOf+IEaKQ-=%WG&}2CE40OjwuTG)2#rBYnL~q;;=y~S-x(#VAL|<`0bInh9-f`WY0(HWlLiA(T{w-;Nd1{6i*oaot_zAkbI zb1W+7^t``reAX%-l60+>7a`sV{U;+JTj>vj{YSm@7UVNqYJ^L=_|E&_&*#+Zl~#_Y`IKki{SlsL;{9RHXX5=~{&zh9|6}b= zn788R%hShn zjCaLK$3?n49rZ8#l2n1_^_U!*8qBAW#aNu(7P7C z6TJ!v?sR=oQe(G$ieD9}BX^G0XaUDB@fdZdr{TNZ_oJ;SL5q%i27MU`bUQqrL3#x3 zxc%_Gb@OCweLXr}BFMkd_ot$*F10ni`C{^stDz&kb$_$dzBYJ^ZOK$@8PgayD>U3H z(9Tq$g|qb7+$n9fZSh=J%w`eGJBat%=VV4fO;1N{`z5$D7wxK}u|PliQIxT*?`Yq> z9TWvEdu{m*vD5}@zpqX))zY$Cg5X*d(0`Q@*JP&dg|b@@HCA(0MLUl=v6nIAe6V@ON+%@}7a7Pt?` zP&o^n`g`l-^Mra+4$1dJeJ+RQ`>v{~NKop6Cl2J5?+uMakWLuX&Jn=b|016Xv8!|AT9liGRPO`vcq& zl)$(D19|4#~rglV3x;u{Un~evCK!i4ocixU?3zKt=%wr)`h+3ApZN{U&-& z`1TU+b-udHIF>oQJ#o$13>imeI7(e(52gatJ?-o)*;q>EX30uEGBXQrK@XIDzC#Vq zTPYdTa>l?18>=TG=V^?g&O_=7ko!te`@qN|oHsX^S7#nZPL)w%_DUhId>FcN7O!9i zoqrGJ&*3S|ob#_>-W;xA#+-kp3$J|4E3s{*!a4EY9k*rNZZmI`#64(bxex6uZ(^O1 zx7COEPp;mHdAz+$B{?qjIQQIe!~=bonm||Qb@JZu%!4a&Zxbc+pu%(7Q`KgfbI=8E za&k-j*1G1D+Ub=vMDmPo%#^kXais!rjrrNQ7kfGFG5OeOsXul`8e^n>BkO2{IUur$ zt_hH*B~^B2f8PFg7#|IoxBp$nWlnn@M{UVWZ;8v@c6N3ib_kRBGJL5s_7F^FPc2z5 zAlVs-2CZcU>JzmeAJN*>ZwE5a7@aF;P$oC=*CF^R=Da_I8SldL_!%ohRUy2hwC}D3 zic+6DYLKG~;g6(!AFeu41C7ua{R+OhvDqTS&!|6Rr0-uaqG&5d6#Y#-uU=5w)Za1o_a(Jmy{vX%lu=zqT^W4mD6Adu z8?58;Tfj|N*I={yJ>C^w};tW95qX1g)!ix&`lYEqQr5KXh)NvTaFt%4?ky zEwWF!wsbj{|GPW$v{)r+DZUH%m)K6V0w_E)iquAe3@2dZ(_tbyY`|1Of zd?;}fezE3umnio+P8aWpx$4zR@;HMcC_P)_TMw&s>K8tY_kctuHwq`c`E>16Gq)Qc z#?u=cml*D;xjfI)EAao|%VKjeGgb;x!FBnnJS!`=$x3bua5?~CH~zH_6~VTA$5HU_a`2vx zpuZHYWSvmH6`@oe43DoXxLk}igGQm{us3E&_Qh`kMw|9i6S3~ecs0qRur4d~fuK4b zVSc3gir`skBTS8M_pGVrSC=>lJ|hmP074#sSg#KdEA;_ljXprE&IgEf`2gWN0>Xa- zL=7AewOBx`yaxzL0YYYg`Gg$^p8P`IKLZs2kuK<7F!#@DtiSr)<{{#45;9bB!^6)a?S8@Jh;Maif z1O6xQ{lKpS{{;A7z(4iy&w$^+`9r{O0{gxt002cveP;}by@vT{AF4BP})3X)x(;`NQ?Jh z(u&RuP$rbfTZbb|MOelB)EI9TANLIWiXolO7Vfv>FXGo1{RtN! z7fBu}-H!uLHwyhEt@b;TdN~44h&7RR2;ReI@jqhRYu{B8!hAZHl;x4K=A=xv|LqKV zgtDHbOxBl@6{du;68oNrm#*OL)SUJ;k+e&?it+Rvc%Q61Qi3^C^O1I0d898_9_h!G zM_k$^9|pg0dc$#>YJs8$HjRGTt3zo9KSR8bJ8`?^fwP!Gp=UACqM$%yU@t|kVlR6k zR3eN=n2b=(G)ieE(&&X>d;D*_%J;Y>sR$0a#~ndj$|ZZ4YT_k;z4!0m_ylq-FDxTW zou124Luik&`8On_$9+{^9*u)jJfig633^uY`XhuN0?Km|B=?El4?~!Tu!VeSMD5C> zZWrNym!o~I0>5I^^hbkNKSQmi#OIWhUhXHW5!)YL#xFRU!e=6Pr}+{WEwEVR1sOxi zppG~seV*rd+5tPlb5+780HRtUlzLi$mKPja^dL6fQ*ljJxqX-EZp!krIa6H56c1*K zhfx2AQvZih|A$lmE2#gK)c+CWN|mL_=RzJ*CUm)9mU2gn1#-L*Yb-iZiU2lG3ux-~-9SCcI(*}!ksHdsv1yYov zexT&`dEoNJ6kLOUrFN4C&cEse&#DxD)!FDLm6Bip@Ey~v{y@~97aR7$?|hj%_s4M@lx zv9EGOb5?LB`6ZgG(r;N~(ysS-^avH6e(3HXaXP{#roAQdfLNG*sF#dm{&V*|Be zBi4kx9<3%bF&g1H%y63p@8oXGbK8Xx0rw#1{RlJM_F|>U<*0$JRQKsTw4^NcpS@6~M+axBPesl}Y77Th>w&+YZV!%03Ceq%W2gE2AkjGo8KR6Is!8LfQt zyz|D%SW3?_=6WS*ZI&`d-e^m(^ohJh8>Ot6gs>RlY05i?epMc|yZ~k74Aga2VpPX4 zUqZ=qE%BZBRD@?P2MAXzZ|d(?iXC%y@aOh(apgmmm+t47Zf6JO$v*TM`qF3UN1vgT zK0|-{3cIW$vjk0CcJgj?C1<6U>XCdXd zh;m#^IWD0bWhLVk&}>1=)9|IHO#W7L(o?{X&U4ALT+Oxg_hLUzpJ^^ivn1?vu$hdcmu2(|L)a6Z$u!7b09Rx*4YC$wMMl41 zSKG}bmt-b-RYucrEoyq#W%LW9P=_3y(Jy=*WBPB%=oiMSZ=imhNlD*`Htw4;`h^MV zX7ng#G7bNS+U6}8{la8*D@M3wG7aBU)#|p4eqpNmmYSB)FMJ!Lw!V|mFW8ljGAV~U zxUNzrzaVQi$$I^n^EK{Nb1~X6lV6barsik#3$~{=lWDMPWo7aUKjcbWnf!vR^0hRh za#+T>SD8%13eM|F^aWEbwO;h^;mEB9P>l%l2pbbNAv}t(DPc3h=7dKRwjex)uqENK zgsljVBWz80JmCq1Clayej7=gm?b|vgah*o`2(u1%kVKE_E0dc*Aun%Eh z!hVFMg#8Hz5S9@RBpgIoPB@rw2;oq|VT8j8D+ntIM-WyKjwD2n6lfStIEHX6;W)za zgcArS5~9T#_fICALRd{Wm2ev2bix^gGYMxA&L*5gIG1o9;e5gcgbN855iTag=kT|b za5>=$!j*)p2p=F^P52<;8p5@N>j)nqTu=BI;p2oG2saWwNw|q{GvQN&TL_;fe1>o< z;j@I#5k612jqpXn?SwlBcM|R*e2wsR!Z!%tBHT^5hwxp(_Xyu7{E%>OZ;VQK0%Mzi z7XiNv{6pYZfL8I2bSkP1pXzk(6a^jBVc)MAMme%_XBUORvg)}3E1HLcfh&8&jU9C z-U8eh_>aIx0dEIx3M|hx2mT9i3*cSA#{kQ7#{$0qd>rr_z^#Ghxf6h225tkq2lyo5 zUBD*;zY2T`@cY2+f#tb;;5UIg0Dla8I< z+y%g5Z!ZMy2;2`?o+||w8`~eaGw_#z<+-l_p9cI@;I6;}f#tbDz+y|wfqMX73M|iE z27ET~<-omwhXTuU!+_5L9uC|W_)1`T?keEV178i?A9w_?JXZzW8+at}K;Y|u`vPAN zd?E1Hfd>PR0hZ^+0)H8J9PlvU8-eAyn}9C{z8Sa@cp|VoHwpMM;K{%vfo}zt=e`Mi z1@LXaV}Pdu%X8C!uLhnDJRbNv!1CPfz}Era0Xzv<_$bc_A8!C209+0HC180@_<9qt z@O3({T$ktM`Yph6eHO61N1l`S+y*S~nF}oU%X4!7cYx*o1>U(lC+FV<7I`fO?h7o> z^#i^OSl%m#Mlc9D2)PK25b_WjBQ!xc3ZW@NGlb>{MR~DndTOX$Tz2*VJrKp2j2 zB|-(lRS1;`S0ju-xCWsL;aY@|2-hKuLbx7bG{V;r#vt5)Fc#q(2;&fLL>P~76T$?9 zn-L}={2#(3gj*0MBixF4Ezg=f%x(FrX^i#KpEFJL-}K+iQTloPylJXm&@Y%~dYj&6 zn(M#ozni16+w+U2g?>rDWRB6>^>))zzpP(2$Lbw=hiRo>(XW`}^iG+@rT?M-VUE|k z^e%IPepSC}PSmgI*GwDzPyJ7Gl73ykZrbX9>3^A%u}1qFrk#FMziCd3v3YMrq-a_JU}6YlLmM z1TEJ6kq7di^LE^}^gWhx9E8*wXK|h1J247E#zCy$IEXbE2eAwzZ!iu*ZD6nCllyVt zjfwloQ@tAQfvzncjA4*GY}05Mi=G{fzUpP*itG2?08-|8^a$Vc5QOKe5S~W(ggifn zJTC;t3!vv6@f(A@-w8U7?WdvHU6Au9;GQCA^EvoUM(#crtwKHVn~t1aj8P3G_{~Hv zz5uck3g%EEb0L`xYMv*TWJaAxuXsEUg?8a*n149sd+|SFQ*AK_vFF}(oJ$PA_KzRU z)XHq!0$2e_Z6#AXf~g(J)Q)0m$1t^Hnc8to?IfmlGG1AN^h;{1na*i8g-OoJV^ZSs z!CuG;)>Mz8Oj?6B8D}U|&LowyNabu&IhRzaIsIcYU> z(pruvdW<<~6LZp5=A>=RNjsRHT{b=BSZz{EC11%HQ2Wb;JPu7h$pZQ7XT7wGUOPLu z1?&Jc1He;(yOpYmT!`7fdTms0-AIdWqq zM{ca*$c+a$a$_}e>Qam$laU*1DfxAElDr+;`ze+v3YiaF^;WzJO#-H<(-W8OJtE_WgWK65|>{v4$Gdqk$DNn zFxOzcb37}KoMGuN!I!hknxkkEc}rdn~>3q~S5gc0^kw7`u>jMWg_MET-w;+jfxrw?F7_UVC^ zS_1_SoQEJbU=hLwggw*)H=jiO+alCFD&Sekh`A~7WFJ-4=3%V!;CU0cKOMiI#C0(c z6OoaVP*^^=B`JBt%E8SelJvD_%0}3Ho3^zDg!oxr3WRc3elAD+Mf|Td`Rrze2_4;0 z^Sc6Gt8jY?X_`)M&mgyFdGw`&*OC*Q?F{M!AS}Y~@L8DW>CO`eM;1H0)P-dc^<|qw z({td?yz4{vwNQMdXe(lPI>K2tGt}FT;50p ze<~zwDnjy=7Ae>?jn)^9I;4Jsd94-lRW4enM7~p4GCfHtZ(+&wEK8>6DDl@R@!gd8 z`yz2D4dyiH=A6^eS|qO9a85%z$bF6OD00_@Qika+oY_z$I-q-`RR^5Ro4}UH&z*;{ z)00$i`1#zJH+!=aV(bFg8*?SrK3JbB*0YkC?$&yUZzUEnIqU68)0(`igJ40p{;k-K zu>FYEg__ZRw4#2MQ?nMbKC_g*?{fOSE9m>K^Zp5enNaBwJ_iJ3!o%5gwzxKbH1B zkNI&T?fv7-m78eqpQgQkhBC$irN>Mv@qa(2rB}4{ zSoaK;zG+$|@A7K0mfqbj*lCEnR+FaKoDt;vSZz7tEI4$gBBV1-5f>k!5Me1oDn5c( zh}WqzZ%}96qR#B5&g`Miyi1*Vk2>=Sy#qs?$)nCR;TlOTsX47OqB%#w-pS8tK^Qx` zA~<|KH|B{vo`X#>C4^jwzZR=M&ep_Q9Jw!Sb>v)rvW}hm3FSEIWR1`&Z#i1RHvmYD zDGw!_T{$9yHNH=QemUK7`sL2U*!dQM!`H2T1y>r?Q@^aX)UIDvvyK4$(mSBdQo>7c z{>-~L4fNff$-BzEh+v!tudwr_T*QX)7bn^0$2UF1#V+F>dmqJ$S=;hJ;fch!Y|1&_l+N+S$*P}^jaqYIkr;P}9-66L&NCm!7 zoC1fO{#s7SS-=m8htGrmNK6jP8)pN9GqHV+SGH7it4(l>Le%aB{t>U|gwOdE`I_{c z$VzH?tT)w#hr2v_!#u=T4h_B2y&_)_kF1Dm%}js?Jp*HsWaWv~tYbgoNh!*Ux}LfI zpr9uxh(-H{!e{m#2g7n8yL_oc$9=?lC%88%hiczX<#lL(tUTlj9<%arD^&=$>`E0g zSOb~K8ptfxKxVTBGKV#gx#XWWrX?dQ6)T;pjH@titwF~7kz0M)XS<9|J~UABa3~*R zB_E~xAlP*{+_)^!%+atHLr^+M$r0`Gl{)iQYV5Pr*ypIR&r@T!QDa|Z>9U=E>FX?A z-eT#pJ0mtATvmnSVcQbD9-M(4p3yCSXd5lii?l%7X@Pdo0_~*c?V<(R zO$+ocEzo;uwLpPpIDaI#Vzni(LE--#Sc@e8Sr0m1gN|qmgk+CE+FcptY*1%h3i<+~ z{X9}HSV9}L5?WG)amG@|d4PJdnwDq-Ez$d;4|uUyqNcP&Eog~aW=J0r+oT3dbjUqj z=+cMrIwti&&IuTlpq!KSRu^(a&mwBqV%nc2v_DH}f0on!tf2i_N&B-3n)Y+`Kw7m+ zY)oU?_`s?lDKY*_^gP~zd}_;}lmM#g|9@i!hzZL$eLXVtK?e#qA(=axk`c5U^E=W5Do z)ukNSS;mo_gE^MBf@66{b1d(8(PrvEoXhbiv3YzSzjLq+3i*18)S#vOuflWu0x$0 zbB6E@8(f`R<`K<6*c@q_5!sES>?W}$G?}I06ppl>!~8g(`EfDx<4Weowakx?F+Xmg zl%J%OpQej-hF7F;T4<7;LUAei`3^tdUA?9*3)O^hhGgp}5=1Nmxt}>P8YBR!IW2(%xW~8~! zj561o(dO&euk{8q)_lW^GdG&?<|Z@2V8wRg1;^^C0Bc8l(gVy zW}gAOBE4NT-mdI!S6{a)q}x@@?MmZzHE_FPx2&q|j`Zvw5M3qEuJv!%<4=`BN0;CR z+ES5hz`1PWTmOKxtdps%-sFyK;~VUYe`FhNfd$FFd@6R@9ofb=SslzB*+v`29Q#MM z(I%^@xg*s|dOy+h~*34c(D#v<-2D z0cRU+vKpj2vW>Q(jxgYCqfJ)NbVs()CjE!*$Tr$!wNZCu8*Q=*synicwksWBz}ZGy zMHbU`RTk4$nZ>kSoyD}t_)B+WTVA-v5eA%Xv{hv>ZP#WoZ6mXoHd&3^9oeRDqa0zt z*+!eJUha-;qiwV!3^?0pv+KQE0<({{F+S(Ak2YB!-yYdV+gL|h7V4YqCt#24MW-1wk)P?Y8KP>tt_T(S{Bpx?JTBk zdKT06oh+seqx`)i`^x&;9cfuup3cl-+V03=+AzA-JF-vTzUxTKLVcT^#k74di)owV z(PpY8l0%qF*oZKXurXm1!lMYA5;h}jPIxq73&LXvTM`~i*oyEt!q$Yx6P`eLB4HcC zlL*@qo=n(|@D#%Kgr^eb6P`xcf$(&~0>aM_b|gH5uoK~#goT7>5q2g#o3IPvX90>Zw87ZUa(yoj)r z@JobhiIM}*5atr*5jG)gO4yvR1z}6VR)nnyPateV*p{#zVSB=S!VZK5gdGVx5f&15 zChS63MA((E8)0|C9)vv!iwS!XmJs$K>`T~>u#~Vr;Q+!i!hwW?2+Iiv6AmF9N;r&g zIAH~0CE*CdD#DS3qXbnb~xWR75FXSX~1s-PY1?M=lf;={~LHF@E+h< zz}WG8-)!J_f#(2Y=ktAYf!_nZ3m7|~@4FlLec*e5KLGwd@Q1)Z0RA8F4}tdr-wXT^ z@Q;8$2F6b8`>-?mz8?dB0(>7Zc1Yico!CE7z&`=jz&`~xz}SiXlN{iOfOCO=4vZbs z_dN`p2mA|Q?3}*uQQ#)PzXZk(>id2L+!Xi;;AX(T25t`g8{ngXe+%3K_; z8Y478I0~UDLNkQs2uC9z?;rU)RHtj6y-C|lDm@2$7kNn#`M~00F9oIZ5e9PpR8#Jw z)0`_wmMK|9V+iIAS7A>sne$!2*}&sC8+bBj15e=^ZPn%y%(t?8r9HDB2i_ZA{Y2*d zCZ5ro=xY7u_6ASOSLJXT%9GHU%;HV=%Dp+<=^&3Q>K0)2kt)@Zv(O4HSGf1YU$|=x zs4Ic}$}`W^x|)BF^Jp%6Z(YIFEZR=W%b~ zJnqe$$GwG;dxnzRM#;TM$!(|PUgK^UT|CK!*DH}70}{z7`I+3oor&Hf>ocb3CzQ$& zm3H(zUgB=u`e1gqTdz-c%D_RM;GM*RmZ) z&S2Vwp|lGXvf*XG4R-6ZCeM?}&aAOpVVjrj zHViEYzQ5t}%vM}NT}}6-eB3S+Qmz!ST56AN-$#V_ zE*_@T!Qk&>{dEQ2%9?Yo4%SQc!fF>K_zh%vK8WS{5OpVhQmWsjDp9I0Kz#uF<6@O3 z*%5gXOZ6$9RD`DoF0teP3o;@;wDYIKzB6v&7i#j0K{D%r&JT8L|H&$FeVB?;reYvd zF^H)c!cgb3?P>s5$tpu#_j+*s2K)x$ ziAPj9cJ*na2K#)C=bN;Bq@Qzck8)X)raLL@MG8wuVINZ1mlXE%XxpfMqn=d11>A&s z-e&cC5Y@?}O!(pQdKKb}5o|rv?WuDGzEjNa^y7C*`JMj!&H#R=jGP$gea-rcQoDCC zS)pGJ`4;L+-VvOI^+@938W?WodvP~0p10o-eS${igwOeA(Qx^)?3W|07WLp#N_-N% zDaH!ZmjhSeS4wX6C$|QWTV>>y< zVmneExYG-~5xMjyp9Yan<>b>~@@WY9G!!}dDctu8-Z9R~+>>meSC)J6Ij`m`AnCB= zZ}Rp_N{8g^S6kh-!G1y4%CI+ETjlbl)2?u?{504v(VGVQRlEHXJ~h~{l)Md?+yS25 z75+N^YbxR^5bR!uu6{53Ru!lb_>JQJ3!{D8pN{%~v@XjLE@NFh@I0i)D#q`qj`|%Y z@H-@i+pXVGC4^HahYzOI2xw!gxIDg5Gdh;ol1(eG|%4HGdvY2vNLb)tuU*GbyO2_TwY<(B0 z;mhId8c$-*<8x0q|JeMLiPem=(LPFDWFG4xiWXZg2_<=vY3khJVdzZ(2b$zd)>p=M&` zQUYNmRWDDU{8F`#VZT#k+2D6(g?+4--zjoz@H=gN208FBtAnP`7ChsnUCIovy;Gw; zqemjlVH-j&dngt3+k0>Z?yj>*wM6j#y1{wOpyWxbfP! zhdjgoh@CBQz8A5)2;7&xSJ&Pw{4XH?3(5b^mh;hZ*(r?gllPUewpq^F=1|r)Wn9KR zYBXD0C!)5wOjWbBbvnN}Q_CJVvpkvGR-B+sp!QhMY9M{B>G=t#9G@i~J`3|*;_2wI z&n|gfh!)redB{lh?8qP2VHQM#Jd&4(jBS$KE5VJ&coOlCaj7%ypKHUeo4vY4k2;9* z0O46|i;^E}EFOP3TPTMyUkzsqZDIX?kGik)FzSDEjz?<4R^y?RNhM`6f-)IJnT)1P zrcfr;l*vrWWEN#In>_(@w3HPK8YGi+S#dCBBjrYeY?3cG5{&^zT}Gq-MfXT^zL7HI z;K?S7trd7%k-%Qb`0sb|zvx&1DX&X83ZRU=%>&unJczx`<*-*v)nN8E52d9VhH~Q* zHN1wJg_H`PiW-BS5#ld&nPcoXg14;@qJ8?-ACgwpa`JaD`8$OC9ZLQVBY!K%-%9d# z1pD+K@VG4V3&TC~5^tQQT3NDv`>s}OYiHs5nZ4rrg3~#tHHn%+2K8J@;RZd=oSqAR z8}vNMk~D*QE*?#To@Y+ag})7Yo~)i1hsssg&LDHwo<{hD{-PVhn5y3*-eLXq9Aso` z%Yh!@(I$0N=aa~s89SFhJnt`6pG0_Pb$CX-eUjwH5@xHvu_KzYO#d+*%Ebs zY0HqdnQ?3fn8bE~$!rIh%65Qh^tTg@?i7F9j_$N01(&mxU^RWi2iZ!nhAlU1Bg*=BkM|G?ySy7n1!%~@(tF@x!hYX zr<(GWRdYsS_NlC5G?VgOM1OxV{rx5M_m|S&Urv92C0l`3QNC*_-*uGlBb4uY_7^pc zH%M*h(34m(%vIpzU8t+rNsoe>H9YgS7qY zY5O0e?SGsy-ar{|W(m+TPSOpzUgQo-V>wfU9;BfJ$f7(ib;yQ1uWFDrud~oexs=bM zJTEeC$n)0wJ7iv8#OL$s(2z^w^)e6S|I0cw2U<0A8Af$tb`aV%Fgpk<&8)%iMV18H zSrY7EEx@fi75Hq{vkT6HSm6KH9t(OCg6A7*gQzKlN9h?^H!0TV!~7o^3xHKDr9P3I zKOnlWlw0QCgsalWjU7rAC8JNO^IHdLUsVFb>GK6M2Y>j#MFFEqvr; z9{^7v<$bboc{UlTNc%F|>SCl~Q!E-PN4#W$yz5&198TEwSBtb<8(`X` zhFRNz%$%-Q;F`>Pe1GR`YR||wP^2Ut|7T(x-3-C&-8=(A-zWbT*C%O#`7LnZ-qAvdRtT3n2NPcN;0siA0xOj z(%IV)xF)v7_Q5%O%MoIFj{c@9q@^hH?X7qe$>jXnZEm!$()tuqV&UM|YVxo`-b18C z+VdXRZtT1Vf4>eL9xvaCb+Dr5-z7FW@JfDk}tF_ek zYO3dnSK#G1^IbFBe9z1=cbd88E;G;EZRVSM%mVX$v(Ws&EHXbd7$;Xp-aKp`KCF3j zuUTS#WR{v`X1V#XSz+!oE6x38mHCNz!2Hy#Ha{~Df;VkEX^KQeM$*P5?OUr6wzHn+ z+IyD5BPl{1Zn>(`qtMd&wl!mkbG z*M{+H!&zCYf8zWOJ*T zV!mmr&246?`IebxzHO$P@0c0pcBqWhXPoXhUq5l%iR{f6?3Fmu+beMx`-{e~zi1+R ziYBqAXmUf_33Fb8cG~15lag#iZyVYN($GGTv~0wF#O9kNxV8tEiy}5RRoP6@la+0A zbTi|lqbF-Bk7BRe@+pxIWGOeGR*rBG~7F=$3NO=`#QmjS=JQGe??l~-HBJ(7<=iS%HXqQ3 z@bSF}A4g++eUTc2UpF-tzwX>qw1>GJJBGOxU0i-lrmoU(Pu(~mN+O!?Y?+IT2Dw%} z{F{1!{}JO}rxyX17kRnNwd;zE3n=4GlyRZG8!MK4gx2AG%v<*B@>Ne?ur{phy(Rli zOOAE(#H59^ls!&mY^@s19;XWSppIsb(|GncO+^3gZE7-l3RbHr-uIjyrGkZfhQ;`63D%9{|c6=F#7dv*z zI~<=cR2PEJQe*4L8rv{0-EPEz!?Mdoz=Lo+{2zPWLJ|UCJpHiTuzFEf?QlDYiA0Mj z(LR)De@e8R60M*_M^d7rJ-+-xKcXMi>-8`7WBOP6as7nepnt75>fh)m^>6hi{X4x` z|6V_(|Dd<%KkBFTpY$^%e+Zz^EkQ~Ysq>1@{uVAp*E|x6%1BW;DH=wKsz^~}*F4B* zotnz>aT;4yrh6O@xSM=TB$>EWwcG}&IKnz7arwoT2$}RTB*o6hct{8`QulLwb+-O# zTzVJ(YfJrfea13gCb4gwa8E*(1XIn;C<)w7!%Tv8M0xNpvj(G?-bBuQ8?#~mjhezcSPA7n7`gRd z^}hN*eTZF0mFX1HORp_Qmmo&&4Ym`^*f7@^RMOH?rowg*Er5S#y7e8vJ}xHX+;^lw3Tq_GdzGV>KbH7h!F%)~Nn{ss}0= z(yS%WlLT#Ax*L)>8e!4F(G%{eet7E1k*yn9U_-cf^5Ll)VOw-$>q$1)5ZQD7@YItd zS~v7GBrnE4=HA`gaal$+9k_100ON+m-xx)IV;udB31*g=s3w_*%tOlU6qeW87fd+5eIXLH6*91Gpl-yxw0;(H@A}TPyhUaWyv=Ez4`)5I+PGDh|JUC4 z=X~yTd0B3+HA!)Mrr5PS1mo9wgml;Sht|qBdc6&Nb9ugjZ;~JUmM&;pn)MAU=ij4> zm|MF>@~O0DrT-Ue3$1mx?WUQi4P`8!TD~#2Ht;PN?@_CLYv5bT<>?jb3e}lqOqUpa zvva@FpG)Ig?Xw}Dj!00RZnlJZeccKC5VU6PR7K=lMT~trkbf8dzfT31^^62OwtFTk zJt*RD%Q&1;_}3L`5L*4J(Ecc^eGO%6-e|VwO<-%@M7HKlq6DV;5(xM!USV)nTkKOI z1?Y6f7dTfWvM{AP6E{(#qr3iLCs)+4puVQD6gP5qgaPIG*kmX6n zhKN;?!`0P!5<1H`A4qk4oif}WB0Q0P*v%lb4Z=X$=VllYl7rPa#6H{oQ0z`96|~Ql zw9n&dpQq41SJOV*e%P7h*erhgL62i~8}*gkjI(6tNJne$Zrni7ckB9a(=jtDQe#LY z1=+8`Zb{;oIa0qndNvMg~8nGp9I95f*2zF;3 zz6VDK_N1LE)*t8(R4?jUiI?kSq@auwl=U)%&sO7XJ(;I*SK=*_TnYLUIay{^%5W!ZO(Lr0zYX2fruufc%1<#OsjMm1ZNX zBfs2waA&Ktu^yaotuN=#^yjM41Gs8*8CQ)Sh}!;Av^K85uiTT28`B!r4ZQkbY6bkN zAdOBZ=Hl8@2zwKxu>!kc^hKNLNUzOwFze(aStlRII{A3k$tT35(YE%d(ps8b+ORgn zCudK3;@WV8>Ex5EGssx{_SA*^7#`r(gDTrZHc;ANRwCH-&YaIDnp*@baX)H!vNB*X zed}H($KtpZS{0E`GfbX-COsvo~n8sUudiN({6Pc2>_k$pJT5|>|W zEiGI}!{hu~O(j82wf%o=y$mIUJP9Cjk~hk%lRWBY5*$ltALik0n9qe7Jo3h)*=E-s z{4YR%N=N)E)tRajR*5?czmcj7exqQqW9_l>otJ4#|3DCDQNuD>jac{@_dpcaqA=@CytXYm{k+7oc^fw}4Zzvl9FxGP`vpRuPvbnQ@QM=Nk$ z%Gb^0lWXIZR2J$nSW%&io~XeG>XloS!=|g|Je5eZ0uCJ1G@F4)o8(~meshF&2qSTd zGDV{=UGK%e3z>E|u3KhZ7PD;Z#j>%4Wn&+fjeS`*_G2C16P+E|*ZW_Z6n zG(9ByUw2J#GS}s8?g-y6UvNkGdNZcY{wI9>Xw|~q9{KJus%66K$D&=^9;R9_ax9MA zY5>)UFpsb?p<1Hk00f5tq3eJsc>qB_AgBcd-vL1hAifKTuK>dK1D?=(|GoiF?B72D z_$$B@fd>NL1bi{@&A{crHv(VkT^|BG7U!1(j{_bCd;{wfv`Tz zm5qR8w98XX}qm7yK`}Zi_2pC*dE95jIhe zoX>tYR<|uh-ns{~itfX25axrt0gvQE{D$eBdZ!w0TAS9Y!t^q|RHeaBjo6O^Zwi-V zLEPmy+(3O6y_O?LS>3&ISIP?2MWnhrsqRUti%E4~OP&5i@5fX-tv@lw#w7+aM5lxn z363{nhaW-MLEbpaE_1C4Xx}@s-@3Dx?nK_EOLcuQi10ZTj)(0Lu9Fg^fEcN=l$C_A zJdD&xxst#)N2!}@M7NYK5a0#JkI6V+f?(~f<42{s3jC0{k)7cAEcf>g6xrK!J1iUu zI1!GA|6{LPh>xW`-Og4D-Nmd;l+a@Kp~dV=i`kDBvy>Lol^l;^Z1FEqzx|bZTs@(F z4Z=!2>V!|OOcMW3>cDT2$F5A0Zxxb9UC1N5N1*fkf-*_UT>A@RA}e=QaBya07E)>v zUWGXq))R?`+x|i90rkdtJWBKkH#_}FA0PAaPN!>*k7IESWt`f`6g#^l^}tg2KUb;# zlw57=1U1%;)li!Y@E*}fTYI)J>fcz4By>p3wh*{>0^WH*E@g~6pk)_=FtQz9+ zCz>Bc)8ugc2*<S{( nou*D}-%OpZIv{=}YTeCP7ip$$!bqiN>Q?+3$*l1cJ?r`Z@9NuK literal 0 HcmV?d00001 diff --git a/app/views/layouts/_fonts.html.erb b/app/views/layouts/_fonts.html.erb index 56ac5f7e88..3d37586782 100644 --- a/app/views/layouts/_fonts.html.erb +++ b/app/views/layouts/_fonts.html.erb @@ -13,4 +13,18 @@ src: url("<%= asset_data_base64('Pyidaungsu-Book-1.8.3_Bold.ttf') %>") format('truetype'); } + @font-face { + font-family: 'Khmer OS Battambang'; + font-style: normal; + font-weight: 300; + src: url("<%= asset_data_base64('KhmerOSBattambang-Regular.ttf') %>") format('truetype'); + } + + @font-face { + font-family: 'Khmer OS Battambang'; + font-style: normal; + font-weight: bold; + src: url("<%= asset_data_base64('KhmerOSBattambang-Bold.ttf') %>") format('truetype'); + } + From 0ebc124f3044d19175d16cdb59ca55cada7c267b Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 8 Feb 2022 14:23:29 +0700 Subject: [PATCH 0088/1114] replace font family for km lang --- app/assets/stylesheets/common_version.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common_version.scss b/app/assets/stylesheets/common_version.scss index cf887722cd..0ebf722666 100644 --- a/app/assets/stylesheets/common_version.scss +++ b/app/assets/stylesheets/common_version.scss @@ -33,7 +33,8 @@ html[lang="km"] { text, tspan, span { - font-family: "DejaVu Sans", "Khmer OS Battambang", sans-serif; + font-family: "open sans", "Khmer OS Battambang", "Helvetica Neue", Helvetica, + Arial, sans-serif; letter-spacing: 0.35px; line-height: 23px; } From 6538fc4f53972c0848bb591d268315c104caf1a0 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 8 Feb 2022 15:48:14 +0700 Subject: [PATCH 0089/1114] removed font battambong fonts --- app/assets/fonts/KhmerOSBattambang-Bold.ttf | Bin 296840 -> 0 bytes app/assets/fonts/KhmerOSBattambang-Regular.ttf | Bin 308232 -> 0 bytes app/views/layouts/_fonts.html.erb | 14 -------------- 3 files changed, 14 deletions(-) delete mode 100755 app/assets/fonts/KhmerOSBattambang-Bold.ttf delete mode 100755 app/assets/fonts/KhmerOSBattambang-Regular.ttf diff --git a/app/assets/fonts/KhmerOSBattambang-Bold.ttf b/app/assets/fonts/KhmerOSBattambang-Bold.ttf deleted file mode 100755 index ac1221205eb6adc95e8a4b34965eabc0538db405..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 296840 zcmeFa349bq_6J_ob0rW$?t_qXa*~ToP9PVMgphCq

    Zt2Lucdxdozt$R#Ksju*1X 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

    8YlvfDsg0%M{f~O6J!j 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

  • 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 diff --git a/app/views/layouts/_fonts.html.erb b/app/views/layouts/_fonts.html.erb index 3d37586782..56ac5f7e88 100644 --- a/app/views/layouts/_fonts.html.erb +++ b/app/views/layouts/_fonts.html.erb @@ -13,18 +13,4 @@ src: url("<%= asset_data_base64('Pyidaungsu-Book-1.8.3_Bold.ttf') %>") format('truetype'); } - @font-face { - font-family: 'Khmer OS Battambang'; - font-style: normal; - font-weight: 300; - src: url("<%= asset_data_base64('KhmerOSBattambang-Regular.ttf') %>") format('truetype'); - } - - @font-face { - font-family: 'Khmer OS Battambang'; - font-style: normal; - font-weight: bold; - src: url("<%= asset_data_base64('KhmerOSBattambang-Bold.ttf') %>") format('truetype'); - } - From 71d96ac5b72df8922d15e9f938d7db2e261e9df4 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 10 Feb 2022 18:37:47 +0700 Subject: [PATCH 0090/1114] 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 6a5f2b5b9eec63be481e2c97995d72a1f3015ed9 Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Thu, 17 Feb 2022 14:56:02 +0700 Subject: [PATCH 0091/1114] [TASK] Add deployment staging production --- config/ansible/inventory.yml | 4 +- config/ansible/roles/deploy/tasks/latest.yml | 37 ++++++++++++++++ config/ansible/roles/deploy/tasks/main.yml | 42 ++++--------------- .../ansible/roles/deploy/tasks/production.yml | 21 ++++++++++ config/ansible/roles/deploy/tasks/staging.yml | 21 ++++++++++ 5 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 config/ansible/roles/deploy/tasks/latest.yml create mode 100644 config/ansible/roles/deploy/tasks/production.yml create mode 100644 config/ansible/roles/deploy/tasks/staging.yml diff --git a/config/ansible/inventory.yml b/config/ansible/inventory.yml index a90c462de6..40a7b7aad4 100644 --- a/config/ansible/inventory.yml +++ b/config/ansible/inventory.yml @@ -12,9 +12,7 @@ all: ansible_connection: local live: - ansible_host: - ansible_user: - ansible_ssh_private_key_file: ~/.ssh/id_rsa + ansible_connection: local children: oscarservers: diff --git a/config/ansible/roles/deploy/tasks/latest.yml b/config/ansible/roles/deploy/tasks/latest.yml new file mode 100644 index 0000000000..657f48ac73 --- /dev/null +++ b/config/ansible/roles/deploy/tasks/latest.yml @@ -0,0 +1,37 @@ +--- + +- name: Internal | Create k8s deploy directory + file: + path: '{{ workspace }}/k8s-deploy' + state: directory + mode: 0755 + +- name: Copy deployment files + template: + src: '{{ item }}' + dest: '{{ workspace }}/k8s-deploy/{{ item | basename | regex_replace("\.j2$", "") }}' + with_fileglob: + '../templates/*.j2' + +- name: K8s apply namespace and databases + shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }}' + loop: + - namespace.yml + - pvc.yml + - redis.yml + - postgres.yml + - mongo.yml + +- name: K8s apply po and ing + shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }} --wait=true' + loop: + - webapp.yml + - webpack.yml + - sidekiq.yml + - ingress.yml + +- name: Wait until container ready + shell: | + while [[ $(KUBECONFIG={{ kubeconfig }} kubectl -n {{ k8s_namespace }} get po -l app=app-{{ stage }}-{{ project_id }}-webapp -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]] ; do echo "waiting for pod" && sleep 1 ; done + args: + executable: /bin/bash diff --git a/config/ansible/roles/deploy/tasks/main.yml b/config/ansible/roles/deploy/tasks/main.yml index 657f48ac73..e8c6f79828 100644 --- a/config/ansible/roles/deploy/tasks/main.yml +++ b/config/ansible/roles/deploy/tasks/main.yml @@ -1,37 +1,13 @@ --- -- name: Internal | Create k8s deploy directory - file: - path: '{{ workspace }}/k8s-deploy' - state: directory - mode: 0755 +- name: Deploy webessentials servers + import_tasks: latest.yml + when: stage == 'latest' -- name: Copy deployment files - template: - src: '{{ item }}' - dest: '{{ workspace }}/k8s-deploy/{{ item | basename | regex_replace("\.j2$", "") }}' - with_fileglob: - '../templates/*.j2' +- name: Deploy staging servers + import_task: staging.yml + when: stage == 'demo' -- name: K8s apply namespace and databases - shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }}' - loop: - - namespace.yml - - pvc.yml - - redis.yml - - postgres.yml - - mongo.yml - -- name: K8s apply po and ing - shell: 'KUBECONFIG={{ kubeconfig }} kubectl apply -f {{ workspace }}/k8s-deploy/{{ item }} --wait=true' - loop: - - webapp.yml - - webpack.yml - - sidekiq.yml - - ingress.yml - -- name: Wait until container ready - shell: | - while [[ $(KUBECONFIG={{ kubeconfig }} kubectl -n {{ k8s_namespace }} get po -l app=app-{{ stage }}-{{ project_id }}-webapp -o 'jsonpath={..status.conditions[?(@.type=="Ready")].status}') != "True" ]] ; do echo "waiting for pod" && sleep 1 ; done - args: - executable: /bin/bash +- name: Deploy production servers + import_task: production.yml + when: stage == 'live' diff --git a/config/ansible/roles/deploy/tasks/production.yml b/config/ansible/roles/deploy/tasks/production.yml new file mode 100644 index 0000000000..f28296e802 --- /dev/null +++ b/config/ansible/roles/deploy/tasks/production.yml @@ -0,0 +1,21 @@ +--- + +- name: Make sure bundle 1.17.3 + shell: gem install bundler -v 1.17.3 + args: + chdir: '{{ workspace }}' + +- name: Run bundle install + shell: bundle install --verbose --jobs 20 --retry 5 + args: + chdir: '{{ workspace }}' + +- name: Run yarn install + shell: yarn install + args: + chdir: '{{ workspace }}' + +- name: Deploy to production server + shell: cap production deploy + args: + chdir: '{{ workspace }}' diff --git a/config/ansible/roles/deploy/tasks/staging.yml b/config/ansible/roles/deploy/tasks/staging.yml new file mode 100644 index 0000000000..caa0c66f31 --- /dev/null +++ b/config/ansible/roles/deploy/tasks/staging.yml @@ -0,0 +1,21 @@ +--- + +- name: Make sure bundle 1.17.3 + shell: gem install bundler -v 1.17.3 + args: + chdir: '{{ workspace }}' + +- name: Run bundle install + shell: bundle install --verbose --jobs 20 --retry 5 + args: + chdir: '{{ workspace }}' + +- name: Run yarn install + shell: yarn install + args: + chdir: '{{ workspace }}' + +- name: Deploy to staging server + shell: cap staging deploy + args: + chdir: '{{ workspace }}' From 7514263e655791fce8a64c2922013807270c06cd Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Feb 2022 19:00:08 +0700 Subject: [PATCH 0092/1114] fixed repeated query of FieldSetting. Fixed Assessment N+1 query in client_grid.rb --- .../stylesheets/clients/client_grid.scss | 8 +++++- .../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/domain_score_fields.rb | 1 + .../families/family_fields.rb | 1 + .../partners/partner_fields.rb | 1 + .../quantitative_case_fields.rb | 1 + .../advanced_searches/school_grade_fields.rb | 1 + app/classes/client_columns_visibility.rb | 2 ++ app/controllers/application_controller.rb | 9 +++++- app/grids/client_grid.rb | 2 +- app/helpers/advanced_search_helper.rb | 28 +++++++++---------- app/helpers/clients_helper.rb | 18 ++---------- 15 files changed, 43 insertions(+), 33 deletions(-) diff --git a/app/assets/stylesheets/clients/client_grid.scss b/app/assets/stylesheets/clients/client_grid.scss index d7e8a65288..804447d235 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, @@ -187,10 +189,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/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/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/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/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/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/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index cf38022f22..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 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/grids/client_grid.rb b/app/grids/client_grid.rb index da057439e0..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| 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 ff3d637f40..1969c59b0a 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,10 +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, - **client_address_translation + **@address_translation } lable_translation_uderscore.map{|k, v| [k.to_s.gsub(/(\_)$/, '').to_sym, v] }.to_h.merge(labels) @@ -336,18 +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['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| From 4742dded3fda13e616b5068dfe3193fc8c69f90c Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Feb 2022 19:00:08 +0700 Subject: [PATCH 0093/1114] 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 --- .../stylesheets/clients/client_grid.scss | 9 +++++- .../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/domain_score_fields.rb | 1 + .../families/family_fields.rb | 1 + .../partners/partner_fields.rb | 1 + .../quantitative_case_fields.rb | 1 + .../advanced_searches/school_grade_fields.rb | 1 + app/classes/client_columns_visibility.rb | 2 ++ app/controllers/application_controller.rb | 9 +++++- app/grids/client_grid.rb | 2 +- app/helpers/advanced_search_helper.rb | 28 +++++++++---------- app/helpers/clients_helper.rb | 18 ++---------- 15 files changed, 44 insertions(+), 33 deletions(-) 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/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/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/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/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/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/client_columns_visibility.rb b/app/classes/client_columns_visibility.rb index cf38022f22..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 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/grids/client_grid.rb b/app/grids/client_grid.rb index da057439e0..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| 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 ff3d637f40..1969c59b0a 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,10 +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, - **client_address_translation + **@address_translation } lable_translation_uderscore.map{|k, v| [k.to_s.gsub(/(\_)$/, '').to_sym, v] }.to_h.merge(labels) @@ -336,18 +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['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| From c00727f21d606a723239de98b99323ef548ff9ca Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Feb 2022 19:00:08 +0700 Subject: [PATCH 0094/1114] 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 --- .../stylesheets/clients/client_grid.scss | 9 +++++- .../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 | 2 ++ app/controllers/application_controller.rb | 9 +++++- app/grids/client_grid.rb | 2 +- app/helpers/advanced_search_helper.rb | 28 +++++++++---------- app/helpers/clients_helper.rb | 18 ++---------- 21 files changed, 50 insertions(+), 33 deletions(-) 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/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 cf38022f22..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 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/grids/client_grid.rb b/app/grids/client_grid.rb index da057439e0..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| 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 ff3d637f40..1969c59b0a 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,10 +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, - **client_address_translation + **@address_translation } lable_translation_uderscore.map{|k, v| [k.to_s.gsub(/(\_)$/, '').to_sym, v] }.to_h.merge(labels) @@ -336,18 +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['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| From 9b8148a5445f977f0bf992c3cba4b5a5c07710ad Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Wed, 23 Feb 2022 16:25:32 +0700 Subject: [PATCH 0095/1114] 20220223 Add check bundler version --- config/ansible/roles/deploy/tasks/main.yml | 4 ++-- .../ansible/roles/deploy/tasks/production.yml | 5 ----- config/ansible/roles/deploy/tasks/staging.yml | 5 ----- config/jenkins/deploy/demo.sh | 16 ++++++++++++++++ config/jenkins/deploy/live.sh | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/config/ansible/roles/deploy/tasks/main.yml b/config/ansible/roles/deploy/tasks/main.yml index e8c6f79828..7846861524 100644 --- a/config/ansible/roles/deploy/tasks/main.yml +++ b/config/ansible/roles/deploy/tasks/main.yml @@ -5,9 +5,9 @@ when: stage == 'latest' - name: Deploy staging servers - import_task: staging.yml + import_tasks: staging.yml when: stage == 'demo' - name: Deploy production servers - import_task: production.yml + import_tasks: production.yml when: stage == 'live' diff --git a/config/ansible/roles/deploy/tasks/production.yml b/config/ansible/roles/deploy/tasks/production.yml index f28296e802..146627f8ea 100644 --- a/config/ansible/roles/deploy/tasks/production.yml +++ b/config/ansible/roles/deploy/tasks/production.yml @@ -1,10 +1,5 @@ --- -- name: Make sure bundle 1.17.3 - shell: gem install bundler -v 1.17.3 - args: - chdir: '{{ workspace }}' - - name: Run bundle install shell: bundle install --verbose --jobs 20 --retry 5 args: diff --git a/config/ansible/roles/deploy/tasks/staging.yml b/config/ansible/roles/deploy/tasks/staging.yml index caa0c66f31..2618c9d19a 100644 --- a/config/ansible/roles/deploy/tasks/staging.yml +++ b/config/ansible/roles/deploy/tasks/staging.yml @@ -1,10 +1,5 @@ --- -- name: Make sure bundle 1.17.3 - shell: gem install bundler -v 1.17.3 - args: - chdir: '{{ workspace }}' - - name: Run bundle install shell: bundle install --verbose --jobs 20 --retry 5 args: diff --git a/config/jenkins/deploy/demo.sh b/config/jenkins/deploy/demo.sh index 918ef385d2..4cf0cf3ce2 100755 --- a/config/jenkins/deploy/demo.sh +++ b/config/jenkins/deploy/demo.sh @@ -1,7 +1,23 @@ #!/bin/bash +# +# Check to make sure the script run as current user +# +GREEN="\033[0;32m" +BROWN="\033[0;33m" +RED="\033[0;31m" +NC="\033[0m" # No Color FILE_NAME=$(basename "$0") STAGE="${FILE_NAME%.*}" +BUNDLE_VERSION="1.17.3" + +BUNDLE_VERSION_CHECK=`bundle --version 2> /dev/null | grep "${BUNDLE_VERSION}"` +if [ -z "${BUNDLE_VERSION_CHECK}" ]; then + echo + echo -e "${RED}Ansible version ${BUNDLE_VERSION} still cannot be found in the path." + echo -e "Please verify with 'ansible --version' command and fix it manually first.${NC}" + exit +fi ansible-playbook config/ansible/playbook.yml -t deploy -vv \ -l ${STAGE} \ diff --git a/config/jenkins/deploy/live.sh b/config/jenkins/deploy/live.sh index ac685ee34e..5b77de79f7 100755 --- a/config/jenkins/deploy/live.sh +++ b/config/jenkins/deploy/live.sh @@ -1,8 +1,25 @@ #!/bin/bash -ex exit 0 +# +# Check to make sure the script run as current user +# +GREEN="\033[0;32m" +BROWN="\033[0;33m" +RED="\033[0;31m" +NC="\033[0m" # No Color + FILE_NAME=$(basename "$0") STAGE="${FILE_NAME%.*}" +BUNDLE_VERSION="1.17.3" + +BUNDLE_VERSION_CHECK=`bundle --version 2> /dev/null | grep "${BUNDLE_VERSION}"` +if [ -z "${BUNDLE_VERSION_CHECK}" ]; then + echo + echo -e "${RED}Ansible version ${BUNDLE_VERSION} still cannot be found in the path." + echo -e "Please verify with 'ansible --version' command and fix it manually first.${NC}" + exit +fi ansible-playbook config/ansible/playbook.yml -t deploy -vv \ -l ${STAGE} \ From 3760e9ac4496bb58838ff736150dc6546c0351db Mon Sep 17 00:00:00 2001 From: Ratha Heang Date: Wed, 23 Feb 2022 17:02:27 +0700 Subject: [PATCH 0096/1114] 2022022301 Add appsignal key --- config/ansible/group_vars/all/vars.yml | 2 + config/ansible/group_vars/all/vault.yml | 47 ++++++++++--------- .../ansible/roles/deploy/tasks/production.yml | 6 +-- config/ansible/roles/deploy/tasks/staging.yml | 6 +-- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/config/ansible/group_vars/all/vars.yml b/config/ansible/group_vars/all/vars.yml index 82255e5d80..6e7f23f371 100644 --- a/config/ansible/group_vars/all/vars.yml +++ b/config/ansible/group_vars/all/vars.yml @@ -41,3 +41,5 @@ aws_access_key_id: '{{ aws_access_key_id_vault }}' aws_secret_access_key: '{{ aws_secret_access_key_vault }}' fog_region: 'ap-southeast-1' fog_directory: + +appsignal_push_api_key: '{{ appsignal_push_api_key_vault }}' diff --git a/config/ansible/group_vars/all/vault.yml b/config/ansible/group_vars/all/vault.yml index 467380c216..46a9f43f1c 100644 --- a/config/ansible/group_vars/all/vault.yml +++ b/config/ansible/group_vars/all/vault.yml @@ -1,23 +1,26 @@ $ANSIBLE_VAULT;1.1;AES256 -33353032633739653832333462306538363330316661633761626234633765303463316333383335 -3937393433386664636631323332346336616165666233660a353137346137333432643935643632 -36376139323761666635316131343362633762383762616331333132633632323934313062616438 -3739616435633730620a393963613530343133386632656534666138656461666239613730663235 -32313135343861353835393266343334393562373962663836373734393231316232303566633266 -32356639363365306337313739333061316662613131663536626631653332373466633831646236 -31613263656462333565646231616431636463326537623534346135633261333961633763396361 -63626335313962353364633737393930373031303765353163353137613132313830613164646631 -34306366663532336635313037323031326563393862343732326566626262653765366134626631 -35383138396138666135626231636361366335343362306137323938616563626430343338663835 -35333331663139316632363137313932353333326231333332653066626339336364353733396463 -39646462346236616438383933613361323234313332383264633833326535646233306134313032 -66333235306535663162643333373235656666343638663164333962633362346631353532653939 -33653065393235373462663239363136303737643861316239383538633231326135613030343133 -35643237643835366566353361386464643361383333623163653637626264383330366437343330 -36633637666437366265373762363938313031396661643238333264306135386236323633613234 -65646534363830363164653232306461656564633836343764643965353762623439336431346434 -66646465393966313462646261643464343364343132313133636366623635333734336536363832 -62333334663562333338663866363237653865666533363530333438646638663036313161653361 -37346237643261373666616339616164366230623930336635393632396535356533313730396366 -33343734646362633238646665326265383632653037633932336664303330363363346361303463 -3262346537386565646336393332386264313761623936343234 +37613034323062313965343664306663323133346234313235393338393736343130613833626434 +3738643837646630656236333435373339633330303736340a356237306530353766616261643362 +30316534333537306232663265356434346637653537333131666334323166323332303531336466 +3439336530373765330a316338616634393734373832396663626435396234653763323237626664 +37393035653831633663666666356338363464656566333962626337343438663965336534303135 +30383863333636333734363164633866616539613231636566623233363532613833326238623437 +30373938313866343863636233643638626238353932366266313764323762616535323432343164 +37323663626164383631653763646133666236373834653538373734373134323664613065343133 +37363562353465336636313833376538636430336538643239306233656662323132316364373562 +62663033353236343465616366623732623862323866646535366437616238653032333636616330 +33356166326365306562386332353231356366326239656463383666383535626130376635633239 +31313237393965386435336333343137383530623663306638356162303266316262613764386339 +37653436316666383434313033393239623132626435623762373461396164393638313366376433 +64316263383432376365356638656664373335633134633762393963653166323733366535303065 +37666237373463646662396137356532613461393831616234323533613965383466363434353236 +62613563306438383463356137343135353734316134306263653063306531393134363261666563 +61363835626130306434376164666432376430616665343336303062363934653934653036326136 +39626132396566376639333237373434653431383530383730396132376139636265333435306564 +35396366666261616363313131353164346235653838613062613963363861353664373665343635 +65353564326635303635343535393837373831393831316339303335373630303834396633383739 +31376334666665663736643466383133666365306438623835653730376135663339613730356166 +37396135663836613337303061616664363038386432663430343365373161323561353166663238 +38313633323465353766393863636339623561326430373630363939613464393739626136323533 +61306463663963306130666632663238306665623835383563633063323137613538623639633238 +36626437373131346338303630613133656539333633386366353031313032646266 diff --git a/config/ansible/roles/deploy/tasks/production.yml b/config/ansible/roles/deploy/tasks/production.yml index 146627f8ea..97666eed1a 100644 --- a/config/ansible/roles/deploy/tasks/production.yml +++ b/config/ansible/roles/deploy/tasks/production.yml @@ -1,16 +1,16 @@ --- - name: Run bundle install - shell: bundle install --verbose --jobs 20 --retry 5 + shell: 'bundle install --verbose --jobs 20 --retry 5' args: chdir: '{{ workspace }}' - name: Run yarn install - shell: yarn install + shell: 'yarn install' args: chdir: '{{ workspace }}' - name: Deploy to production server - shell: cap production deploy + shell: 'APPSIGNAL_PUSH_API_KEY={{ appsignal_push_api_key }} cap production deploy' args: chdir: '{{ workspace }}' diff --git a/config/ansible/roles/deploy/tasks/staging.yml b/config/ansible/roles/deploy/tasks/staging.yml index 2618c9d19a..337dc280ac 100644 --- a/config/ansible/roles/deploy/tasks/staging.yml +++ b/config/ansible/roles/deploy/tasks/staging.yml @@ -1,16 +1,16 @@ --- - name: Run bundle install - shell: bundle install --verbose --jobs 20 --retry 5 + shell: 'bundle install --verbose --jobs 20 --retry 5' args: chdir: '{{ workspace }}' - name: Run yarn install - shell: yarn install + shell: 'yarn install' args: chdir: '{{ workspace }}' - name: Deploy to staging server - shell: cap staging deploy + shell: 'APPSIGNAL_PUSH_API_KEY={{ appsignal_push_api_key }} cap staging deploy' args: chdir: '{{ workspace }}' From f8f72ec78009a6a8e91cbe53521387d3c5b2887c Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 28 Feb 2022 14:41:54 +0700 Subject: [PATCH 0097/1114] updated en.yml key client_active_in_programs --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 412d6861b1..4ba7c94384 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2532,7 +2532,7 @@ en: client_program_stream_by_gender: active_progarm_streams_by_gender: Active Program Streams by client gender active_program_streams: Active Program Streams - client_active_in_programs: Active Program Streams + client_active_in_programs: Clients in Active Program Streams client_tab: action: Action client_form: Client Form From d14249d2e130086227b81c76b956d55b9299c583 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 12:14:27 +0700 Subject: [PATCH 0098/1114] added staging appsignal config option in config/deploy/staging.rb --- config/deploy/staging.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 284d771b34..70bc194556 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -8,6 +8,8 @@ # server 'db.example.com', user: 'deploy', roles: %w{db} set :stage, 'staging' +set :rails_env, :staging +set :appsignal_env, :staging set :branch, proc { `git rev-parse --abbrev-ref staging`.chomp } server '3.0.131.11', user: 'deployer', roles: %w{app web db} From acc5e0b4da029dc21c597d5eab259ecf3ab7af53 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 12:51:01 +0700 Subject: [PATCH 0099/1114] changed AppSignal gem and update Capfile --- Capfile | 2 ++ Gemfile | 2 +- config/deploy.rb | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Capfile b/Capfile index e286908626..19294d767a 100644 --- a/Capfile +++ b/Capfile @@ -30,5 +30,7 @@ require 'capistrano/sidekiq' # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } +require 'appsignal/capistrano' + # Capfile # require 'capistrano/sidekiq/monit' #to require monit tasks # Only for capistrano3 diff --git a/Gemfile b/Gemfile index 1e854c99a9..553f4092f0 100644 --- a/Gemfile +++ b/Gemfile @@ -97,7 +97,7 @@ group :development, :test do end group :staging, :ratanak_staging, :demo, :production do - gem 'appsignal', '~> 1.1.9' +gem 'appsignal', git: 'git@github.com:appsignal/appsignal-ruby.git', branch: 'capistrano_appsignal_env' gem 'asset_sync' end diff --git a/config/deploy.rb b/config/deploy.rb index 80364f3316..ce359f9185 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -60,5 +60,3 @@ set :passenger_restart_with_touch, true set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } - -require 'appsignal/capistrano' From 4b6557fdc9485158afaee62e513a8af22a3b3e87 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 12:58:18 +0700 Subject: [PATCH 0100/1114] changed AppSignal gem and update Capfile --- Capfile | 2 ++ Gemfile | 2 +- config/deploy.rb | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Capfile b/Capfile index e286908626..19294d767a 100644 --- a/Capfile +++ b/Capfile @@ -30,5 +30,7 @@ require 'capistrano/sidekiq' # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } +require 'appsignal/capistrano' + # Capfile # require 'capistrano/sidekiq/monit' #to require monit tasks # Only for capistrano3 diff --git a/Gemfile b/Gemfile index 1e854c99a9..5b32a2a131 100644 --- a/Gemfile +++ b/Gemfile @@ -97,7 +97,7 @@ group :development, :test do end group :staging, :ratanak_staging, :demo, :production do - gem 'appsignal', '~> 1.1.9' + gem 'appsignal', git: 'https://github.com/appsignal/appsignal-ruby.git', branch: 'capistrano_appsignal_env' gem 'asset_sync' end diff --git a/config/deploy.rb b/config/deploy.rb index 80364f3316..ce359f9185 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -60,5 +60,3 @@ set :passenger_restart_with_touch, true set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } - -require 'appsignal/capistrano' From 63edf1e1297fa7e3c207bb49ed3766abb4ffd6a3 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 14:23:37 +0700 Subject: [PATCH 0101/1114] updated AppSignal gem --- Gemfile | 2 +- Gemfile.lock | 5 ++--- config/deploy.rb | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 5b32a2a131..5821d16f91 100644 --- a/Gemfile +++ b/Gemfile @@ -97,7 +97,7 @@ group :development, :test do end group :staging, :ratanak_staging, :demo, :production do - gem 'appsignal', git: 'https://github.com/appsignal/appsignal-ruby.git', branch: 'capistrano_appsignal_env' + gem 'appsignal', '~> 3.0', '>= 3.0.24' gem 'asset_sync' end diff --git a/Gemfile.lock b/Gemfile.lock index 666defda74..a54a386179 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 ce359f9185..4ec3f35519 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -60,3 +60,5 @@ set :passenger_restart_with_touch, true set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } +set :appsignal_revision, `git log --pretty=format:'%h' -n 1 #{fetch(:branch)}` +set :appsignal_user, "deployer" \ No newline at end of file From b6d3b848cb405f6060323d59b92a17942695d74f Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 15:21:33 +0700 Subject: [PATCH 0102/1114] updated deploy.rb --- Capfile | 3 +++ config/appsignal.yml | 2 ++ config/deploy.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/Capfile b/Capfile index 19294d767a..718dc8989c 100644 --- a/Capfile +++ b/Capfile @@ -1,3 +1,6 @@ +require "dotenv" +Dotenv.load + # Load DSL and set up stages require 'capistrano/setup' diff --git a/config/appsignal.yml b/config/appsignal.yml index 1e104df629..9b59f510e8 100644 --- a/config/appsignal.yml +++ b/config/appsignal.yml @@ -8,6 +8,8 @@ default: &defaults ignore_errors: - Apartment::TenantNotFound + - ActiveRecord::RecordNotFound + - ActionController::InvalidAuthenticityToken # Actions that should not be monitored by AppSignal # ignore_actions: diff --git a/config/deploy.rb b/config/deploy.rb index af1ddde4d9..31cbeb0543 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -57,6 +57,47 @@ before :updated, :cleanup_assets end +namespace :appsignal do + # Description is required for it to show up in the tasks list. + desc 'Notify AppSignal of this deploy!' + task :deploy do + # 1. Needs to be run inside an `on` block + # 2. `appsignal_roles` setting set and supplied with default + on roles(fetch(:appsignal_roles, :app)) do + env = fetch(:rails_env, 'production') + user = ENV['USER'] || ENV['USERNAME'] + + appsignal_config = Appsignal::Config.new( + ENV['PWD'], + env, + fetch(:appsignal_config, {}), + self + ) + + if appsignal_config && appsignal_config.active? + # `current_revision` method has been removed. + # When run from `deploy` the `current_revision` setting should be + # set (see: + # https://github.com/capistrano/capistrano/blob/3d641ea994627a22edd01529440b12a9e565001d/lib/capistrano/tasks/git.rake#L52 + # https://github.com/capistrano/capistrano/blob/5986983915163e6681f2546bf6fad599d58cd024/lib/capistrano/dsl.rb#L32 + # ), otherwise (when run with `cap appsignal:deploy`) check again with + # `fetch_revision`. + marker_data = { + revision: fetch(:current_revision) || fetch_revision, + repository: fetch(:repo_url), + user: user + } + + marker = Appsignal::Marker.new(marker_data, appsignal_config, self) + # Dry run conditional removed, now Capistrano does this on a higher level. + marker.transmit + end + end + end +end + +after 'deploy', 'appsignal:deploy' + set :passenger_restart_with_touch, true set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } From b2169d867955e91009d7b40f71cc2b766dbe7d24 Mon Sep 17 00:00:00 2001 From: kirykr Date: Wed, 9 Mar 2022 17:43:15 +0700 Subject: [PATCH 0103/1114] removed task in deploy.rb --- config/deploy.rb | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/config/deploy.rb b/config/deploy.rb index c7dd7647e4..5befcdf9bc 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -59,47 +59,6 @@ before :updated, :cleanup_assets end -namespace :appsignal do - # Description is required for it to show up in the tasks list. - desc 'Notify AppSignal of this deploy!' - task :deploy do - # 1. Needs to be run inside an `on` block - # 2. `appsignal_roles` setting set and supplied with default - on roles(fetch(:appsignal_roles, :app)) do - env = fetch(:rails_env, 'production') - user = ENV['USER'] || ENV['USERNAME'] - - appsignal_config = Appsignal::Config.new( - ENV['PWD'], - env, - fetch(:appsignal_config, {}), - self - ) - - if appsignal_config && appsignal_config.active? - # `current_revision` method has been removed. - # When run from `deploy` the `current_revision` setting should be - # set (see: - # https://github.com/capistrano/capistrano/blob/3d641ea994627a22edd01529440b12a9e565001d/lib/capistrano/tasks/git.rake#L52 - # https://github.com/capistrano/capistrano/blob/5986983915163e6681f2546bf6fad599d58cd024/lib/capistrano/dsl.rb#L32 - # ), otherwise (when run with `cap appsignal:deploy`) check again with - # `fetch_revision`. - marker_data = { - revision: fetch(:current_revision) || fetch_revision, - repository: fetch(:repo_url), - user: user - } - - marker = Appsignal::Marker.new(marker_data, appsignal_config, self) - # Dry run conditional removed, now Capistrano does this on a higher level. - marker.transmit - end - end - end -end - -after 'deploy', 'appsignal:deploy' - set :passenger_restart_with_touch, true set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" } From 7efff9c5b6f8bfd3581fd016658562fb39a59ad5 Mon Sep 17 00:00:00 2001 From: kirykr Date: Mon, 21 Mar 2022 10:58:30 +0700 Subject: [PATCH 0104/1114] fixed fcf_report error line 22 --- 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 afba8b7969e8bb4ed1c79f495c817359f82f36e8 Mon Sep 17 00:00:00 2001 From: Rayuth You Date: Fri, 25 Mar 2022 11:44:50 +0700 Subject: [PATCH 0105/1114] [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 0106/1114] 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 0107/1114] 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 0108/1114] 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 0109/1114] 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 0110/1114] 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 0111/1114] 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 0112/1114] 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 0113/1114] 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 0114/1114] 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 0115/1114] 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 0116/1114] 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 0117/1114] 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 0c3ff42648f5f6b69f0cbe726f5cfcf4ef1d2aaa Mon Sep 17 00:00:00 2001 From: Doungdara Keut Date: Fri, 25 Mar 2022 15:53:28 +0700 Subject: [PATCH 0118/1114] [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 0119/1114] [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 0120/1114] 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 0121/1114] 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 0122/1114] 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 0123/1114] [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 0124/1114] 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 0125/1114] [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 0126/1114] [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 0127/1114] [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 0128/1114] 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 0129/1114] 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 0130/1114] [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 0131/1114] [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 0132/1114] 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 0133/1114] 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 0134/1114] 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 0135/1114] [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 0136/1114] 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 0137/1114] 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 0138/1114] 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 0139/1114] 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 0140/1114] 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 0141/1114] 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 0142/1114] 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 0143/1114] 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 0144/1114] [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 0145/1114] [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 0146/1114] 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 0147/1114] [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 0148/1114] 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 0149/1114] 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 0150/1114] [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 0151/1114] 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 0152/1114] 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 0153/1114] 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 0154/1114] 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 0155/1114] 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 0156/1114] 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 0157/1114] [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 0158/1114] 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 0159/1114] 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 768cebd03fe8a66cf2f1c79d5d6e5419a9dcab83 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 10:29:29 +0700 Subject: [PATCH 0160/1114] fixed missing field --- config/locales/en.yml | 2 ++ config/locales/km.yml | 2 ++ config/locales/my.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 4426ac94b4..71ba17f9e9 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 @@ -535,6 +536,7 @@ en: referral_source_id: Referral Source referred_from: Referred From referred_in: Referred In + referred_out: Referred Out referred_to: Referred To referee: Referee referee_relationship: Referee Relationship to Client diff --git a/config/locales/km.yml b/config/locales/km.yml index e084563fd8..c78a90b426 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: แž€แž˜แŸ’แž˜แžœแžทแž’แžธแž…แž˜แŸ’แž”แž„ @@ -529,6 +530,7 @@ km: referral_source_category_id: แž”แŸ’แžšแž—แŸแž‘แž”แŸ’แžšแž—แž–แž“แŸƒแž€แžถแžšแž”แž‰แŸ’แž‡แžผแž“ referral_source_id: แž”แŸ’แžšแž—แž–แž“แŸƒแž€แžถแžšแž”แž‰แŸ’แž‡แžผแž“ referred_from: แž”แž‰แŸ’แž‡แžผแž“แž˜แž€แž–แžธแžขแž„แŸ’แž‚แž€แžถแžšแžŠแŸƒแž‚แžผแžš + referred_out: แž”แž‰แŸ’แž‡แžผแž“แž˜แž€แž‘แŸ…แžขแž„แŸ’แž‚แž€แžถแžšแžŠแŸƒแž‚แžผแžš referred_to: แž”แž‰แŸ’แž‡แžผแž“แž˜แž€แž‘แŸ…แžขแž„แŸ’แž‚แž€แžถแžšแžŠแŸƒแž‚แžผแžš referee: แžขแŸ’แž“แž€แž”แž‰แŸ’แž‡แžผแž“ referee_relationship: แž‘แŸ†แž“แžถแž€แŸ‹แž‘แŸ†แž“แž„แžšแžœแžถแž„แžขแŸ’แž“แž€แž”แž‰แŸ’แž‡แžผแž“แž“แžนแž„แžขแžแžทแžแžทแž‡แž“ diff --git a/config/locales/my.yml b/config/locales/my.yml index 758056e086..13715504e2 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: แ€ฑแ€”แ€›แ€ฌแ€แ€บแ€‘แ€ฌแ€ธแ€™แ‚ˆแ€…แ€แ€„แ€นแ€žแ€Šแ€นแ€ทแ€›แ€€แ€นแ€…แ€ผแ€ฒ @@ -535,6 +536,7 @@ my: referral_source_category_id: แ€œแ€ฝแ€พแ€ฒแ€•แ€ผแ€ฑแ€ฌแ€„แ€บแ€ธแ€กแ€›แ€„แ€บแ€ธแ€กแ€™แ€ผแ€…แ€บแ€€แ€แ€นแ€ referral_source_id: แ€œแ‚Šแ€ฒแ€ฑแ€ปแ€•แ€ฌแ€„แ€นแ€ธแ€›แ€™แ€Šแ€นแ€ทแ€žแ€ฐ referred_from: แ€€แ€”แ€ฑแ€›แ€Šแ€บแ€Šแ€ฝแ€พแ€”แ€บแ€ธ + referred_out: แ€€แ€”แ€ฑแ€›แ€Šแ€บแ€Šแ€ฝแ€พแ€”แ€บแ€ธ referred_to: แ€€แ€”แ€ฑแ€›แ€Šแ€บแ€Šแ€ฝแ€พแ€”แ€บแ€ธ referred_to_ec: EC แ€žแ€ญแ€ฏแ‚” แ€›แ€Šแ€นแ€Šแ‚Šแ€”แ€นแ€ธแ€žแ€Šแ€นแ‹ referred_to_fc: EC แ€žแ€ญแ€ฏแ‚” แ€›แ€Šแ€นแ€Šแ‚Šแ€”แ€นแ€ธแ€žแ€Šแ€นแ‹ From 297293a19ed590c7aea279a62d5a1ff2ba944df1 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 11:10:08 +0700 Subject: [PATCH 0161/1114] 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 0236fa17ce14ce7d6d0e51623021e44ebc5d93e2 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 11:16:27 +0700 Subject: [PATCH 0162/1114] added missing legal doc translations --- config/locales/en.yml | 6 ++++++ config/locales/km.yml | 6 ++++++ config/locales/my.yml | 9 ++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 1a5842bed9..4bd44e6d50 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1565,6 +1565,7 @@ en: clients: attr: &LEGAL_DOC_ATTR add_service_received: Add Services Received + birth_cert: 'Birth Certificate' complain: Complaint cb_dmat: CB-DMAT detail_form_of_judicial_police: Detailed Form for Identification of Victim of Human Trafficking (Judicial Police) @@ -1576,11 +1577,16 @@ en: labor_trafficking_legal_doc_option: Labour Trafficking legal_processing_doc: Legal Proceeding Documents letter_from_immigration_police: Letter from Immigration Police + local_consent: 'Legal consent' mosavy: MoSAVY mosvy_number: MoSVY Number msdhs: MSDHS + national_id: 'National ID' ngo_partner: NGO Partner + other_legal_doc: 'Others' other_legal_doc_option: Other + passport: 'Passport' + police_interview: 'Police interview' referral_document: Referral Documents screening_interview_form: Screening Interview Form sex_trafficking_legal_doc_option: Sexual Trafficking diff --git a/config/locales/km.yml b/config/locales/km.yml index 7313e5b82d..45c7293ea1 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -1523,7 +1523,9 @@ km: clients: attr: &LEGAL_DOC_ATTR add_service_received: แž”แž“แŸ’แžแŸ‚แž˜แžŸแŸแžœแžถแžŠแŸ‚แž›แž”แžถแž“แž‘แž‘แžฝแž› + birth_cert: 'Birth Certificate' complain: แž–แžถแž€แŸ’แž™แž”แžŽแŸ’แžŠแžนแž„ + cb_dmat: CB-DMAT detail_form_of_judicial_police: แž‘แž˜แŸ’แžšแž„แŸ‹แž›แž˜แŸ’แžขแžทแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแžŸแž˜แŸ’แž—แžถแžŸแž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแž”แž‹แž˜แž‡แž“แžšแž„แž‚แŸ’แžšแŸ„แŸ‡แžŠแŸ„แž™แžขแŸ†แž–แžพแž‡แžฝแž‰แžŠแžผแžšแž˜แž“แžปแžŸแŸ’แžŸ (แž“แž‚แžšแž”แžถแž›แž™แžปแžแŸ’แžแžทแž’แž˜แŸŒ) detail_form_of_mosavy_dosavy: แž‘แž˜แŸ’แžšแž„แŸ‹แž›แž˜แŸ’แžขแžทแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแžŸแž˜แŸ’แž—แžถแžŸแž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแž”แž‹แž˜แž‡แž“แžšแž„แž‚แŸ’แžšแŸ„แŸ‡แžŠแŸ„แž™แžขแŸ†แž–แžพแž‡แžฝแž‰แžŠแžผแžšแž˜แž“แžปแžŸแŸ’แžŸ (แž€แŸ’แžšแžŸแžฝแž„ แžŸ.แžข.แž™/แž˜แž“แŸ’แž‘แžธแžš แžŸ.แžข.แž™) dosavy: แž˜แž“แŸ’แž‘แžธแžš แžŸ.แžข.แž™ @@ -1533,11 +1535,15 @@ km: labor_trafficking_legal_doc_option: แž€แžถแžšแž‡แžฝแž‰แžŠแžผแžšแž–แž›แž€แž˜แŸ’แž˜ legal_processing_doc: แžฏแž€แžŸแžถแžšแž“แžทแžแžทแžœแžทแž’แžธแž…แŸ’แž”แžถแž”แŸ‹ letter_from_immigration_police: แž›แžทแžแžทแžแž–แžธแž”แŸ‰แžผแž›แžธแžŸแžขแž“แŸ’แžแŸ„แž”แŸ’แžšแžœแŸแžŸแž“แŸ + local_consent: 'Legal consent' mosavy: แž€แŸ’แžšแžŸแžฝแž„ แžŸ.แžข.แž™ mosvy_number: แž›แŸแžแžŸแŸ†แž‚แžถแž›แŸ‹ แžŸ.แžข.แž™ msdhs: แž€แŸ’แžšแžŸแžฝแž„แžขแž—แžทแžœแžŒแŸ’แžแž“แŸแžŸแž„แŸ’แž‚แž˜แž“แžทแž„แžŸแž“แŸ’แžแžทแžŸแžปแž (แžแŸƒ) ngo_partner: แžขแž„แŸ’แž‚แž€แžถแžšแž€แŸ’แžšแŸ…แžšแžŠแŸ’แž‹แžถแž—แžทแž”แžถแž›แž‡แžถแžŠแŸƒแž‚แžผ + national_id: 'National ID' + other_legal_doc: 'Others' other_legal_doc_option: แž•แŸ’แžŸแŸแž„แŸ— + police_interview: 'Police interview' referral_document: แžฏแž€แžŸแžถแžšแž”แž‰แŸ’แž‡แžผแž“ screening_interview_form: แž‘แž˜แŸ’แžšแž„แŸ‹แžŸแž˜แŸ’แž—แžถแžŸแž“แŸแž”แž‹แž˜ sex_trafficking_legal_doc_option: แž€แžถแžšแž‡แžฝแž‰แžŠแžผแžšแž•แŸ’แž›แžผแžœแž—แŸแž‘ diff --git a/config/locales/my.yml b/config/locales/my.yml index 41b08798d8..2f93124746 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1558,21 +1558,28 @@ my: clients: attr: &LEGAL_DOC_ATTR add_service_received: Add Services Received + birth_cert: 'Birth Certificate' complain: Complaint + cb_dmat: CB-DMAT detail_form_of_judicial_police: Detailed Form for Identification of Victim of Human Trafficking (Judicial Police) detail_form_of_mosavy_dosavy: Detailed Form for Identification of Victim of Human Trafficking (MoSAVY/DoSAVY) dosavy: DoSAVY - external_id_display: แ€•แ€ผแ€„แ€บแ€• ID display + external_id_display: External ID Display form_indentification: Forms for Identification of Victim of Human Trafficking indentification_doc: Identification Documents labor_trafficking_legal_doc_option: Labour Trafficking legal_processing_doc: Legal Proceeding Documents letter_from_immigration_police: Letter from Immigration Police + local_consent: 'Legal consent' mosavy: MoSAVY mosvy_number: MoSVY Number msdhs: MSDHS + national_id: 'National ID' ngo_partner: NGO Partner + other_legal_doc: 'Others' other_legal_doc_option: Other + passport: 'Passport' + police_interview: 'Police interview' referral_document: Referral Documents screening_interview_form: Screening Interview Form sex_trafficking_legal_doc_option: Sexual Trafficking From 3b51ca4db218f701875a4d17407e8d2545fdfb0f Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 11:20:14 +0700 Subject: [PATCH 0163/1114] fixed missing legal documents translation in report builder rules --- config/locales/my.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/my.yml b/config/locales/my.yml index e1599415c4..d13b7a627d 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1569,6 +1569,7 @@ my: form_indentification: Forms for Identification of Victim of Human Trafficking indentification_doc: Identification Documents labor_trafficking_legal_doc_option: Labour Trafficking + legal_documents: Legal Documents legal_processing_doc: Legal Proceeding Documents letter_from_immigration_police: Letter from Immigration Police local_consent: 'Legal consent' From 86f25aa6e9e92ecc2a8e748062f5425cd439d6b5 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 7 Apr 2022 11:20:26 +0700 Subject: [PATCH 0164/1114] fixed missing legal documents translation in report builder rules --- config/locales/en.yml | 1 + config/locales/km.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 36628caf1f..2a06fcf7f4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1576,6 +1576,7 @@ en: form_indentification: Forms for Identification of Victim of Human Trafficking indentification_doc: Identification Documents labor_trafficking_legal_doc_option: Labour Trafficking + legal_documents: Legal Documents legal_processing_doc: Legal Proceeding Documents letter_from_immigration_police: Letter from Immigration Police local_consent: 'Legal consent' diff --git a/config/locales/km.yml b/config/locales/km.yml index 58e9988dec..cbc927f315 100644 --- a/config/locales/km.yml +++ b/config/locales/km.yml @@ -1534,6 +1534,7 @@ km: form_indentification: แž‘แž˜แŸ’แžšแž„แŸ‹แžŸแž˜แŸ’แž—แžถแžŸแž“แŸแž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแž‡แž“แžšแž„แž‚แŸ’แžšแŸ„แŸ‡แžŠแŸ„แž™แžขแŸ†แž–แžพแž‡แžฝแž‰แžŠแžผแžšแž˜แž“แžปแžŸแŸ’แžŸ indentification_doc: แžฏแž€แžŸแžถแžšแžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽ labor_trafficking_legal_doc_option: แž€แžถแžšแž‡แžฝแž‰แžŠแžผแžšแž–แž›แž€แž˜แŸ’แž˜ + legal_documents: แžฏแž€แžŸแžถแžšแž…แŸ’แž”แžถแž”แŸ‹ legal_processing_doc: แžฏแž€แžŸแžถแžšแž“แžทแžแžทแžœแžทแž’แžธแž…แŸ’แž”แžถแž”แŸ‹ letter_from_immigration_police: แž›แžทแžแžทแžแž–แžธแž”แŸ‰แžผแž›แžธแžŸแžขแž“แŸ’แžแŸ„แž”แŸ’แžšแžœแŸแžŸแž“แŸ local_consent: 'Legal consent' From edfe2ff2d4321453896b5acb4b8589338dddcc16 Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 8 Apr 2022 15:42:19 +0700 Subject: [PATCH 0165/1114] fixed Report builder Enrollment Date with gender result blank --- .../advanced_searches/enrollment_date_sql_builder.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/classes/advanced_searches/enrollment_date_sql_builder.rb b/app/classes/advanced_searches/enrollment_date_sql_builder.rb index ccf3af9dee..e056fc0d3a 100644 --- a/app/classes/advanced_searches/enrollment_date_sql_builder.rb +++ b/app/classes/advanced_searches/enrollment_date_sql_builder.rb @@ -12,7 +12,7 @@ def get_sql 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) - query_string = enrollment_rules.map do |rules| + query_string = enrollment_rules.reject{ |rules| rules['id'][/^(enrollment)/].blank? }.map do |rules| client_enrollment_sql(rules['operator'], rules['field'], rules['value'], rules['input'], rules['type']) end.join(" #{param_rules['condition']} ") @@ -36,7 +36,12 @@ def get_sql when 'between' client_enrollment_date = client_enrollments.where('enrollment_date BETWEEN ? AND ?', @value.first, @value.last) end - client_ids = client_enrollment_date.where(query_string).pluck(:client_id).uniq + + if query_string.present? + client_ids = client_enrollment_date.where(query_string).pluck(:client_id).uniq + else + client_ids = client_enrollment_date.pluck(:client_id).uniq + end { id: sql_string, values: client_ids } end @@ -100,7 +105,7 @@ def iterate_param_rules(param_rules, values = []) if rules.has_key?('rules') iterate_param_rules(rules, values) else - values << rules unless rules['id'][/^(enrollment_|enrollmentdate_)/].present? + values << rules unless rules['id'][/^(enrollmentdate_)/].present? end end values From 4a22d1956127aade18ef47e15b3a83a2e8c9a660 Mon Sep 17 00:00:00 2001 From: lysa Date: Wed, 16 Mar 2022 13:25:05 +0700 Subject: [PATCH 0166/1114] [IMP] Add browser test refs OSC-12 --- spec/features/assessment_spec.rb | 147 ++--------- spec/features/care_plan_spec.rb | 84 +++++++ spec/features/case_note_spec.rb | 46 +++- spec/features/family_spec.rb | 235 ++---------------- spec/features/login_spec.rb | 26 ++ spec/features/report_builder_spec.rb | 24 ++ spec/features/start_page_spec.rb | 15 ++ spec/features/user_spec.rb | 29 ++- .../api/v1/client_duplicate_checker_spec.rb | 82 ++++++ .../api/v1/clients_controller_spec.rb | 6 +- 10 files changed, 337 insertions(+), 357 deletions(-) create mode 100644 spec/features/care_plan_spec.rb create mode 100644 spec/features/login_spec.rb create mode 100644 spec/features/report_builder_spec.rb create mode 100644 spec/features/start_page_spec.rb create mode 100644 spec/requests/api/v1/client_duplicate_checker_spec.rb diff --git a/spec/features/assessment_spec.rb b/spec/features/assessment_spec.rb index 87026d520e..ef6abc7172 100644 --- a/spec/features/assessment_spec.rb +++ b/spec/features/assessment_spec.rb @@ -1,11 +1,13 @@ -xdescribe "Assessment" do +describe "Assessment" do let!(:user) { create(:user) } + let!(:admin) { create(:user, :admin) } let!(:strategic_overviewer_1){ create(:user, :strategic_overviewer) } let!(:user_2){ create(:user) } let!(:client) { create(:client, :accepted, users: [user]) } let!(:client_a) { create(:client, :accepted, users: [user, user_2]) } let!(:client_b) { create(:client, :accepted, users: [user, user_2]) } let!(:fc_case) { create(:case, case_type: 'FC', client: client) } + let!(:family) { create(:family, :active, user: user) } let!(:domain) { create(:domain) } let!(:assessment_a){ create(:assessment, client: client_a) } let!(:assessment_b){ create(:assessment, client: client_b, created_at: 1.week.ago) } # this is 8 days @@ -14,84 +16,9 @@ login_as(user) end - feature 'show' do - let!(:domain_1){ create(:domain, score_1_color: 'danger', score_1_definition: 'Poor', score_2_color: 'warning', score_2_definition: 'Good') } - let!(:assessment_1){ create(:assessment, client: client, created_at: 6.months.ago) } - let!(:assessment_2){ create(:assessment, client: client) } - let!(:assessment_domain_1){ create(:assessment_domain, score: 1, assessment: assessment_1, domain: domain_1) } - let!(:assessment_domain_2){ create(:assessment_domain, previous_score: 1, score: 2, assessment: assessment_2, domain: domain_1) } - - feature 'CSI Assessment' do - before do - visit client_assessment_path(client, assessment_2) - end - - scenario 'Case Plan' do - expect(page).to have_content("Case Plan for #{client.name}") - expect(page).to have_content("Based on Assessment Number 2") - expect(page).to have_content("Completed by OSCaR Team on #{date_format(Date.today)}") - expect(page).to have_css('.btn-danger[data-toggle="tooltip"][data-original-title="

    Poor

    "]', text: '1') - expect(page).to have_css('.btn-warning[data-toggle="tooltip"][data-original-title="

    Good

    "]', text: '2') - end - - scenario 'status' do - expect(page).to have_content('Completed') - end - - context 'edit link' do - context 'log in as case worker / manager / admin' do - context 'visible within 1 week' do - scenario 'eligiable age' do - visit client_assessment_path(client_a, assessment_a) - - expect(page).to have_link(nil, href: edit_client_assessment_path(client_a, assessment_a)) - end - scenario 'uneligiable age' do - client_a.update(date_of_birth: 20.years.ago) - visit client_assessment_path(client_a, assessment_a) - - expect(page).to have_link(nil, href: edit_client_assessment_path(client_a, assessment_a)) - end - end - - scenario 'invisible after 1 week' do - visit client_assessment_path(client_b, assessment_b) - - expect(page).not_to have_link(nil, href: edit_client_assessment_path(client_b, assessment_b)) - end - end - - context 'log in as strategic overviewer' do - before do - login_as(strategic_overviewer_1) - end - - scenario 'invisible' do - visit client_assessment_path(client_b, assessment_b) - expect(page).not_to have_link(nil, href: edit_client_assessment_path(client_b, assessment_b)) - end - end - end - end - - feature 'Custom Assessment', js: true do - before { Setting.first.update(enable_custom_assessment: true) } - let!(:custom_assessment_1){ create(:assessment, client: client, default: false) } - - context 'is not enabled' do - scenario 'unauthorized' do - Setting.first.update(enable_custom_assessment: false) - visit client_assessment_path(client, custom_assessment_1) - - expect(page).to have_content('You are not authorized to access this page.') - end - end - end - - end - feature 'Create' do before do + login_as(admin) visit new_client_assessment_path(client, default: true) end @@ -105,41 +32,36 @@ def add_tasks(n) end end - def with_tasks(n) - choose('1') - expect(page).to have_css('.task_required') - expect(page).to have_css('.assessment-task-btn') - add_tasks(n) + scenario 'invalid for client', js: true do + click_link 'Done' + expect(page).to have_content('This field is required') end - def without_task - choose('4') - expect(page).to have_css('.label-success') - expect(page).not_to have_css('.label-danger') - expect(page).not_to have_css('.label-warning') - expect(page).not_to have_css('.label-info') + scenario 'valid for client', js: true do + fill_in 'assessment_assessment_domains_attributes_0_reason', with: FFaker::Lorem.paragraph + choose('1') + click_link 'Done' + sleep 1 + expect(page).to have_content('Assessment has been successfully created.') + visit client_assessment_path(client, client.assessments.first) + expect(page).to have_content("Case Plan for #{client.name}") + expect(page).to have_content(domain.name.downcase.reverse) end - scenario 'valid', js: true do - with_tasks(1) - without_task - + scenario 'valid for family', js: true do + visit new_family_assessment_path(family, default: true) fill_in 'assessment_assessment_domains_attributes_0_reason', with: FFaker::Lorem.paragraph - fill_in "goal-text-area-#{domain.name.downcase.split.join('-')}", with: FFaker::Lorem.paragraph - - click_link 'Save' + choose('1') + click_link 'Done' sleep 1 + expect(page).to have_content('Assessment has been successfully created.') + visit family_assessment_path(family, family.assessments.first) expect(page).to have_content(domain.name) - expect(page.find('.domain-score')).to have_content('4') - expect(Task.find_by(name: 'ABC').user_id).to eq(user.id) + expect(page).to have_content("Case Plan for #{family.name}") end - scenario 'invalid', js: true do - choose('1') - - fill_in 'assessment_assessment_domains_attributes_0_reason', with: FFaker::Lorem.paragraph - - click_link 'Save' + scenario 'invalid for family', js: true do + click_link 'Done' expect(page).to have_content('This field is required') end @@ -196,18 +118,6 @@ def without_task expect(page).to have_link('Add New CSI Assessment', href: new_client_assessment_path(client_2, default: true)) end end - - context 'Custom Assessment' do - let!(:domain_1){ create(:domain, custom_domain: true) } - scenario 'after minimum assessment duration' do - visit client_assessments_path(client_1) - expect(page).to have_link('Add New Custom Assessment', href: new_client_assessment_path(client_1, default: false)) - end - scenario 'after maximum assessment duration' do - visit client_assessments_path(client_2) - expect(page).to have_link('Add New Custom Assessment', href: new_client_assessment_path(client_2, default: false)) - end - end end context 'assessments readable permission' do @@ -221,13 +131,6 @@ def without_task expect(dashboards_path).to have_content(current_path) end end - - let!(:client_3){ create(:client, :accepted, users: [user], date_of_birth: 18.years.ago) } - scenario 'create assessment button is disable for client whose age is over 18' do - visit client_assessments_path(client_3) - expect(page).to have_css('#disabled_assessment_button') - end - end feature 'Update' do diff --git a/spec/features/care_plan_spec.rb b/spec/features/care_plan_spec.rb new file mode 100644 index 0000000000..286dd85132 --- /dev/null +++ b/spec/features/care_plan_spec.rb @@ -0,0 +1,84 @@ +describe 'Care Plan' do + let!(:admin){ create(:user, :admin) } + let!(:user){ create(:user) } + let!(:family) { create(:family, :active, user: user) } + let!(:client) { create(:client, :accepted, users: [user]) } + let!(:fc_case){ create(:case, case_type: 'FC', client: client) } + let!(:domain_group){ create(:domain_group) } + let!(:domain_1){ create(:domain, score_1_color: 'danger', score_1_definition: 'Poor', score_2_color: 'warning', score_2_definition: 'Good', domain_group:domain_group) } + let!(:assessment_1){ create(:assessment, :custom, client: client, created_at: 6.months.ago) } + let!(:assessment_2){ create(:assessment, :custom, family: family, created_at: 6.months.ago) } + let!(:assessment_domain_1){ create(:assessment_domain, assessment: assessment_1, domain: domain_1, score: 1, reason:'test', goal: 'riri') } + let!(:assessment_domain_2){ create(:assessment_domain, assessment: assessment_2, domain: domain_1, score: 1, reason:'test', goal: 'riri') } + + before do + login_as(admin) + end + + feature 'Create', js: true do + + before do + visit new_client_care_plan_path(client.id, :assessment => assessment_1.id) + expect(page).to have_content('Score from assessment') + end + + scenario 'Incompleted for client', js: true do + click_link "Finish" + expect(page).to have_content('Incompleted') + end + + scenario 'Completed for client', js: true do + text_area = first(:css, 'textarea.goal-input-field').native + text_area.set('Test') + task = find('.task-input-field').native + task.set('Testing Task') + page.execute_script("$('#task_completion_date').val('2022-03-09')") + click_link "Finish" + expect(page).to have_content('Completed') + expect(page).to have_content("Care plan created on #{date_format(Date.today)}") + end + + scenario 'Incompleted for family', js: true do + visit new_family_care_plan_path(family.id, :assessment => assessment_2.id) + click_link "Finish" + expect(page).to have_content('Incompleted') + end + + scenario 'Completed for family', js: true do + visit new_family_care_plan_path(family.id, :assessment => assessment_2.id) + text_area = first(:css, 'textarea.goal-input-field').native + text_area.set('Test') + task = find('.task-input-field').native + task.set('Testing Task') + page.execute_script("$('#task_completion_date').val('2022-03-09')") + click_link "Finish" + expect(page).to have_content('Completed') + expect(page).to have_content("Care plan created on #{date_format(Date.today)}") + end + + scenario 'Task List should be increase by one after user click on button Add Task', js: true do + find('a', text: 'Add task', exact: true).click + expect(all('div.task-form.nested-fields').count).to be == 2 + expect(page).to have_content('Add goal') + end + + scenario 'Task List should be equal 1 by defauls', js: true do + expect(all('div.task-form.nested-fields').count).to be == 1 + expect(page).to have_content('Add goal') + end + + scenario 'Goal input type must be textarea and must be only one from the begining of loading page', js:true do + expect(all('textarea.goal-input-field').count).to be == 1 + end + + scenario 'Input fields should be valid all, Goal, Task, Task Date must be vaild and datatype as string', js: true do + headers = { "ACCEPT" => "application/json" } + find('.goal-input-field') + text_area = first(:css, 'textarea.goal-input-field').native + text_area.send_keys('Test') + find('.task-input-field').set('Testing Task') + find('.task-date-field').set('2022-03-09') + end + end +end + diff --git a/spec/features/case_note_spec.rb b/spec/features/case_note_spec.rb index 6f4ce1b7c4..ffb0fea07e 100644 --- a/spec/features/case_note_spec.rb +++ b/spec/features/case_note_spec.rb @@ -1,10 +1,15 @@ describe 'CaseNote' do let!(:user) { create(:user) } + let!(:admin) { create(:user, :admin) } + let!(:setting) { create(:setting) } + let!(:family) { create(:family, :active, user: user) } let!(:client) { create(:client, :accepted, users: [user]) } let!(:fc_case){ create(:case, case_type: 'FC', client: client) } let!(:domain){ create(:domain, name: '1A') } let!(:assessment){ create(:assessment, client: client) } + let!(:assessment_1){ create(:assessment, family: family) } let!(:assessment_domain){ create(:assessment_domain, assessment: assessment, domain: domain) } + let!(:assessment_domain_1){ create(:assessment_domain, assessment: assessment_1, domain: domain) } before do login_as(user) @@ -17,10 +22,11 @@ def add_tasks(n) (1..n).each do |time| - find('.case-note-task-btn').trigger('click') + find('.case-note-task-btn').click + expect(page).to have_content('New Task') fill_in 'task_name', with: 'ABC' fill_in 'task_completion_date', with: date_format(Date.strptime(FFaker::Time.date)) - find('.add-task-btn').trigger('click') + find('.add-task-btn').click sleep 1 end end @@ -29,27 +35,47 @@ def remove_task(index) page.all('.task-arising a.remove-task')[index].trigger('click') end - xscenario 'valid' do + scenario 'valid for client' do fill_in 'case_note_meeting_date', with: '2017-04-01' fill_in 'Who was there during the visit or conversation?', with: 'Jonh' find("#case_note_interaction_type option[value='Visit']", visible: false).select_option - fill_in 'Note', with: 'This is valid' - + fill_in 'case_note_note', with: 'This is valid' add_tasks(1) - find('#case-note-submit-btn').trigger('click') - + find('#case-note-submit-btn').click sleep 1 expect(page).to have_content('01 April 2017') expect(page).to have_content('Jonh') expect(page).to have_content('This is valid') - expect(Task.find_by(name: 'ABC').user_id).to eq(user.id) end - xscenario 'invalid' do - click_button 'Save' + scenario 'invalid for client' do + find('#case-note-submit-btn').click expect(page).to have_content("can't be blank") end + scenario 'valid for family' do + login_as(admin) + visit new_family_case_note_path(family) + expect(page).to have_content('Meeting detail') + fill_in 'case_note_meeting_date', with: '2017-04-01' + fill_in 'case_note_attendee', with: 'Testing User' + find("#case_note_interaction_type option[value='Visit']", visible: false).select_option + expect(page).to have_content('Visit') + fill_in 'case_note_note', with: 'This is valid' + add_tasks(1) + find('#case-note-submit-btn').click + expect(page).to have_content('01 April 2017') + expect(page).to have_content('Testing User') + expect(page).to have_content('This is valid') + end + + scenario 'invalid for family' do + login_as(admin) + visit new_family_case_note_path(family) + find('#case-note-submit-btn').click + expect(page).to have_css('.has-error') + end + context 'case notes permissions' do scenario 'user has editable permission' do expect(new_client_case_note_path(client)).to have_content(current_path) diff --git a/spec/features/family_spec.rb b/spec/features/family_spec.rb index ade8364222..54d223bdf5 100644 --- a/spec/features/family_spec.rb +++ b/spec/features/family_spec.rb @@ -1,241 +1,36 @@ -xdescribe 'Family' do - let!(:admin){ create(:user, roles: 'admin') } +describe 'Family' do + let!(:admin){ create(:user, :admin) } let!(:province){ create(:province, name:"Phnom Penh") } let!(:district){ create(:district, name: 'Toul Kork', province_id: province.id) } - - let!(:foster_family){ create(:family, :foster, name: 'A') } - let!(:case_worker_a){ create(:user, first_name: 'CW A') } - let!(:case_worker_b){ create(:user, first_name: 'CW B') } - let!(:case_worker_c){ create(:user, first_name: 'CW C') } - let!(:client_a){ create(:client, :accepted, users: [case_worker_a, case_worker_c]) } - let!(:client_b){ create(:client, :accepted, users: [case_worker_b]) } - let!(:case_a){ create(:case, :foster, client: client_a, family: foster_family) } - let!(:case_b){ create(:case, :foster, client: client_b, family: foster_family) } - let!(:client){ create(:client, :accepted) } - let!(:village_1){ create(:village, name_en: 'Wat Neak Kwan') } - let!(:commune_1){ create(:commune, name_en: 'Beoung Kak 2') } - let!(:family){ create(:family, :emergency, name: 'EC Family', province_id: province.id, district_id: district.id, commune_id: commune_1.id, village_id: village_1.id) } - let!(:other_family){ create(:family, name: 'Unknown', dependable_income: true) } - let!(:case){ create(:case, family: other_family) } - let!(:another_client){ create(:client, :accepted) } - let!(:other_client){ create(:client) } before do login_as(admin) end - feature 'List' do - before do - visit families_path - end - - xscenario 'case workers', js: true, skip: '=== consider changing retrieving data logic ===' do - first('td.case_workers a[href="#"]').click - expect(page).to have_content(case_worker_a.name) - expect(page).to have_content(case_worker_b.name) - expect(page).to have_content(case_worker_c.name) - end - - scenario 'name' do - expect(page).to have_content(family.name) - end - - scenario 'edit link' do - expect(page).to have_link(nil, edit_family_path(family)) - end - - xscenario 'delete link' do - expect(page).to have_css("a[href='#{family_path(family)}'][data-method='delete']") - end - - scenario 'show link' do - expect(page).to have_link(nil, family_path(family)) - end - - scenario 'new link' do - expect(page).to have_link('Add New Family', new_family_path) - end - end - feature 'Create', js: true do before do - family.update_attributes(children: [client.id]) visit new_family_path end scenario 'valid' do - fill_in 'Name', with: 'Family Name' - find(".family_province select option[value='#{province.id}']", visible: false).select_option - # find(".family_district select option[value='#{district.id}']", visible: false).select_option - fill_in 'Caregiver Information', with: 'Caregiver info' - find(".family_children select option[value='#{another_client.id}']", visible: false).select_option - find(".family_family_type select option[value='Short Term / Emergency Foster Care']", visible: false).select_option - find(".family_status select option[value='Active']", visible: false).select_option - click_button 'Save' + 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' + 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 + expect(page).to have_content('Family has been successfully created') expect(page).to have_content('Family Name') - # expect(page).to have_content("#{district.name}, #{province.name}") expect(page).to have_content(province.name) - expect(page).to have_content('Caregiver info') - expect(page).to have_content('Family has been successfully created') - expect(page).to have_content(another_client.given_name) - expect(page).not_to have_content(other_client.given_name) - end - - xscenario 'client must belong to only a family' do - find('.family_children').click - expect(page).not_to have_content(client.given_name) - # expect(page).to have_select('Clients', options: [another_client.en_and_local_name, other_client.en_and_local_name]) + expect(page).to have_content('Test') end scenario 'invalid' do - click_button 'Save' - expect(page).to have_content("can't be blank") - end - end - - feature 'Update', js: true do - let!(:pirunseng){ create(:client, :accepted, given_name: 'Pirun', family_name: 'Seng') } - # let!(:ec_family){ create(:family, :emergency, name: 'Emergency Family') } - let!(:non_case_family){ create(:family, family_type: ['Other', 'Birth Family (Both Parents)'].sample) } - let!(:non_case){ create(:case, case_type: 'Referred', client: pirunseng, family: non_case_family) } - # let!(:ec_case){ create(:case, :emergency, client: pirunseng, family: ec_family) } - let!(:another_family){ create(:family, :emergency) } - let!(:another_client){ create(:client, :accepted) } - - feature 'valid' do - before do - visit edit_family_path(non_case_family) - end - scenario 'name' do - fill_in 'Name', with: 'Family Name' - click_button 'Save' - expect(page).to have_content('Family Name') - end - end - - feature 'remove clients from' do - scenario 'birth or inactive family is valid' do - visit edit_family_path(non_case_family) - unselect('Pirun Seng', from: 'Clients', visible: false) - click_button 'Save' - expect(page).to have_content('Family has been successfully updated') - end - end - - scenario 'client must belong to only a family' do - visit edit_family_path(another_family) - find('.family_children').click - expect(page).not_to have_select('Clients', options: [pirunseng.en_and_local_name]) - end - end - - feature 'Delete', js: true do - before do - family.cases.delete_all - visit families_path - end - scenario 'success' do - find("a[href='#{family_path(family)}'][data-method='delete']").trigger('click') - page.has_css?('table.families') - expect(page).not_to have_css("a[href='#{family_path(family)}'][data-method='delete']") - end - scenario 'unsuccess' do - page.has_css?('table.families') - expect(page).to have_css("a[href='#{family_path(other_family)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") - end - end - - feature 'Show' do - before do - visit family_path(family) - end - scenario 'success' do - expect(page).to have_content(family.name) - end - scenario 'link to edit' do - expect(page).to have_link(nil, href: edit_family_path(family)) - end - scenario 'link to delete' do - expect(page).to have_css("a[href='#{family_path(family)}'][data-method='delete']") - end - scenario 'disable delete' do - visit family_path(other_family) - expect(page).to have_css("a[href='#{family_path(other_family)}'][data-method='delete'][class='btn btn-outline btn-danger btn-md disabled']") - end - end - - feature 'Filter', js: true do - before do - province = create(:province, name:"Phnom Penh") - District.find_or_create_by(name: 'Toul Kork', province_id: province.id) - Commune.find_or_create_by(name_en: 'Beoung Kak 2') - visit families_path - find('button.family-search').click - end - scenario 'filter by family type' do - page.find("#family-search-form select#family_grid_family_type option[value='Short Term / Emergency Foster Care']", visible: false).select_option - click_button 'Search' - expect(page).to have_content(family.name) - expect(page).not_to have_content(other_family) - end - - scenario 'filter by family like name' do - fill_in('family_grid_name',with: 'Family') - click_button 'Search' - expect(page).to have_content(family.name) - expect(page).not_to have_content(other_family) - end - - scenario 'filter by family id' do - fill_in('family_grid_id',with: family.id) - click_button 'Search' - expect(page).to have_content(family.name) - expect(page).not_to have_content(other_family) - end - - xscenario 'filter by family province' do - province_id = Province.find_by(name: 'Phnom Penh').id - page.find("#family-search-form select#family_grid_province_id option[value='#{province_id}']", visible: false).select_option - click_button 'Search' - - page.has_css?('table.families') - expect(page).to have_content(family.name) - expect(page).not_to have_content('Unknown') - end - - scenario 'filter by family district' do - district_id = District.find_by(name: 'Toul Kork').id - page.find("#family-search-form select#family_grid_district_id option[value='#{district_id}']", visible: false).select_option - click_button 'Search' - - page.has_css?('table.families') - expect(page).to have_content(family.name) - expect(page).not_to have_css("a[href='#{family_path(other_family)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") - end - - xscenario 'filter by family commune' do - commune_id = Commune.find_by(name_en: 'Beoung Kak 2').id - page.find("#family-search-form select#family_grid_commune_id option[value='#{commune_id}']", visible: false).select_option - click_button 'Search' - expect(page).to have_content(family.name) - expect(page).not_to have_content(other_family.name) - end - - scenario 'filter by family village' do - village_id = Village.find_by(name_en: 'Wat Neak Kwan').id - page.find("#family-search-form select#family_grid_village_id option[value='#{village_id}']", visible: false).select_option - click_button 'Search' - - page.has_css?('table.families') - expect(page).to have_content(family.name) - expect(page).not_to have_css("a[href='#{family_path(other_family)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") - end - - xscenario 'filter by family dependable income' do - page.find("#family-search-form select#family_grid_dependable_income option[value='NO']", visible: false).select_option - click_button 'Search' - expect(page).to have_content(family.name) - expect(page).not_to have_content(other_family.name) + click_link 'Next' + expect(page).to have_content("This field is required.") end end end diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb new file mode 100644 index 0000000000..23f5d93dd0 --- /dev/null +++ b/spec/features/login_spec.rb @@ -0,0 +1,26 @@ +describe 'Login' do + let!(:user){ create(:user) } + feature 'Login', js: true do + before do + visit '/users/sign_in' + expect(page). to have_content('Email', 'Password') + end + scenario 'valid' do + fill_in 'Email', with: user.email + fill_in 'Password', with: user.password + find('.btn-login').click + expect(page).to have_content('Signed in successfully.') + expect(page).to have_content(user.first_name) + end + scenario 'invalid' do + fill_in 'Email', with: user.email + fill_in 'Password', with: '1234' + find('.btn-login').click + expect(page).to have_content('Invalid email or password.') + end + scenario 'validation' do + find('.btn-login').click + expect(page).to have_content('Invalid email or password.') + end + end +end \ No newline at end of file diff --git a/spec/features/report_builder_spec.rb b/spec/features/report_builder_spec.rb new file mode 100644 index 0000000000..1b03bbbdcb --- /dev/null +++ b/spec/features/report_builder_spec.rb @@ -0,0 +1,24 @@ +describe 'Report Builder' do + let!(:user) { create(:user) } + let!(:client) { create(:client, :accepted, users: [user]) } + let!(:family){ create(:family, children: [client.id] ) } + before do + login_as(user) + end + + feature 'Search', js: true do + scenario 'for client' do + visit clients_path + find('.client-search').click + find('#client-grid-search-btn').click + sleep 1 + expect(page).to have_content(client.given_name) + end + scenario 'for family' do + visit families_path + find('#client-grid-search-btn').click + sleep 1 + expect(page).to have_content(family.name) + end + end +end \ No newline at end of file diff --git a/spec/features/start_page_spec.rb b/spec/features/start_page_spec.rb new file mode 100644 index 0000000000..42d8456f81 --- /dev/null +++ b/spec/features/start_page_spec.rb @@ -0,0 +1,15 @@ +describe 'Start Page' do + # let!(:organization){ create(:organization) } + + feature 'Show', js: true do + before do + visit '/' + end + scenario 'valid' do + expect(page).to have_content('Language') + expect(page).not_to have_content('Forgot your password?') + expect(page).to have_content('USAID Disclaimer') + expect(page).to have_css('.organization') + end + end +end \ No newline at end of file diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 128051f196..1e8248e39c 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -1,4 +1,4 @@ -xdescribe 'User' do +describe 'User' do let!(:admin){ create(:user, :admin, pin_number: '11223') } let!(:used_user){ create(:user) } let!(:user){ create(:user) } @@ -38,6 +38,31 @@ end end + feature 'Create' do + before do + login_as(admin) + visit new_user_path + expect(page).to have_content('New User') + end + scenario 'valid' do + fill_in 'First Name (Latin)', with: 'Testing' + fill_in 'Last Name (Latin)', with: 'User' + find('#user_gender option[value="female"]', visible: false).select_option + fill_in 'Email', with: 'test@gmail.com' + fill_in 'user[password]', with: '12345678' + fill_in 'user[password_confirmation]', with: '12345678' + find('#user_roles option[value="admin"]', visible: false).select_option + find('input[value="Save"]').click + expect(page).to have_content('Testing User') + expect(page).to have_content('test@gmail.com') + expect(page).to have_content('Female') + end + scenario 'invalid' do + find('input[value="Save"]').click + expect(page).to have_content("can't be blank") + end + end + feature 'Edit' do before do login_as(admin) @@ -105,7 +130,7 @@ end scenario 'does not succeed' do - expect(page).to have_css("a[href='#{user_path(used_user)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") + expect(page).to have_css("a[href='#{domain_path(used_user)}'][data-method='delete'][class='btn btn-outline btn-danger btn-xs disabled']") end end diff --git a/spec/requests/api/v1/client_duplicate_checker_spec.rb b/spec/requests/api/v1/client_duplicate_checker_spec.rb new file mode 100644 index 0000000000..de632529db --- /dev/null +++ b/spec/requests/api/v1/client_duplicate_checker_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Api::V1::ClientsController, type: :request do + # before do + # allow_any_instance_of(Client).to receive(:generate_random_char).and_return("abcd") + # end + + let(:user) { create(:user, :admin) } + let!(:clients) { create_list(:client, 5, users: [user]) } + + describe '#GET method: client to create new by endpoint api' do + context 'when user not loged in' do + before do + get '/api/v1/clients' + end + + it 'should return status 401' do + expect(response).to have_http_status(:unauthorized) + end + end + end + + + describe '#POST Method: completely created client in system by endpoint api' do + let!(:referral_source){ create(:referral_source) } + context 'when user not loged in' do + before do + post "/api/v1/clients" + end + + it 'should return status 401' do + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when user loged in' do + before do + sign_in(user) + end + + context 'when try to create client' do + before do + client = { format: 'json', client: { gender: 'male', given_name: "example", user_ids: [user.id], initial_referral_date: '2018-02-19', received_by_id: user.id, name_of_referee: FFaker::Name.name, referral_source_category_id: referral_source.id } } + post "/api/v1/clients", client, @auth_headers + end + + it 'should return status 200' do + expect(response).to have_http_status(:success) + end + + it 'should return correct data' do + expect(json['client']['given_name']).to eq("example") + end + end + end + end + + describe 'POST method: Register new client in duplicate checker' do + before do + + end + context 'Results' do + it 'should duplicate client' do + new_client = Client.new({id: 1000, code: "", given_name:clients.first.name}) + Client.all.each do |c| + if c.name == new_client.given_name + c.name.should == new_client.given_name + end + end + end + + it 'should not duplicate client' do + new_client = Client.new({id: 1000, code: "", given_name:"Foobar"}) + Client.all.each do |c| + if c.name == new_client.given_name + c.name.should_not == new_client.given_name + end + end + end + end + end +end diff --git a/spec/requests/api/v1/clients_controller_spec.rb b/spec/requests/api/v1/clients_controller_spec.rb index 66325416c8..27c2df3034 100644 --- a/spec/requests/api/v1/clients_controller_spec.rb +++ b/spec/requests/api/v1/clients_controller_spec.rb @@ -5,7 +5,7 @@ allow_any_instance_of(Client).to receive(:generate_random_char).and_return("abcd") end - let(:user) { create(:user) } + let(:user) { create(:user, :admin) } let!(:clients) { create_list(:client, 5, users: [user]) } describe 'GET #index' do @@ -59,14 +59,14 @@ end end - xcontext 'when user loged in' do + context 'when user loged in' do before do sign_in(user) end context 'when try to create client' do before do - client = { format: 'json', client: { gender: 'male', given_name: "example", user_ids: [user.id], initial_referral_date: '2018-02-19', received_by_id: user.id, name_of_referee: FFaker::Name.name, referral_source_id: referral_source.id } } + client = { format: 'json', client: { gender: 'male', given_name: "example", user_ids: [user.id], initial_referral_date: '2018-02-19', received_by_id: user.id, name_of_referee: FFaker::Name.name, referral_source_category_id: referral_source.id } } post "/api/v1/clients", client, @auth_headers end From 875506947893d1abf8e9f260079de7e149a43a6c Mon Sep 17 00:00:00 2001 From: lysa Date: Mon, 25 Apr 2022 16:00:55 +0700 Subject: [PATCH 0167/1114] [IMP] Add script for run test refs OSC-12 --- config/jenkins/uat/latest.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/jenkins/uat/latest.sh b/config/jenkins/uat/latest.sh index 20abb91f22..f2162b6dc8 100755 --- a/config/jenkins/uat/latest.sh +++ b/config/jenkins/uat/latest.sh @@ -1,3 +1,4 @@ #!/bin/bash -ex -exit 0 - +echo "Testing" +make db_create_test +make rspec \ No newline at end of file From f76a7f3c5ecea3178a31fa9e1072767ca7802a4a Mon Sep 17 00:00:00 2001 From: kirykr Date: Fri, 22 Apr 2022 22:23:41 +0700 Subject: [PATCH 0168/1114] fixed usage report mosvy referral --- app/services/ngo_usage_report.rb | 2 +- lib/tasks/fcf_report.rake | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/ngo_usage_report.rb b/app/services/ngo_usage_report.rb index 81040d73b6..9caa36ba42 100644 --- a/app/services/ngo_usage_report.rb +++ b/app/services/ngo_usage_report.rb @@ -316,7 +316,7 @@ def ngo_referral_client_info(beginning_of_month, end_of_month) end def mosvy_referral_client_info(beginning_of_month, end_of_month) - referrals = Referral.where(created_at: beginning_of_month..end_of_month).where(referred_from: 'MoSVY External System').where("ngo_name = ? OR ngo_name = ?", 'MoSVY', 'MoSVY External System') + referrals = Referral.where(created_at: beginning_of_month..end_of_month).where(referred_from: 'MoSVY External System') clients = Client.where(id: referrals.pluck(:client_id)) { mosvy_ngo_referred_client_count: referrals.count, diff --git a/lib/tasks/fcf_report.rake b/lib/tasks/fcf_report.rake index de6b627d54..229f36f092 100644 --- a/lib/tasks/fcf_report.rake +++ b/lib/tasks/fcf_report.rake @@ -7,7 +7,7 @@ namespace :fcf_report do format = workbook.add_format format.set_align('center') - (8..11).to_a.map{|seq| "2021-#{seq}-01" }.each do |date| + ['2021-12-01', '2022-01-01', '2022-02-01'].each do |date| clients_data = [] sheet_name = Date.parse(date).to_formatted_s(:long) worksheet = workbook.add_worksheet(sheet_name) @@ -20,6 +20,7 @@ namespace :fcf_report do client.created_at.to_formatted_s(:long), "#{full_name}(#{short_name})", client.initial_referral_date.to_formatted_s(:long), + ReferralSource.find(client.referral_source_category_id).try(:name), client.referral_source_name, client.status ] From fe517f2c843b3e48ad099fa5439b9d316dc1de18 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 26 Apr 2022 09:51:49 +0700 Subject: [PATCH 0169/1114] fixed solargraph code intellisense --- .solargraph.yml | 16 ++++++++++++++++ Dockerfile | 1 + Gemfile | 2 +- Gemfile.lock | 31 ++++++++++++++++++------------- 4 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 .solargraph.yml diff --git a/.solargraph.yml b/.solargraph.yml new file mode 100644 index 0000000000..6ec1845927 --- /dev/null +++ b/.solargraph.yml @@ -0,0 +1,16 @@ +--- +include: +- "**/*.rb" +exclude: +- node_modules/**/* +- spec/**/* +- test/**/* +- vendor/**/* +- ".bundle/**/*" +require: [] +domains: [] +reporters: +- require_not_found +require_paths: [] +plugins: [] +max_files: 5000 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c34345f19a..680fda777f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ COPY package.json yarn.lock ./ RUN gem install bundler -v 1.17.3 RUN bundle install --verbose --jobs 20 --retry 5 +RUN gem install solargraph -v 0.39.17 --force RUN npm install -g yarn RUN yarn install --check-files RUN service memcached start diff --git a/Gemfile b/Gemfile index 744d8a4b2b..f47c328d41 100644 --- a/Gemfile +++ b/Gemfile @@ -109,7 +109,7 @@ end group :development do gem 'letter_opener', '~> 1.4.1' gem 'letter_opener_web', '~> 1.3', '>= 1.3.4' - gem 'rubocop', '~> 0.47.1', require: false + gem 'rubocop', '~> 0.81.0', require: false gem 'capistrano', '3.9.0' gem 'capistrano-rails', '~> 1.1.1' gem 'capistrano-passenger', '~> 0.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index 65780c4314..025c5f0a8e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,7 +65,7 @@ GEM fog-core mime-types (>= 2.99) unf - ast (2.3.0) + ast (2.4.2) autoprefixer-rails (6.3.3.1) execjs autosize-rails (1.18.17) @@ -460,6 +460,7 @@ GEM nokogiri (>= 1.6) insensitive_hash (0.3.3) ipaddress (0.8.3) + jaro_winkler (1.5.4) jbuilder (2.4.1) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) @@ -588,9 +589,11 @@ GEM os (0.9.6) paper_trail (5.2.2) activerecord (>= 3.0, < 6.0) - request_store (~> 1.1) - parser (2.3.3.1) ast (~> 2.2) + request_store (~> 1.1) + parallel (1.19.2) + parser (2.7.2.0) + ast (~> 2.4.1) path_expander (1.1.0) pg (0.18.4) phantomjs (2.1.1.0) @@ -603,7 +606,6 @@ GEM cliver (~> 0.3.1) multi_json (~> 1.0) websocket-driver (>= 0.2.0) - powerpack (0.1.1) proxies (0.2.1) pry (0.10.3) coderay (~> 1.1.0) @@ -654,7 +656,7 @@ GEM activesupport (= 4.2.5) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.1) + rainbow (3.1.1) rake (13.0.6) rb-gravatar (1.0.5) rbvmomi (2.4.1) @@ -681,6 +683,7 @@ GEM responders (2.1.1) railties (>= 4.2.0, < 5.1) retriable (3.0.2) + rexml (3.2.5) roo (2.3.2) nokogiri (~> 1) rubyzip (~> 1.1, < 2.0.0) @@ -712,15 +715,17 @@ GEM rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.9.4) - rubocop (0.47.1) - parser (>= 2.3.3.1, < 3.0) - powerpack (~> 0.1) - rainbow (>= 1.99.1, < 3.0) + rubocop (0.81.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.7.0.1) + rainbow (>= 2.2.2, < 4.0) + rexml ruby-progressbar (~> 1.7) - unicode-display_width (~> 1.0, >= 1.0.1) + unicode-display_width (>= 1.4.0, < 2.0) ruby-graphviz (1.2.2) ruby-ole (1.2.12.2) - ruby-progressbar (1.8.1) + ruby-progressbar (1.11.0) ruby_parser (3.14.2) sexp_processor (~> 4.9) rubyzip (1.2.0) @@ -834,7 +839,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.6) - unicode-display_width (1.1.3) + unicode-display_width (1.8.0) uniform_notifier (1.10.0) warden (1.2.6) rack (>= 1.0) @@ -954,7 +959,7 @@ DEPENDENCIES rspec-activemodel-mocks (~> 1.1) rspec-rails (~> 4.0.0) rspec-sidekiq (~> 3.0, >= 3.0.3) - rubocop (~> 0.47.1) + rubocop (~> 0.81.0) ruby-ole (~> 1.2, >= 1.2.12.2) s3 sass-rails (~> 5.0) From 8c400d963aec16a6d1fd672031a171d56cc9d852 Mon Sep 17 00:00:00 2001 From: kirykr Date: Tue, 26 Apr 2022 16:32:02 +0700 Subject: [PATCH 0170/1114] 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 0171/1114] 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 0172/1114] 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 0173/1114] 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 254a5b2701913d5a700ff9b776a82f7d1f3d0232 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 28 Apr 2022 15:11:21 +0700 Subject: [PATCH 0174/1114] Fixed error in /helpers/advanced_search_helper.rb:175 --- app/helpers/advanced_search_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/advanced_search_helper.rb b/app/helpers/advanced_search_helper.rb index 3415c2f5b2..558de1f1b7 100644 --- a/app/helpers/advanced_search_helper.rb +++ b/app/helpers/advanced_search_helper.rb @@ -171,7 +171,7 @@ def format_header(key, group_name = 'client') active_clients: I18n.t('advanced_search.fields.active_clients'), care_plan: I18n.t('advanced_search.fields.care_plan'), **overdue_translations, - **custom_assessment_field_traslation_mapping + **custom_assessment_field_traslation_mapping, **address_translation(group_name) } From a277a7fef11195113f09cd8352596ce7cf388bb6 Mon Sep 17 00:00:00 2001 From: kirykr Date: Thu, 28 Apr 2022 19:05:22 +0700 Subject: [PATCH 0175/1114] 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 ee6d959ce1d5a0affcb1191e866667455177f794 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 3 May 2022 05:36:18 +0700 Subject: [PATCH 0176/1114] added free text type to CRD - client form --- .../quantitative_types/index.coffee | 18 +++++++ .../quantitative_case_fields.rb | 17 ++++--- .../quantitative_case_sql_builder.rb | 45 ++++++++++++----- app/controllers/api/clients_controller.rb | 22 +++++++++ .../quantitative_types_controller.rb | 2 + app/grids/client_grid.rb | 12 +++++ app/helpers/clients_helper.rb | 5 ++ .../components/Commons/inputs/select.js | 4 +- .../components/Commons/inputs/textarea.js | 10 ++-- .../components/NewClientForm/index.js | 47 +++++++++++++++--- .../NewClientForm/referralVulnerability.js | 48 +++++++++++++++---- app/models/client.rb | 4 ++ app/models/client_quantitative_case.rb | 5 +- app/models/client_quantitative_case_base.rb | 6 +++ .../client_quantitative_free_text_case.rb | 3 ++ app/models/quantitative_type.rb | 5 ++ app/views/clients/_form.html.haml | 2 +- app/views/clients/show.haml | 10 +++- app/views/datagrid/_form.html.haml | 7 +++ app/views/quantitative_types/_form.html.haml | 6 ++- app/views/quantitative_types/index.html.haml | 17 +++---- config/locales/en.yml | 5 ++ config/locales/km.yml | 5 ++ config/locales/my.yml | 5 ++ config/locales/ne.yml | 3 ++ ...12732_add_content_to_quantitative_types.rb | 6 +++ ...5_add_type_to_family_quantitative_cases.rb | 10 ++++ ...2_add_type_to_client_quantitative_cases.rb | 10 ++++ db/schema.rb | 15 +++++- 29 files changed, 299 insertions(+), 55 deletions(-) create mode 100644 app/models/client_quantitative_case_base.rb create mode 100644 app/models/client_quantitative_free_text_case.rb create mode 100644 db/migrate/20220501112732_add_content_to_quantitative_types.rb create mode 100644 db/migrate/20220501153405_add_type_to_family_quantitative_cases.rb create mode 100644 db/migrate/20220501153912_add_type_to_client_quantitative_cases.rb diff --git a/app/assets/javascripts/quantitative_types/index.coffee b/app/assets/javascripts/quantitative_types/index.coffee index eb7a44fb44..60037251f0 100644 --- a/app/assets/javascripts/quantitative_types/index.coffee +++ b/app/assets/javascripts/quantitative_types/index.coffee @@ -3,6 +3,7 @@ CIF.Quantitative_typesIndex = do -> _validateForm() _initSelect2() _initICheckBox() + _toggleFieldType() _initSelect2 = -> $('.select2').select2 @@ -17,6 +18,23 @@ CIF.Quantitative_typesIndex = do -> $('form.new_quantitative_type').validate ignore: null + _toggleFieldType = -> + console.log("trigger _toggleFieldType") + + $("form").on "change", ".quantitative_type_field_type select", -> + form = $(this).closest("form") + + if $(this).val() == 'select_option' + form.find(".quantitative_type_multiple-wrapper").show() + form.find(".add-quantitative-data-links").show() + form.find(".nested-fields").show() + else + form.find(".quantitative_type_multiple-wrapper").hide() + form.find(".add-quantitative-data-links").hide() + form.find(".nested-fields").hide() + + $("form .quantitative_type_field_type select").trigger("change") + _initICheckBox = -> $('.i-checks').iCheck checkboxClass: 'icheckbox_square-green' diff --git a/app/classes/advanced_searches/quantitative_case_fields.rb b/app/classes/advanced_searches/quantitative_case_fields.rb index fda1ecfc9f..396512f348 100644 --- a/app/classes/advanced_searches/quantitative_case_fields.rb +++ b/app/classes/advanced_searches/quantitative_case_fields.rb @@ -11,6 +11,7 @@ def initialize(user, visible_on = 'client') def render opt_group = format_header('quantitative') + if @user.admin? || @user.strategic_overviewer? quantitative_types = QuantitativeType.cach_by_visible_on(visible_on) else @@ -19,12 +20,16 @@ def render end quantitative_cases = quantitative_types.map do |qt| - AdvancedSearches::FilterTypes.drop_list_options( - "quantitative__#{qt.id}", - qt.name, - quantitative_cases(qt), - opt_group - ) + if qt.select_option? + AdvancedSearches::FilterTypes.drop_list_options( + "quantitative__#{qt.id}", + qt.name, + quantitative_cases(qt), + opt_group + ) + else + AdvancedSearches::FilterTypes.text_options("quantitative__#{qt.id}", qt.name, opt_group) + end end quantitative_cases.sort_by { |f| f[:label].downcase } diff --git a/app/classes/advanced_searches/quantitative_case_sql_builder.rb b/app/classes/advanced_searches/quantitative_case_sql_builder.rb index 3b6ad210f0..29467299f8 100644 --- a/app/classes/advanced_searches/quantitative_case_sql_builder.rb +++ b/app/classes/advanced_searches/quantitative_case_sql_builder.rb @@ -13,19 +13,42 @@ def initialize(objects, rule, klass_name = 'clients') def get_sql sql_string = "#{klass_name}.id IN (?)" quantitative = QuantitativeType.find_by(name: @field_value) - objects = @objects.joins(:quantitative_cases).where(quantitative_cases: { quantitative_type_id: quantitative.id }) - case @operator - when 'equal' - objects = objects.where(quantitative_cases: { id: @value }) - when 'not_equal' - objects = objects.where.not(quantitative_cases: { id: @value }) - when 'is_empty' - objects = @objects.where.not(id: objects.ids) - when 'is_not_empty' - objects + if quantitative.select_option? + + objects = @objects.joins(:quantitative_cases).where(quantitative_cases: { quantitative_type_id: quantitative.id }) + + case @operator + when 'equal' + objects = objects.where(quantitative_cases: { id: @value }) + when 'not_equal' + objects = objects.where.not(quantitative_cases: { id: @value }) + when 'is_empty' + objects = @objects.where.not(id: objects.ids) + when 'is_not_empty' + objects + end + {id: sql_string, values: objects.ids} + else + objects = @objects.joins(:client_quantitative_free_text_cases).where(client_quantitative_cases: { quantitative_type_id: quantitative.id }) + + case @operator + when 'equal' + objects = objects.where(client_quantitative_cases: { content: @value }) + when 'not_equal' + objects = objects.where.not(client_quantitative_cases: { content: @value }) + when 'contains' + objects = objects.where("LOWER(client_quantitative_cases.content) LIKE ?", "%#{@value&.downcase}%") + when 'not_contains' + objects = objects.where("client_quantitative_cases.content NOT LIKE ?", "%#{@value.downcase}%") + when 'is_empty' + objects = @objects.where.not(id: objects.ids) + when 'is_not_empty' + objects + end + + { id: sql_string, values: objects.ids } end - {id: sql_string, values: objects.ids} end end end diff --git a/app/controllers/api/clients_controller.rb b/app/controllers/api/clients_controller.rb index 5b5624f856..30937b9266 100644 --- a/app/controllers/api/clients_controller.rb +++ b/app/controllers/api/clients_controller.rb @@ -29,6 +29,7 @@ def assessments def create client_saved = false client = Client.new(client_params) + client.transaction do if params.dig(:referee, :id).present? referee = Referee.find(params.dig(:referee, :id)) @@ -52,6 +53,14 @@ def create end if client_saved + if params[:client_quantitative_free_text_cases].present? + params[:client_quantitative_free_text_cases].each do |client_qt_free_text_attr| + client_qt_free_text = client.client_quantitative_free_text_cases.find_or_initialize_by(quantitative_type_id: client_qt_free_text_attr[:quantitative_type_id]) + client_qt_free_text.content = client_qt_free_text_attr[:content] + client_qt_free_text.save + end + end + render json: { slug: client.slug, id: client.id }, status: :ok else render json: client.errors, status: :unprocessable_entity @@ -60,6 +69,7 @@ def create def update client = Client.find(params[:client][:id] || params[:id]) + if params[:client][:id] referee = Referee.find_or_create_by(id: client.referee_id) referee.update_attributes(referee_params) @@ -71,6 +81,14 @@ def update end if client.update_attributes(client_params.except(:referee_id, :carer_id)) + if params[:client_quantitative_free_text_cases].present? + params[:client_quantitative_free_text_cases].each do |client_qt_free_text_attr| + client_qt_free_text = client.client_quantitative_free_text_cases.find_or_initialize_by(quantitative_type_id: client_qt_free_text_attr[:quantitative_type_id]) + client_qt_free_text.content = client_qt_free_text_attr[:content] + client_qt_free_text.save + end + end + if params[:client][:assessment_id] assessment = Assessment.find(params[:client][:assessment_id]) else @@ -239,6 +257,10 @@ def referee_params ) end + def client_quantitative_free_text_cases_params + params.require(:client_quantitative_free_text_cases).permit() + end + def carer_params params.require(:carer).permit( :name, :phone, :outside, :address_type, :current_address, :email, :gender, :house_number, :street_number, :outside_address, :commune_id, :district_id, :province_id, :village_id, :client_relationship, :same_as_client, diff --git a/app/controllers/quantitative_types_controller.rb b/app/controllers/quantitative_types_controller.rb index 1b2bcbcd22..b713bfbf0e 100644 --- a/app/controllers/quantitative_types_controller.rb +++ b/app/controllers/quantitative_types_controller.rb @@ -47,6 +47,8 @@ def quantitative_type_params .permit( :name, :description, + :field_type, + :hint, :is_required, :multiple, visible_on: [], diff --git a/app/grids/client_grid.rb b/app/grids/client_grid.rb index 62b89eab8b..42b44780e3 100644 --- a/app/grids/client_grid.rb +++ b/app/grids/client_grid.rb @@ -523,6 +523,18 @@ def client_hotline_fields end end + dynamic do + quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) unless current_user.nil? + + QuantitativeType.with_field_type(:free_text).each do |qqt_free_text| + if current_user.nil? || quantitative_type_readable_ids.include?(qqt_free_text.id) + column(qqt_free_text.name.to_sym, class: 'quantitative-type', header: -> { qqt_free_text.name }, html: true) do |object| + object.client_quantitative_free_text_cases.where("quantitative_type_id = ?", qqt_free_text.id).pluck(:content).join(', ') + end + end + end + end + dynamic do quantitative_type_readable_ids = current_user.quantitative_type_permissions.readable.pluck(:quantitative_type_id) unless current_user.nil? quantitative_types = QuantitativeType.cached_quantitative_cases diff --git a/app/helpers/clients_helper.rb b/app/helpers/clients_helper.rb index b3935a8e20..df90eb28b2 100644 --- a/app/helpers/clients_helper.rb +++ b/app/helpers/clients_helper.rb @@ -1,4 +1,9 @@ module ClientsHelper + def get_or_build_client_quantitative_free_text_cases + QuantitativeType.where(field_type: 'free_text').map do |qtt| + @client.client_quantitative_free_text_cases.find_or_initialize_by(quantitative_type_id: qtt.id) + end + end def xeditable? client = nil (can?(:manage, client&.object) || can?(:edit, client&.object) || can?(:rud, client&.object)) ? true : false diff --git a/app/javascript/components/Commons/inputs/select.js b/app/javascript/components/Commons/inputs/select.js index 22bbb31ea1..cc8ef28744 100644 --- a/app/javascript/components/Commons/inputs/select.js +++ b/app/javascript/components/Commons/inputs/select.js @@ -58,6 +58,7 @@ export default props => { { inlineClassName && + hintText &&
    { data-html="true" data-placement="bottom" data-trigger="focus" - data-content="And here's some amazing content. It's very engaging. Right?" - data-content={ hintText || 'N/A' }> + data-content={ hintText }> } diff --git a/app/javascript/components/Commons/inputs/textarea.js b/app/javascript/components/Commons/inputs/textarea.js index 97da1e56e5..2d4ed4f881 100644 --- a/app/javascript/components/Commons/inputs/textarea.js +++ b/app/javascript/components/Commons/inputs/textarea.js @@ -4,12 +4,13 @@ export default props => { const { isError, hidden, label, required, onChange, T, value, hintText, inlineClassName, ...others } = props return (
    -