├── .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 | 
4 |
5 | quitcostは**無職になったら**『**どんな**』個人負担が『**いくら**』増えるのかを簡単に知ることができるサービスです。
6 |
7 | 「転職のためにしばらく無職になる予定だけど、保険とか税金とか、何をいくら払えばいいかわからない!!」
8 |
9 | quitcostを使えば、年齢や住所、昨年の年収を回答することで、無職後に負担になる「税金」「保険」「年金」の料金を一括で知ることができます。
10 |
11 | ## 機能概要
12 |
13 | 
14 |
15 | - ユーザーはいくつかの質問に回答することで、無職期間にかかる「国民健康保険料」「国民年金」「住民税」の金額を計算することができます。
16 | - 管理者は計算に必要な「国民健康保険料の料率」と「国民年金保険料の料率」を一覧・登録・更新・削除することができます。
17 |
18 | ### スクリーンショット
19 |
20 | **ユーザー画面**
21 |
22 |
23 |
24 |
25 |
26 | 
27 |
28 | **管理者画面**
29 |
30 |
31 |
32 |
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 | 
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 |
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 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/javascript/src/components/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |
7 |
8 |
9 | 転職準備のために仕事をやめたいけど、お金が心配...。
10 |
11 | いくつかの質問に答えるだけで、無職期間で
どれだけ「保険」「税金」「年金」がかかるか計算できます
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
35 | 準備するもの(なくてもOK!)
36 |
37 |
38 |
39 | 額面の年収がだいたいいくらかわかれば計算できます!
40 |
41 |
42 |
43 |
44 |
45 | 住民税決定通知書
46 |
47 |

52 |
53 |
54 |
55 | 給与明細
56 |
57 |

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
84 |
--------------------------------------------------------------------------------
/app/javascript/src/components/LoadingAnimation.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/javascript/src/components/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | ご指定のページが見つかりません
8 |
9 |
12 | 404: Not Found
13 |
14 |
15 | お探しのページは一時的にアクセスができない状況にあるか、移動もしくは削除された可能性があります。また、URL、ファイル名にタイプミスがないか再度ご確認ください。
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/javascript/src/components/ProgressBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
33 |
--------------------------------------------------------------------------------
/app/javascript/src/components/ProgressStep.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ progress }}
4 | /
5 | {{ steps }}
6 |
7 |
8 |
9 |
24 |
--------------------------------------------------------------------------------
/app/javascript/src/components/SimulationForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
17 |
--------------------------------------------------------------------------------
/app/javascript/src/components/SimulationResultError.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
12 |
13 |
14 | 画面をリロードするか、時間をおいて、もう一度最初からお試しください。
15 |
16 |
17 | エラーが出続ける場合は以下の「Twitterから連絡」から @ikuma-t
18 | までご連絡ください。
19 |
20 |
21 |
39 |
40 |
41 |
42 |
43 |
44 |
56 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/Age.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ error }}
4 |
13 |
14 |
15 |
31 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/EmploymentMonth.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ error }}
6 |
16 |
17 | 計算可能な範囲:
18 | {{ `${from} ~ ${to}` }}
19 |
20 |
21 |
22 |
51 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/InsuranceCompleteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
44 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/PostalCode.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ error || addressError }}
6 |
16 |
17 | お住まいの地域: {{ address }}
18 |
19 |
20 |
21 |
68 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/PreviousSalary.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
18 | 昨昨年度の所得額は住民税決定通知書の「給与所得(所得金額調整控除後)」の値です
20 |
21 |
22 |
23 |
53 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/PreviousSocialInsurance.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
18 | 昨昨年度の社会保険料は、住民税決定通知書の「社会保険料」の値です
20 |
21 |
26 |
27 |
28 |
65 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/RetirementMonth.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ error }}
6 |
16 |
17 | 計算可能な範囲:
18 | {{ `${from} ~ ${to}` }}
19 |
20 |
21 |
22 |
49 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/Salary.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
18 | 昨年度の所得額は住民税決定通知書の「給与所得(所得金額調整控除後)」の値です
20 |
21 |
22 |
23 |
45 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/ScheduledSalary.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
18 | 今年度の所得額は「退職するまでの毎月の給与(満額)」と「賞与(満額)」の合計です
20 |
21 |
22 |
23 |
50 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/ScheduledSocialInsurance.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
33 |
38 |
39 |
40 |
73 |
--------------------------------------------------------------------------------
/app/javascript/src/components/simulation_form/SocialInsurance.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error }}
7 |
17 |
18 | 昨年度の社会保険料は、住民税決定通知書の「社会保険料」の値です
20 |
21 |
26 |
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
--------------------------------------------------------------------------------