Procurement Director Jobs (No ghost jobs, direct from employers)
- 211 companies
- $170K median pay
Open procurement director jobs from company career pages and job boards, checked every hour. Every posting shows pay where listed, how long it has been up, company reviews and whether it looks like a ghost job.
284 jobs found
- 14 hours agoLow ghost riskDanfossOn-siteLjubljana, ObÄina Å alovci$100K - 150KLead
- 14 hours agoLow ghost riskKoch Industries, Inc.HybridAtlanta, GA$180K - 250KExecutive
- 5 days agoLow ghost riskSchneider ElectricOn-siteNashville, TN$208K - 312KLead
- 1 week agoModerate ghost riskDanone SAOn-siteHoofddorp, Provincie Noord-Holland$120K - 180KExecutive
- 2 days agoLow ghost riskS&B Engineers and ConstructorsHybridMemorial Office (West), TX$150K - 200KLead
- 1 day agoLow ghost riskMcCormickOn-siteHunt Valley, MD$141K - 239KLead
- 1 week agoLow ghost riskUnilever PLCOn-siteRotterdam - Hofplein 19$109K - 148KLead
- 5 days agoLow ghost riskUnilever PLCOn-siteRotterdam - Hofplein 19$109K - 148KLead
- 1 week agoLow ghost riskUnilever PLCOn-siteRotterdam - Hofplein 19$109K - 149KLead
- 1 week agoLow ghost riskMaceHybridRiyadh$120K - 200KExecutive
- 5 days agoLow ghost riskDiversified EnergyOn-siteHouston, TX$220K - 300KExecutive
- 1 week agoLow ghost riskPUBLICIS GROUPEHybridNew York, NY$160K - 180KLead
- 1 day agoLow ghost riskNative American Rehabilitation Association of the Northwest, Inc.On-sitePortland, OR$130K - 150KLead
- 6 days agoLow ghost riskAzaOn-siteBaltimore, MD$71K - 92KLead
- 5 days agoLow ghost riskUnilever PLCOn-siteRotterdam - Hofplein 19$109K - 148KLead
- 1 week agoLow ghost riskNational AquariumOn-siteBaltimore, MD$71K - 92KLead
- 1 week agoModerate ghost riskXplornet CommunicationsOn-siteMarkham, ON$150K - 200KExecutive
- 1 week agoLow ghost riskAon CorporationOn-siteSingapore$120K - 200KLead
- 14 hours agoLow ghost riskNorth Carolina A & T State UniversityHybridGreensboro, NC$80K - 110KLead
- 14 hours agoLow ghost riskMorningstar Inc.HybridChicago, IL$166K - 254KLead
Why this job matches
See how well your resume fits this job and what it is missing.
Job Description
Inject the flag directly into the main browser window and the session. window.isSFInternalUser = true; sessionStorage.setItem('SF_Internal_User', 'true');
/
- JOB PAGE ENHANCEMENT SCRIPT
- This script intercepts the default SAP SuccessFactors job layout, extracts key job metadata
- (tokens), evaluates business rules to show/hide specific data (like salary), and rebuilds
- the UI into a modern, 3-column green grid with custom icons.
- /
document.addEventListener('DOMContentLoaded', function () {
- PAGE VALIDATION
Ensure this script only runs on the actual Job Description page, not search or home pages. if (document.querySelector('.job-page')) { let columnOne = document.querySelector('.jobColumnOne'); if (!columnOne) return; Exit if the primary content column is missing
- TITLE EXTRACTION & REFORMATTING
Find the native job title element, extract its text, and remove the native wrapper. let titleElement = columnOne.querySelector('span[itemprop="title"]'); let titleText = titleElement ? titleElement.textContent.trim() : '';
if (titleElement) { Locate the highest level wrapper SAP generated for the title and remove it let titleWrapper = titleElement.closest('.joblayouttoken') || titleElement.closest('.inner'); if (titleWrapper) titleWrapper.remove(); }
Inject the new custom HTML structure at the very top of the job column. This includes the new H1 title and the empty green container/grid for the tokens. columnOne.insertAdjacentHTML('afterbegin', `
${titleText}
`);
Reference to the newly injected grid where we will move the tokens let grid = columnOne.querySelector('.job-grid');
- TOKEN GATHERING
SAP dynamically wraps tokens in different ways (.inner, .joblayouttoken, etc.). We find all labels first, then traverse up the DOM to capture the full container for each. let labels = Array.from(columnOne.querySelectorAll('.joblayouttoken-label')); let allTokens = labels.map(label =>label.closest('.inner') || label.closest('.joblayouttoken') || label.parentElement.parentElement); allTokens = [...new Set(allTokens)]; Remove duplicates to prevent processing the same element twice
Variables to store extracted data for our business rules let jobLocationValue = ""; let countryRegionValue = ""; let regionValue = ""; let jobLevelValue = ""; let employeeGroupValue = "";
- PHASE 1: DATA EXTRACTION (SILENT)
Loop through all tokens just to read their values. We do not move or alter them yet. allTokens.forEach(el =>{ if(!el) return; let labelEl = el.querySelector('.joblayouttoken-label'); if (!labelEl) return;
let labelText = labelEl.innerText; Use our helper function to normalize the label name regardless of the user's language let propId = generatePropertyId(labelText);
let valueEl = el.querySelector('.rtltextaligneligible'); let valueText = valueEl ? valueEl.textContent.trim() : "";
Populate the business rule variables based on the identified property ID if (propId === "JobLocation") jobLocationValue = valueText; else if (propId === "CountryRegion") countryRegionValue = valueText; else if (propId === "Region") regionValue = valueText; else if (propId === "JobLevel") jobLevelValue = valueText.toUpperCase().trim(); else if (propId === "EmployeeGroup") employeeGroupValue = valueText.toLowerCase(); });
- DATA FALLBACK: JOB DESCRIPTION PARSING
If SAP failed to generate specific tokens, attempt to regex them directly out of the raw description text. let descDiv = document.querySelector('.job-description') || columnOne.querySelector('[itemprop="description"]'); if (descDiv) { let descText = descDiv.innerText || ""; if (!employeeGroupValue) { let eg = descText.match(/Employee Group:\s*([^\n]+)/i); if (eg) employeeGroupValue = eg[1].toLowerCase().trim(); } if (!countryRegionValue) { let cr = descText.match(/Country:\s*([^\n]+)/i); if (cr) countryRegionValue = cr[1].toLowerCase().trim(); } if (!regionValue) { let rg = descText.match(/Region:\s*([^\n]+)/i); if (rg) regionValue = rg[1].toLowerCase().trim(); } if (!jobLevelValue) { let jl = descText.match(/Job Level:\s*([^\n]+)/i); Ensure we don't accidentally grab "Job Experience Level" if (jl && !jl[0].toLowerCase().includes("experience")) { jobLevelValue = jl[1].toUpperCase().trim(); } } }
- BUSINESS RULES EVALUATION (SALARY VISIBILITY)
Rule A: Is it a salaried position? let isSalaryPaid = employeeGroupValue.includes('salary');
Rule B: Is the Job Level eligible? (Hide if PL09 or higher, or if ZZ) let isJobLevelSalaryEligible = true; let plMatch = jobLevelValue.match(/PL(\d+)/); if (jobLevelValue.includes("ZZ")) { isJobLevelSalaryEligible = false; } else if (plMatch && parseInt(plMatch[1], 10) >= 9) { isJobLevelSalaryEligible = false; }
Combine city/state and country to check geographic eligibility let fullLocationLower = (jobLocationValue + " " + countryRegionValue).toLowerCase(); let isUSJob = fullLocationLower.includes("usa") || fullLocationLower.includes("united states") || /\bus\b/.test(fullLocationLower);
Define specifically allowed locations for salary transparency compliance let allowedUSStates = ["massachusetts", "illinois", "colorado", "minnesota", "new york", "oregon", "ohio", "california", "washington", "nevada", "maryland", "new jersey"]; let allowedUSAbbr = ["ma", "il", "co", "mn", "ny", "or", "oh", "ca", "wa", "nv", "md", "nj", "wdc", "dc"]; let allowedGlobalCountries = ["canada"];
let isSalaryLocationValid = false;
Rule C: Is the location mandated to show salary? if (isUSJob) { isSalaryLocationValid = allowedUSStates.some(state =>fullLocationLower.includes(state)) || allowedUSAbbr.some(abbr =>new RegExp(`\\b${abbr}\\b`).test(fullLocationLower)); } else { isSalaryLocationValid = allowedGlobalCountries.some(country =>fullLocationLower.includes(country)); }
Final Boolean: All three rules must pass to display the salary range token let showSalaryRange = isSalaryLocationValid && isJobLevelSalaryEligible && isSalaryPaid;
- PHASE 2: UI MANIPULATION & DOM INJECTION
Loop through all tokens again. This time, we apply styles, icons, and move them into the new grid. allTokens.forEach(el =>{ try { Skip processing if this container accidentally holds the main job description if (!el || el.querySelector('[itemprop="description"]')) return;
let labelEl = el.querySelector('.joblayouttoken-label'); if (!labelEl) return;
let labelText = labelEl.innerText; let customPropertyid = generatePropertyId(labelText);
Hide utility tokens that were only needed for backend business logic calculations if (["EmployeeGroup", "CountryRegion", "Region", "JobLevel"].includes(customPropertyid)) { el.style.display = 'none'; return; }
Hide the salary token if it failed the business rule check if (customPropertyid === "SalaryRange" && !showSalaryRange) { el.style.display = 'none'; return; }
TEXT OVERRIDES: Force specific terminology on the frontend if (customPropertyid === "SalaryRange" && labelText.toLowerCase().includes("recruitment salary range")) { labelEl.innerText = labelText.replace(/Recruitment Salary Range/ig, "Annual Salary Range"); } Cleanup messy abbreviations from SAP if (labelText.includes("(Short)")) { labelEl.innerText = labelText.replace(/\(Short\)/g, "(s)"); }
BRUTE FORCE STYLING: Override SAP's native CSS for the Labels labelEl.style.setProperty('font-weight', 'bold', 'important'); labelEl.style.setProperty('font-size', '14px', 'important'); labelEl.style.setProperty('color', '#303030', 'important'); labelEl.style.setProperty('font-family', 'Verdana, Geneva, sans-serif', 'important');
BRUTE FORCE STYLING: Override SAP's native CSS for the Values let valueEl = el.querySelector('.rtltextaligneligible'); if (valueEl) { valueEl.style.setProperty('font-weight', 'normal', 'important'); valueEl.style.setProperty('font-size', '15px', 'important'); valueEl.style.setProperty('color', '#303030', 'important'); valueEl.style.setProperty('font-family', 'Verdana, Geneva, sans-serif', 'important'); }
ICON MAPPING: Assign specific Glyphicons or custom image URLs based on the token type let icon = ''; let iconUrl = '';
switch (customPropertyid) { case 'JobLocation': icon = 'glyphicon-map-marker'; break; case 'BusinessUnit': icon = 'glyphicon-briefcase'; break; case 'JobCategory': icon = 'glyphicon-dashboard'; break; case 'EmploymentType': icon = 'glyphicon-star-empty'; break; case 'ReqID': icon = 'glyphicon-calendar'; break; case 'WorkLocationType': icon = 'glyphicon-paste'; break; case 'JobExperienceLevel': iconUrl = 'https:rmkcdn.successfactors.com/ef55fddb/b4cf3492-3cb2-4e29-a914-3.png'; break; case 'SalaryRange': iconUrl = 'https:rmkcdn.successfactors.com/ef55fddb/3bd06ab1-e5dc-4e72-abcb-3.png'; break; case 'TAPartner': iconUrl = 'https:rmkcdn.successfactors.com/ef55fddb/66a66419-f8c9-4f0e-b94a-6.png'; break; }
Generate the HTML for the icon (either a span for a font-icon or an img tag) let iconHtml = ''; if (icon) { iconHtml = ``; } else if (iconUrl) { iconHtml = ``; }
SAFE DOM MANIPULATION Wrap the existing token and the new icon inside a flexible flexbox row container. We use DOM traversal (previousElementSibling) to avoid using dynamic ID queries which can fail if SAP injects weird characters. let containerToWrap = labelEl.parentElement; if(containerToWrap) { containerToWrap.insertAdjacentHTML('beforebegin', `${iconHtml}`); let wrapper = containerToWrap.previousElementSibling; if(wrapper) { wrapper.appendChild(containerToWrap); Clean up residual text nodes (like line breaks) to prevent unwanted spacing in the layout let nextNode = wrapper.nextSibling; if(nextNode && nextNode.nodeType === 3) nextNode.remove(); } }
Finally, move the fully formatted token element into our custom green grid grid.appendChild(el); } catch (err) { If a specific token is malformed by SAP, log the error but allow the loop to continue formatting the rest of the tokens console.error("Erro ao processar o token:", err); } });
- FINAL CLEANUP
Remove the raw text strings from the bottom of the job description that were used as data fallbacks if (descDiv) { let descHtml = descDiv.i
Company Reviews
Employee reviews3.8
1,964 Reviews- Devops Engineer2026-03-09T04:45:45.667
Pros: good work life balance , good work
Cons: very less salary increment, Late promotions
- Sales Engineer Graduate Program2026-03-04T15:20:00.063
Pros: Danfoss offers a good two years training program. However, after the program they do not hire as an employee.
Cons: Too many meetings in the company !
- Good Luck2026-03-03T14:02:05.220
Pros: Very Diverse Workforce A/C controlled environment
Cons: Inconsistent workload Can’t guarantee 40 hours a week
Related jobs by role, place and level
Every page lists all openings, with likely ghost jobs flagged. Checked every hour.
Frequently asked questions about procurement director jobs
What is a ghost job?
A ghost job is a posting for a role the company isn't actively hiring for. Some stay up after the company fills the role. Others collect resumes for an opening that may never come.
You spend an evening tailoring an application, and nobody on the other side is looking for you.
What do procurement director jobs pay?
Across the postings here that list pay, the median is about $170,000 a year. About 4 in 10 of these postings list a pay range; the rest show an estimate, marked as one.
How many procurement director jobs are there?
At our last count there were about 280 open postings from about 210 employers. The list on this page is live, so the count above it is the current one.
How does Enhancv spot a ghost job?
Every posting goes through five checks: how long it has been up, whether a salary is posted, what employees say about the company, how specific the description is and whether it lists real requirements. Each job shows the checks it passed and the ones it didn't.
How fresh are the job postings?
Enhancv checks postings every hour, and every job shows how long ago it went up. Most hires happen within 30 days of posting. A listing that's been open for two months raises its ghost job risk.
















