Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/admin/observation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def scoped_collection
scope -> { I18n.t("shared.created") }, :created
scope -> { I18n.t("active_admin.observations_page.scope_hidden") }, :hidden
scope -> { I18n.t("active_admin.observations_page.visible") }, :visible
scope -> { I18n.t("activerecord.models.quality_control") }, :quality_control, if: -> { current_user.reviewable_observer_ids.any? } do |observations|
observations.ready_for_or_in_qc.where(id: current_user.quality_controlable_observations)
end

# region filters
filter :id, as: :numeric_range_filter
Expand Down
22 changes: 19 additions & 3 deletions app/admin/quality_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

actions :new, :create

permit_params :reviewer_id, :reviewable_id, :reviewable_type, :passed, :comment
permit_params :reviewer_id, :reviewable_id, :reviewable_type, :decision, :comment

controller do
def new
Expand All @@ -16,7 +16,11 @@ def new

def create
super do |format|
redirect_to reviewable_path and return
if resource.errors.empty?
redirect_to params[:return_to] || reviewable_path, notice: I18n.t("active_admin.quality_control_page.performed_qc", decision: resource.decision) and return
else
resource.reviewable.reload
end
end
end

Expand All @@ -32,9 +36,21 @@ def reviewable_path
f.hidden_field :reviewer_id, value: current_user.id
f.hidden_field :reviewable_id, value: resource.reviewable_id
f.hidden_field :reviewable_type, value: resource.reviewable_type
f.hidden_field :rejectable_decisions, value: resource.reviewable.qc_rejectable_decisions.join(","), disabled: true

f.inputs do
f.input :passed, as: :radio, collection: resource.reviewable.qc_available_decisions, label: I18n.t("operator_documents.qc_form.decision")
f.input :decision, as: :radio, collection: resource.reviewable.qc_available_decisions, label: I18n.t("operator_documents.qc_form.decision")

resource.reviewable.qc_available_decisions.each do |decision|
next unless resource.reviewable.qc_decisions_hints[decision].present?

li class: "input qc-decision-hint", style: "display: none;", "data-hint": decision do
div class: "flash flash_warning" do
resource.reviewable.qc_decisions_hints[decision]
end
end
end

f.input :comment, as: :text
end

Expand Down
11 changes: 8 additions & 3 deletions app/assets/javascripts/quality_controls.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
$(document).ready(function() {
updateQCFields();
$('input[name="quality_control[passed]"]').on('change', function(){
$('input[name="quality_control[decision]"]').on('change', function(){
updateQCFields();
})
})

function updateQCFields() {
const selectedValue = $('input[name="quality_control[passed]"]:checked').val();
const selectedValue = $('input[name="quality_control[decision]"]:checked').val();
const rejectableDecisions = $('#quality_control_rejectable_decisions').val().split(',');
const comment = $('#quality_control_comment_input');
const hint = $('.qc-decision-hint[data-hint="' + selectedValue + '"]');

if (selectedValue === 'false') {
$('.qc-decision-hint').hide();
hint.show();

if (rejectableDecisions.includes(selectedValue)) {
comment.find('textarea').prop('disabled', false);
comment.show();
} else {
Expand Down
41 changes: 20 additions & 21 deletions app/models/observation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class Observation < ApplicationRecord
translates :details, :concern_opinion, :litigation_status, touch: true, versioning: :paper_trail, paranoia: true
active_admin_translates :details, :concern_opinion, :litigation_status

WrongStateError = Class.new(StandardError)
QCError = Class.new(StandardError)

enum :evidence_type, {"No evidence" => 0, "Uploaded documents" => 1, "Evidence presented in the report" => 2}, validate: {allow_nil: true}
enum :observation_type, {"operator" => 0, "government" => 1}, validate: true
Expand Down Expand Up @@ -94,18 +94,7 @@ class Observation < ApplicationRecord
"Ready for QC1" => ["QC1 in progress"],
"QC1 in progress" => ["Rejected", "Ready for QC2"],
"Ready for QC2" => ["QC2 in progress"],
"QC2 in progress" => ["Needs revision", "Ready for publication"]
}
}.freeze

QC_APPROVAL_STATUS_TRANSITIONS = {
"QC1 in progress" => {
false => "Rejected",
true => "Ready for QC2"
},
"QC2 in progress" => {
false => "Needs revision",
true => "Ready for publication"
"QC2 in progress" => ["Needs revision", "Rejected", "Ready for publication"]
}
}.freeze

Expand Down Expand Up @@ -212,6 +201,7 @@ class Observation < ApplicationRecord
scope :by_government, ->(government_id) { joins(:governments).where(governments: {id: government_id}) }
scope :pending, -> { where(validation_status: ["Created", "QC2 in progress"]) }
scope :created, -> { where(validation_status: ["Created", "Ready for QC2"]) }
scope :ready_for_or_in_qc, -> { where(validation_status: ["Ready for QC1", "Ready for QC2", "QC1 in progress", "QC2 in progress"]) }
scope :published, -> { where(validation_status: PUBLISHED_STATES) }
scope :hidden, -> { where(hidden: true) }
scope :visible, -> { where(hidden: [false, nil]) }
Expand Down Expand Up @@ -279,27 +269,36 @@ def all_responsible_for_qc
responsible_for_qc1.or(responsible_for_qc2)
end

def update_qc_status!(qc_passed:)
raise WrongStateError, "QC not in progress" unless qc_in_progress?

def update_qc_status!(qc)
update!(
user_type: :reviewer,
validation_status: QC_APPROVAL_STATUS_TRANSITIONS[validation_status][qc_passed]
validation_status: qc.decision
)
end

def qc_rejectable_decisions
["Rejected", "Needs revision"]
end

def qc_available_decisions
return [] unless QC_APPROVAL_STATUS_TRANSITIONS[validation_status]
return [] unless qc_in_progress?

STATUS_TRANSITIONS[:reviewer][validation_status]
end

QC_APPROVAL_STATUS_TRANSITIONS[validation_status].invert.to_a
def qc_decisions_hints
{
"Rejected" => I18n.t("active_admin.observations_page.rejected_hint"),
"Needs revision" => I18n.t("active_admin.observations_page.needs_revision_hint")
}
end

def qc_metadata(qc_passed:)
def qc_metadata(qc)
return {} unless qc_in_progress?

{
level: (validation_status == "QC1 in progress") ? "QC1" : "QC2",
decision: QC_APPROVAL_STATUS_TRANSITIONS[validation_status][qc_passed]
decision: qc.decision
}
end

Expand Down
15 changes: 12 additions & 3 deletions app/models/quality_control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@ class QualityControl < ApplicationRecord
belongs_to :reviewer, class_name: "User"

validates :passed, inclusion: {in: [true, false]}
validates :decision, presence: true
validates :comment, presence: true, if: -> { !passed && !metadata["backfilled"] }

before_validation :set_passed
before_save :set_metadata
after_create :update_reviewable_qc_status
before_create :update_reviewable_qc_status

private

def set_passed
self.passed = reviewable.qc_rejectable_decisions.exclude?(decision) if reviewable.present?
end

def set_metadata
self.metadata = reviewable.qc_metadata(qc_passed: passed)
self.metadata = reviewable.qc_metadata(self) unless metadata.present?
end

def update_reviewable_qc_status
reviewable.update_qc_status!(qc_passed: passed)
reviewable.update_qc_status!(self)
rescue => e
errors.add(:base, "Failed to update QC status: #{e.message}")
throw :abort
end
end
2 changes: 1 addition & 1 deletion app/resources/v1/quality_control_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class QualityControlResource < BaseResource

has_one :reviewable, polymorphic: true, always_include_linkage_data: true

attributes :comment, :passed, :created_at, :updated_at
attributes :comment, :passed, :decision, :created_at, :updated_at

before_create :set_reviewer

Expand Down
4 changes: 1 addition & 3 deletions app/views/admin/observations/_attributes_table.html.arb
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ panel "Quality Controls" do
end
column :reviewer
column :passed?
tag_column :decision do |qc|
qc.metadata["decision"].presence
end
tag_column :decision
column :comment
column :performed_at, &:created_at
end
Expand Down
5 changes: 4 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ en:
details: 'Operator Document Annex Details'
observations_page:
not_modified: 'Observation NOT modified'
performed_qc: 'Quality Control performed'
moved_ready: 'Observation moved to Ready for Publication'
moved_needs_revision: 'Observation moved to Needs Revision'
moved_qc_in_progress: 'Observation moved to QC in Progress'
Expand Down Expand Up @@ -418,10 +417,13 @@ en:
location_on_map: 'Location on map'
visible: 'Visible'
scope_hidden: 'Hidden'
rejected_hint: 'The observation will be rejected and the monitor will be notified. The monitor can then edit the observation and submit it for QC again.'
needs_revision_hint: 'The monitor will be notified that the observation needs revision with proposed suggestions. The monitor can still publish the observation or submit it for QC again.'
users_page:
responsible_for_countries_hint: Admin responsible for countries will get notifications concerning the private sector in these countries.
quality_control_page:
reviewable_not_in_qc: "Reviewed entity is not in QC in progress status"
performed_qc: 'Quality Control performed with decision: %{decision}'
powered_by: "Admin Backoffice"
delete_model: "Delete %{model}"
delete: "Delete"
Expand Down Expand Up @@ -557,6 +559,7 @@ en:
uploaded_document: Uploaded document #g
user: User #g
user_permission: User permission #g
quality_control: Quality control
attributes:
about_page_entry:
code: Code #g
Expand Down
5 changes: 4 additions & 1 deletion config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,6 @@ es:
details: 'Detalles de Anexo de Documento del Operador'
observations_page:
not_modified: 'Observación NO modificada'
performed_qc: 'Control de Calidad realizado'
moved_ready: 'Observación movida a Listo para Publicación'
moved_needs_revision: 'Observación movida a Necesita Revisión'
moved_qc_in_progress: 'Observación movida a QC en Progreso'
Expand Down Expand Up @@ -431,9 +430,12 @@ es:
location_on_map: 'Ubicación en el mapa'
visible: 'Visible'
scope_hidden: 'Oculto'
rejected_hint: 'La observación será rechazada y el monitor será notificado. El monitor puede luego editar la observación y enviarla nuevamente para QC.'
needs_revision_hint: 'El monitor será notificado de que la observación necesita revisión con sugerencias propuestas. El monitor aún puede publicar la observación o enviarla nuevamente para QC.'
users_page:
responsible_for_countries_hint: Los administradores responsables de países recibirán notificaciones relacionadas con el sector privado en estos países.
quality_control_page:
performed_qc: 'Control de Calidad realizado con decisión: %{decision}'
reviewable_not_in_qc: "La entidad revisada no está en estado QC en progreso"
powered_by: "Admin Backoffice"
delete_model: "Eliminar %{model}"
Expand Down Expand Up @@ -570,6 +572,7 @@ es:
uploaded_document: Documento subido
user: Usuario
user_permission: Permiso de usuario
quality_control: Control de calidad
attributes:
about_page_entry:
code: Código
Expand Down
5 changes: 4 additions & 1 deletion config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ fr:
details: "Détails de l'annexe du document de l'opérateur"
observations_page:
not_modified: 'Observation NON modifiée'
performed_qc: 'Contrôle qualité effectué'
moved_ready: 'Observation déplacée vers Prêt pour publication'
moved_needs_revision: 'Observation déplacée vers Nécessite une révision'
moved_qc_in_progress: 'Observation déplacée au CQ en cours'
Expand Down Expand Up @@ -424,7 +423,10 @@ fr:
location_on_map: 'Emplacement sur la carte'
visible: 'Visible'
scope_hidden: 'Invisible'
rejected_hint: 'L’observation sera rejetée et le moniteur en sera informé. Le moniteur peut ensuite modifier l’observation et la soumettre à nouveau pour le contrôle de qualité.'
needs_revision_hint: 'Le moniteur sera informé que l’observation nécessite une révision avec des suggestions proposées. Le moniteur peut toujours publier l’observation ou la soumettre à nouveau pour le contrôle de qualité.'
quality_control_page:
performed_qc: 'Contrôle de qualité effectué avec la décision : %{decision}'
reviewable_not_in_qc: "L'entité examinée n'a pas le statut de contrôle qualité en cours"
powered_by: "Admin Backoffice"
delete_model: "Supprimer %{model}"
Expand Down Expand Up @@ -554,6 +556,7 @@ fr:
uploaded_document: Document téléchargé #g
user: Utilisateur #g
user_permission: Autorisation utilisateur #g
quality_control: Contrôle de qualité
attributes:
about_page_entry:
code: 'Code'
Expand Down
26 changes: 26 additions & 0 deletions db/migrate/20260121104250_add_decision_to_quality_control.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class AddDecisionToQualityControl < ActiveRecord::Migration[7.2]
class QualityControl < ApplicationRecord
end

def up
add_column :quality_controls, :decision, :string

QualityControl.find_each do |qc|
qc.update!(decision: qc.metadata["decision"])
end

change_column_null :quality_controls, :decision, false
end

def down
QualityControl.find_each do |qc|
next if qc.metadata.present? && qc.metadata["decision"].present?

qc.metadata ||= {}
qc.metadata["decision"] = qc.decision
qc.save!
end

remove_column :quality_controls, :decision
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@
t.jsonb "metadata", default: {}
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "decision", null: false
t.index ["reviewable_type", "reviewable_id"], name: "index_quality_controls_on_reviewable"
t.index ["reviewer_id"], name: "index_quality_controls_on_reviewer_id"
end
Expand Down
1 change: 1 addition & 0 deletions spec/controllers/admin/quality_controls_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
reviewable_id: observation.id,
reviewable_type: "Observation",
reviewer_id: admin.id,
decision: "Rejected",
passed: false,
comment: "Comment"
}
Expand Down
4 changes: 3 additions & 1 deletion spec/factories/quality_controls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
#
FactoryBot.define do
factory :quality_control do
reviewable { create(:observation, validation_status: "QC2 in progress") }
reviewable { build(:observation, validation_status: "QC2 in progress") }
reviewer { build(:admin) }
passed { true }
decision { "Ready for publication" }

trait :not_passed do
decision { "Rejected" }
passed { false }
comment { "Quality control not passed" }
end
Expand Down
7 changes: 1 addition & 6 deletions spec/models/quality_control_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,8 @@
expect(subject).to have(1).error_on(:reviewer)
end

it "should be invalid without passed" do
subject.passed = nil
expect(subject).to have(1).error_on(:passed)
end

it "should be invalid without a comment if not passed" do
subject.passed = false
subject = build(:quality_control, :not_passed, comment: nil)
expect(subject).to have(1).error_on(:comment)
end

Expand Down