Merge pull request #10328 from tchak/refactor-dossier-champ-by-stable-id-controller
refactor(champs): update champs by public_id [controllers]
This commit is contained in:
commit
f4f5416dd3
31 changed files with 803 additions and 239 deletions
|
@ -430,22 +430,6 @@ class ApplicationController < ActionController::Base
|
|||
controller_instance.try(:nav_bar_profile)
|
||||
end
|
||||
|
||||
# Extract a value from params based on the "path"
|
||||
#
|
||||
# params: { dossiers: { champs_public_attributes: { 1234 => { value: "hello" } } } }
|
||||
#
|
||||
# Usage: read_param_value("dossiers[champs_public_attributes][1234]", "value")
|
||||
def read_param_value(path, name)
|
||||
parts = path.split(/\[|\]\[|\]/) + [name]
|
||||
parts.reduce(params) do |value, part|
|
||||
if part.to_i != 0
|
||||
value[part.to_i] || value[part]
|
||||
else
|
||||
value[part]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cast_bool(value)
|
||||
ActiveRecord::Type::Boolean.new.deserialize(value)
|
||||
end
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
class Champs::CarteController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
class Champs::CarteController < Champs::ChampController
|
||||
def index
|
||||
@champ = policy_scope(Champ).find(params[:champ_id])
|
||||
@focus = params[:focus].present?
|
||||
end
|
||||
|
||||
def create
|
||||
champ = policy_scope(Champ).find(params[:champ_id])
|
||||
geo_area = if params_source == GeoArea.sources.fetch(:cadastre)
|
||||
champ.geo_areas.find_by("properties->>'id' = :id", id: create_params_feature[:properties][:id])
|
||||
@champ.geo_areas.find_by("properties->>'id' = :id", id: create_params_feature[:properties][:id])
|
||||
end
|
||||
|
||||
if geo_area.nil?
|
||||
geo_area = champ.geo_areas.build(source: params_source, properties: {})
|
||||
geo_area = @champ.geo_areas.build(source: params_source, properties: {})
|
||||
|
||||
if save_feature(geo_area, create_params_feature)
|
||||
render json: { feature: geo_area.to_feature }, status: :created
|
||||
|
@ -26,8 +22,7 @@ class Champs::CarteController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
champ = policy_scope(Champ).find(params[:champ_id])
|
||||
geo_area = champ.geo_areas.find(params[:id])
|
||||
geo_area = @champ.geo_areas.find(params[:id])
|
||||
|
||||
if save_feature(geo_area, update_params_feature)
|
||||
head :no_content
|
||||
|
@ -37,9 +32,8 @@ class Champs::CarteController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
champ = policy_scope(Champ).find(params[:champ_id])
|
||||
champ.geo_areas.find(params[:id]).destroy!
|
||||
champ.touch
|
||||
@champ.geo_areas.find(params[:id]).destroy!
|
||||
@champ.touch
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
@ -82,7 +76,7 @@ class Champs::CarteController < ApplicationController
|
|||
geo_area.properties.merge!(feature[:properties])
|
||||
end
|
||||
if geo_area.save
|
||||
geo_area.champ.touch
|
||||
@champ.touch
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
26
app/controllers/champs/champ_controller.rb
Normal file
26
app/controllers/champs/champ_controller.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class Champs::ChampController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
before_action :set_champ
|
||||
|
||||
private
|
||||
|
||||
def find_champ
|
||||
if params[:champ_id].present?
|
||||
policy_scope(Champ)
|
||||
.includes(:type_de_champ, dossier: :champs)
|
||||
.find(params[:champ_id])
|
||||
else
|
||||
dossier = policy_scope(Dossier).includes(:champs, revision: [:types_de_champ]).find(params[:dossier_id])
|
||||
type_de_champ = dossier.find_type_de_champ_by_stable_id(params[:stable_id])
|
||||
dossier.champ_for_update(type_de_champ, params_row_id)
|
||||
end
|
||||
end
|
||||
|
||||
def params_row_id
|
||||
params[:row_id]
|
||||
end
|
||||
|
||||
def set_champ
|
||||
@champ = find_champ
|
||||
end
|
||||
end
|
|
@ -1,13 +1,10 @@
|
|||
class Champs::OptionsController < ApplicationController
|
||||
class Champs::OptionsController < Champs::ChampController
|
||||
include TurboChampsConcern
|
||||
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
def remove
|
||||
champ = policy_scope(Champ).includes(:champs).find(params[:champ_id])
|
||||
champ.remove_option([params[:option]].compact, true)
|
||||
champs = champ.private? ? champ.dossier.champs_private_all : champ.dossier.champs_public_all
|
||||
@dossier = champ.private? ? nil : champ.dossier
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update({ params[:champ_id] => true }, champs)
|
||||
@champ.remove_option([params[:option]].compact, true)
|
||||
@dossier = @champ.private? ? nil : @champ.dossier
|
||||
champs_attributes = params[:champ_id].present? ? { @champ.id => true } : { @champ.public_id => { with_public_id: true } }
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_attributes, @champ.dossier.champs)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
class Champs::PieceJustificativeController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
before_action :set_champ
|
||||
|
||||
class Champs::PieceJustificativeController < Champs::ChampController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.turbo_stream
|
||||
|
@ -23,10 +20,6 @@ class Champs::PieceJustificativeController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_champ
|
||||
@champ = policy_scope(Champ).find(params[:champ_id])
|
||||
end
|
||||
|
||||
def attach_piece_justificative
|
||||
save_succeed = nil
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
class Champs::RepetitionController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
class Champs::RepetitionController < Champs::ChampController
|
||||
def add
|
||||
@champ = find_champ
|
||||
row = @champ.add_row(@champ.dossier.revision)
|
||||
@first_champ_id = row.map(&:focusable_input_id).compact.first
|
||||
@row_id = row.first&.row_id
|
||||
|
@ -10,21 +7,14 @@ class Champs::RepetitionController < ApplicationController
|
|||
end
|
||||
|
||||
def remove
|
||||
@champ = find_champ
|
||||
@champ.champs.where(row_id: params[:row_id]).destroy_all
|
||||
@champ.reload
|
||||
@row_id = params[:row_id]
|
||||
@champ.remove_row(params[:row_id])
|
||||
@to_remove = "safe-row-selector-#{params[:row_id]}"
|
||||
@to_focus = @champ.focusable_input_id || helpers.dom_id(@champ, :create_repetition)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_champ
|
||||
if params[:champ_id].present?
|
||||
policy_scope(Champ).includes(:champs).find(params[:champ_id])
|
||||
else
|
||||
policy_scope(Champ)
|
||||
.includes(:champs, :type_de_champ)
|
||||
.find_by!(dossier_id: params[:dossier_id], type_de_champ: { stable_id: params[:stable_id] })
|
||||
end
|
||||
def params_row_id
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
class Champs::RNAController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
class Champs::RNAController < Champs::ChampController
|
||||
def show
|
||||
@champ = policy_scope(Champ).find(params[:champ_id])
|
||||
rna = read_param_value(@champ.input_name, 'value')
|
||||
champs_attributes = params.dig(:dossier, :champs_public_attributes) || params.dig(:dossier, :champs_private_attributes)
|
||||
rna = champs_attributes.values.first[:value]
|
||||
|
||||
unless @champ.fetch_association!(rna)
|
||||
@error = @champ.association_fetch_error_key
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
class Champs::SiretController < ApplicationController
|
||||
before_action :authenticate_logged_user!
|
||||
|
||||
class Champs::SiretController < Champs::ChampController
|
||||
def show
|
||||
@champ = policy_scope(Champ).find(params[:champ_id])
|
||||
champs_attributes = params.dig(:dossier, :champs_public_attributes) || params.dig(:dossier, :champs_private_attributes)
|
||||
siret = champs_attributes.values.first[:value]
|
||||
|
||||
if @champ.fetch_etablissement!(read_param_value(@champ.input_name, 'value'), current_user)
|
||||
if @champ.fetch_etablissement!(siret, current_user)
|
||||
@siret = @champ.etablissement.siret
|
||||
else
|
||||
@siret = @champ.etablissement_fetch_error_key
|
||||
|
|
|
@ -4,9 +4,14 @@ module TurboChampsConcern
|
|||
private
|
||||
|
||||
def champs_to_turbo_update(params, champs)
|
||||
champ_ids = params.keys.map(&:to_i)
|
||||
to_update = if params.values.filter { _1.key?(:with_public_id) }.empty?
|
||||
champ_ids = params.keys.map(&:to_i)
|
||||
champs.filter { _1.id.in?(champ_ids) }
|
||||
else
|
||||
champ_public_ids = params.keys
|
||||
champs.filter { _1.public_id.in?(champ_public_ids) }
|
||||
end.filter { _1.refresh_after_update? || _1.forked_with_changes? }
|
||||
|
||||
to_update = champs.filter { _1.id.in?(champ_ids) && (_1.refresh_after_update? || _1.forked_with_changes?) }
|
||||
to_show, to_hide = champs.filter(&:conditional?)
|
||||
.partition(&:visible?)
|
||||
.map { champs_to_one_selector(_1 - to_update) }
|
||||
|
|
|
@ -273,8 +273,8 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def update_annotations
|
||||
dossier_with_champs.assign_attributes(champs_private_params)
|
||||
if dossier.champs_private_all.any?(&:changed?)
|
||||
dossier_with_champs.update_champs_attributes(champs_private_attributes_params, :private)
|
||||
if dossier.champs.any?(&:changed_for_autosave?) || dossier.champs_private_all.any?(&:changed_for_autosave?) # TODO remove second condition after one deploy
|
||||
dossier.last_champ_private_updated_at = Time.zone.now
|
||||
end
|
||||
|
||||
|
@ -282,7 +282,7 @@ module Instructeurs
|
|||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_params.fetch(:champs_private_all_attributes), dossier.champs_private_all)
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_attributes_params, dossier.champs.filter(&:private?))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -294,7 +294,12 @@ module Instructeurs
|
|||
|
||||
def annotation
|
||||
@dossier = dossier_with_champs(pj_template: false)
|
||||
annotation = @dossier.champs_private_all.find(params[:annotation_id])
|
||||
annotation = if params[:with_public_id].present?
|
||||
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:annotation_id], :private)
|
||||
@dossier.project_champ(type_de_champ, params[:row_id])
|
||||
else
|
||||
@dossier.champs_private_all.find(params[:annotation_id])
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
|
@ -392,12 +397,36 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def champs_private_params
|
||||
champs_params = params.require(:dossier).permit(champs_private_attributes: [
|
||||
:id, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, value: [],
|
||||
champs_attributes: [:id, :_destroy, :value, :primary_value, :secondary_value, :piece_justificative_file, :value_other, :external_id, :numero_allocataire, :code_postal, :code_departement, :feature, value: []]
|
||||
])
|
||||
champs_params[:champs_private_all_attributes] = champs_params.delete(:champs_private_attributes) || {}
|
||||
champs_params
|
||||
champ_attributes = [
|
||||
:id,
|
||||
:value,
|
||||
:value_other,
|
||||
:external_id,
|
||||
:primary_value,
|
||||
:secondary_value,
|
||||
:numero_allocataire,
|
||||
:code_postal,
|
||||
:identifiant,
|
||||
:numero_fiscal,
|
||||
:reference_avis,
|
||||
:ine,
|
||||
:piece_justificative_file,
|
||||
:code_departement,
|
||||
:accreditation_number,
|
||||
:accreditation_birthdate,
|
||||
:feature,
|
||||
:with_public_id,
|
||||
value: []
|
||||
]
|
||||
# Strong attributes do not support records (indexed hash); they only support hashes with
|
||||
# static keys. We create a static hash based on the available keys.
|
||||
public_ids = params.dig(:dossier, :champs_private_attributes)&.keys || []
|
||||
champs_private_attributes = public_ids.map { [_1, champ_attributes] }.to_h
|
||||
params.require(:dossier).permit(champs_private_attributes:)
|
||||
end
|
||||
|
||||
def champs_private_attributes_params
|
||||
champs_private_params.fetch(:champs_private_attributes)
|
||||
end
|
||||
|
||||
def mark_demande_as_read
|
||||
|
|
|
@ -284,7 +284,7 @@ module Users
|
|||
end
|
||||
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
|
||||
render :update, layout: false
|
||||
end
|
||||
end
|
||||
|
@ -298,7 +298,7 @@ module Users
|
|||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
|
||||
render :update, layout: false
|
||||
end
|
||||
end
|
||||
|
@ -310,7 +310,12 @@ module Users
|
|||
|
||||
def champ
|
||||
@dossier = dossier_with_champs(pj_template: false)
|
||||
champ = @dossier.champs_public_all.find(params[:champ_id])
|
||||
champ = if params[:with_public_id].present?
|
||||
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:champ_id], :public)
|
||||
@dossier.project_champ(type_de_champ, params[:row_id])
|
||||
else
|
||||
@dossier.champs_public_all.find(params[:champ_id])
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream do
|
||||
|
@ -478,7 +483,7 @@ module Users
|
|||
end
|
||||
|
||||
def champs_public_params
|
||||
champs_params = params.require(:dossier).permit(champs_public_attributes: [
|
||||
champ_attributes = [
|
||||
:id,
|
||||
:value,
|
||||
:value_other,
|
||||
|
@ -496,10 +501,18 @@ module Users
|
|||
:accreditation_number,
|
||||
:accreditation_birthdate,
|
||||
:feature,
|
||||
:with_public_id,
|
||||
value: []
|
||||
])
|
||||
champs_params[:champs_public_all_attributes] = champs_params.delete(:champs_public_attributes) || {}
|
||||
champs_params
|
||||
]
|
||||
# Strong attributes do not support records (indexed hash); they only support hashes with
|
||||
# static keys. We create a static hash based on the available keys.
|
||||
public_ids = params.dig(:dossier, :champs_public_attributes)&.keys || []
|
||||
champs_public_attributes = public_ids.map { [_1, champ_attributes] }.to_h
|
||||
params.require(:dossier).permit(champs_public_attributes:)
|
||||
end
|
||||
|
||||
def champs_public_attributes_params
|
||||
champs_public_params.fetch(:champs_public_attributes)
|
||||
end
|
||||
|
||||
def dossier_scope
|
||||
|
@ -532,8 +545,8 @@ module Users
|
|||
end
|
||||
|
||||
def update_dossier_and_compute_errors
|
||||
@dossier.assign_attributes(champs_public_params)
|
||||
if @dossier.champs_public_all.any?(&:changed_for_autosave?)
|
||||
@dossier.update_champs_attributes(champs_public_attributes_params, :public)
|
||||
if @dossier.champs.any?(&:changed_for_autosave?) || @dossier.champs_public_all.any?(&:changed_for_autosave?) # TODO remove second condition after one deploy
|
||||
@dossier.last_champ_updated_at = Time.zone.now
|
||||
end
|
||||
|
||||
|
|
|
@ -39,10 +39,12 @@ module Mutations
|
|||
|
||||
def find_annotation(dossier, annotation_id)
|
||||
stable_id, row_id = Champ.decode_typed_id(annotation_id)
|
||||
type_de_champ = dossier.revision.types_de_champ
|
||||
.private_only
|
||||
.find_by(type_champ: annotation_type_champ, stable_id:)
|
||||
|
||||
Champ.joins(:type_de_champ).find_by(type_de_champ: {
|
||||
type_champ: annotation_type_champ, stable_id:, private: true
|
||||
}, private: true, row_id:, dossier:)
|
||||
return nil if type_de_champ.nil?
|
||||
dossier.champ_for_update(type_de_champ, row_id)
|
||||
end
|
||||
|
||||
def annotation_type_champ
|
||||
|
|
|
@ -26,11 +26,13 @@ module Mutations
|
|||
private
|
||||
|
||||
def find_annotation(dossier, annotation_id)
|
||||
stable_id, row_id = Champ.decode_typed_id(annotation_id)
|
||||
stable_id, _row_id = Champ.decode_typed_id(annotation_id)
|
||||
type_de_champ = dossier.revision.types_de_champ
|
||||
.private_only
|
||||
.find_by(type_champ: TypeDeChamp.type_champs.fetch(:repetition), stable_id:)
|
||||
|
||||
Champ.joins(:type_de_champ).find_by(type_de_champ: {
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:repetition), stable_id:, private: true
|
||||
}, private: true, row_id:, dossier:)
|
||||
return nil if type_de_champ.nil?
|
||||
dossier.project_champ(type_de_champ, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,9 @@ class Champ < ApplicationRecord
|
|||
include ChampConditionalConcern
|
||||
include ChampsValidateConcern
|
||||
|
||||
# TODO: remove after one deploy
|
||||
attr_writer :with_public_id
|
||||
|
||||
belongs_to :dossier, inverse_of: false, touch: true, optional: false
|
||||
belongs_to :type_de_champ, inverse_of: :champ, optional: false
|
||||
belongs_to :parent, class_name: 'Champ', optional: true
|
||||
|
|
|
@ -25,6 +25,15 @@ class Champs::RepetitionChamp < Champ
|
|||
added_champs
|
||||
end
|
||||
|
||||
def remove_row(row_id)
|
||||
dossier.champs.where(row_id:).destroy_all
|
||||
dossier.champs.reload
|
||||
end
|
||||
|
||||
def focusable_input_id
|
||||
rows.last&.first&.focusable_input_id
|
||||
end
|
||||
|
||||
def blank?
|
||||
champs.empty?
|
||||
end
|
||||
|
|
131
app/models/concerns/dossier_champs_concern.rb
Normal file
131
app/models/concerns/dossier_champs_concern.rb
Normal file
|
@ -0,0 +1,131 @@
|
|||
module DossierChampsConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def champs_for_revision(scope: nil, root: false)
|
||||
champs_index = champs.group_by(&:stable_id)
|
||||
# Due to some bad data we can have multiple copies of the same champ. Ignore extra copy.
|
||||
.transform_values { _1.sort_by(&:id).uniq(&:row_id) }
|
||||
|
||||
if scope.is_a?(TypeDeChamp)
|
||||
revision
|
||||
.children_of(scope)
|
||||
.flat_map { champs_index[_1.stable_id] || [] }
|
||||
.filter(&:child?) # TODO: remove once bad data (child champ without a row id) is cleaned
|
||||
else
|
||||
revision
|
||||
.types_de_champ_for(scope:, root:)
|
||||
.flat_map { champs_index[_1.stable_id] || [] }
|
||||
end
|
||||
end
|
||||
|
||||
# Get all the champs values for the types de champ in the final list.
|
||||
# Dossier might not have corresponding champ – display nil.
|
||||
# To do so, we build a virtual champ when there is no value so we can call for_export with all indexes
|
||||
def champs_for_export(types_de_champ, row_id = nil)
|
||||
types_de_champ.flat_map do |type_de_champ|
|
||||
champ = champ_for_export(type_de_champ, row_id)
|
||||
type_de_champ.libelles_for_export.map do |(libelle, path)|
|
||||
[libelle, champ&.for_export(path)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def project_champ(type_de_champ, row_id)
|
||||
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
|
||||
if champ.nil?
|
||||
type_de_champ.build_champ(dossier: self, row_id:)
|
||||
else
|
||||
champ
|
||||
end
|
||||
end
|
||||
|
||||
def find_type_de_champ_by_stable_id(stable_id, scope = nil)
|
||||
case scope
|
||||
when :public
|
||||
revision.types_de_champ.public_only
|
||||
when :private
|
||||
revision.types_de_champ.private_only
|
||||
else
|
||||
revision.types_de_champ
|
||||
end.find_by!(stable_id:)
|
||||
end
|
||||
|
||||
def champs_for_prefill(stable_ids)
|
||||
revision
|
||||
.types_de_champ
|
||||
.filter { _1.stable_id.in?(stable_ids) }
|
||||
.filter { !revision.child?(_1) }
|
||||
.map { champ_for_update(_1, nil) }
|
||||
end
|
||||
|
||||
def champ_for_update(type_de_champ, row_id)
|
||||
champ, attributes = champ_with_attributes_for_update(type_de_champ, row_id)
|
||||
champ.assign_attributes(attributes)
|
||||
champ
|
||||
end
|
||||
|
||||
def update_champs_attributes(attributes, scope)
|
||||
# TODO: remove after one deploy
|
||||
if attributes.present? && attributes.values.filter { _1.key?(:with_public_id) }.empty?
|
||||
assign_attributes("champs_#{scope}_all_attributes".to_sym => attributes)
|
||||
@champs_by_public_id = nil
|
||||
return
|
||||
end
|
||||
|
||||
champs_attributes = attributes.to_h.map do |public_id, attributes|
|
||||
champ_attributes_by_public_id(public_id, attributes, scope)
|
||||
end
|
||||
|
||||
assign_attributes(champs_attributes:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def champs_by_public_id
|
||||
@champs_by_public_id ||= champs.sort_by(&:id).index_by(&:public_id)
|
||||
end
|
||||
|
||||
def champ_for_export(type_de_champ, row_id)
|
||||
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
|
||||
if champ.blank? || !champ.visible?
|
||||
nil
|
||||
else
|
||||
champ
|
||||
end
|
||||
end
|
||||
|
||||
def champ_attributes_by_public_id(public_id, attributes, scope)
|
||||
stable_id, row_id = public_id.split('-')
|
||||
type_de_champ = find_type_de_champ_by_stable_id(stable_id, scope)
|
||||
champ_with_attributes_for_update(type_de_champ, row_id).last.merge(attributes)
|
||||
end
|
||||
|
||||
def champ_with_attributes_for_update(type_de_champ, row_id)
|
||||
attributes = type_de_champ.params_for_champ
|
||||
# TODO: Once we have the right index in place, we should change this to use `create_or_find_by` instead of `find_or_create_by`
|
||||
champ = champs
|
||||
.create_with(type_de_champ:, **attributes)
|
||||
.find_or_create_by!(stable_id: type_de_champ.stable_id, row_id:)
|
||||
|
||||
attributes[:id] = champ.id
|
||||
|
||||
# Needed when a revision change the champ type in this case, we reset the champ data
|
||||
if champ.type != attributes[:type]
|
||||
attributes[:value] = nil
|
||||
attributes[:value_json] = nil
|
||||
attributes[:external_id] = nil
|
||||
attributes[:data] = nil
|
||||
end
|
||||
|
||||
parent = revision.parent_of(type_de_champ)
|
||||
if parent.present?
|
||||
attributes[:parent] = champs.find { _1.type_de_champ_id == parent.id }
|
||||
else
|
||||
attributes[:parent] = nil
|
||||
end
|
||||
|
||||
@champs_by_public_id = nil
|
||||
|
||||
[champ, attributes]
|
||||
end
|
||||
end
|
|
@ -13,8 +13,4 @@ module DossierPrefillableConcern
|
|||
assign_attributes(attributes)
|
||||
save(validate: false)
|
||||
end
|
||||
|
||||
def find_champs_by_stable_ids(stable_ids)
|
||||
champs.joins(:type_de_champ).where(types_de_champ: { stable_id: stable_ids.compact.uniq })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ class Dossier < ApplicationRecord
|
|||
include DossierSearchableConcern
|
||||
include DossierSectionsConcern
|
||||
include DossierStateConcern
|
||||
include DossierChampsConcern
|
||||
|
||||
enum state: {
|
||||
brouillon: 'brouillon',
|
||||
|
@ -1036,18 +1037,6 @@ class Dossier < ApplicationRecord
|
|||
columns + champs_for_export(types_de_champ)
|
||||
end
|
||||
|
||||
# Get all the champs values for the types de champ in the final list.
|
||||
# Dossier might not have corresponding champ – display nil.
|
||||
# To do so, we build a virtual champ when there is no value so we can call for_export with all indexes
|
||||
def champs_for_export(types_de_champ, row_id = nil)
|
||||
types_de_champ.flat_map do |type_de_champ|
|
||||
champ = champ_for_export(type_de_champ, row_id)
|
||||
type_de_champ.libelles_for_export.map do |(libelle, path)|
|
||||
[libelle, champ&.for_export(path)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def linked_dossiers_for(instructeur_or_expert)
|
||||
dossier_ids = champs_for_revision.filter(&:dossier_link?).filter_map(&:value)
|
||||
instructeur_or_expert.dossiers.where(id: dossier_ids)
|
||||
|
@ -1141,45 +1130,10 @@ class Dossier < ApplicationRecord
|
|||
user.france_connected_with_one_identity?
|
||||
end
|
||||
|
||||
def champs_for_revision(scope: nil, root: false)
|
||||
champs_index = champs.group_by(&:stable_id)
|
||||
# Due to some bad data we can have multiple copies of the same champ. Ignore extra copy.
|
||||
.transform_values { _1.sort_by(&:id).uniq(&:row_id) }
|
||||
|
||||
if scope.is_a?(TypeDeChamp)
|
||||
revision
|
||||
.children_of(scope)
|
||||
.flat_map { champs_index[_1.stable_id] || [] }
|
||||
.filter(&:child?) # TODO: remove once bad data (child champ without a row id) is cleaned
|
||||
else
|
||||
revision
|
||||
.types_de_champ_for(scope:, root:)
|
||||
.flat_map { champs_index[_1.stable_id] || [] }
|
||||
end
|
||||
end
|
||||
|
||||
def has_annotations?
|
||||
revision.revision_types_de_champ_private.present?
|
||||
end
|
||||
|
||||
def project_champ(type_de_champ, row_id)
|
||||
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
|
||||
if champ.nil?
|
||||
type_de_champ.build_champ(dossier: self, row_id:)
|
||||
else
|
||||
champ
|
||||
end
|
||||
end
|
||||
|
||||
def champ_for_export(type_de_champ, row_id)
|
||||
champ = champs_by_public_id[type_de_champ.public_id(row_id)]
|
||||
if champ.blank? || !champ.visible?
|
||||
nil
|
||||
else
|
||||
champ
|
||||
end
|
||||
end
|
||||
|
||||
def hide_info_with_accuse_lecture?
|
||||
procedure.accuse_lecture? && termine? && accuse_lecture_agreement_at.blank?
|
||||
end
|
||||
|
@ -1190,10 +1144,6 @@ class Dossier < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
def champs_by_public_id
|
||||
@champs_by_public_id ||= champs.sort_by(&:id).index_by(&:public_id)
|
||||
end
|
||||
|
||||
def create_missing_traitemets
|
||||
if en_construction_at.present? && traitements.en_construction.empty?
|
||||
self.traitements.passer_en_construction(processed_at: en_construction_at)
|
||||
|
|
|
@ -23,7 +23,7 @@ class PrefillChamps
|
|||
.to_h
|
||||
|
||||
dossier
|
||||
.find_champs_by_stable_ids(value_by_stable_id.keys)
|
||||
.champs_for_prefill(value_by_stable_id.keys)
|
||||
.map { |champ| [champ, value_by_stable_id[champ.stable_id]] }
|
||||
.map { |champ, value| PrefillValue.new(champ:, value:, dossier:) }
|
||||
end
|
||||
|
|
|
@ -54,14 +54,17 @@ class TypesDeChamp::PrefillRepetitionTypeDeChamp < TypesDeChamp::PrefillTypeDeCh
|
|||
def to_assignable_attributes
|
||||
return unless repetition.is_a?(Hash)
|
||||
|
||||
row = champ.rows[index] || champ.add_row(champ.dossier_revision)
|
||||
row = champ.rows[index] || champ.add_row(revision)
|
||||
row_id = row.first.row_id
|
||||
|
||||
repetition.map do |key, value|
|
||||
next unless key.is_a?(String) && key.starts_with?("champ_")
|
||||
|
||||
subchamp = row.find { |champ| champ.stable_id == Champ.stable_id_from_typed_id(key) }
|
||||
next unless subchamp
|
||||
stable_id = Champ.stable_id_from_typed_id(key)
|
||||
type_de_champ = revision.types_de_champ.find { _1.stable_id == stable_id }
|
||||
next unless type_de_champ
|
||||
|
||||
subchamp = champ.dossier.champ_for_update(type_de_champ, row_id)
|
||||
TypesDeChamp::PrefillTypeDeChamp.build(subchamp.type_de_champ, revision).to_assignable_attributes(subchamp, value)
|
||||
end.compact
|
||||
end
|
||||
|
|
39
app/policies/dossier_policy.rb
Normal file
39
app/policies/dossier_policy.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
class DossierPolicy < ApplicationPolicy
|
||||
# Scope for WRITING to a dossier.
|
||||
#
|
||||
# (If the need for a scope to READ a dossier emerges, we can implement another scope
|
||||
# in this file, following this example: https://github.com/varvet/pundit/issues/368#issuecomment-196111115)
|
||||
class Scope < ApplicationScope
|
||||
def resolve
|
||||
if user.blank?
|
||||
return scope.none
|
||||
end
|
||||
|
||||
# The join must be the same for all elements of the WHERE clause.
|
||||
#
|
||||
# NB: here we want to do `.left_outer_joins(:invites, { :groupe_instructeur: :instructeurs })`,
|
||||
# but for some reasons ActiveRecord <= 5.2 generates bogus SQL. Hence the manual version of it below.
|
||||
joined_scope = scope
|
||||
.joins('LEFT OUTER JOIN invites ON invites.dossier_id = dossiers.id OR invites.dossier_id = dossiers.editing_fork_origin_id')
|
||||
.joins('LEFT OUTER JOIN groupe_instructeurs ON groupe_instructeurs.id = dossiers.groupe_instructeur_id')
|
||||
.joins('LEFT OUTER JOIN assign_tos ON assign_tos.groupe_instructeur_id = groupe_instructeurs.id')
|
||||
.joins('LEFT OUTER JOIN instructeurs ON instructeurs.id = assign_tos.instructeur_id')
|
||||
|
||||
# Users can access public champs on their own dossiers.
|
||||
resolved_scope = joined_scope.where(user_id: user.id)
|
||||
|
||||
# Invited users can access public champs on dossiers they are invited to
|
||||
invite_clause = joined_scope.where('invites.user_id': user.id)
|
||||
resolved_scope = resolved_scope.or(invite_clause)
|
||||
|
||||
if instructeur.present?
|
||||
# Additionnaly, instructeurs can access private champs
|
||||
# on dossiers they are allowed to instruct.
|
||||
instructeur_clause = joined_scope.where('instructeurs.id': instructeur.id)
|
||||
resolved_scope = resolved_scope.or(instructeur_clause)
|
||||
end
|
||||
|
||||
resolved_scope.or(joined_scope.where(for_procedure_preview: true))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,2 @@
|
|||
= turbo_stream.remove "safe-row-selector-#{@row_id}"
|
||||
|
||||
- if @champ.rows.size > 0 && @champ.rows.last&.first&.present?
|
||||
= turbo_stream.focus @champ.rows.last&.first.focusable_input_id
|
||||
- else
|
||||
= turbo_stream.focus dom_id(@champ, :create_repetition)
|
||||
= turbo_stream.remove @to_remove
|
||||
= turbo_stream.focus @to_focus
|
||||
|
|
|
@ -195,9 +195,21 @@ Rails.application.routes.draw do
|
|||
namespace :champs do
|
||||
post ':dossier_id/:stable_id/repetition', to: 'repetition#add', as: :repetition
|
||||
delete ':dossier_id/:stable_id/repetition', to: 'repetition#remove'
|
||||
post ':champ_id/repetition', to: 'repetition#add'
|
||||
delete ':champ_id/repetition', to: 'repetition#remove'
|
||||
|
||||
get ':dossier_id/:stable_id/siret', to: 'siret#show'
|
||||
get ':dossier_id/:stable_id/rna', to: 'rna#show'
|
||||
delete ':dossier_id/:stable_id/options', to: 'options#remove'
|
||||
|
||||
get ':dossier_id/:stable_id/carte/features', to: 'carte#index'
|
||||
post ':dossier_id/:stable_id/carte/features', to: 'carte#create'
|
||||
patch ':dossier_id/:stable_id/carte/features/:id', to: 'carte#update'
|
||||
delete ':dossier_id/:stable_id/carte/features/:id', to: 'carte#destroy'
|
||||
|
||||
get ':dossier_id/:stable_id/piece_justificative', to: 'piece_justificative#show'
|
||||
put ':dossier_id/:stable_id/piece_justificative', to: 'piece_justificative#update'
|
||||
get ':dossier_id/:stable_id/piece_justificative/template', to: 'piece_justificative#template'
|
||||
|
||||
# TODO: remove after migration is ower
|
||||
get ':champ_id/siret', to: 'siret#show', as: :siret
|
||||
get ':champ_id/rna', to: 'rna#show', as: :rna
|
||||
delete ':champ_id/options', to: 'options#remove', as: :options
|
||||
|
|
|
@ -5,8 +5,9 @@ describe Champs::RepetitionController, type: :controller do
|
|||
|
||||
before { sign_in dossier.user }
|
||||
it 'removes repetition' do
|
||||
rows, repetitions = dossier.champs.partition { _1.parent_id.present? }
|
||||
expect { delete :remove, params: { champ_id: repetitions.first.id, row_id: rows.first.row_id }, format: :turbo_stream }
|
||||
rows, repetitions = dossier.champs.partition(&:child?)
|
||||
repetition = repetitions.first
|
||||
expect { delete :remove, params: { dossier_id: repetition.dossier, stable_id: repetition.stable_id, row_id: rows.first.row_id }, format: :turbo_stream }
|
||||
.to change { dossier.reload.champs.size }.from(3).to(1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,8 +7,8 @@ describe Champs::RNAController, type: :controller do
|
|||
let(:champ) { dossier.champs_public.first }
|
||||
|
||||
let(:champs_public_attributes) do
|
||||
champ_attributes = []
|
||||
champ_attributes[champ.id] = { value: rna }
|
||||
champ_attributes = {}
|
||||
champ_attributes[champ.public_id] = { value: rna }
|
||||
champ_attributes
|
||||
end
|
||||
let(:params) do
|
||||
|
|
|
@ -7,13 +7,14 @@ describe Champs::SiretController, type: :controller do
|
|||
let(:champ) { dossier.champs_public.first }
|
||||
|
||||
let(:champs_public_attributes) do
|
||||
champ_attributes = []
|
||||
champ_attributes[champ.id] = { value: siret }
|
||||
champ_attributes = {}
|
||||
champ_attributes[champ.public_id] = { value: siret }
|
||||
champ_attributes
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
champ_id: champ.id,
|
||||
dossier_id: champ.dossier_id,
|
||||
stable_id: champ.stable_id,
|
||||
dossier: {
|
||||
champs_public_attributes: champs_public_attributes
|
||||
}
|
||||
|
|
|
@ -1005,25 +1005,25 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
dossier_id: dossier.id,
|
||||
dossier: {
|
||||
champs_private_attributes: {
|
||||
'0': {
|
||||
id: champ_multiple_drop_down_list.id,
|
||||
champ_multiple_drop_down_list.public_id => {
|
||||
with_public_id: true,
|
||||
value: ['', 'val1', 'val2']
|
||||
},
|
||||
'1': {
|
||||
id: champ_datetime.id,
|
||||
champ_datetime.public_id => {
|
||||
with_public_id: true,
|
||||
value: '2019-12-21T13:17'
|
||||
},
|
||||
'2': {
|
||||
id: champ_linked_drop_down_list.id,
|
||||
champ_linked_drop_down_list.public_id => {
|
||||
with_public_id: true,
|
||||
primary_value: 'primary',
|
||||
secondary_value: 'secondary'
|
||||
},
|
||||
'3': {
|
||||
id: champ_repetition.champs.first.id,
|
||||
champ_repetition.champs.first.public_id => {
|
||||
with_public_id: true,
|
||||
value: 'text'
|
||||
},
|
||||
'4': {
|
||||
id: champ_drop_down_list.id,
|
||||
champ_drop_down_list.public_id => {
|
||||
with_public_id: true,
|
||||
value: '__other__',
|
||||
value_other: 'other value'
|
||||
}
|
||||
|
@ -1074,12 +1074,11 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with invalid champs_public (DecimalNumberChamp)' do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :decimal_number }
|
||||
]
|
||||
end
|
||||
after do
|
||||
Timecop.return
|
||||
end
|
||||
|
||||
context "with new values for champs_private (legacy)" do
|
||||
let(:params) do
|
||||
{
|
||||
procedure_id: procedure.id,
|
||||
|
@ -1095,9 +1094,63 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
}
|
||||
end
|
||||
|
||||
it 'update champs_private' do
|
||||
patch :update_annotations, params: params, format: :turbo_stream
|
||||
champ_datetime.reload
|
||||
expect(champ_datetime.value).to eq(Time.zone.parse('2024-03-30T07:03:00').iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
context "without new values for champs_private" do
|
||||
let(:params) do
|
||||
{
|
||||
procedure_id: procedure.id,
|
||||
dossier_id: dossier.id,
|
||||
dossier: {
|
||||
champs_private_attributes: {},
|
||||
champs_public_attributes: {
|
||||
champ_multiple_drop_down_list.public_id => {
|
||||
with_public_id: true,
|
||||
value: ['', 'val1', 'val2']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it {
|
||||
expect(dossier.reload.last_champ_private_updated_at).to eq(nil)
|
||||
expect(response).to have_http_status(200)
|
||||
}
|
||||
end
|
||||
|
||||
context "with invalid champs_public (DecimalNumberChamp)" do
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :decimal_number }
|
||||
]
|
||||
end
|
||||
|
||||
let(:champ_decimal_number) { dossier.champs_public.first }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
procedure_id: procedure.id,
|
||||
dossier_id: dossier.id,
|
||||
dossier: {
|
||||
champs_private_attributes: {
|
||||
champ_datetime.public_id => {
|
||||
with_public_id: true,
|
||||
value: '2024-03-30T07:03'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'update champs_private' do
|
||||
too_long_float = '3.1415'
|
||||
dossier.champs_public.first.update_column(:value, too_long_float)
|
||||
champ_decimal_number.update_column(:value, too_long_float)
|
||||
patch :update_annotations, params: params, format: :turbo_stream
|
||||
champ_datetime.reload
|
||||
expect(champ_datetime.value).to eq(Time.zone.parse('2024-03-30T07:03:00').iso8601)
|
||||
|
|
|
@ -674,12 +674,12 @@ describe Users::DossiersController, type: :controller do
|
|||
dossier: {
|
||||
groupe_instructeur_id: dossier.groupe_instructeur_id,
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
first_champ.public_id => {
|
||||
with_public_id: true,
|
||||
value: value
|
||||
},
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_champ.public_id => {
|
||||
with_public_id: true,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
}
|
||||
|
@ -719,7 +719,7 @@ describe Users::DossiersController, type: :controller do
|
|||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: { first_champ.id => { id: first_champ.id } }
|
||||
champs_public_attributes: { first_champ.public_id => { with_public_id: true } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -747,7 +747,7 @@ describe Users::DossiersController, type: :controller do
|
|||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: { first_champ.id => { id: first_champ.id, value: value } }
|
||||
champs_public_attributes: { first_champ.public_id => { with_public_id: true, value: value } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -790,12 +790,12 @@ describe Users::DossiersController, type: :controller do
|
|||
dossier: {
|
||||
groupe_instructeur_id: dossier.groupe_instructeur_id,
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
first_champ.public_id => {
|
||||
with_public_id: true,
|
||||
value: value
|
||||
},
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_champ.public_id => {
|
||||
with_public_id: true,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
}
|
||||
|
@ -855,8 +855,8 @@ describe Users::DossiersController, type: :controller do
|
|||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: {
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_champ.public_id => {
|
||||
with_public_id: true,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
}
|
||||
|
@ -951,8 +951,8 @@ describe Users::DossiersController, type: :controller do
|
|||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
first_champ.public_id => {
|
||||
with_public_id: true,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
|
294
spec/models/concern/dossier_champs_concern_spec.rb
Normal file
294
spec/models/concern/dossier_champs_concern_spec.rb
Normal file
|
@ -0,0 +1,294 @@
|
|||
RSpec.describe DossierChampsConcern do
|
||||
let(:procedure) do
|
||||
create(:procedure, types_de_champ_public:, types_de_champ_private:)
|
||||
end
|
||||
let(:types_de_champ_public) do
|
||||
[
|
||||
{ type: :text, libelle: "Un champ text", stable_id: 99 },
|
||||
{ type: :text, libelle: "Un autre champ text", stable_id: 991 },
|
||||
{ type: :yes_no, libelle: "Un champ yes no", stable_id: 992 },
|
||||
{ type: :repetition, libelle: "Un champ répétable", stable_id: 993, mandatory: true, children: [{ type: :text, libelle: 'Nom', stable_id: 994 }] }
|
||||
]
|
||||
end
|
||||
let(:types_de_champ_private) do
|
||||
[
|
||||
{ type: :text, libelle: "Une annotation", stable_id: 995 }
|
||||
]
|
||||
end
|
||||
let(:dossier) { create(:dossier, procedure:) }
|
||||
|
||||
describe "#find_type_de_champ_by_stable_id(public)" do
|
||||
subject { dossier.find_type_de_champ_by_stable_id(992, :public) }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
describe "#find_type_de_champ_by_stable_id(private)" do
|
||||
subject { dossier.find_type_de_champ_by_stable_id(995, :private) }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
describe "#project_champ" do
|
||||
let(:type_de_champ_repetition) { dossier.find_type_de_champ_by_stable_id(993) }
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(99) }
|
||||
let(:type_de_champ_private) { dossier.find_type_de_champ_by_stable_id(995) }
|
||||
let(:row_ids) { dossier.project_champ(type_de_champ_repetition, nil).row_ids }
|
||||
|
||||
context "public champ" do
|
||||
let(:row_id) { nil }
|
||||
subject { dossier.project_champ(type_de_champ_public, row_id) }
|
||||
|
||||
it { expect(subject.persisted?).to be_truthy }
|
||||
|
||||
context "in repetition" do
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(994) }
|
||||
let(:row_id) { row_ids.first }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
expect(subject.parent_id).not_to be_nil
|
||||
}
|
||||
end
|
||||
|
||||
context "missing champ" do
|
||||
before { dossier; Champs::TextChamp.destroy_all }
|
||||
|
||||
it {
|
||||
expect(subject.new_record?).to be_truthy
|
||||
expect(subject.is_a?(Champs::TextChamp)).to be_truthy
|
||||
}
|
||||
|
||||
context "in repetition" do
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(994) }
|
||||
let(:row_id) { row_ids.first }
|
||||
|
||||
it {
|
||||
expect(subject.new_record?).to be_truthy
|
||||
expect(subject.is_a?(Champs::TextChamp)).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "private champ" do
|
||||
subject { dossier.project_champ(type_de_champ_private, nil) }
|
||||
|
||||
it { expect(subject.persisted?).to be_truthy }
|
||||
|
||||
context "missing champ" do
|
||||
before { dossier; Champs::TextChamp.destroy_all }
|
||||
|
||||
it {
|
||||
expect(subject.new_record?).to be_truthy
|
||||
expect(subject.is_a?(Champs::TextChamp)).to be_truthy
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#champs_for_export" do
|
||||
subject { dossier.champs_for_export(dossier.revision.types_de_champ_public) }
|
||||
|
||||
it { expect(subject.size).to eq(4) }
|
||||
it { expect(subject.first).to eq(["Un champ text", nil]) }
|
||||
end
|
||||
|
||||
describe "#champs_for_prefill" do
|
||||
subject { dossier.champs_for_prefill([991, 995]) }
|
||||
|
||||
it {
|
||||
expect(subject.size).to eq(2)
|
||||
expect(subject.map(&:libelle)).to eq(["Une annotation", "Un autre champ text"])
|
||||
expect(subject.all?(&:persisted?)).to be_truthy
|
||||
}
|
||||
|
||||
context "missing champ" do
|
||||
before { dossier; Champs::TextChamp.destroy_all }
|
||||
|
||||
it {
|
||||
expect(subject.size).to eq(2)
|
||||
expect(subject.map(&:libelle)).to eq(["Une annotation", "Un autre champ text"])
|
||||
expect(subject.all?(&:persisted?)).to be_truthy
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#champ_for_update" do
|
||||
let(:type_de_champ_repetition) { dossier.find_type_de_champ_by_stable_id(993) }
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(99) }
|
||||
let(:type_de_champ_private) { dossier.find_type_de_champ_by_stable_id(995) }
|
||||
let(:row_ids) { dossier.project_champ(type_de_champ_repetition, nil).row_ids }
|
||||
let(:row_id) { nil }
|
||||
|
||||
context "public champ" do
|
||||
subject { dossier.champ_for_update(type_de_champ_public, row_id) }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
}
|
||||
|
||||
context "in repetition" do
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(994) }
|
||||
let(:row_id) { row_ids.first }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
expect(subject.parent_id).not_to be_nil
|
||||
}
|
||||
end
|
||||
|
||||
context "missing champ" do
|
||||
before { dossier; Champs::TextChamp.destroy_all }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.is_a?(Champs::TextChamp)).to be_truthy
|
||||
}
|
||||
|
||||
context "in repetition" do
|
||||
let(:type_de_champ_public) { dossier.find_type_de_champ_by_stable_id(994) }
|
||||
let(:row_id) { row_ids.first }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.is_a?(Champs::TextChamp)).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
expect(subject.parent_id).not_to be_nil
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "private champ" do
|
||||
subject { dossier.champ_for_update(type_de_champ_private, row_id) }
|
||||
|
||||
it {
|
||||
expect(subject.persisted?).to be_truthy
|
||||
expect(subject.row_id).to eq(row_id)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_champs_attributes(public)" do
|
||||
let(:type_de_champ_repetition) { dossier.find_type_de_champ_by_stable_id(993) }
|
||||
let(:row_ids) { dossier.project_champ(type_de_champ_repetition, nil).row_ids }
|
||||
let(:row_id) { row_ids.first }
|
||||
|
||||
let(:attributes) do
|
||||
{
|
||||
"99" => { value: "Hello", with_public_id: true },
|
||||
"991" => { value: "World", with_public_id: true },
|
||||
"994-#{row_id}" => { value: "Greer", with_public_id: true }
|
||||
}
|
||||
end
|
||||
|
||||
let(:champ_99) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(99), nil) }
|
||||
let(:champ_991) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(991), nil) }
|
||||
let(:champ_994) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(994), row_id) }
|
||||
|
||||
subject { dossier.update_champs_attributes(attributes, :public) }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs.any?(&:changed_for_autosave?)).to be_truthy
|
||||
expect(champ_99.changed?).to be_truthy
|
||||
expect(champ_991.changed?).to be_truthy
|
||||
expect(champ_994.changed?).to be_truthy
|
||||
expect(champ_99.value).to eq("Hello")
|
||||
expect(champ_991.value).to eq("World")
|
||||
expect(champ_994.value).to eq("Greer")
|
||||
}
|
||||
|
||||
context "missing champs" do
|
||||
before { dossier; Champs::TextChamp.destroy_all; }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs.any?(&:changed_for_autosave?)).to be_truthy
|
||||
expect(champ_99.changed?).to be_truthy
|
||||
expect(champ_991.changed?).to be_truthy
|
||||
expect(champ_994.changed?).to be_truthy
|
||||
expect(champ_99.value).to eq("Hello")
|
||||
expect(champ_991.value).to eq("World")
|
||||
expect(champ_994.value).to eq("Greer")
|
||||
}
|
||||
end
|
||||
|
||||
context 'legacy attributes' do
|
||||
let(:attributes) do
|
||||
{
|
||||
champ_99.id => { value: "Hello", id: champ_99.id },
|
||||
champ_991.id => { value: "World", id: champ_991.id },
|
||||
champ_994.id => { value: "Greer", id: champ_994.id }
|
||||
}
|
||||
end
|
||||
|
||||
let(:champ_99_updated) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(99), nil) }
|
||||
let(:champ_991_updated) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(991), nil) }
|
||||
let(:champ_994_updated) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(994), row_id) }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs_public_all.any?(&:changed_for_autosave?)).to be_truthy
|
||||
dossier.save
|
||||
dossier.reload
|
||||
expect(champ_99_updated.value).to eq("Hello")
|
||||
expect(champ_991_updated.value).to eq("World")
|
||||
expect(champ_994_updated.value).to eq("Greer")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_champs_attributes(private)" do
|
||||
let(:attributes) do
|
||||
{
|
||||
"995" => { value: "Hello", with_public_id: true }
|
||||
}
|
||||
end
|
||||
|
||||
let(:annotation_995) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(995), nil) }
|
||||
|
||||
subject { dossier.update_champs_attributes(attributes, :private) }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs.any?(&:changed_for_autosave?)).to be_truthy
|
||||
expect(annotation_995.changed?).to be_truthy
|
||||
expect(annotation_995.value).to eq("Hello")
|
||||
}
|
||||
|
||||
context "missing champs" do
|
||||
before { dossier; Champs::TextChamp.destroy_all; }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs.any?(&:changed_for_autosave?)).to be_truthy
|
||||
expect(annotation_995.changed?).to be_truthy
|
||||
expect(annotation_995.value).to eq("Hello")
|
||||
}
|
||||
end
|
||||
|
||||
context 'legacy attributes' do
|
||||
let(:attributes) do
|
||||
{
|
||||
annotation_995.id => { value: "Hello", id: annotation_995.id }
|
||||
}
|
||||
end
|
||||
|
||||
let(:annotation_995_updated) { dossier.project_champ(dossier.find_type_de_champ_by_stable_id(995), nil) }
|
||||
|
||||
it {
|
||||
subject
|
||||
expect(dossier.champs_private_all.any?(&:changed_for_autosave?)).to be_truthy
|
||||
dossier.save
|
||||
dossier.reload
|
||||
expect(annotation_995_updated.value).to eq("Hello")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2183,41 +2183,6 @@ describe Dossier, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#find_champs_by_stable_ids' do
|
||||
let(:procedure) { create(:procedure, :published) }
|
||||
let(:dossier) { create(:dossier, :brouillon, procedure: procedure) }
|
||||
|
||||
subject { dossier.find_champs_by_stable_ids(stable_ids) }
|
||||
|
||||
context 'when stable_ids is empty' do
|
||||
let(:stable_ids) { [] }
|
||||
|
||||
it { expect(subject).to match([]) }
|
||||
end
|
||||
|
||||
context 'when stable_ids contains nil or blank values' do
|
||||
let(:stable_ids) { [nil, ""] }
|
||||
|
||||
it { expect(subject).to match([]) }
|
||||
end
|
||||
|
||||
context 'when stable_ids contains present values' do
|
||||
context 'when the dossier has no champ with the given stable ids' do
|
||||
let(:stable_ids) { ['My Neighbor Totoro', 'Miyazaki'] }
|
||||
|
||||
it { expect(subject).to match([]) }
|
||||
end
|
||||
|
||||
context 'when the dossier has champs with the given stable ids' do
|
||||
let!(:type_de_champ_1) { create(:type_de_champ_text, procedure: procedure) }
|
||||
let!(:type_de_champ_2) { create(:type_de_champ_textarea, procedure: procedure) }
|
||||
let(:stable_ids) { [type_de_champ_1.stable_id, type_de_champ_2.stable_id] }
|
||||
|
||||
it { expect(subject).to match_array(dossier.champs_public.joins(:type_de_champ).where(types_de_champ: { stable_id: stable_ids })) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'BatchOperation' do
|
||||
subject { build(:dossier) }
|
||||
it { is_expected.to belong_to(:batch_operation).optional }
|
||||
|
|
79
spec/policies/dossier_policy_spec.rb
Normal file
79
spec/policies/dossier_policy_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
describe DossierPolicy do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure, user: dossier_owner) }
|
||||
let(:dossier_owner) { create(:user) }
|
||||
|
||||
let(:signed_in_user) { create(:user) }
|
||||
let(:account) { { user: signed_in_user } }
|
||||
|
||||
subject { Pundit.policy_scope(account, Dossier) }
|
||||
|
||||
shared_examples_for 'they can access dossier' do
|
||||
it { expect(subject.find_by(id: dossier.id)).to eq(dossier) }
|
||||
end
|
||||
|
||||
shared_examples_for 'they can’t access dossier' do
|
||||
it { expect(subject.find_by(id: dossier.id)).to eq(nil) }
|
||||
end
|
||||
|
||||
context 'when an user only has user rights' do
|
||||
context 'as the dossier owner' do
|
||||
let(:signed_in_user) { dossier_owner }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
end
|
||||
|
||||
context 'as a person invited on the dossier' do
|
||||
let(:invite) { create(:invite, :with_user, dossier: dossier) }
|
||||
let(:signed_in_user) { invite.user }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
end
|
||||
|
||||
context 'as another user' do
|
||||
let(:signed_in_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'they can’t access dossier'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user also has instruction rights' do
|
||||
let(:instructeur) { create(:instructeur, user: signed_in_user) }
|
||||
let(:account) { { user: signed_in_user, instructeur: instructeur } }
|
||||
|
||||
context 'as the dossier instructeur and owner' do
|
||||
let(:signed_in_user) { dossier_owner }
|
||||
before { instructeur.assign_to_procedure(dossier.procedure) }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
end
|
||||
|
||||
context 'as the dossier instructeur (but not owner)' do
|
||||
let(:signed_in_user) { create(:user) }
|
||||
before { instructeur.assign_to_procedure(dossier.procedure) }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
end
|
||||
|
||||
context 'as an instructeur not assigned to the procedure' do
|
||||
let(:signed_in_user) { create(:user) }
|
||||
|
||||
it_behaves_like 'they can’t access dossier'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the champ is on a forked dossier' do
|
||||
let(:signed_in_user) { dossier_owner }
|
||||
let(:origin) { create(:dossier, procedure: procedure, user: dossier_owner) }
|
||||
let(:dossier) { origin.find_or_create_editing_fork(dossier_owner) }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
|
||||
context 'when the user is invited on the origin dossier' do
|
||||
let(:invite) { create(:invite, :with_user, dossier: origin) }
|
||||
let(:signed_in_user) { invite.user }
|
||||
|
||||
it_behaves_like 'they can access dossier'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue