diff --git a/app/admin/organization.rb b/app/admin/organization.rb index 35d3b9e9..d75678f8 100644 --- a/app/admin/organization.rb +++ b/app/admin/organization.rb @@ -324,6 +324,7 @@ f.input :activity_price f.input :activity_participations_form_min f.input :activity_participations_form_max + f.input :activity_participations_form_step, input_html: { min: 1 } translated_input(f, :activity_participations_form_details, hint: t("formtastic.hints.organization.activity_participations_demanded_annually_form_detail"), required: false, @@ -423,6 +424,7 @@ :activity_i18n_scope, :activity_participation_deletion_deadline_in_days, :activity_availability_limit_in_days, :activity_price, :activity_phone, :activity_participations_form_min, :activity_participations_form_max, + :activity_participations_form_step, :activity_participations_demanded_logic, :vat_number, :vat_membership_rate, :vat_activity_rate, :vat_shop_rate, :absences_billed, diff --git a/app/javascript/controllers/members/form_minmax_enforcer_controller.js b/app/javascript/controllers/members/form_minmax_enforcer_controller.js index 93c26bad..48a4b488 100644 --- a/app/javascript/controllers/members/form_minmax_enforcer_controller.js +++ b/app/javascript/controllers/members/form_minmax_enforcer_controller.js @@ -9,14 +9,16 @@ export default class extends Controller { let input = event.target let value = parseInt(input.value) - let minValue = input.getAttribute("min") - if (minValue && value < minValue) { - input.value = minValue - } + let minValue = parseInt(input.getAttribute("min")) || 0 + let maxValue = parseInt(input.getAttribute("max")) || Infinity + let step = parseInt(input.getAttribute("step")) || 1 - let maxValue = input.getAttribute("max") - if (maxValue && value > maxValue) { + if (value < minValue) { + input.value = minValue + } else if (value > maxValue) { input.value = maxValue + } else { + input.value = Math.round((value - minValue) / step) * step + minValue } } } diff --git a/app/models/organization.rb b/app/models/organization.rb index 05b056a6..eab116a4 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -96,6 +96,8 @@ class Organization < ApplicationRecord numericality: { greater_than_or_equal_to: 0 } validates :activity_participations_form_min, :activity_participations_form_max, numericality: { greater_than_or_equal_to: 0, allow_nil: true } + validates :activity_participations_form_step, + numericality: { greater_than_or_equal_to: 1 }, presence: true validate :activity_participations_demanded_logic_must_be_valid validate :basket_price_extra_dynamic_pricing_logic_must_be_valid validates :open_renewal_reminder_sent_after_in_days, diff --git a/app/views/members/members/form_modes/_membership.html.slim b/app/views/members/members/form_modes/_membership.html.slim index 96c83837..2dd17397 100644 --- a/app/views/members/members/form_modes/_membership.html.slim +++ b/app/views/members/members/form_modes/_membership.html.slim @@ -34,7 +34,7 @@ div data-controller="form-disabler form-min-value form-choice-excluder form-choi = ff.input :quantity, as: :numeric, label: basket_complement_label(ff.object.basket_complement), required: false, disabled: support_checked, wrapper_html: { class: ('disabled' if support_checked), data: { form_disabler_target: 'label' } }, input_html: { min: 0, data: { form_disabler_target: 'input', default_value: '0', action: 'blur->form-minmax-enforcer#enforce form-activity#updateInput form-pricing#refresh', activity: ff.object.basket_complement.activity_participations_demanded_annually }, class: 'order-1 dark:bg-black w-16 px-3 py-2 border border-gray-300 dark:border-gray-700 placeholder-gray-400 dark:placeholder-gray-600 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10' }, label_html: { class: 'order-2 ml-4' }, wrapper_class: 'mt-2 flex items-center' - if show_activity_participations? - = f.input :waiting_activity_participations_demanded_annually, as: :numeric, label: activities_human_name, required: true, input_html: { min: Current.org.activity_participations_form_min || 0, max: Current.org.activity_participations_form_max, data: { min: Current.org.activity_participations_form_min, max: Current.org.activity_participations_form_max, form_activity_target: 'input', action: 'blur->form-minmax-enforcer#enforce blur->form-pricing#refresh' }, class: 'mt-1 dark:bg-black w-20 px-3 py-2 border border-gray-300 dark:border-gray-700 placeholder-gray-400 dark:placeholder-gray-600 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 ' }, hint: activity_participations_form_detail, label_html: { class: 'inline-block w-full font-medium text-gray-700 dark:text-gray-300' }, hint_html: { class: 'inline-block w-full text-sm'} + = f.input :waiting_activity_participations_demanded_annually, as: :numeric, label: activities_human_name, required: true, input_html: { min: Current.org.activity_participations_form_min || 0, max: Current.org.activity_participations_form_max, step: Current.org.activity_participations_form_step, data: { min: Current.org.activity_participations_form_min, max: Current.org.activity_participations_form_max, form_activity_target: 'input', action: 'blur->form-minmax-enforcer#enforce blur->form-pricing#refresh' }, class: 'mt-1 dark:bg-black w-20 px-3 py-2 border border-gray-300 dark:border-gray-700 placeholder-gray-400 dark:placeholder-gray-600 text-gray-900 dark:text-gray-100 rounded-md focus:outline-none focus:ring-green-500 focus:border-green-500 focus:z-10 ' }, hint: activity_participations_form_detail, label_html: { class: 'inline-block w-full font-medium text-gray-700 dark:text-gray-300' }, hint_html: { class: 'inline-block w-full text-sm'} - if show_basket_price_extras? div class="print:break-after-page print:break-inside-avoid" data-form-disabler-target='label' diff --git a/config/locales/activerecord.yml b/config/locales/activerecord.yml index 191faf94..bfc7bccf 100644 --- a/config/locales/activerecord.yml +++ b/config/locales/activerecord.yml @@ -1268,6 +1268,11 @@ _: _en: Minimum participations _fr: Participations minimales _it: Partecipazioni minime + activity_participations_form_step: + _de: Schritt der Erhöhung/Verminderung + _en: Step of the increment/decrement + _fr: Pas de l’incrémentation/décrémentation + _it: Passo dell'incremento/decremento activity_phone: _de: Telefon _en: Phone diff --git a/config/locales/formtastic.yml b/config/locales/formtastic.yml index 2eda8fb6..59d2b4b6 100644 --- a/config/locales/formtastic.yml +++ b/config/locales/formtastic.yml @@ -517,6 +517,11 @@ _: _en: Leave blank if the member cannot reduce the number of participations via the registration/renewal form. _fr: Laissez blanc si le membre ne peux pas réduire le nombre de participation via le formulaire d'inscription/renouvellement. _it: Lasciare vuoto se il socio non può ridurre il numero di partecipazioni tramite il modulo di registrazione/rinnovo. + activity_participations_form_step: + _de: Wenn das Mitglied die Anzahl der Teilnahmen über das Anmelde-/Verlängerungsformular ändern kann, ermöglicht dieser Parameter die Definition des Schritts der Auswahl. Wenn der Schritt beispielsweise 2 beträgt, kann das Mitglied 0, 2, 4, 6 usw. wählen. Bitte stellen Sie sicher, dass min und max Vielfache dieses Schritts sind. + _en: If the member can modify the number of participations via the registration/renewal form, this parameter allows you to define the step of the selection. For example, if the step is 2, the member will be able to choose 0, 2, 4, 6, etc. Please make sure that the min and max are multiples of this step. + _fr: Si le membre peut modifier le nombre de participations via le formulaire d'inscription/renouvellement, ce paramètre permet de définir le pas de la sélection. Par exemple, si le pas est de 2, le membre pourra choisir 0 ,2, 4, 6, etc. Merci de veiller à ce que le min et le max soient des multiples de ce pas. + _it: Se il socio può modificare il numero di partecipazioni tramite il modulo di registrazione/rinnovo, questo parametro ti permette di definire il passo della selezione. Ad esempio, se il passo è 2, il socio potrà scegliere 0, 2, 4, 6, ecc. Assicurati che il min e il max siano multipli di questo passo. activity_phone_html: _de: Nummer, die in den E-Mail-Vorlagen der Aktivitäten angezeigt wird. _en: Number displayed in the activity email templates. diff --git a/db/migrate/20241105155415_add_activity_participations_form_step_to_orgs.rb b/db/migrate/20241105155415_add_activity_participations_form_step_to_orgs.rb new file mode 100644 index 00000000..9f00d1d0 --- /dev/null +++ b/db/migrate/20241105155415_add_activity_participations_form_step_to_orgs.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddActivityParticipationsFormStepToOrgs < ActiveRecord::Migration[8.0] + def change + add_column :organizations, :activity_participations_form_step, :integer, default: 1, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 5d7f8a84..e1bebc45 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_10_19_102158) do +ActiveRecord::Schema[8.0].define(version: 2024_11_05_155415) do create_table "absences", force: :cascade do |t| t.bigint "member_id" t.date "started_on" @@ -703,6 +703,7 @@ t.string "sepa_creditor_identifier" t.string "delivery_pdf_member_info", default: "none", null: false t.string "members_subdomain", null: false + t.integer "activity_participations_form_step", default: 1, null: false t.check_constraint "JSON_TYPE(basket_price_extras) = 'array'", name: "organizations_basket_price_extras_is_array" t.check_constraint "JSON_TYPE(billing_year_divisions) = 'array'", name: "organizations_billing_year_divisions_is_array" t.check_constraint "JSON_TYPE(email_notifications) = 'array'", name: "organizations_email_notifications_is_array"