├── .browserslistrc ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── feature.yml │ └── task.yml ├── pull_request_template.md └── workflows │ ├── git-pr-release-action.yml │ ├── lint.yml │ ├── opened-issues-triage.yml │ └── test.yml ├── .gitignore ├── .prettierrc.json ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── dummy.svg │ │ ├── logo.svg │ │ ├── ogp.png │ │ ├── residence.svg │ │ └── salary.svg │ └── stylesheets │ │ └── application.css ├── controllers │ ├── admin │ │ ├── insurances_controller.rb │ │ └── pensions_controller.rb │ ├── api │ │ ├── admin │ │ │ ├── base_controller.rb │ │ │ ├── insurances_controller.rb │ │ │ └── pensions_controller.rb │ │ └── simulations_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── home_controller.rb ├── helpers │ ├── application_helper.rb │ └── meta_tags_helper.rb ├── javascript │ ├── packs │ │ └── application.js │ ├── src │ │ ├── App.vue │ │ ├── components │ │ │ ├── FormWizard.vue │ │ │ ├── Home.vue │ │ │ ├── Insurances.vue │ │ │ ├── LoadingAnimation.vue │ │ │ ├── NotFound.vue │ │ │ ├── Pensions.vue │ │ │ ├── ProgressBar.vue │ │ │ ├── ProgressStep.vue │ │ │ ├── SimulationForm.vue │ │ │ ├── SimulationResult.vue │ │ │ ├── SimulationResultError.vue │ │ │ └── simulation_form │ │ │ │ ├── Age.vue │ │ │ │ ├── EmploymentMonth.vue │ │ │ │ ├── InsuranceCompleteButton.vue │ │ │ │ ├── PostalCode.vue │ │ │ │ ├── PreviousSalary.vue │ │ │ │ ├── PreviousSocialInsurance.vue │ │ │ │ ├── RetirementMonth.vue │ │ │ │ ├── Salary.vue │ │ │ │ ├── ScheduledSalary.vue │ │ │ │ ├── ScheduledSocialInsurance.vue │ │ │ │ └── SocialInsurance.vue │ │ ├── composables │ │ │ ├── useFinancialYear.js │ │ │ ├── useFormat.js │ │ │ ├── useInsurances.js │ │ │ ├── usePensions.js │ │ │ ├── useToast.js │ │ │ └── useValidationSchema.js │ │ ├── insurances.js │ │ ├── local-gov-select.js │ │ ├── main.js │ │ ├── pensions.js │ │ ├── router │ │ │ └── router.js │ │ └── store │ │ │ ├── global.js │ │ │ └── simulation.js │ ├── stylesheets │ │ └── application.css │ └── test │ │ ├── mocks │ │ └── fileMock.js │ │ └── unit │ │ ├── components │ │ ├── InsuranceCompleteButton.spec.js │ │ └── ProgressBar.spec.js │ │ ├── composables │ │ ├── useFinancialYear.spec.js │ │ └── useValidationSchema.spec.js │ │ └── store │ │ └── simulation.spec.js ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ ├── local_tax_law.rb │ │ └── month_iterable.rb │ ├── insurance.rb │ ├── insurance_form.rb │ ├── payment_target_month.rb │ ├── pension.rb │ ├── simulation.rb │ ├── simulation │ │ ├── insurance.rb │ │ ├── insurance │ │ │ ├── base.rb │ │ │ ├── care.rb │ │ │ ├── elderly.rb │ │ │ └── medical.rb │ │ ├── parameter.rb │ │ ├── pension.rb │ │ ├── residence.rb │ │ ├── residence │ │ │ └── june_start_financial_year.rb │ │ └── salary.rb │ └── user.rb ├── validators │ ├── month_anyone_validator.rb │ └── required_salary_and_social_insurance_validator.rb └── views │ ├── admin │ ├── insurances │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ └── pensions │ │ ├── _form.html.slim │ │ ├── edit.html.slim │ │ ├── index.html.slim │ │ └── new.html.slim │ ├── api │ ├── admin │ │ ├── insurances │ │ │ └── index.json.jbuilder │ │ └── pensions │ │ │ └── index.json.jbuilder │ └── simulations │ │ └── show.json.jbuilder │ ├── application │ ├── _errors.html.slim │ └── _footer.html.slim │ ├── devise │ ├── passwords │ │ ├── edit.html.slim │ │ └── new.html.slim │ ├── registrations │ │ └── edit.html.slim │ ├── sessions │ │ └── new.html.slim │ └── shared │ │ ├── _error_messages.html.slim │ │ └── _links.html.slim │ ├── home │ ├── index.html.slim │ ├── privacy_policy.html.slim │ └── tos.html.slim │ └── layouts │ ├── application.html.slim │ ├── mailer.html.slim │ └── mailer.text.slim ├── babel.config.js ├── bin ├── bundle ├── cyopen ├── cypress ├── lint ├── rails ├── rake ├── setup ├── spring ├── test ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── fiscali.rb │ ├── inflections.rb │ ├── meta_tags.rb │ ├── mime_types.rb │ ├── new_framework_defaults_7_0.rb │ ├── permissions_policy.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ ├── en.yml │ └── ja.yml ├── puma.rb ├── routes.rb ├── slim_lint.yml ├── spring.rb ├── storage.yml ├── webpack │ ├── development.js │ ├── environment.js │ ├── loaders │ │ └── vue.js │ ├── production.js │ └── test.js └── webpacker.yml ├── cy-open.yml ├── cypress.yml ├── db ├── migrate │ ├── 20211222102420_create_active_storage_tables.active_storage.rb │ ├── 20211222102523_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20211222102524_create_active_storage_variant_records.active_storage.rb │ ├── 20211222102525_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb │ ├── 20211226135907_create_insurances.rb │ ├── 20211230081511_create_payment_target_months.rb │ ├── 20220109115534_remove_local_gov_code_from_payment_target_months.rb │ ├── 20220201011602_create_pentions.rb │ ├── 20220201025801_add_index_to_pentions.rb │ ├── 20220201053103_change_pention_to_pension.rb │ ├── 20220211122122_change_column_month_to_payment_target_month.rb │ ├── 20220309122700_devise_create_users.rb │ └── 20220402061638_delete_active_storage_tables.rb ├── schema.rb ├── seeds.rb └── seeds │ └── csv │ └── insurances.csv ├── docker-compose.yml ├── e2e ├── cypress.json └── cypress │ └── integration │ ├── router.spec.js │ └── simulation.spec.js ├── heroku.yml ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── factories │ ├── insurance_form.rb │ ├── insurances.rb │ ├── payment_target_months.rb │ ├── pensions.rb │ └── users.rb ├── models │ ├── concerns │ │ ├── local_tax_law_spec.rb │ │ └── month_iterable_spec.rb │ ├── insurance_form_spec.rb │ ├── insurance_spec.rb │ ├── pension_spec.rb │ ├── simulation │ │ ├── insurance │ │ │ ├── care_spec.rb │ │ │ ├── elderly_spec.rb │ │ │ └── medical_spec.rb │ │ ├── insurance_spec.rb │ │ ├── parameter_spec.rb │ │ ├── pension_spec.rb │ │ ├── residence │ │ │ └── june_start_financial_year_spec.rb │ │ ├── residence_spec.rb │ │ └── salary_spec.rb │ └── simulation_spec.rb ├── rails_helper.rb ├── requests │ └── api │ │ ├── admin │ │ ├── insurances_spec.rb │ │ └── pentions_spec.rb │ │ └── simulations_spec.rb ├── spec_helper.rb ├── support │ └── custom_validator_helper.rb ├── system │ ├── insurance_spec.rb │ └── pension_spec.rb └── validators │ └── required_salary_and_social_insurance_validator_spec.rb ├── storage └── .keep ├── tailwind.config.js ├── tmp ├── .keep └── pids │ └── .keep ├── vendor └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es2021": true, 5 | "jest/globals": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:vue/vue3-essential", 10 | "prettier" 11 | ], 12 | "parserOptions": { 13 | "ecmaVersion": 13, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "vue", 18 | "jest", 19 | "cypress" 20 | ], 21 | "rules": { 22 | "vue/multi-word-component-names": 0 23 | }, 24 | "globals": { 25 | "$ref": "readonly", 26 | "$$": "readonly", 27 | "$computed": "readonly", 28 | "defineProps": "readonly", 29 | "defineEmits": "readonly", 30 | "defineExpose": "readonly", 31 | "withDefaults": "readonly" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark the yarn lockfile as having been generated. 7 | yarn.lock linguist-generated 8 | 9 | # Mark any vendored files as having been vendored. 10 | vendor/* linguist-vendored 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: バグレポート 2 | description: バグを見つけたらレポートお願いします。 3 | assignees: IkumaTadokoro 4 | labels: ["バグ"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "バグ報告ありがとうございます!以下のフォームに入力をお願いします" 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: 概要 13 | description: 簡潔な説明 14 | validations: 15 | required: false 16 | - type: textarea 17 | id: reproduction 18 | attributes: 19 | label: 再現手順 20 | description: バグの再現手順 21 | placeholder: | 22 | 1. /fooにアクセス。 23 | 2. 「投稿」をクリック。 24 | 3. エラーが表示される。 25 | validations: 26 | required: false 27 | - type: textarea 28 | id: expected 29 | attributes: 30 | label: 期待される振る舞い 31 | description: 簡潔な説明 32 | validations: 33 | required: false 34 | - type: textarea 35 | id: screenshot 36 | attributes: 37 | label: スクリーンショット 38 | validations: 39 | required: false 40 | - type: textarea 41 | id: environment 42 | attributes: 43 | label: 環境 44 | placeholder: | 45 | - OS: [e.g. iOS] 46 | - ブラウザ: [e.g. chrome, safari] 47 | - バージョン: [e.g. 22] 48 | validations: 49 | required: false 50 | - type: textarea 51 | id: related_issues 52 | attributes: 53 | label: 関連Issue 54 | placeholder: "Ref: #3899" 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 新機能提案 2 | description: 新機能の提案はこちらから 3 | assignees: IkumaTadokoro 4 | labels: ["新機能"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "新機能提案ありがとうございます!以下のフォームに入力をお願いします" 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: 機能の説明 13 | description: 簡潔な説明 14 | validations: 15 | required: false 16 | - type: textarea 17 | id: reason 18 | attributes: 19 | label: なぜこの機能が必要なのか 20 | validations: 21 | required: false 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task.yml: -------------------------------------------------------------------------------- 1 | name: タスク 2 | description: タスクを追加する場合はこちらから 3 | assignees: IkumaTadokoro 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: "タスクの登録。以下のフォームを入力してください。" 8 | - type: textarea 9 | id: subtask 10 | attributes: 11 | label: サブタスク 12 | value: | 13 | - [ ] 14 | validations: 15 | required: false 16 | - type: textarea 17 | id: note 18 | attributes: 19 | label: メモ 20 | validations: 21 | required: false 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | PR提出前のチェックリスト: 5 | 6 | - [ ] PRの関心は**ただ一つ**だけになっている & 文法的に正しく、明確かつ完全なタイトルと本文になっている 7 | - [ ] [良いコミットメッセージ][1]を書いている 8 | - [ ] 関連issueがある場合、コミットメッセージに[Closing Keywords][2]を使っている 9 | - [ ] Featureブランチは最新版の`main`ブランチに追随している (そうでなければrebaseすること) 10 | - [ ] 関連するコミットはsquashした 11 | - [ ] テストを追加した 12 | - [ ] `bin/lint`と`bin/rspec`を実行した 13 | 14 | [1]: https://postd.cc/how-to-write-a-git-commit-message/ 15 | 16 | [2]: https://docs.github.com/ja/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository 17 | -------------------------------------------------------------------------------- /.github/workflows/git-pr-release-action.yml: -------------------------------------------------------------------------------- 1 | name: Create a release pull-request 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release_pull_request: 8 | runs-on: ubuntu-latest 9 | name: release_pull_request 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v3 13 | - name: create-release-pr 14 | uses: grassedge/git-pr-release-action@v1.0 15 | with: 16 | base: production 17 | head: main 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | labels: release 20 | assign: true 21 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Set up Ruby 15 | uses: ruby/setup-ruby@v1 16 | with: 17 | bundler-cache: true 18 | 19 | - name: Cache node modules 20 | uses: actions/cache@v2.1.4 21 | with: 22 | path: node_modules 23 | key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} 24 | restore-keys: | 25 | ${{ runner.os }}-node- 26 | - name: yarn install 27 | run: yarn install --check-files 28 | 29 | - name: Rubocop 30 | run: bundle exec rubocop 31 | 32 | - name: Slim Lint 33 | run: bundle exec slim-lint app/views -c config/slim_lint.yml 34 | 35 | - name: JS Lint 36 | run: bin/yarn lint 37 | -------------------------------------------------------------------------------- /.github/workflows/opened-issues-triage.yml: -------------------------------------------------------------------------------- 1 | name: Move new issues into Kanban 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | jobs: 7 | register: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: alex-page/github-project-automation-plus@v0.7.1 11 | with: 12 | project: Kanban 13 | column: インボックス 14 | repo-token: ${{ secrets.GHPROJECT_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | env: 9 | DOCKER_BUILDKIT: 1 10 | COMPOSE_DOCKER_CLI_BUILD: 1 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Setup docker 18 | shell: bash 19 | run: | 20 | docker-compose build 21 | docker-compose run web rails db:setup 22 | env: 23 | RAILS_ENV: test 24 | - name: webpacker 25 | run: | 26 | docker-compose run web yarn install 27 | docker-compose run web bundle exec rails webpacker:compile 28 | - name: Rspec 29 | run: | 30 | docker-compose run web bundle exec rspec 31 | env: 32 | RAILS_ENV: test 33 | - name: Jest 34 | run: docker-compose run web yarn test 35 | - name: Cypress 36 | run: | 37 | docker-compose up -d web 38 | docker-compose exec -T web bin/webpack 39 | docker-compose exec -T web rails s -d 40 | docker-compose up --exit-code-from cypress 41 | - uses: actions/upload-artifact@v3 42 | if: failure() 43 | with: 44 | name: cypress-screenshots 45 | path: e2e/cypress/screenshots 46 | - uses: actions/upload-artifact@v3 47 | if: always() 48 | with: 49 | name: cypress-videos 50 | path: e2e/cypress/videos 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore pidfiles, but keep the directory. 17 | /tmp/pids/* 18 | !/tmp/pids/ 19 | !/tmp/pids/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | 25 | /public/assets 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | /public/packs 32 | /public/packs-test 33 | /node_modules 34 | /yarn-error.log 35 | yarn-debug.log* 36 | .yarn-integrity 37 | 38 | # Cypress Artifacts 39 | /e2e/cypress/screenshots/ 40 | /e2e/cypress/videos/ 41 | 42 | # Ignore env file 43 | .env 44 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | "prettier-config-standard" 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require rails_helper 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-fjord: 3 | - config/rubocop.yml 4 | - config/rails.yml 5 | 6 | Metrics/BlockLength: 7 | Exclude: 8 | - spec/**/* 9 | 10 | Metrics/ClassLength: 11 | Exclude: 12 | - spec/**/* 13 | 14 | AllCops: 15 | Exclude: 16 | - '**/templates/**/*' 17 | - '**/vendor/**/*' 18 | - app/views/**/* 19 | - config/**/* 20 | - config.ru 21 | - node_modules/**/* 22 | - db/migrate/* 23 | - db/schema.rb 24 | - storage/**/* 25 | - tmp/**/* 26 | - bin/**/* 27 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.0.3 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0.3 2 | ARG RAILS_ENV=development 3 | ARG RAILS_SERVE_STATIC_FILES 4 | ENV RAILS_ENV=$RAILS_ENV 5 | ENV RAILS_SERVE_STATIC_FILES=$RAILS_SERVE_STATIC_FILES 6 | 7 | RUN curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 8 | RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - 9 | RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 10 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs postgresql-client yarn 11 | 12 | RUN mkdir /quitcost 13 | WORKDIR /quitcost 14 | COPY Gemfile /quitcost/Gemfile 15 | COPY Gemfile.lock /quitcost/Gemfile.lock 16 | 17 | RUN gem install bundler -v '2.2.31' 18 | RUN bundle install 19 | 20 | COPY package.json /quitcost/package.json 21 | COPY yarn.lock /quitcost/yarn.lock 22 | RUN yarn install 23 | 24 | COPY . /quitcost 25 | # Compile assets 26 | RUN if [ "$RAILS_ENV" = "production" ]; then SECRET_KEY_BASE=$(rake secret) bundle exec rake assets:precompile; fi 27 | 28 | EXPOSE 3000 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby '3.0.3' 7 | 8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' 9 | gem 'rails', '~> 7.0.0' 10 | # Use postgresql as the database for Active Record 11 | gem 'pg', '~> 1.1' 12 | # Use Puma as the app server 13 | gem 'puma', '~> 5.0' 14 | # Use SCSS for stylesheets 15 | gem 'html2slim' 16 | gem 'sass-rails', '>= 6' 17 | gem 'slim-rails' 18 | # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker 19 | gem 'webpacker', '~> 5.0' 20 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 21 | gem 'jbuilder', '~> 2.7' 22 | # Use Redis adapter to run Action Cable in production 23 | # gem 'redis', '~> 4.0' 24 | # Use Active Model has_secure_password 25 | # gem 'bcrypt', '~> 3.1.7' 26 | 27 | # Use Active Storage variant 28 | # gem 'image_processing', '~> 1.2' 29 | 30 | # Reduces boot times through caching; required in config/boot.rb 31 | gem 'bootsnap', '>= 1.4.4', require: false 32 | gem 'devise' 33 | gem 'devise-i18n' 34 | gem 'fiscali' 35 | gem 'jp_local_gov' 36 | gem 'kaminari' 37 | gem 'meta-tags' 38 | gem 'rails-i18n' 39 | 40 | group :development, :test do 41 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 42 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 43 | gem 'dotenv-rails' 44 | gem 'factory_bot_rails' 45 | gem 'rspec-rails' 46 | end 47 | 48 | group :development do 49 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 50 | gem 'web-console', '>= 4.1.0' 51 | # Display performance information such as SQL time and flame graphs for each request in your browser. 52 | # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md 53 | gem 'listen', '~> 3.3' 54 | gem 'rack-mini-profiler', '~> 2.0' 55 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 56 | gem 'letter_opener_web' 57 | gem 'rubocop', require: false 58 | gem 'rubocop-fjord', '~> 0.2.0', require: false 59 | gem 'rubocop-performance', require: false 60 | gem 'rubocop-rails', require: false 61 | gem 'rubocop-rspec', require: false 62 | gem 'slim_lint', require: false 63 | gem 'spring' 64 | end 65 | 66 | group :test do 67 | # Adds support for Capybara system testing and selenium driver 68 | gem 'capybara', '>= 3.26' 69 | gem 'selenium-webdriver' 70 | # Easy installation and use of web drivers to run system tests with browsers 71 | gem 'webdrivers' 72 | end 73 | 74 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 75 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quitcost 2 | 3 | ![image](https://user-images.githubusercontent.com/61409641/161411721-2c357460-910a-4674-854d-ff541f82963a.png) 4 | 5 | quitcostは**無職になったら**『**どんな**』個人負担が『**いくら**』増えるのかを簡単に知ることができるサービスです。 6 | 7 | 「転職のためにしばらく無職になる予定だけど、保険とか税金とか、何をいくら払えばいいかわからない!!」 8 | 9 | quitcostを使えば、年齢や住所、昨年の年収を回答することで、無職後に負担になる「税金」「保険」「年金」の料金を一括で知ることができます。 10 | 11 | ## 機能概要 12 | 13 | ![image](https://user-images.githubusercontent.com/61409641/161371308-b8ccce3d-5318-4069-ba8a-fc40b1537afc.png) 14 | 15 | - ユーザーはいくつかの質問に回答することで、無職期間にかかる「国民健康保険料」「国民年金」「住民税」の金額を計算することができます。 16 | - 管理者は計算に必要な「国民健康保険料の料率」と「国民年金保険料の料率」を一覧・登録・更新・削除することができます。 17 | 18 | ### スクリーンショット 19 | 20 | **ユーザー画面** 21 | 22 | CleanShot 2022-04-03 at 13 32 49@2x 23 | 24 | CleanShot 2022-04-03 at 13 33 58@2x 25 | 26 | ![CleanShot 2022-04-03 at 13 34 35@2x](https://user-images.githubusercontent.com/61409641/161411664-ad81f69d-c792-4a49-b169-6e3ade20b99a.png) 27 | 28 | **管理者画面** 29 | 30 | CleanShot 2022-04-03 at 13 35 14@2x 31 | 32 | CleanShot 2022-04-03 at 13 35 36@2x 33 | 34 | ## 利用方法 35 | 36 | ### ユーザー 37 | 38 | https://quitcost.herokuapp.com からご利用いただけます。 39 | 40 | 41 | ### 管理者 42 | 43 | `/users/sign_in`にアクセスし、管理者としてログインすることで、各種レコードの変更ができます。 44 | 45 | | key | value | 46 | |--|-------| 47 | | Eメール | quitcost@example.com | 48 | | パスワード | quitcost | 49 | 50 | ## インストール 51 | 52 | ```bash 53 | # コンテナを起動 54 | $ docker-compose up -d 55 | $ docker-compose exec web bash 56 | 57 | # 以下コンテナ内で実行 58 | $ bin/setup 59 | $ bin/rails s 60 | ``` 61 | 62 | ## テスト 63 | 64 | ### RSpec + Jest 65 | 66 | ```bash 67 | $ docker-compose exec web bash 68 | 69 | # 以下コンテナ内で実行 70 | $ bin/test 71 | ``` 72 | 73 | ### Cypress 74 | 75 | ```bash 76 | # CypressのDockerイメージを利用するため、コンテナ外で実行することに注意 77 | $ bin/cypress 78 | ``` 79 | 80 | ### Cypress Test Runnerを使用する 81 | 82 | Dockerコンテナ上で起動しているCypressをクライアント、ローカル環境をXサーバーとしてTest Runnerを投影します。 83 | 84 | 1. [XQuartz](https://www.xquartz.org/)をインストール 85 | 2. 環境設定 -> セキュリティから「ネットワーク・クライアントからの接続を許可」にチェックを入れる(XQuartz.appの再起動が必要) 86 | ![image](https://user-images.githubusercontent.com/61409641/161372070-e4bbce84-08ec-44e3-ae76-1335878814d5.png) 87 | 88 | 3. コンテナ外で`bin/cyopen`を実行。Test Runnerが立ち上がります 89 | 90 | ## Lint 91 | 92 | ```bash 93 | $ docker-compose exec web bash 94 | 95 | # 以下コンテナ内で実行 96 | $ bin/lint 97 | ``` 98 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/dummy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/app/assets/images/ogp.png -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/controllers/admin/insurances_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Admin::InsurancesController < ApplicationController 4 | before_action :authenticate_user! 5 | before_action :set_insurance, only: %i[edit update] 6 | 7 | def index; end 8 | 9 | def new 10 | @insurance_form = InsuranceForm.new 11 | return unless params[:id] 12 | 13 | copy_attribute 14 | end 15 | 16 | def create 17 | @insurance_form = InsuranceForm.new(**insurance_form_params) 18 | if @insurance_form.save 19 | redirect_to %i[admin insurances], notice: '保険料率を保存しました。' 20 | else 21 | render :new 22 | end 23 | end 24 | 25 | def edit 26 | @insurance_form = InsuranceForm.new(@insurance) 27 | end 28 | 29 | def update 30 | @insurance_form = InsuranceForm.new(@insurance, **insurance_form_params) 31 | if @insurance_form.save 32 | redirect_to %i[admin insurances], notice: '保険料率を更新しました。' 33 | else 34 | render :edit 35 | end 36 | end 37 | 38 | private 39 | 40 | def set_insurance 41 | @insurance = Insurance.find(params[:id]) 42 | end 43 | 44 | def insurance_form_params 45 | params.require(:insurance).permit( 46 | :year, 47 | :local_gov_code, 48 | :medical_income_basis, 49 | :medical_asset_basis, 50 | :medical_capita_basis, 51 | :medical_household_basis, 52 | :medical_limit, 53 | :elderly_income_basis, 54 | :elderly_asset_basis, 55 | :elderly_capita_basis, 56 | :elderly_household_basis, 57 | :elderly_limit, 58 | :care_income_basis, 59 | :care_asset_basis, 60 | :care_capita_basis, 61 | :care_household_basis, 62 | :care_limit, 63 | *InsuranceForm.calendars 64 | ) 65 | end 66 | 67 | def copy_attribute 68 | insurance = Insurance.find(params[:id]) 69 | payment_target_months = insurance.payment_target_months.map { _1.month.month } 70 | @insurance_form.local_gov_code = insurance.local_gov_code 71 | @insurance_form.medical_income_basis = insurance.medical_income_basis 72 | @insurance_form.medical_asset_basis = insurance.medical_asset_basis 73 | @insurance_form.medical_capita_basis = insurance.medical_capita_basis 74 | @insurance_form.medical_household_basis = insurance.medical_household_basis 75 | @insurance_form.medical_limit = insurance.medical_limit 76 | @insurance_form.elderly_income_basis = insurance.elderly_income_basis 77 | @insurance_form.elderly_asset_basis = insurance.elderly_asset_basis 78 | @insurance_form.elderly_capita_basis = insurance.elderly_capita_basis 79 | @insurance_form.elderly_household_basis = insurance.elderly_household_basis 80 | @insurance_form.elderly_limit = insurance.elderly_limit 81 | @insurance_form.care_income_basis = insurance.care_income_basis 82 | @insurance_form.care_asset_basis = insurance.care_asset_basis 83 | @insurance_form.care_capita_basis = insurance.care_capita_basis 84 | @insurance_form.care_household_basis = insurance.care_household_basis 85 | @insurance_form.care_limit = insurance.care_limit 86 | PaymentTargetMonth::CALENDAR.each_value { |num| @insurance_form.send("month#{num}=", payment_target_months.any? { |month| month == num }) } 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /app/controllers/admin/pensions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Admin::PensionsController < ApplicationController 4 | before_action :authenticate_user! 5 | before_action :set_pension, only: %i[edit update] 6 | 7 | def index; end 8 | 9 | def new 10 | @pension = Pension.new 11 | end 12 | 13 | def create 14 | @pension = Pension.new(pension_params) 15 | if @pension.save 16 | redirect_to %i[admin pensions], notice: '保険料率を保存しました。' 17 | else 18 | render :new 19 | end 20 | end 21 | 22 | def edit; end 23 | 24 | def update 25 | if @pension.update(pension_params) 26 | redirect_to %i[admin pensions], notice: '保険料率を更新しました。' 27 | else 28 | render :edit 29 | end 30 | end 31 | 32 | private 33 | 34 | def set_pension 35 | @pension = Pension.find(params[:id]) 36 | end 37 | 38 | def pension_params 39 | params.require(:pension).permit(:year, :contribution) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/controllers/api/admin/base_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::Admin::BaseController < ApplicationController 4 | before_action :admin? 5 | 6 | private 7 | 8 | def admin? 9 | return if user_signed_in? 10 | 11 | render json: { type: '/problems/authentication_required', title: 'ログインしてください' }, status: :unauthorized, content_type: 'application/problem+json' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/api/admin/insurances_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::Admin::InsurancesController < API::Admin::BaseController 4 | def index 5 | @insurances = Insurance 6 | .preload(:payment_target_months) 7 | .order(year: :desc) 8 | .order(:local_gov_code) 9 | .all 10 | .page(params[:page]) 11 | end 12 | 13 | def destroy 14 | insurance = Insurance.find(params[:id]) 15 | insurance.destroy 16 | render status: :ok, json: { message: 'Deleted the Insurance', data: insurance } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/api/admin/pensions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::Admin::PensionsController < API::Admin::BaseController 4 | def index 5 | @pensions = Pension.order(year: :desc).all.page(params[:page]) 6 | end 7 | 8 | def destroy 9 | pension = Pension.find(params[:id]) 10 | pension.destroy 11 | render status: :ok, json: { message: 'Deleted the Pension', data: pension } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/api/simulations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::SimulationsController < ApplicationController 4 | before_action :validate_parameter, only: :show 5 | 6 | def show 7 | @simulation = Simulation.new(parameter) 8 | end 9 | 10 | private 11 | 12 | def validate_parameter 13 | return if parameter.valid? 14 | 15 | render json: { 'errors': parameter.errors.full_messages }, status: :bad_request 16 | end 17 | 18 | def parameter 19 | @parameter ||= Simulation::Parameter.new(simulation_params) 20 | end 21 | 22 | def simulation_params 23 | params.permit( 24 | :retirement_month, 25 | :employment_month, 26 | :prefecture, 27 | :city, 28 | :age, 29 | :simulation_date, 30 | :previous_salary, 31 | :salary, 32 | :scheduled_salary, 33 | :previous_social_insurance, 34 | :social_insurance, 35 | :scheduled_social_insurance 36 | ) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | before_action :basic_auth, if: :staging? 5 | 6 | def basic_auth 7 | authenticate_or_request_with_http_basic do |user, password| 8 | user == ENV['BASIC_AUTH_USER'] && password == ENV['BASIC_AUTH_PASSWORD'] 9 | end 10 | end 11 | 12 | protected 13 | 14 | def staging? 15 | ENV['DB_NAME'] == 'quitcost_staging' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HomeController < ApplicationController 4 | def index; end 5 | 6 | def privacy_policy; end 7 | 8 | def tos; end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/meta_tags_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MetaTagsHelper 4 | def default_meta_tags # rubocop:disable Metrics/MethodLength 5 | { 6 | site: 'quitcost', 7 | title: 'quitcost', 8 | reverse: true, 9 | charset: 'utf-8', 10 | description: '「無職になったらいくらかかる?」をいますぐ計算! quitcost(クイットコスト)は転職準備でしばらく無職になる人のための、無職期間で『どんな』個人負担が『いくら』増えるのかを計算するサービスです', 11 | viewport: 'width=device-width, initial-scale=1.0', 12 | og: { 13 | title: :title, 14 | type: 'website', 15 | site_name: 'quitcost', 16 | description: :description, 17 | image: image_url('ogp.png'), 18 | url: 'https://quitcost.herokuapp.com/' 19 | }, 20 | twitter: { 21 | title: :title, 22 | card: 'summary_large_image', 23 | site: '@ikumatdkr', 24 | description: :description, 25 | image: image_url('ogp.png'), 26 | domain: 'https://quitcost.herokuapp.com/' 27 | } 28 | } 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | import Rails from '@rails/ujs' 7 | import 'stylesheets/application.css' 8 | import 'src/main.js' 9 | import 'src/insurances.js' 10 | import 'src/pensions.js' 11 | import 'src/local-gov-select.js' 12 | import { library, dom } from '@fortawesome/fontawesome-svg-core' 13 | import { 14 | faInfoCircle, 15 | faRobot, 16 | faEdit, 17 | faTrash, 18 | faClone 19 | } from '@fortawesome/free-solid-svg-icons' 20 | 21 | library.add(faInfoCircle, faRobot, faEdit, faTrash, faClone) 22 | dom.watch() 23 | Rails.start() 24 | -------------------------------------------------------------------------------- /app/javascript/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/javascript/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 84 | -------------------------------------------------------------------------------- /app/javascript/src/components/LoadingAnimation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/javascript/src/components/NotFound.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /app/javascript/src/components/ProgressBar.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | -------------------------------------------------------------------------------- /app/javascript/src/components/ProgressStep.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /app/javascript/src/components/SimulationForm.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /app/javascript/src/components/SimulationResultError.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 56 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/Age.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/EmploymentMonth.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/InsuranceCompleteButton.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 44 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/PostalCode.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 68 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/PreviousSalary.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/PreviousSocialInsurance.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 65 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/RetirementMonth.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 49 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/Salary.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 45 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/ScheduledSalary.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 50 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/ScheduledSocialInsurance.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 73 | -------------------------------------------------------------------------------- /app/javascript/src/components/simulation_form/SocialInsurance.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 61 | -------------------------------------------------------------------------------- /app/javascript/src/composables/useFinancialYear.js: -------------------------------------------------------------------------------- 1 | import { 2 | addMonths, 3 | subMonths, 4 | addYears, 5 | subYears, 6 | lastDayOfMonth 7 | } from 'date-fns' 8 | 9 | export const useFinancialYear = ( 10 | date, 11 | beginning_of_fiscal_year = 1, 12 | date_beginning_of_fiscal_year = beginning_of_fiscal_year 13 | ) => { 14 | const year = 15 | date_beginning_of_fiscal_year === 1 16 | ? date.getFullYear() 17 | : subMonths(date, date_beginning_of_fiscal_year - 1).getFullYear() 18 | const beginningOfYear = new Date(year, beginning_of_fiscal_year - 1, 1) 19 | const nextBeginningOfYear = addYears(beginningOfYear, 1) 20 | const afterNextBeginningOfYear = addYears(beginningOfYear, 2) 21 | const lastBeginningOfYear = subYears(beginningOfYear, 1) 22 | const beforeLastBeginningOfYear = subYears(beginningOfYear, 2) 23 | const endOfYear = lastDayOfMonth(addMonths(beginningOfYear, 11)) 24 | const nextEndOfYear = addYears(endOfYear, 1) 25 | const lastEndOfYear = subYears(endOfYear, 1) 26 | const beforeLastEndOfYear = subYears(endOfYear, 2) 27 | 28 | return { 29 | beginningOfYear, 30 | nextBeginningOfYear, 31 | afterNextBeginningOfYear, 32 | lastBeginningOfYear, 33 | beforeLastBeginningOfYear, 34 | endOfYear, 35 | nextEndOfYear, 36 | lastEndOfYear, 37 | beforeLastEndOfYear, 38 | year 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/javascript/src/composables/useFormat.js: -------------------------------------------------------------------------------- 1 | import { useCurrencyFormat, useIntlNumberFormat } from 'vue-composable' 2 | 3 | export function useFormat() { 4 | const lang = 'jp' 5 | const yenOption = { currency: 'JPY', currencyDisplay: 'symbol' } 6 | const percentOption = { style: 'decimal', minimumFractionDigits: 2 } 7 | const { formatString: formatYen } = useCurrencyFormat(yenOption, lang) 8 | const { formatString: formatPercent } = useIntlNumberFormat( 9 | percentOption, 10 | lang 11 | ) 12 | 13 | return { 14 | formatYen, 15 | formatPercent 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/javascript/src/composables/useInsurances.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const useInsurances = () => { 4 | const insurances = ref([]) 5 | const totalPages = ref(0) 6 | 7 | const getInsurances = async (query) => { 8 | const insurancesAPI = `/api/admin/insurances.json?${query}` 9 | const response = await fetch(insurancesAPI, { 10 | method: 'GET', 11 | headers: { 'X-Requested-With': 'XMLHttpRequest' }, 12 | credentials: 'same-origin', 13 | redirect: 'manual' 14 | }) 15 | const json = await response 16 | .json() 17 | .catch((e) => console.warn('Failed to parsing', e)) 18 | insurances.value = json.insurance 19 | totalPages.value = parseInt(json.totalPages) 20 | } 21 | 22 | const token = () => { 23 | const meta = document.querySelector('meta[name="csrf-token"]') 24 | return meta ? meta.getAttribute('content') : '' 25 | } 26 | 27 | const deleteInsurance = async (insuranceId) => { 28 | const insuranceAPI = `/api/admin/insurances/${insuranceId}` 29 | await fetch(insuranceAPI, { 30 | method: 'DELETE', 31 | headers: { 32 | 'X-Requested-With': 'XMLHttpRequest', 33 | 'X-CSRF-Token': token() 34 | }, 35 | credentials: 'same-origin', 36 | redirect: 'manual' 37 | }) 38 | } 39 | 40 | return { 41 | insurances, 42 | totalPages, 43 | getInsurances, 44 | deleteInsurance 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/javascript/src/composables/usePensions.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export const usePensions = () => { 4 | const pensions = ref([]) 5 | const totalPages = ref(0) 6 | 7 | const getPensions = async (query) => { 8 | const pensionsAPI = `/api/admin/pensions.json?${query}` 9 | const response = await fetch(pensionsAPI, { 10 | method: 'GET', 11 | headers: { 'X-Requested-With': 'XMLHttpRequest' }, 12 | credentials: 'same-origin', 13 | redirect: 'manual' 14 | }) 15 | const json = await response 16 | .json() 17 | .catch((e) => console.warn('Failed to parsing', e)) 18 | pensions.value = json.pensions 19 | totalPages.value = parseInt(json.totalPages) 20 | } 21 | 22 | const token = () => { 23 | const meta = document.querySelector('meta[name="csrf-token"]') 24 | return meta ? meta.getAttribute('content') : '' 25 | } 26 | 27 | const deletePension = async (pensionId) => { 28 | const pensionAPI = `/api/admin/pensions/${pensionId}` 29 | await fetch(pensionAPI, { 30 | method: 'DELETE', 31 | headers: { 32 | 'X-Requested-With': 'XMLHttpRequest', 33 | 'X-CSRF-Token': token() 34 | }, 35 | credentials: 'same-origin', 36 | redirect: 'manual' 37 | }) 38 | } 39 | 40 | return { 41 | pensions, 42 | totalPages, 43 | getPensions, 44 | deletePension 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/javascript/src/composables/useToast.js: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2' 2 | 3 | export const useToast = () => { 4 | const toast = (title) => { 5 | Swal.fire({ 6 | title: title, 7 | toast: true, 8 | position: 'top-end', 9 | showConfirmButton: false, 10 | timer: 3000, 11 | timerProgressBar: true 12 | }) 13 | } 14 | 15 | return { toast } 16 | } 17 | -------------------------------------------------------------------------------- /app/javascript/src/composables/useValidationSchema.js: -------------------------------------------------------------------------------- 1 | import { object, string, number, date, ref } from 'yup' 2 | import { useFinancialYear } from './useFinancialYear' 3 | import { format } from 'date-fns' 4 | 5 | export const useValidationSchema = (baseDate) => { 6 | const { afterNextBeginningOfYear } = useFinancialYear(baseDate, 4) 7 | const from = format(baseDate, 'yyyy-MM') 8 | const to = format(afterNextBeginningOfYear, 'yyyy-MM') 9 | 10 | const numberPresence = (columnName) => { 11 | return number() 12 | .transform((value) => (isNaN(value) ? undefined : value)) 13 | .required(`${columnName}は必須です`) 14 | .typeError('無効な数値です。') 15 | } 16 | 17 | const datePresence = (columnName) => { 18 | return date() 19 | .nullable() 20 | .transform((value, original) => (original === '' ? null : value)) 21 | .required(`${columnName}は必須です`) 22 | .typeError('無効な日付形式です。') 23 | } 24 | 25 | const RetirementMonth = object({ 26 | retirementMonth: datePresence('退職予定月') 27 | .min(from, `退職予定月には ${from} 以降の月を指定してください`) 28 | .max(to, `退職予定月には ${to} 以前の月を指定してください`) 29 | }) 30 | 31 | const EmploymentMonth = object({ 32 | retirementMonth: date(), 33 | employmentMonth: datePresence('転職予定月') 34 | .min( 35 | ref('retirementMonth'), 36 | `転職予定月には、退職予定月以降の月を指定してください` 37 | ) 38 | .max(to, `転職予定月には ${to} 以前の月を指定してください`) 39 | }) 40 | 41 | const Age = object({ 42 | age: numberPresence('年齢') 43 | .min(0, '0以上の整数を入力してください') 44 | .integer('整数で入力してください') 45 | }) 46 | 47 | const PostalCode = object({ 48 | postalCode: string() 49 | .matches(/^[0-9]{3}-[0-9]{4}$/, { 50 | message: '7桁の郵便番号を入力してください', 51 | excludeEmptyString: true 52 | }) 53 | .required('郵便番号は必須です'), 54 | address: string().required('該当する市区町村がありません') 55 | }) 56 | 57 | const PreviousSalary = object({ 58 | previousSalary: numberPresence('昨昨年度の所得') 59 | .min(0, '0以上の整数を入力してください') 60 | .integer('整数で入力してください') 61 | }) 62 | 63 | const PreviousSocialInsurance = object({ 64 | previousSocialInsurance: numberPresence('昨昨年度の社会保険料') 65 | .min(0, '0以上の整数を入力してください') 66 | .integer('整数で入力してください') 67 | }) 68 | 69 | const Salary = object({ 70 | salary: numberPresence('昨年度の所得') 71 | .min(0, '0以上の整数を入力してください') 72 | .integer('整数で入力してください') 73 | }) 74 | 75 | const SocialInsurance = object({ 76 | socialInsurance: numberPresence('昨年度の社会保険料') 77 | .min(0, '0以上の整数を入力してください') 78 | .integer('整数で入力してください') 79 | }) 80 | 81 | const ScheduledSalary = object({ 82 | scheduledSalary: numberPresence('今年度の所得') 83 | .min(0, '0以上の整数を入力してください') 84 | .integer('整数で入力してください') 85 | }) 86 | 87 | const ScheduledSocialInsurance = object({ 88 | scheduledSocialInsurance: numberPresence('今年度の社会保険料') 89 | .min(0, '0以上の整数を入力してください') 90 | .integer('整数で入力してください') 91 | }) 92 | 93 | const validationSchema = { 94 | RetirementMonth, 95 | EmploymentMonth, 96 | Age, 97 | PostalCode, 98 | PreviousSalary, 99 | PreviousSocialInsurance, 100 | Salary, 101 | SocialInsurance, 102 | ScheduledSalary, 103 | ScheduledSocialInsurance 104 | } 105 | 106 | return validationSchema 107 | } 108 | -------------------------------------------------------------------------------- /app/javascript/src/insurances.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Insurances from './components/Insurances.vue' 3 | 4 | document.addEventListener('DOMContentLoaded', () => { 5 | const selector = '#js-insurances' 6 | const insurances = document.querySelector(selector) 7 | if (insurances) { 8 | const app = createApp(Insurances) 9 | app.mount(selector) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /app/javascript/src/local-gov-select.js: -------------------------------------------------------------------------------- 1 | import Choices from 'choices.js' 2 | 3 | document.addEventListener('DOMContentLoaded', () => { 4 | const element = document.getElementById('js-local-gov-select') 5 | if (element) { 6 | return new Choices(element, { 7 | removeItemButton: true, 8 | allowHTML: true, 9 | shouldSort: false, 10 | searchResultLimit: 5, 11 | searchPlaceholderValue: '市区町村名を入力してください', 12 | noResultsText: '一致する情報は見つかりません', 13 | itemSelectText: '選択' 14 | }) 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /app/javascript/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import Maska from 'maska' 4 | import router from './router/router' 5 | import globalStore, { GlobalStoreKey } from './store/global' 6 | 7 | document.addEventListener('DOMContentLoaded', () => { 8 | const selector = '#app' 9 | const app = document.querySelector(selector) 10 | if (app) { 11 | const app = createApp(App) 12 | app.use(Maska) 13 | app.use(router) 14 | app.provide(GlobalStoreKey, globalStore()) 15 | app.mount(selector) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /app/javascript/src/pensions.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Pensions from './components/Pensions.vue' 3 | 4 | document.addEventListener('DOMContentLoaded', () => { 5 | const selector = '#js-pensions' 6 | const pensions = document.querySelector(selector) 7 | if (pensions) { 8 | const app = createApp(Pensions) 9 | app.mount(selector) 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /app/javascript/src/store/global.js: -------------------------------------------------------------------------------- 1 | import { inject } from 'vue' 2 | import simulationStore from './simulation' 3 | 4 | export default function globalStore() { 5 | return { 6 | simulation: simulationStore() 7 | } 8 | } 9 | 10 | export const GlobalStoreKey = 'GlobalStore' 11 | 12 | export function useGlobalStore() { 13 | const store = inject(GlobalStoreKey) 14 | if (!store) { 15 | throw new Error(`${GlobalStoreKey} is not provided`) 16 | } 17 | return store 18 | } 19 | -------------------------------------------------------------------------------- /app/javascript/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@700&display=swap'); 2 | @import url('choices.js/public/assets/styles/choices.min.css'); 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | .admin-form-label { 8 | @apply leading-7 text-sm 9 | } 10 | 11 | .admin-form-input { 12 | @apply w-full rounded-md border-2 border-boundaryBlack focus:border-primary text-base outline-none py-1 px-3 leading-8 transition-colors duration-200 ease-in-out 13 | } 14 | 15 | .admin-form-submit { 16 | @apply text-white bg-primary border-0 py-2 px-4 focus:outline-none hover:bg-green-900 rounded-full text-sm 17 | } 18 | 19 | .admin-table-header { 20 | @apply px-4 py-4 whitespace-nowrap; 21 | } 22 | 23 | .admin-table-data-center { 24 | @apply border-t border-boundaryBlack px-2 py-2 text-center; 25 | } 26 | 27 | .admin-table-data-right { 28 | @apply border-t border-boundaryBlack px-2 py-2 text-right; 29 | } 30 | 31 | .choices__inner { 32 | @apply p-0 bg-white border-white inline-flex items-center min-h-fit text-base; 33 | } 34 | 35 | .choices[data-type*=select-one] .choices__inner { 36 | @apply p-0; 37 | } 38 | 39 | .is-focused .choices__inner { 40 | @apply border-white; 41 | } 42 | 43 | .choices__list--single { 44 | @apply p-0; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /app/javascript/test/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = '' 2 | -------------------------------------------------------------------------------- /app/javascript/test/unit/components/InsuranceCompleteButton.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import InsuranceCompleteButton from 'components/simulation_form/InsuranceCompleteButton' 3 | 4 | describe('InsuranceCompleteButton', () => { 5 | describe('emit', () => { 6 | describe('when InsuranceCompleteButton is clicked', () => { 7 | describe('when props:salary is 0', () => { 8 | it('fires completeInsurance event with insurance: 0', async () => { 9 | const wrapper = shallowMount(InsuranceCompleteButton, { 10 | props: { 11 | salary: 0 12 | } 13 | }) 14 | 15 | await wrapper 16 | .get(`[data-test-id="completeInsuranceButton"]`) 17 | .trigger('click') 18 | 19 | const emit = wrapper.emitted() 20 | 21 | expect(emit).toHaveProperty('completeInsurance') 22 | 23 | expect(emit['completeInsurance'][0]).toEqual([0]) 24 | }) 25 | }) 26 | 27 | describe('when props:salary is NOT 0', () => { 28 | it('fires completeInsurance event with insurance which is 15% of the salary rounded down to the nearest 100 yen', async () => { 29 | const wrapper = shallowMount(InsuranceCompleteButton, { 30 | props: { 31 | salary: 4578921 32 | } 33 | }) 34 | 35 | await wrapper 36 | .get(`[data-test-id="completeInsuranceButton"]`) 37 | .trigger('click') 38 | 39 | const emit = wrapper.emitted() 40 | 41 | expect(emit).toHaveProperty('completeInsurance') 42 | 43 | // 4578921 * 15% = 686831.15 44 | expect(emit['completeInsurance'][0]).toEqual([686800]) 45 | }) 46 | }) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /app/javascript/test/unit/components/ProgressBar.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import ProgressBar from 'components/ProgressBar' 3 | 4 | describe('ProgressBar', () => { 5 | describe('props', () => { 6 | describe('when top is 0', () => { 7 | it('should show only steps bar', () => { 8 | const wrapper = shallowMount(ProgressBar, { 9 | props: { 10 | top: 0, 11 | bottom: 10 12 | } 13 | }) 14 | 15 | expect(wrapper.find(`[data-test-id="Progress"]`).exists()).toBe(false) 16 | expect(wrapper.find(`[data-test-id="Steps"]`).exists()).toBe(true) 17 | }) 18 | }) 19 | 20 | describe('when top is NOT 0 and top is smaller than bottom', () => { 21 | const wrapper = shallowMount(ProgressBar, { 22 | props: { 23 | top: 3, 24 | bottom: 10 25 | } 26 | }) 27 | 28 | it('should show only progress bar', () => { 29 | expect(wrapper.find(`[data-test-id="Progress"]`).exists()).toBe(true) 30 | expect(wrapper.find(`[data-test-id="Steps"]`).exists()).toBe(false) 31 | }) 32 | 33 | it('should calculate progress by top/bottom', () => { 34 | expect( 35 | wrapper.get(`[data-test-id="Progress"]`).attributes().style 36 | ).toContain('width: 30%') // 3 / 10 * 100 = 30% 37 | }) 38 | }) 39 | 40 | describe('when top is bottom', () => { 41 | const wrapper = shallowMount(ProgressBar, { 42 | props: { 43 | top: 10, 44 | bottom: 10 45 | } 46 | }) 47 | 48 | it('should show only progress bar', () => { 49 | expect(wrapper.find(`[data-test-id="Progress"]`).exists()).toBe(true) 50 | expect(wrapper.find(`[data-test-id="Steps"]`).exists()).toBe(false) 51 | }) 52 | 53 | it('should calculate progress by top/bottom, and its result is 100%', () => { 54 | expect( 55 | wrapper.get(`[data-test-id="Progress"]`).attributes().style 56 | ).toContain('width: 100%') // 10 / 10 * 100 = 100% 57 | }) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /app/javascript/test/unit/store/simulation.spec.js: -------------------------------------------------------------------------------- 1 | import simulationStore from 'store/simulation' 2 | 3 | describe('#add_params', () => { 4 | const mockDate = new Date('2022-03-15') 5 | jest.spyOn(global, 'Date').mockImplementation(() => mockDate) 6 | const simulation = simulationStore() 7 | 8 | it('add value at the end of params', () => { 9 | expect(simulation.params).toEqual({ 10 | simulationDate: mockDate 11 | }) 12 | 13 | const values = { employmentMonth: '2022/03' } 14 | simulation.add_params(values) 15 | expect(simulation.params).toEqual({ 16 | simulationDate: mockDate, 17 | employmentMonth: '2022/03' 18 | }) 19 | }) 20 | }) 21 | 22 | describe('#reset', () => { 23 | const mockDate = new Date('2022-03-15') 24 | jest.spyOn(global, 'Date').mockImplementation(() => mockDate) 25 | const simulation = simulationStore() 26 | 27 | it('reset params, result, routes and currentStep', () => { 28 | simulation.reset() 29 | expect(simulation.params).toEqual({ simulationDate: mockDate }) 30 | expect(simulation.steps).toEqual(10) 31 | expect(simulation.currentStepIdx).toEqual(0) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: 'from@example.com' 5 | layout 'mailer' 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/concerns/local_tax_law.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LocalTaxLaw 4 | extend self 5 | 6 | # 第二十条の四の二 第一項 7 | def calc_tax_base(&process) 8 | result = yield process 9 | result.floor(-3) 10 | end 11 | 12 | # 第二十条の四の二 第三項 13 | # 住民税と国民健康保険は「政令で定める地方税」に相当しないため簡素化して実装 14 | def calc_determined_amount(&process) 15 | result = yield process 16 | result.floor(-2) 17 | end 18 | 19 | # 第二十条の四の二 第六項 20 | def calc_installments(total, dues, municipal_ordinance: false, special_insurance: false) 21 | number_of_payments = dues.size 22 | digits = calc_digits(municipal_ordinance, special_insurance) 23 | not_first_month = (total / number_of_payments).floor(digits) 24 | first_month = total - not_first_month * (number_of_payments - 1) 25 | [first_month, Array.new(number_of_payments - 1) { not_first_month }].flatten 26 | end 27 | 28 | private 29 | 30 | # 第二十条の四の二 第九項(特別徴収の国民保険)および第二十条の四の二 第六項(条例の定める範囲) 31 | def calc_digits(municipal_ordinance, special_insurance) 32 | municipal_ordinance || special_insurance ? -2 : -3 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/models/concerns/month_iterable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MonthIterable 4 | def months_between(from:, to:) 5 | Enumerator.produce(from, &:next_month).take_while { |date| date < to } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/insurance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Insurance < ApplicationRecord 4 | include JpLocalGov 5 | jp_local_gov :local_gov_code 6 | 7 | paginates_per 20 8 | 9 | has_many :payment_target_months, dependent: :destroy do 10 | # 責務的にはPaymentTargetMonthにおいた方がいいと思うが、おそらくCollectionProxyに対する`any?`ではなくなるため、 11 | # クエリ発行回数が増える。そのため一旦はhas_manyのブロック内で定義する 12 | PaymentTargetMonth::CALENDAR.each do |month_name, month_num| 13 | define_method "#{month_name}_is_target?" do 14 | any? { |row| row.month.month == month_num } 15 | end 16 | end 17 | end 18 | 19 | def self.rate(year:, local_gov_code:) 20 | prefecture_capital_code = JpLocalGov.where(prefecture: JpLocalGov.find(local_gov_code).prefecture, prefecture_capital: true).first.code 21 | 22 | if exists?(year: year, local_gov_code: local_gov_code) 23 | find_by(year: year, local_gov_code: local_gov_code) 24 | elsif exists?(local_gov_code: local_gov_code) 25 | maximum_year = where(local_gov_code: local_gov_code).maximum(:year) 26 | find_by(year: maximum_year, local_gov_code: local_gov_code) 27 | elsif exists?(year: year, local_gov_code: prefecture_capital_code) 28 | find_by(year: year, local_gov_code: prefecture_capital_code) 29 | else 30 | maximum_year = where(local_gov_code: prefecture_capital_code).maximum(:year) 31 | find_by(year: maximum_year, local_gov_code: prefecture_capital_code) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/models/payment_target_month.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PaymentTargetMonth < ApplicationRecord 4 | belongs_to :insurance 5 | 6 | CALENDAR = { 7 | january: 1, 8 | february: 2, 9 | march: 3, 10 | april: 4, 11 | may: 5, 12 | june: 6, 13 | july: 7, 14 | august: 8, 15 | september: 9, 16 | october: 10, 17 | november: 11, 18 | december: 12 19 | }.freeze 20 | 21 | validates :month, presence: true 22 | end 23 | -------------------------------------------------------------------------------- /app/models/pension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Pension < ApplicationRecord 4 | paginates_per 20 5 | 6 | validates :year, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, uniqueness: true 7 | validates :contribution, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/simulation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation 4 | CATEGORY = %i[insurance pension residence].freeze 5 | 6 | def initialize(parameter) 7 | @parameter = parameter 8 | end 9 | 10 | def grand_total 11 | sub_total.values.sum 12 | end 13 | 14 | def sub_total 15 | CATEGORY.index_with { |category| monthly_payment.sum { |r| r[:fee][category] } } 16 | end 17 | 18 | def monthly_payment 19 | group_by_month = [].concat(insurance, pension, residence).group_by { |simulation| simulation[:month] } 20 | group_by_month.map do |month, value| 21 | { month: month, fee: {}.merge(*value.map { |data| data.slice(*CATEGORY) }) } 22 | end 23 | end 24 | 25 | def retirement_month 26 | @parameter.retirement_month 27 | end 28 | 29 | def employment_month 30 | @parameter.employment_month 31 | end 32 | 33 | private 34 | 35 | def insurance 36 | Simulation::Insurance.calc(@parameter) 37 | end 38 | 39 | def pension 40 | Simulation::Pension.calc(@parameter) 41 | end 42 | 43 | def residence 44 | Simulation::Residence.calc(@parameter) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /app/models/simulation/insurance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Insurance 4 | include MonthIterable 5 | 6 | def self.calc(parameter) 7 | new(parameter).calc 8 | end 9 | 10 | def initialize(parameter) 11 | @from = parameter.retirement_month 12 | @to = parameter.employment_month 13 | @local_gov_code = parameter.local_gov_code 14 | @age = parameter.age 15 | @salary_table = parameter.salary_table 16 | end 17 | 18 | def calc 19 | monthly_insurance 20 | end 21 | 22 | private 23 | 24 | def monthly_insurance 25 | yearly_insurance.flat_map do |year, fee| 26 | unemployed_term = unemployed_term_by_fiscal_year[year] 27 | subscription_term = months_between(from: unemployed_term.first, to: unemployed_term.first.end_of_financial_year) 28 | payment_target_months = build_payment_target_month(year).select { |month| month >= unemployed_term.first } 29 | 30 | actual_fee = fee * subscription_term.count / PaymentTargetMonth::CALENDAR.count 31 | fees_by_payment_target_month = LocalTaxLaw.calc_installments(actual_fee, payment_target_months, municipal_ordinance: true) 32 | 33 | unemployed_term.map do |month| 34 | { month: month, insurance: payment_target_months.include?(month) ? fees_by_payment_target_month.shift : 0 } 35 | end 36 | end 37 | end 38 | 39 | def yearly_insurance 40 | result = {} 41 | fiscal_years.each do |year| 42 | salary = @salary_table[year] 43 | result[year] = LocalTaxLaw.calc_determined_amount { calc_medical(year, salary) + calc_elderly(year, salary) + calc_care(year, salary) } 44 | end 45 | result 46 | end 47 | 48 | def build_payment_target_month(year) 49 | months = Insurance.rate(year: year, local_gov_code: @local_gov_code).payment_target_months.map(&:month) 50 | diff = year - months.first.financial_year 51 | diff.zero? ? months : months.map { |month| month.advance(years: diff) } 52 | end 53 | 54 | def fiscal_years 55 | unemployed_term.map(&:financial_year).uniq 56 | end 57 | 58 | def unemployed_term_by_fiscal_year 59 | unemployed_term.group_by(&:financial_year) 60 | end 61 | 62 | def unemployed_term 63 | months_between(from: @from, to: @to).map(&:beginning_of_month) 64 | end 65 | 66 | def calc_medical(year, salary) 67 | Simulation::Insurance::Medical.calc(year: year, local_gov_code: @local_gov_code, income: salary, age: @age) 68 | end 69 | 70 | def calc_elderly(year, salary) 71 | Simulation::Insurance::Elderly.calc(year: year, local_gov_code: @local_gov_code, income: salary, age: @age) 72 | end 73 | 74 | def calc_care(year, salary) 75 | Simulation::Insurance::Care.calc(year: year, local_gov_code: @local_gov_code, income: salary, age: @age) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /app/models/simulation/insurance/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Insurance::Base 4 | BASIC_DEDUCTION = 430_000 5 | 6 | def self.calc(year:, local_gov_code:, income:, age:) 7 | new(year, local_gov_code, income, age).calculate 8 | end 9 | 10 | def initialize(year, local_gov_code, income, age) 11 | @income = income 12 | @rate = Insurance.rate(year: year, local_gov_code: local_gov_code) 13 | @age = age 14 | end 15 | 16 | def calculate 17 | return 0 if @age >= 75 18 | 19 | limit = @rate.send("#{name}_limit") 20 | calculate_result = income_basis + capita_basis + household_basis 21 | calculate_result <= limit ? calculate_result : limit 22 | end 23 | 24 | protected 25 | 26 | # 所得割 27 | def income_basis 28 | (salary * @rate.send("#{name}_income_basis") / 100).round 29 | end 30 | 31 | # 均等割 32 | def capita_basis 33 | @rate.send("#{name}_capita_basis") 34 | end 35 | 36 | # 世帯割 37 | def household_basis 38 | @rate.send("#{name}_household_basis") 39 | end 40 | 41 | def name 42 | self.class.name.demodulize.downcase 43 | end 44 | 45 | def salary 46 | candidate = Simulation::Salary.calc(@income) - BASIC_DEDUCTION 47 | candidate.positive? ? candidate : 0 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/models/simulation/insurance/care.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Insurance::Care < Simulation::Insurance::Base 4 | def calculate 5 | return 0 if @age < 40 || @age >= 65 6 | 7 | super 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/simulation/insurance/elderly.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Insurance::Elderly < Simulation::Insurance::Base; end 4 | -------------------------------------------------------------------------------- /app/models/simulation/insurance/medical.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Insurance::Medical < Simulation::Insurance::Base; end 4 | -------------------------------------------------------------------------------- /app/models/simulation/parameter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Parameter 4 | include ActiveModel::Model 5 | include ActiveModel::Attributes 6 | 7 | attr_reader :local_gov_code, :salary_table, :social_insurance_table 8 | 9 | attribute :simulation_date, :time 10 | attribute :retirement_month, :time 11 | attribute :employment_month, :time 12 | attribute :prefecture, :string 13 | attribute :city, :string 14 | attribute :age, :integer 15 | attribute :previous_salary, :integer 16 | attribute :previous_social_insurance, :integer 17 | attribute :salary, :integer 18 | attribute :social_insurance, :integer 19 | attribute :scheduled_salary, :integer 20 | attribute :scheduled_social_insurance, :integer 21 | 22 | def initialize(params) 23 | super(parse_dates!(params)) 24 | @local_gov_code = build_local_gov_code 25 | @salary_table = build_salary_table 26 | @social_insurance_table = build_social_insurance_table 27 | end 28 | 29 | with_options presence: true do 30 | validates :retirement_month 31 | validates :employment_month 32 | validates :local_gov_code 33 | validates :age 34 | end 35 | 36 | validates :age, numericality: { greater_than_or_equal_to: 0 } 37 | validates :employment_month, comparison: { greater_than: :retirement_month } 38 | validates :previous_salary, comparison: { greater_than_or_equal_to: :previous_social_insurance } 39 | validates :salary, comparison: { greater_than_or_equal_to: :social_insurance } 40 | validates :scheduled_salary, comparison: { greater_than_or_equal_to: :scheduled_social_insurance } 41 | validate :month_of_retirement_month_should_be_greater_than_or_equal_simulation_date 42 | validates_with RequiredSalaryAndSocialInsuranceValidator 43 | 44 | private 45 | 46 | # ActiveSupport::TimeWithZoneを前提にした処理設計になっているが、 47 | # ActiveModel::Attributesの型キャストにサポートがないため、型キャストが実行される前に一度変換を実施する 48 | def parse_dates!(params) 49 | date_attributes = %i[simulation_date retirement_month employment_month] 50 | date_attributes.each { |attribute| params[attribute] = Time.zone.parse(params[attribute]) } 51 | params 52 | end 53 | 54 | def build_local_gov_code 55 | JpLocalGov.where(prefecture: prefecture, city: city).first.code 56 | end 57 | 58 | def build_salary_table 59 | { 60 | base_fiscal_year.pred => previous_salary, 61 | base_fiscal_year => salary, 62 | base_fiscal_year.next => scheduled_salary 63 | } 64 | end 65 | 66 | def build_social_insurance_table 67 | { 68 | base_fiscal_year.pred => previous_social_insurance, 69 | base_fiscal_year => social_insurance, 70 | base_fiscal_year.next => scheduled_social_insurance 71 | } 72 | end 73 | 74 | def base_fiscal_year 75 | simulation_date.financial_year 76 | end 77 | 78 | def month_of_retirement_month_should_be_greater_than_or_equal_simulation_date 79 | errors.add(:retirement_month, ": #{simulation_date.month}以降の月を指定してください") if retirement_month.month < simulation_date.month 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /app/models/simulation/pension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Pension 4 | include MonthIterable 5 | 6 | def self.calc(parameter) 7 | new(parameter).call 8 | end 9 | 10 | def initialize(parameter) 11 | @from = parameter.retirement_month 12 | @to = parameter.employment_month 13 | end 14 | 15 | def call 16 | calculate_pension 17 | end 18 | 19 | private 20 | 21 | def calculate_pension 22 | months = months_between(from: @from, to: @to) 23 | fiscal_years = months.map(&:financial_year).uniq 24 | contribution_table = fiscal_years.index_with do |year| 25 | query = Pension.exists?(year: year) ? year : Pension.maximum(:year) 26 | Pension.find_by(year: query).contribution 27 | end 28 | 29 | months.map { |month| { month: month, pension: contribution_table[month.financial_year] } } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/simulation/residence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Residence 4 | include MonthIterable 5 | using Simulation::Residence::JuneStartFinancialYear 6 | 7 | BASIC_DEDUCTION = 430_000 8 | PREFECTURE_CAPITA_BASIS = 1_500 9 | CITY_CAPITA_BASIS = 3_500 10 | PREFECTURE_TAX_DEDUCTION = 1_000 11 | CITY_TAX_DEDUCTION = 1_500 12 | PREFECTURE_TAX_RATE = 4 13 | CITY_TAX_RATE = 6 14 | DUES = [6, 8, 10, 1].freeze 15 | SPECIAL_COLLECTION_DUES = (1..12).to_a.freeze 16 | NON_TAXABLE_SALARY_LIMIT = 1_000_000 17 | 18 | def self.calc(parameter) 19 | new(parameter).calc 20 | end 21 | 22 | def initialize(parameter) 23 | @from = parameter.retirement_month 24 | @to = parameter.employment_month 25 | @salary_table = parameter.salary_table 26 | @social_insurance_table = parameter.social_insurance_table 27 | end 28 | 29 | def calc 30 | monthly_residence 31 | end 32 | 33 | private 34 | 35 | def monthly_residence 36 | fiscal_years.flat_map do |year| 37 | unemployed_term = unemployed_term_by_fiscal_year[year] 38 | payment_completed_term = months_between(from: unemployed_term.first.beginning_of_residence_fy, to: unemployed_term.first) 39 | payment_target_months = dues(year).select { |month| month >= unemployed_term.first }.presence || [unemployed_term.first] 40 | 41 | unpaid_fee = calc_special_collection(yearly_residence(year)).drop(payment_completed_term.count).sum 42 | fees_by_month = LocalTaxLaw.calc_installments(unpaid_fee, payment_target_months) 43 | 44 | unemployed_term.map { |month| { month: month, residence: payment_target_months.include?(month) ? fees_by_month.shift : 0 } } 45 | end 46 | end 47 | 48 | def dues(year) 49 | DUES.map { |month| Time.zone.parse("#{month >= 4 ? year : year.next}-#{format('%02d', month)}-01") } 50 | end 51 | 52 | def calc_special_collection(yearly_residence) 53 | LocalTaxLaw.calc_installments(yearly_residence, SPECIAL_COLLECTION_DUES) 54 | end 55 | 56 | def fiscal_years 57 | unemployed_term.map(&:residence_financial_year).uniq 58 | end 59 | 60 | def unemployed_term_by_fiscal_year 61 | unemployed_term.group_by(&:residence_financial_year) 62 | end 63 | 64 | def unemployed_term 65 | months_between(from: @from, to: @to) 66 | end 67 | 68 | def yearly_residence(year) 69 | return 0 if @salary_table[year] <= NON_TAXABLE_SALARY_LIMIT 70 | 71 | LocalTaxLaw.calc_determined_amount { income_basis(year) + capita_basis } 72 | end 73 | 74 | def income_basis(year) 75 | income_basis_before_tax_apply(year) - tax_deduction 76 | end 77 | 78 | def capita_basis 79 | PREFECTURE_CAPITA_BASIS + CITY_CAPITA_BASIS 80 | end 81 | 82 | def tax_deduction 83 | PREFECTURE_TAX_DEDUCTION + CITY_TAX_DEDUCTION 84 | end 85 | 86 | def income_basis_before_tax_apply(year) 87 | tax_rates.map { |tax_rate| (taxation_total_income(year) * tax_rate / 100).floor(-2) }.sum 88 | end 89 | 90 | def tax_rates 91 | [PREFECTURE_TAX_RATE, CITY_TAX_RATE] 92 | end 93 | 94 | def taxation_total_income(year) 95 | LocalTaxLaw.calc_tax_base { total_income(year) - income_deduction(year) } 96 | end 97 | 98 | def total_income(year) 99 | Simulation::Salary.calc(@salary_table[year]) 100 | end 101 | 102 | def income_deduction(year) 103 | @social_insurance_table[year] + BASIC_DEDUCTION 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /app/models/simulation/residence/june_start_financial_year.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Simulation::Residence::JuneStartFinancialYear 4 | # Refs: config/initializers/fiscali.rb 5 | refine ActiveSupport::TimeWithZone do 6 | def beginning_of_residence_fy 7 | prev_month.prev_month.beginning_of_financial_year.next_month.next_month 8 | end 9 | 10 | def residence_financial_year 11 | prev_month.prev_month.financial_year 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/simulation/salary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Simulation::Salary 4 | TABLE = { 5 | 0..550_999 => { plus: 0, multiply: 0, divide: 1 }, 6 | 551_000..1_618_999 => { plus: -550_000, multiply: 1, divide: 1 }, 7 | 1_619_000..1_619_999 => { plus: 1_069_000, multiply: 0, divide: 1 }, 8 | 1_620_000..1_621_999 => { plus: 1_070_000, multiply: 0, divide: 1 }, 9 | 1_622_000..1_623_999 => { plus: 1_072_000, multiply: 0, divide: 1 }, 10 | 1_624_000..1_627_999 => { plus: 1_074_000, multiply: 0, divide: 1 }, 11 | 1_628_000..1_799_999 => { plus: 100_000, multiply: 2.4, divide: 4 }, 12 | 1_800_000..3_599_999 => { plus: -80_000, multiply: 2.8, divide: 4 }, 13 | 3_600_000..6_599_999 => { plus: -440_000, multiply: 3.2, divide: 4 }, 14 | 6_600_000..8_499_999 => { plus: -1_100_000, multiply: 0.9, divide: 1 }, 15 | 8_500_000.. => { plus: -1_950_000, multiply: 1, divide: 1 } 16 | }.freeze 17 | 18 | def self.calc(income) 19 | new(income).calc 20 | end 21 | 22 | def initialize(income) 23 | @income = income 24 | end 25 | 26 | def calc 27 | calculate_salary 28 | end 29 | 30 | private 31 | 32 | def calculate_salary 33 | value = TABLE.select { |row| row.include?(@income) }.values.first 34 | base = value[:divide] == 1 ? @income : (@income / value[:divide]).floor(-3) 35 | (base * value[:multiply]).floor + value[:plus] 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User < ApplicationRecord 4 | # Include default devise modules. Others available are: 5 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 6 | devise :database_authenticatable, :registerable, 7 | :recoverable, :rememberable, :validatable 8 | end 9 | -------------------------------------------------------------------------------- /app/validators/month_anyone_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MonthAnyoneValidator < ActiveModel::Validator 4 | def validate(record) 5 | months = PaymentTargetMonth::CALENDAR.each_value.map { |num| record.send("month#{num}") } 6 | return if months.any? 7 | 8 | record.errors.add(:payment_target_months, '納付対象月は最低1つチェックしてください') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/validators/required_salary_and_social_insurance_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RequiredSalaryAndSocialInsuranceValidator < ActiveModel::Validator 4 | def validate(record) 5 | targets = [] 6 | targets.push(:previous_salary, :previous_social_insurance) if require_previous?(record) 7 | targets.push(:salary, :social_insurance) if require_current?(record) 8 | targets.push(:scheduled_salary, :scheduled_social_insurance) if require_scheduled?(record) 9 | 10 | targets.each { |target| record.errors.add(target, 'は必須です') if record.send(target).nil? } 11 | end 12 | 13 | private 14 | 15 | def require_previous?(record) 16 | record.retirement_month < record.simulation_date.beginning_of_financial_year.advance(months: 2) 17 | end 18 | 19 | def require_current?(record) 20 | record.retirement_month < record.simulation_date.beginning_of_financial_year.advance(months: 2, years: 1) 21 | end 22 | 23 | def require_scheduled?(record) 24 | record.employment_month >= record.simulation_date.beginning_of_financial_year.advance(years: 1) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/admin/insurances/edit.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民健康保険料 | 編集') 2 | 3 | .container.mx-auto.mb-8 4 | .mx-auto.px-2.max-w-2xl 5 | h1.text-3xl.py-4 6 | | 国民健康保険料編集 7 | .px-2 8 | = render 'form', insurance_form: @insurance_form 9 | -------------------------------------------------------------------------------- /app/views/admin/insurances/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民健康保険料 | 一覧') 2 | 3 | #js-insurances 4 | -------------------------------------------------------------------------------- /app/views/admin/insurances/new.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民健康保険料 | 登録') 2 | 3 | .container.mx-auto.mb-8 4 | .mx-auto.px-2.max-w-2xl 5 | h1.text-3xl.py-4 6 | | 国民健康保険料登録 7 | .px-2 8 | = render 'form', insurance_form: @insurance_form 9 | -------------------------------------------------------------------------------- /app/views/admin/pensions/_form.html.slim: -------------------------------------------------------------------------------- 1 | = render 'errors', object: pension 2 | = form_with model: [:admin, pension], html: { name: 'pension' } do |f| 3 | .flex.flex-col.mb-4 4 | = f.label :year, class: 'admin-form-label' 5 | = f.number_field :year, class: 'admin-form-input' 6 | .flex.flex-col.mb-4 7 | = f.label :contribution, class: 'admin-form-label' 8 | = f.number_field :contribution, class: 'admin-form-input' 9 | .flex.justify-end 10 | - if pension.new_record? 11 | = f.submit '登録', class: 'admin-form-submit' 12 | - else 13 | = f.submit '更新', class: 'admin-form-submit' 14 | -------------------------------------------------------------------------------- /app/views/admin/pensions/edit.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民年金保険料 | 編集') 2 | 3 | .container.mx-auto.mb-8 4 | .mx-auto.px-2.max-w-2xl 5 | h1.text-3xl.py-4 6 | | 国民年金保険料編集 7 | .px-2 8 | = render 'form', pension: @pension 9 | -------------------------------------------------------------------------------- /app/views/admin/pensions/index.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民年金保険料 | 一覧') 2 | 3 | #js-pensions 4 | -------------------------------------------------------------------------------- /app/views/admin/pensions/new.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '国民年金保険料 | 登録') 2 | 3 | .container.mx-auto.mb-8 4 | .mx-auto.px-2.max-w-2xl 5 | h1.text-3xl.py-4 6 | | 国民年金保険料登録 7 | .px-2 8 | = render 'form', pension: @pension 9 | -------------------------------------------------------------------------------- /app/views/api/admin/insurances/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.insurance @insurances do |insurance| 2 | json.id insurance.id 3 | json.year insurance.year 4 | json.prefecture insurance.local_government.prefecture 5 | json.city insurance.local_government.city 6 | json.prefecture_capital insurance.local_government.prefecture_capital 7 | json.medical_income_basis insurance.medical_income_basis 8 | json.medical_asset_basis insurance.medical_asset_basis 9 | json.medical_capita_basis insurance.medical_capita_basis 10 | json.medical_household_basis insurance.medical_household_basis 11 | json.medical_limit insurance.medical_limit 12 | json.elderly_income_basis insurance.elderly_income_basis 13 | json.elderly_asset_basis insurance.elderly_asset_basis 14 | json.elderly_capita_basis insurance.elderly_capita_basis 15 | json.elderly_household_basis insurance.elderly_household_basis 16 | json.elderly_limit insurance.elderly_limit 17 | json.care_income_basis insurance.care_income_basis 18 | json.care_asset_basis insurance.care_asset_basis 19 | json.care_capita_basis insurance.care_capita_basis 20 | json.care_household_basis insurance.care_household_basis 21 | json.care_limit insurance.care_limit 22 | json.january insurance.payment_target_months.january_is_target? 23 | json.february insurance.payment_target_months.february_is_target? 24 | json.march insurance.payment_target_months.march_is_target? 25 | json.april insurance.payment_target_months.april_is_target? 26 | json.may insurance.payment_target_months.may_is_target? 27 | json.june insurance.payment_target_months.june_is_target? 28 | json.july insurance.payment_target_months.july_is_target? 29 | json.august insurance.payment_target_months.august_is_target? 30 | json.september insurance.payment_target_months.september_is_target? 31 | json.october insurance.payment_target_months.october_is_target? 32 | json.november insurance.payment_target_months.november_is_target? 33 | json.december insurance.payment_target_months.december_is_target? 34 | json.edit_insurance_path edit_admin_insurance_path(insurance) 35 | end 36 | 37 | json.totalPages @insurances.total_pages 38 | -------------------------------------------------------------------------------- /app/views/api/admin/pensions/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.pensions @pensions do |pension| 2 | json.id pension.id 3 | json.year pension.year 4 | json.contribution pension.contribution 5 | json.edit_pension_path edit_admin_pension_path(pension) 6 | end 7 | 8 | json.totalPages @pensions.total_pages 9 | -------------------------------------------------------------------------------- /app/views/api/simulations/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.simulation do 2 | json.retirement_month @simulation.retirement_month 3 | json.employment_month @simulation.employment_month 4 | json.grand_total @simulation.grand_total 5 | json.sub_total @simulation.sub_total 6 | json.monthly_payment @simulation.monthly_payment 7 | end 8 | -------------------------------------------------------------------------------- /app/views/application/_errors.html.slim: -------------------------------------------------------------------------------- 1 | - if object.errors.any? 2 | .bg-red-100.border.border-red-400.text-red-700.px-4.py-3.rounded.mb-2 3 | h3.mb-2 4 | | 入力内容にエラーがありました 5 | ul.ml-4.text-sm 6 | - object.errors.full_messages.each do |msg| 7 | li.leading-relaxed.list-disc 8 | = msg 9 | -------------------------------------------------------------------------------- /app/views/application/_footer.html.slim: -------------------------------------------------------------------------------- 1 | footer.border-t.border-boundaryBlack.py-6 2 | .container.mx-auto 3 | nav 4 | ul.flex.items-center.flex-row.justify-center 5 | li.text-xs.text-gray.mx-2.cursor-pointer.hover:underline 6 | = link_to '利用規約', tos_path 7 | li.text-xs.text-gray.mx-2.cursor-pointer.hover:underline 8 | = link_to 'プライバシーポリシー', privacy_policy_path 9 | li.text-xs.text-gray.mx-2.cursor-pointer.hover:underline 10 | = link_to 'GitHub Project', 'https://github.com/IkumaTadokoro/quitcost' 11 | small.block.mt-4.text-center.text-xs.text-gray.cursor-pointer.hover:underline 12 | | © 13 | = link_to 'ikuma-t', 'https://twitter.com/ikumatdkr' 14 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, 'パスワード変更') 2 | 3 | .container.mx-auto.my-40 4 | .mx-auto.p-4.max-w-sm.bg-white.rounded-lg.border.border-boundaryBlack.shadow-md.sm:p-6.lg:p-8 5 | h2.text-xl 6 | | パスワード変更 7 | = form_with model: @user, url: user_password_path, method: :patch, class: 'space-y-2' do |f| 8 | = render 'devise/shared/error_messages', model: f.object 9 | .field 10 | = f.label :password, class: 'admin-form-label' 11 | - if @minimum_password_length 12 | em 13 | | ( 14 | = @minimum_password_length 15 | | characters minimum) 16 | = f.password_field :password, autofocus: true, autocomplete: 'new-password', class: 'admin-form-input' 17 | = f.label :password_confirmation, class: 'admin-form-label' 18 | = f.password_field :password_confirmation, autocomplete: 'new-password', class: 'admin-form-input' 19 | = f.hidden_field :reset_password_token 20 | .actions.flex.justify-end 21 | = f.submit 'パスワード変更', class: 'admin-form-submit' 22 | = render 'devise/shared/links' 23 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, 'パスワードのリセット') 2 | 3 | .container.mx-auto.my-40 4 | .mx-auto.p-4.max-w-sm.bg-white.rounded-lg.border.border-boundaryBlack.shadow-md.sm:p-6.lg:p-8 5 | h2.text-xl 6 | | パスワード変更 7 | = form_with model: @user, url: user_password_path, html: { method: :post }, class: 'space-y-2' do |f| 8 | = render 'devise/shared/error_messages', model: f.object 9 | .field 10 | = f.label :email, class: 'admin-form-label' 11 | = f.email_field :email, autofocus: true, autocomplete: 'email', class: 'admin-form-input' 12 | .actions.flex.justify-end 13 | = f.submit '再設定用リンクを送信する', class: 'admin-form-submit' 14 | = render 'devise/shared/links' 15 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '管理者情報編集') 2 | 3 | .container.mx-auto.my-40 4 | .mx-auto.p-4.max-w-sm.bg-white.rounded-lg.border.border-boundaryBlack.shadow-md.sm:p-6.lg:p-8 5 | h2.text-xl 6 | | 管理者情報編集 7 | = form_with model: @user, url: user_registration_path, html: { method: :put }, class: 'space-y-2' do |f| 8 | = render 'devise/shared/error_messages', model: f.object 9 | .field 10 | = f.label :email, class: 'admin-form-label' 11 | = f.email_field :email, autofocus: true, autocomplete: 'email', class: 'admin-form-input' 12 | - if devise_mapping.confirmable? && resource.pending_reconfirmation? 13 | div 14 | | Currently waiting confirmation for: 15 | = resource.unconfirmed_email 16 | .field 17 | = f.label :password, class: 'admin-form-label' 18 | i.text-sm 19 | | (変更しない場合は空欄にしてください) 20 | - if @minimum_password_length 21 | em 22 | | ( 23 | = @minimum_password_length 24 | | characters minimum) 25 | = f.password_field :password, autocomplete: 'new-password', class: 'admin-form-input' 26 | = f.label :password_confirmation, class: 'admin-form-label' 27 | = f.password_field :password_confirmation, autocomplete: 'new-password', class: 'admin-form-input' 28 | = f.label :current_password, class: 'admin-form-label' 29 | = f.password_field :current_password, autocomplete: 'current-password', class: 'admin-form-input' 30 | .actions.flex.justify-end 31 | = f.submit '更新', class: 'admin-form-submit' 32 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.slim: -------------------------------------------------------------------------------- 1 | - content_for(:title, '管理者ログイン') 2 | 3 | .container.mx-auto.my-40 4 | .mx-auto.p-4.max-w-sm.bg-white.rounded-lg.border.border-boundaryBlack.shadow-md.sm:p-6.lg:p-8 5 | h2.text-xl 6 | | 管理者ログイン 7 | = form_with model: @user, url: user_session_path, class: 'space-y-2' do |f| 8 | .field 9 | = f.label :email, class: 'admin-form-label' 10 | = f.email_field :email, autofocus: true, autocomplete: 'email', class: 'admin-form-input' 11 | .field 12 | = f.label :password, class: 'admin-form-label' 13 | = f.password_field :password, autocomplete: 'current-password', class: 'admin-form-input' 14 | - if devise_mapping.rememberable? 15 | .field 16 | = f.check_box :remember_me 17 | = f.label :remember_me, class: 'ml-2 admin-form-label' 18 | .actions.flex.justify-end 19 | = f.submit 'ログイン', class: 'admin-form-submit' 20 | = render 'devise/shared/links' 21 | -------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.slim: -------------------------------------------------------------------------------- 1 | - if resource.errors.any? 2 | #error_explanation.text-sm 3 | h2 4 | = I18n.t('errors.messages.not_saved', 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | ul 8 | - resource.errors.full_messages.each do |message| 9 | li 10 | = message 11 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.slim: -------------------------------------------------------------------------------- 1 | .mt-4.text-sm.text-gray.flex.flex-col.items-end 2 | - if controller_name != 'sessions' 3 | = link_to 'ログイン', new_session_path(resource_name) 4 | - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' 5 | = link_to 'パスワードをお忘れですか?', new_password_path(resource_name) 6 | - if devise_mapping.confirmable? && controller_name != 'confirmations' 7 | = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) 8 | - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' 9 | = link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) 10 | - if devise_mapping.omniauthable? 11 | - resource_class.omniauth_providers.each do |provider| 12 | = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post 13 | -------------------------------------------------------------------------------- /app/views/home/index.html.slim: -------------------------------------------------------------------------------- 1 | #app 2 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title 5 | = yield :title 6 | = display_meta_tags default_meta_tags 7 | = csrf_meta_tags 8 | = csp_meta_tag 9 | = stylesheet_pack_tag 'application' 10 | = stylesheet_link_tag 'application', media: 'all' 11 | = javascript_pack_tag 'application' 12 | = favicon_link_tag('/favicon.ico') 13 | body.font-notojp.text-black.flex.flex-col.min-h-screen 14 | header.flex.justify-between.items-center.md:px-8.px-4.py-4.border-b-2.border-boundaryBlack.shadow-sm.sticky.top-0.z-50.bg-white 15 | h1.text-3xl 16 | = link_to root_path do 17 | = image_tag('logo.svg', alt: 'quitcost', class: 'h-6 md:h-10 hover:opacity-70') 18 | - if user_signed_in? 19 | nav.mr-auto.mx-16 20 | ul.flex 21 | li.px-4 22 | = link_to '保険', admin_insurances_path 23 | li.px-4 24 | = link_to '年金', admin_pensions_path 25 | nav 26 | ul.flex.items-center 27 | li.px-4 28 | = link_to current_user.email, edit_user_registration_path 29 | li.px-4 30 | button.text-white.bg-primary.border-0.py-2.px-4.focus:outline-none.hover:opacity-70.rounded-full.text-sm 31 | = link_to 'ログアウト', destroy_user_session_path, method: :delete 32 | - if notice 33 | .w-full 34 | .p-24.bg-secondary.items-center.leading-none.flex.py-3 35 | span.flex.rounded-full.bg-primary.uppercase.px-2.py-1.text-xs.mr-3.text-white 36 | | INFO 37 | span.mr-2.text-left.text-sm.lex-auto.text-black 38 | = notice 39 | - if alert 40 | .w-full 41 | .p-24.bg-red-400.items-center.text-white.leading-none.flex.py-3 42 | span.flex.rounded-full.bg-red-700.uppercase.px-2.py-1.text-xs.mr-3 43 | | ALERT 44 | span.font-semibold.mr-2.text-left.text-sm.lex-auto 45 | = alert 46 | main.flex-1 47 | = yield 48 | = render 'footer' 49 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta[http-equiv="Content-Type" content="text/html; charset=utf-8"] 5 | style 6 | | /* Email styles need to be inline */ 7 | body 8 | = yield 9 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.slim: -------------------------------------------------------------------------------- 1 | = yield 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | '@babel/preset-env', 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | '@babel/preset-env', 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | 'babel-plugin-macros', 41 | '@babel/plugin-syntax-dynamic-import', 42 | isTestEnv && 'babel-plugin-dynamic-import-node', 43 | '@babel/plugin-transform-destructuring', 44 | [ 45 | '@babel/plugin-proposal-class-properties', 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | '@babel/plugin-proposal-object-rest-spread', 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | '@babel/plugin-proposal-private-methods', 58 | { 59 | loose: true 60 | } 61 | ], 62 | [ 63 | '@babel/plugin-proposal-private-property-in-object', 64 | { 65 | loose: true 66 | } 67 | ], 68 | [ 69 | '@babel/plugin-transform-runtime', 70 | { 71 | helpers: false 72 | } 73 | ], 74 | [ 75 | '@babel/plugin-transform-regenerator', 76 | { 77 | async: false 78 | } 79 | ] 80 | ].filter(Boolean) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /bin/cyopen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IP=$(ipconfig getifaddr en0) 4 | /usr/X11/bin/xhost + $IP 5 | 6 | DISPLAY=$IP:0 7 | 8 | docker-compose -f docker-compose.yml -f cy-open.yml up --exit-code-from cypress 9 | -------------------------------------------------------------------------------- /bin/cypress: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker-compose.yml -f cypress.yml up --exit-code-from cypress 4 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bundle exec rubocop -a 4 | bundle exec slim-lint app/views -c config/slim_lint.yml 5 | bin/yarn lint 6 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) 3 | gem "bundler" 4 | require "bundler" 5 | 6 | # Load Spring without loading other gems in the Gemfile, for speed. 7 | Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring| 8 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 9 | gem "spring", spring.version 10 | require "spring/binstub" 11 | rescue Gem::LoadError 12 | # Ignore when Spring is not installed. 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bundle exec rspec 4 | yarn test 5 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Quitcost 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 6.1 13 | config.i18n.default_locale = :ja 14 | config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s] 15 | config.time_zone = 'Tokyo' 16 | 17 | # Configuration for the application, engines, and railties goes here. 18 | # 19 | # These settings can be overridden in specific environments using the files 20 | # in config/environments, which are processed later. 21 | # 22 | # config.time_zone = "Central Time (US & Canada)" 23 | # config.eager_load_paths << Rails.root.join("extras") 24 | 25 | config.generators do |g| 26 | g.test_framework :rspec 27 | g.fixture_replacement :factory_bot, dir: "spec/factories" 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | YERj5yTQ0XLfia/lUeehovUw3vs57VDoKX68XfSk5vxoCCu9wsXYm2+v8/+jK73CQVtccO33yMtPbnAx4Cn8pt3Ktq70gPKvDxH0XP9nhLe9ibv+wtELOoK1elH9DAglYkcIJhoN3ZnidXghmsXlhz8S9apVinzi6T5sXDP9HULtTUbrO5dLpjfEGByidvkSA9Lt9uWUfBrQitqie6dzCBjwB1bN1LWhoI066TcHKW0fOSB2FU5ZyjQ5bMxk33rhgHdIhscCasfFqfkpfgAol/yniGsm11abv/v90hgeN7Sx/KscUEn9g6qObBVCo2hxbUFKVmTllvQA5FfWgY+Bijy8dzR44dZVhwtOpG4DW9GuxrMCsVlxyBNLN2J9crIrPBB92WCLJKefGEnD9Fv/6TXQlp88oKqTOK3F--UV7TYnpS8/wK3gDa--sWQDZieHoN3gu9cxk2almg== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see Rails configuration guide 21 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | host: db 24 | username: postgres 25 | password: 26 | 27 | development: 28 | <<: *default 29 | database: quitcost_development 30 | 31 | # The specified database role being used to connect to postgres. 32 | # To create additional roles in postgres see `$ createuser --help`. 33 | # When left blank, postgres will use the default role. This is 34 | # the same name as the operating system user running Rails. 35 | #username: quitcost 36 | 37 | # The password associated with the postgres role (username). 38 | #password: 39 | 40 | # Connect on a TCP socket. Omitted by default since the client uses a 41 | # domain socket that doesn't need configuration. Windows does not have 42 | # domain sockets, so uncomment these lines. 43 | #host: localhost 44 | 45 | # The TCP port the server listens on. Defaults to 5432. 46 | # If your server runs on a different port number, change accordingly. 47 | #port: 5432 48 | 49 | # Schema search path. The server defaults to $user,public 50 | #schema_search_path: myapp,sharedapp,public 51 | 52 | # Minimum log levels, in increasing order: 53 | # debug5, debug4, debug3, debug2, debug1, 54 | # log, notice, warning, error, fatal, and panic 55 | # Defaults to warning. 56 | #min_messages: notice 57 | 58 | # Warning: The database defined as "test" will be erased and 59 | # re-generated from your development database when you run "rake". 60 | # Do not set this db to the same as development or production. 61 | test: 62 | <<: *default 63 | database: quitcost_test 64 | 65 | # As with config/credentials.yml, you never want to store sensitive information, 66 | # like your database password, in your source code. If your source code is 67 | # ever seen by anyone, they now have access to your database. 68 | # 69 | # Instead, provide the password or a full connection URL as an environment 70 | # variable when you boot the app. For example: 71 | # 72 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 73 | # 74 | # If the connection URL is provided in the special DATABASE_URL environment 75 | # variable, Rails will automatically merge its configuration values on top of 76 | # the values provided in this file. Alternatively, you can specify a connection 77 | # URL environment variable explicitly: 78 | # 79 | # production: 80 | # url: <%= ENV['MY_APP_DATABASE_URL'] %> 81 | # 82 | # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 83 | # for a full overview on how database connection configuration can be specified. 84 | # 85 | production: 86 | <<: *default 87 | database: quitcost_production 88 | username: quitcost 89 | password: <%= ENV['QUITCOST_DATABASE_PASSWORD'] %> 90 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Store uploaded files on the local file system (see config/storage.yml for options). 37 | config.active_storage.service = :local 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 45 | config.action_mailer.delivery_method = :letter_opener_web 46 | 47 | # Print deprecation notices to the Rails logger. 48 | config.active_support.deprecation = :log 49 | 50 | # Raise exceptions for disallowed deprecations. 51 | config.active_support.disallowed_deprecation = :raise 52 | 53 | # Tell Active Support which deprecation messages to disallow. 54 | config.active_support.disallowed_deprecation_warnings = [] 55 | 56 | # Raise an error on page load if there are pending migrations. 57 | config.active_record.migration_error = :page_load 58 | 59 | # Highlight code that triggered database queries in logs. 60 | config.active_record.verbose_query_logs = true 61 | 62 | # Suppress logger output for asset requests. 63 | config.assets.quiet = true 64 | 65 | # Raises error for missing translations. 66 | # config.i18n.raise_on_missing_translations = true 67 | 68 | # Annotate rendered view with file names. 69 | # config.action_view.annotate_rendered_view_with_filenames = true 70 | 71 | # Uncomment if you wish to allow Action Cable access from any origin. 72 | # config.action_cable.disable_request_forgery_protection = true 73 | 74 | config.hosts << "web" 75 | end 76 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV["CI"].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | end 61 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report CSP violations to a specified URI. See: 24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [ 5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :salary, :insurance, :age, :prefecture, :city, :month 6 | ] 7 | -------------------------------------------------------------------------------- /config/initializers/fiscali.rb: -------------------------------------------------------------------------------- 1 | Time.fiscal_zone = :japan 2 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | 18 | ActiveSupport::Inflector.inflections(:en) do |inflect| 19 | inflect.acronym "API" 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/meta_tags.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Use this setup block to configure all options available in MetaTags. 4 | MetaTags.configure do |config| 5 | # How many characters should the title meta tag have at most. Default is 70. 6 | # Set to nil or 0 to remove limits. 7 | # config.title_limit = 70 8 | 9 | # When true, site title will be truncated instead of title. Default is false. 10 | # config.truncate_site_title_first = false 11 | 12 | # Maximum length of the page description. Default is 300. 13 | # Set to nil or 0 to remove limits. 14 | # config.description_limit = 300 15 | 16 | # Maximum length of the keywords meta tag. Default is 255. 17 | # config.keywords_limit = 255 18 | 19 | # Default separator for keywords meta tag (used when an Array passed with 20 | # the list of keywords). Default is ", ". 21 | # config.keywords_separator = ', ' 22 | 23 | # When true, keywords will be converted to lowercase, otherwise they will 24 | # appear on the page as is. Default is true. 25 | # config.keywords_lowercase = true 26 | 27 | # When true, the output will not include new line characters between meta tags. 28 | # Default is false. 29 | # config.minify_output = false 30 | 31 | # When false, generated meta tags will be self-closing () instead 32 | # of open (``). Default is true. 33 | # config.open_meta_tags = true 34 | 35 | # List of additional meta tags that should use "property" attribute instead 36 | # of "name" attribute in tags. 37 | # config.property_tags.push( 38 | # 'x-hearthstone:deck', 39 | # ) 40 | end 41 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | activerecord: 3 | attributes: 4 | insurance: 5 | year: 年度 6 | local_gov_code: 市区町村名 7 | medical_income_basis: 所得割(医療分) 8 | medical_asset_basis: 資産割(医療分) 9 | medical_capita_basis: 均等割(医療分) 10 | medical_household_basis: 平等割(医療分) 11 | medical_limit: 限度額(医療分) 12 | elderly_income_basis: 所得割(後期高齢者支援分) 13 | elderly_asset_basis: 資産割(後期高齢者支援分) 14 | elderly_capita_basis: 均等割(後期高齢者支援分) 15 | elderly_household_basis: 平等割(後期高齢者支援分) 16 | elderly_limit: 限度額(後期高齢者支援分) 17 | care_income_basis: 所得割(介護分) 18 | care_asset_basis: 資産割(介護分) 19 | care_capita_basis: 均等割(介護分) 20 | care_household_basis: 平等割(介護分) 21 | care_limit: 限度額(介護分) 22 | month1: 1月 23 | month2: 2月 24 | month3: 3月 25 | month4: 4月 26 | month5: 5月 27 | month6: 6月 28 | month7: 7月 29 | month8: 8月 30 | month9: 9月 31 | month10: 10月 32 | month11: 11月 33 | month12: 12月 34 | pension: 35 | year: 年度 36 | contribution: 保険料 37 | activemodel: 38 | attributes: 39 | insurance_form: 40 | year: 年度 41 | local_gov_code: 市区町村名 42 | medical_income_basis: 所得割(医療分) 43 | medical_asset_basis: 資産割(医療分) 44 | medical_capita_basis: 均等割(医療分) 45 | medical_household_basis: 平等割(医療分) 46 | medical_limit: 限度額(医療分) 47 | elderly_income_basis: 所得割(後期高齢者支援分) 48 | elderly_asset_basis: 資産割(後期高齢者支援分) 49 | elderly_capita_basis: 均等割(後期高齢者支援分) 50 | elderly_household_basis: 平等割(後期高齢者支援分) 51 | elderly_limit: 限度額(後期高齢者支援分) 52 | care_income_basis: 所得割(介護分) 53 | care_asset_basis: 資産割(介護分) 54 | care_capita_basis: 均等割(介護分) 55 | care_household_basis: 平等割(介護分) 56 | care_limit: 限度額(介護分) 57 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | devise_for :users, :skip => [:registrations] 3 | as :user do 4 | get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration' 5 | put 'users' => 'devise/registrations#update', :as => 'user_registration' 6 | end 7 | mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? 8 | authenticated do 9 | root to: 'admin/insurances#index', as: :authenticated_root 10 | end 11 | root to: 'home#index' 12 | 13 | namespace :admin do 14 | resources :insurances, only: %i(index new create edit update) 15 | resources :pensions, only: %i(index new create edit update) 16 | end 17 | namespace :api do 18 | resource :simulations, only: %i(show) 19 | namespace :admin do 20 | resources :insurances, only: %i(index destroy) 21 | resources :pensions, only: %i(index destroy) 22 | end 23 | end 24 | resources :home, only: %i(index) 25 | get 'privacy_policy', to: 'home#privacy_policy', as: 'privacy_policy' 26 | get 'tos', to: 'home#tos', as: 'tos' 27 | get '*path', to: 'home#index' 28 | end 29 | -------------------------------------------------------------------------------- /config/slim_lint.yml: -------------------------------------------------------------------------------- 1 | exclude: 2 | - 'app/views/style_guide/**/*.slim' 3 | 4 | linters: 5 | LineLength: 6 | max: 350 7 | RuboCop: 8 | enabled: true 9 | ignored_cops: 10 | - Layout/ArgumentAlignment 11 | - Layout/ArrayAlignment 12 | - Layout/BlockAlignment 13 | - Layout/EmptyLineAfterGuardClause 14 | - Layout/EndAlignment 15 | - Layout/FirstArrayElementIndentation 16 | - Layout/FirstParameterIndentation 17 | - Layout/HashAlignment 18 | - Layout/IndentationConsistency 19 | - Layout/IndentationWidth 20 | - Layout/InitialIndentation 21 | - Layout/LineLength 22 | - Layout/MultilineArrayBraceLayout 23 | - Layout/MultilineAssignmentLayout 24 | - Layout/MultilineHashBraceLayout 25 | - Layout/MultilineMethodCallBraceLayout 26 | - Layout/MultilineMethodCallIndentation 27 | - Layout/MultilineMethodDefinitionBraceLayout 28 | - Layout/MultilineOperationIndentation 29 | - Layout/ParameterAlignment 30 | - Layout/TrailingEmptyLines 31 | - Layout/TrailingWhitespace 32 | - Lint/Void 33 | - Metrics/BlockLength 34 | - Metrics/BlockNesting 35 | - Naming/FileName 36 | - Style/FrozenStringLiteralComment 37 | - Style/IdenticalConditionalBranches 38 | - Style/IfUnlessModifier 39 | - Style/Next 40 | - Style/WhileUntilDo 41 | - Style/WhileUntilModifier 42 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | const { VueLoaderPlugin } = require('vue-loader') 3 | const vue = require('./loaders/vue') 4 | const { DefinePlugin } = require('webpack') 5 | 6 | function hotfixPostcssLoaderConfig(subloader) { 7 | const subloaderName = subloader.loader 8 | if (subloaderName === 'postcss-loader') { 9 | if (subloader.options.postcssOptions) { 10 | console.log( 11 | '\x1b[31m%s\x1b[0m', 12 | 'Remove postcssOptions workaround in config/webpack/environment.js' 13 | ) 14 | } else { 15 | subloader.options.postcssOptions = subloader.options.config 16 | delete subloader.options.config 17 | } 18 | } 19 | } 20 | 21 | environment.loaders.keys().forEach((loaderName) => { 22 | const loader = environment.loaders.get(loaderName) 23 | loader.use.forEach(hotfixPostcssLoaderConfig) 24 | }) 25 | 26 | environment.config.merge({ 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.mjs$/, 31 | include: /node_modules/, 32 | type: 'javascript/auto' 33 | } 34 | ] 35 | } 36 | }) 37 | 38 | environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin()) 39 | environment.plugins.prepend( 40 | 'Define', 41 | new DefinePlugin({ 42 | __VUE_OPTIONS_API__: false, 43 | __VUE_PROD_DEVTOOLS__: false 44 | }) 45 | ) 46 | environment.loaders.prepend('vue', vue) 47 | module.exports = environment 48 | -------------------------------------------------------------------------------- /config/webpack/loaders/vue.js: -------------------------------------------------------------------------------- 1 | function removeAttr(astEl, name) { 2 | const { attrsList, attrsMap } = astEl 3 | if (attrsMap[name]) { 4 | delete attrsMap[name] 5 | const index = attrsList.findIndex((x) => x.name === name) 6 | attrsList.splice(index, 1) 7 | } 8 | return astEl 9 | } 10 | 11 | function removeTestAttr(astEl) { 12 | removeAttr(astEl, 'data-test-id') 13 | removeAttr(astEl, ':data-test-id') 14 | return astEl 15 | } 16 | 17 | const compileModules = 18 | process.env.NODE_ENV === 'production' 19 | ? [{ preTransformNode: removeTestAttr }] 20 | : [] 21 | 22 | module.exports = { 23 | test: /\.vue$/, 24 | loader: 'vue-loader', 25 | options: { 26 | reactivityTransform: true, 27 | compilerOptions: { 28 | modules: compileModules 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | webpack_compile_output: true 10 | 11 | # Additional paths webpack should lookup modules 12 | # ['app/assets', 'engine/foo/app/assets'] 13 | additional_paths: ['app/assets/images'] 14 | 15 | # Reload manifest.json on all requests so we reload latest compiled packs 16 | cache_manifest: false 17 | 18 | # Extract and emit a css file 19 | extract_css: false 20 | 21 | static_assets_extensions: 22 | - .jpg 23 | - .jpeg 24 | - .png 25 | - .gif 26 | - .tiff 27 | - .ico 28 | - .svg 29 | - .eot 30 | - .otf 31 | - .ttf 32 | - .woff 33 | - .woff2 34 | 35 | extensions: 36 | - .vue 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Reference: https://webpack.js.org/configuration/dev-server/ 56 | dev_server: 57 | https: false 58 | host: 0.0.0.0 59 | port: 3035 60 | public: 0.0.0.0:3035 61 | hmr: false 62 | # Inline should be set to true if using HMR 63 | inline: true 64 | overlay: true 65 | compress: true 66 | disable_host_check: true 67 | use_local_ip: false 68 | quiet: false 69 | pretty: false 70 | headers: 71 | 'Access-Control-Allow-Origin': '*' 72 | watch_options: 73 | ignored: '**/node_modules/**' 74 | 75 | 76 | test: 77 | <<: *default 78 | compile: true 79 | 80 | # Compile test packs to a separate directory 81 | public_output_path: packs-test 82 | 83 | production: 84 | <<: *default 85 | 86 | # Production depends on precompilation of packs prior to booting for performance. 87 | compile: false 88 | 89 | # Extract and emit a css file 90 | extract_css: true 91 | 92 | # Cache manifest.json for performance 93 | cache_manifest: true 94 | -------------------------------------------------------------------------------- /cy-open.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | web: 5 | command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -d -p 3000 -b '0.0.0.0' && bin/webpack-dev-server" 6 | cypress: 7 | entrypoint: cypress open --project /e2e 8 | environment: 9 | - DISPLAY 10 | volumes: 11 | - /tmp/.X11-unix:/tmp/.X11-unix 12 | -------------------------------------------------------------------------------- /cypress.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | web: 5 | command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -d -p 3000 -b '0.0.0.0' && bin/webpack-dev-server" 6 | -------------------------------------------------------------------------------- /db/migrate/20211222102420_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :active_storage_blobs, id: primary_key_type do |t| 8 | t.string :key, null: false 9 | t.string :filename, null: false 10 | t.string :content_type 11 | t.text :metadata 12 | t.string :service_name, null: false 13 | t.bigint :byte_size, null: false 14 | t.string :checksum 15 | 16 | if connection.supports_datetime_with_precision? 17 | t.datetime :created_at, precision: 6, null: false 18 | else 19 | t.datetime :created_at, null: false 20 | end 21 | 22 | t.index [ :key ], unique: true 23 | end 24 | 25 | create_table :active_storage_attachments, id: primary_key_type do |t| 26 | t.string :name, null: false 27 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 28 | t.references :blob, null: false, type: foreign_key_type 29 | 30 | if connection.supports_datetime_with_precision? 31 | t.datetime :created_at, precision: 6, null: false 32 | else 33 | t.datetime :created_at, null: false 34 | end 35 | 36 | t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true 37 | t.foreign_key :active_storage_blobs, column: :blob_id 38 | end 39 | 40 | create_table :active_storage_variant_records, id: primary_key_type do |t| 41 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 42 | t.string :variation_digest, null: false 43 | 44 | t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true 45 | t.foreign_key :active_storage_blobs, column: :blob_id 46 | end 47 | end 48 | 49 | private 50 | def primary_and_foreign_key_types 51 | config = Rails.configuration.generators 52 | setting = config.options[config.orm][:primary_key_type] 53 | primary_key_type = setting || :primary_key 54 | foreign_key_type = setting || :bigint 55 | [primary_key_type, foreign_key_type] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /db/migrate/20211222102523_add_service_name_to_active_storage_blobs.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20190112182829) 2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] 3 | def up 4 | unless column_exists?(:active_storage_blobs, :service_name) 5 | add_column :active_storage_blobs, :service_name, :string 6 | 7 | if configured_service = ActiveStorage::Blob.service.name 8 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) 9 | end 10 | 11 | change_column :active_storage_blobs, :service_name, :string, null: false 12 | end 13 | end 14 | 15 | def down 16 | remove_column :active_storage_blobs, :service_name 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20211222102524_create_active_storage_variant_records.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20191206030411) 2 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] 3 | def change 4 | # Use Active Record's configured type for primary key 5 | create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| 6 | t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type 7 | t.string :variation_digest, null: false 8 | 9 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 10 | t.foreign_key :active_storage_blobs, column: :blob_id 11 | end 12 | end 13 | 14 | private 15 | def primary_key_type 16 | config = Rails.configuration.generators 17 | config.options[config.orm][:primary_key_type] || :primary_key 18 | end 19 | 20 | def blobs_primary_key_type 21 | pkey_name = connection.primary_key(:active_storage_blobs) 22 | pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } 23 | pkey_column.bigint? ? :bigint : pkey_column.type 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20211222102525_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20211119233751) 2 | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0] 3 | def change 4 | change_column_null(:active_storage_blobs, :checksum, true) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20211226135907_create_insurances.rb: -------------------------------------------------------------------------------- 1 | class CreateInsurances < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :insurances do |t| 4 | t.integer :year, comment: "年度" 5 | t.string :local_gov_code, comment: "地方公共団体コード" 6 | t.decimal :medical_income_basis, comment: "医療保険分(所得割)", default: 0, precision: 5, scale: 2 7 | t.decimal :medical_asset_basis, comment: "医療保険分(資産割)", default: 0, precision: 5, scale: 2 8 | t.integer :medical_capita_basis, comment: "医療保険分(均等割)", default: 0 9 | t.integer :medical_household_basis, comment: "医療保険分(平等割)", default: 0 10 | t.integer :medical_limit, comment: "医療保険分限度額", default: 0 11 | t.decimal :elderly_income_basis, comment: "後期高齢者支援金分(所得割)", default: 0, precision: 5, scale: 2 12 | t.decimal :elderly_asset_basis, comment: "後期高齢者支援金分(資産割)", default: 0, precision: 5, scale: 2 13 | t.integer :elderly_capita_basis, comment: "後期高齢者支援金分(均等割)", default: 0 14 | t.integer :elderly_household_basis, comment: "後期高齢者支援金分(平等割)", default: 0 15 | t.integer :elderly_limit, comment: "後期高齢者支援金分限度額", default: 0 16 | t.decimal :care_income_basis, comment: "介護保険分(所得割)", default: 0, precision: 5, scale: 2 17 | t.decimal :care_asset_basis, comment: "介護保険分(資産割)", default: 0, precision: 5, scale: 2 18 | t.integer :care_capita_basis, comment: "介護保険分(均等割)", default: 0 19 | t.integer :care_household_basis, comment: "介護保険分(平等割)", default: 0 20 | t.integer :care_limit, comment: "介護保険分限度額", default: 0 21 | 22 | t.timestamps 23 | end 24 | 25 | add_index :insurances, %i[year local_gov_code], unique: true 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20211230081511_create_payment_target_months.rb: -------------------------------------------------------------------------------- 1 | class CreatePaymentTargetMonths < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :payment_target_months do |t| 4 | t.string :local_gov_code, comment: '地方公共団体コード' 5 | t.date :month, comment: '年月' 6 | t.references :insurance, null: false, foreign_key: true, index: false 7 | 8 | t.timestamps 9 | end 10 | 11 | add_index :payment_target_months, %i[local_gov_code month], unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20220109115534_remove_local_gov_code_from_payment_target_months.rb: -------------------------------------------------------------------------------- 1 | class RemoveLocalGovCodeFromPaymentTargetMonths < ActiveRecord::Migration[7.0] 2 | def change 3 | remove_column :payment_target_months, :local_gov_code, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220201011602_create_pentions.rb: -------------------------------------------------------------------------------- 1 | class CreatePentions < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :pentions do |t| 4 | t.integer :year, null: false, comment: '年度' 5 | t.integer :contribution, null: false, comment: '国民年金保険料' 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20220201025801_add_index_to_pentions.rb: -------------------------------------------------------------------------------- 1 | class AddIndexToPentions < ActiveRecord::Migration[7.0] 2 | def change 3 | add_index :pentions, :year, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220201053103_change_pention_to_pension.rb: -------------------------------------------------------------------------------- 1 | class ChangePentionToPension < ActiveRecord::Migration[7.0] 2 | def change 3 | rename_table :pentions, :pensions 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220211122122_change_column_month_to_payment_target_month.rb: -------------------------------------------------------------------------------- 1 | class ChangeColumnMonthToPaymentTargetMonth < ActiveRecord::Migration[7.0] 2 | def change 3 | change_column :payment_target_months, :month, :timestamp 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220309122700_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[7.0] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | # t.integer :sign_in_count, default: 0, null: false 19 | # t.datetime :current_sign_in_at 20 | # t.datetime :last_sign_in_at 21 | # t.string :current_sign_in_ip 22 | # t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | # t.string :confirmation_token 26 | # t.datetime :confirmed_at 27 | # t.datetime :confirmation_sent_at 28 | # t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | # t.string :unlock_token # Only if unlock strategy is :email or :both 33 | # t.datetime :locked_at 34 | 35 | 36 | t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | # add_index :users, :confirmation_token, unique: true 42 | # add_index :users, :unlock_token, unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /db/migrate/20220402061638_delete_active_storage_tables.rb: -------------------------------------------------------------------------------- 1 | class DeleteActiveStorageTables < ActiveRecord::Migration[7.0] 2 | def change 3 | drop_table :active_storage_attachments 4 | drop_table :active_storage_variant_records 5 | drop_table :active_storage_blobs 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Metrics/BlockLength 4 | 5 | require 'csv' 6 | 7 | ActiveRecord::Base.transaction do 8 | # Create Admin User 9 | Rails.logger.debug 'Create Admin User...' 10 | User.create!(email: 'quitcost@example.com', password: 'quitcost') 11 | 12 | # Create Pensions 13 | Rails.logger.debug 'Create Pensions...' 14 | Pension.create!(year: 2021, contribution: 16_610) 15 | Pension.create!(year: 2022, contribution: 16_590) 16 | 17 | # Create Insurance & Payment Target Month 18 | Rails.logger.debug 'Create Insurance & Payment Target Month...' 19 | CSV.foreach('db/seeds/csv/insurances.csv', headers: true) do |row| 20 | insurance = Insurance.create!( 21 | year: row['year'], 22 | local_gov_code: row['local_gov_code'], 23 | medical_income_basis: row['medical_income_basis'], 24 | medical_asset_basis: row['medical_asset_basis'], 25 | medical_capita_basis: row['medical_capita_basis'], 26 | medical_household_basis: row['medical_household_basis'], 27 | medical_limit: row['medical_limit'], 28 | elderly_income_basis: row['elderly_income_basis'], 29 | elderly_asset_basis: row['elderly_asset_basis'], 30 | elderly_capita_basis: row['elderly_capita_basis'], 31 | elderly_household_basis: row['elderly_household_basis'], 32 | elderly_limit: row['elderly_limit'], 33 | care_income_basis: row['care_income_basis'], 34 | care_asset_basis: row['care_asset_basis'], 35 | care_capita_basis: row['care_capita_basis'], 36 | care_household_basis: row['care_household_basis'], 37 | care_limit: row['care_limit'] 38 | ) 39 | 40 | PaymentTargetMonth::CALENDAR.each do |month_name_symbol, month| 41 | month_name = month_name_symbol.to_s 42 | 43 | if row[month_name] == 'TRUE' 44 | year = month >= PaymentTargetMonth::CALENDAR[:april] ? row['year'] : row['year'].next 45 | insurance.payment_target_months.create!(month: Time.zone.parse("#{year}-#{format('%02d', month)}-01")) 46 | end 47 | end 48 | end 49 | end 50 | 51 | # rubocop:enable Metrics/BlockLength 52 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: postgres 5 | volumes: 6 | - ./tmp/db:/var/lib/postgresql/data 7 | environment: 8 | POSTGRES_HOST_AUTH_METHOD: trust 9 | ports: 10 | - "5433:5432" 11 | chrome: 12 | image: selenium/standalone-chrome:latest 13 | ports: 14 | - "4444:4444" 15 | cypress: 16 | image: "cypress/included:9.4.1" 17 | depends_on: 18 | - web 19 | environment: 20 | - CYPRESS_baseUrl=http://web:3000 21 | working_dir: /e2e 22 | volumes: 23 | - ./e2e:/e2e 24 | web: 25 | build: . 26 | command: tail -f /dev/null 27 | volumes: 28 | - .:/quitcost 29 | - bundle:/quitcost/vendor/bundle 30 | - node_modules:/quitcost/node_modules 31 | - tmp:/quitcost/tmp 32 | ports: 33 | - "3000:3000" 34 | - "3035:3035" 35 | # Ports required for debugging 36 | - "1234:1234" 37 | - "26168:26168" 38 | environment: 39 | BINDING: "0.0.0.0" 40 | depends_on: 41 | - db 42 | - chrome 43 | volumes: 44 | node_modules: 45 | bundle: 46 | tmp: 47 | -------------------------------------------------------------------------------- /e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": false, 3 | "supportFile": false, 4 | "viewportWidth": 1440, 5 | "viewportHeight": 960 6 | } 7 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | setup: 2 | addons: 3 | - plan: heroku-postgresql 4 | as: DATABASE 5 | build: 6 | docker: 7 | web: Dockerfile 8 | config: 9 | RAILS_ENV: production 10 | RAILS_SERVE_STATIC_FILES: enabled 11 | release: 12 | image: web 13 | command: 14 | - rake db:migrate 15 | run: 16 | web: bundle exec puma -C config/puma.rb 17 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quitcost", 3 | "private": true, 4 | "scripts": { 5 | "lint": "run-p lint:*", 6 | "lint:eslint": "eslint 'app/javascript/**/*.{js,vue}' --max-warnings=0", 7 | "lint:prettier": "prettier app/javascript/**/*.{js,vue} --check", 8 | "test": "jest --passWithNoTests", 9 | "test:unit": "jest --passWithNoTests /app/javascript/test/unit", 10 | "test:integrations": "jest --passWithNoTests /app/javascript/test/integrations" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 14 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 15 | "@fullhuman/postcss-purgecss": "4", 16 | "@hennge/vue3-pagination": "^1.0.17", 17 | "@rails/ujs": "^6.0.0", 18 | "@rails/webpacker": "5.4.3", 19 | "@vue/compiler-sfc": "^3.2.26", 20 | "@vueuse/core": "^7.6.2", 21 | "autoprefixer": "^10.4.0", 22 | "axios": "^0.26.0", 23 | "axios-jsonp": "^1.0.4", 24 | "choices.js": "^10.1.0", 25 | "date-fns": "^2.28.0", 26 | "maska": "^1.5.0", 27 | "postcss": "^8.4.5", 28 | "postcss-loader": "4", 29 | "sweetalert2": "^11.3.6", 30 | "tailwindcss": "^3.0.7", 31 | "vee-validate": "^4.5.8", 32 | "vue": "^3.2.26", 33 | "vue-composable": "^1.0.0-beta.24", 34 | "vue-loader": "^17.0.0", 35 | "vue-router": "4", 36 | "webpack": "^4.46.0", 37 | "webpack-cli": "^3.3.12", 38 | "yup": "^0.32.11" 39 | }, 40 | "version": "0.1.0", 41 | "devDependencies": { 42 | "@testing-library/dom": "^8.11.3", 43 | "@testing-library/vue": "^6.5.1", 44 | "@vue/test-utils": "^2.0.0-rc.17", 45 | "@vue/vue3-jest": "27.0.0-alpha.4", 46 | "babel-jest": "^27.5.1", 47 | "eslint": "^8.5.0", 48 | "eslint-config-prettier": "^8.3.0", 49 | "eslint-plugin-cypress": "^2.12.1", 50 | "eslint-plugin-jest": "^26.1.1", 51 | "eslint-plugin-vue": "^8.2.0", 52 | "eslint-webpack-plugin": "^3.1.1", 53 | "jest": "^27.5.1", 54 | "npm-run-all": "^4.1.5", 55 | "prettier": "2.5.1", 56 | "prettier-config-standard": "^4.0.0", 57 | "prettier-plugin-tailwindcss": "^0.1.8", 58 | "webpack-dev-server": "^3" 59 | }, 60 | "jest": { 61 | "roots": [ 62 | "/app/javascript/test" 63 | ], 64 | "moduleFileExtensions": [ 65 | "js", 66 | "vue" 67 | ], 68 | "transform": { 69 | "^.+\\.js$": "babel-jest", 70 | "^.+\\.vue$": "@vue/vue3-jest" 71 | }, 72 | "testEnvironment": "jsdom", 73 | "moduleNameMapper": { 74 | "^@/(.*)$": "/app/javascript/src/$1", 75 | "^components/(.*)$": "/app/javascript/src/components/$1", 76 | "^composables/(.*)$": "/app/javascript/src/composables/$1", 77 | "^store/(.*)$": "/app/javascript/src/store/$1", 78 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/app/javascript/test/mocks/fileMock.js" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('postcss-import'), 5 | require('postcss-flexbugs-fixes'), 6 | require('postcss-preset-env')({ 7 | autoprefixer: { 8 | flexbox: 'no-2009' 9 | }, 10 | stage: 3 11 | }) 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/factories/insurance_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :insurance_form do 5 | sequence(:year, 2000) 6 | local_gov_code { JpLocalGov::Random.code } 7 | medical_income_basis { rand(0.00..100.00).round(2) } 8 | medical_asset_basis { rand(0.00..100.00).round(2) } 9 | medical_capita_basis { rand(0..100_000) } 10 | medical_household_basis { rand(0..100_000) } 11 | medical_limit { rand(0..100_000) } 12 | elderly_income_basis { rand(0.00..100.00).round(2) } 13 | elderly_asset_basis { rand(0.00..100.00).round(2) } 14 | elderly_capita_basis { rand(0..100_000) } 15 | elderly_household_basis { rand(0..100_000) } 16 | elderly_limit { rand(0..100_000) } 17 | care_income_basis { rand(0.00..100.00).round(2) } 18 | care_asset_basis { rand(0.00..100.00).round(2) } 19 | care_capita_basis { rand(0..100_000) } 20 | care_household_basis { rand(0..100_000) } 21 | care_limit { rand(0..100_000) } 22 | month1 { false } 23 | month2 { false } 24 | month3 { false } 25 | month4 { false } 26 | month5 { false } 27 | month6 { false } 28 | month7 { false } 29 | month8 { false } 30 | month9 { false } 31 | month10 { false } 32 | month11 { false } 33 | month12 { false } 34 | 35 | trait(:all_months_are_target) do 36 | month1 { true } 37 | month2 { true } 38 | month3 { true } 39 | month4 { true } 40 | month5 { true } 41 | month6 { true } 42 | month7 { true } 43 | month8 { true } 44 | month9 { true } 45 | month10 { true } 46 | month11 { true } 47 | month12 { true } 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/factories/insurances.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :insurance do 5 | sequence(:year, 2000) 6 | local_gov_code { JpLocalGov::Random.code } 7 | medical_income_basis { rand(0.00..100.00).round(2) } 8 | medical_asset_basis { rand(0.00..100.00).round(2) } 9 | medical_capita_basis { rand(0..100_000) } 10 | medical_household_basis { rand(0..100_000) } 11 | medical_limit { rand(0..100_000) } 12 | elderly_income_basis { rand(0.00..100.00).round(2) } 13 | elderly_asset_basis { rand(0.00..100.00).round(2) } 14 | elderly_capita_basis { rand(0..100_000) } 15 | elderly_household_basis { rand(0..100_000) } 16 | elderly_limit { rand(0..100_000) } 17 | care_income_basis { rand(0.00..100.00).round(2) } 18 | care_asset_basis { rand(0.00..100.00).round(2) } 19 | care_capita_basis { rand(0..100_000) } 20 | care_household_basis { rand(0..100_000) } 21 | care_limit { rand(0..100_000) } 22 | 23 | trait(:with_payment_target_months) do 24 | transient do 25 | months { PaymentTargetMonth::CALENDAR.values } 26 | end 27 | 28 | after(:create) do |insurance, evaluator| 29 | insurance.payment_target_months = evaluator.months.map do |month| 30 | year = month >= PaymentTargetMonth::CALENDAR[:april] ? insurance.year : insurance.year.next 31 | build(:payment_target_month, month: Time.zone.parse("#{year}-#{format('%02d', month)}-01")) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/factories/payment_target_months.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :payment_target_month do 5 | month { Time.current.beginning_of_month } 6 | insurance 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/pensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :pension do 5 | sequence(:year, 2000) 6 | contribution { rand(0..30_000) } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :user do 5 | sequence(:email) { |n| "test#{n}@example.com" } 6 | sequence(:password) { |n| "password#{n}" } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/models/concerns/local_tax_law_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'LocalTaxLaw' do 6 | describe '.calc_tax_base' do 7 | subject { LocalTaxLaw.calc_tax_base { 9999 } } 8 | it { is_expected.to eq 9000 } 9 | end 10 | 11 | describe '.calc_determined_amount' do 12 | subject { LocalTaxLaw.calc_determined_amount { 9999 } } 13 | it { is_expected.to eq 9900 } 14 | end 15 | 16 | describe 'calc_installments' do 17 | context 'when any flags are NOT given' do 18 | subject { LocalTaxLaw.calc_installments(600_000, [6, 7, 8, 9, 10, 11, 12]) } 19 | context 'when dues size is positive' do 20 | it "summarises the fraction of each month's fee of less than ¥1,000 the first month" do 21 | expect(subject).to eq [90_000, 85_000, 85_000, 85_000, 85_000, 85_000, 85_000] 22 | expect(subject.sum).to eq 600_000 23 | end 24 | end 25 | end 26 | 27 | context 'when flags are given' do 28 | subject do 29 | LocalTaxLaw.calc_installments(600_000, [6, 7, 8, 9, 10, 11, 12], municipal_ordinance: municipal_ordinance, special_insurance: special_insurance) 30 | end 31 | 32 | context 'when municipal_ordinance is given' do 33 | let!(:municipal_ordinance) { true } 34 | let!(:special_insurance) { false } 35 | it 'returns fee rounded down 100 yen except for first-month' do 36 | expect(subject).to eq [85_800, 85_700, 85_700, 85_700, 85_700, 85_700, 85_700] 37 | expect(subject.sum).to eq 600_000 38 | end 39 | end 40 | 41 | context 'when special_insurance is given' do 42 | let!(:municipal_ordinance) { false } 43 | let!(:special_insurance) { true } 44 | it 'returns fee rounded down 100 yen except for first-month' do 45 | expect(subject).to eq [85_800, 85_700, 85_700, 85_700, 85_700, 85_700, 85_700] 46 | expect(subject.sum).to eq 600_000 47 | end 48 | end 49 | 50 | context 'when municipal_ordinance and special_insurance are given' do 51 | let!(:municipal_ordinance) { true } 52 | let!(:special_insurance) { true } 53 | it 'returns fee rounded down 100 yen except for first-month' do 54 | expect(subject).to eq [85_800, 85_700, 85_700, 85_700, 85_700, 85_700, 85_700] 55 | expect(subject.sum).to eq 600_000 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/models/concerns/month_iterable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'MonthIterable' do 6 | describe '#months_between' do 7 | include MonthIterable 8 | subject { months_between(from: from, to: to) } 9 | 10 | context 'when valid parameters are given' do 11 | context 'when the start month > end month' do 12 | let!(:from) { Time.zone.parse('2022-04-01') } 13 | let!(:to) { Time.zone.parse('2023-04-01') } 14 | it 'returns each month from the start month to 1 month before the end month' do 15 | months = [ 16 | Time.zone.parse('2022-04-01'), 17 | Time.zone.parse('2022-05-01'), 18 | Time.zone.parse('2022-06-01'), 19 | Time.zone.parse('2022-07-01'), 20 | Time.zone.parse('2022-08-01'), 21 | Time.zone.parse('2022-09-01'), 22 | Time.zone.parse('2022-10-01'), 23 | Time.zone.parse('2022-11-01'), 24 | Time.zone.parse('2022-12-01'), 25 | Time.zone.parse('2023-01-01'), 26 | Time.zone.parse('2023-02-01'), 27 | Time.zone.parse('2023-03-01') 28 | ] 29 | expect(subject).to eq months 30 | end 31 | end 32 | 33 | context 'when the start month = end month' do 34 | let!(:from) { Time.zone.parse('2022-04-01') } 35 | let!(:to) { Time.zone.parse('2022-04-01') } 36 | it { is_expected.to eq [] } 37 | end 38 | 39 | context 'when the start month < end month' do 40 | let!(:from) { Time.zone.parse('2023-04-01') } 41 | let!(:to) { Time.zone.parse('2022-04-01') } 42 | it { is_expected.to eq [] } 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/models/insurance_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Insurance, type: :model do 6 | describe 'month_name_is_target?' do 7 | let!(:insurance_pay_only_december) { create(:insurance, :with_payment_target_months, months: [12]) } 8 | 9 | context 'when insurance has specified month as a payment target' do 10 | it 'returns true' do 11 | expect(insurance_pay_only_december.payment_target_months.december_is_target?).to be_truthy 12 | end 13 | end 14 | 15 | context 'when specified month is NOT payment target' do 16 | it 'returns false' do 17 | expect(insurance_pay_only_december.payment_target_months.january_is_target?).to be_falsey 18 | end 19 | end 20 | end 21 | 22 | describe '.rate' do 23 | subject { Insurance.rate(year: 2022, local_gov_code: '012033') } 24 | 25 | context 'when Insurance for specified year and local government is exist' do 26 | before { create(:insurance, year: 2022, local_gov_code: '012033') } 27 | it 'returns Insurance record, `year` is specified year and `local_gov_code` is specified code' do 28 | expect(subject.year).to eq 2022 29 | expect(subject.local_gov_code).to eq '012033' 30 | end 31 | end 32 | 33 | context 'when Insurance for specified year and local government is NOT exist' do 34 | context 'when Insurance for specified local government and other year is exist' do 35 | before { create(:insurance, year: 2021, local_gov_code: '012033') } 36 | it 'returns Insurance record, `year` is other year and `local_gov_code` is specified code' do 37 | expect(subject.year).to eq 2021 38 | expect(subject.local_gov_code).to eq '012033' 39 | end 40 | end 41 | 42 | context 'when Insurance for specified local government is NOT exist' do 43 | context 'when Insurance for specified year and prefecture capital of specified local government is exist' do 44 | before { create(:insurance, year: 2022, local_gov_code: '011002') } 45 | it 'returns Insurance record, `year` is specified year and `local_gov_code` is prefecture capital' do 46 | expect(subject.year).to eq 2022 47 | expect(subject.local_gov_code).to eq '011002' 48 | end 49 | end 50 | 51 | context 'when Insurance for specified year and prefecture capital of specified local government is NOT exist' do 52 | before { create(:insurance, year: 2021, local_gov_code: '011002') } 53 | it 'returns Insurance record, `year` is other year and `local_gov_code` is prefecture capital' do 54 | expect(subject.year).to eq 2021 55 | expect(subject.local_gov_code).to eq '011002' 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/models/pension_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Pension, type: :model do 6 | describe '#validate' do 7 | describe ':year' do 8 | subject { pension.errors[:year] } 9 | before { pension.valid? } 10 | let(:pension) { build(:pension, year: year) } 11 | 12 | context 'when year is nil' do 13 | let(:year) { nil } 14 | it { is_expected.to include 'を入力してください' } 15 | end 16 | 17 | context 'when year is negative' do 18 | let(:year) { -1 } 19 | it { is_expected.to include 'は0以上の値にしてください' } 20 | end 21 | 22 | context 'when year is 0' do 23 | let(:year) { 0 } 24 | it { is_expected.to be_empty } 25 | end 26 | 27 | context 'when year is 1' do 28 | let(:year) { 1 } 29 | it { is_expected.to be_empty } 30 | end 31 | 32 | context 'when year is decimal' do 33 | let(:year) { 1.0 } 34 | it { is_expected.to include 'は整数で入力してください' } 35 | end 36 | 37 | context 'when year is string' do 38 | let(:year) { '2000' } 39 | it { is_expected.to include 'は数値で入力してください' } 40 | end 41 | end 42 | 43 | describe ':contribution' do 44 | subject { pension.errors[:contribution] } 45 | before { pension.valid? } 46 | let(:pension) { build(:pension, contribution: contribution) } 47 | 48 | context 'when contribution is nil' do 49 | let(:contribution) { nil } 50 | it { is_expected.to include 'を入力してください' } 51 | end 52 | 53 | context 'when contribution is negative' do 54 | let(:contribution) { -1 } 55 | it { is_expected.to include 'は0以上の値にしてください' } 56 | end 57 | 58 | context 'when contribution is 0' do 59 | let(:contribution) { 0 } 60 | it { is_expected.to be_empty } 61 | end 62 | 63 | context 'when contribution is 1' do 64 | let(:contribution) { 1 } 65 | it { is_expected.to be_empty } 66 | end 67 | 68 | context 'when contribution is decimal' do 69 | let(:contribution) { 1.0 } 70 | it { is_expected.to include 'は整数で入力してください' } 71 | end 72 | 73 | context 'when contribution is string' do 74 | let(:contribution) { '2000' } 75 | it { is_expected.to include 'は数値で入力してください' } 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/models/simulation/insurance/care_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Simulation::Insurance::Care, type: :model do 6 | describe '.calc' do 7 | subject { Simulation::Insurance::Care.calc(year: year, local_gov_code: local_gov_code, income: income, age: age) } 8 | let!(:year) { 2021 } 9 | let!(:local_gov_code) { '131016' } 10 | let!(:insurance) do 11 | create( 12 | :insurance, 13 | year: 2021, 14 | local_gov_code: '131016', 15 | care_income_basis: 1.21, 16 | care_capita_basis: 14_200, 17 | care_household_basis: 10_600, 18 | care_limit: 170_000 19 | ) 20 | end 21 | 22 | context 'when age is less than 40' do 23 | context 'when age is less than 40' do 24 | let!(:age) { 39 } 25 | let!(:income) { 14_380_000 } 26 | it { is_expected.to eq 0 } 27 | end 28 | end 29 | 30 | context 'when age is between 40 and 64' do 31 | context 'when age is 40' do 32 | let!(:age) { 40 } 33 | context 'when calculate result is less than care limit' do 34 | let!(:income) { 14_379_958 } 35 | it { is_expected.to eq 169_999 } 36 | end 37 | 38 | context 'when calculate result is care_limit' do 39 | let!(:income) { 14_380_000 } 40 | it { is_expected.to eq insurance.care_limit } 41 | end 42 | 43 | context 'when calculation result is over care_limit' do 44 | let!(:income) { 14_380_001 } 45 | it { is_expected.to eq insurance.care_limit } 46 | end 47 | end 48 | 49 | context 'when age is 64' do 50 | let!(:age) { 64 } 51 | context 'when calculate result is less than care limit' do 52 | let!(:income) { 14_379_958 } 53 | it { is_expected.to eq 169_999 } 54 | end 55 | 56 | context 'when calculate result is care_limit' do 57 | let!(:income) { 14_380_000 } 58 | it { is_expected.to eq insurance.care_limit } 59 | end 60 | 61 | context 'when calculation result is over care_limit' do 62 | let!(:income) { 14_380_001 } 63 | it { is_expected.to eq insurance.care_limit } 64 | end 65 | end 66 | end 67 | 68 | context 'when age is 65 or more' do 69 | let!(:income) { 14_380_000 } 70 | context 'age is 65' do 71 | let!(:age) { 65 } 72 | it { is_expected.to eq 0 } 73 | end 74 | 75 | context 'when age is over 65' do 76 | let!(:age) { 66 } 77 | it { is_expected.to eq 0 } 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/models/simulation/insurance/elderly_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Simulation::Insurance::Elderly, type: :model do 6 | describe '.calc' do 7 | subject { Simulation::Insurance::Elderly.calc(year: year, local_gov_code: local_gov_code, income: income, age: age) } 8 | let!(:year) { 2021 } 9 | let!(:local_gov_code) { '131016' } 10 | let!(:insurance) do 11 | create( 12 | :insurance, 13 | year: 2021, 14 | local_gov_code: '131016', 15 | elderly_income_basis: 2.04, 16 | elderly_capita_basis: 11_000, 17 | elderly_household_basis: 5_600, 18 | elderly_limit: 190_000 19 | ) 20 | end 21 | 22 | context 'when age is less than 75' do 23 | let!(:age) { 74 } 24 | 25 | context 'when calculate result is less than elderly limit' do 26 | let!(:income) { 10_879_975 } 27 | it { is_expected.to eq 189_999 } 28 | end 29 | 30 | context 'when calculate result is elderly_limit' do 31 | let!(:income) { 10_880_000 } 32 | it { is_expected.to eq insurance.elderly_limit } 33 | end 34 | 35 | context 'when calculation result is over elderly_limit' do 36 | let!(:income) { 10_880_001 } 37 | it { is_expected.to eq insurance.elderly_limit } 38 | end 39 | end 40 | 41 | context 'when age is 75 or more' do 42 | let!(:income) { 10_880_000 } 43 | context 'age is 75' do 44 | let!(:age) { 75 } 45 | it { is_expected.to eq 0 } 46 | end 47 | 48 | context 'when age is over 75' do 49 | let!(:age) { 76 } 50 | it { is_expected.to eq 0 } 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/models/simulation/insurance/medical_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Simulation::Insurance::Medical, type: :model do 6 | describe '.calc' do 7 | subject { Simulation::Insurance::Medical.calc(year: year, local_gov_code: local_gov_code, income: income, age: age) } 8 | let!(:year) { 2021 } 9 | let!(:local_gov_code) { '131016' } 10 | let!(:insurance) do 11 | create( 12 | :insurance, 13 | year: 2021, 14 | local_gov_code: '131016', 15 | medical_income_basis: 7.25, 16 | medical_capita_basis: 37_300, 17 | medical_household_basis: 12_700, 18 | medical_limit: 630_000 19 | ) 20 | end 21 | 22 | context 'when age is less than 75' do 23 | let!(:age) { 74 } 24 | 25 | context 'when calculate result is less than medical limit' do 26 | let!(:income) { 10_379_993 } 27 | it { is_expected.to eq 629_999 } 28 | end 29 | 30 | context 'when calculate result is medical_limit' do 31 | let!(:income) { 10_380_000 } 32 | it { is_expected.to eq insurance.medical_limit } 33 | end 34 | 35 | context 'when calculation result is over medical_limit' do 36 | let!(:income) { 10_380_001 } 37 | it { is_expected.to eq insurance.medical_limit } 38 | end 39 | end 40 | 41 | context 'when age is 75 or more' do 42 | let!(:income) { 10_380_000 } 43 | context 'age is 75' do 44 | let!(:age) { 75 } 45 | it { is_expected.to eq 0 } 46 | end 47 | 48 | context 'when age is over 75' do 49 | let!(:age) { 76 } 50 | it { is_expected.to eq 0 } 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/models/simulation/parameter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'Simulation::Parameter' do 6 | describe '#salary_table' do 7 | subject { Simulation::Parameter.new(params).salary_table } 8 | let!(:params) do 9 | { 10 | retirement_month: '2022-06-01', 11 | employment_month: '2022-12-01', 12 | age: '20', 13 | prefecture: '東京都', 14 | city: '千代田区', 15 | simulation_date: simulation_date, 16 | previous_salary: '5000000', 17 | salary: '5500000', 18 | scheduled_salary: '5800000', 19 | previous_social_insurance: '730000', 20 | social_insurance: '825000', 21 | scheduled_social_insurance: '870000' 22 | } 23 | end 24 | 25 | context 'when simulation_date is March' do 26 | let!(:simulation_date) { '2022-03-01' } 27 | it { is_expected.to eq({ 2020 => 5_000_000, 2021 => 5_500_000, 2022 => 5_800_000 }) } 28 | end 29 | 30 | context 'when simulation_date is April' do 31 | let!(:simulation_date) { '2022-04-01' } 32 | it { is_expected.to eq({ 2021 => 5_000_000, 2022 => 5_500_000, 2023 => 5_800_000 }) } 33 | end 34 | end 35 | 36 | describe '#social_insurance_table' do 37 | subject { Simulation::Parameter.new(params).social_insurance_table } 38 | let!(:params) do 39 | { 40 | retirement_month: '2022-06-01', 41 | employment_month: '2022-12-01', 42 | age: '20', 43 | prefecture: '東京都', 44 | city: '千代田区', 45 | simulation_date: simulation_date, 46 | previous_salary: '5000000', 47 | salary: '5500000', 48 | scheduled_salary: '5800000', 49 | previous_social_insurance: '730000', 50 | social_insurance: '825000', 51 | scheduled_social_insurance: '870000' 52 | } 53 | end 54 | 55 | context 'when simulation_date is March' do 56 | let!(:simulation_date) { '2022-03-01' } 57 | it { is_expected.to eq({ 2020 => 730_000, 2021 => 825_000, 2022 => 870_000 }) } 58 | end 59 | 60 | context 'when simulation_date is April' do 61 | let!(:simulation_date) { '2022-04-01' } 62 | it { is_expected.to eq({ 2021 => 730_000, 2022 => 825_000, 2023 => 870_000 }) } 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/models/simulation/residence/june_start_financial_year_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'Simulation::Residence::JuneStartFinancialYear', type: :model do 6 | using Simulation::Residence::JuneStartFinancialYear 7 | 8 | describe '#beginning_of_residence_fy' do 9 | subject { time.beginning_of_residence_fy } 10 | let!(:time) { Time.zone.parse("2022-#{month}-01") } 11 | 12 | context 'when month is January' do 13 | let!(:month) { '1' } 14 | it { is_expected.to eq Time.zone.parse('2021-06-01') } 15 | end 16 | 17 | context 'when month is March' do 18 | let!(:month) { '3' } 19 | it { is_expected.to eq Time.zone.parse('2021-06-01') } 20 | end 21 | 22 | context 'when month is April' do 23 | let!(:month) { '4' } 24 | it { is_expected.to eq Time.zone.parse('2021-06-01') } 25 | end 26 | 27 | context 'when month is May' do 28 | let!(:month) { '5' } 29 | it { is_expected.to eq Time.zone.parse('2021-06-01') } 30 | end 31 | 32 | context 'when month is June' do 33 | let!(:month) { '6' } 34 | it { is_expected.to eq Time.zone.parse('2022-06-01') } 35 | end 36 | 37 | context 'when month is December' do 38 | let!(:month) { '12' } 39 | it { is_expected.to eq Time.zone.parse('2022-06-01') } 40 | end 41 | end 42 | 43 | describe '#residence_financial_year' do 44 | subject { time.residence_financial_year } 45 | let!(:time) { Time.zone.parse("2022-#{month}-01") } 46 | 47 | context 'when month is January' do 48 | let!(:month) { '1' } 49 | it { is_expected.to eq 2021 } 50 | end 51 | 52 | context 'when month is March' do 53 | let!(:month) { '3' } 54 | it { is_expected.to eq 2021 } 55 | end 56 | 57 | context 'when month is April' do 58 | let!(:month) { '4' } 59 | it { is_expected.to eq 2021 } 60 | end 61 | 62 | context 'when month is May' do 63 | let!(:month) { '5' } 64 | it { is_expected.to eq 2021 } 65 | end 66 | 67 | context 'when month is June' do 68 | let!(:month) { '6' } 69 | it { is_expected.to eq 2022 } 70 | end 71 | 72 | context 'when month is December' do 73 | let!(:month) { '12' } 74 | it { is_expected.to eq 2022 } 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/requests/api/admin/insurances_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'API::Admin::Insurances', type: :request do 6 | let!(:user) { create(:user) } 7 | 8 | describe 'GET /api/admin/insurances' do 9 | before { create_list(:insurance, 21) } 10 | context 'when client sign in as admin' do 11 | it 'returns http success and insurance json' do 12 | sign_in user 13 | 14 | get api_admin_insurances_path(format: :json) 15 | expect(response).to have_http_status(:ok) 16 | 17 | json = JSON.parse(response.body) 18 | expect(json['insurance'].length).to eq(20) 19 | end 20 | end 21 | 22 | context 'when client DOES NOT sign in as admin' do 23 | it 'returns http unauthorized' do 24 | get api_admin_insurances_path(format: :json) 25 | expect(response).to have_http_status(:unauthorized) 26 | end 27 | end 28 | end 29 | 30 | describe 'DELETE /api/admin/insurances/:id' do 31 | let!(:insurance) { create(:insurance) } 32 | context 'when client sign in as admin' do 33 | it 'should delete a record and returns http success' do 34 | sign_in user 35 | expect { delete api_admin_insurance_path(insurance.id) }.to change(Insurance, :count).by(-1) 36 | expect(response).to have_http_status(:ok) 37 | end 38 | end 39 | 40 | context 'when client DOES NOT sign in as admin' do 41 | it 'should NOT delete a record and returns http unauthorized' do 42 | delete api_admin_insurance_path(insurance.id) 43 | expect(response).to have_http_status(:unauthorized) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/requests/api/admin/pentions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'API::Admin::Pensions', type: :request do 6 | let!(:user) { create(:user) } 7 | 8 | describe 'GET /api/admin/pensions' do 9 | before { create_list(:pension, 21) } 10 | context 'when client sign in as admin' do 11 | it 'returns http success and pension json' do 12 | sign_in user 13 | 14 | get api_admin_pensions_path(format: :json) 15 | expect(response).to have_http_status(:ok) 16 | 17 | json = JSON.parse(response.body) 18 | expect(json['pensions'].length).to eq(20) 19 | end 20 | end 21 | 22 | context 'when client DOES NOT sign in as admin' do 23 | it 'returns http unauthorized' do 24 | get api_admin_pensions_path(format: :json) 25 | expect(response).to have_http_status(:unauthorized) 26 | end 27 | end 28 | end 29 | 30 | describe 'DELETE /api/pensions/:id' do 31 | let!(:pension) { create(:pension) } 32 | context 'when client sign in as admin' do 33 | it 'should delete a record and returns http success' do 34 | sign_in user 35 | expect { delete api_admin_pension_path(pension.id) }.to change(Pension, :count).by(-1) 36 | expect(response).to have_http_status(:ok) 37 | end 38 | end 39 | 40 | context 'when client DOES NOT sign in as admin' do 41 | it 'should NOT delete a record and returns http unauthorized' do 42 | delete api_admin_pension_path(pension.id) 43 | expect(response).to have_http_status(:unauthorized) 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/requests/api/simulations_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'API::Admin::Simulations', type: :request do 6 | describe 'GET /api/simulations?q' do 7 | before do 8 | create( 9 | :insurance, 10 | :with_payment_target_months, 11 | months: [6, 7, 8, 9, 10, 11, 12, 1, 2, 3], 12 | year: 2021, 13 | local_gov_code: '131016', 14 | medical_income_basis: 7.25, 15 | medical_capita_basis: 0, 16 | medical_household_basis: 37_300, 17 | medical_limit: 630_000, 18 | elderly_income_basis: 2.04, 19 | elderly_capita_basis: 0, 20 | elderly_household_basis: 11_000, 21 | elderly_limit: 190_000, 22 | care_income_basis: 1.21, 23 | care_capita_basis: 0, 24 | care_household_basis: 14_200, 25 | care_limit: 170_000 26 | ) 27 | create( 28 | :insurance, 29 | :with_payment_target_months, 30 | months: [6, 7, 8, 9, 10, 11, 12, 1, 2, 3], 31 | year: 2022, 32 | local_gov_code: '131016', 33 | medical_income_basis: 7.25, 34 | medical_capita_basis: 0, 35 | medical_household_basis: 37_300, 36 | medical_limit: 630_000, 37 | elderly_income_basis: 2.04, 38 | elderly_capita_basis: 0, 39 | elderly_household_basis: 11_000, 40 | elderly_limit: 190_000, 41 | care_income_basis: 1.21, 42 | care_capita_basis: 0, 43 | care_household_basis: 14_200, 44 | care_limit: 170_000 45 | ) 46 | create(:pension, year: 2021, contribution: 16_610) 47 | create(:pension, year: 2022, contribution: 16_590) 48 | end 49 | 50 | context 'when params is valid' do 51 | it 'returns http ok and expected json' do 52 | params = { 53 | retirement_month: '2022-03-01', 54 | employment_month: '2022-06-01', 55 | prefecture: '東京都', 56 | city: '千代田区', 57 | age: '40', 58 | simulation_date: '2022-02-11', 59 | previous_salary: '2_000_000', 60 | salary: '2_000_000', 61 | scheduled_salary: '2_000_000', 62 | previous_social_insurance: '100_000', 63 | social_insurance: '100_000', 64 | scheduled_social_insurance: '100_000' 65 | } 66 | get api_simulations_path(format: :json), params: params 67 | expect(response).to have_http_status(:ok) 68 | 69 | json = JSON.parse(response.body)['simulation'] 70 | expect(json.keys).to include('retirement_month', 'employment_month', 'grand_total', 'sub_total', 'monthly_payment') 71 | end 72 | end 73 | 74 | context 'when params is NOT valid' do 75 | it 'returns http bad_request' do 76 | params = { 77 | retirement_month: '2022-03-01', 78 | employment_month: '2022-06-01', 79 | prefecture: '東京都', 80 | city: '千代田区', 81 | age: '40', 82 | simulation_date: '2022-02-11', 83 | previous_salary: '', 84 | salary: '', 85 | scheduled_salary: '', 86 | previous_social_insurance: '', 87 | social_insurance: '', 88 | scheduled_social_insurance: '' 89 | } 90 | get api_simulations_path(format: :json), params: params 91 | expect(response).to have_http_status(:bad_request) 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec/support/custom_validator_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CustomValidatorHelper 4 | def build_validator_mock(attribute: nil, record: nil, validator: nil, options: nil) 5 | record ||= :record 6 | attribute ||= :attribute 7 | validator ||= described_class.to_s.underscore.gsub(/_validator\Z/, '').to_sym 8 | options ||= true 9 | 10 | Struct.new(attribute, *record, keyword_init: true) do 11 | include ActiveModel::Validations 12 | 13 | def self.name 14 | 'DummyModel' 15 | end 16 | 17 | validates attribute, validator => options 18 | end 19 | end 20 | end 21 | 22 | RSpec.configure do |config| 23 | config.include CustomValidatorHelper, type: :model 24 | end 25 | -------------------------------------------------------------------------------- /spec/system/pension_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe 'Pension', type: :system, js: true do 6 | let!(:user) { create(:user) } 7 | 8 | describe 'index' do 9 | scenario 'visiting the index before sign in' do 10 | visit admin_pensions_path 11 | assert_current_path new_user_session_path 12 | assert_text 'ログインもしくはアカウント登録してください。' 13 | end 14 | 15 | scenario 'visiting the index' do 16 | sign_in user 17 | visit admin_pensions_path 18 | 19 | expect(page).to have_content '国民年金保険料一覧' 20 | end 21 | end 22 | 23 | describe 'create' do 24 | let(:pension) { build(:pension) } 25 | scenario 'create a new record' do 26 | sign_in user 27 | visit admin_pensions_path 28 | click_link '新規登録' 29 | 30 | expect(page).to have_content '国民年金保険料登録' 31 | 32 | within 'form[name=pension]' do 33 | fill_in '年度', with: pension.year 34 | fill_in '保険料', with: pension.contribution 35 | end 36 | 37 | expect { click_button '登録' }.to change { Pension.count }.from(0).to(1) 38 | assert_current_path admin_pensions_path 39 | assert_text '保険料率を保存しました。' 40 | end 41 | end 42 | 43 | describe 'update' do 44 | before { @pension = create(:pension, year: 2022, contribution: 16_540) } 45 | scenario 'update a existing record' do 46 | sign_in user 47 | visit admin_pensions_path 48 | click_link '国民年金保険料編集' 49 | 50 | expect(page).to have_content '国民年金保険料編集' 51 | 52 | fill_in '年度', with: 2023 53 | fill_in '保険料', with: 16_600 54 | 55 | click_button '更新' 56 | assert_current_path admin_pensions_path 57 | assert_text '保険料率を更新しました。' 58 | end 59 | end 60 | 61 | describe 'destroy' do 62 | before { @pension = create(:pension) } 63 | scenario 'destroy a existing record' do 64 | sign_in user 65 | visit admin_pensions_path 66 | 67 | expect do 68 | all('tbody td')[1].click 69 | accept_confirm 70 | assert_text '保険料率を削除しました。' 71 | sleep(1) 72 | end 73 | .to change { Pension.count }.from(1).to(0) 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/storage/.keep -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './app/views/**/*.html.slim', 4 | './app/helpers/**/*.rb', 5 | './app/javascript/**/*.js', 6 | './app/javascript/**/*.vue' 7 | ], 8 | theme: { 9 | extend: { 10 | colors: { 11 | primary: '#117766', 12 | secondary: '#DFEEE5', 13 | accent: '#FFB600', 14 | black: '#1B322F', 15 | gray: '#858585', 16 | boundaryBlack: 'rgba(27, 50, 47, 0.16)' 17 | }, 18 | fontFamily: { 19 | notojp: ['Noto Sans JP'] 20 | } 21 | } 22 | }, 23 | plugins: [] 24 | } 25 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/tmp/.keep -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/tmp/pids/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IkumaTadokoro/quitcost/dab8ce77f658504f8c9e2695da90cdc4a80b3687/vendor/.keep --------------------------------------------------------------------------------