From e1f337284031e07641fe2f9dc93ef2043715fcf7 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Wed, 23 Sep 2026 17:51:15 -0400 Subject: [PATCH 01/14] Add files via upload --- examples/promotion-nomination/README.MD | 49 +++ examples/promotion-nomination/example.json | 5 + .../model/DefaultPhoto.attachment | 13 + .../ManagerPromotionNomination.securitydomain | 6 + .../model/NominateEmployee.task | 10 + .../PromotionNominationBO.businessobject | 149 ++++++++ .../PromotionNominationBP.businessprocess | 25 ++ .../model/PromotionNominations.report | 101 +++++ .../presentation/eventDetails.pmd | 160 ++++++++ .../presentation/managerNomination.pmd | 354 ++++++++++++++++++ .../promotionNomination_rvylxm.amd | 42 +++ .../promotionNomination_rvylxm.smd | 21 ++ 12 files changed, 935 insertions(+) create mode 100644 examples/promotion-nomination/README.MD create mode 100644 examples/promotion-nomination/example.json create mode 100644 examples/promotion-nomination/model/DefaultPhoto.attachment create mode 100644 examples/promotion-nomination/model/ManagerPromotionNomination.securitydomain create mode 100644 examples/promotion-nomination/model/NominateEmployee.task create mode 100644 examples/promotion-nomination/model/PromotionNominationBO.businessobject create mode 100644 examples/promotion-nomination/model/PromotionNominationBP.businessprocess create mode 100644 examples/promotion-nomination/model/PromotionNominations.report create mode 100644 examples/promotion-nomination/presentation/eventDetails.pmd create mode 100644 examples/promotion-nomination/presentation/managerNomination.pmd create mode 100644 examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd create mode 100644 examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.MD new file mode 100644 index 0000000..482dd88 --- /dev/null +++ b/examples/promotion-nomination/README.MD @@ -0,0 +1,49 @@ +# Promotion Nomination App + +A streamlined promotion nomination platform that enables managers to identify, evaluate, and advocate for top-performing direct reports. + +## What it is + +The Promotion Nomination App automates the collection of employee career data and routes nominations through a multi-level approval workflow. It ensures consistent evaluation, transparency, and governance across all promotion decisions by handling everything from employee selection through final approval and archival. + +## What's inside + +**Core components:** +- **managerNomination**: Landing page where managers submit and track nominations +- **eventDetails**: Business process approval page definition for multi-level review + +**Key capabilities:** +- Employee selection from direct reports with intuitive dropdown interface +- Auto-populated career data (current job profile, tenure, hire date, promotion history) +- Optional integration with Workday Talent details or third-party platforms +- Structured fields for nomination rationale and target role alignment +- Promotion cycle tracking (e.g., 2026-Q1) +- Multi-level approval routing to next-level managers and People Business Partner +- Secure storage and audit trail via Custom Business Object + +## How to use it + +**1. Select Employee** +Manager selects an eligible worker from their list of direct reports. + +**2. Auto-Populate Career History** +The system automatically pulls: +- Current job profile and time in position +- Original hire date +- Last promotion date + +**3. Build the Nomination** +Manager provides promotion justification with: +- Reason for promotion (merit, increased responsibilities, restructuring) +- Target role (proposed job profile) +- Timeline (proposed effective date) +- Business impact statement + +**4. Route Through Approvals** +The nomination enters the multi-level approval chain. Once all approvals are granted, data is archived to the Custom Business Object. + +## Before you deploy + +- Update the app base URL in the AMD file with your app name +- Optionally configure Workday Orchestrate integration to automatically trigger the Change Job business process upon final approval +- Test the approval workflow with test users at each level diff --git a/examples/promotion-nomination/example.json b/examples/promotion-nomination/example.json new file mode 100644 index 0000000..68bed35 --- /dev/null +++ b/examples/promotion-nomination/example.json @@ -0,0 +1,5 @@ +{ + "title": "Promotion Nomination", + "description": "Workday App for managers to nominate their direct reports for promotions", + "type": "Extend App" +} diff --git a/examples/promotion-nomination/model/DefaultPhoto.attachment b/examples/promotion-nomination/model/DefaultPhoto.attachment new file mode 100644 index 0000000..b7730ee --- /dev/null +++ b/examples/promotion-nomination/model/DefaultPhoto.attachment @@ -0,0 +1,13 @@ +{ + "id": 1, + "name": "DefaultPhoto", + "label": "Default Photo", + "defaultSecurityDomains": [ + "ManagerPromotionNomination" + ], + "defaultCollection": { + "name": "defaultPhotos", + "label": "Default Photos" + }, + "fields": [] +} \ No newline at end of file diff --git a/examples/promotion-nomination/model/ManagerPromotionNomination.securitydomain b/examples/promotion-nomination/model/ManagerPromotionNomination.securitydomain new file mode 100644 index 0000000..ba44c22 --- /dev/null +++ b/examples/promotion-nomination/model/ManagerPromotionNomination.securitydomain @@ -0,0 +1,6 @@ +{ + "id": 1, + "name": "ManagerPromotionNomination", + "enabledForPageSecurity": true, + "enabledForOrchestrationSecurity": false +} \ No newline at end of file diff --git a/examples/promotion-nomination/model/NominateEmployee.task b/examples/promotion-nomination/model/NominateEmployee.task new file mode 100644 index 0000000..e01d788 --- /dev/null +++ b/examples/promotion-nomination/model/NominateEmployee.task @@ -0,0 +1,10 @@ +{ + "id": 1, + "name": "NominateEmployee", + "label": "Nominate Employee", + "securityDomains": [ + "ManagerPromotionNomination" + ], + "businessObject": "PromotionNominationBO", + "routePath": "/" +} \ No newline at end of file diff --git a/examples/promotion-nomination/model/PromotionNominationBO.businessobject b/examples/promotion-nomination/model/PromotionNominationBO.businessobject new file mode 100644 index 0000000..cc34647 --- /dev/null +++ b/examples/promotion-nomination/model/PromotionNominationBO.businessobject @@ -0,0 +1,149 @@ +{ + "id": 1, + "name": "PromotionNominationBO", + "label": "Promotion Nomination B O", + "defaultSecurityDomains": [ + "ManagerPromotionNomination" + ], + "defaultCollection": { + "name": "promotionNominationBOS", + "label": "Promotion Nomination B O S" + }, + "fields": [ + { + "enableReportingFromTarget": false, + "secureByTarget": true, + "target": "WORKER", + "isPurgeable": false, + "useForDisplay": false, + "type": "SINGLE_INSTANCE", + "name": "nominee", + "id": 1 + }, + { + "enableReportingFromTarget": false, + "secureByTarget": true, + "target": "WORKER", + "isPurgeable": false, + "useForDisplay": false, + "type": "SINGLE_INSTANCE", + "name": "nominator", + "id": 2 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "label": "Nomination Reason", + "type": "TEXT", + "name": "nominationReason", + "id": 3 + }, + { + "precision": "DAY", + "enableIndex": false, + "isPurgeable": false, + "type": "DATE", + "name": "proposedDate", + "id": 4 + }, + { + "enableReportingFromTarget": false, + "secureByTarget": false, + "target": "JOB_PROFILE", + "isPurgeable": false, + "useForDisplay": false, + "type": "SINGLE_INSTANCE", + "name": "proposedJobProfile", + "id": 5 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "promotionCycle", + "id": 6 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "timeInJobProfile", + "id": 7 + }, + { + "precision": "DAY", + "enableIndex": false, + "isPurgeable": false, + "type": "DATE", + "name": "hireDate", + "id": 8 + }, + { + "enableReportingFromTarget": false, + "secureByTarget": false, + "target": "JOB_PROFILE", + "isPurgeable": false, + "useForDisplay": false, + "type": "SINGLE_INSTANCE", + "name": "currentJobProfile", + "id": 9 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "currentJobTitle", + "id": 10 + }, + { + "precision": "DAY", + "enableIndex": false, + "isPurgeable": false, + "type": "DATE", + "name": "lastPromotionDate", + "id": 11 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "workerConsideration", + "id": 12 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "businessNeed", + "id": 13 + }, + { + "enableSearch": false, + "enableIndex": false, + "isPurgeable": false, + "useForDisplay": false, + "isReferenceId": false, + "type": "TEXT", + "name": "workerSuitRole", + "id": 14 + } + ] +} \ No newline at end of file diff --git a/examples/promotion-nomination/model/PromotionNominationBP.businessprocess b/examples/promotion-nomination/model/PromotionNominationBP.businessprocess new file mode 100644 index 0000000..39dd80a --- /dev/null +++ b/examples/promotion-nomination/model/PromotionNominationBP.businessprocess @@ -0,0 +1,25 @@ +{ + "id": 1, + "name": "PromotionNominationBP", + "label": "Promotion Nomination BP", + "description": "Business Process Type for PromotionNominationBP", + "useForOrchestrate": false, + "allowsCancel": true, + "approvalStep": { + "pageRoute": "/eventDetails", + "actions": [ + "APPROVE", + "DENY" + ] + }, + "actionSteps": [], + "targetBusinessObject": "PromotionNominationBO", + "securityDomains": [ + "ManagerPromotionNomination" + ], + "details": { + "pageRoute": "/eventDetails/{eventId}" + }, + "allowsManualSendBack": false, + "allowsRescind": true +} \ No newline at end of file diff --git a/examples/promotion-nomination/model/PromotionNominations.report b/examples/promotion-nomination/model/PromotionNominations.report new file mode 100644 index 0000000..972caf2 --- /dev/null +++ b/examples/promotion-nomination/model/PromotionNominations.report @@ -0,0 +1,101 @@ +{ + "id": 1, + "name": "PromotionNominations", + "businessObject": "PromotionNominationBO", + "defaultSecurityDomains": [ + "ManagerPromotionNomination" + ], + "sortConfigs": [], + "columns": [ + { + "id": 1, + "name": "PromotionNominationBO", + "order": "a", + "isSelfReferencing": true + }, + { + "id": 2, + "name": "Nominee", + "order": "b", + "field": "nominee" + }, + { + "id": 3, + "name": "Nominator", + "order": "c", + "field": "nominator" + }, + { + "id": 4, + "name": "Nomination Reason", + "order": "d", + "field": "nominationReason" + }, + { + "id": 5, + "name": "Proposed Date", + "order": "e", + "field": "proposedDate" + }, + { + "id": 6, + "name": "Proposed Job Profile", + "order": "f", + "field": "proposedJobProfile" + }, + { + "id": 7, + "name": "Promotion Cycle", + "order": "g", + "field": "promotionCycle" + }, + { + "id": 8, + "name": "Time In Job Profile", + "order": "h", + "field": "timeInJobProfile" + }, + { + "id": 9, + "name": "Hire Date", + "order": "i", + "field": "hireDate" + }, + { + "id": 10, + "name": "Current Job Profile", + "order": "j", + "field": "currentJobProfile" + }, + { + "id": 11, + "name": "Current Job Title", + "order": "k", + "field": "currentJobTitle" + }, + { + "id": 12, + "name": "Last Promotion Date", + "order": "l", + "field": "lastPromotionDate" + }, + { + "id": 13, + "name": "Worker Consideration", + "order": "m", + "field": "workerConsideration" + }, + { + "id": 15, + "name": "Worker Suit Role", + "order": "o", + "field": "workerSuitRole" + }, + { + "id": 16, + "name": "Business Need", + "order": "p", + "field": "businessNeed" + } + ] +} \ No newline at end of file diff --git a/examples/promotion-nomination/presentation/eventDetails.pmd b/examples/promotion-nomination/presentation/eventDetails.pmd new file mode 100644 index 0000000..b0a4eae --- /dev/null +++ b/examples/promotion-nomination/presentation/eventDetails.pmd @@ -0,0 +1,160 @@ +{ + "id": "eventDetails", + "securityDomains": [ + "ManagerPromotionNomination" + ], + "endPoints": [ + { + "name": "eventGenericBP", + "baseUrlType": "workday-bp", + "url": "<% 'events/{{queryParams.eventId}}' %>", + "authType": "sso", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + }, + { + "name": "getPromotionNominationBP", + "baseUrlType": "app", + "url": "<% 'promotionNominationBOS/{{eventGenericBP.for.id}}' %>", + "authType": "sso", + "httpMethod": "GET", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + } + ], + "presentation": { + "title": { + "type": "title", + "label": "View Promotion Nomination Details" + }, + "body": { + "type": "section", + "horizontal": false, + "children": [ + { + "enabled": false, + "type": "text", + "label": "Nominee", + "id": "nomineeWidget", + "value": "<% empty getPromotionNominationBP.nominee.descriptor ? 'No Nominee value found' : getPromotionNominationBP.nominee.descriptor %>" + }, + { + "enabled": false, + "type": "text", + "label": "Nominator", + "id": "nominatorWidget", + "value": "<% empty getPromotionNominationBP.nominator.descriptor ? 'No Nominator value found' : getPromotionNominationBP.nominator.descriptor %>" + }, + { + "type": "fieldSet", + "children": [ + { + "type": "text", + "id": "nominationReasonWidget", + "label": "Nomination Reason", + "value": "<% empty getPromotionNominationBP.nominationReason ? 'No Nominator value found' : getPromotionNominationBP.nominationReason %>" + }, + { + "visible": true, + "type": "date", + "id": "proposedDateWidget", + "label": "Proposed Date", + "value": "<% empty getPromotionNominationBP.proposedDate ? null : getPromotionNominationBP.proposedDate %>" + }, + { + "enabled": false, + "type": "text", + "label": "Proposed Job Profile", + "id": "proposedJobProfileWidget", + "value": "<% empty getPromotionNominationBP.proposedJobProfile.descriptor ? 'No Nominator value found' : getPromotionNominationBP.proposedJobProfile.descriptor %>" + }, + { + "type": "text", + "id": "promotionCycleWidget", + "label": "Promotion Cycle", + "value": "<% empty getPromotionNominationBP.promotionCycle ? 'No Nominator value found' : getPromotionNominationBP.promotionCycle %>" + }, + { + "type": "text", + "id": "timeInJobProfileWidget", + "label": "Time in Job Profile", + "value": "<% empty getPromotionNominationBP.timeInJobProfile ? 'No Nominator value found' : getPromotionNominationBP.timeInJobProfile %>" + }, + { + "type": "date", + "id": "hireDateeWidget", + "label": "Hire Date", + "value": "<% empty getPromotionNominationBP.hireDate ? null : getPromotionNominationBP.hireDate %>" + }, + { + "type": "text", + "id": "currentJobProfileWidget", + "label": "Current Job Profile", + "value": "<% empty getPromotionNominationBP.currentJobProfile.descriptor ? 'No Nominator value found' : getPromotionNominationBP.currentJobProfile.descriptor %>" + }, + { + "type": "text", + "id": "currentJobTitleWidget", + "label": "Current Job Title", + "value": "<% empty getPromotionNominationBP.currentJobTitle ? 'No Nominator value found' : getPromotionNominationBP.currentJobTitle %>" + }, + { + "visible": true, + "type": "date", + "id": "lastPromotionDateWidget", + "label": "Last Promotion Date", + "value": "<% empty getPromotionNominationBP.lastPromotionDate ? null : getPromotionNominationBP.lastPromotionDate %>" + }, + { + "type": "date", + "id": "hireDateWidget", + "label": "Employee Hire Date", + "value": "<% empty getPromotionNominationBP.hireDate ? null : getPromotionNominationBP.hireDate %>" + }, + { + "type": "textArea", + "id": "workerConsiderationWidget", + "label": "Why do you consicder the worker?", + "value": "<% empty getPromotionNominationBP.workerConsideration ? 'No Nominator value found' : getPromotionNominationBP.workerConsideration %>" + }, + { + "type": "textArea", + "id": "businessNeedWidget", + "label": "What is the business need?", + "value": "<% empty getPromotionNominationBP.businessNeed ? 'No Nominator value found' : getPromotionNominationBP.businessNeed %>" + }, + { + "type": "textArea", + "id": "workerSuitRoleWidget", + "label": "Does the Worker suit the role?", + "value": "<% empty getPromotionNominationBP.workerSuitRole ? 'No Nominator value found' : getPromotionNominationBP.workerSuitRole %>" + } + ] + } + ] + }, + "footer": { + "type": "footer", + "children": [ + { + "type": "richText", + "enabled": "false", + "value": "Powered By Workday Extend" + } + ] + }, + "pageType": "VIEW" + } +} \ No newline at end of file diff --git a/examples/promotion-nomination/presentation/managerNomination.pmd b/examples/promotion-nomination/presentation/managerNomination.pmd new file mode 100644 index 0000000..d43f076 --- /dev/null +++ b/examples/promotion-nomination/presentation/managerNomination.pmd @@ -0,0 +1,354 @@ +{ + "id": "managerNomination", + "securityDomains": [ + "ManagerPromotionNomination" + ], + "endPoints": [ + { + "name": "me", + "baseUrlType": "workday-staffing", + "url": "/workers/me", + "authType": "sso" + }, + { + "_comment": "Retrieves logged-in manager's direct reports ", + "name": "getEmployeeList", + "baseUrlType": "workday-wql", + "authType": "sso", + "url": "<% + var query= '/data?query=' + string:urlEncode('SELECT worker FROM myDirectReports WHERE workerType in (d588c41a446c11de98360015c5e6daf6)'); + return query; + %>" + }, + { + "name": "workerInfo", + "url": "https://api.workday.com/common/v1/workers/me", + "authType": "sso" + }, + { + "_comment": "Retrieves selected worker information ", + "deferred": true, + "name": "getEmployeeData", + "baseUrlType": "workday-wql", + "authType": "sso", + "url": "<% + var query= '/data?query=' + string:urlEncode('SELECT worker, location, manager_Level01, hireDate, timeInJobProfile, jobProfile, jobTitle, lastPromotionDate FROM myDirectReports WHERE worker in \"' +selectedWorkerIdQuery+ '\"'); + return query; + %>" + }, + { + "name": "getJobProfiles", + "baseUrlType": "workday-wql", + "authType": "sso", + "url": "<% + var query= '/data?query=' + string:urlEncode('SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter) WHERE jobProfileName startswith \"' +instanceListQuery+ '\"'); + // console.info('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXz', query); + return query.replace('%2B','%20'); + %>", + "deferred": true + }, + { + "name": "getJobProfilesNoSearch", + "baseUrlType": "workday-wql", + "authType": "sso", + "url": "<% + var query= '/data?query=' + string:urlEncode('SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter)'); + // console.info('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXz', query); + return query.replace('%2B','%20'); + %>" + } + ], + "presentation": { + "title": { + "type": "title", + "label": "Employee Promotion Nomination" + }, + "body": { + "type": "fieldSet", + "horizontal": false, + "children": [ + { + "type": "section", + "children": [ + { + "type": "readOnlyText", + "id": "welcome", + "value": "Welcome to the Promotion Nomination App. Please select an employee to begin" + }, + { + "horizontal": true, + "type": "section", + "verticalAlignment": "top", + "children": [ + { + "required": true, + "type": "instanceList", + "label": "Select an Employee", + "id": "selectedEmployeeWidget", + "displayKey": "worker.descriptor", + "idKey": "worker.id", + "values": "<% getEmployeeList.data %>", + "onChange": "<% + if (!empty selectedEmployeeWidget.value[0]) //Retrieve selected employee information + { + + var responseEmpData =getEmployeeData.invoke({'selectedWorkerIdQuery': selectedEmployeeWidget.value[0]}); + + + var timeInJobProfileVar =(!empty responseEmpData.data[0].timeInJobProfile) ? responseEmpData.data[0].timeInJobProfile : null; + var currentJobProfileVar = (!empty responseEmpData.data[0].jobProfile.descriptor) ? responseEmpData.data[0].jobProfile.descriptor: null; + var currentJobProfileIDVar = (!empty responseEmpData.data[0].jobProfile.id) ? responseEmpData.data[0].jobProfile.id: null; + var currentJobProfileResponseValue = [{'id': currentJobProfileIDVar, 'descriptor': currentJobProfileVar}]; + var rawHireDateVar = (!empty responseEmpData.data[0].hireDate) ? responseEmpData.data[0].hireDate: null; + var parsedHireDateVar = date:parse(rawHireDateVar, 'yyyy-MM-dd'); + var jobTitleVar = (!empty responseEmpData.data[0].jobTitle) ? responseEmpData.data[0].jobTitle: null; + var rawLastPromotionDateVar = (!empty responseEmpData.data[0].lastPromotionDate) ? responseEmpData.data[0].lastPromotionDate: null; + var parsedLastPromotionDateVar = date:parse(rawLastPromotionDateVar, 'yyyy-MM-dd'); + + + promotionReasonWidget.visible = true; + proposedDateWidget.visible = true; + proposedDateWidget.visible = true; + proposedJobProfileWidget.visible = true; + promotionCycleWidget.visible = true; + + timeJobProfileWidget.value = timeInJobProfileVar; + timeJobProfileWidget.visible = true; + + currentJobProfileWidget.setValues(currentJobProfileResponseValue); + currentJobProfileWidget.setValue(currentJobProfileResponseValue.map(item => {item.id})); + currentJobProfileWidget.visible = true; + + hireDateWidget.value = parsedHireDateVar; + hireDateWidget.visible = true; + + currentJobTitleWidget.value = jobTitleVar; + currentJobTitleWidget.visible = true; + + lastPromotionDateWidget.value = parsedLastPromotionDateVar; + lastPromotionDateWidget.visible = true; + + promotionConsiderationQ1.visible = true; + promotionBusNeedQ2.visible = true; + roleConsiderationQ3.visible = true; + } + else + { + self.setError('Please select a worker to nominate'); + } %>" + } + ], + "columnWidthRatios": [ + "0.75", + "0.25" + ] + }, + { + "required": true, + "visible": false, + "type": "radioGroup", + "id": "promotionReasonWidget", + "label": "Reason for Promotion", + "instanceList": [ + { + "id": "100A", + "descriptor": "Merit/Performance" + }, + { + "id": "101B", + "descriptor": "Increased Responsibilities" + }, + { + "id": "102C", + "descriptor": "Organizational Restructuring" + } + ] + }, + { + "required": true, + "visible": false, + "label": "Proposed Effective Date", + "type": "date", + "id": "proposedDateWidget" + }, + { + "visible": false, + "label": "Proposed Job Profile", + "type": "instanceList", + "id": "proposedJobProfileWidget", + "values": "<% getJobProfilesNoSearch.data %>", + "displayKey": "jobProfileName", + "idKey": "workdayID" + } + ] + }, + { + "type": "section", + "id": "derivedFieldsSection", + "children": [ + { + "enabled": false, + "visible": false, + "label": "Promotion Cycle", + "type": "text", + "id": "promotionCycleWidget", + "value": "2026-Q1" + }, + { + "enabled": false, + "label": "Current Manager", + "visible": false, + "type": "text", + "id": "currentManagerWidget" + }, + { + "enabled": false, + "label": "Time in Job Profile", + "visible": false, + "type": "text", + "id": "timeJobProfileWidget" + }, + { + "enabled": false, + "label": "Hire Date", + "visible": false, + "type": "date", + "id": "hireDateWidget" + }, + { + "enabled": false, + "visible": false, + "label": "Current Job Profile", + "type": "instanceList", + "idKey": "id", + "id": "currentJobProfileWidget", + "values": "<% [] %>" + }, + { + "enabled": false, + "label": "Current Job Title", + "visible": false, + "type": "text", + "id": "currentJobTitleWidget" + }, + { + "enabled": false, + "label": "Date of Last Promotion", + "visible": false, + "type": "date", + "id": "lastPromotionDateWidget" + }, + { + "required": true, + "visible": false, + "type": "textArea", + "id": "promotionConsiderationQ1", + "label": "Why is the worker being considered?" + }, + { + "required": true, + "visible": false, + "type": "textArea", + "id": "promotionBusNeedQ2", + "label": "What is the business need?" + }, + { + "required": true, + "visible": false, + "type": "textArea", + "id": "roleConsiderationQ3", + "label": "How does this worker suit the role?" + } + ] + } + ] + }, + "footer": { + "type": "footer", + "children": [ + { + "enabled": "false", + "type": "richText" + } + ] + }, + "pageType": "EDIT" + }, + "outboundData": { + "outboundEndPoints": [ + { + "name": "storePromotionNominationBO", + "baseUrlType": "app", + "url": "promotionNominationBOS", + "httpMethod": "POST", + "authType": "sso", + "onSend": "<% + //Outbound endpoint that submits the BO + + + var obj = {:}; + + obj.add('nominee', (!empty selectedEmployeeWidget.selectedEntries[0].id) ? {'id':selectedEmployeeWidget.selectedEntries[0].id} : null); + obj.add('nominator', (!empty me.id) ? {'id':me.id} : null); + obj.add('nominationReason', (!empty promotionReasonWidget.selectedEntries[0].descriptor) ? promotionReasonWidget.selectedEntries[0].descriptor : null); + obj.add('proposedDate', (!empty proposedDateWidget.value) ? date:format(proposedDateWidget.value, 'yyyy-MM-dd') : null); + obj.add('proposedJobProfile', (!empty proposedJobProfileWidget.selectedEntries[0].id) ? {'id':proposedJobProfileWidget.selectedEntries[0].id} : null); + obj.add('promotionCycle', (!empty promotionCycleWidget.value) ? promotionCycleWidget.value : null); + obj.add('timeInJobProfile', (!empty timeJobProfileWidget.value) ? timeJobProfileWidget.value : null); + obj.add('hireDate', (!empty hireDateWidget.value) ? date:format(hireDateWidget.value, 'yyyy-MM-dd') : null); + obj.add('currentJobProfile', (!empty currentJobProfileWidget.selectedEntries[0].id) ? {'id':currentJobProfileWidget.selectedEntries[0].id} : null); + obj.add('currentJobTitle', (!empty currentJobTitleWidget.value) ? currentJobTitleWidget.value : null); + obj.add('lastPromotionDate', (!empty lastPromotionDateWidget.value) ? date:format(lastPromotionDateWidget.value, 'yyyy-MM-dd') : '2020-01-01'); + obj.add('workerConsideration', (!empty promotionConsiderationQ1.value) ? promotionConsiderationQ1.value : null); + obj.add('businessNeed', (!empty promotionBusNeedQ2.value) ? promotionBusNeedQ2.value : null); + obj.add('workerSuitRole', (!empty roleConsiderationQ3.value) ? roleConsiderationQ3.value : null); + self.data = obj; + console.info('Nomination payload is ' + json:asJSON(self.data)); + return self.data; + %>", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + }, + { + "name": "submitPromotion", + "baseUrlType": "app", + "url": "<% 'https://api.workday.com/apps/promotionNomination_rvylxm/v1/promotionNominationBPEvents' %>", + "authType": "sso", + "onSend": "<% + var payload = + { + 'description': 'Approval for Promotion Nomination', + 'businessProcessTarget': {'id': storePromotionNominationBO.id} + }; + console.info('zzzzzzzzzzz ' + payload.businessProcessTarget); + return payload; + %>", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + }, + { + "type": "outboundVariable", + "name": "ExtensionResponse", + "variableScope": "flow", + "values": [ + { + "outboundPath": "PromotionSubmissionResponseValue", + "value": "<% submitPromotion.id %>" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd new file mode 100644 index 0000000..730aa59 --- /dev/null +++ b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd @@ -0,0 +1,42 @@ +{ + "appProperties": [ ], + "tasks": [ + { + "id": "managerNomination", + "routingPattern": "/", + "page": { + "id": "managerNomination" + } + }, + { + "id": "eventDetails", + "routingPattern": "/eventDetails/{eventId}", + "page": { + "id": "eventDetails" + } + } + ], + "applicationId": "promotionNomination_rvylxm", + "dataProviders": [ + { + "key": "workday-staffing", + "value": "https://api.workday.com/staffing/v7/" + }, + { + "key": "workday-wql", + "value": "https://api.us.wcp.workday.com/wql/v1" + }, + { + "key": "workday-common", + "value": "https://api.us.wcp.workday.com/common/v1" + }, + { + "key": "workday-bp", + "value": "https://api.workday.com/businessProcess/v1/" + }, + { + "key": "app", + "value": "https://api.workday.com/apps/promotionNomination_rvylxm/v1/" + } + ] +} \ No newline at end of file diff --git a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd new file mode 100644 index 0000000..2137d71 --- /dev/null +++ b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd @@ -0,0 +1,21 @@ +{ + "id": "promotionNomination_rvylxm", + "languages": [ + { + "code": "en-US", + "description": "English" + } + ], + "siteAuth": { + "authTypes": [ + { + "scheme": "SSO", + "id": "sso" + } + ] + }, + "siteId": "promotionNomination_rvylxm", + "cdnEnabled": true, + "siteProperties": [ ], + "applicationId": "promotionNomination_rvylxm" +} \ No newline at end of file From 76056aafdfe4233fbd1d98c871d64a9f4d316045 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:06:45 -0400 Subject: [PATCH 02/14] Revise README for Promotion Nomination App Updated the README to clarify the purpose and functionality of the Promotion Nomination App, including detailed usage instructions and key capabilities. --- examples/promotion-nomination/README.MD | 54 +++++++++---------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.MD index 482dd88..04b46dd 100644 --- a/examples/promotion-nomination/README.MD +++ b/examples/promotion-nomination/README.MD @@ -1,49 +1,33 @@ -# Promotion Nomination App - -A streamlined promotion nomination platform that enables managers to identify, evaluate, and advocate for top-performing direct reports. +# Promotion Nomination ## What it is -The Promotion Nomination App automates the collection of employee career data and routes nominations through a multi-level approval workflow. It ensures consistent evaluation, transparency, and governance across all promotion decisions by handling everything from employee selection through final approval and archival. +A Workday Extend app that lets a manager nominate one of their direct reports for promotion. The manager picks an employee, sees career details pulled from Workday, adds a justification, and submits. The nomination is saved to a custom business object and sent into a business process for approval. A second page shows a submitted nomination as read-only details when someone opens the business process event. + +It is a good reference if you want to see WQL-driven form prefill, a two-step submit (save an object, then start a business process on it), and a VIEW page driven by a business process event ID. ## What's inside -**Core components:** -- **managerNomination**: Landing page where managers submit and track nominations -- **eventDetails**: Business process approval page definition for multi-level review +- `presentation/managerNomination.pmd`: the landing page (route `/`). Employee picker, prefilled career fields, nomination form, and the submit logic. +- `presentation/eventDetails.pmd`: the read-only page (route `/eventDetails/{eventId}`). Loads the business process event, follows it to the saved nomination, and displays it. +- `presentation/promotionNomination_rvylxm.amd`: the app definition. Declares the two routes and the five data providers (`workday-staffing`, `workday-wql`, `workday-common`, `workday-bp`, `app`), all built on `apiGatewayEndpoint`. -**Key capabilities:** -- Employee selection from direct reports with intuitive dropdown interface -- Auto-populated career data (current job profile, tenure, hire date, promotion history) -- Optional integration with Workday Talent details or third-party platforms -- Structured fields for nomination rationale and target role alignment -- Promotion cycle tracking (e.g., 2026-Q1) -- Multi-level approval routing to next-level managers and People Business Partner -- Secure storage and audit trail via Custom Business Object +Both pages use the `ManagerPromotionNomination` security domain. ## How to use it -**1. Select Employee** -Manager selects an eligible worker from their list of direct reports. - -**2. Auto-Populate Career History** -The system automatically pulls: -- Current job profile and time in position -- Original hire date -- Last promotion date - -**3. Build the Nomination** -Manager provides promotion justification with: -- Reason for promotion (merit, increased responsibilities, restructuring) -- Target role (proposed job profile) -- Timeline (proposed effective date) -- Business impact statement +1. **Pick an employee.** The list comes from a WQL query on `myDirectReports`. +2. **Review the prefilled data.** Once an employee is chosen, the page fills in current job profile, current job title, time in job profile, hire date and last promotion date. These fields are read-only. +3. **Complete the nomination.** Choose a reason (Merit/Performance, Increased Responsibilities, or Organizational Restructuring), a proposed effective date and a proposed job profile. The promotion cycle is set to the current year. Then answer three required questions: why the worker is being considered, what the business need is, and how the worker suits the role. +4. **Submit.** The page posts the nomination to the `promotionNominationBOS` business object, then posts to `promotionNominationBPEvents` with that object as the business process target. The new event ID is returned to the flow as `PromotionSubmissionResponseValue`. +5. **View the result.** Opening the business process event lands on `eventDetails`, which shows the nominee, nominator, reason, dates, job profiles and the three written answers. -**4. Route Through Approvals** -The nomination enters the multi-level approval chain. Once all approvals are granted, data is archived to the Custom Business Object. +To test, sign in as a manager with direct reports and run through the steps above. Also try an employee with no hire date, and a submission with no proposed job profile selected. Both should still submit. ## Before you deploy -- Update the app base URL in the AMD file with your app name -- Optionally configure Workday Orchestrate integration to automatically trigger the Change Job business process upon final approval -- Test the approval workflow with test users at each level +- **App reference id.** The `app` data provider in `presentation/promotionNomination_rvylxm.amd` has the id `promotionNomination_rvylxm` in its URL, and the `.amd` file name carries it too. Replace it with the id your tenant generates when you import the app. +- **Worker type ID.** The `getEmployeeList` endpoint in `managerNomination.pmd` limits direct reports to one worker type, using the ID `d588c41a446c11de98360015c5e6daf6`. That ID is specific to the tenant this example came from. Replace it with the worker type you want, or remove the filter. +- **Security domain.** Map `ManagerPromotionNomination` to the security groups that should submit and view nominations, then activate pending security policy changes. +- **Business object and business process.** The app expects a `promotionNominationBOS` business object and a `promotionNominationBPEvents` business process. Approval steps, such as routing to a next-level manager or a People Business Partner, are set up in the business process definition, not in these page files. Configure them for your own approval chain. +- **Last promotion date fallback.** When a worker has no last promotion date, `managerNomination.pmd` uses `2020-01-01`. Change that default in the employee `onChange` if it does not suit your data. From 88c958f4135673211908b0531a5cce2f6d823251 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:07:26 -0400 Subject: [PATCH 03/14] Enhance description in example.json for clarity Updated the description to provide more detailed information about the promotion nomination process. --- examples/promotion-nomination/example.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/promotion-nomination/example.json b/examples/promotion-nomination/example.json index 68bed35..4e55cfa 100644 --- a/examples/promotion-nomination/example.json +++ b/examples/promotion-nomination/example.json @@ -1,5 +1,5 @@ { "title": "Promotion Nomination", - "description": "Workday App for managers to nominate their direct reports for promotions", + "description": "A manager nominates a direct report for promotion. Career data is prefilled from Workday, the nomination is saved to a business object, and a business process routes it for approval.", "type": "Extend App" } From 52c5fe19b4ddf857bd14cf80c229814d4b5dda32 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:16:08 -0400 Subject: [PATCH 04/14] Update README with app reference ID and deployment notes Clarified app reference ID details and updated instructions for tenant-specific configurations. --- examples/promotion-nomination/README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.MD index 04b46dd..85da078 100644 --- a/examples/promotion-nomination/README.MD +++ b/examples/promotion-nomination/README.MD @@ -26,7 +26,7 @@ To test, sign in as a manager with direct reports and run through the steps abov ## Before you deploy -- **App reference id.** The `app` data provider in `presentation/promotionNomination_rvylxm.amd` has the id `promotionNomination_rvylxm` in its URL, and the `.amd` file name carries it too. Replace it with the id your tenant generates when you import the app. +- **App reference id.** The id `promotionNomination_rvylxm` appears in the `applicationId` field of the `.amd`, in the `id`, `applicationId` and `siteId` fields of the `.smd`, in the `app` data provider URL in the `.amd`, and in the `.amd` and `.smd` file names. Replace it with the id your tenant generates on import, and keep the `.amd` and `.smd` values identical. - **Worker type ID.** The `getEmployeeList` endpoint in `managerNomination.pmd` limits direct reports to one worker type, using the ID `d588c41a446c11de98360015c5e6daf6`. That ID is specific to the tenant this example came from. Replace it with the worker type you want, or remove the filter. - **Security domain.** Map `ManagerPromotionNomination` to the security groups that should submit and view nominations, then activate pending security policy changes. - **Business object and business process.** The app expects a `promotionNominationBOS` business object and a `promotionNominationBPEvents` business process. Approval steps, such as routing to a next-level manager or a People Business Partner, are set up in the business process definition, not in these page files. Configure them for your own approval chain. From 61fe617b3943dcb3eb201272386f0cf7afb62435 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:16:41 -0400 Subject: [PATCH 05/14] Update README with site definition and security domain info Added details about the site definition and security domain usage. --- examples/promotion-nomination/README.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.MD index 85da078..2418ed3 100644 --- a/examples/promotion-nomination/README.MD +++ b/examples/promotion-nomination/README.MD @@ -11,6 +11,7 @@ It is a good reference if you want to see WQL-driven form prefill, a two-step su - `presentation/managerNomination.pmd`: the landing page (route `/`). Employee picker, prefilled career fields, nomination form, and the submit logic. - `presentation/eventDetails.pmd`: the read-only page (route `/eventDetails/{eventId}`). Loads the business process event, follows it to the saved nomination, and displays it. - `presentation/promotionNomination_rvylxm.amd`: the app definition. Declares the two routes and the five data providers (`workday-staffing`, `workday-wql`, `workday-common`, `workday-bp`, `app`), all built on `apiGatewayEndpoint`. +- `presentation/promotionNomination_rvylxm.smd`: the site definition. Holds the app and site ids, the language, and the SSO authentication scheme. Both pages use the `ManagerPromotionNomination` security domain. From f6462f38b46d7dbf45cec79c7dacc71ca6833a97 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:17:40 -0400 Subject: [PATCH 06/14] Refactor API endpoints in promotionNomination_rvylxm Updated API endpoint values to use dynamic gateway endpoint. --- .../presentation/promotionNomination_rvylxm.amd | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd index 730aa59..dbc7a4d 100644 --- a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd +++ b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd @@ -1,5 +1,5 @@ { - "appProperties": [ ], + "appProperties": [], "tasks": [ { "id": "managerNomination", @@ -16,27 +16,27 @@ } } ], - "applicationId": "promotionNomination_rvylxm", +"applicationId": "promotionNomination_rvylxm", "dataProviders": [ { "key": "workday-staffing", - "value": "https://api.workday.com/staffing/v7/" + "value": "<% apiGatewayEndpoint + '/staffing/v7/' %>" }, { "key": "workday-wql", - "value": "https://api.us.wcp.workday.com/wql/v1" + "value": "<% apiGatewayEndpoint + '/wql/v1' %>" }, { "key": "workday-common", - "value": "https://api.us.wcp.workday.com/common/v1" + "value": "<% apiGatewayEndpoint + '/common/v1' %>" }, { "key": "workday-bp", - "value": "https://api.workday.com/businessProcess/v1/" + "value": "<% apiGatewayEndpoint + '/businessProcess/v1/' %>" }, { "key": "app", - "value": "https://api.workday.com/apps/promotionNomination_rvylxm/v1/" + "value": "<% apiGatewayEndpoint + '/apps/promotionNomination_rvylxm/v1/' %>" } ] -} \ No newline at end of file +} From 5439ab62b5f513993a4083972a9561d50a530eb7 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:18:09 -0400 Subject: [PATCH 07/14] Refactor promotionNomination_rvylxm.smd JSON structure Removed duplicate siteId and applicationId fields, and adjusted JSON structure. --- .../presentation/promotionNomination_rvylxm.smd | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd index 2137d71..67a7573 100644 --- a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd +++ b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.smd @@ -1,5 +1,7 @@ { "id": "promotionNomination_rvylxm", + "applicationId": "promotionNomination_rvylxm", + "siteId": "promotionNomination_rvylxm", "languages": [ { "code": "en-US", @@ -13,9 +15,5 @@ "id": "sso" } ] - }, - "siteId": "promotionNomination_rvylxm", - "cdnEnabled": true, - "siteProperties": [ ], - "applicationId": "promotionNomination_rvylxm" -} \ No newline at end of file + } +} From 33ce6f1721f5060db5c0e68629df3a7704e35e70 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:18:31 -0400 Subject: [PATCH 08/14] Enhance managerNomination.pmd with new endpoints and error handling Updated the manager nomination presentation file to include additional failOnStatusCodes and modified query construction for various endpoints. --- .../presentation/managerNomination.pmd | 224 +++++++++--------- 1 file changed, 118 insertions(+), 106 deletions(-) diff --git a/examples/promotion-nomination/presentation/managerNomination.pmd b/examples/promotion-nomination/presentation/managerNomination.pmd index d43f076..6bc3f98 100644 --- a/examples/promotion-nomination/presentation/managerNomination.pmd +++ b/examples/promotion-nomination/presentation/managerNomination.pmd @@ -7,77 +7,162 @@ { "name": "me", "baseUrlType": "workday-staffing", + "authType": "sso", "url": "/workers/me", - "authType": "sso" + "failOnStatusCodes": [{"code": 400}, {"code": 403}] }, - { - "_comment": "Retrieves logged-in manager's direct reports ", - "name": "getEmployeeList", + { + "name": "getEmployeeList", "baseUrlType": "workday-wql", "authType": "sso", "url": "<% - var query= '/data?query=' + string:urlEncode('SELECT worker FROM myDirectReports WHERE workerType in (d588c41a446c11de98360015c5e6daf6)'); + let query= `/data?query={{string:urlEncode'SELECT worker FROM myDirectReports WHERE workerType in (d588c41a446c11de98360015c5e6daf6)'}}` return query; - %>" + %>", + "failOnStatusCodes": [{"code": 400}, {"code": 403}] }, { - "name": "workerInfo", - "url": "https://api.workday.com/common/v1/workers/me", - "authType": "sso" - }, + "deferred": true, + "name": "getEmployeeData", + "baseUrlType": "workday-wql", + "authType": "sso", + "url": "<% return '/data?query=' + string:urlEncode(`SELECT worker, location, manager_Level01, hireDate, timeInJobProfile, jobProfile, jobTitle, lastPromotionDate FROM myDirectReports WHERE worker in \"{{selectedWorkerIdQuery}}\"`); %>", + "failOnStatusCodes": [{"code": 400}, {"code": 403}] +}, { - "_comment": "Retrieves selected worker information ", "deferred": true, - "name": "getEmployeeData", - "baseUrlType": "workday-wql", - "authType": "sso", - "url": "<% - var query= '/data?query=' + string:urlEncode('SELECT worker, location, manager_Level01, hireDate, timeInJobProfile, jobProfile, jobTitle, lastPromotionDate FROM myDirectReports WHERE worker in \"' +selectedWorkerIdQuery+ '\"'); - return query; - %>" - }, - { "name": "getJobProfiles", "baseUrlType": "workday-wql", "authType": "sso", "url": "<% - var query= '/data?query=' + string:urlEncode('SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter) WHERE jobProfileName startswith \"' +instanceListQuery+ '\"'); + let query= '/data?query=' + string:urlEncode(`SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter) WHERE jobProfileName startswith \"{{instanceListQuery}}\"`); // console.info('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXz', query); return query.replace('%2B','%20'); %>", - "deferred": true + + "failOnStatusCodes": [{"code": 400}, {"code": 403}] }, { "name": "getJobProfilesNoSearch", "baseUrlType": "workday-wql", "authType": "sso", "url": "<% - var query= '/data?query=' + string:urlEncode('SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter)'); + const query= `/data?query={{string:urlEncode'SELECT jobProfileName, workdayID FROM allActiveJobProfiles (dataSourceFilter = defaultFilter)'}}`; // console.info('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXz', query); return query.replace('%2B','%20'); - %>" + %>", + "failOnStatusCodes": [{"code": 400}, {"code": 403}] } ], + "outboundData": { + "outboundEndPoints": [ + { + "name": "storePromotionNominationBO", + "baseUrlType": "app", + "authType": "sso", + "url": "promotionNominationBOS", + "httpMethod": "POST", + + "onSend": "<% + //Outbound endpoint that submits the BO + + let obj = {:}; + + // Required or always-present values, no guards needed + obj.add('nominee', {'id': selectedEmployeeWidget.selectedEntries[0].id}); + obj.add('nominator', {'id': me.id}); + obj.add('nominationReason', promotionReasonWidget.selectedEntries[0].descriptor); + obj.add('proposedDate', date:format(proposedDateWidget.value, 'yyyy-MM-dd')); + obj.add('workerConsideration', promotionConsiderationQ1.value); + obj.add('businessNeed', promotionBusNeedQ2.value); + obj.add('workerSuitRole', roleConsiderationQ3.value); + + // Derived text values, passed through as-is + obj.add('promotionCycle', promotionCycleWidget.value); + obj.add('timeInJobProfile', timeJobProfileWidget.value); + obj.add('currentJobTitle', currentJobTitleWidget.value); + obj.add('lastPromotionDate', date:format(lastPromotionDateWidget.value, 'yyyy-MM-dd')); + + // Only the values that can legitimately be empty keep a guard + obj.add('hireDate', (!empty hireDateWidget.value) ? date:format(hireDateWidget.value, 'yyyy-MM-dd') : null); + obj.add('currentJobProfile', (!empty currentJobProfileWidget.selectedEntries[0].id) ? {'id': currentJobProfileWidget.selectedEntries[0].id} : null); + obj.add('proposedJobProfile', (!empty proposedJobProfileWidget.selectedEntries[0].id) ? {'id': proposedJobProfileWidget.selectedEntries[0].id} : null); + + self.data = obj; + return self.data; + + + %>", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + }, + { + "name": "submitPromotion", + "baseUrlType": "app", + "authType": "sso", + "url": "<% 'promotionNominationBPEvents' %>", + + "onSend": "<% + let payload = + { + 'description': 'Approval for Promotion Nomination', + 'businessProcessTarget': {'id': storePromotionNominationBO.id} + }; + //console.info('zzzzzzzzzzz ' + payload.businessProcessTarget); + return payload; + %>", + "failOnStatusCodes": [ + { + "code": 400 + }, + { + "code": 403 + } + ] + }, + { + "type": "outboundVariable", + "name": "ExtensionResponse", + "variableScope": "flow", + "values": [ + { + "outboundPath": "PromotionSubmissionResponseValue", + "value": "<% submitPromotion.id %>" + } + ] + } + ] + }, "presentation": { "title": { "type": "title", + "id": "mainTitleLabel", "label": "Employee Promotion Nomination" }, "body": { "type": "fieldSet", + "id": "fieldSetVertical", "horizontal": false, "children": [ { "type": "section", + "id": "section1", "children": [ { "type": "readOnlyText", - "id": "welcome", + "id": "welcomeLabel", "value": "Welcome to the Promotion Nomination App. Please select an employee to begin" }, { "horizontal": true, "type": "section", + "id": "section2", "verticalAlignment": "top", "children": [ { @@ -102,7 +187,7 @@ var rawHireDateVar = (!empty responseEmpData.data[0].hireDate) ? responseEmpData.data[0].hireDate: null; var parsedHireDateVar = date:parse(rawHireDateVar, 'yyyy-MM-dd'); var jobTitleVar = (!empty responseEmpData.data[0].jobTitle) ? responseEmpData.data[0].jobTitle: null; - var rawLastPromotionDateVar = (!empty responseEmpData.data[0].lastPromotionDate) ? responseEmpData.data[0].lastPromotionDate: null; + var rawLastPromotionDateVar = (!empty responseEmpData.data[0].lastPromotionDate) ? responseEmpData.data[0].lastPromotionDate : '2020-01-01'; var parsedLastPromotionDateVar = date:parse(rawLastPromotionDateVar, 'yyyy-MM-dd'); @@ -192,7 +277,7 @@ "label": "Promotion Cycle", "type": "text", "id": "promotionCycleWidget", - "value": "2026-Q1" + "value": "<% return date:year([TODAY_DATE]).toString(); %>" }, { "enabled": false, @@ -265,90 +350,17 @@ }, "footer": { "type": "footer", + "id": "footerTextID", "children": [ { - "enabled": "false", + "enabled": false, + "id": "footerrichTextID", "type": "richText" } ] }, "pageType": "EDIT" - }, - "outboundData": { - "outboundEndPoints": [ - { - "name": "storePromotionNominationBO", - "baseUrlType": "app", - "url": "promotionNominationBOS", - "httpMethod": "POST", - "authType": "sso", - "onSend": "<% - //Outbound endpoint that submits the BO - - - var obj = {:}; - - obj.add('nominee', (!empty selectedEmployeeWidget.selectedEntries[0].id) ? {'id':selectedEmployeeWidget.selectedEntries[0].id} : null); - obj.add('nominator', (!empty me.id) ? {'id':me.id} : null); - obj.add('nominationReason', (!empty promotionReasonWidget.selectedEntries[0].descriptor) ? promotionReasonWidget.selectedEntries[0].descriptor : null); - obj.add('proposedDate', (!empty proposedDateWidget.value) ? date:format(proposedDateWidget.value, 'yyyy-MM-dd') : null); - obj.add('proposedJobProfile', (!empty proposedJobProfileWidget.selectedEntries[0].id) ? {'id':proposedJobProfileWidget.selectedEntries[0].id} : null); - obj.add('promotionCycle', (!empty promotionCycleWidget.value) ? promotionCycleWidget.value : null); - obj.add('timeInJobProfile', (!empty timeJobProfileWidget.value) ? timeJobProfileWidget.value : null); - obj.add('hireDate', (!empty hireDateWidget.value) ? date:format(hireDateWidget.value, 'yyyy-MM-dd') : null); - obj.add('currentJobProfile', (!empty currentJobProfileWidget.selectedEntries[0].id) ? {'id':currentJobProfileWidget.selectedEntries[0].id} : null); - obj.add('currentJobTitle', (!empty currentJobTitleWidget.value) ? currentJobTitleWidget.value : null); - obj.add('lastPromotionDate', (!empty lastPromotionDateWidget.value) ? date:format(lastPromotionDateWidget.value, 'yyyy-MM-dd') : '2020-01-01'); - obj.add('workerConsideration', (!empty promotionConsiderationQ1.value) ? promotionConsiderationQ1.value : null); - obj.add('businessNeed', (!empty promotionBusNeedQ2.value) ? promotionBusNeedQ2.value : null); - obj.add('workerSuitRole', (!empty roleConsiderationQ3.value) ? roleConsiderationQ3.value : null); - self.data = obj; - console.info('Nomination payload is ' + json:asJSON(self.data)); - return self.data; - %>", - "failOnStatusCodes": [ - { - "code": 400 - }, - { - "code": 403 - } - ] - }, - { - "name": "submitPromotion", - "baseUrlType": "app", - "url": "<% 'https://api.workday.com/apps/promotionNomination_rvylxm/v1/promotionNominationBPEvents' %>", - "authType": "sso", - "onSend": "<% - var payload = - { - 'description': 'Approval for Promotion Nomination', - 'businessProcessTarget': {'id': storePromotionNominationBO.id} - }; - console.info('zzzzzzzzzzz ' + payload.businessProcessTarget); - return payload; - %>", - "failOnStatusCodes": [ - { - "code": 400 - }, - { - "code": 403 - } - ] - }, - { - "type": "outboundVariable", - "name": "ExtensionResponse", - "variableScope": "flow", - "values": [ - { - "outboundPath": "PromotionSubmissionResponseValue", - "value": "<% submitPromotion.id %>" - } - ] - } - ] } -} \ No newline at end of file + + +} From e87aa5f6c295cbda88e723c7a678595960a09d35 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:19:10 -0400 Subject: [PATCH 09/14] Update URL syntax and add IDs in eventDetails.pmd --- .../presentation/eventDetails.pmd | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/promotion-nomination/presentation/eventDetails.pmd b/examples/promotion-nomination/presentation/eventDetails.pmd index b0a4eae..3822984 100644 --- a/examples/promotion-nomination/presentation/eventDetails.pmd +++ b/examples/promotion-nomination/presentation/eventDetails.pmd @@ -7,7 +7,7 @@ { "name": "eventGenericBP", "baseUrlType": "workday-bp", - "url": "<% 'events/{{queryParams.eventId}}' %>", + "url": "<% `events/{{queryParams.eventId}}` %>", "authType": "sso", "failOnStatusCodes": [ { @@ -21,7 +21,7 @@ { "name": "getPromotionNominationBP", "baseUrlType": "app", - "url": "<% 'promotionNominationBOS/{{eventGenericBP.for.id}}' %>", + "url": "<% `promotionNominationBOS/{{eventGenericBP.for.id}}` %>", "authType": "sso", "httpMethod": "GET", "failOnStatusCodes": [ @@ -37,10 +37,12 @@ "presentation": { "title": { "type": "title", - "label": "View Promotion Nomination Details" + "label": "View Promotion Nomination Details", + "id": "titleHeading" }, "body": { "type": "section", + "id": "sectionID1", "horizontal": false, "children": [ { @@ -59,6 +61,7 @@ }, { "type": "fieldSet", + "id": "fieldSet1", "children": [ { "type": "text", @@ -147,14 +150,16 @@ }, "footer": { "type": "footer", + "id": "footerTextID1", "children": [ { "type": "richText", - "enabled": "false", + "id": "footerTextID", + "enabled": false, "value": "Powered By Workday Extend" } ] }, "pageType": "VIEW" } -} \ No newline at end of file +} From e32d2cc6f159c1ac1c6d77c31a698d58e4ce1110 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:19:29 -0400 Subject: [PATCH 10/14] Fix JSON formatting in eventDetails.pmd From 8fe7826aa7955fa58264b5290eea9ae136d15137 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:25:34 -0400 Subject: [PATCH 11/14] Replace hardcoded applicationId with site variable --- .../presentation/promotionNomination_rvylxm.amd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd index dbc7a4d..3dccbb1 100644 --- a/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd +++ b/examples/promotion-nomination/presentation/promotionNomination_rvylxm.amd @@ -16,7 +16,7 @@ } } ], -"applicationId": "promotionNomination_rvylxm", +"applicationId": site.applicationId, "dataProviders": [ { "key": "workday-staffing", From e0402f75326a72124db4437777d364e4175c64d4 Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sat, 3 Oct 2026 12:28:14 -0400 Subject: [PATCH 12/14] Update README with app reference ID instructions Clarified instructions for app reference ID and provided guidance for handling errors in the app builder. --- examples/promotion-nomination/README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.MD index 2418ed3..e2be148 100644 --- a/examples/promotion-nomination/README.MD +++ b/examples/promotion-nomination/README.MD @@ -27,7 +27,7 @@ To test, sign in as a manager with direct reports and run through the steps abov ## Before you deploy -- **App reference id.** The id `promotionNomination_rvylxm` appears in the `applicationId` field of the `.amd`, in the `id`, `applicationId` and `siteId` fields of the `.smd`, in the `app` data provider URL in the `.amd`, and in the `.amd` and `.smd` file names. Replace it with the id your tenant generates on import, and keep the `.amd` and `.smd` values identical. +- **App reference id.** If he site.applicationid throws an error on the app builder, please replace it with your application ID. - **Worker type ID.** The `getEmployeeList` endpoint in `managerNomination.pmd` limits direct reports to one worker type, using the ID `d588c41a446c11de98360015c5e6daf6`. That ID is specific to the tenant this example came from. Replace it with the worker type you want, or remove the filter. - **Security domain.** Map `ManagerPromotionNomination` to the security groups that should submit and view nominations, then activate pending security policy changes. - **Business object and business process.** The app expects a `promotionNominationBOS` business object and a `promotionNominationBPEvents` business process. Approval steps, such as routing to a next-level manager or a People Business Partner, are set up in the business process definition, not in these page files. Configure them for your own approval chain. From 0366ad34e258636b0b43a5a0e57a6db2a57a855d Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Wed, 7 Oct 2026 12:19:32 -0400 Subject: [PATCH 13/14] Fix README filename case sensitivity --- examples/promotion-nomination/{README.MD => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/promotion-nomination/{README.MD => README.md} (100%) diff --git a/examples/promotion-nomination/README.MD b/examples/promotion-nomination/README.md similarity index 100% rename from examples/promotion-nomination/README.MD rename to examples/promotion-nomination/README.md From c41cab29dd4fed89770d29d31e22a2f26924892f Mon Sep 17 00:00:00 2001 From: abhi-matti Date: Sun, 11 Oct 2026 16:35:49 -0400 Subject: [PATCH 14/14] Add promotion nomination example to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f0afc8e..b607ed5 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ Community examples, open to everyone. This is the section external contributions | [`expense-policy-agent-skill`](examples/expense-policy-agent-skill) | A markdown skill that teaches an agent to answer expense policy questions and escalate anything it cannot verify. | Agent Skill | | [`employee-data-orchestration`](examples/employee-data-orchestration) | An orchestration that reads worker data from one tenant and creates matching records through the Workday REST API. | Orchestration | | [`peer-kudos-home-card`](examples/peer-kudos-home-card) | An Extend app with a Home page card, custom business object, and orchestration that lets employees celebrate peer contributions and notifies managers. | Extend App | +| [`promotion-nomination`](examples/promotion-nomination) | A manager nominates a direct report for promotion. Career data is prefilled from Workday, the nomination is saved to a business object, and a business process routes it for approval. | Extend App | | [`pto-policy-agent-skill`](examples/pto-policy-agent-skill) | A markdown skill that teaches an agent to answer employee questions about PTO, leave accrual, rollover, and sick leave policy with clear escalation rules. | Agent Skill | | [`stock-notifications`](examples/stock-notifications) | An Extend app that fetches Workday's current stock price from an external API and displays it on a home page card. | Extend App | | [`wql-anniversary-celebrations`](examples/wql-anniversary-celebrations) | An orchestration that queries Workday via WQL for active workers celebrating milestone work anniversaries and formats an announcement digest payload for collaboration channels. | Orchestration |