Merge branch 'dev'
This commit is contained in:
commit
7c315f906f
33 changed files with 264 additions and 110 deletions
7
Gemfile
7
Gemfile
|
@ -64,8 +64,6 @@ gem 'leaflet-rails'
|
||||||
gem 'leaflet-markercluster-rails', '~> 0.7.0'
|
gem 'leaflet-markercluster-rails', '~> 0.7.0'
|
||||||
gem 'leaflet-draw-rails'
|
gem 'leaflet-draw-rails'
|
||||||
|
|
||||||
gem 'bootstrap-datepicker-rails'
|
|
||||||
|
|
||||||
gem 'chartkick'
|
gem 'chartkick'
|
||||||
|
|
||||||
gem 'logstasher'
|
gem 'logstasher'
|
||||||
|
@ -76,7 +74,10 @@ gem 'hashie'
|
||||||
|
|
||||||
gem 'mailjet'
|
gem 'mailjet'
|
||||||
|
|
||||||
gem 'smart_listing'
|
# FIXME: this is a fork, go back to official version
|
||||||
|
# once https://github.com/Sology/smart_listing/pull/139
|
||||||
|
# has been merged and released
|
||||||
|
gem 'smart_listing', git: 'https://github.com/mizinsky/smart_listing.git', branch: 'kaminari-update'
|
||||||
|
|
||||||
gem 'bootstrap-wysihtml5-rails', '~> 0.3.3.8'
|
gem 'bootstrap-wysihtml5-rails', '~> 0.3.3.8'
|
||||||
|
|
||||||
|
|
21
Gemfile.lock
21
Gemfile.lock
|
@ -15,6 +15,17 @@ GIT
|
||||||
open4 (~> 1.3.4)
|
open4 (~> 1.3.4)
|
||||||
rake
|
rake
|
||||||
|
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/mizinsky/smart_listing.git
|
||||||
|
revision: bcdd4f25954fc6f4faa3d6ea6ea9a69c65da678b
|
||||||
|
branch: kaminari-update
|
||||||
|
specs:
|
||||||
|
smart_listing (1.2.1)
|
||||||
|
coffee-rails
|
||||||
|
jquery-rails
|
||||||
|
kaminari (>= 0.17)
|
||||||
|
rails (>= 3.2)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
@ -93,8 +104,6 @@ GEM
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
bindata (2.4.1)
|
bindata (2.4.1)
|
||||||
bindex (0.5.0)
|
bindex (0.5.0)
|
||||||
bootstrap-datepicker-rails (1.6.4.1)
|
|
||||||
railties (>= 3.0)
|
|
||||||
bootstrap-sass (3.3.7)
|
bootstrap-sass (3.3.7)
|
||||||
autoprefixer-rails (>= 5.2.1)
|
autoprefixer-rails (>= 5.2.1)
|
||||||
sass (>= 3.3.4)
|
sass (>= 3.3.4)
|
||||||
|
@ -668,11 +677,6 @@ GEM
|
||||||
tilt (~> 2.0)
|
tilt (~> 2.0)
|
||||||
skylight (1.5.0)
|
skylight (1.5.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
smart_listing (1.2.0)
|
|
||||||
coffee-rails
|
|
||||||
jquery-rails
|
|
||||||
kaminari (~> 0.17)
|
|
||||||
rails (>= 3.2)
|
|
||||||
spreadsheet_architect (1.4.8)
|
spreadsheet_architect (1.4.8)
|
||||||
axlsx (>= 2.0)
|
axlsx (>= 2.0)
|
||||||
rodf (= 0.3.7)
|
rodf (= 0.3.7)
|
||||||
|
@ -753,7 +757,6 @@ DEPENDENCIES
|
||||||
active_model_serializers
|
active_model_serializers
|
||||||
administrate
|
administrate
|
||||||
apipie-rails
|
apipie-rails
|
||||||
bootstrap-datepicker-rails
|
|
||||||
bootstrap-sass (~> 3.3.5)
|
bootstrap-sass (~> 3.3.5)
|
||||||
bootstrap-wysihtml5-rails (~> 0.3.3.8)
|
bootstrap-wysihtml5-rails (~> 0.3.3.8)
|
||||||
brakeman
|
brakeman
|
||||||
|
@ -823,7 +826,7 @@ DEPENDENCIES
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
simple_form
|
simple_form
|
||||||
skylight
|
skylight
|
||||||
smart_listing
|
smart_listing!
|
||||||
spreadsheet_architect (~> 1.4.8)
|
spreadsheet_architect (~> 1.4.8)
|
||||||
spring
|
spring
|
||||||
spring-commands-rspec
|
spring-commands-rspec
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
//= require chartkick
|
//= require chartkick
|
||||||
//= require_tree ./old_design
|
//= require_tree ./old_design
|
||||||
//= require bootstrap-sprockets
|
//= require bootstrap-sprockets
|
||||||
//= require bootstrap-datepicker/core
|
|
||||||
//= require bootstrap-datepicker/locales/bootstrap-datepicker.fr.js
|
|
||||||
|
|
||||||
//= require leaflet.js
|
//= require leaflet.js
|
||||||
//= require d3.min
|
//= require d3.min
|
||||||
|
|
|
@ -46,7 +46,6 @@
|
||||||
// = require attestation_recapitulatif
|
// = require attestation_recapitulatif
|
||||||
|
|
||||||
// = require_self
|
// = require_self
|
||||||
// = require bootstrap-datepicker3
|
|
||||||
// = require leaflet
|
// = require leaflet
|
||||||
// = require font-awesome
|
// = require font-awesome
|
||||||
// = require franceconnect
|
// = require franceconnect
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
@import "bootstrap";
|
@import "bootstrap";
|
||||||
@import "bootstrap-datepicker3";
|
|
||||||
|
|
||||||
#description-page #liste-champs {
|
#description-page #liste-champs {
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,16 @@ class Admin::AccompagnateursController < AdminController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
assign_scope = @procedure.gestionnaires
|
assign_scope = @procedure.gestionnaires
|
||||||
|
|
||||||
|
# FIXME: remove this comment (no code to remove) when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed.
|
||||||
|
#
|
||||||
|
# No need to permit parameters for smart_listing, because
|
||||||
|
# there are no sortable columns
|
||||||
|
#
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@accompagnateurs_assign = smart_listing_create :accompagnateurs_assign,
|
@accompagnateurs_assign = smart_listing_create :accompagnateurs_assign,
|
||||||
assign_scope,
|
assign_scope,
|
||||||
partial: "admin/accompagnateurs/list_assign",
|
partial: "admin/accompagnateurs/list_assign",
|
||||||
|
@ -14,6 +24,15 @@ class Admin::AccompagnateursController < AdminController
|
||||||
not_assign_scope = current_administrateur.gestionnaires.where.not(id: assign_scope.ids)
|
not_assign_scope = current_administrateur.gestionnaires.where.not(id: assign_scope.ids)
|
||||||
not_assign_scope = not_assign_scope.where("email LIKE ?", "%#{params[:filter]}%") if params[:filter]
|
not_assign_scope = not_assign_scope.where("email LIKE ?", "%#{params[:filter]}%") if params[:filter]
|
||||||
|
|
||||||
|
# FIXME: remove this comment (no code to remove) when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed.
|
||||||
|
#
|
||||||
|
# No need to permit parameters for smart_listing, because
|
||||||
|
# there are no sortable columns
|
||||||
|
#
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@accompagnateurs_not_assign = smart_listing_create :accompagnateurs_not_assign,
|
@accompagnateurs_not_assign = smart_listing_create :accompagnateurs_not_assign,
|
||||||
not_assign_scope,
|
not_assign_scope,
|
||||||
partial: "admin/accompagnateurs/list_not_assign",
|
partial: "admin/accompagnateurs/list_not_assign",
|
||||||
|
|
|
@ -3,6 +3,12 @@ class Admin::GestionnairesController < AdminController
|
||||||
helper SmartListing::Helper
|
helper SmartListing::Helper
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
permit_smart_listing_params
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@gestionnaires = smart_listing_create :gestionnaires,
|
@gestionnaires = smart_listing_create :gestionnaires,
|
||||||
current_administrateur.gestionnaires,
|
current_administrateur.gestionnaires,
|
||||||
partial: "admin/gestionnaires/list",
|
partial: "admin/gestionnaires/list",
|
||||||
|
|
|
@ -5,6 +5,12 @@ class Admin::ProceduresController < AdminController
|
||||||
before_action :retrieve_procedure, only: [:show, :edit]
|
before_action :retrieve_procedure, only: [:show, :edit]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
permit_smart_listing_params
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@procedures = smart_listing_create :procedures,
|
@procedures = smart_listing_create :procedures,
|
||||||
current_administrateur.procedures.publiees.order(published_at: :desc),
|
current_administrateur.procedures.publiees.order(published_at: :desc),
|
||||||
partial: "admin/procedures/list",
|
partial: "admin/procedures/list",
|
||||||
|
@ -14,6 +20,12 @@ class Admin::ProceduresController < AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def archived
|
def archived
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
permit_smart_listing_params
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@procedures = smart_listing_create :procedures,
|
@procedures = smart_listing_create :procedures,
|
||||||
current_administrateur.procedures.archivees.order(published_at: :desc),
|
current_administrateur.procedures.archivees.order(published_at: :desc),
|
||||||
partial: "admin/procedures/list",
|
partial: "admin/procedures/list",
|
||||||
|
@ -25,6 +37,12 @@ class Admin::ProceduresController < AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def draft
|
def draft
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
permit_smart_listing_params
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@procedures = smart_listing_create :procedures,
|
@procedures = smart_listing_create :procedures,
|
||||||
current_administrateur.procedures.brouillons.order(created_at: :desc),
|
current_administrateur.procedures.brouillons.order(created_at: :desc),
|
||||||
partial: "admin/procedures/list",
|
partial: "admin/procedures/list",
|
||||||
|
|
|
@ -109,4 +109,35 @@ class ApplicationController < ActionController::Base
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def permit_smart_listing_params
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
self.params = params.permit(
|
||||||
|
# Dossiers
|
||||||
|
:liste,
|
||||||
|
dossiers_smart_listing:
|
||||||
|
[
|
||||||
|
:page,
|
||||||
|
:per_page,
|
||||||
|
{ sort: [:id, :'procedure.libelle', :state, :updated_at] }
|
||||||
|
],
|
||||||
|
# Gestionnaires
|
||||||
|
gestionnaires_smart_listing:
|
||||||
|
[
|
||||||
|
:page,
|
||||||
|
:per_page,
|
||||||
|
{ sort: [:email] }
|
||||||
|
],
|
||||||
|
# Procédures
|
||||||
|
procedures_smart_listing:
|
||||||
|
[
|
||||||
|
:page,
|
||||||
|
:per_page,
|
||||||
|
{ sort: [:id, :libelle, :published_at] }
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# END OF FIXME
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,12 @@ class Users::DossiersController < UsersController
|
||||||
return redirect_to users_dossiers_path
|
return redirect_to users_dossiers_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# FIXME: remove when
|
||||||
|
# https://github.com/Sology/smart_listing/issues/134
|
||||||
|
# is fixed
|
||||||
|
permit_smart_listing_params
|
||||||
|
# END OF FIXME
|
||||||
|
|
||||||
@dossiers = smart_listing_create :dossiers,
|
@dossiers = smart_listing_create :dossiers,
|
||||||
@dossiers_filtered,
|
@dossiers_filtered,
|
||||||
partial: "users/dossiers/list",
|
partial: "users/dossiers/list",
|
||||||
|
|
|
@ -19,6 +19,16 @@ class ChampDecorator < Draper::Decorator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def date_for_input
|
||||||
|
if object.value.present?
|
||||||
|
if type_champ == "date"
|
||||||
|
object.value
|
||||||
|
elsif type_champ == "datetime" && object.value != ' 00:00'
|
||||||
|
DateTime.parse(object.value, "%Y-%m-%d %H:%M").strftime("%Y-%m-%d")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def description_with_links
|
def description_with_links
|
||||||
description.gsub(URI.regexp, '<a target="_blank" href="\0">\0</a>') if description
|
description.gsub(URI.regexp, '<a target="_blank" href="\0">\0</a>') if description
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,13 +32,9 @@ class SIADE::EtablissementAdapter
|
||||||
end
|
end
|
||||||
|
|
||||||
def adresse
|
def adresse
|
||||||
adresse = ''
|
[:l1, :l2, :l3, :l4, :l5, :l6, :l7].map do |line|
|
||||||
[:l1, :l2, :l3, :l4, :l5, :l6, :l7].each do |line|
|
data_source[:etablissement][:adresse][line]
|
||||||
if data_source[:etablissement][:adresse][line].present?
|
end.compact.join("\r\n")
|
||||||
adresse = adresse + data_source[:etablissement][:adresse][line] + "\r\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
adresse
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def address_attribut_to_fetch
|
def address_attribut_to_fetch
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Champ < ActiveRecord::Base
|
||||||
delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ
|
delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ
|
||||||
|
|
||||||
before_save :format_date_to_iso, if: Proc.new { type_champ == 'date' }
|
before_save :format_date_to_iso, if: Proc.new { type_champ == 'date' }
|
||||||
before_save :serialize_datetime_if_needed, if: Proc.new { type_champ == 'datetime' }
|
before_save :format_datetime, if: Proc.new { type_champ == 'datetime' }
|
||||||
before_save :multiple_select_to_string, if: Proc.new { type_champ == 'multiple_drop_down_list' }
|
before_save :multiple_select_to_string, if: Proc.new { type_champ == 'multiple_drop_down_list' }
|
||||||
|
|
||||||
after_save :internal_notification, if: Proc.new { dossier.present? }
|
after_save :internal_notification, if: Proc.new { dossier.present? }
|
||||||
|
@ -17,15 +17,6 @@ class Champ < ActiveRecord::Base
|
||||||
mandatory
|
mandatory
|
||||||
end
|
end
|
||||||
|
|
||||||
def data_provide
|
|
||||||
return 'datepicker' if (type_champ == 'datetime') && !(BROWSER.value.chrome? || BROWSER.value.edge?)
|
|
||||||
return 'typeahead' if type_champ == 'address'
|
|
||||||
end
|
|
||||||
|
|
||||||
def data_date_format
|
|
||||||
('dd/mm/yyyy' if type_champ == 'datetime')
|
|
||||||
end
|
|
||||||
|
|
||||||
def same_hour? num
|
def same_hour? num
|
||||||
same_date? num, '%H'
|
same_date? num, '%H'
|
||||||
end
|
end
|
||||||
|
@ -102,7 +93,7 @@ class Champ < ActiveRecord::Base
|
||||||
self.value = date
|
self.value = date
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_datetime_if_needed
|
def format_datetime
|
||||||
if (value =~ /=>/).present?
|
if (value =~ /=>/).present?
|
||||||
date = begin
|
date = begin
|
||||||
hash_date = YAML.safe_load(value.gsub('=>', ': '))
|
hash_date = YAML.safe_load(value.gsub('=>', ': '))
|
||||||
|
@ -111,8 +102,11 @@ class Champ < ActiveRecord::Base
|
||||||
rescue
|
rescue
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
self.value = date
|
self.value = date
|
||||||
|
elsif /^\d{2}\/\d{2}\/\d{4}\s\d{2}:\d{2}$/ =~ value # old browsers can send with dd/mm/yyyy hh:mm format
|
||||||
|
self.value = DateTime.parse(value, "%d/%m/%Y %H:%M").strftime("%Y-%m-%d %H:%M")
|
||||||
|
elsif !(/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}$/ =~ value) # a datetime not correctly formatted should not be stored
|
||||||
|
self.value = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Commentaire < ActiveRecord::Base
|
||||||
after_create :notify
|
after_create :notify
|
||||||
|
|
||||||
def header
|
def header
|
||||||
"#{email}, " + I18n.l(created_at.localtime, format: '%d %b %Y %H:%M')
|
"#{email}, #{I18n.l(created_at.localtime, format: '%d %b %Y %H:%M')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def file_url
|
def file_url
|
||||||
|
|
|
@ -154,12 +154,9 @@ class Dossier < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_specific_hash_values_to_string(hash_to_convert)
|
def convert_specific_hash_values_to_string(hash_to_convert)
|
||||||
hash = {}
|
hash_to_convert.transform_values do |value|
|
||||||
hash_to_convert.each do |key, value|
|
serialize_value_for_export(value)
|
||||||
value = serialize_value_for_export(value)
|
|
||||||
hash.store(key, value)
|
|
||||||
end
|
end
|
||||||
hash
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_data_strings_array
|
def full_data_strings_array
|
||||||
|
@ -170,11 +167,11 @@ class Dossier < ActiveRecord::Base
|
||||||
|
|
||||||
def export_entreprise_data
|
def export_entreprise_data
|
||||||
if entreprise.present?
|
if entreprise.present?
|
||||||
etablissement_attr = EtablissementCsvSerializer.new(self.etablissement).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h
|
etablissement_attr = EtablissementCsvSerializer.new(self.etablissement).attributes.transform_keys { |k| "etablissement.#{k}".parameterize.underscore.to_sym }
|
||||||
entreprise_attr = EntrepriseSerializer.new(self.entreprise).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h
|
entreprise_attr = EntrepriseSerializer.new(self.entreprise).attributes.transform_keys { |k| "entreprise.#{k}".parameterize.underscore.to_sym }
|
||||||
else
|
else
|
||||||
etablissement_attr = EtablissementSerializer.new(Etablissement.new).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h
|
etablissement_attr = EtablissementSerializer.new(Etablissement.new).attributes.transform_keys { |k| "etablissement.#{k}".parameterize.underscore.to_sym }
|
||||||
entreprise_attr = EntrepriseSerializer.new(Entreprise.new).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h
|
entreprise_attr = EntrepriseSerializer.new(Entreprise.new).attributes.transform_keys { |k| "entreprise.#{k}".parameterize.underscore.to_sym }
|
||||||
end
|
end
|
||||||
convert_specific_hash_values_to_string(etablissement_attr.merge(entreprise_attr))
|
convert_specific_hash_values_to_string(etablissement_attr.merge(entreprise_attr))
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,8 +55,8 @@ class Gestionnaire < ActiveRecord::Base
|
||||||
procedure_ids = followed_dossiers.pluck(:procedure_id)
|
procedure_ids = followed_dossiers.pluck(:procedure_id)
|
||||||
|
|
||||||
if procedure_ids.include?(procedure.id)
|
if procedure_ids.include?(procedure.id)
|
||||||
return followed_dossiers.where(procedure_id: procedure.id).inject(0) do |acc, dossier|
|
return followed_dossiers.where(procedure_id: procedure.id).sum do |dossier|
|
||||||
acc += dossier.notifications.where(already_read: false).count
|
dossier.notifications.where(already_read: false).count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
0
|
0
|
||||||
|
|
|
@ -23,11 +23,11 @@ class RenderPartialService
|
||||||
private
|
private
|
||||||
|
|
||||||
def retrieve_navbar
|
def retrieve_navbar
|
||||||
'layouts/navbars/navbar_' + retrieve_name
|
"layouts/navbars/navbar_#{retrieve_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve_left_panel
|
def retrieve_left_panel
|
||||||
'layouts/left_panels/left_panel_' + retrieve_name
|
"layouts/left_panels/left_panel_#{retrieve_name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve_name
|
def retrieve_name
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%input.form-control{ name: "champs['#{champ.id}']",
|
%input.form-control{ name: "champs['#{champ.id}']",
|
||||||
placeholder: "JJ/MM/AAAA",
|
placeholder: "JJ/MM/AAAA",
|
||||||
id: "champs_#{champ.id}",
|
id: "champs_#{champ.id}",
|
||||||
value: champ.value ? champ.object.value : champ.value,
|
value: champ.date_for_input,
|
||||||
type: "date" }
|
type: "date" }
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
%input.form-control{ name: "champs['#{champ.id}']",
|
= render partial: 'users/description/champs/date', locals: { champ: champ }
|
||||||
placeholder: champ.libelle,
|
|
||||||
id: "champs_#{champ.id}",
|
|
||||||
value: (champ.value.split(/[ ][0-9]*:[0-9]*/).first if champ.value.present?),
|
|
||||||
type: champ.type_champ,
|
|
||||||
'data-provide' => champ.data_provide,
|
|
||||||
'data-date-format' => champ.data_date_format }
|
|
||||||
|
|
||||||
%select.form-control{ name: "time_hour['#{champ.id}']", style: 'margin-left: 5px;', id: "time_hour_#{champ.id}" }
|
%br
|
||||||
|
%select.form-control{ name: "time_hour['#{champ.id}']", style: 'width:70px;display:inline;', id: "time_hour_#{champ.id}" }
|
||||||
- (0..23).each do |num|
|
- (0..23).each do |num|
|
||||||
- num = "%.2i" %num
|
- num = "%.2i" %num
|
||||||
%option{ value: num, selected: (:selected if champ.same_hour?(num)) }
|
%option{ value: num, selected: (:selected if champ.same_hour?(num)) }
|
||||||
= num
|
= num
|
||||||
h
|
h
|
||||||
%select.form-control{ name: "time_minute['#{champ.id}']", id: "time_minute_#{champ.id}" }
|
%select.form-control{ name: "time_minute['#{champ.id}']", style: 'width:70px;display:inline;', id: "time_minute_#{champ.id}" }
|
||||||
- (0..59).each do |num|
|
- (0..59).each do |num|
|
||||||
- num = "%.2i" %num
|
- num = "%.2i" %num
|
||||||
- if num.to_i%5 == 0
|
- if num.to_i%5 == 0
|
||||||
|
|
30
lib/tasks/2018_01_18_clean_datetime_in_champs.rake
Normal file
30
lib/tasks/2018_01_18_clean_datetime_in_champs.rake
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
namespace :'2018_01_18_clean_datetime_in_champs' do
|
||||||
|
task clean: :environment do
|
||||||
|
datetime_champs = TypeDeChamp.where(type_champ: "datetime").flat_map{ |t| t.champ }
|
||||||
|
|
||||||
|
# Match " HH:MM" => nil a datetime is not valid if not composed by date AND time
|
||||||
|
datetime_champs.select { |c| /^\s\d{2}:\d{2}$/.match(c.value) }.each do |c|
|
||||||
|
puts "cleaning #{c.value} => nil"
|
||||||
|
c.update_columns(value: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Match "dd/mm/YYYY HH:MM" => "YYYY-mm-dd HH:MM"
|
||||||
|
datetime_champs.select { |c| /^\d{2}\/\d{2}\/\d{4}\s\d{2}:\d{2}$/ =~ c.value }.each do |c|
|
||||||
|
formated_date = DateTime.parse(c.value, "%d/%m/%Y %H:%M").strftime("%Y-%m-%d %H:%M")
|
||||||
|
puts "cleaning #{c.value} => #{formated_date}"
|
||||||
|
c.update_columns(value: formated_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Match "ddmmYYYY HH:MM" => "YYYY-mm-dd HH:MM"
|
||||||
|
datetime_champs.select { |c| /^\d{8}\s\d{2}:\d{2}$/ =~ c.value }.each do |c|
|
||||||
|
day = c.value[0,2]
|
||||||
|
month = c.value[2,2]
|
||||||
|
year = c.value[4,4]
|
||||||
|
hours = c.value[9,2]
|
||||||
|
minutes = c.value[12,2]
|
||||||
|
formated_date = "#{year}-#{month}-#{day} #{hours}:#{minutes}"
|
||||||
|
puts "cleaning #{c.value} => #{formated_date}"
|
||||||
|
c.update_columns(value: formated_date)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,17 @@ describe Admin::GestionnairesController, type: :controller do
|
||||||
it { expect(subject.status).to eq(200) }
|
it { expect(subject.status).to eq(200) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET #index with sorting and pagination' do
|
||||||
|
subject {
|
||||||
|
get :index,
|
||||||
|
'gestionnaires_smart_listing[page]': 1,
|
||||||
|
'gestionnaires_smart_listing[per_page]': 10,
|
||||||
|
'gestionnaires_smart_listing[sort][email]': 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
it { expect(subject.status).to eq(200) }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
let(:email) { 'test@plop.com' }
|
let(:email) { 'test@plop.com' }
|
||||||
let(:procedure_id) { nil }
|
let(:procedure_id) { nil }
|
||||||
|
|
|
@ -42,18 +42,51 @@ describe Admin::ProceduresController, type: :controller do
|
||||||
it { expect(response.status).to eq(200) }
|
it { expect(response.status).to eq(200) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET #index with sorting and pagination' do
|
||||||
|
subject {
|
||||||
|
get :index,
|
||||||
|
'procedures_smart_listing[page]': 1,
|
||||||
|
'procedures_smart_listing[per_page]': 10,
|
||||||
|
'procedures_smart_listing[sort][id]': 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
it { expect(subject.status).to eq(200) }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #archived' do
|
describe 'GET #archived' do
|
||||||
subject { get :archived }
|
subject { get :archived }
|
||||||
|
|
||||||
it { expect(response.status).to eq(200) }
|
it { expect(response.status).to eq(200) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET #archived with sorting and pagination' do
|
||||||
|
subject {
|
||||||
|
get :archived,
|
||||||
|
'procedures_smart_listing[page]': 1,
|
||||||
|
'procedures_smart_listing[per_page]': 10,
|
||||||
|
'procedures_smart_listing[sort][libelle]': 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
it { expect(subject.status).to eq(200) }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #published' do
|
describe 'GET #published' do
|
||||||
subject { get :published }
|
subject { get :published }
|
||||||
|
|
||||||
it { expect(response.status).to eq(200) }
|
it { expect(response.status).to eq(200) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET #draft with sorting and pagination' do
|
||||||
|
subject {
|
||||||
|
get :draft,
|
||||||
|
'procedures_smart_listing[page]': 1,
|
||||||
|
'procedures_smart_listing[per_page]': 10,
|
||||||
|
'procedures_smart_listing[sort][published_at]': 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
it { expect(subject.status).to eq(200) }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'DELETE #destroy' do
|
describe 'DELETE #destroy' do
|
||||||
let(:procedure_draft) { create :procedure, administrateur: admin, published_at: nil, archived_at: nil }
|
let(:procedure_draft) { create :procedure, administrateur: admin, published_at: nil, archived_at: nil }
|
||||||
let(:procedure_published) { create :procedure, administrateur: admin, published_at: Time.now, archived_at: nil }
|
let(:procedure_published) { create :procedure, administrateur: admin, published_at: Time.now, archived_at: nil }
|
||||||
|
|
|
@ -249,7 +249,7 @@ shared_examples 'description_controller_spec' do
|
||||||
describe 'Sauvegarde des champs' do
|
describe 'Sauvegarde des champs' do
|
||||||
let(:champs_dossier) { dossier.champs }
|
let(:champs_dossier) { dossier.champs }
|
||||||
let(:dossier_text_value) { 'test value' }
|
let(:dossier_text_value) { 'test value' }
|
||||||
let(:dossier_date_value) { '23/06/2016' }
|
let(:dossier_date_value) { '2018-01-31' }
|
||||||
let(:dossier_hour_value) { '17' }
|
let(:dossier_hour_value) { '17' }
|
||||||
let(:dossier_minute_value) { '00' }
|
let(:dossier_minute_value) { '00' }
|
||||||
let(:dossier_datetime_champ_id) { dossier.champs.find { |c| c.type_champ == "datetime" }.id }
|
let(:dossier_datetime_champ_id) { dossier.champs.find { |c| c.type_champ == "datetime" }.id }
|
||||||
|
@ -302,8 +302,8 @@ shared_examples 'description_controller_spec' do
|
||||||
before do
|
before do
|
||||||
post :update, params: {
|
post :update, params: {
|
||||||
dossier_id: dossier_id,
|
dossier_id: dossier_id,
|
||||||
'piece_justificative_' + all_pj_type[0].to_s => piece_justificative_0,
|
"piece_justificative_#{all_pj_type[0].to_s}" => piece_justificative_0,
|
||||||
'piece_justificative_' + all_pj_type[1].to_s => piece_justificative_1
|
"piece_justificative_#{all_pj_type[1].to_s}" => piece_justificative_1
|
||||||
}
|
}
|
||||||
dossier.reload
|
dossier.reload
|
||||||
end
|
end
|
||||||
|
@ -314,8 +314,8 @@ shared_examples 'description_controller_spec' do
|
||||||
|
|
||||||
post :update, params: {
|
post :update, params: {
|
||||||
dossier_id: dossier_id,
|
dossier_id: dossier_id,
|
||||||
'piece_justificative_' + all_pj_type[0].to_s => piece_justificative_0,
|
"piece_justificative_#{all_pj_type[0].to_s}" => piece_justificative_0,
|
||||||
'piece_justificative_' + all_pj_type[1].to_s => piece_justificative_1
|
"piece_justificative_#{all_pj_type[1].to_s}" => piece_justificative_1
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -355,8 +355,8 @@ shared_examples 'description_controller_spec' do
|
||||||
subject {
|
subject {
|
||||||
patch :pieces_justificatives, params: {
|
patch :pieces_justificatives, params: {
|
||||||
dossier_id: dossier.id,
|
dossier_id: dossier.id,
|
||||||
'piece_justificative_' + all_pj_type[0].to_s => piece_justificative_0,
|
"piece_justificative_#{all_pj_type[0].to_s}" => piece_justificative_0,
|
||||||
'piece_justificative_' + all_pj_type[1].to_s => piece_justificative_1
|
"piece_justificative_#{all_pj_type[1].to_s}" => piece_justificative_1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,8 +434,8 @@ shared_examples 'description_controller_spec_POST_piece_justificatives_for_owner
|
||||||
subject {
|
subject {
|
||||||
patch :pieces_justificatives, params: {
|
patch :pieces_justificatives, params: {
|
||||||
dossier_id: dossier.id,
|
dossier_id: dossier.id,
|
||||||
'piece_justificative_' + all_pj_type[0].to_s => piece_justificative_0,
|
"piece_justificative_#{all_pj_type[0].to_s}" => piece_justificative_0,
|
||||||
'piece_justificative_' + all_pj_type[1].to_s => piece_justificative_1
|
"piece_justificative_#{all_pj_type[1].to_s}" => piece_justificative_1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,4 +61,36 @@ describe ChampDecorator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#date_for_input' do
|
||||||
|
subject { decorator.date_for_input }
|
||||||
|
|
||||||
|
describe "for a date" do
|
||||||
|
let(:type_champ) { :date }
|
||||||
|
|
||||||
|
context "when value is an ISO date" do
|
||||||
|
before { champ.update value: "2017-12-31" }
|
||||||
|
it { is_expected.to eq "2017-12-31" }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when value is empty" do
|
||||||
|
before { champ.update value: nil }
|
||||||
|
it { is_expected.to eq nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "for a datetime" do
|
||||||
|
let(:type_champ) { :date }
|
||||||
|
|
||||||
|
context "when value is an formatted datetime" do
|
||||||
|
before { champ.update value: "2017-12-30 23:17" }
|
||||||
|
it { is_expected.to eq "2017-12-30" }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when value is empty" do
|
||||||
|
before { champ.update value: nil }
|
||||||
|
it { is_expected.to eq nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,7 +44,7 @@ feature 'user is on description page' do
|
||||||
end
|
end
|
||||||
context 'when he adds a piece_justificative and submit form', vcr: { cassette_name: 'description_page_upload_piece_justificative_adds_cerfa_and_submit' } do
|
context 'when he adds a piece_justificative and submit form', vcr: { cassette_name: 'description_page_upload_piece_justificative_adds_cerfa_and_submit' } do
|
||||||
before do
|
before do
|
||||||
file_input_id = 'piece_justificative_' + dossier.types_de_piece_justificative.first.id.to_s
|
file_input_id = "piece_justificative_#{dossier.types_de_piece_justificative.first.id.to_s}"
|
||||||
attach_file(file_input_id, File.path('spec/support/files/dossierPDF.pdf'))
|
attach_file(file_input_id, File.path('spec/support/files/dossierPDF.pdf'))
|
||||||
click_on('Soumettre mon dossier')
|
click_on('Soumettre mon dossier')
|
||||||
dossier.reload
|
dossier.reload
|
||||||
|
|
|
@ -21,7 +21,7 @@ feature 'As a User I want to edit a dossier I own' do
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'Getting a dossier, I want to create a new message on', js: true do
|
scenario 'Getting a dossier, I want to create a new message on', js: true do
|
||||||
page.find_by_id('tr_dossier_' + dossier.id.to_s).click
|
page.find_by_id("tr_dossier_#{dossier.id.to_s}").click
|
||||||
expect(page).to have_current_path(users_dossier_recapitulatif_path(Dossier.first.id.to_s))
|
expect(page).to have_current_path(users_dossier_recapitulatif_path(Dossier.first.id.to_s))
|
||||||
page.find_by_id('open-message').click
|
page.find_by_id('open-message').click
|
||||||
page.execute_script("$('#texte_commentaire').data('wysihtml5').editor.setValue('Contenu du nouveau message')")
|
page.execute_script("$('#texte_commentaire').data('wysihtml5').editor.setValue('Contenu du nouveau message')")
|
||||||
|
@ -30,7 +30,7 @@ feature 'As a User I want to edit a dossier I own' do
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'On the same dossier, I want to edit informations', js: true do
|
scenario 'On the same dossier, I want to edit informations', js: true do
|
||||||
page.find_by_id('tr_dossier_' + dossier.id.to_s).click
|
page.find_by_id("tr_dossier_#{dossier.id.to_s}").click
|
||||||
expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier.id.to_s))
|
expect(page).to have_current_path(users_dossier_recapitulatif_path(dossier.id.to_s))
|
||||||
|
|
||||||
# Linked Dossier
|
# Linked Dossier
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe SIADE::EtablissementAdapter do
|
||||||
|
|
||||||
context 'Concaténation lignes adresse' do
|
context 'Concaténation lignes adresse' do
|
||||||
it 'L\'entreprise contient bien une adresse sur plusieurs lignes' do
|
it 'L\'entreprise contient bien une adresse sur plusieurs lignes' do
|
||||||
expect(subject[:adresse]).to eq("OCTO TECHNOLOGY\r\n50 AVENUE DES CHAMPS ELYSEES\r\n75008 PARIS\r\nFRANCE\r\n")
|
expect(subject[:adresse]).to eq("OCTO TECHNOLOGY\r\n50 AVENUE DES CHAMPS ELYSEES\r\n75008 PARIS\r\nFRANCE")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,28 +26,6 @@ shared_examples 'champ_spec' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'data_provide' do
|
|
||||||
let(:champ) { create :champ }
|
|
||||||
|
|
||||||
subject { champ.data_provide }
|
|
||||||
|
|
||||||
context 'when type_champ is datetime' do
|
|
||||||
before do
|
|
||||||
champ.type_de_champ = create :type_de_champ_public, type_champ: 'datetime'
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to eq 'datepicker' }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when type_champ is address' do
|
|
||||||
before do
|
|
||||||
champ.type_de_champ = create :type_de_champ_public, type_champ: 'address'
|
|
||||||
end
|
|
||||||
|
|
||||||
it { is_expected.to eq 'typeahead' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.departement', vcr: { cassette_name: 'call_geo_api_departements' } do
|
describe '.departement', vcr: { cassette_name: 'call_geo_api_departements' } do
|
||||||
subject { Champ.departements }
|
subject { Champ.departements }
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,22 @@ describe Champ do
|
||||||
|
|
||||||
it_should_behave_like "champ_spec"
|
it_should_behave_like "champ_spec"
|
||||||
|
|
||||||
describe '#serialize_datetime_if_needed' do
|
describe '#format_datetime' do
|
||||||
let(:type_de_champ) { TypeDeChamp.new(type_champ: 'datetime') }
|
let(:type_de_champ) { TypeDeChamp.new(type_champ: 'datetime') }
|
||||||
let(:champ) { Champ.new(type_de_champ: type_de_champ, value: value) }
|
let(:champ) { Champ.new(type_de_champ: type_de_champ, value: value) }
|
||||||
|
|
||||||
before { champ.save }
|
before { champ.save }
|
||||||
|
|
||||||
# when using the old form, and the ChampsService Class
|
context 'when the value is sent by a modern browser' do
|
||||||
# TODO: to remove
|
let(:value) { '2017-12-31 10:23' }
|
||||||
context 'when the value is already serialized' do
|
|
||||||
let(:value) { '12/01/2017 10:23' }
|
|
||||||
|
|
||||||
it { expect(champ.value).to eq(value) }
|
it { expect(champ.value).to eq(value) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the value is not already serialized' do
|
context 'when the value is sent by a old browser' do
|
||||||
let(:value) { '{ 1=>2017, 2=>01, 3=>12, 4=>10, 5=>23 }' }
|
let(:value) { '31/12/2018 09:26' }
|
||||||
|
|
||||||
it { expect(champ.value).to eq('12/01/2017 10:23') }
|
it { expect(champ.value).to eq('2018-12-31 09:26') }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -179,10 +179,10 @@ describe TagsSubstitutionConcern, type: :model do
|
||||||
dossier.champs
|
dossier.champs
|
||||||
.select { |champ| champ.type_champ == 'datetime' }
|
.select { |champ| champ.type_champ == 'datetime' }
|
||||||
.first
|
.first
|
||||||
.update_attributes(value: '13/09/2017 09:00')
|
.update_attributes(value: '2017-09-13 09:00')
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq('15/04/2017 13/09/2017 09:00') }
|
it { is_expected.to eq('15/04/2017 2017-09-13 09:00') }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,7 +95,7 @@ describe Gestionnaire, type: :model do
|
||||||
it { is_expected.to eq 0 }
|
it { is_expected.to eq 0 }
|
||||||
it { expect(gestionnaire.follows.count).to eq 0 }
|
it { expect(gestionnaire.follows.count).to eq 0 }
|
||||||
it do
|
it do
|
||||||
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:inject)
|
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:sum)
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -108,7 +108,7 @@ describe Gestionnaire, type: :model do
|
||||||
it { is_expected.to eq 0 }
|
it { is_expected.to eq 0 }
|
||||||
it { expect(gestionnaire.follows.count).to eq 1 }
|
it { expect(gestionnaire.follows.count).to eq 1 }
|
||||||
it do
|
it do
|
||||||
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:inject)
|
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).not_to receive(:sum)
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -124,7 +124,7 @@ describe Gestionnaire, type: :model do
|
||||||
it { is_expected.to eq 1 }
|
it { is_expected.to eq 1 }
|
||||||
it { expect(gestionnaire.follows.count).to eq 1 }
|
it { expect(gestionnaire.follows.count).to eq 1 }
|
||||||
it do
|
it do
|
||||||
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).to receive(:inject)
|
expect_any_instance_of(Dossier::ActiveRecord_AssociationRelation).to receive(:sum)
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe ChampsService do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses and save the date' do
|
it 'parses and save the date' do
|
||||||
expect(champ_datetime.value).to eq('d 12:24')
|
expect(champ_datetime.value).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe 'users/description/show.html.haml', type: :view do
|
||||||
let(:champ_datetime) { champs.where(type_de_champ_id: types_de_champ.id).first }
|
let(:champ_datetime) { champs.where(type_de_champ_id: types_de_champ.id).first }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
champ_datetime.value = "22/06/2016 12:05"
|
champ_datetime.value = "2016-06-22 12:05"
|
||||||
champ_datetime.save
|
champ_datetime.save
|
||||||
render
|
render
|
||||||
end
|
end
|
||||||
|
@ -82,7 +82,7 @@ describe 'users/description/show.html.haml', type: :view do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'datetime value is correctly setup when is not nil' do
|
describe 'datetime value is correctly setup when is not nil' do
|
||||||
it { expect(rendered).to have_css("input[type='datetime'][id='champs_#{champ_datetime.id}'][value='22/06/2016']") }
|
it { expect(rendered).to have_css("input[type='date'][id='champs_#{champ_datetime.id}'][value='2016-06-22']") }
|
||||||
it { expect(rendered).to have_css("option[value='12'][selected='selected']") }
|
it { expect(rendered).to have_css("option[value='12'][selected='selected']") }
|
||||||
it { expect(rendered).to have_css("option[value='05'][selected='selected']") }
|
it { expect(rendered).to have_css("option[value='05'][selected='selected']") }
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue