Il funnel mostra stage attuale e cumulato, i KPI escludono Da contattare e le liste usano il totale reale invece del tetto a 20. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.3 KiB
Ruby
115 lines
3.3 KiB
Ruby
require "test_helper"
|
|
|
|
class DashboardMetricsTest < ActiveSupport::TestCase
|
|
setup do
|
|
@project = projects(:matchlivetv)
|
|
Current.user = users(:marco)
|
|
Current.project = @project
|
|
@opps = []
|
|
end
|
|
|
|
teardown do
|
|
Current.user = nil
|
|
Current.project = nil
|
|
end
|
|
|
|
test "computes stage counts and pipeline value" do
|
|
metrics = Dashboard::Metrics.new
|
|
assert metrics.stage_counts.key?("interested")
|
|
assert metrics.stage_counts.key?("to_contact")
|
|
assert_kind_of Numeric, metrics.open_pipeline_value
|
|
assert_kind_of Array, metrics.funnel_steps
|
|
end
|
|
|
|
test "funnel shows current snapshot and reached-or-beyond without skipping stages" do
|
|
add_opp("to_contact")
|
|
2.times { add_opp("contacted") }
|
|
add_opp("replied")
|
|
add_opp("interested")
|
|
2.times { add_opp("proposal", estimated_value: 100) }
|
|
add_opp("lost", lost_reason: "price")
|
|
|
|
steps = metrics.funnel_steps.index_by { |step| step[:stage] }
|
|
|
|
assert_equal Dashboard::Metrics::FUNNEL_STAGES, metrics.funnel_steps.map { |step| step[:stage] }
|
|
|
|
assert_equal 2, steps["contacted"][:current]
|
|
assert_equal 6, steps["contacted"][:reached]
|
|
assert_equal 0, steps["first_use"][:current]
|
|
assert_equal 2, steps["first_use"][:reached]
|
|
assert_equal 2, steps["proposal"][:current]
|
|
assert_equal 2, steps["proposal"][:reached]
|
|
assert_equal 0, steps["won"][:current]
|
|
refute steps.key?("lost")
|
|
|
|
contacted_step = steps["replied"]
|
|
assert_equal "4 su 6 hanno superato Contattato", contacted_step[:conversion_label]
|
|
assert_equal 67, contacted_step[:rate]
|
|
end
|
|
|
|
test "next action KPI excludes da contattare and attention remainder uses real total" do
|
|
2.times { add_opp("to_contact", send_status: "to_send") }
|
|
10.times { add_opp("contacted") }
|
|
interested = add_opp("interested")
|
|
Task.create!(
|
|
title: "Richiama",
|
|
organization: interested.organization,
|
|
opportunity: interested,
|
|
assigned_user: users(:marco),
|
|
due_at: 1.day.from_now,
|
|
priority: "normal",
|
|
task_type: "call",
|
|
status: "pending"
|
|
)
|
|
|
|
assert_equal 10, metrics.opportunities_without_next_action
|
|
|
|
preview = metrics.attention_items[:opportunities_without_next_action]
|
|
assert_equal 10, preview[:total]
|
|
assert_equal 8, preview[:items].size
|
|
end
|
|
|
|
test "demo and first use conversions are blank when those stages were never used" do
|
|
add_opp("proposal")
|
|
|
|
assert_nil metrics.demo_to_won_conversion
|
|
assert_nil metrics.first_use_to_won_conversion
|
|
end
|
|
|
|
test "demo conversion is zero when someone is in demo and nobody won" do
|
|
add_opp("demo_trial")
|
|
|
|
assert_equal 0, metrics.demo_to_won_conversion
|
|
end
|
|
|
|
test "goal progress" do
|
|
goal = sales_goals(:september)
|
|
assert_equal 0, goal.current_value
|
|
assert_equal 0, goal.progress_percentage
|
|
end
|
|
|
|
private
|
|
|
|
def metrics
|
|
Dashboard::Metrics.new(scope: Opportunity.where(id: @opps.map(&:id)), project: @project)
|
|
end
|
|
|
|
def add_opp(stage, **attrs)
|
|
org = Organization.create!(
|
|
name: "Soc #{stage} #{SecureRandom.hex(3)}",
|
|
status: "prospect",
|
|
organization_type: "societa_sportiva"
|
|
)
|
|
opp = Opportunity.create!(
|
|
{
|
|
organization: org,
|
|
project: @project,
|
|
name: "Deal #{stage}",
|
|
pipeline_stage: stage
|
|
}.merge(attrs)
|
|
)
|
|
@opps << opp
|
|
opp
|
|
end
|
|
end
|