diff --git a/config/ucpath_fields.yml b/config/ucpath_fields.yml index 9ead35f..3151ac2 100644 --- a/config/ucpath_fields.yml +++ b/config/ucpath_fields.yml @@ -205,3 +205,8 @@ job: jpath: "$.position.percentOfFullTime" dbdef: "VARCHAR(16)" status: OPTIONAL + + - name: percent_of_fulltime_job + jpath: "$.percentOfFullTime" + dbdef: "VARCHAR(16)" + status: OPTIONAL \ No newline at end of file diff --git a/lib/ucpath/jobs.rb b/lib/ucpath/jobs.rb index 9f4f8ca..d6fa9c6 100644 --- a/lib/ucpath/jobs.rb +++ b/lib/ucpath/jobs.rb @@ -105,8 +105,13 @@ def valid_org_relationship?(j) end # 4. The job will be ineligible if the percentage of full time is zero + # Note - percentage of full time can appear in 2 different places (ugh) def positive_full_time?(j) - j.percent_of_fulltime.to_f.positive? + values = [] + values << j.percent_of_fulltime if j.respond_to?(:percent_of_fulltime) + values << j.percent_of_fulltime_job if j.respond_to?(:percent_of_fulltime_job) + + values.any? { |v| v.to_f.positive? } end def find_priority_jobs(job_list) diff --git a/lib/ucpath/user.rb b/lib/ucpath/user.rb index 9db9742..c25d7f2 100644 --- a/lib/ucpath/user.rb +++ b/lib/ucpath/user.rb @@ -144,12 +144,13 @@ def create_user_record job_code = jobs.job.job_code || nil priority_job = Config.check_ucpath_code('priority_job_codes', job_code) percent_of_fulltime = jobs.job.percent_of_fulltime.to_f + percent_of_fulltime_job = jobs.job.percent_of_fulltime_job.to_f # PERCENT OF FULL TIME CHECK # AP-559 If an employee is in a position that is 0 FTE, # their record should should be filtered out from the UCPath files. - if percent_of_fulltime.zero? - logger.info "#{id} - Ineligible: Percentage of Full Time Check: #{percent_of_fulltime}" + if percent_of_fulltime.zero? && percent_of_fulltime_job.zero? + logger.info "#{id} - Ineligible: Percentage of Full Time Check" return nil end diff --git a/spec/data/ucpath/10725310_jobs.json b/spec/data/ucpath/10725310_jobs.json new file mode 100644 index 0000000..3bd4a45 --- /dev/null +++ b/spec/data/ucpath/10725310_jobs.json @@ -0,0 +1,144 @@ +{ + "source": "UCB-HR-PATH-DB", + "correlationId": "8c37994b-3132-4f04-98b0-5ecdc96aec79", + "timeStamp": "2026-02-03", + "httpStatus": { + "code": "200", + "description": "OK" + }, + "response": [ + { + "identifiers": [ + { + "type": "hr-employee-id", + "id": "10725310" + }, + { + "type": "campus-uid", + "id": "10725310" + } + ], + "jobs": [ + { + "number": 0, + "sequence": 0, + "type": { + "code": "4", + "description": "Staff: Limited" + }, + "classification": { + "code": "1", + "description": "Professional & Support Staff" + }, + "position": { + "number": "41195443", + "department": { + "code": "KPADM", + "description": "Library Administration" + }, + "description": "LIBRARY AST 2", + "pool": { + }, + "percentOfFullTime": 0.000000, + "employeeRelationship": { + "code": "E", + "description": "All Others, Not Confidential" + }, + "jobCode": { + "code": { + "code": "006760", + "description": "LIBRARY AST 2" + }, + "status": { + "code": "A", + "description": "Active" + }, + "family": { + "code": "J092", + "description": "Library Services" + }, + "function": { + "code": "442", + "description": "Library" + }, + "flsaEligibilty": { + }, + "representation": { + "union": { + "code": "CX", + "description": "Clerical & Allied Services" + } + } + }, + "reportsTo": { + "position": { + "number": "40145569" + } + }, + "status": { + "code": "A", + "description": "Approved" + }, + "headCount": { + "active": 1, + "maximum": 1, + "status": { + "code": "F", + "description": "Filled" + } + }, + "active": { + "code": "A", + "description": "Active" + }, + "fromDate": "2025-08-22" + }, + "organizationRelationship": { + "code": "EMP", + "description": "Employee" + }, + "flsaEligibilty": { + "code": "N", + "description": "Nonexempt" + }, + "percentOfFullTime": 0.000000, + "fullTimeStatus": { + "code": "X", + "description": "Fixed" + }, + "permanent": { + "code": "R", + "description": "Not Applicable" + }, + "primary": { + "code": "P", + "description": "Primary Job" + }, + "department": { + "code": "KPADM", + "description": "Library Administration" + }, + "location": { + "code": "10467", + "description": "Bancroft Lib (Doe Anx)-F03-94704" + }, + "status": { + "hrStatus": { + "code": "A", + "description": "Active" + }, + "employmentStatus": { + "code": "A", + "description": "Active" + }, + "benefitsStatus": { + "code": "A", + "description": "Active" + } + }, + "fromDate": "2025-10-28" + } + ] + } + ] +} diff --git a/spec/data/ucpath/10725310_user.json b/spec/data/ucpath/10725310_user.json new file mode 100644 index 0000000..a9ea906 --- /dev/null +++ b/spec/data/ucpath/10725310_user.json @@ -0,0 +1,51 @@ +{ + "source": "UCB-HR-PATH-DB", + "correlationId": "23d9a1b2-7f7a-4693-8a88-051a7d3c5caa", + "timeStamp": "2026-02-18", + "httpStatus": { + "code": "200", + "description": "OK" + }, + "response": [ + { + "identifiers": [ + { + "type": "hr-employee-id", + "id": "10725310" + }, + { + "type": "campus-uid", + "id": "1926706" + }, + { + "type": "calnet-id", + "id": "hptruong" + }, + { + "type": "campus-solutions-id", + "id": "3040792017" + } + ], + "names": [ + { + "type": { + "code": "PRF", + "description": "Preferred" + }, + "familyName": "Familyname", + "givenName": "Givenname", + "fromDate": "2024-03-26" + }, + { + "type": { + "code": "PRI", + "description": "Primary" + }, + "familyName": "Familyname", + "givenName": "Givenname", + "fromDate": "2024-03-26" + } + ] + } + ] +} diff --git a/spec/lib/ucpath_spec.rb b/spec/lib/ucpath_spec.rb index 0caf694..d30ff63 100644 --- a/spec/lib/ucpath_spec.rb +++ b/spec/lib/ucpath_spec.rb @@ -586,6 +586,15 @@ u = UCPath::User.new(id) expect(u.eligible?).to be(false) end + + it 'skips with zero Percentage of FullTime' do + id = '10725310' + stub_ucpath_user(id) + stub_ucpath_jobs(id) + + u = UCPath::User.new(id) + expect(u.eligible?).to be(false) + end end describe UCPath::Jobs do @@ -603,7 +612,7 @@ def eligible(job) # Minimal structs to stand in for your “job” objects let(:choose_job_struct) { Struct.new(:expected_end_date) } let(:eligible_job_struct) do - Struct.new(:hr_status_code, :expected_end_date, :org_relationship_code, :job_code, :percent_of_fulltime) + Struct.new(:hr_status_code, :expected_end_date, :org_relationship_code, :job_code, :percent_of_fulltime, :percent_of_fulltime_job) end describe '#choose_job' do @@ -652,7 +661,7 @@ def eligible(job) end context "when hr_status_code is not 'A'" do - let(:job) { eligible_job_struct.new('I', '', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('I', '', '', 'ANY', 1.0, 0.0) } it 'returns false' do expect(eligible(job)).to be(false) @@ -660,7 +669,7 @@ def eligible(job) end context "when hr_status_code is 'A' and expected_end_date is blank" do - let(:job) { eligible_job_struct.new('A', '', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('A', '', '', 'ANY', 1.0, 0.0) } it 'returns true' do expect(eligible(job)).to be(true) @@ -668,7 +677,7 @@ def eligible(job) end context "when hr_status_code is 'A' and expected_end_date is after today" do - let(:job) { eligible_job_struct.new('A', '2026-03-01', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('A', '2026-03-01', '', 'ANY', 1.0, 0.0) } it 'returns true' do expect(eligible(job)).to be(true) @@ -676,7 +685,7 @@ def eligible(job) end context "when hr_status_code is 'A' and expected_end_date is today" do - let(:job) { eligible_job_struct.new('A', '2026-02-23', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('A', '2026-02-23', '', 'ANY', 1.0, 0.0) } it 'returns false (<= today is not eligible per implementation)' do expect(eligible(job)).to be(false) @@ -684,7 +693,7 @@ def eligible(job) end context "when hr_status_code is 'A' and expected_end_date is before today" do - let(:job) { eligible_job_struct.new('A', '2026-02-01', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('A', '2026-02-01', '', 'ANY', 1.0, 0.0) } it 'returns false' do expect(eligible(job)).to be(false) @@ -692,7 +701,7 @@ def eligible(job) end context 'when org_relationship_code is blank and other conditions pass' do - let(:job) { eligible_job_struct.new('A', '', '', 'ANY', 1.0) } + let(:job) { eligible_job_struct.new('A', '', '', 'ANY', 1.0, 0.0) } it 'returns true' do expect(eligible(job)).to be(true) @@ -700,7 +709,7 @@ def eligible(job) end context "when org_relationship_code is 'CWR' and job_code is NOT in either allowlist" do - let(:job) { eligible_job_struct.new('A', '', 'CWR', 'NOT_ALLOWED', 1.0) } + let(:job) { eligible_job_struct.new('A', '', 'CWR', 'NOT_ALLOWED', 1.0, 0.0) } before do allow(Config).to receive(:check_ucpath_code) @@ -718,7 +727,7 @@ def eligible(job) end context "when org_relationship_code is 'CWR' and job_code IS in visiting_scholar_job_code" do - let(:job) { eligible_job_struct.new('A', '', 'CWR', 'ALLOWED', 1.0) } + let(:job) { eligible_job_struct.new('A', '', 'CWR', 'ALLOWED', 1.0, 0.0) } before do allow(Config).to receive(:check_ucpath_code) @@ -736,7 +745,7 @@ def eligible(job) end context "when org_relationship_code is 'CWR' and job_code IS in ucb_academic_dept_affiliate_code" do - let(:job) { eligible_job_struct.new('A', '', 'CWR', 'ALLOWED', 1.0) } + let(:job) { eligible_job_struct.new('A', '', 'CWR', 'ALLOWED', 1.0, 0.0) } before do allow(Config).to receive(:check_ucpath_code)