mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Ensure ResourceAllocation is scoped to a work package for now, allow further classes later
This commit is contained in:
@@ -29,6 +29,8 @@
|
||||
#++
|
||||
|
||||
class ResourceAllocation < ApplicationRecord
|
||||
ALLOWED_ENTITY_TYPES = %w[WorkPackage].freeze
|
||||
|
||||
belongs_to :entity, polymorphic: true, optional: false
|
||||
belongs_to :principal, class_name: "User", optional: true, inverse_of: :resource_allocations
|
||||
|
||||
@@ -46,6 +48,10 @@ class ResourceAllocation < ApplicationRecord
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
validates :entity_type,
|
||||
inclusion: { in: ALLOWED_ENTITY_TYPES },
|
||||
allow_blank: true
|
||||
|
||||
validate :end_date_after_start_date
|
||||
|
||||
# Resource allocations are scoped to whatever project their (polymorphic)
|
||||
|
||||
+2
-2
@@ -43,8 +43,8 @@ RSpec.describe ResourceAllocations::CreateContract do
|
||||
let(:current_user) do
|
||||
create(:user, member_with_permissions: { project => %i[view_resource_planners allocate_user_resources] })
|
||||
end
|
||||
let(:planner) { create(:resource_planner, project:, principal: current_user) }
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: planner, principal: current_user) }
|
||||
let(:work_package) { create(:work_package, project:) }
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: work_package, principal: current_user) }
|
||||
let(:contract) { described_class.new(resource_allocation, current_user) }
|
||||
|
||||
it "allows entity to be set" do
|
||||
|
||||
+2
-2
@@ -36,9 +36,9 @@ RSpec.describe ResourceAllocations::DeleteContract do
|
||||
|
||||
shared_let(:project) { create(:project, enabled_module_names: %w[resource_management]) }
|
||||
shared_let(:owner) { create(:user) }
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: planner, principal: owner) }
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: work_package, principal: owner) }
|
||||
let(:contract) { described_class.new(resource_allocation, current_user) }
|
||||
|
||||
context "when user has allocate_user_resources" do
|
||||
|
||||
+2
-2
@@ -34,10 +34,10 @@ require "contracts/shared/model_contract_shared_context"
|
||||
RSpec.shared_examples_for "resource allocation contract" do
|
||||
shared_let(:project) { create(:project, enabled_module_names: %w[resource_management]) }
|
||||
shared_let(:owner) { create(:user) }
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let(:resource_allocation) do
|
||||
build_stubbed(:resource_allocation, entity: planner, principal: owner)
|
||||
build_stubbed(:resource_allocation, entity: work_package, principal: owner)
|
||||
end
|
||||
|
||||
context "when user has the allocate_user_resources permission" do
|
||||
|
||||
+2
-2
@@ -43,8 +43,8 @@ RSpec.describe ResourceAllocations::UpdateContract do
|
||||
let(:current_user) do
|
||||
create(:user, member_with_permissions: { project => %i[view_resource_planners allocate_user_resources] })
|
||||
end
|
||||
let(:planner) { create(:resource_planner, project:, principal: current_user) }
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: planner, principal: current_user) }
|
||||
let(:work_package) { create(:work_package, project:) }
|
||||
let(:resource_allocation) { build_stubbed(:resource_allocation, entity: work_package, principal: current_user) }
|
||||
let(:contract) { described_class.new(resource_allocation, current_user) }
|
||||
|
||||
it "does not allow entity to be set" do
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
FactoryBot.define do
|
||||
factory :resource_allocation, class: "ResourceAllocation" do
|
||||
entity factory: :resource_planner
|
||||
entity factory: :work_package
|
||||
principal factory: :user
|
||||
state { "requested" }
|
||||
start_date { Date.new(2026, 1, 5) }
|
||||
|
||||
@@ -70,9 +70,9 @@ RSpec.describe ResourceAllocation do
|
||||
describe "validations" do
|
||||
shared_let(:project) { create(:project, enabled_module_names: %w[resource_management]) }
|
||||
shared_let(:owner) { create(:user, member_with_permissions: { project => %i[view_resource_planners] }) }
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let(:allocation) { build(:resource_allocation, entity: planner, principal: owner) }
|
||||
let(:allocation) { build(:resource_allocation, entity: work_package, principal: owner) }
|
||||
|
||||
it "is valid with the factory defaults" do
|
||||
expect(allocation).to be_valid
|
||||
@@ -115,6 +115,23 @@ RSpec.describe ResourceAllocation do
|
||||
end
|
||||
end
|
||||
|
||||
describe "entity type" do
|
||||
it "lists the supported entity types" do
|
||||
expect(described_class::ALLOWED_ENTITY_TYPES).to eq(%w[WorkPackage])
|
||||
end
|
||||
|
||||
it "is valid when the entity type is in the allowed list" do
|
||||
allocation.entity = work_package
|
||||
expect(allocation).to be_valid
|
||||
end
|
||||
|
||||
it "is invalid when the entity type is outside the allowed list" do
|
||||
allocation.entity = create(:resource_planner, project:, principal: owner)
|
||||
expect(allocation).not_to be_valid
|
||||
expect(allocation.errors.symbols_for(:entity_type)).to include(:inclusion)
|
||||
end
|
||||
end
|
||||
|
||||
describe "allocated_time numericality" do
|
||||
it "is invalid when zero" do
|
||||
allocation.allocated_time = 0
|
||||
@@ -175,7 +192,7 @@ RSpec.describe ResourceAllocation do
|
||||
describe "user_filter serialization" do
|
||||
shared_let(:project) { create(:project, enabled_module_names: %w[resource_management]) }
|
||||
shared_let(:owner) { create(:user, member_with_permissions: { project => %i[view_resource_planners] }) }
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
it "serializes filters using the same coder as UserQuery" do
|
||||
coder = described_class.type_for_attribute(:user_filter).coder
|
||||
@@ -191,7 +208,7 @@ RSpec.describe ResourceAllocation do
|
||||
filter.operator = "~"
|
||||
filter.values = ["alice"]
|
||||
|
||||
allocation = create(:resource_allocation, entity: planner, principal: owner, user_filter: [filter])
|
||||
allocation = create(:resource_allocation, entity: work_package, principal: owner, user_filter: [filter])
|
||||
|
||||
reloaded = described_class.find(allocation.id)
|
||||
expect(reloaded.user_filter.size).to eq(1)
|
||||
@@ -201,7 +218,7 @@ RSpec.describe ResourceAllocation do
|
||||
end
|
||||
|
||||
it "defaults to an empty array" do
|
||||
allocation = create(:resource_allocation, entity: planner, principal: owner)
|
||||
allocation = create(:resource_allocation, entity: work_package, principal: owner)
|
||||
expect(allocation.reload.user_filter).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
+3
-3
@@ -35,12 +35,12 @@ RSpec.describe ResourceAllocations::CreateService, type: :model do
|
||||
shared_let(:owner) do
|
||||
create(:user, member_with_permissions: { project => %i[view_resource_planners allocate_user_resources] })
|
||||
end
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let(:assignee) { create(:user, member_with_permissions: { project => %i[view_resource_planners] }) }
|
||||
let(:params) do
|
||||
{
|
||||
entity: planner,
|
||||
entity: work_package,
|
||||
principal: assignee,
|
||||
start_date: Date.new(2026, 1, 1),
|
||||
end_date: Date.new(2026, 1, 31),
|
||||
@@ -53,7 +53,7 @@ RSpec.describe ResourceAllocations::CreateService, type: :model do
|
||||
it "creates a resource allocation" do
|
||||
result = service_call
|
||||
expect(result).to be_success, "expected success but got: #{result.errors.full_messages}"
|
||||
expect(result.result.entity).to eq(planner)
|
||||
expect(result.result.entity).to eq(work_package)
|
||||
expect(result.result.principal).to eq(assignee)
|
||||
expect(result.result.allocated_time).to eq(8)
|
||||
end
|
||||
|
||||
+2
-2
@@ -35,9 +35,9 @@ RSpec.describe ResourceAllocations::DeleteService, type: :model do
|
||||
shared_let(:owner) do
|
||||
create(:user, member_with_permissions: { project => %i[view_resource_planners allocate_user_resources] })
|
||||
end
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let!(:resource_allocation) { create(:resource_allocation, entity: planner, principal: owner) }
|
||||
let!(:resource_allocation) { create(:resource_allocation, entity: work_package, principal: owner) }
|
||||
|
||||
subject(:service_call) do
|
||||
described_class.new(user:, model: resource_allocation).call
|
||||
|
||||
+5
-5
@@ -35,10 +35,10 @@ RSpec.describe ResourceAllocations::UpdateService, type: :model do
|
||||
shared_let(:owner) do
|
||||
create(:user, member_with_permissions: { project => %i[view_resource_planners allocate_user_resources] })
|
||||
end
|
||||
shared_let(:planner) { create(:resource_planner, project:, principal: owner) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
let!(:resource_allocation) do
|
||||
create(:resource_allocation, entity: planner, principal: owner, state: "requested", allocated_time: 8)
|
||||
create(:resource_allocation, entity: work_package, principal: owner, state: "requested", allocated_time: 8)
|
||||
end
|
||||
|
||||
subject(:service_call) do
|
||||
@@ -52,13 +52,13 @@ RSpec.describe ResourceAllocations::UpdateService, type: :model do
|
||||
end
|
||||
|
||||
context "when attempting to change the entity" do
|
||||
let(:other_planner) { create(:resource_planner, project:, principal: owner) }
|
||||
let(:other_work_package) { create(:work_package, project:) }
|
||||
|
||||
it "fails because entity is not writable" do
|
||||
result = described_class.new(user: owner, model: resource_allocation).call(entity: other_planner)
|
||||
result = described_class.new(user: owner, model: resource_allocation).call(entity: other_work_package)
|
||||
expect(result).not_to be_success
|
||||
expect(result.errors.symbols_for(:entity_id)).to include(:error_readonly)
|
||||
expect(resource_allocation.reload.entity).to eq(planner)
|
||||
expect(resource_allocation.reload.entity).to eq(work_package)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user