implement first step - accepte without motivation and PJ
This commit is contained in:
parent
378f3c5fb0
commit
d7ebb67889
15 changed files with 217 additions and 39 deletions
|
@ -1,25 +1,33 @@
|
|||
en:
|
||||
archiver:
|
||||
finish:
|
||||
title: The bulk action is finished
|
||||
text:
|
||||
text_success:
|
||||
one: 1/1 file has been archived
|
||||
other: "%{success_count}/%{count} files have been archived"
|
||||
in_progress:
|
||||
title: A bulk action is processing
|
||||
text_success:
|
||||
one: 1/1 is being archived
|
||||
other: "%{progress_count}/%{count} files have been archived"
|
||||
passer_en_instruction:
|
||||
finish:
|
||||
title: The bulk action is finished
|
||||
text:
|
||||
text_success:
|
||||
one: 1/1 file has been changed to instructing
|
||||
other: "%{success_count}/%{count} files have been changed to instructing"
|
||||
in_progress:
|
||||
title: A bulk action is processing
|
||||
text_success:
|
||||
one: 1/1 is being changed to instructing
|
||||
other: "%{progress_count}/%{count} files have been changed to instructing"
|
||||
accepter:
|
||||
finish:
|
||||
text_success:
|
||||
one: 1/1 file has been accepted
|
||||
other: "%{success_count}/%{count} files have been accepted"
|
||||
in_progress:
|
||||
text_success:
|
||||
one: 1/1 is being accepted
|
||||
other: "%{progress_count}/%{count} files have been accepted"
|
||||
title:
|
||||
finish: The bulk action is finished
|
||||
in_progress: A bulk action is processing
|
||||
link_text: Refresh this webpage
|
||||
after_link_text: to check if the process is over.
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
fr:
|
||||
archiver:
|
||||
finish:
|
||||
title: L'action de masse est terminée
|
||||
text_success:
|
||||
one: 1 dossier a été archivé
|
||||
other: "%{success_count}/%{count} dossiers ont été archivés"
|
||||
in_progress:
|
||||
title: Une action de masse est en cours
|
||||
text_success:
|
||||
one: 1 dossier sera archivé
|
||||
other: "%{progress_count}/%{count} dossiers ont été archivés"
|
||||
passer_en_instruction:
|
||||
finish:
|
||||
title: L'action de masse est terminée
|
||||
text_success:
|
||||
one: 1 dossier a été passé en instruction
|
||||
other: "%{success_count}/%{count} dossiers ont été passés en instruction"
|
||||
in_progress:
|
||||
title: Une action de masse est en cours
|
||||
text_success:
|
||||
one: 1 dossier sera passé en instruction
|
||||
other: "%{progress_count}/%{count} dossiers ont été passés en instruction"
|
||||
accepter:
|
||||
finish:
|
||||
text_success:
|
||||
one: 1 dossier a été accepté
|
||||
other: "%{success_count}/%{count} dossiers ont été acceptés"
|
||||
in_progress:
|
||||
text_success:
|
||||
one: 1 dossier sera accepté
|
||||
other: "%{progress_count}/%{count} dossiers ont été acceptés"
|
||||
title:
|
||||
finish: L'action de masse est terminée
|
||||
in_progress: Une action de masse est en cours
|
||||
link_text: Recharger la page
|
||||
after_link_text: pour voir si l'opération est finie.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
|
||||
- else
|
||||
= render Dsfr::AlertComponent.new(title: t(".#{batch.operation}.in_progress.title"), state: :info, heading_level: 'h2') do |c|
|
||||
= render Dsfr::AlertComponent.new(title: t(".title.in_progress"), state: :info, heading_level: 'h2') do |c|
|
||||
- c.body do
|
||||
%p= t(".#{batch.operation}.in_progress.text_success", count: @batch.total_count, progress_count: @batch.progress_count)
|
||||
|
||||
|
|
|
@ -11,21 +11,45 @@ class Dossiers::BatchOperationComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def available_operations
|
||||
options = []
|
||||
case @statut
|
||||
when 'traites' then
|
||||
options.push [t(".operations.archiver"), BatchOperation.operations.fetch(:archiver)]
|
||||
{
|
||||
options:
|
||||
[
|
||||
{
|
||||
label: t(".operations.archiver"),
|
||||
operation: BatchOperation.operations.fetch(:archiver)
|
||||
}
|
||||
]
|
||||
}
|
||||
when 'suivis' then
|
||||
options.push [t(".operations.passer_en_instruction"), BatchOperation.operations.fetch(:passer_en_instruction)]
|
||||
{
|
||||
options:
|
||||
[
|
||||
|
||||
{
|
||||
label: t(".operations.passer_en_instruction"),
|
||||
operation: BatchOperation.operations.fetch(:passer_en_instruction)
|
||||
},
|
||||
|
||||
{
|
||||
label: t(".operations.accepter"),
|
||||
operation: BatchOperation.operations.fetch(:accepter)
|
||||
}
|
||||
]
|
||||
}
|
||||
else
|
||||
{
|
||||
options: []
|
||||
}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def icons
|
||||
{
|
||||
archiver: 'fr-icon-folder-2-line',
|
||||
passer_en_instruction: 'fr-icon-edit-line'
|
||||
passer_en_instruction: 'fr-icon-edit-line',
|
||||
accepter: 'fr-icon-success-line'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,3 +2,4 @@ fr:
|
|||
operations:
|
||||
archiver: 'Archive selected files'
|
||||
passer_en_instruction: 'Change selected files to instructing'
|
||||
accepter: 'Accept seleted files'
|
||||
|
|
|
@ -2,3 +2,4 @@ fr:
|
|||
operations:
|
||||
archiver: 'Archiver les dossiers sélectionnés'
|
||||
passer_en_instruction: 'Passer en instruction les dossiers sélectionnés'
|
||||
accepter: 'Accepter les dossiers sélectionnés'
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
= form_for(BatchOperation.new, url: instructeur_batch_operations_path(procedure_id: procedure.id), method: :post, id: dom_id(BatchOperation.new), html: { class: 'flex justify-end' }, data: { "batch-operation-target" => "form"}) do |form|
|
||||
.flex.align-center
|
||||
- available_operations.each do |opt|
|
||||
= form.button opt[0], class: ['fr-btn fr-btn--icon-left', icons[opt[1].to_sym]], disabled: :disabled, name: "#{form.object_name}[operation]", data: { "batch-operation-target" => "submit", "submitter-operation" => opt[1]}
|
||||
- if available_operations[:options].present?
|
||||
- if available_operations[:options].count == 1
|
||||
- opt = available_operations[:options].dig(0)
|
||||
= form_for(BatchOperation.new, url: instructeur_batch_operations_path(procedure_id: procedure.id), method: :post, html: { class: 'flex justify-end', id: "#{dom_id(BatchOperation.new)}_#{opt[:operation]}" }, data: { "batch-operation-target" => "form"}) do |form|
|
||||
= form.button opt[:label], class: ['fr-btn fr-btn--icon-left', icons[opt[:operation].to_sym]], disabled: :disabled, name: "#{form.object_name}[operation]", data: { "batch-operation-target" => "submit", "submitter-operation" => opt[:operation] }
|
||||
|
||||
-else
|
||||
.flex.justify-end
|
||||
.dropdown{ data: { controller: 'menu-button', popover: 'true' } }
|
||||
-# Dropdown button title
|
||||
%button.fr-btn.dropdown-button{ id: 'batch_operation_dropdown', disabled: :disabled, data: { menu_button_target: 'button'} }
|
||||
Actions multiples
|
||||
|
||||
#state-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
|
||||
|
||||
%ul.dropdown-items
|
||||
- available_operations[:options].each do |opt|
|
||||
%li{ 'data-turbo': 'true' }
|
||||
= form_for(BatchOperation.new, url: instructeur_batch_operations_path(procedure_id: procedure.id), method: :post, html: { class: 'flex justify-end', id: "#{dom_id(BatchOperation.new)}_#{opt[:operation]}" }, data: { "batch-operation-target" => "form"}) do |form|
|
||||
= form.button opt[:label], class: ['fr-btn--icon-left', icons[opt[:operation].to_sym]], disabled: :disabled, name: "#{form.object_name}[operation]", data: { "batch-operation-target" => "submit", "submitter-operation" => opt[:operation]}
|
||||
|
|
|
@ -3,25 +3,28 @@ import { ApplicationController } from './application_controller';
|
|||
export class BatchOperationController extends ApplicationController {
|
||||
static targets = ['form', 'input', 'submit'];
|
||||
|
||||
declare readonly formTarget: HTMLFormElement;
|
||||
declare readonly submitTarget: HTMLInputElement;
|
||||
declare readonly formTargets: HTMLFormElement[];
|
||||
declare readonly submitTargets: HTMLInputElement[];
|
||||
declare readonly inputTargets: HTMLInputElement[];
|
||||
|
||||
connect() {
|
||||
this.formTarget.addEventListener(
|
||||
'submit',
|
||||
this.interceptFormSubmit.bind(this)
|
||||
this.formTargets.forEach((e) =>
|
||||
e.addEventListener('submit', this.interceptFormSubmit.bind(this))
|
||||
);
|
||||
}
|
||||
|
||||
// DSFR recommends a <input type="submit" /> or <button type="submit" /> a form (not a <select>)
|
||||
// but we have many actions on the same form (archive all, accept all, ...)
|
||||
// so we intercept the form submit, and set the BatchOperation.operation by hand using the Event.submitter
|
||||
interceptFormSubmit(event: SubmitEvent) {
|
||||
interceptFormSubmit(event: SubmitEvent): SubmitEvent {
|
||||
const submitter = event.submitter as HTMLInputElement;
|
||||
|
||||
submitter.setAttribute('value', submitter.dataset.submitterOperation || '');
|
||||
|
||||
this.inputTargets.forEach((e) =>
|
||||
e.setAttribute(
|
||||
'form',
|
||||
`new_batch_operation_${submitter.dataset.submitterOperation}`
|
||||
));
|
||||
return event;
|
||||
}
|
||||
|
||||
|
@ -40,10 +43,17 @@ export class BatchOperationController extends ApplicationController {
|
|||
|
||||
toggleSubmitButtonWhenNeeded() {
|
||||
const available = this.inputTargets.some((e) => e.checked);
|
||||
const dropdown = document.querySelector("#batch_operation_dropdown");
|
||||
if (available) {
|
||||
this.submitTarget.removeAttribute('disabled');
|
||||
this.submitTargets.forEach((e) => e.removeAttribute('disabled'));
|
||||
if (dropdown) {
|
||||
dropdown.removeAttribute('disabled');
|
||||
}
|
||||
} else {
|
||||
this.submitTarget.setAttribute('disabled', 'disabled');
|
||||
this.submitTargets.forEach((e) => e.setAttribute('disabled', 'disabled'));
|
||||
if (dropdown) {
|
||||
dropdown.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
class BatchOperation < ApplicationRecord
|
||||
enum operation: {
|
||||
archiver: 'archiver',
|
||||
passer_en_instruction: 'passer_en_instruction'
|
||||
passer_en_instruction: 'passer_en_instruction',
|
||||
accepter: 'accepter'
|
||||
}
|
||||
|
||||
has_many :dossiers, dependent: :nullify
|
||||
|
@ -53,6 +54,8 @@ class BatchOperation < ApplicationRecord
|
|||
query.not_archived.state_termine
|
||||
when BatchOperation.operations.fetch(:passer_en_instruction) then
|
||||
query.state_en_construction
|
||||
when BatchOperation.operations.fetch(:accepter) then
|
||||
query.state_en_instruction
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -67,6 +70,8 @@ class BatchOperation < ApplicationRecord
|
|||
dossier.archiver!(instructeur)
|
||||
when BatchOperation.operations.fetch(:passer_en_instruction)
|
||||
dossier.passer_en_instruction(instructeur: instructeur)
|
||||
when BatchOperation.operations.fetch(:accepter)
|
||||
dossier.accepter(instructeur: instructeur)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -78,8 +78,7 @@
|
|||
- @batch_operations.each do |batch_operation|
|
||||
= render Dossiers::BatchAlertComponent.new(batch: batch_operation, procedure: @procedure)
|
||||
|
||||
.flex
|
||||
.flex-grow= render batch_operation_component
|
||||
= render batch_operation_component
|
||||
.fr-table.fr-table--bordered
|
||||
%table.table.dossiers-table.hoverable
|
||||
%thead
|
||||
|
@ -125,7 +124,7 @@
|
|||
- if p.batch_operation_id.present?
|
||||
= check_box_tag :"batch_operation[dossier_ids][]", p.dossier_id, true, disabled: true, id: dom_id(BatchOperation.new, "checkbox_#{p.dossier_id}"), aria: {label: t('views.instructeurs.dossiers.batch_operation.disabled')}
|
||||
- else
|
||||
= check_box_tag :"batch_operation[dossier_ids][]", p.dossier_id, false, data: { "batch-operation-target" => "input", "action" => "batch-operation#onCheckOne"}, form: dom_id(BatchOperation.new), id: dom_id(BatchOperation.new, "checkbox_#{p.dossier_id}"), aria: {label: t('views.instructeurs.dossiers.batch_operation.enabled')}
|
||||
= check_box_tag :"batch_operation[dossier_ids][]", p.dossier_id, false, data: { "batch-operation-target" => "input", "action" => "batch-operation#onCheckOne"}, id: dom_id(BatchOperation.new, "checkbox_#{p.dossier_id}"), aria: {label: t('views.instructeurs.dossiers.batch_operation.enabled')}
|
||||
|
||||
- if @not_archived_notifications_dossier_ids.include?(p.dossier_id)
|
||||
%span.notifications{ 'aria-label': 'notifications' }
|
||||
|
|
|
@ -50,10 +50,9 @@ RSpec.describe Dossiers::BatchAlertComponent, type: :component do
|
|||
it { is_expected.to have_text("1/2 dossiers ont été archivés") }
|
||||
it { expect(batch_operation.seen_at).to eq(nil) }
|
||||
|
||||
it 'does not display alert on the next render' do
|
||||
it 'on next render "seen_at" is set to avoid rendering alert' do
|
||||
render_inline(component).to_html
|
||||
expect(batch_operation.seen_at).not_to eq(nil)
|
||||
expect(subject).not_to have_text("1 dossier n'a pas été archivé")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -105,10 +104,62 @@ RSpec.describe Dossiers::BatchAlertComponent, type: :component do
|
|||
it { is_expected.to have_text("1/2 dossiers ont été passés en instruction") }
|
||||
it { expect(batch_operation.seen_at).to eq(nil) }
|
||||
|
||||
it 'does not display alert on the next render' do
|
||||
it 'on next render "seen_at" is set to avoid rendering alert' do
|
||||
render_inline(component).to_html
|
||||
expect(batch_operation.seen_at).not_to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
describe 'accepter' do
|
||||
let(:component) do
|
||||
described_class.new(
|
||||
batch: batch_operation,
|
||||
procedure: procedure
|
||||
)
|
||||
end
|
||||
let!(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let!(:dossier_2) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let!(:batch_operation) { create(:batch_operation, operation: :accepter, dossiers: [dossier, dossier_2], instructeur: instructeur) }
|
||||
|
||||
context 'in_progress' do
|
||||
before {
|
||||
batch_operation.track_processed_dossier(true, dossier)
|
||||
batch_operation.reload
|
||||
}
|
||||
|
||||
it { is_expected.to have_selector('.fr-alert--info') }
|
||||
it { is_expected.to have_text("Une action de masse est en cours") }
|
||||
it { is_expected.to have_text("1/2 dossiers ont été acceptés") }
|
||||
end
|
||||
|
||||
context 'finished and success' do
|
||||
before {
|
||||
batch_operation.track_processed_dossier(true, dossier)
|
||||
batch_operation.track_processed_dossier(true, dossier_2)
|
||||
batch_operation.reload
|
||||
}
|
||||
|
||||
it { is_expected.to have_selector('.fr-alert--success') }
|
||||
it { is_expected.to have_text("L'action de masse est terminée") }
|
||||
it { is_expected.to have_text("2 dossiers ont été acceptés") }
|
||||
it { expect(batch_operation.seen_at).to eq(nil) }
|
||||
end
|
||||
|
||||
context 'finished and fail' do
|
||||
before {
|
||||
batch_operation.track_processed_dossier(false, dossier)
|
||||
batch_operation.track_processed_dossier(true, dossier_2)
|
||||
batch_operation.reload
|
||||
}
|
||||
|
||||
it { is_expected.to have_selector('.fr-alert--warning') }
|
||||
it { is_expected.to have_text("L'action de masse est terminée") }
|
||||
it { is_expected.to have_text("1/2 dossiers ont été acceptés") }
|
||||
it { expect(batch_operation.seen_at).to eq(nil) }
|
||||
|
||||
it 'on next render "seen_at" is set to avoid rendering alert' do
|
||||
render_inline(component).to_html
|
||||
expect(batch_operation.seen_at).not_to eq(nil)
|
||||
expect(subject).not_to have_text("1 dossier n'a pas passé en instruction")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,13 +14,13 @@ RSpec.describe Dossiers::BatchOperationComponent, type: :component do
|
|||
subject { render_inline(component).to_html }
|
||||
context 'statut traite' do
|
||||
let(:statut) { 'traites' }
|
||||
it { is_expected.to have_selector('button') }
|
||||
it { is_expected.to have_button('Archiver les dossiers sélectionnés', disabled: true) }
|
||||
end
|
||||
|
||||
subject { render_inline(component).to_html }
|
||||
context 'statut suivis' do
|
||||
let(:statut) { 'suivis' }
|
||||
it { is_expected.to have_selector('button') }
|
||||
it { is_expected.to have_button('Actions multiples', disabled: true) }
|
||||
end
|
||||
|
||||
context 'statut tous' do
|
||||
|
|
|
@ -28,5 +28,16 @@ FactoryBot.define do
|
|||
]
|
||||
end
|
||||
end
|
||||
|
||||
trait :accepter do
|
||||
operation { BatchOperation.operations.fetch(:accepter) }
|
||||
after(:build) do |batch_operation, evaluator|
|
||||
procedure = create(:simple_procedure, :published, instructeurs: [evaluator.invalid_instructeur.presence || batch_operation.instructeur], administrateurs: [create(:administrateur)])
|
||||
batch_operation.dossiers = [
|
||||
create(:dossier, :with_individual, :en_instruction, procedure: procedure),
|
||||
create(:dossier, :with_individual, :en_instruction, procedure: procedure)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,6 +46,20 @@ describe BatchOperationProcessOneJob, type: :job do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when operation is "accepter"' do
|
||||
let(:batch_operation) do
|
||||
create(:batch_operation, :accepter,
|
||||
options.merge(instructeur: create(:instructeur)))
|
||||
end
|
||||
|
||||
it 'accepts the dossier in the batch' do
|
||||
expect { subject.perform_now }
|
||||
.to change { dossier_job.reload.accepte? }
|
||||
.from(false)
|
||||
.to(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier is out of sync (ie: someone applied a transition somewhere we do not know)' do
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:simple_procedure, instructeurs: [instructeur]) }
|
||||
|
|
|
@ -176,6 +176,36 @@ describe BatchOperation, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#dossiers_safe_scope (with accepter)' do
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:simple_procedure, instructeurs: [instructeur]) }
|
||||
let(:batch_operation) { create(:batch_operation, operation: :accepter, instructeur: instructeur, dossiers: [dossier]) }
|
||||
|
||||
context 'when dossier is valid' do
|
||||
let(:dossier) { create(:dossier, :en_instruction, :with_individual, procedure: procedure) }
|
||||
|
||||
it 'find dosssier' do
|
||||
expect(batch_operation.dossiers_safe_scope).to include(dossier)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier is already accepte' do
|
||||
let(:dossier) { create(:dossier, :accepte, :with_individual, archived: true, procedure: procedure) }
|
||||
|
||||
it 'skips dossier is already en instruction' do
|
||||
expect(batch_operation.dossiers_safe_scope).not_to include(dossier)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dossier is not in state en instruction' do
|
||||
let(:dossier) { create(:dossier, :en_construction, :with_individual, procedure: procedure) }
|
||||
|
||||
it 'does not enqueue any job' do
|
||||
expect(batch_operation.dossiers_safe_scope).not_to include(dossier)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#safe_create!' do
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:simple_procedure, instructeurs: [instructeur]) }
|
||||
|
|
Loading…
Reference in a new issue