2019-08-01-01 (#4162)

2019-08-01-01
This commit is contained in:
Pierre de La Morinerie 2019-08-01 12:05:08 +02:00 committed by GitHub
commit da692483cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 524 additions and 123 deletions

View file

@ -47,6 +47,7 @@ gem 'prawn' # PDF Generation
gem 'prawn_rails'
gem 'premailer-rails'
gem 'puma' # Use Puma as the app server
gem 'pundit'
gem 'rack-mini-profiler'
gem 'rails'
gem 'rails-i18n' # Locales par défaut

View file

@ -431,6 +431,8 @@ GEM
pry (~> 0.10)
public_suffix (3.0.3)
puma (3.12.0)
pundit (2.0.1)
activesupport (>= 3.0.0)
rack (2.0.6)
rack-mini-profiler (1.0.1)
rack (>= 1.2.0)
@ -749,6 +751,7 @@ DEPENDENCIES
premailer-rails
pry-byebug
puma
pundit
rack-mini-profiler
rails
rails-controller-testing

View file

@ -1,5 +1,6 @@
class ApplicationController < ActionController::Base
include TrustedDeviceConcern
include Pundit
MAINTENANCE_MESSAGE = 'Le site est actuellement en maintenance. Il sera à nouveau disponible dans un court instant.'
@ -41,12 +42,18 @@ class ApplicationController < ActionController::Base
logged_user.present?
end
def logged_user_ids
logged_users.map(&:id)
end
helper_method :logged_in?
def pundit_user
if administrateur_signed_in?
current_administrateur
elsif gestionnaire_signed_in?
current_gestionnaire
else
current_user
end
end
protected
def authenticate_logged_user!

View file

@ -14,15 +14,9 @@ class Champs::CarteController < ApplicationController
end
@champ = if params[:champ_id].present?
Champ
.joins(:dossier)
.where(dossiers: { user_id: logged_user_ids })
.find(params[:champ_id])
policy_scope(Champ).find(params[:champ_id])
else
TypeDeChamp
.joins(:procedure)
.where(procedures: { administrateur_id: logged_user_ids })
.find(params[:type_de_champ_id]).champ.build
policy_scope(TypeDeChamp).find(params[:type_de_champ_id]).champ.build
end
geo_areas = []
@ -61,11 +55,15 @@ class Champs::CarteController < ApplicationController
end
end
selection_utilisateur = ApiCartoService.generate_selection_utilisateur(coordinates)
selection_utilisateur[:source] = GeoArea.sources.fetch(:selection_utilisateur)
geo_areas << selection_utilisateur
@champ.geo_areas = geo_areas.map do |geo_area|
GeoArea.new(geo_area)
end
@champ.value = GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates)
@champ.value = coordinates.to_json
end
if @champ.persisted?

View file

@ -2,10 +2,7 @@ class Champs::RepetitionController < ApplicationController
before_action :authenticate_logged_user!
def show
@champ = Champ
.joins(:dossier)
.where(dossiers: { user_id: logged_user_ids })
.find(params[:champ_id])
@champ = policy_scope(Champ).find(params[:champ_id])
@position = params[:position]
row = (@champ.champs.empty? ? 0 : @champ.champs.last.row) + 1

View file

@ -108,10 +108,14 @@ module Gestionnaires
def repasser_en_instruction
if dossier.en_instruction?
flash.notice = 'Le dossier est déjà en instruction.'
else
if dossier.accepte?
flash.notice = 'Il nest pas possible de repasser un dossier accepté en instruction.'
else
flash.notice = "Le dossier #{dossier.id} a été repassé en instruction."
dossier.repasser_en_instruction!(current_gestionnaire)
end
end
render partial: 'state_button_refresh', locals: { dossier: dossier }
end

View file

@ -30,6 +30,16 @@ module Manager
redirect_to manager_dossier_path(dossier)
end
def repasser_en_instruction
dossier = Dossier.find(params[:id])
dossier.repasser_en_instruction(current_administration)
logger.info("Le dossier #{dossier.id} est repassé en instruction par #{current_administration.email}")
flash[:notice] = "Le dossier #{dossier.id} est repassé en instruction."
redirect_to manager_dossier_path(dossier)
end
private
def unfiltered_list?

View file

@ -26,7 +26,7 @@ module ProcedureHelper
if logo.blank?
ActionController::Base.helpers.image_url("marianne.svg")
else
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(logo.filename).url
else
LocalDownloader.new(logo.path, 'logo').url

View file

@ -68,9 +68,13 @@ const TypeDeChamp = sortableElement(
<div className="flex justify-start delete">
<button
className="button small icon-only danger"
onClick={() =>
dispatch({ type: 'removeTypeDeChamp', params: { typeDeChamp } })
}
onClick={() => {
if (confirm('Êtes vous sûr de vouloir supprimer ce champ ?'))
dispatch({
type: 'removeTypeDeChamp',
params: { typeDeChamp }
});
}}
>
<FontAwesomeIcon icon="trash" title="Supprimer" />
</button>

View file

@ -3,7 +3,7 @@ class WeeklyOverviewJob < ApplicationJob
def perform(*args)
# Feature flipped to avoid mails in staging due to unprocessed dossier
if Flipflop.weekly_overview?
if Rails.application.config.ds_weekly_overview
Gestionnaire.all
.map { |gestionnaire| [gestionnaire, gestionnaire.last_week_overview] }
.reject { |_, overview| overview.nil? }

View file

@ -2,4 +2,13 @@ class Attestation < ApplicationRecord
belongs_to :dossier
mount_uploader :pdf, AttestationUploader
def pdf_url
if Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(pdf.path).url
elsif pdf&.url
# FIXME: this is horrible but used only in dev and will be removed after migration
File.join(LOCAL_DOWNLOAD_URL, pdf.url)
end
end
end

View file

@ -5,10 +5,11 @@ class Champ < ApplicationRecord
has_many :commentaires
has_one_attached :piece_justificative_file
# We declare champ specific relationships (Champs::CarteChamp and Champs::SiretChamp)
# We declare champ specific relationships (Champs::CarteChamp, Champs::SiretChamp and Champs::RepetitionChamp)
# here because otherwise we can't easily use includes in our queries.
has_many :geo_areas, dependent: :destroy
belongs_to :etablissement, dependent: :destroy
has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, to: :type_de_champ

View file

@ -19,6 +19,10 @@ class Champs::CarteChamp < Champ
end
end
def selection_utilisateur
geo_areas.find(&:selection_utilisateur?)
end
def cadastres?
type_de_champ&.cadastres && type_de_champ.cadastres != '0'
end
@ -45,6 +49,49 @@ class Champs::CarteChamp < Champ
def geo_json
@geo_json ||= begin
geo_area = selection_utilisateur
if geo_area
geo_area.geometry
else
geo_json_from_value
end
end
end
def selection_utilisateur_size
if geo_json.present?
geo_json['coordinates'].size
else
0
end
end
def to_render_data
{
position: position,
selection: user_geo_area&.geometry,
quartiersPrioritaires: quartiers_prioritaires? ? quartiers_prioritaires.as_json(except: :properties) : [],
cadastres: cadastres? ? cadastres.as_json(except: :properties) : [],
parcellesAgricoles: parcelles_agricoles? ? parcelles_agricoles.as_json(except: :properties) : []
}
end
def user_geo_area
geo_area = selection_utilisateur
if geo_area.present?
geo_area
elsif geo_json_from_value.present?
GeoArea.new(
geometry: geo_json_from_value,
source: GeoArea.sources.fetch(:selection_utilisateur)
)
end
end
def geo_json_from_value
@geo_json_from_value ||= begin
parsed_value = value.blank? ? nil : JSON.parse(value)
# We used to store in the value column a json array with coordinates.
if parsed_value.is_a?(Array)
@ -62,34 +109,11 @@ class Champs::CarteChamp < Champ
end
end
def selection_utilisateur_size
if geo_json.present?
geo_json['coordinates'].size
else
0
end
end
def to_render_data
{
position: position,
selection: geo_json,
quartiersPrioritaires: quartiers_prioritaires? ? quartiers_prioritaires.as_json(except: :properties) : [],
cadastres: cadastres? ? cadastres.as_json(except: :properties) : [],
parcellesAgricoles: parcelles_agricoles? ? parcelles_agricoles.as_json(except: :properties) : []
}
end
def user_geo_area
if geo_json.present?
GeoArea.new(
geometry: geo_json,
source: GeoArea.sources.fetch(:selection_utilisateur)
)
end
end
def for_api
geo_json&.to_json
nil
end
def for_export
nil
end
end

View file

@ -1,6 +1,4 @@
class Champs::RepetitionChamp < Champ
has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
accepts_nested_attributes_for :champs, allow_destroy: true
def rows
@ -21,11 +19,21 @@ class Champs::RepetitionChamp < Champ
# The user cannot enter any information here so it doesnt make much sense to search
end
def rows_for_export
rows.each.with_index(1).map do |champs, index|
Champs::RepetitionChamp::Row.new(index: index, dossier_id: dossier_id.to_s, champs: champs)
end
end
class Row < Hashie::Dash
property :index
property :dossier_id
property :champs
def read_attribute_for_serialization(attribute)
self[attribute]
end
def spreadsheet_columns
[
['Dossier ID', :dossier_id],

View file

@ -52,7 +52,7 @@ class Commentaire < ApplicationRecord
if piece_jointe.virus_scanner.safe?
Rails.application.routes.url_helpers.url_for(piece_jointe)
end
elsif Flipflop.remote_storage?
elsif Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(file.path).url
elsif file&.url
# FIXME: this is horrible but used only in dev and will be removed after migration

View file

@ -86,6 +86,7 @@ class Dossier < ApplicationRecord
event :repasser_en_instruction, after: :after_repasser_en_instruction do
transitions from: :refuse, to: :en_instruction
transitions from: :sans_suite, to: :en_instruction
transitions from: :accepte, to: :en_instruction
end
end
@ -108,7 +109,24 @@ class Dossier < ApplicationRecord
scope :en_construction, -> { not_archived.state_en_construction }
scope :en_instruction, -> { not_archived.state_en_instruction }
scope :termine, -> { not_archived.state_termine }
scope :downloadable_sorted, -> { state_not_brouillon.includes(:etablissement, :user, :individual, :followers_gestionnaires, :avis, champs: { etablissement: [:champ], type_de_champ: :drop_down_list }, champs_private: { etablissement: [:champ], type_de_champ: :drop_down_list }).order(en_construction_at: 'asc') }
scope :downloadable_sorted, -> {
state_not_brouillon
.includes(
:user,
:individual,
:followers_gestionnaires,
:avis,
etablissement: :champ,
champs: {
etablissement: :champ,
type_de_champ: :drop_down_list
},
champs_private: {
etablissement: :champ,
type_de_champ: :drop_down_list
}
).order(en_construction_at: 'asc')
}
scope :en_cours, -> { not_archived.state_en_construction_ou_instruction }
scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) }
scope :followed_by, -> (gestionnaire) { joins(:follows).where(follows: { gestionnaire: gestionnaire }) }
@ -120,14 +138,22 @@ class Dossier < ApplicationRecord
champs: [
:geo_areas,
:etablissement,
piece_justificative_file_attachment: :blob,
champs: [
piece_justificative_file_attachment: :blob
]
],
champs_private: [
:geo_areas,
:etablissement,
piece_justificative_file_attachment: :blob,
champs: [
piece_justificative_file_attachment: :blob
]
],
avis: [],
justificatif_motivation_attachment: :blob,
attestation: [],
avis: { piece_justificative_file_attachment: :blob },
etablissement: [],
individual: [],
user: [])

View file

@ -120,6 +120,6 @@ class Etablissement < ApplicationRecord
end
def libelle_for_export
champ&.libelle
champ&.libelle || 'Dossier'
end
end

View file

@ -30,4 +30,8 @@ class GeoArea < ApplicationRecord
scope :quartiers_prioritaires, -> { where(source: sources.fetch(:quartier_prioritaire)) }
scope :cadastres, -> { where(source: sources.fetch(:cadastre)) }
scope :parcelles_agricoles, -> { where(source: sources.fetch(:parcelle_agricole)) }
def selection_utilisateur?
source == self.class.sources.fetch(:selection_utilisateur)
end
end

View file

@ -225,6 +225,10 @@ class Gestionnaire < ApplicationRecord
end
end
def user
User.find_by(email: email)
end
private
def annotations_hash(demande, annotations_privees, avis, messagerie)

View file

@ -0,0 +1,49 @@
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope.all
end
end
end

View file

@ -0,0 +1,24 @@
class ChampPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.is_a?(User)
scope
.joins(:dossier)
.where({ dossiers: { user_id: user.id } })
elsif user.is_a?(Gestionnaire)
scope_with_join = scope.joins(dossier: :follows)
scope_with_left_join = scope.left_joins(dossier: :follows)
if user.user
scope_with_left_join
.where({ dossiers: { user_id: user.user.id } })
.or(scope_with_left_join.where(dossiers: { follows: { gestionnaire_id: user.id } }))
else
scope_with_join.where(dossiers: { follows: { gestionnaire_id: user.id } })
end
else
scope.none
end
end
end
end

View file

@ -0,0 +1,13 @@
class TypeDeChampPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.is_a?(Administrateur)
scope
.joins(procedure: [:administrateurs])
.where({ administrateurs: { id: user.id } })
else
scope.none
end
end
end
end

View file

@ -8,6 +8,7 @@ class ChampSerializer < ActiveModel::Serializer
has_many :geo_areas, if: :include_geo_areas?
has_one :etablissement, if: :include_etablissement?
has_one :entreprise, if: :include_etablissement?
has_many :rows, serializer: RowSerializer, if: :include_rows?
def value
case object
@ -35,6 +36,10 @@ class ChampSerializer < ActiveModel::Serializer
object.etablissement&.entreprise
end
def rows
object.rows_for_export
end
def include_etablissement?
object.is_a?(Champs::SiretChamp)
end
@ -43,6 +48,10 @@ class ChampSerializer < ActiveModel::Serializer
object.is_a?(Champs::CarteChamp)
end
def include_rows?
object.is_a?(Champs::RepetitionChamp)
end
private
def legacy_type_de_champ

View file

@ -14,6 +14,9 @@ class DossierSerializer < ActiveModel::Serializer
:motivation,
:instructeurs
attribute :attestation, if: :include_attestation?
attribute :justificatif_motivation, if: :include_justificatif_motivation?
has_one :individual
has_one :entreprise
has_one :etablissement
@ -22,7 +25,6 @@ class DossierSerializer < ActiveModel::Serializer
has_many :champs_private
has_many :pieces_justificatives
has_many :types_de_piece_justificative
has_one :justificatif_motivation
has_many :avis
has_many :champs, serializer: ChampSerializer
@ -37,7 +39,9 @@ class DossierSerializer < ActiveModel::Serializer
if champ_carte.present?
carto_champs = champ_carte.geo_areas.to_a
if !carto_champs.find(&:selection_utilisateur?)
carto_champs << champ_carte.user_geo_area
end
champs += carto_champs.compact
end
end
@ -53,10 +57,12 @@ class DossierSerializer < ActiveModel::Serializer
PiecesJustificativesService.serialize_champs_as_pjs(object)
end
def justificatif_motivation
if object.justificatif_motivation.attached?
Rails.application.routes.url_helpers.url_for(object.justificatif_motivation)
def attestation
object.attestation.pdf_url
end
def justificatif_motivation
Rails.application.routes.url_helpers.url_for(object.justificatif_motivation)
end
def types_de_piece_justificative
@ -102,4 +108,12 @@ class DossierSerializer < ActiveModel::Serializer
def processed_at
object.processed_at&.in_time_zone('UTC')
end
def include_attestation?
object.accepte?
end
def include_justificatif_motivation?
object.justificatif_motivation.attached?
end
end

View file

@ -0,0 +1,5 @@
class RowSerializer < ActiveModel::Serializer
has_many :champs, serializer: ChampSerializer
attribute :index, key: :id
end

View file

@ -22,4 +22,10 @@ class ApiCartoService
).results
end
end
def self.generate_selection_utilisateur(coordinates)
{
geometry: JSON.parse(GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates))
}
end
end

View file

@ -53,16 +53,14 @@ class ProcedureExportV2Service
[dossier.champs, dossier.champs_private]
.flatten
.select { |champ| champ.is_a?(Champs::RepetitionChamp) }
end
end.group_by(&:libelle)
end
def champs_repetables_options
champs_repetables.map do |champ|
champs_repetables.map do |libelle, champs|
[
champ.libelle,
champ.rows.each_with_index.map do |champs, index|
Champs::RepetitionChamp::Row.new(index: index + 1, dossier_id: champ.dossier_id.to_s, champs: champs)
end
libelle,
champs.flat_map(&:rows_for_export)
]
end
end

View file

@ -4,7 +4,7 @@ class AttestationTemplateLogoUploader < BaseUploader
end
# Choose what kind of storage to use for this uploader:
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
storage :fog
else
storage :file
@ -13,7 +13,7 @@ class AttestationTemplateLogoUploader < BaseUploader
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if !Flipflop.remote_storage?
if !Rails.application.secrets.fog[:enabled]
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end

View file

@ -4,7 +4,7 @@ class AttestationTemplateSignatureUploader < BaseUploader
end
# Choose what kind of storage to use for this uploader:
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
storage :fog
else
storage :file
@ -13,7 +13,7 @@ class AttestationTemplateSignatureUploader < BaseUploader
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if !Flipflop.remote_storage?
if !Rails.application.secrets.fog[:enabled]
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end

View file

@ -4,7 +4,7 @@ class AttestationUploader < BaseUploader
end
# Choose what kind of storage to use for this uploader:
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
storage :fog
else
storage :file
@ -13,7 +13,7 @@ class AttestationUploader < BaseUploader
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if !Flipflop.remote_storage?
if !Rails.application.secrets.fog[:enabled]
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end

View file

@ -3,7 +3,7 @@ class CommentaireFileUploader < BaseUploader
Rails.root.join("public")
end
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
storage :fog
else
storage :file

View file

@ -4,7 +4,7 @@ class ProcedureLogoUploader < BaseUploader
end
# Choose what kind of storage to use for this uploader:
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
storage :fog
else
storage :file
@ -13,7 +13,7 @@ class ProcedureLogoUploader < BaseUploader
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if !Flipflop.remote_storage?
if !Rails.application.secrets.fog[:enabled]
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
@ -27,7 +27,7 @@ class ProcedureLogoUploader < BaseUploader
def filename
if file.present?
if original_filename.present? || model.logo_secure_token
if Flipflop.remote_storage?
if Rails.application.secrets.fog[:enabled]
filename = "#{model.class.to_s.underscore}-#{secure_token}.#{file.extension&.downcase}"
else
filename = "logo-#{secure_token}.#{file.extension&.downcase}"

View file

@ -28,6 +28,9 @@ as well as a link to its edit page.
</h1>
<div>
<% if dossier.accepte? %>
<%= link_to 'Repasser en instruction', repasser_en_instruction_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous le passage en instruction du dossier ?" } %>
<% end %>
<% if dossier.hidden_at.nil? %>
<%= link_to 'Supprimer le dossier', hide_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
<% end %>

View file

@ -39,5 +39,7 @@ module TPS
# Make main application helpers available in administrate
Administrate::ApplicationController.helper(TPS::Application.helpers)
end
config.ds_weekly_overview = ENV['APP_NAME'] == 'tps'
end
end

View file

@ -29,12 +29,8 @@ Flipflop.configure do
end
group :production do
feature :remote_storage,
default: ENV['FOG_ENABLED'] == 'enabled'
feature :insee_api_v3,
default: true
feature :weekly_overview,
default: ENV['APP_NAME'] == 'tps'
feature :pre_maintenance_mode
feature :maintenance_mode
end

View file

@ -16,6 +16,7 @@ Rails.application.routes.draw do
resources :dossiers, only: [:index, :show] do
post 'hide', on: :member
post 'repasser_en_instruction', on: :member
end
resources :administrateurs, only: [:index, :show, :new, :create] do

View file

@ -34,6 +34,7 @@ defaults: &defaults
pipedrive:
key: <%= ENV['PIPEDRIVE_KEY'] %>
fog:
enabled: <%= ENV['FOG_ENABLED'] == 'enabled' %>
openstack_tenant: <%= ENV['FOG_OPENSTACK_TENANT'] %>
openstack_api_key: <%= ENV['FOG_OPENSTACK_API_KEY'] %>
openstack_username: <%= ENV['FOG_OPENSTACK_USERNAME'] %>

View file

@ -513,6 +513,7 @@ ActiveRecord::Schema.define(version: 2019_07_17_151228) do
t.boolean "durees_conservation_required", default: true
t.string "path"
t.string "declarative_with_state"
t.text "monavis"
t.text "monavis_embed"
t.index ["declarative_with_state"], name: "index_procedures_on_declarative_with_state"
t.index ["hidden_at"], name: "index_procedures_on_hidden_at"

View file

@ -0,0 +1,28 @@
namespace :after_party do
desc 'Deployment task: migrate_geo_area_data'
task migrate_geo_area_data: :environment do
puts "Running deploy task 'migrate_geo_area_data'"
progress = ProgressReport.new(Champs::CarteChamp.count)
Champs::CarteChamp.includes(:geo_areas).find_each do |champ|
geo_area = champ.geo_areas.find(&:selection_utilisateur?)
geo_json = champ.geo_json_from_value
if geo_area.blank? && geo_json.present?
GeoArea.create(
champ: champ,
geometry: geo_json,
source: GeoArea.sources.fetch(:selection_utilisateur)
)
progress.inc
end
end
progress.finish
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord.create version: '20190731152733'
end
end

View file

@ -154,10 +154,10 @@ describe API::V1::DossiersController do
context 'when dossier exists and belongs to procedure' do
let(:procedure_id) { procedure.id }
let(:date_creation) { Time.zone.local(2008, 9, 1, 10, 5, 0) }
let!(:dossier) { Timecop.freeze(date_creation) { create(:dossier, :with_entreprise, :en_construction, procedure: procedure, motivation: "Motivation") } }
let!(:dossier) { Timecop.freeze(date_creation) { create(:dossier, :with_entreprise, :with_attestation, :accepte, procedure: procedure, motivation: "Motivation") } }
let(:dossier_id) { dossier.id }
let(:body) { JSON.parse(retour.body, symbolize_names: true) }
let(:field_list) { [:id, :created_at, :updated_at, :archived, :individual, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state, :simplified_state, :initiated_at, :processed_at, :received_at, :motivation, :email, :instructeurs, :justificatif_motivation, :avis] }
let(:field_list) { [:id, :created_at, :updated_at, :archived, :individual, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state, :simplified_state, :initiated_at, :processed_at, :received_at, :motivation, :email, :instructeurs, :attestation, :avis] }
subject { body[:dossier] }
it 'return REST code 200', :show_in_doc do
@ -165,7 +165,7 @@ describe API::V1::DossiersController do
end
it { expect(subject[:id]).to eq(dossier.id) }
it { expect(subject[:state]).to eq('initiated') }
it { expect(subject[:state]).to eq('closed') }
it { expect(subject[:created_at]).to eq('2008-09-01T08:05:00.000Z') }
it { expect(subject[:updated_at]).to eq('2008-09-01T08:05:00.000Z') }
it { expect(subject[:archived]).to eq(dossier.archived) }
@ -235,6 +235,22 @@ describe API::V1::DossiersController do
it { expect(subject[:type_champ]).to eq('text') }
end
end
describe 'repetition' do
let(:procedure) { create(:procedure, administrateur: admin) }
let(:champ) { build(:champ_repetition) }
let(:dossier) { create(:dossier, :en_construction, champs: [champ], procedure: procedure) }
subject { super().first[:rows] }
it 'should have rows' do
expect(subject.size).to eq(2)
expect(subject[0][:id]).to eq(1)
expect(subject[0][:champs].size).to eq(2)
expect(subject[0][:champs].map { |c| c[:value] }).to eq(['text', '42'])
expect(subject[0][:champs].map { |c| c[:type_de_champ][:type_champ] }).to eq(['text', 'number'])
end
end
end
describe 'champs_private' do

View file

@ -4,6 +4,8 @@ describe Gestionnaires::DossiersController, type: :controller do
render_views
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:administration) { create(:administration) }
let(:gestionnaires) { [gestionnaire] }
let(:procedure) { create(:procedure, :published, gestionnaires: gestionnaires) }
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
@ -153,9 +155,10 @@ describe Gestionnaires::DossiersController, type: :controller do
describe '#repasser_en_instruction' do
let(:dossier) { create(:dossier, :refuse, procedure: procedure) }
let(:current_user) { gestionnaire }
before do
sign_in gestionnaire
sign_in current_user
post :repasser_en_instruction,
params: { procedure_id: procedure.id, dossier_id: dossier.id },
format: 'js'
@ -173,6 +176,29 @@ describe Gestionnaires::DossiersController, type: :controller do
expect(response).to have_http_status(:ok)
end
end
context 'when the dossier is accepte' do
let(:dossier) { create(:dossier, :accepte, procedure: procedure) }
it 'it is not possible to go back to en_instruction as gestionnaire' do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte))
expect(response).to have_http_status(:ok)
end
context 'as administrateur' do
let (:current_user) { administrateur }
it 'it is not possible to go back to en_instruction' do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte))
expect(response).to have_http_status(:ok)
end
end
context 'as superadmin' do
let (:current_user) { administration }
it 'it is not possible to go back to en_instruction' do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:accepte))
expect(response).to have_http_status(:ok)
end
end
end
end
describe '#terminer' do

View file

@ -11,4 +11,17 @@ describe Manager::DossiersController, type: :controller do
it { expect(dossier.hidden_at).not_to be_nil }
end
describe '#repasser_en_instruction' do
let(:administration) { create :administration }
let!(:dossier) { create(:dossier, :accepte) }
before do
sign_in administration
post :repasser_en_instruction, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.en_instruction?).to be true }
end
end

View file

@ -9,5 +9,9 @@ FactoryBot.define do
nom { 'XYZ' }
commune { 'Paris' }
end
trait :selection_utilisateur do
source { GeoArea.sources.fetch(:selection_utilisateur) }
end
end
end

View file

@ -11,7 +11,9 @@ feature 'As an administrateur I can edit types de champ', js: true do
end
it "Add a new champ" do
page.accept_alert do
click_on 'Supprimer'
end
within '.buttons' do
click_on 'Ajouter un champ'
@ -48,8 +50,10 @@ feature 'As an administrateur I can edit types de champ', js: true do
expect(page).to have_selector('#champ-3-libelle')
within '.type-de-champ[data-index="2"]' do
page.accept_alert do
click_on 'Supprimer'
end
end
expect(page).not_to have_selector('#champ-3-libelle')
fill_in 'champ-2-libelle', with: 'libellé de champ 2'
@ -68,8 +72,9 @@ feature 'As an administrateur I can edit types de champ', js: true do
blur
expect(page).to have_content('Formulaire enregistré')
page.refresh
page.accept_alert do
click_on 'Supprimer'
end
expect(page).to have_content('Formulaire enregistré')
expect(page).to have_content('Supprimer', count: 1)
page.refresh

View file

@ -8,7 +8,10 @@ RSpec.describe WeeklyOverviewJob, type: :job do
context 'if the feature is enabled' do
before do
Flipflop::FeatureSet.current.test!.switch!(:weekly_overview, true)
Rails.application.config.ds_weekly_overview = true
end
after do
Rails.application.config.ds_weekly_overview = false
end
context 'with one gestionnaire with one overview' do
@ -35,7 +38,6 @@ RSpec.describe WeeklyOverviewJob, type: :job do
context 'if the feature is disabled' do
before do
Flipflop::FeatureSet.current.test!.switch!(:weekly_overview, false)
allow(Gestionnaire).to receive(:all)
WeeklyOverviewJob.new.perform
end

View file

@ -3,9 +3,9 @@ require 'spec_helper'
describe Champs::CarteChamp do
let(:champ) { Champs::CarteChamp.new(value: value) }
let(:value) { '' }
let(:geo_json) { GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates) }
let(:coordinates) { [[{ "lat" => 48.87442541960633, "lng" => 2.3859214782714844 }, { "lat" => 48.87273183590832, "lng" => 2.3850631713867183 }, { "lat" => 48.87081237174292, "lng" => 2.3809432983398438 }, { "lat" => 48.8712640169951, "lng" => 2.377510070800781 }, { "lat" => 48.87510283703279, "lng" => 2.3778533935546875 }, { "lat" => 48.87544154230615, "lng" => 2.382831573486328 }, { "lat" => 48.87442541960633, "lng" => 2.3859214782714844 }]] }
let(:parsed_geo_json) { JSON.parse(geo_json) }
let(:geo_json_as_string) { GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates) }
let(:geo_json) { JSON.parse(geo_json_as_string) }
describe '#to_render_data' do
subject { champ.to_render_data }
@ -47,15 +47,15 @@ describe Champs::CarteChamp do
context 'when the value is coordinates' do
let(:value) { coordinates.to_json }
let(:selection) { parsed_geo_json }
let(:selection) { geo_json }
it { is_expected.to eq(render_data) }
end
context 'when the value is geojson' do
let(:value) { geo_json }
let(:value) { geo_json.to_json }
let(:selection) { parsed_geo_json }
let(:selection) { geo_json }
it { is_expected.to eq(render_data) }
end
@ -89,7 +89,7 @@ describe Champs::CarteChamp do
end
context 'when the value is geojson' do
let(:value) { geo_json }
let(:value) { geo_json.to_json }
it { is_expected.to eq(1) }
end

View file

@ -0,0 +1,56 @@
require 'spec_helper'
describe ChampPolicy do
let(:user) { create(:user) }
let(:dossier) { create(:dossier, user: user) }
let!(:champ) { create(:champ_text, dossier: dossier) }
let(:pundit_user) { user }
subject { Pundit.policy_scope(pundit_user, Champ) }
context 'when the user has only user rights' do
context 'cannot access champs for other dossiers' do
let(:pundit_user) { create(:user) }
it { expect(subject.find_by(id: champ.id)).to eq(nil) }
end
context 'can access champs for its own dossiers' do
it {
expect(subject.find(champ.id)).to eq(champ)
}
end
end
context 'when the user has only gestionnaire rights' do
context 'can access champs for dossiers it follows' do
let(:dossier) { create(:dossier, :followed) }
let(:pundit_user) { dossier.followers_gestionnaires.first }
it { expect(subject.find(champ.id)).to eq(champ) }
end
end
context 'when the user has user and gestionnaire rights' do
let(:pundit_user) { dossier.followers_gestionnaires.first }
let(:dossier) { create(:dossier, :followed) }
let(:user) { create(:user, email: pundit_user.email) }
let(:dossier2) { create(:dossier, user: user) }
let!(:champ_2) { create(:champ_text, dossier: dossier2) }
context 'can access champs for dossiers it follows' do
it do
expect(pundit_user.user).to eq(user)
expect(subject.find(champ.id)).to eq(champ)
end
end
context 'can access champs for its own dossiers' do
it do
expect(pundit_user.user).to eq(user)
expect(subject.find(champ_2.id)).to eq(champ_2)
end
end
end
end

View file

@ -0,0 +1,23 @@
require 'spec_helper'
describe TypeDeChampPolicy do
let(:procedure) { create(:procedure) }
let!(:type_de_champ) { create(:type_de_champ_text, procedure: procedure) }
let(:pundit_user) { create(:user) }
subject { Pundit.policy_scope(pundit_user, TypeDeChamp) }
context 'when the user has only user rights' do
it 'can not access' do
expect(subject.find_by(id: type_de_champ.id)).to eq(nil)
end
end
context 'when the user has administrateur rights' do
let(:pundit_user) { procedure.administrateurs.first }
it 'can access' do
expect(subject.find(type_de_champ.id)).to eq(type_de_champ)
end
end
end

View file

@ -26,9 +26,9 @@ describe ChampSerializer do
context 'when type champ is carte' do
let(:champ) { create(:champ_carte, value: value, geo_areas: [geo_area].compact) }
let(:value) { nil }
let(:geo_area) { create(:geo_area, geometry: parsed_geo_json) }
let(:parsed_geo_json) { JSON.parse(geo_json) }
let(:geo_json) { GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates) }
let(:geo_area) { create(:geo_area, geometry: geo_json) }
let(:geo_json_as_string) { GeojsonService.to_json_polygon_for_selection_utilisateur(coordinates) }
let(:geo_json) { JSON.parse(geo_json_as_string) }
let(:coordinates) { [[{ "lat" => 48.87442541960633, "lng" => 2.3859214782714844 }, { "lat" => 48.87273183590832, "lng" => 2.3850631713867183 }, { "lat" => 48.87081237174292, "lng" => 2.3809432983398438 }, { "lat" => 48.8712640169951, "lng" => 2.377510070800781 }, { "lat" => 48.87510283703279, "lng" => 2.3778533935546875 }, { "lat" => 48.87544154230615, "lng" => 2.382831573486328 }, { "lat" => 48.87442541960633, "lng" => 2.3859214782714844 }]] }
let(:serialized_champ) {
@ -49,10 +49,14 @@ describe ChampSerializer do
let(:serialized_id) { -1 }
let(:serialized_description) { "" }
let(:serialized_order_place) { -1 }
let(:serialized_value) { parsed_geo_json }
let(:serialized_value) { geo_json }
context 'and geo_area is selection_utilisateur' do
let(:geo_area) { create(:geo_area, :selection_utilisateur, geometry: geo_json) }
context 'value is empty' do
let(:geo_area) { nil }
context 'when value is nil' do
let(:value) { nil }
@ -85,7 +89,7 @@ describe ChampSerializer do
end
context 'when value is geojson' do
let(:value) { geo_json }
let(:value) { geo_json.to_json }
it { expect(subject).to eq(serialized_champ) }
end
@ -105,7 +109,7 @@ describe ChampSerializer do
let(:serialized_order_place) { champ.order_place }
let(:serialized_libelle) { champ.libelle }
let(:serialized_type_champ) { champ.type_champ }
let(:serialized_value) { geo_json }
let(:serialized_value) { nil }
context 'when value is coordinates' do
let(:value) { coordinates.to_json }
@ -114,7 +118,7 @@ describe ChampSerializer do
end
context 'when value is geojson' do
let(:value) { geo_json }
let(:value) { geo_json.to_json }
it { expect(subject).to eq(serialized_champ) }
end
@ -147,7 +151,7 @@ describe ChampSerializer do
it {
expect(subject[:geo_areas].first).to include(
source: GeoArea.sources.fetch(:cadastre),
geometry: parsed_geo_json,
geometry: geo_json,
numero: '42',
feuille: 'A11'
)
@ -165,13 +169,13 @@ describe ChampSerializer do
end
context 'and geo_area is quartier_prioritaire' do
let(:geo_area) { create(:geo_area, :quartier_prioritaire, geometry: parsed_geo_json) }
let(:geo_area) { create(:geo_area, :quartier_prioritaire, geometry: geo_json) }
context 'new_api' do
it {
expect(subject[:geo_areas].first).to include(
source: GeoArea.sources.fetch(:quartier_prioritaire),
geometry: parsed_geo_json,
geometry: geo_json,
nom: 'XYZ',
commune: 'Paris'
)

View file

@ -93,6 +93,9 @@ describe ProcedureExportV2Service do
context 'with etablissement' do
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) }
let(:dossier_etablissement) { etablissements_sheet.data[1] }
let(:champ_etablissement) { etablissements_sheet.data[0] }
it 'should have headers' do
expect(etablissements_sheet.headers).to eq([
"Dossier ID",
@ -132,6 +135,8 @@ describe ProcedureExportV2Service do
it 'should have data' do
expect(etablissements_sheet.data.size).to eq(2)
expect(dossier_etablissement[1]).to eq("Dossier")
expect(champ_etablissement[1]).to eq("siret")
end
end
@ -155,8 +160,13 @@ describe ProcedureExportV2Service do
end
context 'with repetitions' do
let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) }
let(:champ_repetition) { dossier.champs.find { |champ| champ.type_champ == 'repetition' } }
let!(:dossiers) do
[
create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure),
create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure)
]
end
let(:champ_repetition) { dossiers.first.champs.find { |champ| champ.type_champ == 'repetition' } }
it 'should have sheets' do
expect(subject.sheets.map(&:name)).to eq(['Dossiers', 'Etablissements', 'Avis', champ_repetition.libelle])
@ -172,7 +182,7 @@ describe ProcedureExportV2Service do
end
it 'should have data' do
expect(repetition_sheet.data.size).to eq(2)
expect(repetition_sheet.data.size).to eq(4)
end
end
end

View file

@ -147,14 +147,6 @@ RSpec.configure do |config|
Typhoeus::Expectation.clear
ActionMailer::Base.deliveries.clear
if Flipflop.remote_storage?
VCR.use_cassette("ovh_storage_init") do
CarrierWave.configure do |config|
config.fog_credentials = { provider: 'OpenStack' }
end
end
end
}
RSpec::Matchers.define :have_same_attributes_as do |expected, options|