Merge pull request #9237 from demarches-simplifiees/cleaning-after-new-routing-logic
Nettoyage du code de l'ancien système de routage
This commit is contained in:
commit
36a78dfd2d
19 changed files with 26 additions and 153 deletions
|
@ -282,15 +282,6 @@ module Administrateurs
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_routing_criteria_name
|
|
||||||
if procedure.update(routing_criteria_name: routing_criteria_name)
|
|
||||||
flash[:notice] = "Le libellé est maintenant « #{procedure.routing_criteria_name} »."
|
|
||||||
else
|
|
||||||
flash[:alert] = "Le libellé du routage doit être rempli."
|
|
||||||
end
|
|
||||||
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_instructeurs_self_management_enabled
|
def update_instructeurs_self_management_enabled
|
||||||
procedure.update!(instructeurs_self_management_enabled_params)
|
procedure.update!(instructeurs_self_management_enabled_params)
|
||||||
|
|
||||||
|
@ -415,10 +406,6 @@ module Administrateurs
|
||||||
.order(:email)
|
.order(:email)
|
||||||
end
|
end
|
||||||
|
|
||||||
def routing_criteria_name
|
|
||||||
params[:procedure][:routing_criteria_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def available_instructeur_emails
|
def available_instructeur_emails
|
||||||
all = current_administrateur.instructeurs.map(&:email)
|
all = current_administrateur.instructeurs.map(&:email)
|
||||||
assigned = groupe_instructeur.instructeurs.map(&:email)
|
assigned = groupe_instructeur.instructeurs.map(&:email)
|
||||||
|
|
|
@ -487,32 +487,6 @@ module Users
|
||||||
redirect_to dossier_path(dossier)
|
redirect_to dossier_path(dossier)
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_change_groupe_instructeur?
|
|
||||||
if params[:dossier].key?(:groupe_instructeur_id)
|
|
||||||
groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
|
|
||||||
if groupe_instructeur_id.nil?
|
|
||||||
@dossier.groupe_instructeur_id.present?
|
|
||||||
else
|
|
||||||
@dossier.groupe_instructeur_id != groupe_instructeur_id.to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def groupe_instructeur_from_params
|
|
||||||
groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
|
|
||||||
if groupe_instructeur_id.present?
|
|
||||||
@dossier.procedure.groupe_instructeurs.find(groupe_instructeur_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def should_fill_groupe_instructeur?
|
|
||||||
!@dossier.procedure.routing_enabled? && @dossier.groupe_instructeur_id.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def defaut_groupe_instructeur
|
|
||||||
@dossier.procedure.defaut_groupe_instructeur
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_dossier_and_compute_errors
|
def update_dossier_and_compute_errors
|
||||||
errors = []
|
errors = []
|
||||||
@dossier.assign_attributes(champs_public_params)
|
@dossier.assign_attributes(champs_public_params)
|
||||||
|
@ -523,10 +497,6 @@ module Users
|
||||||
errors += format_errors(errors: @dossier.errors)
|
errors += format_errors(errors: @dossier.errors)
|
||||||
end
|
end
|
||||||
|
|
||||||
if should_change_groupe_instructeur?
|
|
||||||
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
errors
|
errors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -537,10 +507,6 @@ module Users
|
||||||
errors += format_errors(errors: @dossier.errors)
|
errors += format_errors(errors: @dossier.errors)
|
||||||
errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs)
|
errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs)
|
||||||
|
|
||||||
if should_fill_groupe_instructeur?
|
|
||||||
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
|
|
||||||
end
|
|
||||||
|
|
||||||
RoutingEngine.compute(@dossier)
|
RoutingEngine.compute(@dossier)
|
||||||
|
|
||||||
errors
|
errors
|
||||||
|
|
|
@ -56,6 +56,8 @@ class Dossier < ApplicationRecord
|
||||||
include DossierSearchableConcern
|
include DossierSearchableConcern
|
||||||
include DossierSectionsConcern
|
include DossierSectionsConcern
|
||||||
|
|
||||||
|
self.ignored_columns += [:migrated_champ_routage]
|
||||||
|
|
||||||
enum state: {
|
enum state: {
|
||||||
brouillon: 'brouillon',
|
brouillon: 'brouillon',
|
||||||
en_construction: 'en_construction',
|
en_construction: 'en_construction',
|
||||||
|
@ -664,14 +666,6 @@ class Dossier < ApplicationRecord
|
||||||
procedure.discarded? || (brouillon? && !procedure.dossier_can_transition_to_en_construction?)
|
procedure.discarded? || (brouillon? && !procedure.dossier_can_transition_to_en_construction?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_groupe_instructeur_details?
|
|
||||||
procedure.routing_enabled? && groupe_instructeur.present? && (!procedure.feature_enabled?(:procedure_routage_api) || !defaut_groupe_instructeur?) && !procedure.feature_enabled?(:routing_rules)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_groupe_instructeur_selector?
|
|
||||||
procedure.routing_enabled? && !procedure.feature_enabled?(:procedure_routage_api) && !procedure.feature_enabled?(:routing_rules)
|
|
||||||
end
|
|
||||||
|
|
||||||
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
||||||
return if groupe_instructeur.present? && groupe_instructeur.procedure != procedure
|
return if groupe_instructeur.present? && groupe_instructeur.procedure != procedure
|
||||||
return if self.groupe_instructeur == groupe_instructeur
|
return if self.groupe_instructeur == groupe_instructeur
|
||||||
|
|
|
@ -69,7 +69,14 @@ class Procedure < ApplicationRecord
|
||||||
|
|
||||||
include Discard::Model
|
include Discard::Model
|
||||||
self.discard_column = :hidden_at
|
self.discard_column = :hidden_at
|
||||||
self.ignored_columns += [:direction, :durees_conservation_required, :cerfa_flag, :test_started_at, :lien_demarche]
|
self.ignored_columns += [
|
||||||
|
:direction,
|
||||||
|
:durees_conservation_required,
|
||||||
|
:cerfa_flag,
|
||||||
|
:test_started_at,
|
||||||
|
:lien_demarche,
|
||||||
|
:migrated_champ_routage
|
||||||
|
]
|
||||||
|
|
||||||
default_scope -> { kept }
|
default_scope -> { kept }
|
||||||
|
|
||||||
|
@ -356,7 +363,6 @@ class Procedure < ApplicationRecord
|
||||||
validates :api_entreprise_token, jwt_token: true, allow_blank: true
|
validates :api_entreprise_token, jwt_token: true, allow_blank: true
|
||||||
validates :api_particulier_token, format: { with: /\A[A-Za-z0-9\-_=.]{15,}\z/ }, allow_blank: true
|
validates :api_particulier_token, format: { with: /\A[A-Za-z0-9\-_=.]{15,}\z/ }, allow_blank: true
|
||||||
validate :validate_auto_archive_on_in_the_future, if: :will_save_change_to_auto_archive_on?
|
validate :validate_auto_archive_on_in_the_future, if: :will_save_change_to_auto_archive_on?
|
||||||
validates :routing_criteria_name, presence: true, allow_blank: false
|
|
||||||
|
|
||||||
before_save :update_juridique_required
|
before_save :update_juridique_required
|
||||||
after_initialize :ensure_path_exists
|
after_initialize :ensure_path_exists
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#
|
#
|
||||||
class ProcedureRevision < ApplicationRecord
|
class ProcedureRevision < ApplicationRecord
|
||||||
self.implicit_order_column = :created_at
|
self.implicit_order_column = :created_at
|
||||||
|
self.ignored_columns += [:migrated_champ_routage]
|
||||||
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
||||||
belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy
|
belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module RoutingEngine
|
module RoutingEngine
|
||||||
def self.compute(dossier)
|
def self.compute(dossier)
|
||||||
return if !dossier.procedure.feature_enabled?(:routing_rules)
|
|
||||||
return if dossier.forced_groupe_instructeur
|
return if dossier.forced_groupe_instructeur
|
||||||
|
|
||||||
matching_groupe = dossier.procedure.groupe_instructeurs.active.reject(&:routing_to_configure?).find do |gi|
|
matching_groupe = dossier.procedure.groupe_instructeurs.active.reject(&:routing_to_configure?).find do |gi|
|
||||||
|
|
|
@ -1,15 +1 @@
|
||||||
- if dossier.show_groupe_instructeur_details?
|
|
||||||
.fr-my-4v.fr-px-4v
|
|
||||||
.flex
|
|
||||||
|
|
||||||
%p.champ-label.flex-grow= dossier.procedure.routing_criteria_name
|
|
||||||
%p.fr-mb-0.fr-text--xs
|
|
||||||
- if demande_seen_at && dossier.groupe_instructeur_updated_at && demande_seen_at < dossier.groupe_instructeur_updated_at
|
|
||||||
%span.fr-badge.fr-badge--sm{ class: badge_class_if_unseen(demande_seen_at, dossier.groupe_instructeur_updated_at) }
|
|
||||||
= t(:updated_at, scope: [:views, :shared, :dossiers, :form], datetime: try_format_datetime(dossier.groupe_instructeur_updated_at))
|
|
||||||
|
|
||||||
.champ-content{ class: highlight_if_unseen_class(demande_seen_at, dossier.groupe_instructeur_updated_at) }
|
|
||||||
%p= dossier.groupe_instructeur.label
|
|
||||||
|
|
||||||
|
|
||||||
= render ViewableChamp::SectionComponent.new(champs: champs, demande_seen_at: demande_seen_at, profile: profile)
|
= render ViewableChamp::SectionComponent.new(champs: champs, demande_seen_at: demande_seen_at, profile: profile)
|
||||||
|
|
|
@ -19,15 +19,6 @@
|
||||||
- if dossier.procedure.notice.attached?
|
- if dossier.procedure.notice.attached?
|
||||||
= render Dsfr::DownloadComponent.new(attachment: dossier.procedure.notice , url: notice_url(dossier.procedure), name: t("views.shared.dossiers.edit.notice"), ephemeral_link: administrateur_signed_in?)
|
= render Dsfr::DownloadComponent.new(attachment: dossier.procedure.notice , url: notice_url(dossier.procedure), name: t("views.shared.dossiers.edit.notice"), ephemeral_link: administrateur_signed_in?)
|
||||||
|
|
||||||
- if dossier.show_groupe_instructeur_selector?
|
|
||||||
%span{ data: { controller: 'autosave' } }
|
|
||||||
= f.label :groupe_instructeur_id do
|
|
||||||
= dossier.procedure.routing_criteria_name
|
|
||||||
%span.mandatory *
|
|
||||||
= f.select :groupe_instructeur_id,
|
|
||||||
dossier.procedure.groupe_instructeurs.active.map { |gi| [gi.label, gi.id] },
|
|
||||||
{ include_blank: dossier.brouillon? }
|
|
||||||
|
|
||||||
= render EditableChamp::SectionComponent.new(champs: dossier_for_editing.champs_public)
|
= render EditableChamp::SectionComponent.new(champs: dossier_for_editing.champs_public)
|
||||||
|
|
||||||
- if dossier.pending_correction?
|
- if dossier.pending_correction?
|
||||||
|
|
|
@ -17,7 +17,6 @@ features = [
|
||||||
:dossier_pdf_vide,
|
:dossier_pdf_vide,
|
||||||
:hide_instructeur_email,
|
:hide_instructeur_email,
|
||||||
:procedure_routage_api,
|
:procedure_routage_api,
|
||||||
:routing_rules,
|
|
||||||
:groupe_instructeur_api_hack,
|
:groupe_instructeur_api_hack,
|
||||||
:rerouting
|
:rerouting
|
||||||
]
|
]
|
||||||
|
|
|
@ -31,7 +31,6 @@ en:
|
||||||
declarative_with_state/en_instruction: Instruction
|
declarative_with_state/en_instruction: Instruction
|
||||||
declarative_with_state/accepte: Accepted
|
declarative_with_state/accepte: Accepted
|
||||||
api_particulier_token: Token API Particulier
|
api_particulier_token: Token API Particulier
|
||||||
routing_criteria_name: Routing criteria name
|
|
||||||
initiated_mail: File sorted for processing notification email
|
initiated_mail: File sorted for processing notification email
|
||||||
received_mail: File submitted notification email
|
received_mail: File submitted notification email
|
||||||
closed_mail: File sorted as accepted notification email
|
closed_mail: File sorted as accepted notification email
|
||||||
|
|
|
@ -37,7 +37,6 @@ fr:
|
||||||
declarative_with_state/en_instruction: En instruction
|
declarative_with_state/en_instruction: En instruction
|
||||||
declarative_with_state/accepte: Accepté
|
declarative_with_state/accepte: Accepté
|
||||||
api_particulier_token: Jeton API Particulier
|
api_particulier_token: Jeton API Particulier
|
||||||
routing_criteria_name: Nomination du routage
|
|
||||||
initiated_mail: L’email de notification de passage de dossier en instruction
|
initiated_mail: L’email de notification de passage de dossier en instruction
|
||||||
received_mail: L’email de notification de dépôt de dossier
|
received_mail: L’email de notification de dépôt de dossier
|
||||||
closed_mail: L’email de notification d’acceptation de dossier
|
closed_mail: L’email de notification d’acceptation de dossier
|
||||||
|
|
|
@ -58,37 +58,3 @@ fr:
|
||||||
procedures: Démarches
|
procedures: Démarches
|
||||||
simple_routing:
|
simple_routing:
|
||||||
procedures: Démarches
|
procedures: Démarches
|
||||||
edit:
|
|
||||||
routing:
|
|
||||||
title: Libellé de la liste de groupes
|
|
||||||
group_management:
|
|
||||||
title: Gestion des Groupes
|
|
||||||
delete: supprimer le groupe
|
|
||||||
delete_confirmation: Êtes-vous sûr de vouloir supprimer le groupe "%{group_name}"
|
|
||||||
move_files:
|
|
||||||
zero: déplacer les dossiers en brouillon
|
|
||||||
one: déplacer un dossier
|
|
||||||
other: déplacer les %{count} dossiers
|
|
||||||
move_files_confirmation: Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer
|
|
||||||
add_a_group:
|
|
||||||
title: Ajouter un nom de groupe
|
|
||||||
notice: Ce groupe sera un choix de la liste "%{routing_criteria_name}"
|
|
||||||
csv_import:
|
|
||||||
link_text: exemple de fichier
|
|
||||||
instructeurs_file_path: /csv/import-instructeurs-test.csv
|
|
||||||
groupes_file_path: /csv/fr/import-groupe-test.csv
|
|
||||||
view: voir
|
|
||||||
set_up: paramétrer
|
|
||||||
button:
|
|
||||||
add_group: Ajouter le groupe
|
|
||||||
rename: Renommer
|
|
||||||
existing_groupe:
|
|
||||||
one: "%{count} groupe existe"
|
|
||||||
other: "%{count} groupes existent"
|
|
||||||
routing:
|
|
||||||
title: Routage
|
|
||||||
notice_html: |
|
|
||||||
Le routage permet d’acheminer les dossiers vers différents groupes d’instructeurs. Il s’active automatiquement dès qu’une démarche compte au moins deux groupes actifs.
|
|
||||||
button:
|
|
||||||
self_managment_toggle: Activer l’autogestion des instructeurs
|
|
||||||
add_group: Ajouter le groupe
|
|
||||||
|
|
|
@ -542,7 +542,6 @@ Rails.application.routes.draw do
|
||||||
get 'simple_routing'
|
get 'simple_routing'
|
||||||
post 'create_simple_routing'
|
post 'create_simple_routing'
|
||||||
delete 'destroy_all_groups_but_defaut'
|
delete 'destroy_all_groups_but_defaut'
|
||||||
patch 'update_routing_criteria_name'
|
|
||||||
patch 'update_instructeurs_self_management_enabled'
|
patch 'update_instructeurs_self_management_enabled'
|
||||||
post 'import'
|
post 'import'
|
||||||
get 'export_groupe_instructeurs'
|
get 'export_groupe_instructeurs'
|
||||||
|
|
|
@ -692,18 +692,6 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#update_routing_criteria_name' do
|
|
||||||
before do
|
|
||||||
patch :update_routing_criteria_name,
|
|
||||||
params: {
|
|
||||||
procedure_id: procedure.id,
|
|
||||||
procedure: { routing_criteria_name: 'new name !' }
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it { expect(procedure.reload.routing_criteria_name).to eq('new name !') }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#create_simple_routing' do
|
describe '#create_simple_routing' do
|
||||||
let!(:procedure3) do
|
let!(:procedure3) do
|
||||||
create(:procedure,
|
create(:procedure,
|
||||||
|
|
|
@ -242,7 +242,7 @@ describe Instructeur, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#notifications_for_groupe_instructeurs' do
|
describe '#notifications_for_groupe_instructeurs' do
|
||||||
# a procedure, one group, 2 instructeurs
|
# a procedure, two groups, 2 instructeurs
|
||||||
let(:procedure) { create(:simple_procedure, :routee, :with_type_de_champ_private, :for_individual) }
|
let(:procedure) { create(:simple_procedure, :routee, :with_type_de_champ_private, :for_individual) }
|
||||||
let(:gi_p1) { procedure.groupe_instructeurs.last }
|
let(:gi_p1) { procedure.groupe_instructeurs.last }
|
||||||
let!(:dossier) { create(:dossier, :en_construction, :with_individual, :followed, procedure: procedure, groupe_instructeur: gi_p1) }
|
let!(:dossier) { create(:dossier, :en_construction, :with_individual, :followed, procedure: procedure, groupe_instructeur: gi_p1) }
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
describe RoutingEngine, type: :model do
|
describe RoutingEngine, type: :model do
|
||||||
include Logic
|
include Logic
|
||||||
|
|
||||||
before { Flipper.enable(:routing_rules, procedure) }
|
|
||||||
|
|
||||||
describe '.compute' do
|
describe '.compute' do
|
||||||
let(:procedure) do
|
let(:procedure) do
|
||||||
create(:procedure,
|
create(:procedure,
|
||||||
|
|
|
@ -20,7 +20,6 @@ describe 'The routing with rules', js: true do
|
||||||
let(:artistique_user) { create(:user, password: password) }
|
let(:artistique_user) { create(:user, password: password) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Flipper.enable(:routing_rules, procedure)
|
|
||||||
procedure.defaut_groupe_instructeur.instructeurs << administrateur.instructeur
|
procedure.defaut_groupe_instructeur.instructeurs << administrateur.instructeur
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -59,18 +59,11 @@ describe 'shared/dossiers/champs', type: :view do
|
||||||
|
|
||||||
context "with a routed procedure" do
|
context "with a routed procedure" do
|
||||||
let(:procedure) do
|
let(:procedure) do
|
||||||
create(:procedure,
|
create(:procedure, :routee)
|
||||||
:routee,
|
|
||||||
routing_criteria_name: 'departement')
|
|
||||||
end
|
end
|
||||||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let(:champs) { [] }
|
let(:champs) { [] }
|
||||||
|
|
||||||
it "renders the routing criteria name and its value" do
|
|
||||||
expect(subject).to include(procedure.routing_criteria_name)
|
|
||||||
expect(subject).to include(dossier.groupe_instructeur.label)
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with seen_at" do
|
context "with seen_at" do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:dossier) { create(:dossier) }
|
||||||
let(:nouveau_groupe_instructeur) { create(:groupe_instructeur, procedure: dossier.procedure) }
|
let(:nouveau_groupe_instructeur) { create(:groupe_instructeur, procedure: dossier.procedure) }
|
||||||
|
|
|
@ -132,16 +132,19 @@ describe 'shared/dossiers/edit', type: :view do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a routed procedure' do
|
context 'with a routed procedure' do
|
||||||
let(:procedure) do
|
let(:procedure_routee) do
|
||||||
create(:procedure,
|
create(:procedure, :routee)
|
||||||
:routee,
|
|
||||||
routing_criteria_name: 'departement')
|
|
||||||
end
|
end
|
||||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
let!(:drop_down_tdc) { create(:type_de_champ_drop_down_list, procedure: procedure_routee, drop_down_options: options) }
|
||||||
let(:champs) { [] }
|
let(:options) { procedure_routee.groupe_instructeurs.pluck(:label) }
|
||||||
|
let(:dossier) { create(:dossier, procedure: procedure_routee) }
|
||||||
|
let(:champs) { [champ_drop_down] }
|
||||||
|
let(:champ_drop_down) { create(:champ_drop_down_list, dossier: dossier, type_de_champ: drop_down_tdc, value: options.first) }
|
||||||
|
|
||||||
it 'renders the routing criteria name and its value' do
|
before { dossier.champs_public << champs }
|
||||||
expect(subject).to have_field(procedure.routing_criteria_name)
|
|
||||||
|
it 'renders the libelle of the type de champ used for routing' do
|
||||||
|
expect(subject).to include(champ_drop_down.libelle)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when groupe instructeur is selected' do
|
context 'when groupe instructeur is selected' do
|
||||||
|
@ -149,8 +152,8 @@ describe 'shared/dossiers/edit', type: :view do
|
||||||
dossier.groupe_instructeur = dossier.procedure.defaut_groupe_instructeur
|
dossier.groupe_instructeur = dossier.procedure.defaut_groupe_instructeur
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the routing criteria name and its value' do
|
it 'renders the routing libelle and its value' do
|
||||||
expect(subject).to have_field(procedure.routing_criteria_name)
|
expect(subject).to include(champ_drop_down.libelle)
|
||||||
expect(subject).to include(dossier.groupe_instructeur.label)
|
expect(subject).to include(dossier.groupe_instructeur.label)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue