diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/README.md new file mode 100644 index 00000000..947322ca --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/README.md @@ -0,0 +1,104 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Employee Add Dependents + +## Overview + +This topic enables employees to view their existing dependents and add new dependents to their Workday profile through a conversational interface. Dependents include spouses, domestic partners, children, and other family members who may be covered under employee benefits. + +## Features + +- View existing dependents with their relationship and date of birth +- Add new dependents (spouse, child, domestic partner, etc.) +- Dynamic relationship type dropdown populated from Workday reference data +- Confirmation flow showing summary of dependent details before submission +- Form validation for required fields + +## Snapshots + +![Add Dependents](add_dependents.png) + +## Trigger Phrases + +- "Add a dependent" +- "I want to add my child as a dependent" +- "Add my spouse to my benefits" +- "Register a new dependent" +- "I need to add a family member" +- "Show my dependents" + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Copilot Studio topic definition with conversation flow | +| `msdyn_HRWorkdayHCMEmployeeGetDependents.xml` | XML template for fetching existing dependents | +| `msdyn_HRWorkdayHCMEmployeeAddDependent.xml` | XML template for adding a new dependent | + +## Workday APIs Used + +| API | Purpose | +|-----|---------| +| `Human_Resources v45.0` | Fetch existing dependents | +| `Benefits_Administration v45.1` | Add new dependent | + +## Flow Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Triggers Topic │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Fetch Reference Data (Relationship Types) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Fetch Existing Dependents │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Display Existing Dependents (or "No dependents") │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Add Dependent Form (Adaptive Card) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Confirmation Card │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Submit to Workday │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Success/Error Message │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Configurations + +Environment makers need to configure the following in the topic: + +| Configuration | Description | Location in Topic | +|---------------|-------------|-------------------| +| **Gender Options** | Configure available gender options (Male, Female, Not_Declared) | Adaptive card dropdown | +| **Country Codes** | Define available country codes (USA, CAN, GBR, etc.) | Adaptive card dropdown | +| **Workday Icon** | Update the icon URL to match your organization's branding | Topic properties > Icon | +| **Workday URL** | Set your organization's Workday tenant URL | HTTP action or connector configuration | + +## Dependencies + +- **msdyn_HRWorkdayHCMEmployeeGetDependents template**: Required for fetching existing dependents +- **Employee Context**: Worker ID must be available in the conversation context diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/add_dependents.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/add_dependents.png new file mode 100644 index 00000000..a005d28a Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/add_dependents.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeAddDependent.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeAddDependent.xml new file mode 100644 index 00000000..02744f36 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeAddDependent.xml @@ -0,0 +1,65 @@ + + + + + User + + Template_AddDependentRequest + Benefits_Administration + v45.1 + + + + //*[local-name()='Dependent_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID']/text() + DependentWID + + + //*[local-name()='Dependent_Reference']/*[local-name()='ID' and @*[local-name()='type']='Dependent_ID']/text() + DependentID + + + + + + + + + + true + true + true + + Dependent added via Copilot + + {Employee_ID} + + + + + + {Employee_ID} + + + {Relationship_Type} + + true + + + + + {Country_Code} + + {First_Name} + {Last_Name} + + + {Date_Of_Birth} + + {Gender} + + + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeGetDependents.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeGetDependents.xml new file mode 100644 index 00000000..7a87290b --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/msdyn_HRWorkdayHCMEmployeeGetDependents.xml @@ -0,0 +1,104 @@ + + + + + User + + Template_GetDependentsRequest + Human_Resources + v42.0 + + + + + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Dependent']/*[local-name()='Dependent_Reference']/*[local-name()='ID' and @*[local-name()='type']='Dependent_ID']/text() + DependentID + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Dependent']/*[local-name()='Dependent_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID']/text() + DependentWID + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Person_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID']/text() + PersonWID + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/@*[local-name()='Formatted_Name'] + FullName + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='First_Name']/text() + FirstName + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='Last_Name']/text() + LastName + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Personal_Data']/*[local-name()='Personal_Information_Data']/*[local-name()='Birth_Date']/text() + DateOfBirth + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Personal_Data']/*[local-name()='Personal_Information_Data']//*[local-name()='Gender_Reference']/*[local-name()='ID' and @*[local-name()='type']='Gender_Code']/text() + Gender + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Related_Person_Relationship_Reference']/*[local-name()='ID' and @*[local-name()='type']='Related_Person_Relationship_ID']/text() + RelationshipTypeID + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Dependent']/*[local-name()='Dependent_Data']/*[local-name()='Full-time_Student']/text() + IsFullTimeStudent + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Dependent']]/*[local-name()='Dependent']/*[local-name()='Dependent_Data']/*[local-name()='Disabled']/text() + IsDisabled + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + false + false + false + false + false + false + false + true + false + false + false + false + false + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/topic.yaml new file mode 100644 index 00000000..68986343 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeAddDependents/topic.yaml @@ -0,0 +1,412 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when the user wants to ADD, REGISTER, or ENROLL a NEW DEPENDENT / family member on THEIR OWN benefits / Workday profile — spouse, domestic partner, child, son, daughter, newborn, baby, stepchild, adopted/foster child, parent, or any dependent eligible under company benefits. + + Trigger phrases: + - "Add a dependent" + - "Register / enroll a new dependent" + - "Add my child / spouse / partner to my benefits" + - "Add my newborn / baby / new family member" + - "I had a baby / got married / adopted — add them as a dependent" + - Life-event changes (marriage, birth, adoption, qualifying life event / QLE) needing a new dependent + + Data is submitted to Workday and belongs exclusively to the requesting user. + + Do NOT trigger for: + - Adding another person's dependents + - Removing/de-enrolling a dependent (different topic) + - Viewing currently listed dependents (different topic) + - Updating contact info, beneficiaries, or emergency contacts (different topics) + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: {} + + actions: + # Set Workday URL + - kind: SetVariable + id: set_workday_url + variable: Topic.WorkdayUrl + value: https://impl.workday.com//home.htmld + + # Set Workday icon URL + - kind: SetVariable + id: set_workday_icon_url + variable: Topic.WorkdayIconUrl + value: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII= + + # Intro message + - kind: SetVariable + id: set_intro_msg_text + variable: Topic.introMsgText + value: ="Sure, I can help you with that. Here's where you can add a new dependent. You can also add a dependent on [Workday](" & Topic.WorkdayUrl & "), an HR platform your company uses." + + - kind: SendActivity + id: intro_msg + activity: "{Topic.introMsgText}" + + # Step 1: Fetch reference data for relationship types + - kind: ConditionGroup + id: checkRelationshipTypes + conditions: + - id: relationshipTypesUninitialized + condition: =IsBlank(Global.RelatedPersonRelationshipLookupTable) + displayName: If relationship type picklist is uninitialized + actions: + - kind: BeginDialog + id: fetchRelationshipTypes + displayName: Redirect to Workday System Get Reference Data + input: + binding: + referenceDataKey: Related_Person_Relationship_ID + dialog: msdyn_copilotforemployeeselfservicehr.topic.GetReferenceData + + # Step 2: Fetch existing dependents + - kind: BeginDialog + id: getDependents_BeginDialog + displayName: Get Current Dependents + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Now(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetDependents + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: checkGetSuccess + conditions: + - id: getSuccessCondition + condition: =Topic.isSuccess = false + actions: + - kind: SendActivity + id: sendErrorMessage + activity: I encountered an error retrieving your dependent information. Please try again later or contact support. + - kind: EndDialog + id: endOnError + + # Step 3: Parse the response + - kind: ParseValue + id: parseDependentsResponse + displayName: Parse dependents response + variable: Topic.dependentsRecord + valueType: + kind: Record + properties: + DependentID: + type: + kind: Table + properties: + Value: String + DependentWID: + type: + kind: Table + properties: + Value: String + PersonWID: + type: + kind: Table + properties: + Value: String + FullName: + type: + kind: Table + properties: + Value: String + FirstName: + type: + kind: Table + properties: + Value: String + LastName: + type: + kind: Table + properties: + Value: String + DateOfBirth: + type: + kind: Table + properties: + Value: String + Gender: + type: + kind: Table + properties: + Value: String + RelationshipTypeID: + type: + kind: Table + properties: + Value: String + IsFullTimeStudent: + type: + kind: Table + properties: + Value: String + IsDisabled: + type: + kind: Table + properties: + Value: String + value: =Topic.workdayResponse + + # Step 4: Transform to table (XPath already filters to only actual dependents) + - kind: SetVariable + id: setVariable_transformDependents + displayName: Transform dependents to table + variable: Topic.dependentsTable + value: |- + =ForAll( + Sequence(CountRows(Topic.dependentsRecord.DependentID)), + { + DependentID: Last(FirstN(Topic.dependentsRecord.DependentID, Value)).Value, + DependentWID: Last(FirstN(Topic.dependentsRecord.DependentWID, Value)).Value, + PersonWID: Last(FirstN(Topic.dependentsRecord.PersonWID, Value)).Value, + FullName: Last(FirstN(Topic.dependentsRecord.FullName, Value)).Value, + FirstName: Last(FirstN(Topic.dependentsRecord.FirstName, Value)).Value, + LastName: Last(FirstN(Topic.dependentsRecord.LastName, Value)).Value, + DateOfBirth: Last(FirstN(Topic.dependentsRecord.DateOfBirth, Value)).Value, + Gender: Last(FirstN(Topic.dependentsRecord.Gender, Value)).Value, + RelationshipType: LookUp(Global.RelatedPersonRelationshipLookupTable, ID = Last(FirstN(Topic.dependentsRecord.RelationshipTypeID, Value)).Value).Referenced_Object_Descriptor, + RelationshipTypeID: Last(FirstN(Topic.dependentsRecord.RelationshipTypeID, Value)).Value, + IsFullTimeStudent: Last(FirstN(Topic.dependentsRecord.IsFullTimeStudent, Value)).Value = "1", + IsDisabled: Last(FirstN(Topic.dependentsRecord.IsDisabled, Value)).Value = "1" + } + ) + + # Step 5: Collect dependent information using Adaptive Card + - kind: AdaptiveCardPrompt + id: collectDependentCard + displayName: Collect dependent information + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Add a new dependent", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: "Fields marked with * are required.", + wrap: true, + size: "Small", + spacing: "Small" + }, + { + type: "Input.Text", + id: "firstName", + label: "First name", + placeholder: "Enter first name", + isRequired: true, + errorMessage: "First name is required." + }, + { + type: "Input.Text", + id: "lastName", + label: "Last name", + placeholder: "Enter last name", + isRequired: true, + errorMessage: "Last name is required." + }, + { + type: "Input.Date", + id: "dateOfBirth", + label: "Date of birth", + isRequired: true, + errorMessage: "Date of birth is required." + }, + { + type: "Input.ChoiceSet", + id: "gender", + label: "Gender", + style: "compact", + isRequired: true, + errorMessage: "Please select a gender.", + placeholder: "Select gender", + choices: [ + { title: "Male", value: "Male" }, + { title: "Female", value: "Female" }, + { title: "Not declared", value: "Not_Declared" } + ] + }, + { + type: "Input.ChoiceSet", + id: "relationshipType", + label: "Relationship to employee", + style: "compact", + isRequired: true, + errorMessage: "Please select a relationship.", + placeholder: "Select relationship", + choices: ForAll(Global.RelatedPersonRelationshipLookupTable, { title: ThisRecord.Referenced_Object_Descriptor, value: ThisRecord.ID }) + }, + { + type: "Input.ChoiceSet", + id: "country", + label: "Country", + style: "compact", + isRequired: true, + errorMessage: "Please select a country.", + value: "USA", + choices: [ + { title: "United States", value: "USA" }, + { title: "Canada", value: "CAN" }, + { title: "United Kingdom", value: "GBR" }, + { title: "India", value: "IND" }, + { title: "Australia", value: "AUS" }, + { title: "Germany", value: "DEU" }, + { title: "France", value: "FRA" } + ] + } + ], + actions: [ + { type: "Action.Submit", title: "Submit", id: "Submit", data: { actionSubmitId: "Submit" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.formActionId + firstName: Topic.firstName + lastName: Topic.lastName + dateOfBirth: Topic.dateOfBirth + gender: Topic.gender + relationshipType: Topic.relationshipType + country: Topic.country + outputType: + properties: + actionSubmitId: String + firstName: String + lastName: String + dateOfBirth: String + gender: String + relationshipType: String + country: String + + # Step 7: Handle cancel + - kind: ConditionGroup + id: checkCancelAction + conditions: + - id: cancelCondition + condition: =Topic.formActionId = "Cancel" + actions: + - kind: SendActivity + id: sendCancelMessage + activity: Request cancelled. No dependent was added. Is there anything else I can help you with? + - kind: CancelAllDialogs + id: cancelAll + + # Step 8: Validate required fields + - kind: ConditionGroup + id: validateFields + conditions: + - id: missingFieldsCondition + condition: =IsBlank(Topic.firstName) || IsBlank(Topic.lastName) || IsBlank(Topic.dateOfBirth) || IsBlank(Topic.gender) || IsBlank(Topic.relationshipType) || IsBlank(Topic.country) + actions: + - kind: SendActivity + id: sendValidationError + activity: Please fill in all required fields (first name, last name, date of birth, gender, relationship, and country). + - kind: EndDialog + id: endOnValidationError + + # Step 9: Set relationship type ID (already comes from lookup table) + - kind: SetVariable + id: mapRelationshipType + displayName: Set relationship type ID + variable: Topic.relationshipTypeId + value: =Topic.relationshipType + + # Step 10: Call Add Dependent API + - kind: BeginDialog + id: addDependent_BeginDialog + displayName: Add Dependent to Workday + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{First_Name}"",""value"":""" & Topic.firstName & """},{""key"":""{Last_Name}"",""value"":""" & Topic.lastName & """},{""key"":""{Date_Of_Birth}"",""value"":""" & Topic.dateOfBirth & """},{""key"":""{Gender}"",""value"":""" & Topic.gender & """},{""key"":""{Relationship_Type}"",""value"":""" & Topic.relationshipTypeId & """},{""key"":""{Country_Code}"",""value"":""" & Topic.country & """}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeAddDependent + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.addErrorResponse + isSuccess: Topic.addIsSuccess + workdayResponse: Topic.addWorkdayResponse + + # Step 11: Show result + - kind: ConditionGroup + id: checkAddSuccess + conditions: + - id: addSuccessCondition + condition: =Topic.addIsSuccess = true + actions: + - kind: SendActivity + id: sendSuccessIntroMessage + activity: Great! You've added a new dependent. + + - kind: SetVariable + id: setOtherDependentsList + variable: Topic.otherDependentsList + value: =If(CountRows(Topic.dependentsTable) > 0, Concat(Topic.dependentsTable, FullName, ", "), "None") + + - kind: SendActivity + id: sendSuccessMessage + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "✅ Your new dependent was added", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Name", value: Topic.firstName & " " & Topic.lastName }, + { title: "Date of birth", value: Topic.dateOfBirth }, + { title: "Gender", value: Topic.gender }, + { title: "Relationship to employee", value: LookUp(Global.RelatedPersonRelationshipLookupTable, ID = Topic.relationshipType).Referenced_Object_Descriptor }, + { title: "Other dependents", value: Topic.otherDependentsList } + ] + } + ] + } + + - kind: CancelAllDialogs + id: endOnSuccess + + elseActions: + - kind: SendActivity + id: sendAddErrorMessage + activity: |- + There was an error adding the dependent. Please try again later or contact HR support. + + **Error details:** {Topic.addErrorResponse} + + - kind: CancelAllDialogs + id: endOnError2 + +inputType: {} +outputType: + properties: + addIsSuccess: + displayName: Add Success + type: Boolean diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/README.md new file mode 100644 index 00000000..9dee802a --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/README.md @@ -0,0 +1,98 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Get Vacation Balance + +## Overview + +This scenario allows employees to check their time off balances from Workday. It retrieves vacation, sick leave, floating holiday, and other plan balances for the requesting user. + +The `EmployeeGetVacationBalance` topic allows employees to: +- **View** their current time off balances across all plans +- **Select** a specific as-of date to check historical or future balances +- **Receive** AI-formatted responses with balance details + +## Features + +- **Date Selection**: Prompts user to select an as-of date via Adaptive Card, defaults to today +- **Multiple Plan Support**: Returns balances for all time off plans (vacation, sick, floating holiday, etc.) +- **AI-Formatted Response**: Uses generative AI to format the balance response in a friendly, conversational way +- **Automatic Date Extraction**: If user includes a date in their request, it's automatically extracted + +## Snapshots + +### Date Selection Card +![Date Selection Card](get_vacation_balance.png) + +## Trigger Phrases + +- "What is my vacation balance?" +- "How much time off can I take?" +- "What is my Workday vacation balance?" +- "Check my PTO balance" +- "How many sick days do I have left?" +- "Show me my time off balance as of January 1st" + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Main topic definition with conversation flow and Adaptive Card | +| `msdyn_HRWorkdayHCMEmployeeGetVacationBalance.xml` | XML template for the Get_Time_Off_Plan_Balances API request | + +## Workday APIs Used + +| API | Service | Version | Purpose | +|-----|---------|---------|---------| +| `Get_Time_Off_Plan_Balances` | Absence_Management | v42.0 | Retrieve employee's time off plan balances | + +## Flow Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Triggers Topic │ +│ "What is my vacation balance?" │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌───────────────┴───────────────┐ + │ Date provided in request? │ + └───────────────┬───────────────┘ + │ │ + Yes No + │ │ + ▼ ▼ + ┌───────────────────────┐ ┌─────────────────────┐ + │ Use extracted date │ │ Show Date Picker │ + │ │ │ Adaptive Card │ + └───────────────────────┘ │ (defaults to today)│ + │ └──────────┬──────────┘ + │ │ + └────────────┬───────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Call Workday API │ +│ (Get_Time_Off_Plan_Balances) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ AI Formats Response │ +│ (Friendly message with balances by plan) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Display to User │ +│ "As of [date], here are your balances: │ +│ • Vacation: X hours │ +│ • Sick Leave: Y hours" │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Dependencies + +- `msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution` - For API execution +- `Global.ESS_UserContext_Employee_Id` - Current user's employee ID diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/get_vacation_balance.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/get_vacation_balance.png new file mode 100644 index 00000000..4d53f446 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/get_vacation_balance.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/msdyn_HRWorkdayHCMEmployeeGetVacationBalance.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/msdyn_HRWorkdayHCMEmployeeGetVacationBalance.xml new file mode 100644 index 00000000..ab2b9b4f --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/msdyn_HRWorkdayHCMEmployeeGetVacationBalance.xml @@ -0,0 +1,85 @@ + + + + + User + + msdyn_HRWorkdayHCMEmployeeGetVacationBalance + Absence_Management + v42.0 + + + + + Employee_ID + {Employee_ID} + + + As_Of_Effective_Date + {As_Of_Effective_Date} + + + + + + + RemainingBalance + + //*[local-name()='Time_Off_Plan_Balance_Record'] + /*[local-name()='Time_Off_Plan_Balance_Position_Record'] + /*[local-name()='Time_Off_Plan_Balance']/text() + + + + + + PlanDescriptor + + //*[local-name()='Time_Off_Plan_Balance_Record'] + /*[local-name()='Time_Off_Plan_Reference']/@*[local-name()='Descriptor'] + + + + + + PlanID + + //*[local-name()='Time_Off_Plan_Balance_Record'] + /*[local-name()='Time_Off_Plan_Reference'] + /*[local-name()='ID'][@*[local-name()='type']='Absence_Plan_ID']/text() + + + + + + UnitOfTime + + //*[local-name()='Time_Off_Plan_Balance_Record'] + /*[local-name()='Unit_of_Time_Reference']/@*[local-name()='Descriptor'] + + + + + + + + + + + + + + + {Employee_ID} + + + + + {As_Of_Effective_Date} + + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/topic.yaml new file mode 100644 index 00000000..30dc2836 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeGetVacationBalance/topic.yaml @@ -0,0 +1,144 @@ +kind: AdaptiveDialog +inputs: + - kind: AutomaticTaskInput + propertyName: AsOfEffectiveDate + description: The as-of effective date (YYYY-MM-DD) to use when retrieving vacation balances. If the user includes a date in their prompt, the NLU should populate this automatically. + entity: DateTimePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +modelDescription: |- + Use this topic when the user asks about THEIR OWN time off balance / leave balance / vacation balance / PTO balance / remaining days / accrued hours stored in Workday — vacation, PTO (paid time off), annual leave, sick leave, sick days, personal days, personal time, floating holidays, comp time, accrued hours, remaining days, carryover, or any leave-type balance. + + Triggers: "What's my vacation balance?", "How many PTO days do I have left?", "How much sick leave do I have?", "Show my time off balance", "Remaining vacation days", "Accrued hours", "How many days off do I have?", "What's my carryover?". + + Data belongs exclusively to the requesting user. + + Do NOT trigger for: + - REQUESTING / submitting time off (use the RequestTimeOff topic) + - Cancelling or modifying a submitted leave request + - General questions about leave policy or accrual rules +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - What is my vacation balance? + - How much time off can I take? + - what is my workday vacation balance? + + actions: + - kind: SetVariable + id: set_workday_url + variable: Topic.WorkdayUrl + value: https://impl.workday.com//home.htmld + + - kind: SetVariable + id: set_intro_msg_text + variable: Topic.introMsgText + value: ="Sure, here's what I found on your time off balance from [Workday](" & Topic.WorkdayUrl & "), an HR platform your company uses." + + - kind: SendActivity + id: intro_msg + activity: "{Topic.introMsgText}" + + - kind: SetVariable + id: setVariable_8qSSM1 + variable: Topic.date + value: =Text(Today(), "yyyy-MM-dd") + + - kind: ConditionGroup + id: ask_effective_date_if_blank + conditions: + - id: isBlank + condition: =IsBlank(Topic.AsOfEffectiveDate) + actions: + - kind: AdaptiveCardPrompt + id: askDateCard1 + displayName: Select As-Of Date + card: |- + ={ + type: "AdaptiveCard", + '$schema': "https://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Which date would you like to check your vacation balance for?", + weight: "Bolder", + wrap: true + }, + { + type: "Input.Date", + id: "asOfDate", + label: "Select date", + value: Topic.date + } + ], + actions: [ + { + type: "Action.Submit", + title: "Submit" + } + ] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId + asOfDate: Topic.asOfDate + + outputType: + properties: + actionSubmitId: String + asOfDate: Date + + - kind: SetVariable + id: ensure_effective_date2 + variable: Topic.AsOfEffectiveDate + value: =If(IsBlank(Topic.AsOfEffectiveDate), DateTimeValue(Topic.asOfDate), Topic.AsOfEffectiveDate) + + - kind: BeginDialog + id: Y74Om43 + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Topic.AsOfEffectiveDate, "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetVacationBalance + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: AnswerQuestionWithAI + id: igank32 + autoSend: false + variable: Topic.VacationBalance + userInput: =Topic.workdayResponse + additionalInstructions: Do not show citation. Display each vacation balances in a separate line and highlight vacation name. Only reply back with the Vacation Balance. Format the response in in friendly way and make sure to tie it back directly to the orginal question of the user. ({System.Activity.Text}). Display ({Topic.asOfDate}) with the message as of. Never include the "Plan ID". + +inputType: + properties: + AsOfEffectiveDate: + displayName: AsOfEffectiveDate + description: The effective date to use when retrieving vacation balances (YYYY-MM-DD). Defaults to today's date if not provided. + type: DateTime + +outputType: + properties: + finalizedData: + displayName: finalizedData + type: + kind: Record + properties: + CostCenterCode: String + CostCenterName: String + EmployeeName: String + + VacationBalance: + displayName: VacationBalance + type: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/README.md new file mode 100644 index 00000000..248e2578 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/README.md @@ -0,0 +1,93 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Employee Update Residential Address + +## Overview + +This topic enables employees to manage their home/residential address in Workday through the Copilot agent. It retrieves the employee's current home addresses, allows them to select which one to update, add a new address, or modify an existing one, and submits the changes via the Workday Change Home Contact Information API. + +## Features + +- View current home addresses with primary address marked +- Update any field of an existing home address +- Add a new home address when no addresses exist or user wants to add another +- Set or change which address is the primary home address +- Form pre-population with current address values + +## Snapshots + +![Update Residential Address](update_address.png) + +## Trigger Phrases + +- "Update my home address" +- "I want to update my residential address" +- "Change my address" +- "Update my street address" +- "I moved to a new address" + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Copilot Studio topic definition with conversation flow | +| `msdyn_GetResidentialAddress_Template.xml` | XML template to retrieve current home addresses | +| `msdyn_UpdateResidentialAddress_Template.xml` | XML template to update an existing home address | +| `msdyn_AddResidentialAddress_Template.xml` | XML template to add a new home address | + +## Workday APIs Used + +| API | Purpose | +|-----|---------| +| `Get_Workers` | Retrieve current home address information | +| `Change_Home_Contact_Information` | Add or update home address | + +## Flow Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Triggers Topic │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Fetch Current Home Addresses │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Selection (Multiple) or Auto-Select (Single) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Collect New Address via Adaptive Card Form │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Submit to Workday │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Success/Error Message │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Configurations + +Environment makers need to configure the following in the topic: + +| Configuration | Description | Location in Topic | +|---------------|-------------|-------------------| +| **Countries** | Available country options (USA, CAN, GBR, etc.) | Adaptive card dropdown | +| **States/Provinces** | Available state/province codes per country | Adaptive card dropdown | +| **Workday Icon** | Update the icon URL to match your organization's branding | Topic properties > Icon | +| **Workday URL** | Set your organization's Workday tenant URL | HTTP action or connector configuration | + +## Dependencies + +- **Employee Context**: Worker ID must be available in the conversation context diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_AddResidentialAddress_Template.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_AddResidentialAddress_Template.xml new file mode 100644 index 00000000..74699815 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_AddResidentialAddress_Template.xml @@ -0,0 +1,69 @@ + + + + + User + + Template_AddResidentialAddressRequest + Human_Resources + v42.0 + + + + //*[local-name()='Event_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID']/text() + EventWID + + + + + + + + + + false + true + true + + New address added via Copilot + + {Employee_ID} + + + + + + {Employee_ID} + + {Event_Effective_Date} + + + + + + {Country_Code} + + + {State_Province_Code} + + {Address_Line_1} + {Address_Line_2} + {Postal_Code} + {City} + + + + + HOME + + + + + + + + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_GetResidentialAddress_Template.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_GetResidentialAddress_Template.xml new file mode 100644 index 00000000..e3424dc9 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_GetResidentialAddress_Template.xml @@ -0,0 +1,106 @@ + + + + + User + + Template_GetResidentialAddressRequest + Human_Resources + v42.0 + + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/@*[local-name()='Formatted_Address'] + FormattedAddress + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Address_ID']/text() + AddressID + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Country_Reference']/*[local-name()='ID' and @*[local-name()='type']='ISO_3166-1_Alpha-3_Code']/text() + CountryCode + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Country_Region_Reference']/*[local-name()='ID' and @*[local-name()='type']='Country_Region_ID']/text() + StateProvinceCode + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Municipality']/text() + City + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Postal_Code']/text() + PostalCode + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Address_Line_Data' and @*[local-name()='Type']='ADDRESS_LINE_1']/text() + AddressLine1 + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Address_Line_Data' and @*[local-name()='Type']='ADDRESS_LINE_2']/text() + AddressLine2 + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Usage_Data']/*[local-name()='Type_Data']/@*[local-name()='Primary'] + IsPrimary + + + //*[local-name()='Contact_Data']/*[local-name()='Address_Data']/*[local-name()='Usage_Data']/*[local-name()='Type_Data']/*[local-name()='Type_Reference']/*[local-name()='ID' and @*[local-name()='type']='Communication_Usage_Type_ID']/text() + UsageType + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + true + false + false + true + true + true + true + true + true + true + true + true + true + true + true + true + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_UpdateResidentialAddress_Template.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_UpdateResidentialAddress_Template.xml new file mode 100644 index 00000000..6110c982 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/msdyn_UpdateResidentialAddress_Template.xml @@ -0,0 +1,71 @@ + + + + + User + + Template_UpdateResidentialAddressRequest + Human_Resources + v42.0 + + + + //*[local-name()='Event_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID']/text() + EventWID + + + + + + + + + + false + true + true + + Address updated via Copilot + + {Employee_ID} + + + + + + {Employee_ID} + + {Event_Effective_Date} + + + + + + {Country_Code} + + + {State_Province_Code} + + {Address_Line_1} + {Address_Line_2} + {Postal_Code} + {City} + + + + + HOME + + + + + {Address_ID} + + + + + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/topic.yaml new file mode 100644 index 00000000..569f608e --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/topic.yaml @@ -0,0 +1,769 @@ +kind: AdaptiveDialog +modelDescription: |- + You will respond only to requests related to update, change, edit, modify, or correct the requesting user's own residential/home address in Workday. + + TRIGGER THIS when user wants to: update/change/fix/correct their home address, report they moved or relocated, fix a wrong address on file, change street/city/state/zip code. + + All address data is retrieved from and updated through Workday, and pertains exclusively to the requesting user. + + Do not respond about other people's data. + + Example valid requests: + "Update my home address" + "I want to update my residential address" + "Change my address" + "Update my street address" + "I moved to a new address" +beginDialog: + kind: OnRecognizedIntent + id: main + intent: {} + actions: + - kind: BeginDialog + id: getAddress_BeginDialog + displayName: Get Current Residential Address + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Now(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetResidentialAddress + + dialog: msdyn_copilotforemployeeselfservicedahr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: checkGetSuccess + conditions: + - id: getSuccessCondition + condition: =Topic.isSuccess = false + actions: + - kind: SendActivity + id: sendErrorMessage + activity: I encountered an error retrieving your current address. Please try again later or contact support. + + - kind: EndDialog + id: endOnError + + - kind: ParseValue + id: parseAddressResponse + displayName: Parse address response + variable: Topic.addressRecord + valueType: + kind: Record + properties: + AddressID: + type: + kind: Table + properties: + Value: String + + AddressLine1: + type: + kind: Table + properties: + Value: String + + AddressLine2: + type: + kind: Table + properties: + Value: String + + City: + type: + kind: Table + properties: + Value: String + + CountryCode: + type: + kind: Table + properties: + Value: String + + FormattedAddress: + type: + kind: Table + properties: + Value: String + + IsPrimary: + type: + kind: Table + properties: + Value: String + + PostalCode: + type: + kind: Table + properties: + Value: String + + StateProvinceCode: + type: + kind: Table + properties: + Value: String + + UsageType: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_transformAddresses + displayName: Transform addresses to table + variable: Topic.addressTable + value: |- + =ForAll( + Sequence(CountRows(Topic.addressRecord.AddressID)), + { + AddressID: Last(FirstN(Topic.addressRecord.AddressID, Value)).Value, + FormattedAddress: Last(FirstN(Topic.addressRecord.FormattedAddress, Value)).Value, + CountryCode: Last(FirstN(Topic.addressRecord.CountryCode, Value)).Value, + StateProvinceCode: Last(FirstN(Topic.addressRecord.StateProvinceCode, Value)).Value, + City: Last(FirstN(Topic.addressRecord.City, Value)).Value, + PostalCode: Last(FirstN(Topic.addressRecord.PostalCode, Value)).Value, + AddressLine1: Last(FirstN(Topic.addressRecord.AddressLine1, Value)).Value, + AddressLine2: Last(FirstN(Topic.addressRecord.AddressLine2, Value)).Value, + IsPrimary: If(Last(FirstN(Topic.addressRecord.IsPrimary, Value)).Value = "1", true, false), + UsageType: Last(FirstN(Topic.addressRecord.UsageType, Value)).Value + } + ) + + - kind: SetVariable + id: setVariable_homeAddresses + displayName: Filter to HOME addresses only + variable: Topic.homeAddresses + value: =Filter(Topic.addressTable, UsageType = "HOME") + + - kind: ConditionGroup + id: checkExistingAddresses + conditions: + - id: noAddressesCondition + condition: =CountRows(Topic.homeAddresses) = 0 + actions: + - kind: SendActivity + id: sendNoAddressMessage + activity: I don't see a home address on file for you. Let's add a new one. + + - kind: SetVariable + id: setIsNewAddress + variable: Topic.isNewAddress + value: =true + + - kind: SetVariable + id: setSelectedAddressID_New + variable: Topic.selectedAddressID + value: ="" + + elseActions: + - kind: SetVariable + id: setIsExistingAddress + variable: Topic.isNewAddress + value: =false + + - kind: ConditionGroup + id: checkSingleOrMultipleAddresses + conditions: + - id: singleAddressCondition + condition: =CountRows(Topic.homeAddresses) = 1 + displayName: Single address - auto-select + actions: + - kind: SetVariable + id: autoSelectAddress + displayName: Auto-select single address + variable: Topic.selectedAddress + value: =First(Topic.homeAddresses) + + - kind: SetVariable + id: autoSetAddressID + variable: Topic.selectedAddressID + value: =Topic.selectedAddress.AddressID + + - kind: SendActivity + id: sendCurrentAddressMsg + activity: |- + I found your current home address: + + 📍 **{Topic.selectedAddress.FormattedAddress}** + + elseActions: + - kind: SetVariable + id: buildAddressChoices + displayName: Build address selection choices + variable: Topic.addressChoices + value: |- + =ForAll( + Topic.homeAddresses, + { + title: ThisRecord.AddressLine1 & ", " & ThisRecord.City & ", " & ThisRecord.StateProvinceCode & " " & ThisRecord.PostalCode & " (" & If(ThisRecord.IsPrimary, "Primary", "Home") & ")", + value: ThisRecord.AddressID + } + ) + + - kind: SetVariable + id: set_workday_icon_url + variable: Topic.WorkdayIconUrl + value: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAExUlEQVR4nOVbSUwUQRT9XSJxQVExLoQZLwJGGZfEJcY5KJx0Er2piF7d4hkTTby5njxoRE9eHM5oxqOY2O5jXFAjYFzAgBrEDKIxGpf8xta2+ld1z3T1TPfMSwiZ7vrd9V69/931H... + + - kind: AdaptiveCardPrompt + id: selectAddressCard + displayName: Select address to update + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "auto", + items: [ + { + type: "Image", + url: Topic.WorkdayIconUrl, + style: "RoundedCorners", + size: "Small", + height: "20px", + width: "20px" + } + ], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "auto", + items: [ + { + type: "TextBlock", + text: "Workday", + size: "Small", + weight: "Bolder" + } + ], + verticalContentAlignment: "Center", + spacing: "Small" + } + ] + }, + { + type: "TextBlock", + text: "Select an address to update", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: "You have " & CountRows(Topic.homeAddresses) & " addresses. Select to update or add a new address.", + wrap: true, + spacing: "Small" + }, + { + type: "Input.ChoiceSet", + id: "selectedAddress", + label: "Address", + style: "compact", + isRequired: true, + errorMessage: "Please select an address.", + choices: ForAll(Topic.addressChoices, { title: ThisRecord.title, value: ThisRecord.value }), + value: If(CountRows(Topic.addressChoices) > 0, First(Topic.addressChoices).value, "") + } + ], + actions: [ + { type: "Action.Submit", title: "Continue", id: "Continue", data: { actionSubmitId: "Continue" } }, + { type: "Action.Submit", title: "Add an address", id: "AddNew", data: { actionSubmitId: "AddNew" }, associatedInputs: "none" }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.selectionActionId + selectedAddress: Topic.selectedAddressID + + outputType: + properties: + actionSubmitId: String + selectedAddress: String + + - kind: ConditionGroup + id: handleSelectionCancel + conditions: + - id: selectionCancelled + condition: =Topic.selectionActionId = "Cancel" + actions: + - kind: SendActivity + id: selectionCancelMsg + activity: Your request has been cancelled. Is there anything else I can help you with? + + - kind: CancelAllDialogs + id: selectionCancelAll + + - kind: ConditionGroup + id: handleAddNewAddress + conditions: + - id: addNewSelected + condition: =Topic.selectionActionId = "AddNew" + actions: + - kind: SetVariable + id: setIsNewAddressFromSelection + variable: Topic.isNewAddress + value: =true + + - kind: SetVariable + id: clearSelectedAddressID + variable: Topic.selectedAddressID + value: ="" + + - kind: ConditionGroup + id: checkIfContinueSelected + conditions: + - id: continueSelected + condition: =Topic.selectionActionId = "Continue" + actions: + - kind: SetVariable + id: setSelectedAddress + displayName: Set selected address details + variable: Topic.selectedAddress + value: =LookUp(Topic.homeAddresses, AddressID = Topic.selectedAddressID) + + - kind: ConditionGroup + id: prefillExistingAddressFields + conditions: + - id: isUpdatingExistingAddress + condition: =Topic.isNewAddress = false + actions: + - kind: SetVariable + id: setCurrentAddressLine1 + variable: Topic.currentAddressLine1 + value: =Topic.selectedAddress.AddressLine1 + + - kind: SetVariable + id: setCurrentAddressLine2 + variable: Topic.currentAddressLine2 + value: =Topic.selectedAddress.AddressLine2 + + - kind: SetVariable + id: setCurrentCity + variable: Topic.currentCity + value: =Topic.selectedAddress.City + + - kind: SetVariable + id: setCurrentStateProvince + variable: Topic.currentStateProvince + value: =Topic.selectedAddress.StateProvinceCode + + - kind: SetVariable + id: setCurrentPostalCode + variable: Topic.currentPostalCode + value: =Topic.selectedAddress.PostalCode + + - kind: SetVariable + id: setCurrentCountryCode + variable: Topic.currentCountryCode + value: =Topic.selectedAddress.CountryCode + + - kind: SetVariable + id: setCurrentIsPrimary + variable: Topic.currentIsPrimary + value: =Topic.selectedAddress.IsPrimary + elseActions: + - kind: SetVariable + id: clearCurrentAddressLine1 + variable: Topic.currentAddressLine1 + value: ="" + + - kind: SetVariable + id: clearCurrentAddressLine2 + variable: Topic.currentAddressLine2 + value: ="" + + - kind: SetVariable + id: clearCurrentCity + variable: Topic.currentCity + value: ="" + + - kind: SetVariable + id: clearCurrentStateProvince + variable: Topic.currentStateProvince + value: ="" + + - kind: SetVariable + id: clearCurrentPostalCode + variable: Topic.currentPostalCode + value: ="" + + - kind: SetVariable + id: clearCurrentCountryCode + variable: Topic.currentCountryCode + value: ="" + + - kind: SetVariable + id: clearCurrentIsPrimary + variable: Topic.currentIsPrimary + value: =false + + - kind: ConditionGroup + id: shortCircuitOnSelectionCancel + conditions: + - id: selectionCancelledShortCircuit + condition: =Topic.selectionActionId = "Cancel" + actions: + - kind: EndDialog + id: endOnSelectionCancel + + - kind: AdaptiveCardPrompt + id: collectAddressCard + displayName: Collect new address information + card: |- + ={ + type: "AdaptiveCard", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Update Your Home Address", + weight: "Bolder", + size: "Large" + }, + { + type: "TextBlock", + text: "Please enter your new address details:", + wrap: true + }, + { + type: "Input.Text", + id: "addressLine1", + label: "Address line 1", + placeholder: "Street address", + isRequired: true, + errorMessage: "Address line 1 is required.", + value: If(IsBlank(Topic.currentAddressLine1), "", Topic.currentAddressLine1) + }, + { + type: "Input.Text", + id: "addressLine2", + label: "Address line 2", + placeholder: "Apt, Suite, Unit, etc. (optional)", + value: If(IsBlank(Topic.currentAddressLine2), "", Topic.currentAddressLine2) + }, + { + type: "Input.Text", + id: "city", + label: "City", + placeholder: "City", + isRequired: true, + errorMessage: "City is required.", + value: If(IsBlank(Topic.currentCity), "", Topic.currentCity) + }, + { + type: "Input.ChoiceSet", + id: "country", + label: "Country", + style: "compact", + isRequired: true, + errorMessage: "Please select a country.", + value: If(IsBlank(Topic.currentCountryCode), "USA", Topic.currentCountryCode), + choices: [ + { title: "United States", value: "USA" }, + { title: "Canada", value: "CAN" }, + { title: "United Kingdom", value: "GBR" }, + { title: "Australia", value: "AUS" }, + { title: "Germany", value: "DEU" }, + { title: "France", value: "FRA" }, + { title: "India", value: "IND" }, + { title: "Japan", value: "JPN" }, + { title: "Mexico", value: "MEX" }, + { title: "Brazil", value: "BRA" } + ] + }, + { + type: "Input.ChoiceSet", + id: "stateProvince", + label: "State/province", + style: "compact", + isRequired: true, + errorMessage: "Please select a state/province.", + value: If(IsBlank(Topic.currentStateProvince), "", Topic.currentStateProvince), + choices: [ + { title: "Alabama", value: "USA-AL" }, + { title: "Alaska", value: "USA-AK" }, + { title: "Arizona", value: "USA-AZ" }, + { title: "Arkansas", value: "USA-AR" }, + { title: "California", value: "USA-CA" }, + { title: "Colorado", value: "USA-CO" }, + { title: "Connecticut", value: "USA-CT" }, + { title: "Delaware", value: "USA-DE" }, + { title: "Florida", value: "USA-FL" }, + { title: "Georgia", value: "USA-GA" }, + { title: "Hawaii", value: "USA-HI" }, + { title: "Idaho", value: "USA-ID" }, + { title: "Illinois", value: "USA-IL" }, + { title: "Indiana", value: "USA-IN" }, + { title: "Iowa", value: "USA-IA" }, + { title: "Kansas", value: "USA-KS" }, + { title: "Kentucky", value: "USA-KY" }, + { title: "Louisiana", value: "USA-LA" }, + { title: "Maine", value: "USA-ME" }, + { title: "Maryland", value: "USA-MD" }, + { title: "Massachusetts", value: "USA-MA" }, + { title: "Michigan", value: "USA-MI" }, + { title: "Minnesota", value: "USA-MN" }, + { title: "Mississippi", value: "USA-MS" }, + { title: "Missouri", value: "USA-MO" }, + { title: "Montana", value: "USA-MT" }, + { title: "Nebraska", value: "USA-NE" }, + { title: "Nevada", value: "USA-NV" }, + { title: "New Hampshire", value: "USA-NH" }, + { title: "New Jersey", value: "USA-NJ" }, + { title: "New Mexico", value: "USA-NM" }, + { title: "New York", value: "USA-NY" }, + { title: "North Carolina", value: "USA-NC" }, + { title: "North Dakota", value: "USA-ND" }, + { title: "Ohio", value: "USA-OH" }, + { title: "Oklahoma", value: "USA-OK" }, + { title: "Oregon", value: "USA-OR" }, + { title: "Pennsylvania", value: "USA-PA" }, + { title: "Rhode Island", value: "USA-RI" }, + { title: "South Carolina", value: "USA-SC" }, + { title: "South Dakota", value: "USA-SD" }, + { title: "Tennessee", value: "USA-TN" }, + { title: "Texas", value: "USA-TX" }, + { title: "Utah", value: "USA-UT" }, + { title: "Vermont", value: "USA-VT" }, + { title: "Virginia", value: "USA-VA" }, + { title: "Washington", value: "USA-WA" }, + { title: "West Virginia", value: "USA-WV" }, + { title: "Wisconsin", value: "USA-WI" }, + { title: "Wyoming", value: "USA-WY" }, + { title: "District of Columbia", value: "USA-DC" }, + { title: "Ontario", value: "CAN-ON" }, + { title: "Quebec", value: "CAN-QC" }, + { title: "British Columbia", value: "CAN-BC" }, + { title: "Alberta", value: "CAN-AB" } + ] + }, + { + type: "Input.Text", + id: "postalCode", + label: "Postal/ZIP code", + placeholder: "Postal code", + isRequired: true, + errorMessage: "Postal code is required.", + value: If(IsBlank(Topic.currentPostalCode), "", Topic.currentPostalCode) + }, + { + type: "Input.Toggle", + id: "isPrimary", + title: "Set as primary address", + value: If(Topic.currentIsPrimary = true, "true", "false"), + valueOn: "true", + valueOff: "false" + } + ], + actions: [ + { + type: "Action.Submit", + title: "Update Address", + data: { + action: "updateAddress" + } + }, + { + type: "Action.Submit", + title: "Cancel", + data: { + action: "cancel" + } + } + ] + } + output: + binding: + action: Topic.cardAction + addressLine1: Topic.newAddressLine1 + addressLine2: Topic.newAddressLine2 + city: Topic.newCity + country: Topic.newCountry + isPrimary: Topic.newIsPrimary + postalCode: Topic.newPostalCode + stateProvince: Topic.newStateProvince + + outputType: + properties: + action: String + addressLine1: String + addressLine2: String + city: String + country: String + isPrimary: String + postalCode: String + stateProvince: String + + - kind: ConditionGroup + id: checkCancelAction + conditions: + - id: cancelCondition + condition: =Topic.cardAction = "cancel" + actions: + - kind: SendActivity + id: sendCancelMessage + activity: Address update cancelled. No changes were made. + + - kind: EndDialog + id: endOnCancel + + - kind: ConditionGroup + id: validateFields + conditions: + - id: missingFieldsCondition + condition: =IsBlank(Topic.newAddressLine1) || IsBlank(Topic.newCity) || IsBlank(Topic.newStateProvince) || IsBlank(Topic.newPostalCode) + actions: + - kind: SendActivity + id: sendValidationError + activity: Please fill in all required fields (Address Line 1, City, State/Province, and Postal Code). + + - kind: EndDialog + id: endOnValidationError + + - kind: ConditionGroup + id: checkAddOrUpdate + conditions: + - id: isNewAddressCondition + condition: =Topic.isNewAddress = true + displayName: Add new address + actions: + - kind: BeginDialog + id: addAddress_BeginDialog + displayName: Add New Residential Address + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{Event_Effective_Date}"",""value"":"""& Text(Now(), "yyyy-MM-dd") &"""},{""key"":""{Country_Code}"",""value"":""" & Topic.newCountry & """},{""key"":""{State_Province_Code}"",""value"":""" & Topic.newStateProvince & """},{""key"":""{Address_Line_1}"",""value"":""" & Topic.newAddressLine1 & """},{""key"":""{Address_Line_2}"",""value"":""" & If(IsBlank(Topic.newAddressLine2), "", Topic.newAddressLine2) & """},{""key"":""{City}"",""value"":""" & Topic.newCity & """},{""key"":""{Postal_Code}"",""value"":""" & Topic.newPostalCode & """},{""key"":""{Is_Primary}"",""value"":""" & If(Topic.newIsPrimary = "true", "true", "false") & """}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeAddResidentialAddress + + dialog: msdyn_copilotforemployeeselfservicedahr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.updateErrorResponse + isSuccess: Topic.updateIsSuccess + workdayResponse: Topic.updateWorkdayResponse + + elseActions: + - kind: BeginDialog + id: updateAddress_BeginDialog + displayName: Update Residential Address + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{Event_Effective_Date}"",""value"":"""& Text(Now(), "yyyy-MM-dd") &"""},{""key"":""{Address_ID}"",""value"":""" & Topic.selectedAddressID & """},{""key"":""{Country_Code}"",""value"":""" & Topic.newCountry & """},{""key"":""{State_Province_Code}"",""value"":""" & Topic.newStateProvince & """},{""key"":""{Address_Line_1}"",""value"":""" & Topic.newAddressLine1 & """},{""key"":""{Address_Line_2}"",""value"":""" & If(IsBlank(Topic.newAddressLine2), "", Topic.newAddressLine2) & """},{""key"":""{City}"",""value"":""" & Topic.newCity & """},{""key"":""{Postal_Code}"",""value"":""" & Topic.newPostalCode & """},{""key"":""{Is_Primary}"",""value"":""" & If(Topic.newIsPrimary = "true", "true", "false") & """}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeUpdateResidentialAddress + + dialog: msdyn_copilotforemployeeselfservicedahr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.updateErrorResponse + isSuccess: Topic.updateIsSuccess + workdayResponse: Topic.updateWorkdayResponse + + - kind: ConditionGroup + id: checkUpdateSuccess + conditions: + - id: updateSuccessCondition + condition: =Topic.updateIsSuccess = true + actions: + - kind: SendActivity + id: sendSuccessIntroMessage + activity: Great! You've updated your address. + + - kind: SendActivity + id: sendSuccessMessage + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "auto", + items: [ + { + type: "Image", + url: Topic.WorkdayIconUrl, + style: "RoundedCorners", + size: "Small", + height: "20px", + width: "20px" + } + ], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "auto", + items: [ + { + type: "TextBlock", + text: "Workday", + size: "Small", + weight: "Bolder" + } + ], + verticalContentAlignment: "Center", + spacing: "Small" + } + ] + }, + { + type: "TextBlock", + text: "✅ Address updated", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: Topic.newAddressLine1 & ", " & Topic.newCity & ", " & Topic.newStateProvince & " " & Topic.newPostalCode & " (" & If(Topic.newIsPrimary = "true", "Primary", "Home") & ")", + wrap: true, + spacing: "Small" + } + ] + } + + - kind: CancelAllDialogs + id: endOnSuccess + + elseActions: + - kind: SendActivity + id: sendUpdateErrorMessage + activity: |- + There was an error updating your address. Please try again later or contact support. + + **Error details:** {Topic.updateErrorResponse} + + - kind: CancelAllDialogs + id: endOnUpdateError + +inputType: {} +outputType: + properties: + updateIsSuccess: + displayName: Update Success + type: Boolean \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/update_address.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/update_address.png new file mode 100644 index 00000000..690daa35 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/EmployeeUpdateResidentialAddress/update_address.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/README.md new file mode 100644 index 00000000..4f751033 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/README.md @@ -0,0 +1,16 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Employee Scenarios + +Copilot Studio topics for employee self-service actions in Workday. + +| Folder | Description | +| --- | --- | +| [EmployeeAddDependents/](./EmployeeAddDependents/) | Add dependents to benefits | +| [EmployeeGetVacationBalance/](./EmployeeGetVacationBalance/) | Check vacation balance | +| [EmployeeUpdateResidentialAddress/](./EmployeeUpdateResidentialAddress/) | Update residential address | +| [WorkdayEmployeeRequestTimeOff/](./WorkdayEmployeeRequestTimeOff/) | Request time off | +| [WorkdayGetUserProfile/](./WorkdayGetUserProfile/) | View user profile | +| [WorkdayManageEmergencyContact/](./WorkdayManageEmergencyContact/) | Manage emergency contacts | diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/README.md new file mode 100644 index 00000000..c0fec5fa --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/README.md @@ -0,0 +1,229 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Employee Request Time Off + +This topic lets employees submit single-day or multi-day time off requests to Workday directly from a Copilot Studio agent. + +When an employee triggers this topic, the agent: + +1. Fetches current leave balances from Workday +2. Shows an Adaptive Card form with leave type, dates, hours, and reason +3. Submits the request to the Workday `Enter_Time_Off` API +4. Displays a confirmation card or a friendly error with retry options + +**Example trigger phrases:** "Request time off" · "Request vacation from January 5th to January 10th" · "Submit sick leave for next week" + +![Request Time Off form and adaptive card](requestTimeOffAdapativeCard.png) +![Request Time Off form submitted](requestTimeOffSubmitted.png) + +{: .important } +> This topic ships with sample values from a reference Workday tenant. Every Workday tenant is configured differently — you must replace the Plan IDs, Reason IDs, leave types, and tenant URL with values from your own Workday setup before importing. See [Configure the topic](#configure-the-topic) for details. + +## Prerequisites + +Before you start, make sure you have: + +- The **msdyn_copilotforemployeeselfservicehr** managed solution installed in your agent +- A Workday tenant with the **Absence_Management** module enabled +- A Workday connector configured with **User**-level authentication in your Power Platform environment +- The global variable **`Global.ESS_UserContext_Employee_Id`** populated at session start with the logged-in employee's Workday Employee ID +- The **`msdyn_HRWorkdayHCMEmployeeGetVacationBalance`** template already imported (from the [EmployeeGetVacationBalance](../EmployeeGetVacationBalance/) folder) +- Your organization's Workday Absence Plan IDs, Time Off Type IDs, and Reason IDs + +## What's in this folder + +| File | Description | Do you need to edit it? | +|------|-------------|------------------------| +| `topic.yaml` | Conversation flow, Adaptive Card form, configuration tables, error handling | Yes — update tenant URL, Plan IDs, Reason IDs | +| `msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml` | SOAP template for the Workday `Enter_Time_Off` API | Usually no | +| `requestTimeOffAdapativeCard.png` | Screenshot of the Adaptive Card form | No | +| `requestTimeOffSubmitted.png` | Screenshot of the confirmation card | No | +| `README.md` | This file | No | + +## Import the templates + +Before configuring the topic, import the XML templates into your agent. + +1. Add `msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml` to your agent's ESS Template Configuration. + +2. If you haven't already, import the balance template `msdyn_HRWorkdayHCMEmployeeGetVacationBalance` from the [EmployeeGetVacationBalance](../EmployeeGetVacationBalance/) folder. This topic calls it to display leave balances. + +## Configure the topic + +Open `topic.yaml` and update the following values to match your Workday tenant. All sample values shown below come from a reference tenant and **must** be replaced. + +### Step 1: Set your Workday tenant URL + +Find the `set_workday_url` node and replace `` with your tenant name: + +```yaml +# Before +value: https://impl.workday.com//home.htmld + +# After — replace contoso_corp with your tenant +value: https://impl.workday.com/contoso_corp/home.htmld +``` + +Your Workday URL typically follows the pattern `https://.workday.com//home.htmld`. Check your browser address bar when logged into Workday. + +### Step 2: Update Plan IDs + +Find the `set_plan_config` node. Replace each `PlanID` with the corresponding ID from your Workday tenant: + +``` +=Table( + {PlanID: "ABSENCE_PLAN-6-139", DisplayName: "Floating holiday"}, + {PlanID: "ABSENCE_PLAN-6-159", DisplayName: "Sick leave"}, + {PlanID: "ABSENCE_PLAN-6-158", DisplayName: "Vacation"} +) +``` + +You can find your Plan IDs in Workday under **Setup > Absence Plans > Plan ID**. Only plans listed here **and** returned by the Workday API will appear in the balance header. + +### Step 3: Update Reason IDs + +Find the **second** `set_time_off_reason_config` node. Replace each `ReasonID` with your tenant's value: + +| ReasonKey | TimeOffTypeID | ReasonID ← replace this | +|-----------|---------------|------------------------| +| `full_day` | `ESS_Vacation_Hours` | `Full Day_Vac` | +| `half_day_am` | `ESS_Vacation_Hours` | `Half Day_AM_Vac` | +| `half_day_pm` | `ESS_Vacation_Hours` | `Half Day_PM_Vac` | +| `full_day` | `ESS_Floating_Holiday_Hours` | `full_day_floating` | +| `half_day_am` | `ESS_Floating_Holiday_Hours` | `half_day_am_floating` | +| `half_day_pm` | `ESS_Floating_Holiday_Hours` | `half_day_pm_floating` | +| `full_day` | `ESS_Comp_Off` | `Full day-COI` | +| `half_day_am` | `ESS_Comp_Off` | `Half day AM-COI` | +| `half_day_pm` | `ESS_Comp_Off` | `Half day PM-COI` | +| `full_day` | `ESS_Sick_Time_Off` | `Full day_Sick_IN` | +| `half_day_am` | `ESS_Sick_Time_Off` | `Half day AM_Sick_IN` | +| `half_day_pm` | `ESS_Sick_Time_Off` | `Half day PM_Sick_IN` | + +Don't rename the `ReasonKey` values — they're bound to the Adaptive Card "Reason for time off" dropdown. + +### Step 4 (optional): Update leave-type choices + +If your organization uses different leave types than Vacation, Floating holiday, Sick leave, and Comp off, update two places: + +**The Adaptive Card dropdown** — in the `collect_time_off` node, edit the `choices` array. For example, to add Bereavement: + +```json +{ "title": "Bereavement", "value": "ESS_Bereavement" } +``` + +**The keyword mapping** — in the `set_leave_type_config` node, add a row so the agent can pre-select the type from the user's utterance: + +| Keywords (comma-separated) | LeaveTypeValue | +|---------------------------|----------------| +| `vacation,annual,pto,holiday pay` | `Vacation_Hours` | +| `floating,floater,float day` | `Floating_Holiday_Hours` | +| `sick,illness,medical,unwell,not feeling well` | `ESS_Sick_Time_Off` | + +If you add a new leave type, also add matching rows to `TimeOffReasonConfig` (one per ReasonKey). + +### Step 5 (optional): Update the Workday icon + +Find the `set_workday_icon_url` node and replace the 1×1 pixel placeholder with your organization's Workday icon URL. + +## Import and test the topic + +1. In Copilot Studio, go to **Topics > + Add a topic > From file**. +2. Select `topic.yaml` and import. +3. Open **Test your agent** and try these: + +| What to test | What to expect | +|-------------|---------------| +| Say "I want to request time off" | Balance header appears, then the Adaptive Card form | +| Say "Request vacation from September 15 to September 19" and submit | Success card with date range, type, hours, and reason | +| Submit with end date before start date | Error: "The end date can't be earlier than the start date" with retry | +| Select "I don't see my leave type listed" and submit | Redirect message with a link to Workday | +| Click **Cancel** | "Leave request was cancelled - nothing was submitted" | +| Submit dates that overlap an existing request | Friendly AI-rewritten error with retry | + +If balances show as empty but the form appears, your `PlanConfig` Plan IDs likely don't match your Workday tenant. + +## XML template reference + +The `msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml` file is a SOAP template for the Workday `Enter_Time_Off_Request` operation. You usually don't need to edit it — placeholder tokens like `{Employee_ID}` and `{timeoff_Dates}` are filled at runtime. + +Two settings you might want to change: + +| Setting | Default | Change if… | +|---------|---------|------------| +| `` | `false` (requires manager approval) | Your process skips approval — set to `true` | +| `` and `wd:version` | `v42.0` | Your tenant uses a different API version | + +{: .important } +> If you change the API version, update it in **both** the `` element and the `wd:version` attribute. Mismatched versions cause API errors. + +## What you can safely edit in the YAML + +| What | Where in `topic.yaml` | +|------|----------------------| +| Workday tenant URL | `set_workday_url` node | +| Plan IDs | `set_plan_config` node | +| Reason IDs | Second `set_time_off_reason_config` node | +| Leave-type keywords | `set_leave_type_config` node | +| Adaptive Card dropdown choices | `collect_time_off` node → `choices` array | +| Trigger phrases | `modelDescription` block | +| Workday icon | `set_workday_icon_url` node | +| Balance effective date | `set_as_of_effective_date` node (defaults to `Today()`) | + +**Don't** change these — they'll break the topic: + +- Node `id` values (`id: main`, `id: collect_time_off`, etc.) — referenced by `GotoAction` jumps +- `kind:` values — Copilot Studio node types +- `dialog:` references — point to the shared solution +- `output: binding:` mappings — must match shared dialog outputs +- XML placeholder tokens (`{Employee_ID}`, `{timeoff_Dates}`, etc.) — filled at runtime +- The `EmployeeName` input — the model uses it to validate the request is for the logged-in user + +## Dependencies + +This topic depends on three external components: + +- **Workday `Get_Time_Off_Plan_Balances`** — Absence_Management service, User-level auth. Uses the `msdyn_HRWorkdayHCMEmployeeGetVacationBalance` template from [EmployeeGetVacationBalance](../EmployeeGetVacationBalance/). Called at topic start to show balances. + +- **Workday `Enter_Time_Off`** — Absence_Management v42.0, User-level auth. Uses the `msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay` template in this folder. Called after form submission. + +- **`WorkdaySystemGetCommonExecution` dialog** — from the `msdyn_copilotforemployeeselfservicehr` solution. Executes XML templates against the Workday connector. Must be pre-installed. + +## Troubleshooting + +**Balance header is empty or shows all zeros** +Your `PlanConfig` Plan IDs don't match your Workday tenant, or the balance template isn't imported. Verify the IDs and confirm `msdyn_HRWorkdayHCMEmployeeGetVacationBalance` is in your ESS Template Configuration. + +**"Something went wrong" when submitting** +The `ReasonID` values in `TimeOffReasonConfig` don't match your Workday tenant. Check your IDs in Workday under **Setup > Time Off Reasons**. + +**Authentication error** +`Global.ESS_UserContext_Employee_Id` is empty or the Workday connector isn't configured. Verify both. + +**Vacation isn't pre-selected when the user says "request vacation"** +The keyword mapping uses `Vacation_Hours` but the dropdown value is `ESS_Vacation_Hours`. Update `LeaveTypeConfig` to use `ESS_Vacation_Hours`. See [Open Questions](#open-questions). + +**Success card shows a raw ID like `ESS_Vacation_Hours`** +The `Switch()` expression in the success card doesn't match the dropdown values. See [Open Questions](#open-questions). + +## Tips + +- Import the balance template **before** this topic — the balance lookup runs at topic start and fails silently if the template is missing. +- Update `PlanConfig`, `TimeOffReasonConfig`, **and** the Adaptive Card `choices` together — they're interconnected but maintained separately. +- Test with a real Workday employee ID that has leave balances. +- Keep `ReasonKey` values as `full_day`, `half_day_am`, `half_day_pm` — they're bound to the Adaptive Card dropdown. + +## Handle repo updates + +When this repo publishes a new version of the topic: + +1. **Diff first.** Your customized tenant URL, Plan IDs, Reason IDs, and leave-type keywords will be overwritten if you re-import `topic.yaml`. +2. **Merge.** Copy your config values from the current topic, pull the updated file, paste your values back, then import. +3. The XML template is safe to overwrite — unless you changed `Auto_Complete` or the API version. + +## Known limitations + +- **Leave type pre-selection may not work for Vacation and Floating Holiday.** The keyword mapping (`LeaveTypeConfig`) uses `Vacation_Hours` and `Floating_Holiday_Hours`, but the Adaptive Card dropdown expects `ESS_Vacation_Hours` and `ESS_Floating_Holiday_Hours`. To fix this, update the `LeaveTypeValue` entries in `LeaveTypeConfig` to include the `ESS_` prefix. + +- **Success card may show raw IDs instead of friendly names.** After a successful submission, the confirmation card may display `ESS_Vacation_Hours` instead of "Vacation". To work around this, update the `Switch()` expression in the success card to match the `ESS_`-prefixed values from the dropdown. diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml new file mode 100644 index 00000000..9d4a5342 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay.xml @@ -0,0 +1,61 @@ + + + + + User + + msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay + Absence_Management + v42.0 + + + + + //*[local-name()='Time_Off_Event_Reference']/*[local-name()='ID' and @*[local-name()='type']='WID'] + Event_WID + + + //*[local-name()='Time_Off_Entry_Reference'] + Entry_References + + + + + + + + + + false + true + true + + {timeoff_Comment} + + + + + {Employee_ID} + + + + + {timeoff_Date} + {timeoff_Hours_Per_Day} + + {timeoff_Time_Off_Type} + + + + + + {timeoff_Reason} + + {timeoff_Comment} + + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffAdapativeCard.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffAdapativeCard.png new file mode 100644 index 00000000..649b781b Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffAdapativeCard.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffSubmitted.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffSubmitted.png new file mode 100644 index 00000000..5b41ff1e Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/requestTimeOffSubmitted.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/request_time_off.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/request_time_off.png new file mode 100644 index 00000000..e2577532 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/request_time_off.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/topic.yaml new file mode 100644 index 00000000..a4be00d8 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeeRequestTimeOff/topic.yaml @@ -0,0 +1,910 @@ +kind: AdaptiveDialog +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: The name of the employee to fetch data for. + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + + - kind: AutomaticTaskInput + propertyName: InputStartDate + description: The start date of the time off request. + entity: DateTimePrebuiltEntity + shouldPromptUser: false + + - kind: AutomaticTaskInput + propertyName: InputEndDate + description: The end date of the time off request. + entity: DateTimePrebuiltEntity + shouldPromptUser: false + + - kind: AutomaticTaskInput + propertyName: InputHoursPerDay + description: The number of hours per day for the time off request. + entity: NumberPrebuiltEntity + shouldPromptUser: false + + - kind: AutomaticTaskInput + propertyName: InputTimeOffType + description: Extract the type of leave mentioned such as vacation, sick, sick leave, floating holiday, floater, PTO, annual leave, medical, or illness. Extract keywords like 'sick', 'vacation', 'floating', 'PTO', 'annual'. + entity: StringPrebuiltEntity + shouldPromptUser: false + + - kind: AutomaticTaskInput + propertyName: InputLeaveReason + description: The reason or comment for the time off request. Extract any reason, justification, or explanation the user provides for their leave such as 'family event', 'doctor appointment', 'personal', 'travel', etc. + entity: StringPrebuiltEntity + shouldPromptUser: false + +modelDescription: |- + Use this topic when the user wants to REQUEST, SUBMIT, APPLY FOR, BOOK, or TAKE THEIR OWN time off / leave in Workday (Absence_Management) — vacation, PTO, paid time off, holiday, sick leave, personal day, parental leave, bereavement leave, jury duty, sabbatical, or any absence type. + + Trigger phrases: + - "Request time off" + - "I need to submit time off" + - "Please request vacation from to " + - "Request sick leave for next week" + - "I need time off from to for " + - "Apply for / book / take a vacation / leave" + + If submitting for themselves, the topic may prompt for: time off type, start date, end date, reason, and hours. + + Data belongs exclusively to the requesting user. + + Do NOT trigger for: + - Requesting time off for someone else + - Viewing existing time-off balances or history (different topic) + - Cancelling/modifying a submitted request (different topic) + - General questions about leave policy or accruals +beginDialog: + kind: OnRecognizedIntent + id: main + intent: {} + actions: + - kind: SetVariable + id: set_workday_url + variable: Topic.WorkdayUrl + value: https://impl.workday.com//home.htmld + + - kind: SetVariable + id: set_workday_icon_url + variable: Topic.WorkdayIconUrl + value: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII= + + # ================================================================ + # DATE CONFIGURATION + # Set the effective date for balance lookup. Default is Today(). + # Customers can change this to a specific date if needed. + # ================================================================ + - kind: SetVariable + id: set_as_of_effective_date + variable: Topic.AsOfEffectiveDate + value: =Today() + + # ================================================================ + # REASON MAPPING CONFIGURATION + # Maps generic reason (Full Day / Half Day AM / Half Day PM) + + # Time Off Type to the correct Workday ReasonID. + # ReasonKey must match the dropdown values below. + # TimeOffTypeID must match the time off type dropdown values. + # ================================================================ + - kind: SetVariable + id: set_time_off_reason_config + variable: Topic.TimeOffReasonConfig + value: |- + =Table( + {ReasonKey: "full_day", TimeOffTypeID: "ESS_Floating_Holiday_Hours", ReasonID: "full_day_floating"}, + {ReasonKey: "half_day_am", TimeOffTypeID: "ESS_Floating_Holiday_Hours", ReasonID: "half_day_am_floating"}, + {ReasonKey: "half_day_pm", TimeOffTypeID: "ESS_Floating_Holiday_Hours", ReasonID: "half_day_pm_floating"}, + {ReasonKey: "full_day", TimeOffTypeID: "ESS_Comp_Off", ReasonID: "Full day-COI"}, + {ReasonKey: "half_day_am", TimeOffTypeID: "ESS_Comp_Off", ReasonID: "Half day AM-COI"}, + {ReasonKey: "half_day_pm", TimeOffTypeID: "ESS_Comp_Off", ReasonID: "Half day PM-COI"}, + {ReasonKey: "full_day", TimeOffTypeID: "ESS_Sick_Time_Off", ReasonID: "Full day_Sick_IN"}, + {ReasonKey: "half_day_am", TimeOffTypeID: "ESS_Sick_Time_Off", ReasonID: "Half day AM_Sick_IN"}, + {ReasonKey: "half_day_pm", TimeOffTypeID: "ESS_Sick_Time_Off", ReasonID: "Half day PM_Sick_IN"}, + {ReasonKey: "full_day", TimeOffTypeID: "ESS_Vacation_Hours", ReasonID: "Full Day_Vac"}, + {ReasonKey: "half_day_am", TimeOffTypeID: "ESS_Vacation_Hours", ReasonID: "Half Day_AM_Vac"}, + {ReasonKey: "half_day_pm", TimeOffTypeID: "ESS_Vacation_Hours", ReasonID: "Half Day_PM_Vac"} + ) + + # ================================================================ + # LEAVE TYPE CONFIGURATION + # Edit keywords below to customize how user phrases map to leave types. + # Keywords are comma-separated and case-insensitive. + # LeaveTypeValue must match the dropdown choice values. + # ================================================================ + - kind: SetVariable + id: set_leave_type_config + variable: Topic.LeaveTypeConfig + value: |- + =Table( + {Keywords: "vacation,annual,pto,holiday pay", LeaveTypeValue: "ESS_Vacation_Hours"}, + {Keywords: "floating,floater,float day", LeaveTypeValue: "ESS_Floating_Holiday_Hours"}, + {Keywords: "sick,illness,medical,unwell,not feeling well", LeaveTypeValue: "ESS_Sick_Time_Off"} + ) + + # Map extracted time off type to dropdown value using config table + - kind: SetVariable + id: set_mapped_time_off_type + variable: Topic.MappedTimeOffType + value: |- + =If( + IsBlank(Topic.InputTimeOffType), + "", + Coalesce( + First( + Filter( + Topic.LeaveTypeConfig, + !IsEmpty(Filter(Split(Keywords, ","), Trim(Value) in Lower(Topic.InputTimeOffType))) + ) + ).LeaveTypeValue, + "" + ) + ) + + - kind: BeginDialog + id: get_leave_balance + displayName: Get Leave Balance + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":""" & Text(Topic.AsOfEffectiveDate, "yyyy-MM-dd") & """}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetVacationBalance + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.balanceErrorResponse + isSuccess: Topic.balanceIsSuccess + workdayResponse: Topic.balanceResponse + + - kind: ParseValue + id: parse_balance + variable: Topic.parsedBalance + valueType: + kind: Record + properties: + PlanDescriptor: + type: + kind: Table + properties: + Value: String + + PlanID: + type: + kind: Table + properties: + Value: String + + RemainingBalance: + type: + kind: Table + properties: + Value: String + + UnitOfTime: + type: + kind: Table + properties: + Value: String + + value: =Topic.balanceResponse + + # ================================================================ + # PLAN BALANCE CONFIGURATION + # Maps Plan IDs (from balance API) to display names. + # Update PlanID values to match your Workday configuration. + # Only plans listed here AND returned by the API will be displayed. + # ================================================================ + - kind: SetVariable + id: set_plan_config + variable: Topic.PlanConfig + value: |- + =Table( + {PlanID: "ABSENCE_PLAN-6-139", DisplayName: "Floating holiday"}, + {PlanID: "ABSENCE_PLAN-6-159", DisplayName: "Sick leave"}, + {PlanID: "ABSENCE_PLAN-6-158", DisplayName: "Vacation"} + ) + + # Create merged balance table for easy lookup by PlanID + - kind: SetVariable + id: set_balance_table + variable: Topic.BalanceTable + value: |- + =ForAll( + Sequence(CountRows(Topic.parsedBalance.PlanID)), + { + PlanID: Index(Topic.parsedBalance.PlanID, Value).Value, + Balance: Index(Topic.parsedBalance.RemainingBalance, Value).Value + } + ) + + # Build display data: only include plans that exist in BOTH config AND API response + - kind: SetVariable + id: set_display_balances + variable: Topic.DisplayBalances + value: |- + =ForAll( + Filter( + Topic.PlanConfig, + !IsBlank(LookUp(Topic.BalanceTable, PlanID = ThisRecord.PlanID).Balance) + ) As plan, + { + PlanID: plan.PlanID, + DisplayName: plan.DisplayName, + Balance: LookUp(Topic.BalanceTable, PlanID = plan.PlanID).Balance + } + ) + + - kind: SetVariable + id: build_intro_message + variable: Topic.introMessage + value: ="Sure, I'll help you submit a time off request. Here's a form where you can choose the type of leave and dates you want off. Don't see the type of leave you want? [Book directly in Workday](" & Topic.WorkdayUrl & ")" + + - kind: SendActivity + id: intro_message + activity: "{Topic.introMessage}" + + - kind: ConditionGroup + id: need_inputs + conditions: + - id: always_true + condition: true + actions: + - kind: AdaptiveCardPrompt + id: collect_time_off + displayName: Ask time off type, start date, end date, reason and hours + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Book your time off", + weight: "Bolder", + size: "Medium", + wrap: true, + color: "Default" + }, + { + type: "Container", + style: "emphasis", + bleed: true, + items: [ + { + type: "TextBlock", + text: "Available balance in hours as of " & Text(Topic.AsOfEffectiveDate, "mmmm d, yyyy"), + size: "Small", + weight: "Bolder", + wrap: true + }, + { + type: "ColumnSet", + columns: ForAll( + Topic.DisplayBalances, + { + type: "Column", + width: "stretch", + items: [ + { type: "TextBlock", text: DisplayName, size: "Small", wrap: true }, + { type: "TextBlock", text: Text(Balance), weight: "Bolder", spacing: "Small" } + ] + } + ) + } + ] + }, + { + type: "TextBlock", + text: "Fields marked with * are required.", + wrap: true, + spacing: "Medium", + size: "Small" + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.ChoiceSet", + id: "timeOffType", + label: "Type of time off", + style: "compact", + value: Topic.MappedTimeOffType, + isRequired: true, + errorMessage: "Please select a type.", + choices: [ + { title: "Vacation", value: "ESS_Vacation_Hours" }, + { title: "Floating holiday", value: "ESS_Floating_Holiday_Hours" }, + { title: "Sick leave", value: "ESS_Sick_Time_Off" }, + { title: "Comp off", value: "ESS_Comp_Off" }, + { title: "I don't see my leave type listed", value: "NOT_LISTED" } + ] + } + ] + } + ] + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Date", + id: "startDate", + label: "Start date", + value: If(IsBlank(Topic.InputStartDate), "", Text(Topic.InputStartDate, "yyyy-MM-dd")), + isRequired: true, + errorMessage: "Please select a start date." + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Date", + id: "endDate", + label: "End date", + value: If(IsBlank(Topic.InputEndDate), "", Text(Topic.InputEndDate, "yyyy-MM-dd")), + isRequired: true, + errorMessage: "Please select an end date." + } + ] + } + ] + }, + { + type: "Input.Number", + id: "hoursPerDay", + label: "Hours per day", + min: 1, + max: 24, + value: If(IsBlank(Topic.InputHoursPerDay), 8, Value(Topic.InputHoursPerDay)), + isRequired: true, + errorMessage: "Please enter hours per day." + }, + { + type: "Input.ChoiceSet", + id: "timeOffReason", + label: "Reason for time off", + style: "compact", + isRequired: true, + errorMessage: "Please select a reason.", + choices: [ + { title: "Full Day", value: "full_day" }, + { title: "Half Day AM", value: "half_day_am" }, + { title: "Half Day PM", value: "half_day_pm" } + ] + }, + { + type: "Input.Text", + id: "leaveComment", + label: "Additional comments (optional)", + placeholder: "e.g. Details about your time off", + value: If(IsBlank(Topic.InputLeaveReason), "", Topic.InputLeaveReason), + isMultiline: true, + isRequired: false + }, + { + type: "ColumnSet", + spacing: "Medium", + columns: [ + { + type: "Column", + width: "auto", + items: [ + { + type: "ActionSet", + actions: [ + { type: "Action.Submit", title: "Submit", id: "Submit", data: { actionSubmitId: "Submit" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ] + } + ], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "stretch", + items: [], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "auto", + items: [ + { + type: "ColumnSet", + spacing: "None", + columns: [ + { + type: "Column", + width: "auto", + items: [ + { + type: "Image", + url: Topic.WorkdayIconUrl, + style: "RoundedCorners", + size: "Small", + height: "20px", + width: "20px" + } + ], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "auto", + items: [ + { + type: "TextBlock", + text: "Workday", + size: "Small", + color: "#242424", + weight: "Bolder" + } + ], + verticalContentAlignment: "Center", + spacing: "Small" + } + ] + } + ], + verticalContentAlignment: "Center" + } + ] + } + ], + actions: [] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId + endDate: Topic.endDate + hoursPerDay: Topic.hoursPerDay + leaveComment: Topic.leaveComment + startDate: Topic.startDate + timeOffReason: Topic.timeOffReason + timeOffType: Topic.timeOffType + + outputType: + properties: + actionSubmitId: String + endDate: String + hoursPerDay: String + leaveComment: String + startDate: String + timeOffReason: String + timeOffType: String + + - kind: ConditionGroup + id: handle_cancel + conditions: + - id: cancel_pressed + condition: =Topic.actionSubmitId = "Cancel" + actions: + - kind: SendActivity + id: cancel_processing_msg + activity: Processing cancellation... + + - kind: SendActivity + id: cancel_ack_msg + activity: Leave request was cancelled - nothing was submitted. + + - kind: SendActivity + id: cancel_followup_msg + activity: No worries, the form has been discarded and nothing was submitted to Workday. Want to start a new request or need anything else? + + - kind: EndDialog + id: end_on_cancel + + # Handle "I don't see my leave type listed" selection + - kind: ConditionGroup + id: handle_not_listed + conditions: + - id: type_not_listed + condition: =Topic.timeOffType = "NOT_LISTED" + actions: + - kind: SetVariable + id: set_not_listed_message + variable: Topic.notListedMessage + value: ="Since your leave type isn't listed here, you can book directly in [Workday](" & Topic.WorkdayUrl & "), or let me know if you want to change your type." + + - kind: SendActivity + id: not_listed_msg + activity: "{Topic.notListedMessage}" + + - kind: CancelAllDialogs + id: end_not_listed + + # Validate end date >= start date + - kind: ConditionGroup + id: validate_dates + conditions: + - id: end_before_start + condition: =!IsBlank(Topic.endDate) && !IsBlank(Topic.startDate) && DateValue(Topic.endDate) < DateValue(Topic.startDate) + actions: + - kind: SendActivity + id: date_error_msg + activity: The end date can't be earlier than the start date. + + # Ask user if they want to try again + - kind: Question + id: ask_retry_dates + interruptionPolicy: + allowInterruption: false + + variable: Topic.retryDateChoice + prompt: Would you like to try with different dates? + entity: BooleanPrebuiltEntity + + - kind: ConditionGroup + id: handle_retry_date_choice + conditions: + - id: user_wants_retry_dates + condition: =Topic.retryDateChoice = true + actions: + - kind: GotoAction + id: goto_form_on_date_retry + actionId: collect_time_off + + elseActions: + - kind: SendActivity + id: end_on_no_date_retry + activity: No problem! Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_on_no_date_retry + + # ======================================================== + # V3: Build list of dates and pass to Plugin + # ======================================================== + + # Map selected Time Off Type + Reason to Workday ReasonID + - kind: SetVariable + id: resolve_reason_id + variable: Topic.resolvedReasonID + value: =LookUp(Topic.TimeOffReasonConfig, ReasonKey = Topic.timeOffReason && TimeOffTypeID = Topic.timeOffType).ReasonID + + - kind: SetVariable + id: set_time_off_comment + variable: Topic.timeOffComment + value: =If(IsBlank(Topic.leaveComment), "ess generated time off request", Topic.leaveComment) + + # Initialize date list (empty string) + - kind: SetVariable + id: init_date_list + variable: Topic.dateList + value: ="" + + # Initialize loop counter + - kind: SetVariable + id: init_iterator + variable: Topic.newIterator + value: =0 + + # Set up loop variable + - kind: SetVariable + id: set_iterator + variable: Topic.iterator + value: =Topic.newIterator + + # Calculate current date for this iteration + - kind: SetVariable + id: calc_current_date + variable: Topic.currentDate + value: =Text(DateAdd(DateValue(Topic.startDate), Topic.iterator, TimeUnit.Days), "yyyy-MM-dd") + + # Loop to build comma-separated date list + - kind: ConditionGroup + id: build_date_list_loop + conditions: + - id: should_continue_building + condition: =DateValue(Topic.currentDate) <= DateValue(Topic.endDate) + actions: + # Append date to list (with comma separator if not first) + - kind: SetVariable + id: append_date + variable: Topic.dateList + value: =If(Topic.dateList = "", Topic.currentDate, Topic.dateList & "," & Topic.currentDate) + + # Increment counter + - kind: SetVariable + id: increment_iterator + variable: Topic.newIterator + value: =Topic.newIterator + 1 + + # Continue loop + - kind: GotoAction + id: goto_build_loop + actionId: set_iterator + + # Total days is the count from loop + - kind: SetVariable + id: calc_total_days + variable: Topic.totalDays + value: =Topic.newIterator + + # Make single API call - Plugin will build XML entries from date list + - kind: BeginDialog + id: execute_workday_multiday + displayName: Submit Multi-Day Time Off Request + input: + binding: + parameters: |- + ="{""params"":[" & + "{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """}," & + "{""key"":""{timeoff_Dates}"",""value"":""" & Topic.dateList & """}," & + "{""key"":""{timeoff_Time_Off_Type}"",""value"":""" & Topic.timeOffType & """}," & + "{""key"":""{timeoff_Hours_Per_Day}"",""value"":""" & Topic.hoursPerDay & """}," & + "{""key"":""{timeoff_Comment}"",""value"":""" & Topic.timeOffComment & """}," & + "{""key"":""{timeoff_Reason}"",""value"":""" & Topic.resolvedReasonID & """}" & + "]}" + scenarioName: msdyn_HRWorkdayAbsenceEnterTimeOff_MultiDay + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + # Parse error message for display + - kind: SetVariable + id: init_last_error + variable: Topic.lastErrorMessage + value: =If(Topic.isSuccess = true, "", Topic.errorResponse) + + - kind: SetVariable + id: parse_error_message + variable: Topic.friendlyErrorMessage + value: =IfError(Text(ParseJSON(Topic.lastErrorMessage).error.message), IfError(Text(ParseJSON(Topic.lastErrorMessage).message), Topic.lastErrorMessage)) + + - kind: ConditionGroup + id: report_result + conditions: + - id: request_failed + condition: =Topic.isSuccess = false + actions: + # Use AI to generate a friendly, conversational error message + - kind: AnswerQuestionWithAI + id: generate_friendly_error + autoSend: false + variable: Topic.aiGeneratedError + userInput: =Topic.friendlyErrorMessage + additionalInstructions: Reframe the following Workday error message in a friendly way for an employee. Keep it to ONE short sentence describing what went wrong. Do NOT include suggestions or next steps. Do NOT apologize. Do NOT use technical jargon. Example output format - "The dates you selected conflict with an existing request." + + # Set final error message with fallback + - kind: SetVariable + id: set_final_error_message + variable: Topic.finalErrorMessage + value: =If(IsBlank(Topic.aiGeneratedError), "The dates you selected aren't available.", Topic.aiGeneratedError) + + - kind: SendActivity + id: friendly_error_message + activity: "{Topic.finalErrorMessage}" + + # Ask user if they want to try again + - kind: Question + id: ask_retry + interruptionPolicy: + allowInterruption: false + + variable: Topic.retryChoice + prompt: Would you like to try with different dates? + entity: BooleanPrebuiltEntity + + - kind: ConditionGroup + id: handle_retry_choice + conditions: + - id: user_wants_retry + condition: =Topic.retryChoice = true + actions: + - kind: GotoAction + id: goto_form_on_retry + actionId: collect_time_off + + elseActions: + - kind: SendActivity + id: end_on_no_retry + activity: No problem! Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_on_no_retry + + elseActions: + - kind: SendActivity + id: success_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "✅ Request submitted", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Your time off request has been successfully submitted and is now pending approval.", + wrap: true, + spacing: "Small" + }, + { + type: "Table", + spacing: "Medium", + showGridLines: false, + firstRowAsHeader: false, + columns: [ + { width: 1 }, + { width: 2 } + ], + rows: [ + { + type: "TableRow", + cells: [ + { + type: "TableCell", + items: [{ type: "TextBlock", text: "Type of time off", weight: "Bolder" }] + }, + { + type: "TableCell", + items: [{ type: "TextBlock", text: Switch(Topic.timeOffType, "ESS_Vacation_Hours", "Vacation", "ESS_Floating_Holiday_Hours", "Floating holiday", "ESS_Sick_Time_Off", "Sick leave", "ESS_Comp_Off", "Comp off", Topic.timeOffType), wrap: true }] + } + ] + }, + { + type: "TableRow", + cells: [ + { + type: "TableCell", + items: [{ type: "TextBlock", text: "Time off date range", weight: "Bolder" }] + }, + { + type: "TableCell", + items: [{ type: "TextBlock", text: Text(DateValue(Topic.startDate), "mmmm d, yyyy") & " to " & Text(DateValue(Topic.endDate), "mmmm d, yyyy") & " (" & Text(Topic.totalDays) & " days)", wrap: true }] + } + ] + }, + { + type: "TableRow", + cells: [ + { + type: "TableCell", + items: [{ type: "TextBlock", text: "Total hours", weight: "Bolder" }] + }, + { + type: "TableCell", + items: [{ type: "TextBlock", text: Text(Topic.totalDays * Value(Topic.hoursPerDay)) & " hours (" & Text(Topic.totalDays) & " x " & Topic.hoursPerDay & "-hour days)", wrap: true }] + } + ] + }, + { + type: "TableRow", + cells: [ + { + type: "TableCell", + items: [{ type: "TextBlock", text: "Reason", weight: "Bolder" }] + }, + { + type: "TableCell", + items: [{ type: "TextBlock", text: Switch(Topic.timeOffReason, "full_day", "Full Day", "half_day_am", "Half Day AM", "half_day_pm", "Half Day PM", Topic.timeOffReason), wrap: true }] + } + ] + }, + { + type: "TableRow", + cells: [ + { + type: "TableCell", + items: [{ type: "TextBlock", text: "Comments", weight: "Bolder" }] + }, + { + type: "TableCell", + items: [{ type: "TextBlock", text: If(IsBlank(Topic.leaveComment), "—", Topic.leaveComment), wrap: true }] + } + ] + } + ] + }, + { + type: "Container", + horizontalAlignment: "Right", + items: [ + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "auto", + items: [ + { + type: "Image", + url: Topic.WorkdayIconUrl, + style: "RoundedCorners", + size: "Small", + height: "20px", + width: "20px" + } + ], + verticalContentAlignment: "Center" + }, + { + type: "Column", + width: "auto", + items: [ + { + type: "TextBlock", + text: "Workday", + size: "Small", + color: "#242424", + weight: "Bolder" + } + ], + verticalContentAlignment: "Center", + spacing: "Small" + } + ] + } + ] + } + ], + actions: [] + } + + - kind: SendActivity + id: follow_up_msg + activity: Can I help you submit another request? + + - kind: CancelAllDialogs + id: end_dialogs + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: The name of the employee to fetch data for. + type: String + + InputEndDate: + displayName: InputEndDate + description: The end date of the time off request. + type: DateTime + + InputHoursPerDay: + displayName: InputHoursPerDay + description: The number of hours per day for the time off request. + type: Number + + InputLeaveReason: + displayName: InputLeaveReason + description: Additional comments or context for the time off request. + type: String + + InputStartDate: + displayName: InputStartDate + description: The start date of the time off request. + type: DateTime + + InputTimeOffType: + displayName: InputTimeOffType + description: The type of time off being requested. + type: String + +outputType: {} \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/msdyn_HRWorkdayHCMEmployeeGetJobTaxonomy.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/msdyn_HRWorkdayHCMEmployeeGetJobTaxonomy.xml new file mode 100644 index 00000000..627e54d8 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/msdyn_HRWorkdayHCMEmployeeGetJobTaxonomy.xml @@ -0,0 +1,50 @@ + + + + + + User + + Template_GetWorkerRequest + Human_Resources + v42.0 + + + + //*[local-name()="Position_Title"]/text() + JobTitle + + + //*[local-name()="Business_Title"]/text() + BusinessTitle + + + //*[local-name()="Job_Profile_Name"]/text() + JobProfile + + + //*[local-name()='Job_Profile_Summary_Data']/*[local-name()='Job_Family_Reference']/*[local-name()='ID' and @*[local-name()='type']='Job_Family_ID']/text() + JobFamilyId + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/topic.yaml new file mode 100644 index 00000000..82e9f17a --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayEmployeesviewtheirjobtaxonomy/topic.yaml @@ -0,0 +1,138 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when the user asks about THEIR OWN job function / job family / job profile / job classification / job taxonomy / role taxonomy / internal title / external title / job code / job category stored in Workday. Data belongs exclusively to the requesting user. + + Valid: "What is my external title?", "What's my internal title?", "What's my job function / job family / job profile / job category?", "Show me my job classification", "What is my job code?", "What's my role taxonomy?" + + do NOT trigger for general HR policy or org-structure questions they are out of scope. + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - What is my job title? + - What job function do I belong to? + - What is my job function? + - What job category do I belong to? + - What is my role? + - What job Family do I belong to? + - What is my Job Profile? + - What is my internal title? + - What is my external title? + + actions: + - kind: BeginDialog + id: 7R6DUf + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetJobTaxonomy + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: tLP86E + displayName: Parse job profiles + variable: Topic.workdayResponseTable + valueType: + kind: Record + properties: + BusinessTitle: + type: + kind: Table + properties: + Value: String + + JobFamilyId: + type: + kind: Table + properties: + Value: String + + JobProfile: + type: + kind: Table + properties: + Value: String + + JobTitle: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_FRHCD9 + variable: Topic.transformResponse + value: |- + ={ + JobTitle: First(Topic.workdayResponseTable.JobTitle).Value, + BusinessTitle: First(Topic.workdayResponseTable.BusinessTitle).Value, + JobProfile: First(Topic.workdayResponseTable.JobProfile).Value, + JobFamilyId: First(Topic.workdayResponseTable.JobFamilyId).Value + } + + - kind: BeginDialog + id: RZJGDY + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Job_Family_ID}"",""value"":""" & Topic.transformResponse.JobFamilyId & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetJobTaxonomyGeneric + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: rPHxhw + displayName: Parse Job Family Name + variable: Topic.jobFamilyTable + valueType: + kind: Record + properties: + JobFamily: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_mS3r3D + displayName: Init Job Taxonomy Data + variable: Topic.jobFunctionData + value: |- + ={ + EmployeeName: Global.ESS_UserContext_Employee_Firstname & " " & Global.ESS_UserContext_Employee_Lastname, + JobTitle: Topic.transformResponse.JobTitle, + JobProfile: Topic.transformResponse.JobProfile, + BusinessTitle: Topic.transformResponse.BusinessTitle, + JobFamily: First(Topic.jobFamilyTable.JobFamily).Value + } + +outputType: + properties: + jobFunctionData: + displayName: jobFunctionData + type: + kind: Record + properties: + BusinessTitle: String + EmployeeName: String + JobFamily: String + JobProfile: String + JobTitle: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/msdyn_HRWorkdayHCMEmployeeGetHomeContactInformation.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/msdyn_HRWorkdayHCMEmployeeGetHomeContactInformation.xml new file mode 100644 index 00000000..f218282a --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/msdyn_HRWorkdayHCMEmployeeGetHomeContactInformation.xml @@ -0,0 +1,55 @@ + + + + + + User + + msdyn_HRWorkdayHCMEmployeeGetHomeContactInformationRequest + Human_Resources + v42.0 + + + + //*[local-name()='Phone_Information_Data'] + PhoneInformation + + + //*[local-name()='Email_Information_Data'] + EmailInformation + + + //@*[local-name()='Formatted_Address'] + HomeAddress + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + 1 + 999 + + + + false + false + false + false + false + true + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/topic.yaml new file mode 100644 index 00000000..389dd218 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetContactInformation/topic.yaml @@ -0,0 +1,421 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when the user asks to VIEW or READ THEIR OWN contact information stored in Workday — home/personal address, mailing address, alternate address, personal email, work email, personal phone, mobile/cell phone, primary phone, secondary phone, work phone, home phone, emergency contact, or any contact-profile field. "Home" and "Personal" are synonymous. + + Trigger on read-only queries about "my" contact info: "What is my address?", "Show my email on file", "What's my phone number in Workday?", "What contact info do I have?", "Show my mobile number", "What's my work email?", "Display my contact details". + + Data is retrieved from Workday and belongs exclusively to the requesting user. + + Do NOT trigger for: + - Another person's contact info +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: The name of the employee whose data will be fetched + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show my Contact details? + - What is my Work Phone? + - What is my Work Email? + - What is my Work Address? + - Show my Work Contact information? + - Show my Personal Phone number? + - What is my Home Phone number? + - Show my Home Email? + - Can you tell me what my Personal Email is ? + - Which Personal email is registered as primary? + - Show my Home address? + - Show my Personal Contact Information? + - Show [EmployeeName]'s contact info + + actions: + - kind: BeginDialog + id: uzpKxp + displayName: Check user access + input: + binding: + EmployeeName: =Topic.EmployeeName + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemAccessCheck + + - kind: BeginDialog + id: Z6efSa + displayName: Fetch Work Contact Information from Workday + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMEmployeeGetWorkContactInformation" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: b90ZAp + displayName: Parse Work Contact data + variable: Topic.workContactData + valueType: + kind: Record + properties: + EmailInformation: + type: + kind: Table + properties: + Email_Data: + type: + kind: Record + properties: + Email_Address: String + + Email_ID: String + Email_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Usage_Data: + type: + kind: Record + properties: + "@Public": String + Type_Data: + type: + kind: Record + properties: + "@Primary": String + Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + PhoneInformation: + type: + kind: Table + properties: + Phone_Data: + type: + kind: Record + properties: + "@Formatted_Phone": String + Complete_Phone_Number: String + Country_Code_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Device_Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Phone_ID: String + Phone_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Usage_Data: + type: + kind: Table + properties: + "@Public": String + Type_Data: + type: + kind: Record + properties: + "@Primary": String + Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + value: =Topic.workdayResponse + + - kind: BeginDialog + id: tZ3bkQ + displayName: Fetch Home Contact Information from Workday + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMEmployeeGetHomeContactInformation" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: D15SKZ + displayName: Parse Home Contact data + variable: Topic.homeContactData + valueType: + kind: Record + properties: + EmailInformation: + type: + kind: Table + properties: + Email_Data: + type: + kind: Record + properties: + Email_Address: String + Email_Comment: String + + Email_ID: String + Email_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Usage_Data: + type: + kind: Record + properties: + "@Public": String + Type_Data: + type: + kind: Record + properties: + "@Primary": String + Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + HomeAddress: + type: + kind: Table + properties: + Value: String + + PhoneInformation: + type: + kind: Table + properties: + Phone_Data: + type: + kind: Record + properties: + "@Formatted_Phone": String + Complete_Phone_Number: String + Country_Code_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Device_Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Phone_ID: String + Phone_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Usage_Data: + type: + kind: Table + properties: + "@Public": String + Type_Data: + type: + kind: Record + properties: + "@Primary": String + Type_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + value: =Topic.workdayResponse + + - kind: BeginDialog + id: p2xAZU + displayName: Fetch Work Address + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMEmployeeGetWorkAddress" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: 3IdZeI + displayName: Parse Work Address + variable: Topic.workAddress + valueType: + kind: Record + properties: + WorkAddress: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_LXnWnT + displayName: Set final data + variable: Topic.finalizedContactInformation + value: |- + ={ + employeeName: Global.ESS_UserContext_Employee_Firstname & " " & Global.ESS_UserContext_Employee_Lastname, + workPhoneNumbers: ForAll(Topic.workContactData.PhoneInformation, {PhoneNumber: ThisRecord.Phone_Data.'@Formatted_Phone', IsPrimaryPhoneNumber: CountIf(ThisRecord.Usage_Data, Type_Data.'@Primary' = "1") > 0}), + workEmails: ForAll(Topic.workContactData.EmailInformation, {EmailAddress: ThisRecord.Email_Data.Email_Address, IsPrimaryEmailAddress: ThisRecord.Usage_Data.Type_Data.'@Primary'}), + workAddress: First(Topic.workAddress.WorkAddress).Value, + homePhoneNumbers: ForAll(Topic.homeContactData.PhoneInformation, {PhoneNumber: ThisRecord.Phone_Data.'@Formatted_Phone', IsPrimaryPhoneNumber: CountIf(ThisRecord.Usage_Data, Type_Data.'@Primary' = "1") > 0}), + homeEmails: ForAll(Topic.homeContactData.EmailInformation, {EmailAddress: ThisRecord.Email_Data.Email_Address, IsPrimaryEmailAddress: ThisRecord.Usage_Data.Type_Data.'@Primary'}), + homeAddress: First(Topic.homeContactData.HomeAddress).Value + } + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: The name of the employee whose data will be fetched + type: String + +outputType: + properties: + finalizedContactInformation: + displayName: finalizedContactInformation + type: + kind: Record + properties: + employeeName: String + homeAddress: String + homeEmails: + type: + kind: Table + properties: + EmailAddress: String + IsPrimaryEmailAddress: String + + homePhoneNumbers: + type: + kind: Table + properties: + IsPrimaryPhoneNumber: Boolean + PhoneNumber: String + + workAddress: String + workEmails: + type: + kind: Table + properties: + EmailAddress: String + IsPrimaryEmailAddress: String + + workPhoneNumbers: + type: + kind: Table + properties: + IsPrimaryPhoneNumber: Boolean + PhoneNumber: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/msdyn_HRWorkdayHCMEmployeeGetEducation.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/msdyn_HRWorkdayHCMEmployeeGetEducation.xml new file mode 100644 index 00000000..d32311db --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/msdyn_HRWorkdayHCMEmployeeGetEducation.xml @@ -0,0 +1,38 @@ + + + + + + User + + HRWorkdayHCMEmployeeGetEducationRequest + Human_Resources + v41.0 + + + + //*[local-name()='Worker']/*[local-name()='Worker_Data']/*[local-name()='Qualification_Data']/*[local-name()='Education'] + EducationData + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/topic.yaml new file mode 100644 index 00000000..dae12482 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetEducation/topic.yaml @@ -0,0 +1,242 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when the user asks to VIEW or READ THEIR OWN education / academic history / schooling stored in Workday — degree (bachelor's, master's, MBA, PhD), diploma, certificate, school/university/college/institution, alma mater, major, field of study, minor, graduation year/date, GPA, academic qualifications, or credentials. + + Trigger on "my" education-related queries: "What's my education?", "Show my degree", "Where did I study?", "What university is on my profile?", "What's my major / field of study?", "Show my academic background / qualifications / credentials", "What schooling do I have on file?". + + Data is retrieved from Workday and belongs exclusively to the requesting user. + + Do NOT trigger for general questions about tuition reimbursement, learning programs, or training +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: The name of the person whose employment data is being requested. + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show my Education Details + - From which school I completed my BS degree Education? + - Show my Education Degrees + - What was my field of Study during Education? + - Show my First Year attended for BS degree Education? + - Show my Last Year attended for BS degree Education? + + actions: + - kind: BeginDialog + id: zQU3vE + displayName: Check access + input: + binding: + EmployeeName: =Topic.EmployeeName + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemAccessCheck + + - kind: BeginDialog + id: B7ae9T + displayName: Redirect to Get CommonExecution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMEmployeeGetEducation" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: yJ0sSR + displayName: Parse Workday response + variable: Topic.parsedWorkdayResponse + valueType: + kind: Record + properties: + EducationData: + type: + kind: Table + properties: + Education_Data: + type: + kind: Table + properties: + Country_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Date_Degree_Received: String + Degree_Completed_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Degree_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Education_ID: String + Field_Of_Study_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + First_Year_Attended: String + Is_Highest_Level_of_Education: String + Last_Year_Attended: String + Location: String + School_Name: String + School_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Education_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + - kind: BeginDialog + id: M8G7as + displayName: Refresh country name reference data + input: + binding: + IsTableEmpty: =IsBlank(Global.CountryNameLookupTable) + ReferenceDataKey: ISO_3166-1_Alpha-2_Code + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: BeginDialog + id: iM5A5i + displayName: Refresh degree reference data + input: + binding: + IsTableEmpty: =IsBlank(Global.DegreeTypeLookupTable) + ReferenceDataKey: Degree_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: BeginDialog + id: zG3du7 + displayName: Refresh field of study reference data + input: + binding: + IsTableEmpty: =IsBlank(Global.FieldOfStudyLookupTable) + ReferenceDataKey: Field_Of_Study_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: SetVariable + id: setVariable_nD5Gkb + displayName: Set final result data + variable: Topic.FinalizedEducationData + value: | + =ForAll( + Topic.parsedWorkdayResponse.EducationData, + ForAll( + ThisRecord.Education_Data, + With( + {currentRecord: ThisRecord}, + { + EmployeeName: Global.ESS_UserContext_Employee_Firstname & " " & Global.ESS_UserContext_Employee_Lastname, + Country: LookUp( + Global.CountryNameLookupTable, + ID = LookUp( + currentRecord.Country_Reference.ID, + '@type' = First(Global.CountryNameLookupTable).Reference_ID_Type + ).'#text' + ).Referenced_Object_Descriptor, + School: currentRecord.School_Name, + Degree: LookUp( + Global.DegreeTypeLookupTable, + ID = LookUp( + currentRecord.Degree_Reference.ID, + '@type' = First(Global.DegreeTypeLookupTable).Reference_ID_Type + ).'#text' + ).Referenced_Object_Descriptor, + FieldOfStudy: LookUp( + Global.FieldOfStudyLookupTable, + ID = LookUp( + currentRecord.Field_Of_Study_Reference.ID, + '@type' = First(Global.FieldOfStudyLookupTable).Reference_ID_Type + ).'#text' + ).Referenced_Object_Descriptor, + FirstYearAttended: currentRecord.First_Year_Attended, + LastYearAttended: currentRecord.Last_Year_Attended + } + ) + ) + ) + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: The name of the person whose employment data is being requested. + type: String + +outputType: + properties: + FinalizedEducationData: + displayName: FinalizedEducationData + type: + kind: Table + properties: + Value: + type: + kind: Table + properties: + Country: String + Degree: String + EmployeeName: String + FieldOfStudy: String + FirstYearAttended: String + LastYearAttended: String + School: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/msdyn_HRWorkdayHCMEmployeeGetGovernmentIds.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/msdyn_HRWorkdayHCMEmployeeGetGovernmentIds.xml new file mode 100644 index 00000000..3eca23e4 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/msdyn_HRWorkdayHCMEmployeeGetGovernmentIds.xml @@ -0,0 +1,38 @@ + + + + + + User + + Template_GetPersonalInformationRequest + Human_Resources + v42.0 + + + + //*[local-name()='Government_ID']/*[local-name()='Government_ID_Data'] + GovernmentId + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/topic.yaml new file mode 100644 index 00000000..507d6e77 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetGovernmentIDs/topic.yaml @@ -0,0 +1,162 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when the user asks about THEIR OWN government-issued IDs / immigration documents / identity documents stored in Workday — Social Security Number (SSN), national ID, passport, driver's license, tax ID, PAN card, Aadhaar, National Insurance (NI) number, SIN, visa, work visa, work permit, work authorization, green card, permanent resident (PR) card, or related fields. Data belongs exclusively to the requesting user. + + Trigger on "my" government-ID queries: "What is my SSN / PAN / Aadhaar / NI number?", "What's my passport number?", "When does my visa / work permit expire?", "Show my driver's license on file", "What's my tax ID?", "What government IDs are on my Workday profile?", "What's my green card / PR status?". + + Do NOT trigger for general questions about ID applications, renewals, immigration law, or work authorization policy +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: The name of the employee whose data is being requested. + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - What are my Government Ids? + - Which Government ids are available on my profile? + - Show my Government Ids? + - Show me [EmployeeName]'s government Ids + + actions: + - kind: BeginDialog + id: GlRaX3 + displayName: Check user access + input: + binding: + EmployeeName: =Topic.EmployeeName + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemAccessCheck + + - kind: BeginDialog + id: 3bFDxH + displayName: Redirect to Workday System Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMEmployeeGetGovernmentIds" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: v0OBu2 + displayName: Parse workdayResponse into table + variable: Topic.parsedWorkdayResponse + valueType: + kind: Record + properties: + GovernmentId: + type: + kind: Table + properties: + Country_Reference: + type: + kind: Record + properties: + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Expiration_Date: String + ID: String + ID_Type_Reference: + type: + kind: Record + properties: + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Issued_Date: String + + value: =Topic.workdayResponse + + - kind: BeginDialog + id: 6k3mYE + displayName: Refresh country name reference data + input: + binding: + IsTableEmpty: =IsBlank(Global.CountryNameLookupTable) + ReferenceDataKey: ISO_3166-1_Alpha-2_Code + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: BeginDialog + id: Xoydcu + displayName: Refresh government ID type data + input: + binding: + IsTableEmpty: =IsBlank(Global.GovernmentIdTypeLookupTable) + ReferenceDataKey: Government_ID_Type_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: SetVariable + id: setVariable_ZYrAxQ + displayName: Get values from reference tables + variable: Topic.finalizedResponseTableData + value: |- + =ForAll( + Topic.parsedWorkdayResponse.GovernmentId, + With( + {currentRecord: ThisRecord}, + { + EmployeeName: Global.ESS_UserContext_Employee_Firstname & " " & Global.ESS_UserContext_Employee_Lastname, + CountryName: LookUp( + Global.CountryNameLookupTable, + ID = LookUp( + currentRecord.Country_Reference.ID, + '@type' = First(Global.CountryNameLookupTable).Reference_ID_Type + ).'#text' + ).Referenced_Object_Descriptor, + ExpirationDate: currentRecord.Expiration_Date, + IDType: LookUp( + Global.GovernmentIdTypeLookupTable, + ID = LookUp( + currentRecord.ID_Type_Reference.ID, + '@type' = First(Global.GovernmentIdTypeLookupTable).Reference_ID_Type + ).'#text' + ).Referenced_Object_Descriptor, + IssuedDate: currentRecord.Issued_Date, + ID: ID + } + ) + ) + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: The name of the employee whose data is being requested. + type: String + +outputType: + properties: + finalizedResponseTableData: + displayName: finalizedResponseTableData + type: + kind: Table + properties: + CountryName: String + EmployeeName: String + ExpirationDate: String + ID: String + IDType: String + IssuedDate: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/README.md new file mode 100644 index 00000000..ccb49768 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/README.md @@ -0,0 +1,168 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Get User Profile + +This scenario enables employees to retrieve their profile information from Workday through a conversational interface. It provides comprehensive employee data including personal details, employment information, contact details, and tenure calculations. + +## Features + +- **Personal Information**: Name, Date of Birth, Gender +- **Employment Details**: Employee ID, Business Title, Organization/Department, Manager, Location +- **Contact Information**: Work Email, Work Phone, Home Email, Home Phone, Home Address +- **Employment Status**: Active/Inactive status, Hire Date +- **Tenure Calculation**: Continuous Service Date and calculated Length of Service (years, months, days) + +## Trigger Phrases + +Users can activate this topic with phrases like: +- "What is my profile?" +- "Show my profile" +- "What is my employee ID?" +- "What is my job title?" +- "What is my work email?" +- "Who is my manager?" +- "What department am I in?" +- "What is my tenure?" +- "How long have I been with the company?" +- "What is my hire date?" +- "Am I an active employee?" + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Main Copilot Studio topic definition | +| `msdyn_HRWorkdayHCMEmployeeGetUserProfile.xml` | XML template with XPath extractions for profile data | + +## Workday APIs Used + +| API | Service | Version | Purpose | +|-----|---------|---------|---------| +| `Get_Workers` | Human_Resources | v45.0 | Retrieve comprehensive employee profile data | + +## Data Retrieved + +The topic extracts the following fields from Workday: + +| Field | XPath Source | Description | +|-------|--------------|-------------| +| `EmployeeID` | Worker_Data/Worker_ID | Employee's Workday ID | +| `Name` | Worker_Descriptor | Employee's full name | +| `DOB` | Personal_Information_Data/Birth_Date | Date of birth | +| `Gender` | Gender_Reference/@Descriptor | Gender | +| `BusinessTitle` | Position_Data/Business_Title | Job title | +| `Organization` | Organization_Reference (SUPERVISORY_ORGANIZATION) | Department/Org | +| `Manager` | Manager_Reference/@Descriptor | Direct manager's name | +| `Location` | Location_Reference/@Descriptor | Work location | +| `HireDate` | Worker_Status_Data/Hire_Date | Original hire date | +| `WorkEmail` | Email_Address (WORK usage type) | Work email address | +| `HomeAddress` | Address_Data (HOME usage type) | Formatted home address | +| `HomeEmail` | Email_Address (HOME usage type, primary) | Personal email | +| `HomePhone` | Phone_Data (HOME usage type, primary) | Home phone number | +| `WorkPhone` | Phone_Data (WORK usage type, primary) | Work phone number | +| `Status` | Worker_Status_Data/Active | Employment status (Active/Inactive) | +| `ContinuousServiceDate` | Worker_Status_Data/Continuous_Service_Date | Service start date | +| `LengthOfService` | *Calculated* | Years, months, days of service | + +## Flow Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Triggers Topic │ +│ (e.g., "What is my profile?") │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Call Get_Workers API │ +│ (with Employee_ID and As_Of_Date) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Parse Response via XPath │ +│ (Extract all profile fields) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Calculate Length of Service │ +│ (Years, Months, Days from Continuous Service Date) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Return finalizedData Record │ +│ (All fields available for AI to respond) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Length of Service Calculation + +The topic automatically calculates the employee's length of service from their Continuous Service Date: + +``` +Years: RoundDown(DateDiff(ServiceDate, Today, Months) / 12, 0) +Months: Mod(DateDiff(ServiceDate, Today, Months), 12) +Days: DateDiff(AdjustedDate, Today, Days) +``` + +Output format: "X year(s) Y month(s) Z day(s)" + +## Dependencies + +This topic requires the following system topics/dialogs: +- `msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution` - For executing Workday API calls + +## Global Variables Used + +| Variable | Description | +|----------|-------------| +| `Global.ESS_UserContext_Employee_Id` | The logged-in employee's Workday Employee ID | + +## Output + +The topic outputs a `finalizedData` record containing all profile fields that can be used by the AI orchestrator to formulate responses based on what the user specifically asked for. + +```yaml +outputType: + properties: + finalizedData: + type: Record + properties: + EmployeeID: String + Name: String + DOB: String + Gender: String + BusinessTitle: String + Organization: String + Manager: String + Location: String + HireDate: String + WorkEmail: String + HomeAddress: String + HomeEmail: String + HomePhone: String + WorkPhone: String + Status: String + ContinuousServiceDate: String + LengthOfService: String +``` + +## Important Notes + +1. **Privacy**: This topic only returns data for the requesting user. Questions about other employees (managers, colleagues) are explicitly rejected per the model description. + +2. **Tenure Information**: Length of Service is only included in the AI's response when the user specifically asks about tenure, service length, or how long they've been with the company. + +3. **Status Conversion**: The raw `Active` field from Workday (1 or 0) is converted to human-readable "Active" or "Inactive". + +4. **Response Optimization**: The Get_Workers request is optimized to exclude unnecessary data (benefits, qualifications, photos, etc.) to improve performance. + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0 | December 2025 | Initial release with comprehensive profile retrieval | diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/msdyn_HRWorkdayHCMEmployeeGetUserProfile.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/msdyn_HRWorkdayHCMEmployeeGetUserProfile.xml new file mode 100644 index 00000000..37412e3d --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/msdyn_HRWorkdayHCMEmployeeGetUserProfile.xml @@ -0,0 +1,131 @@ + + + + + + User + + Template_GetWorkerRequest + Human_Resources + v45.0 + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + EmployeeID + + + //*[local-name()='Get_Workers_Response']/*[local-name()='Response_Data']/*[local-name()='Worker'][1]/*[local-name()='Worker_Descriptor']/text() + Name + + + //*[local-name()='Personal_Information_Data']/*[local-name()='Birth_Date']/text() + DOB + + + //*[local-name()='Personal_Information_For_Country_Data']/*[local-name()='Country_Personal_Information_Data']/*[local-name()='Gender_Reference']/@*[local-name()='Descriptor'] + Gender + + + //*[local-name()='Position_Data']/*[local-name()='Business_Title']/text() + BusinessTitle + + + //*[local-name()='Worker_Organization_Data']/*[local-name()='Organization_Reference']/*[local-name()='ID'][@*[local-name()='type']='Organization_Reference_ID' and contains(text(),'SUPERVISORY_ORGANIZATION')]/../@*[local-name()='Descriptor'] + Organization + + + //*[local-name()='Worker_Supervisory_Management_Chain_Data']/*[local-name()='Management_Chain_Data']/*[local-name()='Manager_Reference']/@*[local-name()='Descriptor'] + Manager + + + //*[local-name()='Business_Site_Summary_Data']/*[local-name()='Location_Reference']/*[local-name()='ID'][@*[local-name()='type']='Location_ID']/../@*[local-name()='Descriptor'] + Location + + + //*[local-name()='Worker_Status_Data']/*[local-name()='Hire_Date']/text() + HireDate + + + //*[local-name()='Email_Address_Data'][*[local-name()='Usage_Data']/*[local-name()='Type_Data']/*[local-name()='Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Communication_Usage_Type_ID' and text()='WORK']]/*[local-name()='Email_Address']/text() + WorkEmail + + + //*[local-name()='Address_Data'][*[local-name()='Usage_Data']/*[local-name()='Type_Data']/*[local-name()='Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Communication_Usage_Type_ID' and text()='HOME']]/@*[local-name()='Formatted_Address'] + HomeAddress + + + //*[local-name()='Email_Address_Data'][*[local-name()='Usage_Data']/*[local-name()='Type_Data'][@*[local-name()='Primary']='1']/*[local-name()='Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Communication_Usage_Type_ID' and text()='HOME']]/*[local-name()='Email_Address']/text() + HomeEmail + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Contact_Data']/*[local-name()='Phone_Data'][*[local-name()='Usage_Data']/*[local-name()='Type_Data'][@*[local-name()='Primary']='1']/*[local-name()='Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Communication_Usage_Type_ID' and text()='HOME']]/@*[local-name()='Tenant_Formatted_Phone'] + HomePhone + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Contact_Data']/*[local-name()='Phone_Data'][*[local-name()='Usage_Data']/*[local-name()='Type_Data'][@*[local-name()='Primary']='1']/*[local-name()='Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Communication_Usage_Type_ID' and text()='WORK']]/@*[local-name()='Tenant_Formatted_Phone'] + WorkPhone + + + //*[local-name()='Worker_Status_Data']/*[local-name()='Active']/text() + Status + + + //*[local-name()='Worker_Status_Data']/*[local-name()='Continuous_Service_Date']/text() + ContinuousServiceDate + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + false + true + true + false + true + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/topic.yaml new file mode 100644 index 00000000..bdcbd766 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGetUserProfile/topic.yaml @@ -0,0 +1,234 @@ +kind: AdaptiveDialog +modelDescription: |- + Returns user's personal profile from Workday: identity, contact, and org info only. + + Fields: Name, Emp ID, DOB, Gender, Title, Organization, Manager, Location, Email, Phone, Home Address. + + DO NOT route here for: job level, grade, band, FTE, rehire date, exempt status, employment type, hire date + + For FULL PROFILE: + Heading = ""Your employee profile"" + Intro = ""Here's what I found in Workday, an HR platform your company uses."" + Sections: Personal information, Role and work. + + For TENURE/POSITION: + Heading = ""Time in your position"" + Intro = ""You've been in your current role as a [Position] for [LengthOfService]."" + Sections: About your position + + Rules: + - Large heading, sentence case, section divider below + - Bold section headings, non-bold sub bullets + - Address in one row, intro after heading + - Only relevant fields unless full profile requested" +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - What is my profile? + - Show my profile + - What is my employee ID? + - What is my job title? + - What is my work email? + - What is my manager's name? + - Who is my manager? + - What department am I in? + - What is my organization? + - What is my work phone number? + - What is my home address? + - What is my hire date? + - When did I start working here? + - What is my tenure? + - How long have I been with the company? + - What is my length of service? + - Am I an active employee? + - What is my employment status? + - What is my date of birth? + - What is my gender? + - What is my location? + - Where do I work? + + actions: + - kind: BeginDialog + id: dZAI4Y + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetUserProfile + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: fP9fKn + displayName: Parse workdayResponse + variable: Topic.parsedWorkdayResponse + valueType: + kind: Record + properties: + EmployeeID: + type: + kind: Table + properties: + Value: String + Name: + type: + kind: Table + properties: + Value: String + DOB: + type: + kind: Table + properties: + Value: String + Gender: + type: + kind: Table + properties: + Value: String + BusinessTitle: + type: + kind: Table + properties: + Value: String + Organization: + type: + kind: Table + properties: + Value: String + Manager: + type: + kind: Table + properties: + Value: String + Location: + type: + kind: Table + properties: + Value: String + HireDate: + type: + kind: Table + properties: + Value: String + WorkEmail: + type: + kind: Table + properties: + Value: String + HomeAddress: + type: + kind: Table + properties: + Value: String + HomeEmail: + type: + kind: Table + properties: + Value: String + HomePhone: + type: + kind: Table + properties: + Value: String + WorkPhone: + type: + kind: Table + properties: + Value: String + Status: + type: + kind: Table + properties: + Value: String + ContinuousServiceDate: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_ServiceDate + displayName: Calculate service date values + variable: Topic.serviceDateCalc + value: =DateValue(First(Topic.parsedWorkdayResponse.ContinuousServiceDate).Value) + + - kind: SetVariable + id: setVariable_Years + displayName: Calculate years + variable: Topic.yearsOfService + value: =RoundDown(DateDiff(Topic.serviceDateCalc, Today(), TimeUnit.Months) / 12, 0) + + - kind: SetVariable + id: setVariable_Months + displayName: Calculate months + variable: Topic.monthsOfService + value: =Mod(DateDiff(Topic.serviceDateCalc, Today(), TimeUnit.Months), 12) + + - kind: SetVariable + id: setVariable_Days + displayName: Calculate days + variable: Topic.daysOfService + value: =DateDiff(DateAdd(Topic.serviceDateCalc, DateDiff(Topic.serviceDateCalc, Today(), TimeUnit.Months), TimeUnit.Months), Today(), TimeUnit.Days) + + - kind: SetVariable + id: setVariable_LHTcFu + displayName: Set finalized data + variable: Topic.finalizedData + value: |- + ={ + EmployeeID: First(Topic.parsedWorkdayResponse.EmployeeID).Value, + Name: First(Topic.parsedWorkdayResponse.Name).Value, + DOB: First(Topic.parsedWorkdayResponse.DOB).Value, + Gender: First(Topic.parsedWorkdayResponse.Gender).Value, + BusinessTitle: First(Topic.parsedWorkdayResponse.BusinessTitle).Value, + Organization: First(Topic.parsedWorkdayResponse.Organization).Value, + Manager: First(Topic.parsedWorkdayResponse.Manager).Value, + Location: First(Topic.parsedWorkdayResponse.Location).Value, + HireDate: First(Topic.parsedWorkdayResponse.HireDate).Value, + WorkEmail: First(Topic.parsedWorkdayResponse.WorkEmail).Value, + HomeAddress: Substitute(Substitute(Concat(Topic.parsedWorkdayResponse.HomeAddress, Value, ", "), Char(10), ", "), Char(13), ""), + HomeEmail: First(Topic.parsedWorkdayResponse.HomeEmail).Value, + HomePhone: First(Topic.parsedWorkdayResponse.HomePhone).Value, + WorkPhone: First(Topic.parsedWorkdayResponse.WorkPhone).Value, + Status: If(First(Topic.parsedWorkdayResponse.Status).Value = "1", "Active", "Inactive"), + ContinuousServiceDate: First(Topic.parsedWorkdayResponse.ContinuousServiceDate).Value, + LengthOfService: + If(Topic.yearsOfService > 0, Topic.yearsOfService & " year" & If(Topic.yearsOfService > 1, "s", "") & " ", "") & + If(Topic.monthsOfService > 0, Topic.monthsOfService & " month" & If(Topic.monthsOfService > 1, "s", "") & " ", "") & + Topic.daysOfService & " day" & If(Topic.daysOfService <> 1, "s", "") + } + +inputType: {} +outputType: + properties: + finalizedData: + displayName: finalizedData + type: + kind: Record + properties: + EmployeeID: String + Name: String + DOB: String + Gender: String + BusinessTitle: String + Organization: String + Manager: String + Location: String + HireDate: String + WorkEmail: String + HomeAddress: String + HomeEmail: String + HomePhone: String + WorkPhone: String + Status: String + ContinuousServiceDate: String + LengthOfService: String diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/msdyn_HRWorkdayHCMEmployeeGiveFeedback.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/msdyn_HRWorkdayHCMEmployeeGiveFeedback.xml new file mode 100644 index 00000000..6d30bdaf --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/msdyn_HRWorkdayHCMEmployeeGiveFeedback.xml @@ -0,0 +1,60 @@ + + + + + User + + msdyn_HRWorkdayHCMEmployeeGiveFeedback + Talent + v42.0 + + + + Employee_ID + {Employee_ID} + + + Employee_ID_Of_Receiver + {Employee_ID_Of_Receiver} + + + Comment + {Comment} + + + + + WID + + //*[local-name()='Give_Feedback_Response'] + /*[local-name()='Give_Feedback_Event_Reference'] + /*[local-name()='ID'][@*[local-name()='type']='WID']/text() + + + + + + + + + + + true + true + + + + {Employee_ID} + + + {Employee_ID_Of_Receiver} + + {Comment} + true + false + false + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/topic.yaml new file mode 100644 index 00000000..6ca7bb66 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayGivePeerFeedback/topic.yaml @@ -0,0 +1,83 @@ +kind: AdaptiveDialog +modelDescription: |- + This topic provides a way to send feedback to the coworkers. + + Some of the valid requests are as follows: + + give feedback + share feedback for a colleague + submit peer feedback + feedback for employee + I want to appreciate my teammate + report feedback about coworker +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - send feedback about workday + - give feedback on workday + - workday feedback + - report issue with workday + - share thoughts on workday + - submit comments for workday + - provide input on workday experience + - suggest improvements for workday + - complain about workday + + actions: + - kind: Question + id: Question_KkmuHF + variable: Topic.varEmployeeId + prompt: What is the _Employee ID_ of the colleague you want to give feedback for? + entity: StringPrebuiltEntity + + - kind: ConditionGroup + id: ConditionGroup_8NOr8O + conditions: + - id: ConditionItem_GLZBTd + condition: =!IsBlank(Topic.varEmployeeId) + actions: + - kind: Question + id: Question_ZGYUYN + variable: Topic.FeedbackText + prompt: Please enter your feedback for your coworker. + entity: StringPrebuiltEntity + + - kind: BeginDialog + id: Za15eH + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """}, {""key"":""{Employee_ID_Of_Receiver}"",""value"":"""& Topic.varEmployeeId & """}, {""key"":""{Comment}"",""value"":""" & Topic.FeedbackText & """}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGiveFeedback + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: conditionGroup_r9LEup + conditions: + - id: conditionItem_XVznZc + condition: =IsBlank(Topic.errorResponse) && Topic.isSuccess = true + actions: + - kind: SendActivity + id: sendActivity_ISzuar + activity: Feedback is sent successfully + + elseActions: + - kind: SendActivity + id: sendActivity_qhWHOF + activity: There is a failure in sending the feedback {Topic.errorResponse} + + elseActions: + - kind: SendActivity + id: SendActivity_jLD6lX + activity: You cannot send feedback to yourself. + +inputType: {} +outputType: {} \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/README.md b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/README.md new file mode 100644 index 00000000..2a518385 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/README.md @@ -0,0 +1,104 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Manage Emergency Contact + +## Overview + +This topic enables employees to manage their emergency contacts in Workday through a conversational interface. Employees can view existing contacts, add new emergency contacts, update existing ones, and delete contacts they no longer need. + +## Features + +- View existing emergency contacts with primary contact highlighted +- Add new emergency contacts with full details +- Update details of existing emergency contacts +- Delete non-primary emergency contacts +- Set or change the primary emergency contact +- Assign priority levels (2-10) to contacts + +## Snapshots + +![Manage Emergency Contact](update_emergency_contact.png) + +![Emergency Contact Form](update_emergency_contact_2.png) + +## Trigger Phrases + +- "Manage my emergency contacts" +- "Update my emergency contact" +- "Add emergency contact" +- "Change my emergency contact information" +- "Delete my emergency contact" +- "Show my emergency contacts" + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Copilot Studio topic definition with conversation flow | +| `msdyn_HRWorkdayHCMEmployeeGetEmergencyContactInfo.xml` | XML template to fetch existing emergency contacts | +| `msdyn_HRWorkdayHCMEmployeeAddEmergencyContact.xml` | XML template to add a new emergency contact | +| `msdyn_HRWorkdayHCMEmployeeUpdateEmergencyContact.xml` | XML template to update an existing emergency contact | +| `msdyn_HRWorkdayDeleteEmergencyContact.xml` | XML template to delete an emergency contact | + +## Workday APIs Used + +| API | Purpose | +|-----|---------| +| `Get_Workers` | Retrieve employee's existing emergency contacts | +| `Change_Emergency_Contacts` | Add, update, or delete emergency contact information | + +## Flow Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Triggers Topic │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Fetch Reference Data (Country Codes, Relationships) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Fetch Existing Emergency Contacts │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Selection Card (or Go to Add if no contacts) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Add/Update Form (Adaptive Card) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Submit to Workday │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Show Success/Error Message │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Configurations + +Environment makers need to configure the following in the topic: + +| Configuration | Description | Location in Topic | +|---------------|-------------|-------------------| +| **Relationship Types** | Available relationship options from Workday reference data | Dynamic from GetReferenceData | +| **Country Codes** | Phone country codes from Workday reference data | Dynamic from GetReferenceData | +| **States/Provinces** | Available state/province codes per country | Adaptive card dropdown | +| **Workday Icon** | Update the icon URL to match your organization's branding | Topic properties > Icon | +| **Workday URL** | Set your organization's Workday tenant URL | HTTP action or connector configuration | + +## Dependencies + +- **Employee Context**: Worker ID must be available in the conversation context diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayDeleteEmergencyContact.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayDeleteEmergencyContact.xml new file mode 100644 index 00000000..0a762798 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayDeleteEmergencyContact.xml @@ -0,0 +1,58 @@ + + + + + + + User + + Template_DeleteEmergencyContactRequest + Human_Resources + v45.0 + + + + //*[local-name()='Emergency_Contact_Event_Reference']/*[local-name()='ID'][@*[local-name()='type']='WID']/text() + EventID + + + + + + + + + + false + true + true + + {Comment} + + {Employee_ID} + + + + + + {Employee_ID} + + false + + + {Emergency_Contact_WID} + + true + + false + {Priority} + + {Relationship_Type} + + + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeAddEmergencyContact.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeAddEmergencyContact.xml new file mode 100644 index 00000000..8d4bcf05 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeAddEmergencyContact.xml @@ -0,0 +1,101 @@ + + + + + User + + Template_AddEmergencyContactRequest + Human_Resources + v45.0 + + + + //*[local-name()='Emergency_Contact_Event_Reference']/*[local-name()='ID'][@*[local-name()='type']='WID']/text() + EventID + + + + + + + + + + true + true + true + + {Comment} + + {Employee_ID} + + + + + + {Employee_ID} + + false + + false + + {Primary} + {Priority} + + {Relationship_Type} + + + + + + + {Country_Code} + + {First_Name} + {Last_Name} + + + + + + + {Address_Country_Code} + + {Address_Line_1} + {City} + + {State_Province} + + {Postal_Code} + + + + HOME + + + + + + {Address_Country_Code} + {Phone_Country_Code} + {Phone_Number} + + {Phone_Device_Type} + + + + + HOME + + + + = + + + + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeGetEmergencyContactInfo.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeGetEmergencyContactInfo.xml new file mode 100644 index 00000000..eb8c0af2 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeGetEmergencyContactInfo.xml @@ -0,0 +1,38 @@ + + + + + + User + + Template_GetEmergencyContactInfoRequest + Human_Resources + v45.0 + + + + //*[local-name()='Related_Person_Data']/*[local-name()='Related_Person'][*[local-name()='Emergency_Contact']] + EmergencyContacts + + + + + + + + + + + {Employee_ID} + + + + {As_Of_Effective_Date} + + + true + + + + + diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeUpdateEmergencyContact.xml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeUpdateEmergencyContact.xml new file mode 100644 index 00000000..e6928dc4 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/msdyn_HRWorkdayHCMEmployeeUpdateEmergencyContact.xml @@ -0,0 +1,105 @@ + + + + + + User + + Template_UpdateEmergencyContactRequest + Human_Resources + v45.0 + + + + //*[local-name()='Emergency_Contact_Event_Reference']/*[local-name()='ID'][@*[local-name()='type']='WID']/text() + EventID + + + + + + + + + + false + true + true + + {Comment} + + {Employee_ID} + + + + + + {Employee_ID} + + false + + + {Emergency_Contact_WID} + + false + + {Primary} + {Priority} + + {Relationship_Type} + + + + + + + {Country_Code} + + {First_Name} + {Last_Name} + + + + + + + {Address_Country_Code} + + {Address_Line_1} + {City} + + {State_Province} + + {Postal_Code} + + + + HOME + + + + + + {Address_Country_Code} + {Phone_Country_Code} + {Phone_Number} + + {Phone_Device_Type} + + + + + HOME + + + + + + + + + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/topic.yaml new file mode 100644 index 00000000..f8b5367f --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/topic.yaml @@ -0,0 +1,1378 @@ +kind: AdaptiveDialog +inputs: + - kind: AutomaticTaskInput + propertyName: InputAction + description: "Look for keywords 'add', 'new', or 'create' in the user's request. If found, extract 'add'. Look for keywords 'get', 'view', 'show', 'list', 'see', 'display', or 'what are' in the user's request. If found, extract 'get'. Look for keywords 'delete' or 'remove' in the user's request. If found, extract 'delete'. Otherwise extract 'manage'." + entity: StringPrebuiltEntity + shouldPromptUser: false + +modelDescription: |- + Use this topic when the user wants to MANAGE — ADD, UPDATE, CHANGE, MODIFY, EDIT, REPLACE, or REMOVE — THEIR OWN emergency contact / next of kin / ICE (in case of emergency) contact in Workday. Includes adding a new emergency contact and updating an existing one. + + Triggers: + - "Manage my emergency contacts" + - "Update my emergency contact" + - "Add or update emergency contact" + - "Change my emergency contact information" + - "Add a new emergency contact" + - "Update emergency contact name / phone / email / relationship" + - "Remove an emergency contact" + - "Set up my next of kin" + + Data is submitted to Workday and belongs exclusively to the requesting user. + + Do NOT trigger for general questions about emergency procedures or company safety policies + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: {} + actions: + - kind: SetVariable + id: set_workday_icon_url_2 + variable: Topic.WorkdayIconUrl + value: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII= + + - kind: ConditionGroup + id: check_country_codes + conditions: + - id: country_codes_uninitialized + condition: =IsBlank(Global.CountryCodeLookupTable) + displayName: If country code picklist is uninitialized + actions: + - kind: BeginDialog + id: fetch_country_codes + displayName: Redirect to Workday System Get Reference Data + input: + binding: + referenceDataKey: Country_Phone_Code_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.GetReferenceData + + - kind: ConditionGroup + id: check_relationship_types + conditions: + - id: relationship_types_uninitialized + condition: =IsBlank(Global.RelatedPersonRelationshipLookupTable) + displayName: If relationship type picklist is uninitialized + actions: + - kind: BeginDialog + id: fetch_relationship_types + displayName: Redirect to Workday System Get Reference Data + input: + binding: + referenceDataKey: Related_Person_Relationship_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.GetReferenceData + + - kind: ConditionGroup + id: check_state_province_choices + conditions: + - id: state_choices_uninitialized + condition: =IsBlank(Global.StateProvinceChoices) + displayName: If state/province choices are uninitialized + actions: + - kind: SetVariable + id: init_state_province_choices + variable: Global.StateProvinceChoices + value: | + =[ + { title: "Alabama", value: "USA-AL", country: "USA" }, + { title: "Alaska", value: "USA-AK", country: "USA" }, + { title: "Arizona", value: "USA-AZ", country: "USA" }, + { title: "Arkansas", value: "USA-AR", country: "USA" }, + { title: "California", value: "USA-CA", country: "USA" }, + { title: "Colorado", value: "USA-CO", country: "USA" }, + { title: "Connecticut", value: "USA-CT", country: "USA" }, + { title: "Delaware", value: "USA-DE", country: "USA" }, + { title: "Florida", value: "USA-FL", country: "USA" }, + { title: "Georgia", value: "USA-GA", country: "USA" }, + { title: "Hawaii", value: "USA-HI", country: "USA" }, + { title: "Idaho", value: "USA-ID", country: "USA" }, + { title: "Illinois", value: "USA-IL", country: "USA" }, + { title: "Indiana", value: "USA-IN", country: "USA" }, + { title: "Iowa", value: "USA-IA", country: "USA" }, + { title: "Kansas", value: "USA-KS", country: "USA" }, + { title: "Kentucky", value: "USA-KY", country: "USA" }, + { title: "Louisiana", value: "USA-LA", country: "USA" }, + { title: "Maine", value: "USA-ME", country: "USA" }, + { title: "Maryland", value: "USA-MD", country: "USA" }, + { title: "Massachusetts", value: "USA-MA", country: "USA" }, + { title: "Michigan", value: "USA-MI", country: "USA" }, + { title: "Minnesota", value: "USA-MN", country: "USA" }, + { title: "Mississippi", value: "USA-MS", country: "USA" }, + { title: "Missouri", value: "USA-MO", country: "USA" }, + { title: "Montana", value: "USA-MT", country: "USA" }, + { title: "Nebraska", value: "USA-NE", country: "USA" }, + { title: "Nevada", value: "USA-NV", country: "USA" }, + { title: "New Hampshire", value: "USA-NH", country: "USA" }, + { title: "New Jersey", value: "USA-NJ", country: "USA" }, + { title: "New Mexico", value: "USA-NM", country: "USA" }, + { title: "New York", value: "USA-NY", country: "USA" }, + { title: "North Carolina", value: "USA-NC", country: "USA" }, + { title: "North Dakota", value: "USA-ND", country: "USA" }, + { title: "Ohio", value: "USA-OH", country: "USA" }, + { title: "Oklahoma", value: "USA-OK", country: "USA" }, + { title: "Oregon", value: "USA-OR", country: "USA" }, + { title: "Pennsylvania", value: "USA-PA", country: "USA" }, + { title: "Rhode Island", value: "USA-RI", country: "USA" }, + { title: "South Carolina", value: "USA-SC", country: "USA" }, + { title: "South Dakota", value: "USA-SD", country: "USA" }, + { title: "Tennessee", value: "USA-TN", country: "USA" }, + { title: "Texas", value: "USA-TX", country: "USA" }, + { title: "Utah", value: "USA-UT", country: "USA" }, + { title: "Vermont", value: "USA-VT", country: "USA" }, + { title: "Virginia", value: "USA-VA", country: "USA" }, + { title: "Washington", value: "USA-WA", country: "USA" }, + { title: "West Virginia", value: "USA-WV", country: "USA" }, + { title: "Wisconsin", value: "USA-WI", country: "USA" }, + { title: "Wyoming", value: "USA-WY", country: "USA" }, + { title: "District of Columbia", value: "USA-DC", country: "USA" }, + { title: "Alberta", value: "CA-AB", country: "CAN" }, + { title: "British Columbia", value: "CA-BC", country: "CAN" }, + { title: "Manitoba", value: "CA-MB", country: "CAN" }, + { title: "New Brunswick", value: "CA-NB", country: "CAN" }, + { title: "Newfoundland and Labrador", value: "CA-NL", country: "CAN" }, + { title: "Northwest Territories", value: "CA-NT", country: "CAN" }, + { title: "Nova Scotia", value: "CA-NS", country: "CAN" }, + { title: "Nunavut", value: "CA-NU", country: "CAN" }, + { title: "Ontario", value: "CA-ON", country: "CAN" }, + { title: "Prince Edward Island", value: "CA-PE", country: "CAN" }, + { title: "Quebec", value: "CA-QC", country: "CAN" }, + { title: "Saskatchewan", value: "CA-SK", country: "CAN" }, + { title: "Yukon", value: "CA-YT", country: "CAN" }, + { title: "England", value: "GBR-ENG", country: "GBR" }, + { title: "Scotland", value: "GBR-SCT", country: "GBR" }, + { title: "Wales", value: "GBR-WLS", country: "GBR" }, + { title: "Northern Ireland", value: "GBR-NIR", country: "GBR" }, + { title: "Andaman and Nicobar Islands", value: "IN-AN", country: "IND" }, + { title: "Andhra Pradesh", value: "IN-AP", country: "IND" }, + { title: "Arunachal Pradesh", value: "IN-AR", country: "IND" }, + { title: "Assam", value: "IN-AS", country: "IND" }, + { title: "Bihar", value: "IN-BR", country: "IND" }, + { title: "Chandigarh", value: "IN-CH", country: "IND" }, + { title: "Chhattisgarh", value: "IN-CG", country: "IND" }, + { title: "Dadra and Nagar Haveli", value: "IN-DN", country: "IND" }, + { title: "Daman and Diu", value: "IN-DD", country: "IND" }, + { title: "Delhi", value: "IN-DL", country: "IND" }, + { title: "Goa", value: "IN-GA", country: "IND" }, + { title: "Gujarat", value: "IN-GJ", country: "IND" }, + { title: "Haryana", value: "IN-HR", country: "IND" }, + { title: "Himachal Pradesh", value: "IN-HP", country: "IND" }, + { title: "Jammu and Kashmir", value: "IN-JK", country: "IND" }, + { title: "Jharkhand", value: "IN-JH", country: "IND" }, + { title: "Karnataka", value: "IN-KA", country: "IND" }, + { title: "Kerala", value: "IN-KL", country: "IND" }, + { title: "Ladakh", value: "IN-LA", country: "IND" }, + { title: "Lakshadweep", value: "IN-LD", country: "IND" }, + { title: "Madhya Pradesh", value: "IN-MP", country: "IND" }, + { title: "Maharashtra", value: "IN-MH", country: "IND" }, + { title: "Manipur", value: "IN-MN", country: "IND" }, + { title: "Meghalaya", value: "IN-ML", country: "IND" }, + { title: "Mizoram", value: "IN-MZ", country: "IND" }, + { title: "Nagaland", value: "IN-NL", country: "IND" }, + { title: "Odisha", value: "IN-OD", country: "IND" }, + { title: "Puducherry", value: "IN-PY", country: "IND" }, + { title: "Punjab", value: "IN-PB", country: "IND" }, + { title: "Rajasthan", value: "IN-RJ", country: "IND" }, + { title: "Sikkim", value: "IN-SK", country: "IND" }, + { title: "Tamil Nadu", value: "IN-TN", country: "IND" }, + { title: "Telangana", value: "IN-TS", country: "IND" }, + { title: "Tripura", value: "IN-TR", country: "IND" }, + { title: "Uttar Pradesh", value: "IN-UP", country: "IND" }, + { title: "Uttarakhand", value: "IN-UK", country: "IND" }, + { title: "West Bengal", value: "IN-WB", country: "IND" }, + { title: "Australian Capital Territory", value: "AU-ACT", country: "AUS" }, + { title: "New South Wales", value: "AU-NSW", country: "AUS" }, + { title: "Northern Territory", value: "AU-NT", country: "AUS" }, + { title: "Queensland", value: "AU-QLD", country: "AUS" }, + { title: "South Australia", value: "AU-SA", country: "AUS" }, + { title: "Tasmania", value: "AU-TAS", country: "AUS" }, + { title: "Victoria", value: "AU-VIC", country: "AUS" }, + { title: "Western Australia", value: "AU-WA", country: "AUS" } + ] + + # Detect if user wants to view contacts only (get/view/show/list) + - kind: SetVariable + id: set_is_view_only + variable: Topic.isViewOnly + value: =(!IsBlank(Topic.InputAction) && "get" in Lower(Topic.InputAction)) || "get" in Lower(System.Activity.Text) || "view" in Lower(System.Activity.Text) || "show" in Lower(System.Activity.Text) || "list" in Lower(System.Activity.Text) || "see my" in Lower(System.Activity.Text) || "what are" in Lower(System.Activity.Text) || "display" in Lower(System.Activity.Text) + + # Detect if user wants to delete a contact + - kind: SetVariable + id: set_is_delete_mode + variable: Topic.isDeleteIntent + value: =(!IsBlank(Topic.InputAction) && "delete" in Lower(Topic.InputAction)) || "delete" in Lower(System.Activity.Text) || "remove" in Lower(System.Activity.Text) + + # Check if user explicitly wants to add a new contact - skip loading existing contacts + - kind: ConditionGroup + id: check_direct_add + conditions: + - id: user_wants_add_directly + condition: =Topic.isViewOnly <> true && ((!IsBlank(Topic.InputAction) && "add" in Lower(Topic.InputAction)) || "add" in Lower(System.Activity.Text) || "new emergency contact" in Lower(System.Activity.Text) || "create" in Lower(System.Activity.Text)) + displayName: User wants to add a new contact directly + actions: + - kind: SetVariable + id: set_add_mode_direct + variable: Topic.isUpdateMode + value: =false + - kind: SendActivity + id: direct_add_msg + activity: Sure, I can help you add a new emergency contact. + - kind: GotoAction + id: goto_country_selection_direct + actionId: country_selection_card + + - kind: BeginDialog + id: fetch_emergency_contacts + displayName: Fetch existing emergency contacts + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeGetEmergencyContactInfo + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.fetchErrorResponse + isSuccess: Topic.fetchIsSuccess + workdayResponse: Topic.existingContactsResponse + + - kind: ParseValue + id: parse_contacts + displayName: Parse emergency contacts response + variable: Topic.parsedContacts + valueType: + kind: Record + properties: + EmergencyContacts: + type: + kind: Table + properties: + Emergency_Contact: + type: + kind: Record + properties: + Emergency_Contact_Data: + type: + kind: Record + properties: + "@Primary": String + "@Priority": String + Emergency_Contact_ID: String + + Emergency_Contact_Reference: + type: + kind: Record + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Person_Reference: + type: + kind: Record + properties: + "@Descriptor": String + + Personal_Data: + type: + kind: Record + properties: + Contact_Data: + type: + kind: Record + properties: + Address_Data: + type: + kind: Table + properties: + "@Formatted_Address": String + Address_Line_Data: + type: + kind: Record + properties: + "#text": String + + Country_Reference: + type: + kind: Record + properties: + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Country_Region_Reference: + type: + kind: Record + properties: + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + Municipality: String + Postal_Code: String + + Phone_Data: + type: + kind: Record + properties: + "@Tenant_Formatted_Phone": String + International_Phone_Code: String + Phone_Number: String + + Name_Data: + type: + kind: Record + properties: + Legal_Name_Data: + type: + kind: Record + properties: + Name_Detail_Data: + type: + kind: Record + properties: + "@Formatted_Name": String + First_Name: String + Last_Name: String + + Related_Person_Relationship_Reference: + type: + kind: Table + properties: + "@Descriptor": String + ID: + type: + kind: Table + properties: + "@type": String + "#text": String + + value: =Topic.existingContactsResponse + + - kind: SetVariable + id: set_contact_list + displayName: Transform contacts for display + variable: Topic.contactSelectionList + value: | + =ForAll( + Filter( + Topic.parsedContacts.EmergencyContacts, + !IsBlank(LookUp(ThisRecord.Emergency_Contact.Emergency_Contact_Reference.ID, '@type' = "WID").'#text') + ), + { + DisplayName: ThisRecord.Personal_Data.Name_Data.Legal_Name_Data.Name_Detail_Data.'@Formatted_Name', + FirstName: ThisRecord.Personal_Data.Name_Data.Legal_Name_Data.Name_Detail_Data.First_Name, + LastName: ThisRecord.Personal_Data.Name_Data.Legal_Name_Data.Name_Detail_Data.Last_Name, + Phone: ThisRecord.Personal_Data.Contact_Data.Phone_Data.'@Tenant_Formatted_Phone', + PhoneNumber: ThisRecord.Personal_Data.Contact_Data.Phone_Data.Phone_Number, + PhoneCountryCode: ThisRecord.Personal_Data.Contact_Data.Phone_Data.International_Phone_Code, + Address: First(ThisRecord.Personal_Data.Contact_Data.Address_Data).'@Formatted_Address', + AddressLine1: First(ThisRecord.Personal_Data.Contact_Data.Address_Data).Address_Line_Data.'#text', + City: First(ThisRecord.Personal_Data.Contact_Data.Address_Data).Municipality, + PostalCode: First(ThisRecord.Personal_Data.Contact_Data.Address_Data).Postal_Code, + StateProvince: LookUp(First(ThisRecord.Personal_Data.Contact_Data.Address_Data).Country_Region_Reference.ID, '@type' = "Country_Region_ID").'#text', + CountryCode: LookUp(First(ThisRecord.Personal_Data.Contact_Data.Address_Data).Country_Reference.ID, '@type' = "ISO_3166-1_Alpha-3_Code").'#text', + RelationshipType: First(ThisRecord.Related_Person_Relationship_Reference).'@Descriptor', + RelationshipTypeID: LookUp(First(ThisRecord.Related_Person_Relationship_Reference).ID, '@type' = "Related_Person_Relationship_ID").'#text', + IsPrimary: ThisRecord.Emergency_Contact.Emergency_Contact_Data.'@Primary', + Priority: ThisRecord.Emergency_Contact.Emergency_Contact_Data.'@Priority', + WID: LookUp(ThisRecord.Emergency_Contact.Emergency_Contact_Reference.ID, '@type' = "WID").'#text' + } + ) + + - kind: ConditionGroup + id: check_contacts_exist + conditions: + - id: has_existing_contacts + condition: =CountRows(Topic.contactSelectionList) > 0 + displayName: If user has existing emergency contacts + actions: + - kind: SetVariable + id: build_selection_choices + displayName: Build selection choices + variable: Topic.selectionChoices + value: | + =ForAll( + Filter( + SortByColumns(Topic.contactSelectionList, "IsPrimary", SortOrder.Descending, "Priority", SortOrder.Ascending), + !IsBlank(ThisRecord.DisplayName) && !IsBlank(ThisRecord.WID) + ), + { + title: If(IsBlank(ThisRecord.DisplayName), "Unknown Contact", ThisRecord.DisplayName), + value: ThisRecord.WID + } + ) + + - kind: SetVariable + id: set_workday_url_2 + variable: Topic.WorkdayUrl + value: https://impl.workday.com//home.htmld + + - kind: SetVariable + id: build_contact_table + displayName: Build formatted contact table + variable: Topic.contactTableText + value: | + ="| Name | Relationship | Phone | Address |" & Char(10) & "|------|-------------|-------|---------|" & Char(10) & Concat( + SortByColumns(Topic.contactSelectionList, "IsPrimary", SortOrder.Descending, "Priority", SortOrder.Ascending), + "| " & If(!IsBlank(ThisRecord.DisplayName), ThisRecord.DisplayName, "Unknown") & If(ThisRecord.IsPrimary = "true" || ThisRecord.IsPrimary = "1", " ★", "") & " | " & If(!IsBlank(ThisRecord.RelationshipType), ThisRecord.RelationshipType, "—") & " | " & If(!IsBlank(ThisRecord.Phone), ThisRecord.Phone, "—") & " | " & If(!IsBlank(ThisRecord.Address), ThisRecord.Address, "—") & " |", + Char(10) + ) + + # View-only mode: show table as Adaptive Card (full width) and end + - kind: ConditionGroup + id: check_view_only + conditions: + - id: is_view_only_mode + condition: =Topic.isViewOnly = true + displayName: User only wants to view contacts + actions: + - kind: SetVariable + id: set_view_intro_text + variable: Topic.viewIntroText + value: ="Here are your emergency contacts from [Workday](" & Topic.WorkdayUrl & "), an HR platform your company uses." + + - kind: SendActivity + id: view_only_intro + activity: "{Topic.viewIntroText}" + + - kind: SendActivity + id: view_only_table_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.3", + body: [ + { + type: "TextBlock", + text: "Your emergency contacts (" & Text(CountRows(Topic.contactSelectionList)) & ")", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "ColumnSet", + separator: true, + spacing: "Medium", + columns: [ + { type: "Column", width: 2, items: [{ type: "TextBlock", text: "Name", weight: "Bolder", wrap: true }] }, + { type: "Column", width: 2, items: [{ type: "TextBlock", text: "Relationship", weight: "Bolder", wrap: true }] }, + { type: "Column", width: 2, items: [{ type: "TextBlock", text: "Phone", weight: "Bolder", wrap: true }] }, + { type: "Column", width: 3, items: [{ type: "TextBlock", text: "Address", weight: "Bolder", wrap: true }] } + ] + }, + { + type: "Container", + items: ForAll( + SortByColumns(Topic.contactSelectionList, "IsPrimary", SortOrder.Descending, "Priority", SortOrder.Ascending), + { + type: "ColumnSet", + separator: true, + columns: [ + { type: "Column", width: 2, items: [{ type: "TextBlock", text: If(!IsBlank(ThisRecord.DisplayName), ThisRecord.DisplayName, "Unknown") & If(ThisRecord.IsPrimary = "true" || ThisRecord.IsPrimary = "1", " ★", ""), wrap: true }] }, + { type: "Column", width: 2, items: [{ type: "TextBlock", text: If(!IsBlank(ThisRecord.RelationshipType), ThisRecord.RelationshipType, "—"), wrap: true }] }, + { type: "Column", width: 2, items: [{ type: "TextBlock", text: If(!IsBlank(ThisRecord.Phone), ThisRecord.Phone, "—"), wrap: true }] }, + { type: "Column", width: 3, items: [{ type: "TextBlock", text: If(!IsBlank(ThisRecord.Address), ThisRecord.Address, "—"), wrap: true }] } + ] + } + ) + } + ] + } + + - kind: SendActivity + id: view_only_footer + activity: "If you need to update or add any information for these contacts, just let me know which one you'd like to change or if you want to add a new contact." + + - kind: CancelAllDialogs + id: end_view_only + + - kind: SetVariable + id: set_intro_msg_text + variable: Topic.introMsgText + value: ="Sure, I can help you with that. Here's where you can update and add emergency contacts. I've identified " & Text(CountRows(Topic.selectionChoices)) & " emergency contact(s) of yours from [Workday](" & Topic.WorkdayUrl & "), an HR platform your company uses." + + - kind: SendActivity + id: intro_selection_msg + activity: "{Topic.introMsgText}" + + - kind: AdaptiveCardPrompt + id: select_contact_card + displayName: Select emergency contact to update + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: If(Topic.isDeleteIntent, "Select an emergency contact to delete", "Select an emergency contact to update, delete, or add a new contact."), + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: If(Topic.isDeleteIntent, "You have " & CountRows(Topic.selectionChoices) & " emergency contact(s). Select the contact you want to delete.", "You have " & CountRows(Topic.selectionChoices) & " emergency contact(s). Select to update or add a new contact."), + wrap: true, + spacing: "Small" + }, + { + type: "Input.ChoiceSet", + id: "selectedContact", + label: "Emergency contact", + style: "compact", + isRequired: true, + errorMessage: "Please select an emergency contact.", + choices: ForAll(Topic.selectionChoices, { title: ThisRecord.title, value: ThisRecord.value }), + value: First(Topic.selectionChoices).value + } + ], + actions: If(Topic.isDeleteIntent, + [ + { type: "Action.Submit", title: "Delete contact", id: "DELETE", data: { actionSubmitId: "DELETE" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ], + [ + { type: "Action.Submit", title: "Continue", id: "Continue", data: { actionSubmitId: "Continue" } }, + { type: "Action.Submit", title: "Delete contact", id: "DELETE", data: { actionSubmitId: "DELETE" } }, + { type: "Action.Submit", title: "Add an emergency contact", id: "ADD_NEW", data: { actionSubmitId: "ADD_NEW" }, associatedInputs: "none" } + ] + ) + } + output: + binding: + actionSubmitId: Topic.selectionActionId + selectedContact: Topic.selectedContactWID + + outputType: + properties: + actionSubmitId: String + selectedContact: String + + - kind: ConditionGroup + id: handle_selection_cancel + conditions: + - id: selection_cancelled + condition: =Topic.selectionActionId = "Cancel" + actions: + - kind: SendActivity + id: selection_cancel_msg + activity: Your request has been cancelled. Is there anything else you need help with? + + - kind: CancelAllDialogs + id: selection_cancel_all + + - kind: ConditionGroup + id: set_mode + conditions: + - id: is_update_mode + condition: =Topic.selectionActionId = "Continue" + displayName: Update existing contact + actions: + - kind: SetVariable + id: set_update_mode + variable: Topic.isUpdateMode + value: =true + + - kind: SetVariable + id: set_selected_contact_data + variable: Topic.selectedContactData + value: =LookUp(Topic.contactSelectionList, WID = Topic.selectedContactWID) + + - kind: ConditionGroup + id: handle_selection_delete + conditions: + - id: selection_delete_requested + condition: =Topic.selectionActionId = "DELETE" + displayName: User wants to delete from selection card + actions: + - kind: SetVariable + id: set_selected_contact_for_delete + variable: Topic.selectedContactData + value: =LookUp(Topic.contactSelectionList, WID = Topic.selectedContactWID) + + - kind: GotoAction + id: goto_delete_confirmation + actionId: handle_delete_action + + elseActions: + - kind: SetVariable + id: set_add_mode + variable: Topic.isUpdateMode + value: =false + + elseActions: + - kind: SetVariable + id: set_add_mode_no_contacts + variable: Topic.isUpdateMode + value: =false + + - kind: SendActivity + id: no_contacts_msg + activity: You don't have any emergency contacts configured yet. Let's add one now. + + # Show context message before form (only for update mode) + - kind: ConditionGroup + id: show_update_context_msg + conditions: + - id: is_update_show_msg + condition: =Topic.isUpdateMode = true + actions: + - kind: SetVariable + id: set_update_context_text + variable: Topic.updateContextText + value: ="You've pulled up " & Topic.selectedContactData.FirstName & " " & Topic.selectedContactData.LastName & "'s emergency contact details. In this form you can edit details or remove " & Topic.selectedContactData.FirstName & " from your emergency contacts." + + - kind: SendActivity + id: update_context_msg + activity: "{Topic.updateContextText}" + + # Step 1: Country selection card (needed to filter states in the next card) + - kind: AdaptiveCardPrompt + id: country_selection_card + displayName: Select country for address + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Select country", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: "Choose the country for this contact's address. The state/province options in the next step will be filtered accordingly.", + wrap: true, + size: "Small", + spacing: "Small" + }, + { + type: "Input.ChoiceSet", + id: "countryCode", + label: "Country", + style: "compact", + isRequired: true, + errorMessage: "Please select a country.", + choices: [ + { title: "United States", value: "USA" }, + { title: "Canada", value: "CAN" }, + { title: "United Kingdom", value: "GBR" }, + { title: "India", value: "IND" }, + { title: "Australia", value: "AUS" } + ], + value: If(Topic.isUpdateMode, Topic.selectedContactData.CountryCode, "USA") + } + ], + actions: [ + { type: "Action.Submit", title: "Continue", id: "Continue", data: { actionSubmitId: "Continue" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.countrySelectionActionId + countryCode: Topic.countryCode + + outputType: + properties: + actionSubmitId: String + countryCode: String + + - kind: ConditionGroup + id: handle_country_cancel + conditions: + - id: country_cancelled + condition: =Topic.countrySelectionActionId = "Cancel" + actions: + - kind: SendActivity + id: country_cancel_msg + activity: Your request has been cancelled. Is there anything else you need help with? + + - kind: CancelAllDialogs + id: country_cancel_all + + # Step 2: Build filtered state/province choices based on selected country + - kind: SetVariable + id: set_filtered_state_choices + displayName: Filter states by selected country + variable: Topic.filteredStateProvinceChoices + value: =Filter(Global.StateProvinceChoices, country = Topic.countryCode) + + # Map address country to default phone dial code + - kind: SetVariable + id: set_default_phone_dial_code + displayName: Default phone dial code for selected country + variable: Topic.defaultPhoneDialCode + value: =Switch(Topic.countryCode, "USA", "1", "CAN", "1", "GBR", "44", "IND", "91", "AUS", "61", "1") + + # Step 3: Main emergency contact form (with filtered states) + - kind: AdaptiveCardPrompt + id: emergency_contact_form + displayName: Emergency contact form + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: If(Topic.isUpdateMode, "Update emergency contact", "Add emergency contact"), + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "TextBlock", + text: "Fields marked with * are required.", + wrap: true, + size: "Small", + spacing: "Small" + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Text", + id: "firstName", + label: "First name", + placeholder: "Enter first name", + isRequired: true, + errorMessage: "First name is required.", + maxLength: 100, + value: If(Topic.isUpdateMode, Topic.selectedContactData.FirstName, "") + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Text", + id: "lastName", + label: "Last name", + placeholder: "Enter last name", + isRequired: true, + errorMessage: "Last name is required.", + maxLength: 100, + value: If(Topic.isUpdateMode, Topic.selectedContactData.LastName, "") + } + ] + } + ] + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.ChoiceSet", + id: "priority", + label: "Priority (only applies if not primary)", + style: "compact", + isRequired: true, + errorMessage: "Please select a priority.", + choices: [ + { title: "2 - High", value: "2" }, + { title: "3", value: "3" }, + { title: "4", value: "4" }, + { title: "5 - Medium", value: "5" }, + { title: "6", value: "6" }, + { title: "7", value: "7" }, + { title: "8", value: "8" }, + { title: "9", value: "9" }, + { title: "10 - Low", value: "10" } + ], + value: If(Topic.isUpdateMode, If(Value(Topic.selectedContactData.Priority) > 1, Topic.selectedContactData.Priority, "2"), "2") + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.ChoiceSet", + id: "relationshipType", + label: "Relationship", + style: "compact", + isRequired: true, + errorMessage: "Please select a relationship type.", + choices: ForAll(Global.RelatedPersonRelationshipLookupTable, { title: ThisRecord.Referenced_Object_Descriptor, value: ThisRecord.ID }), + value: If(Topic.isUpdateMode, Topic.selectedContactData.RelationshipTypeID, First(Global.RelatedPersonRelationshipLookupTable).ID) + } + ] + } + ] + }, + { + type: "Input.Toggle", + id: "isPrimaryContact", + title: "Set as primary emergency contact", + value: If(Topic.isUpdateMode, If(Topic.selectedContactData.IsPrimary = "true" || Topic.selectedContactData.IsPrimary = "1", "true", "false"), "false"), + valueOn: "true", + valueOff: "false" + }, + { + type: "Input.ChoiceSet", + id: "phoneDeviceType", + label: "Phone type", + style: "compact", + isRequired: true, + errorMessage: "Please select a phone type.", + separator: true, + choices: [ + { title: "Mobile", value: "Mobile" }, + { title: "Home", value: "Home" }, + { title: "Work", value: "Work" } + ], + value: "Mobile", + spacing: "Medium" + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.ChoiceSet", + id: "phoneCountryCode", + label: "Phone country code", + style: "compact", + isRequired: true, + errorMessage: "Please select a country code.", + choices: ForAll(Global.CountryCodeLookupTable, { title: ThisRecord.Referenced_Object_Descriptor, value: ThisRecord.ID }), + value: If(Topic.isUpdateMode, LookUp(Global.CountryCodeLookupTable, Last(Split(ID, "_")).Value = Topic.selectedContactData.PhoneCountryCode).ID, LookUp(Global.CountryCodeLookupTable, Last(Split(ID, "_")).Value = Topic.defaultPhoneDialCode).ID) + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Text", + id: "phoneNumber", + label: "Phone number", + placeholder: "Enter phone number", + isRequired: true, + errorMessage: "Phone number is required.", + maxLength: 20, + value: If(Topic.isUpdateMode, Topic.selectedContactData.PhoneNumber, "") + } + ] + } + ] + }, + { + type: "Input.Text", + id: "addressLine1", + label: "Address line 1", + placeholder: "Enter street address", + isRequired: true, + errorMessage: "Address is required.", + maxLength: 200, + value: If(Topic.isUpdateMode, Topic.selectedContactData.AddressLine1, ""), + separator: true, + spacing: "Medium" + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Text", + id: "city", + label: "City", + placeholder: "Enter city", + isRequired: true, + errorMessage: "City is required.", + maxLength: 100, + value: If(Topic.isUpdateMode, Topic.selectedContactData.City, "") + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.ChoiceSet", + id: "stateProvince", + label: "State/Province", + style: "compact", + isRequired: true, + errorMessage: "Please select a state/province.", + choices: If(CountRows(Topic.filteredStateProvinceChoices) > 0, ForAll(Topic.filteredStateProvinceChoices, { title: ThisRecord.title, value: ThisRecord.value }), [{ title: "N/A — enter details in Address field", value: "N_A" }]), + value: If(Topic.isUpdateMode && !IsBlank(Topic.selectedContactData.StateProvince), Topic.selectedContactData.StateProvince, If(CountRows(Topic.filteredStateProvinceChoices) > 0, First(Topic.filteredStateProvinceChoices).value, "N_A")) + } + ] + } + ] + }, + { + type: "ColumnSet", + columns: [ + { + type: "Column", + width: "stretch", + items: [ + { + type: "Input.Text", + id: "postalCode", + label: "Postal code", + placeholder: "Enter postal code", + isRequired: true, + errorMessage: "Postal code is required.", + maxLength: 20, + value: If(Topic.isUpdateMode, Topic.selectedContactData.PostalCode, "") + } + ] + }, + { + type: "Column", + width: "stretch", + items: [ + { + type: "TextBlock", + text: "Country", + size: "Small", + weight: "Bolder", + spacing: "Small" + }, + { + type: "TextBlock", + text: LookUp([{v:"USA",t:"United States"},{v:"CAN",t:"Canada"},{v:"GBR",t:"United Kingdom"},{v:"IND",t:"India"},{v:"AUS",t:"Australia"}], v = Topic.countryCode).t, + wrap: true, + spacing: "Small" + } + ] + } + ] + } + ], + actions: If(Topic.isUpdateMode, + [ + { type: "Action.Submit", title: "Submit changes", id: "Submit", data: { actionSubmitId: "Submit" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ], + [ + { type: "Action.Submit", title: "Submit", id: "Submit", data: { actionSubmitId: "Submit" } }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", data: { actionSubmitId: "Cancel" }, associatedInputs: "none" } + ] + ) + } + output: + binding: + actionSubmitId: Topic.formActionId + addressLine1: Topic.addressLine1 + city: Topic.city + firstName: Topic.firstName + isPrimaryContact: Topic.isPrimaryContact + lastName: Topic.lastName + phoneCountryCode: Topic.phoneCountryCode + phoneDeviceType: Topic.phoneDeviceType + phoneNumber: Topic.phoneNumber + postalCode: Topic.postalCode + priority: Topic.priority + relationshipType: Topic.relationshipType + stateProvince: Topic.stateProvince + + outputType: + properties: + actionSubmitId: String + addressLine1: String + city: String + firstName: String + isPrimaryContact: String + lastName: String + phoneCountryCode: String + phoneDeviceType: String + phoneNumber: String + postalCode: String + priority: String + relationshipType: String + stateProvince: String + + - kind: ConditionGroup + id: handle_form_cancel + conditions: + - id: form_cancelled + condition: =Topic.formActionId = "Cancel" + actions: + - kind: SendActivity + id: form_cancel_msg + activity: Your request has been cancelled. Is there anything else you need help with? + + - kind: CancelAllDialogs + id: form_cancel_all + + # Handle delete action - show confirmation (reached from selection card or form) + - kind: ConditionGroup + id: handle_delete_action + conditions: + - id: delete_requested + condition: =Topic.formActionId = "Delete" || Topic.selectionActionId = "DELETE" + displayName: User wants to delete contact + actions: + # Check if contact is primary - don't allow delete + - kind: ConditionGroup + id: check_primary_delete + conditions: + - id: is_primary_contact + condition: =Topic.selectedContactData.IsPrimary = "true" || Topic.selectedContactData.IsPrimary = "1" + displayName: Cannot delete primary contact + actions: + - kind: SendActivity + id: cannot_delete_primary_msg + activity: You cannot delete your primary emergency contact. Please designate another contact as primary first, then you can remove this one. + + - kind: CancelAllDialogs + id: cancel_primary_delete + + elseActions: + # Not primary - proceed with delete confirmation + - kind: SetVariable + id: set_delete_confirm_text + variable: Topic.deleteConfirmText + value: ="Are you sure you want to delete this emergency contact?" + + - kind: SendActivity + id: delete_confirm_text_msg + activity: "{Topic.deleteConfirmText}" + + - kind: AdaptiveCardPrompt + id: confirm_delete_card + displayName: Confirm delete + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Confirm deletion", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Name", value: Topic.selectedContactData.FirstName & " " & Topic.selectedContactData.LastName }, + { title: "Priority", value: LookUp([{v:"2",t:"2 - High"},{v:"3",t:"3"},{v:"4",t:"4"},{v:"5",t:"5 - Medium"},{v:"6",t:"6"},{v:"7",t:"7"},{v:"8",t:"8"},{v:"9",t:"9"},{v:"10",t:"10 - Low"}], v = Topic.selectedContactData.Priority).t }, + { title: "Relationship", value: Topic.selectedContactData.RelationshipType }, + { title: "Phone", value: Topic.selectedContactData.Phone }, + { title: "Address", value: Topic.selectedContactData.Address } + ] + } + ], + actions: [ + { type: "Action.Submit", title: "Yes, delete", id: "Yes", data: { actionSubmitId: "Yes" } }, + { type: "Action.Submit", title: "Cancel", id: "No", data: { actionSubmitId: "No" } } + ] + } + output: + binding: + actionSubmitId: Topic.confirmDeleteAction + + outputType: + properties: + actionSubmitId: String + + - kind: ConditionGroup + id: check_delete_confirmation + conditions: + - id: confirmed_delete + condition: =Topic.confirmDeleteAction = "Yes" + displayName: User confirmed delete + actions: + - kind: BeginDialog + id: execute_delete + displayName: Execute Workday Delete + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{Emergency_Contact_WID}"",""value"":""" & Topic.selectedContactWID & """},{""key"":""{Priority}"",""value"":""" & Topic.selectedContactData.Priority & """},{""key"":""{Relationship_Type}"",""value"":""" & Topic.selectedContactData.RelationshipTypeID & """},{""key"":""{Comment}"",""value"":""Removing emergency contact via Copilot""}]}" + scenarioName: msdyn_DeleteEmergencyContact + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: report_delete_result + conditions: + - id: delete_failed + condition: =Topic.isSuccess = false + actions: + # Parse error message for display + - kind: SetVariable + id: set_delete_error_raw + variable: Topic.deleteErrorRaw + value: =Topic.errorResponse + + - kind: SetVariable + id: parse_delete_error + variable: Topic.deleteErrorParsed + value: =IfError(Text(ParseJSON(Topic.deleteErrorRaw).error.message), IfError(Text(ParseJSON(Topic.deleteErrorRaw).message), Topic.deleteErrorRaw)) + + - kind: SetVariable + id: set_delete_error_display + variable: Topic.deleteErrorDisplay + value: =If(IsBlank(Topic.deleteErrorParsed), "An unknown error occurred.", Topic.deleteErrorParsed) + + - kind: SendActivity + id: delete_failure_msg + activity: |- + An error occurred and the emergency contact was not deleted. + + **Error details:** {Topic.deleteErrorDisplay} + + Please try again or contact support. + + - kind: CancelAllDialogs + id: end_on_delete_failure + + elseActions: + - kind: SendActivity + id: delete_success_msg + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "✅ Emergency contact deleted", + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Name", value: Topic.selectedContactData.FirstName & " " & Topic.selectedContactData.LastName }, + { title: "Priority", value: LookUp([{v:"2",t:"2 - High"},{v:"3",t:"3"},{v:"4",t:"4"},{v:"5",t:"5 - Medium"},{v:"6",t:"6"},{v:"7",t:"7"},{v:"8",t:"8"},{v:"9",t:"9"},{v:"10",t:"10 - Low"}], v = Topic.selectedContactData.Priority).t }, + { title: "Relationship", value: Topic.selectedContactData.RelationshipType }, + { title: "Phone", value: Topic.selectedContactData.Phone }, + { title: "Address", value: Topic.selectedContactData.Address } + ] + } + ] + } + + - kind: CancelAllDialogs + id: end_delete_dialogs + + elseActions: + - kind: SendActivity + id: delete_cancelled_msg + activity: Delete cancelled. Is there anything else I can help you with? + + - kind: CancelAllDialogs + id: cancel_delete_dialogs + + # Only process submit action (not cancel or delete) + - kind: ConditionGroup + id: check_submit_action + conditions: + - id: is_submit_action + condition: =Topic.formActionId = "Submit" + displayName: User submitted the form + actions: + - kind: ConditionGroup + id: submit_to_workday + conditions: + - id: is_update_submit + condition: =Topic.isUpdateMode = true + displayName: Submit update to Workday + actions: + - kind: BeginDialog + id: execute_update + displayName: Execute Workday Update + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{Emergency_Contact_WID}"",""value"":""" & Topic.selectedContactWID & """},{""key"":""{Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""},{""key"":""{First_Name}"",""value"":""" & Topic.firstName & """},{""key"":""{Last_Name}"",""value"":""" & Topic.lastName & """},{""key"":""{Primary}"",""value"":""" & Topic.isPrimaryContact & """},{""key"":""{Priority}"",""value"":""" & If(Topic.isPrimaryContact = "true", "1", Topic.priority) & """},{""key"":""{Relationship_Type}"",""value"":""" & Topic.relationshipType & """},{""key"":""{Phone_Number}"",""value"":""" & Topic.phoneNumber & """},{""key"":""{Phone_Device_Type}"",""value"":""" & Topic.phoneDeviceType & """},{""key"":""{Phone_Country_Code}"",""value"":""" & Last(Split(Topic.phoneCountryCode, "_")).Value & """},{""key"":""{Address_Line_1}"",""value"":""" & Topic.addressLine1 & """},{""key"":""{City}"",""value"":""" & Topic.city & """},{""key"":""{State_Province}"",""value"":""" & Topic.stateProvince & """},{""key"":""{Postal_Code}"",""value"":""" & Topic.postalCode & """},{""key"":""{Country_Code}"",""value"":""" & Topic.countryCode & """},{""key"":""{Address_Country_Code}"",""value"":""" & Topic.countryCode & """},{""key"":""{Comment}"",""value"":""Updating emergency contact""}]}" + scenarioName: msdyn_UpdateEmergencyContact + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + elseActions: + - kind: BeginDialog + id: execute_add + displayName: Execute Workday Add + input: + binding: + parameters: ="{""params"":[{""key"":""{Employee_ID}"",""value"":""" & Global.ESS_UserContext_Employee_Id & """},{""key"":""{Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""},{""key"":""{First_Name}"",""value"":""" & Topic.firstName & """},{""key"":""{Last_Name}"",""value"":""" & Topic.lastName & """},{""key"":""{Primary}"",""value"":""" & Topic.isPrimaryContact & """},{""key"":""{Priority}"",""value"":""" & If(Topic.isPrimaryContact = "true", "1", Topic.priority) & """},{""key"":""{Relationship_Type}"",""value"":""" & Topic.relationshipType & """},{""key"":""{Phone_Number}"",""value"":""" & Topic.phoneNumber & """},{""key"":""{Phone_Device_Type}"",""value"":""" & Topic.phoneDeviceType & """},{""key"":""{Phone_Country_Code}"",""value"":""" & Last(Split(Topic.phoneCountryCode, "_")).Value & """},{""key"":""{Address_Line_1}"",""value"":""" & Topic.addressLine1 & """},{""key"":""{City}"",""value"":""" & Topic.city & """},{""key"":""{State_Province}"",""value"":""" & Topic.stateProvince & """},{""key"":""{Postal_Code}"",""value"":""" & Topic.postalCode & """},{""key"":""{Country_Code}"",""value"":""" & Topic.countryCode & """},{""key"":""{Address_Country_Code}"",""value"":""" & Topic.countryCode & """},{""key"":""{Comment}"",""value"":""Adding emergency contact""}]}" + scenarioName: msdyn_HRWorkdayHCMEmployeeAddEmergencyContact + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: report_result + conditions: + - id: failed + condition: =Topic.isSuccess = false + actions: + # Parse error message for display + - kind: SetVariable + id: set_submit_error_raw + variable: Topic.submitErrorRaw + value: =Topic.errorResponse + + - kind: SetVariable + id: parse_submit_error + variable: Topic.submitErrorParsed + value: =IfError(Text(ParseJSON(Topic.submitErrorRaw).error.message), IfError(Text(ParseJSON(Topic.submitErrorRaw).message), Topic.submitErrorRaw)) + + - kind: SetVariable + id: set_submit_error_display + variable: Topic.submitErrorDisplay + value: =If(IsBlank(Topic.submitErrorParsed), "An unknown error occurred.", Topic.submitErrorParsed) + + - kind: SetVariable + id: set_failure_msg_text + variable: Topic.failureMsgText + value: =If(Topic.isUpdateMode, "An error occurred and your emergency contact was not updated.", "An error occurred and your emergency contact was not added.") + + - kind: SendActivity + id: failure_msg + activity: |- + {Topic.failureMsgText} + + **Error details:** {Topic.submitErrorDisplay} + + Please try again or contact support. + + - kind: CancelAllDialogs + id: end_on_failure + + elseActions: + - kind: SetVariable + id: set_workday_url + variable: Topic.WorkdayUrl + value: ="https://impl.workday.com//home.htmld" + + - kind: SetVariable + id: set_success_intro_text + variable: Topic.successIntroText + value: =If(Topic.isUpdateMode, "Great! You've updated your emergency contact.", "Great! You've added a new emergency contact.") + + - kind: SendActivity + id: success_intro_msg + activity: "{Topic.successIntroText}" + + - kind: SendActivity + id: success_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: If(Topic.isUpdateMode, "✅ Emergency contact updated", "✅ Emergency contact added"), + weight: "Bolder", + size: "Medium", + wrap: true, + spacing: "Medium" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Name", value: Topic.firstName & " " & Topic.lastName }, + { title: "Priority", value: If(Topic.isPrimaryContact = "true", "Primary", LookUp([{v:"2",t:"2 - High"},{v:"3",t:"3"},{v:"4",t:"4"},{v:"5",t:"5 - Medium"},{v:"6",t:"6"},{v:"7",t:"7"},{v:"8",t:"8"},{v:"9",t:"9"},{v:"10",t:"10 - Low"}], v = Topic.priority).t) }, + { title: "Relationship", value: LookUp(Global.RelatedPersonRelationshipLookupTable, ID = Topic.relationshipType).Referenced_Object_Descriptor }, + { title: "Phone", value: "+" & Last(Split(Topic.phoneCountryCode, "_")).Value & "-" & Topic.phoneNumber & " (" & Topic.phoneDeviceType & ")" }, + { title: "Address", value: Topic.addressLine1 & " " & Topic.city & ", " & Topic.countryCode & " " & Topic.postalCode } + ] + } + ] + } + + - kind: SendActivity + id: success_footer_msg + activity: Is there anything else I can help you with? + + - kind: CancelAllDialogs + id: end_dialogs + + elseActions: + # This handles case when formActionId is not "Submit" (fallback for Cancel/Delete that weren't caught) + - kind: CancelAllDialogs + id: end_unexpected_action + +inputType: + properties: + InputAction: + displayName: InputAction + description: The action the user wants to perform (add, update, manage). + type: String + +outputType: {} \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact.png new file mode 100644 index 00000000..a91628fa Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact_2.png b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact_2.png new file mode 100644 index 00000000..46ae5a9d Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/EmployeeScenarios/WorkdayManageEmergencyContact/update_emergency_contact_2.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetInboxTasks.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetInboxTasks.yaml new file mode 100644 index 00000000..2cfcaf80 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetInboxTasks.yaml @@ -0,0 +1,188 @@ +kind: AdaptiveDialog +modelDescription: |- + Shows the employee's open tasks from their Workday inbox. + Available fields per task: task title (descriptor), due date, assigned date, step type, initiator (who submitted), overall process name. + + Display rules: + - Heading: "Your open tasks in Workday" + - Show total count: "You have N open task(s)" + - If total exceeds the shown count, note: "showing the most recent N" + - List each task with: title, process, who it is from, step type, assigned date, due date + - If no tasks: "Your Workday inbox is empty. No open tasks found." + - Answer only for the requesting user's own tasks. Do not retrieve tasks for others. + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - What tasks do I have in Workday? + - Show my open tasks + - What's in my Workday inbox? + - Do I have any pending tasks? + - What actions are waiting for me in Workday? + - Show me my to-do list in Workday + - My Workday tasks + - Any tasks I need to complete? + + actions: + - kind: SetVariable + id: set_max_tasks + displayName: Set max tasks to show + variable: Topic.MaxTasks + value: =20 + + - kind: BeginDialog + id: get_inbox_tasks + displayName: Get Workday inbox tasks + input: + binding: + parameters: ="{""workerID"":""Employee_ID=" & Global.ESS_UserContext_Employee_Id & """,""limit"":" & Topic.MaxTasks & ",""offset"":0}" + operationName: GetWorkerInboxTasks + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: handle_result + conditions: + - id: success + condition: =Topic.isSuccess = true + actions: + - kind: ParseValue + id: parse_inbox + displayName: Parse inbox tasks response + variable: Topic.parsedInbox + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + due: String + assigned: String + stepType: + type: + kind: Record + properties: + descriptor: String + initiator: + type: + kind: Record + properties: + descriptor: String + overallProcess: + type: + kind: Record + properties: + descriptor: String + total: + type: Number + + value: =Topic.workdayResponse + + - kind: SetVariable + id: set_shown_count + variable: Topic.ShownCount + value: =CountRows(Topic.parsedInbox.data) + + - kind: SetVariable + id: set_total + variable: Topic.TotalTasks + value: =Coalesce(Topic.parsedInbox.total, Topic.ShownCount) + + - kind: ConditionGroup + id: check_empty + conditions: + - id: has_tasks + condition: =Topic.ShownCount > 0 + actions: + - kind: SendActivity + id: show_tasks_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Your open tasks in Workday", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "You have " & Topic.TotalTasks & " open task" & If(Topic.TotalTasks = 1, "", "s") & If(Topic.TotalTasks > Topic.ShownCount, ", showing the most recent " & Topic.ShownCount, ""), + wrap: true, + spacing: "Small", + isSubtle: true + }, + { + type: "Container", + spacing: "Medium", + items: ForAll( + Topic.parsedInbox.data, + { + type: "Container", + style: "emphasis", + bleed: false, + spacing: "Small", + items: [ + { + type: "TextBlock", + text: Coalesce(descriptor, "Untitled task"), + weight: "Bolder", + wrap: true + }, + { + type: "FactSet", + spacing: "Small", + facts: [ + { title: "Process", value: If(IsBlank(overallProcess.descriptor), "-", overallProcess.descriptor) }, + { title: "From", value: If(IsBlank(initiator.descriptor), "-", initiator.descriptor) }, + { title: "Type", value: If(IsBlank(stepType.descriptor), "-", stepType.descriptor) }, + { title: "Assigned", value: If(IsBlank(assigned), "-", Left(assigned, 10)) }, + { title: "Due", value: If(IsBlank(due), "-", Left(due, 10)) } + ] + } + ] + } + ) + } + ], + actions: [] + } + + - kind: CancelAllDialogs + id: end_dialogs + + elseActions: + - kind: SendActivity + id: no_tasks + activity: Your Workday inbox is empty. No open tasks found. Is there anything else I can help with? + + - kind: CancelAllDialogs + id: end_no_tasks + + elseActions: + - kind: SendActivity + id: error_msg + activity: I was not able to retrieve your tasks right now. Please try again or check your Workday inbox directly. + + - kind: CancelAllDialogs + id: end_error + +inputType: {} +outputType: {} diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetPaySlips.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetPaySlips.yaml new file mode 100644 index 00000000..bf624735 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/GetPaySlips.yaml @@ -0,0 +1,185 @@ +kind: AdaptiveDialog +modelDescription: |- + Shows the employee's pay slip history from Workday. + Available fields per pay slip: pay period name (descriptor), pay date, gross pay, net pay, status. + + Display rules: + - Heading: "Your pay slips" + - Show count: "Here are your N most recent pay slips from Workday." + - Each slip shows: pay period name, pay date, gross pay, net pay, status + - If no pay slips found: "No pay slips were found in Workday." + - Answer only if the user is asking about their own pay slips. + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show my pay slips + - View my pay stubs + - Where can I find my payslip? + - Show my pay history + - I want to see my paycheck + - Download my pay stub + - What is my latest paycheck? + - Show me my salary slips + - Pay slips + + actions: + - kind: SetVariable + id: set_max_slips + displayName: Set max pay slips to fetch + variable: Topic.MaxSlips + value: =10 + + - kind: BeginDialog + id: get_pay_slips + displayName: Get pay slips + input: + binding: + parameters: ="{""workerID"":""Employee_ID=" & Global.ESS_UserContext_Employee_Id & """,""limit"":" & Topic.MaxSlips & ",""offset"":0}" + operationName: GetWorkerPaySlips + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: handle_result + conditions: + - id: success + condition: =Topic.isSuccess = true + actions: + - kind: ParseValue + id: parse_payslips + displayName: Parse pay slips response + variable: Topic.parsedPaySlips + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + date: String + gross: Number + net: Number + status: + type: + kind: Record + properties: + descriptor: String + total: + type: Number + + value: =Topic.workdayResponse + + - kind: SetVariable + id: set_count + variable: Topic.slipCount + value: =CountRows(Topic.parsedPaySlips.data) + + - kind: ConditionGroup + id: check_empty + conditions: + - id: has_slips + condition: =Topic.slipCount > 0 + actions: + - kind: SendActivity + id: show_slips + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Your pay slips", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Here are your " & Topic.slipCount & " most recent pay slip" & If(Topic.slipCount = 1, "", "s") & " from Workday.", + wrap: true, + spacing: "Small", + isSubtle: true + }, + { + type: "Container", + spacing: "Medium", + items: ForAll( + Topic.parsedPaySlips.data, + { + type: "Container", + style: "emphasis", + bleed: false, + spacing: "Small", + items: [ + { + type: "TextBlock", + text: Coalesce(descriptor, "Pay slip"), + weight: "Bolder", + wrap: true + }, + { + type: "FactSet", + spacing: "Small", + facts: [ + { + title: "Pay date", + value: If(IsBlank(date), "-", Left(date, 10)) + }, + { + title: "Gross pay", + value: If(IsBlank(Text(gross)), "-", Text(gross, "#,##0.00")) + }, + { + title: "Net pay", + value: If(IsBlank(Text(net)), "-", Text(net, "#,##0.00")) + }, + { + title: "Status", + value: Coalesce(status.descriptor, "-") + } + ] + } + ] + } + ) + } + ], + actions: [] + } + + - kind: CancelAllDialogs + id: end_dialogs + + elseActions: + - kind: SendActivity + id: no_slips + activity: No pay slips were found in Workday. If you recently started, pay slips may not be available yet. + + - kind: CancelAllDialogs + id: end_no_slips + + elseActions: + - kind: SendActivity + id: error_msg + activity: I was not able to retrieve your pay slips. Please try again or view them directly in Workday. + + - kind: CancelAllDialogs + id: end_error + +inputType: {} +outputType: {} diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/README.md b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/README.md new file mode 100644 index 00000000..f0f9b95e --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/README.md @@ -0,0 +1,95 @@ +# Workday Extended Topics + +These topics extend the base ESS agent with additional Workday scenarios. They use the Workday REST API via the WorkdayRESTExecution flow and WorkdaySystemGetRESTExecution system topic included in the base solution. Add only the topics relevant to your organization and also ensure that the OAuth Client in Workday has needed scopes to access the data. + +| File | Who uses it | What it does | +| --- | --- | --- | +| `GetInboxTasks.yaml` | All employees | Shows open Workday inbox tasks | +| `GetPaySlips.yaml` | All employees | Shows recent pay slips with pay date, gross, net, and status | +| `RequestFeedback.yaml` | All employees | Requests feedback from a coworker | +| `TransferEmployee.yaml` | Managers | Transfers a direct report to a new manager | + +## Before you start + +The following are included in the **EssITWorkdayHCM** and **EssHRWorkdayHCM** base solutions. Confirm they are active before adding any topic here. + +1. In Copilot Studio, go to **Topics** and confirm **WorkdaySystemGetRESTExecution** is present and turned on. + +2. In this topic, go to the node where the flow is configured as **WorkdayRESTExecution** and navigate to this flow (Power Automate page) and confirm its status is **On**. If it is off, open it and turn it on. You may be asked to authorize the Workday connection. + +If either is missing, import the latest EssITWorkdayHCM or EssHRWorkdayHCM solution first. + +## Namespace check + +Each topic references other topics by their full namespace. Copilot Studio resolves these automatically based on which solution you have deployed. After saving a topic, verify in the code editor that all `dialog:` references match your solution's namespace. + +**EssITWorkdayHCM** — references should use `msdyn_copilotforemployeeselfserviceit.topic.` + +**EssHRWorkdayHCM** — references should use `msdyn_copilotforemployeeselfservicehr.topic.` + +If any reference does not match, update it manually in the code editor before publishing. + +## How to add a topic + +Copilot Studio does not support file upload for topics. Add each one using the code editor: + +1. Open your ESS agent in Copilot Studio. +2. Go to **Topics** and click **Add a topic** then **From blank**. +3. Give the topic a name (see the suggested names in the table below). +4. Click the **...** menu on the topic and select **Open code editor**. +5. Select all the existing content, paste in the contents of the `.yaml` file, and save. +6. Repeat for any other topics you want to add. +7. Publish the agent when done. + +Suggested topic names to use in step 3: + +| File | Suggested topic name | +| --- | --- | +| `GetInboxTasks.yaml` | Get Employee Inbox Tasks | +| `GetPaySlips.yaml` | Get Pay Slips | +| `RequestFeedback.yaml` | Request Feedback on Worker | +| `TransferEmployee.yaml` | Transfer Employee | + +## Configuration + +Most topics work without any changes after pasting. The exception is `TransferEmployee.yaml`. + +**TransferEmployee.yaml** has one optional setting near the top of the file: + +```yaml +- kind: SetVariable + id: set_reason_id + variable: Topic.TransferReasonId + value: "" +``` + +If your Workday tenant requires a job change reason for transfers, replace `""` with your tenant's transfer reason ID (for example `"JOB_CHANGE_REASON-6-5"`). Find this in Workday under **Maintain Job Change Reasons**. If your tenant does not require it, leave the value as `""`. + +**RequestFeedback.yaml** requires at least one feedback template to be configured in your Workday tenant. The template list is fetched dynamically at runtime. No changes to the topic file are needed once templates are set up in Workday. + +## Adjustable limits + +Both `GetInboxTasks.yaml` and `GetPaySlips.yaml` have a variable at the top that controls how many records are fetched. Change the value in the code editor after pasting if needed. + +| Topic | Variable | Default | Maximum | +| --- | --- | --- | --- | +| GetInboxTasks | `Topic.MaxTasks` | 20 | 100 | +| GetPaySlips | `Topic.MaxSlips` | 10 | 100 | + +## Previews + +**Get Inbox Tasks** + +![Get Inbox Tasks](assets/GetInboxTasks.png) + +**Request Feedback** + +![Request Feedback](assets/RequestFeedback.png) + +**Transfer Employee** + +![Transfer Employee](assets/TransferEmployee.png) + +**Get Pay Slips** + +![Get Pay Slips](assets/GetPaySlips.png) \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/RequestFeedback.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/RequestFeedback.yaml new file mode 100644 index 00000000..788a0f25 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/RequestFeedback.yaml @@ -0,0 +1,393 @@ +kind: AdaptiveDialog +modelDescription: |- + Allows an employee to request feedback FROM a coworker about themselves. + The employee is the subject of the feedback; the coworker (responder) is who provides it. + + Trigger: "I want to request feedback from Carl" + + Steps: + 1. Find the coworker (responder) by name search - skipped if name was in the trigger + 2. Select a feedback template, sharing preference, and expiration date in one combined card + 3. Submit and show confirmation + +inputs: + - kind: AutomaticTaskInput + propertyName: ResponderName + description: Name of the coworker the employee wants feedback from, if mentioned in the trigger message + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - I want to request feedback from someone + - Request feedback from a coworker + - Ask Carl for feedback + - Get feedback from a colleague + - Request peer feedback + - I want feedback on my performance + - Request feedback + + actions: + # Step 1 - Get responder name. Skip prompt if name came from the trigger message. + - kind: ConditionGroup + id: check_name_in_trigger + conditions: + - id: name_provided + condition: =!IsBlank(Topic.ResponderName) + actions: + - kind: SetVariable + id: use_trigger_name + variable: Topic.responderSearch + value: =Topic.ResponderName + + elseActions: + - kind: Question + id: ask_responder_name + interruptionPolicy: + allowInterruption: false + variable: Topic.responderSearch + prompt: Who would you like to request feedback from? Please enter their name. + entity: StringPrebuiltEntity + + # Search for the coworker by name + - kind: BeginDialog + id: search_responder + displayName: Search for responder by name + input: + binding: + parameters: ="{""search"":""" & Topic.responderSearch & """,""limit"":10,""offset"":0}" + operationName: SearchWorkers + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.searchError + isSuccess: Topic.searchSuccess + workdayResponse: Topic.searchResponse + + - kind: ConditionGroup + id: check_search_result + conditions: + - id: search_failed + condition: =Topic.searchSuccess = false + actions: + - kind: SendActivity + id: search_fail_msg + activity: I could not search for that name in Workday. Please try again. + + - kind: CancelAllDialogs + id: cancel_search_failed + + - kind: ParseValue + id: parse_responder + displayName: Parse responder search result + variable: Topic.parsedResponder + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + primaryWorkEmail: String + businessTitle: String + total: + type: Number + + value: =Topic.searchResponse + + - kind: ConditionGroup + id: check_responder_found + conditions: + - id: not_found + condition: =CountRows(Topic.parsedResponder.data) = 0 + actions: + - kind: SendActivity + id: not_found_msg + activity: No Workday worker found matching **{Topic.responderSearch}**. Please check the name and try again. + + - kind: CancelAllDialogs + id: cancel_not_found + + # Pre-set worker ID and name for single result - no picker needed in the combined form + - kind: SetVariable + id: pre_set_responder_id + variable: Topic.responderWorkerID + value: =If(CountRows(Topic.parsedResponder.data) = 1, First(Topic.parsedResponder.data).id, "") + + - kind: SetVariable + id: pre_set_responder_name + variable: Topic.responderName + value: =If(CountRows(Topic.parsedResponder.data) = 1, First(Topic.parsedResponder.data).descriptor, "") + + # Get feedback templates - always scoped to the requesting employee, not the responder + - kind: BeginDialog + id: get_templates + displayName: Get feedback templates + input: + binding: + parameters: ="{""workerID"":""Employee_ID=" & Global.ESS_UserContext_Employee_Id & """,""limit"":20,""offset"":0}" + operationName: GetFeedbackTemplates + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.templateError + isSuccess: Topic.templateSuccess + workdayResponse: Topic.templateResponse + + - kind: ParseValue + id: parse_templates + displayName: Parse feedback templates + variable: Topic.parsedTemplates + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + total: + type: Number + + value: =If(Topic.templateSuccess, Topic.templateResponse, "{""data"":[],""total"":0}") + + # Step 2 - Combined card: worker picker (only when multiple results) + template + sharing + expiration + - kind: AdaptiveCardPrompt + id: collect_feedback_form + displayName: Combined feedback request form + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Request feedback", + weight: "Bolder", + size: "Medium", + wrap: true + }, + If( + CountRows(Topic.parsedResponder.data) = 1, + { + type: "TextBlock", + text: "Requesting feedback from " & Topic.responderName, + wrap: true, + spacing: "Small", + isSubtle: true + }, + { + type: "Input.ChoiceSet", + id: "selectedResponderId", + label: "Select coworker", + style: "compact", + isRequired: true, + errorMessage: "Please select a coworker.", + choices: ForAll( + Topic.parsedResponder.data, + { + title: descriptor & If(IsBlank(businessTitle), "", " - " & businessTitle) & If(IsBlank(primaryWorkEmail), "", " (" & primaryWorkEmail & ")"), + value: id + } + ) + } + ), + { + type: "Input.ChoiceSet", + id: "feedbackTemplateId", + label: "Feedback template", + style: "compact", + isRequired: true, + errorMessage: "Please select a feedback template.", + spacing: "Medium", + choices: If( + CountRows(Topic.parsedTemplates.data) = 0, + [ { title: "No templates available. Check Workday configuration.", value: "" } ], + ForAll(Topic.parsedTemplates.data, { title: descriptor, value: id }) + ) + }, + { + type: "Input.ChoiceSet", + id: "feedbackSharing", + label: "Who can see this feedback?", + style: "compact", + isRequired: false, + value: "shareWithMe", + choices: [ + { title: "Share with me only", value: "shareWithMe" }, + { title: "Also share with my manager", value: "shareWithManager" } + ] + }, + { + type: "Input.Date", + id: "expirationDate", + label: "Request expires on", + value: Text(DateAdd(Today(), 14, TimeUnit.Days), "yyyy-MM-dd"), + isRequired: false + } + ], + actions: [ + { type: "Action.Submit", title: "Submit Request", id: "Submit" }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId + selectedResponderId: Topic.selectedResponderId + feedbackTemplateId: Topic.feedbackTemplateId + feedbackSharing: Topic.feedbackSharing + expirationDate: Topic.expirationDate + + outputType: + properties: + actionSubmitId: String + selectedResponderId: String + feedbackTemplateId: String + feedbackSharing: String + expirationDate: String + + - kind: ConditionGroup + id: handle_form_cancel + conditions: + - id: cancelled + condition: =Topic.actionSubmitId = "Cancel" + actions: + - kind: SendActivity + id: form_cancel_msg + activity: Feedback request cancelled. Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_form + + # Resolve final worker ID - use picker selection if multiple results were shown + - kind: SetVariable + id: resolve_responder_id + variable: Topic.responderWorkerID + value: =If(IsBlank(Topic.selectedResponderId), Topic.responderWorkerID, Topic.selectedResponderId) + + - kind: SetVariable + id: resolve_responder_name + variable: Topic.responderName + value: =If(IsBlank(Topic.selectedResponderId), Topic.responderName, LookUp(Topic.parsedResponder.data, id = Topic.selectedResponderId).descriptor) + + # Map feedbackSharing choice to Workday fields: + # "shareWithMe" - feedbackConfidential=false, showFeedbackProviderName=true + # "shareWithManager" - feedbackConfidential=true, showFeedbackProviderName=true + - kind: SetVariable + id: set_confidential + variable: Topic.feedbackConfidential + value: =If(Topic.feedbackSharing = "shareWithManager", "true", "false") + + # Step 3 - Submit the feedback request + - kind: BeginDialog + id: submit_feedback + displayName: Submit feedback request + input: + binding: + parameters: ="{""workerID"":""Employee_ID=" & Global.ESS_UserContext_Employee_Id & """,""feedbackTemplateId"":""" & Topic.feedbackTemplateId & """,""feedbackResponders"":[{""id"":""" & Topic.responderWorkerID & """}],""expirationDate"":""" & Topic.expirationDate & """,""feedbackConfidential"":" & Topic.feedbackConfidential & ",""showFeedbackProviderName"":true}" + operationName: RequestFeedback + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + # Step 4 - Show result + - kind: ConditionGroup + id: show_result + conditions: + - id: success + condition: =Topic.isSuccess = true + actions: + - kind: ParseValue + id: parse_feedback_result + variable: Topic.parsedFeedback + valueType: + kind: Record + properties: + id: String + descriptor: String + requestDate: String + feedbackOverallStatus: String + + value: =Topic.workdayResponse + + - kind: SendActivity + id: success_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Feedback request sent", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Your feedback request has been submitted. " & Topic.responderName & " will receive a notification.", + wrap: true, + spacing: "Small" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Feedback from", value: Topic.responderName }, + { title: "Request date", value: Coalesce(Topic.parsedFeedback.requestDate, Text(Today(), "yyyy-MM-dd")) }, + { title: "Expires", value: Topic.expirationDate }, + { title: "Visibility", value: If(Topic.feedbackSharing = "shareWithManager", "Shared with you and your manager", "Visible to you only") } + ] + } + ], + actions: [] + } + + - kind: CancelAllDialogs + id: end_success + + elseActions: + - kind: AnswerQuestionWithAI + id: generate_error + autoSend: false + variable: Topic.friendlyError + userInput: =Topic.errorResponse + additionalInstructions: Reframe this Workday error for an employee in one short sentence. Do not apologize or include technical details. + + - kind: SendActivity + id: error_msg + activity: "{If(IsBlank(Topic.friendlyError), \"The feedback request could not be submitted. Please check that the template is configured correctly in Workday.\", Topic.friendlyError)}" + + - kind: CancelAllDialogs + id: end_error + +inputType: + properties: + ResponderName: + displayName: ResponderName + description: Name of the coworker to request feedback from, if mentioned in the trigger message + type: String + +outputType: {} diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/TransferEmployee.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/TransferEmployee.yaml new file mode 100644 index 00000000..99bc642a --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/TransferEmployee.yaml @@ -0,0 +1,607 @@ +kind: AdaptiveDialog +modelDescription: |- + Allows a manager to transfer one of their direct reports to a different manager. + The scenario is "Transfer to Different Manager" (move the worker to a new supervisory org). + + Steps: + 1. Manager selects one of their direct reports to transfer + 2. Manager provides the new manager's email address + 3. Agent resolves the new manager's supervisory org (via REST search) + 4. Manager confirms effective date and submits + 5. Agent shows confirmation with job change details and Workday link + + NOTE: The jobChangeReason field may be required by your Workday tenant. + Set TRANSFER_REASON_ID to your tenant's transfer job change reason ID. + If not required, leave it blank and the connector will omit it. + +inputs: + - kind: AutomaticTaskInput + propertyName: ReporteeName + description: Name of the direct report to transfer, if mentioned in the trigger message + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Transfer an employee to a new manager + - I want to transfer someone to a different manager + - Move a direct report to another team + - Transfer employee + - Reassign a reportee to a new manager + - Change manager for an employee + + actions: + - kind: BeginDialog + id: check_manager + displayName: Check manager criteria + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdayManagerCheck + + # ================================================================ + # CONFIGURATION - customize for your environment + # TRANSFER_REASON_ID: your Workday job change reason ID for transfers. + # Leave blank ("") if your Workday tenant does not require it. + # ================================================================ + - kind: SetVariable + id: set_reason_id + displayName: Set job change reason ID + variable: Topic.TransferReasonId + value: "" + + # Step 1 - Get direct reports and let manager select one + - kind: BeginDialog + id: get_direct_reports + displayName: Get direct reports + input: + binding: + parameters: ="{""workerID"":""Employee_ID=" & Global.ESS_UserContext_Employee_Id & """,""limit"":50,""offset"":0}" + operationName: GetWorkerDirectReports + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.reportsError + isSuccess: Topic.reportsSuccess + workdayResponse: Topic.reportsResponse + + - kind: ConditionGroup + id: check_reports_fetched + conditions: + - id: reports_failed + condition: =Topic.reportsSuccess = false + actions: + - kind: SendActivity + id: reports_error_msg + activity: I was not able to retrieve your direct reports right now. Please try again. + + - kind: CancelAllDialogs + id: cancel_reports_failed + + - kind: ParseValue + id: parse_reports + displayName: Parse direct reports + variable: Topic.parsedReports + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + + value: =Topic.reportsResponse + + - kind: ConditionGroup + id: check_has_reports + conditions: + - id: no_reports + condition: =CountRows(Topic.parsedReports.data) = 0 + actions: + - kind: SendActivity + id: no_reports_msg + activity: You do not have any direct reports to transfer. + + - kind: CancelAllDialogs + id: cancel_no_reports + + # P1: Autofill reportee from trigger message if name was provided + - kind: SetVariable + id: autofill_reportee + displayName: Autofill reportee selection if name was provided + variable: Topic.autofillReporteeId + value: =If(IsBlank(Topic.ReporteeName), "", Coalesce(LookUp(Topic.parsedReports.data, descriptor = Topic.ReporteeName).id, LookUp(Topic.parsedReports.data, StartsWith(descriptor, Topic.ReporteeName)).id, "")) + + - kind: AdaptiveCardPrompt + id: select_reportee + displayName: Select direct report to transfer + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Transfer an employee to a new manager", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Select the direct report you want to transfer:", + wrap: true, + spacing: "Small" + }, + { + type: "Input.ChoiceSet", + id: "reporteeId", + style: "compact", + isRequired: true, + errorMessage: "Please select an employee to transfer.", + value: Topic.autofillReporteeId, + choices: ForAll( + Topic.parsedReports.data, + { title: descriptor, value: id } + ) + } + ], + actions: [ + { type: "Action.Submit", title: "Next", id: "Next" }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId + reporteeId: Topic.targetWorkerID + + outputType: + properties: + actionSubmitId: String + reporteeId: String + + - kind: ConditionGroup + id: handle_reportee_cancel + conditions: + - id: cancelled + condition: =Topic.actionSubmitId = "Cancel" + actions: + - kind: SendActivity + id: cancel_msg + activity: Transfer cancelled. Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_all + + - kind: SetVariable + id: set_reportee_name + variable: Topic.targetWorkerName + value: =LookUp(Topic.parsedReports.data, id = Topic.targetWorkerID).descriptor + + # Step 2 - Ask for new manager's name + - kind: AdaptiveCardPrompt + id: collect_transfer_details + displayName: Collect new manager details and effective date + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Transfer " & Topic.targetWorkerName, + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Provide the new manager's details:", + wrap: true, + spacing: "Small" + }, + { + type: "Input.Text", + id: "newManagerSearch", + label: "New manager's name", + placeholder: "Search by name...", + isRequired: true, + errorMessage: "Please enter the new manager's name." + }, + { + type: "Input.Date", + id: "effectiveDate", + label: "Effective date for the transfer", + value: Text(Today(), "yyyy-MM-dd"), + isRequired: true, + errorMessage: "Please provide an effective date." + } + ], + actions: [ + { type: "Action.Submit", title: "Next", id: "Next" }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId2 + newManagerSearch: Topic.newManagerSearch + effectiveDate: Topic.effectiveDate + + outputType: + properties: + actionSubmitId: String + newManagerSearch: String + effectiveDate: String + + - kind: ConditionGroup + id: handle_details_cancel + conditions: + - id: cancelled + condition: =Topic.actionSubmitId2 = "Cancel" + actions: + - kind: SendActivity + id: cancel_msg2 + activity: Transfer cancelled. Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_all2 + + # Step 3 - Search for new manager by name to get their worker ID + - kind: BeginDialog + id: search_new_manager + displayName: Search for new manager by name + input: + binding: + parameters: ="{""search"":""" & Topic.newManagerSearch & """,""limit"":5,""offset"":0}" + operationName: SearchWorkers + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.searchError + isSuccess: Topic.searchSuccess + workdayResponse: Topic.searchResponse + + - kind: ConditionGroup + id: check_manager_found + conditions: + - id: search_failed + condition: =Topic.searchSuccess = false + actions: + - kind: SendActivity + id: search_fail_msg + activity: I could not search for that name in Workday. Please try again. + + - kind: CancelAllDialogs + id: cancel_search_failed + + - kind: ParseValue + id: parse_manager_search + displayName: Parse new manager search result + variable: Topic.parsedManager + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + primaryWorkEmail: String + businessTitle: String + total: + type: Number + + value: =Topic.searchResponse + + - kind: ConditionGroup + id: check_manager_result + conditions: + - id: not_found + condition: =CountRows(Topic.parsedManager.data) = 0 + actions: + - kind: SendActivity + id: not_found_msg + activity: No Workday worker found matching **{Topic.newManagerSearch}**. Please check the name and try again. + + - kind: CancelAllDialogs + id: cancel_not_found + + - id: single_result + condition: =CountRows(Topic.parsedManager.data) = 1 + actions: + - kind: SetVariable + id: set_new_manager_id_single + variable: Topic.newManagerWorkerID + value: =First(Topic.parsedManager.data).id + + - kind: SetVariable + id: set_new_manager_name_single + variable: Topic.newManagerName + value: =First(Topic.parsedManager.data).descriptor + + elseActions: + - kind: AdaptiveCardPrompt + id: pick_manager + displayName: Ask manager to select from multiple results + card: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Select the new manager", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "Multiple workers match. Please select the correct manager:", + wrap: true, + spacing: "Small", + isSubtle: true + }, + { + type: "Input.ChoiceSet", + id: "selectedManagerId", + style: "compact", + isRequired: true, + errorMessage: "Please select a manager.", + choices: ForAll( + Topic.parsedManager.data, + { + title: descriptor & If(IsBlank(businessTitle), "", " - " & businessTitle) & If(IsBlank(primaryWorkEmail), "", " (" & primaryWorkEmail & ")"), + value: id + } + ) + } + ], + actions: [ + { type: "Action.Submit", title: "Select", id: "Select" }, + { type: "Action.Submit", title: "Cancel", id: "Cancel", associatedInputs: "none" } + ] + } + output: + binding: + actionSubmitId: Topic.actionSubmitId3 + selectedManagerId: Topic.selectedManagerId + + outputType: + properties: + actionSubmitId: String + selectedManagerId: String + + - kind: ConditionGroup + id: handle_manager_pick_cancel + conditions: + - id: cancelled + condition: =Topic.actionSubmitId3 = "Cancel" + actions: + - kind: SendActivity + id: manager_pick_cancel_msg + activity: Transfer cancelled. Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_manager_pick + + - kind: SetVariable + id: set_new_manager_id_multi + variable: Topic.newManagerWorkerID + value: =Topic.selectedManagerId + + - kind: SetVariable + id: set_new_manager_name_multi + variable: Topic.newManagerName + value: =LookUp(Topic.parsedManager.data, id = Topic.selectedManagerId).descriptor + + # Step 4 - Get supervisory org managed by the new manager + - kind: BeginDialog + id: get_manager_org + displayName: Get supervisory org managed by new manager + input: + binding: + parameters: ="{""workerID"":""" & Topic.newManagerWorkerID & """,""limit"":5,""offset"":0}" + operationName: GetSupervisoryOrganizationsManaged + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.orgError + isSuccess: Topic.orgSuccess + workdayResponse: Topic.orgResponse + + - kind: ConditionGroup + id: check_org_found + conditions: + - id: org_failed + condition: =Topic.orgSuccess = false + actions: + - kind: SendActivity + id: org_fail_msg + activity: I found **{Topic.newManagerName}** in Workday but could not retrieve their supervisory organization. They may not manage a team yet. + + - kind: CancelAllDialogs + id: cancel_org_failed + + - kind: ParseValue + id: parse_org + displayName: Parse supervisory org + variable: Topic.parsedOrg + valueType: + kind: Record + properties: + data: + type: + kind: Table + properties: + id: String + descriptor: String + total: + type: Number + + value: =Topic.orgResponse + + - kind: ConditionGroup + id: check_org_exists + conditions: + - id: no_org + condition: =CountRows(Topic.parsedOrg.data) = 0 + actions: + - kind: SendActivity + id: no_org_msg + activity: "**{Topic.newManagerName}** does not manage a supervisory organization in Workday. A manager must have an active org before an employee can be transferred to them." + + - kind: CancelAllDialogs + id: cancel_no_org + + # Use the first org (the primary one). If manager has multiple orgs, use the first. + - kind: SetVariable + id: set_org_id + variable: Topic.supervisoryOrgId + value: =First(Topic.parsedOrg.data).id + + - kind: SetVariable + id: set_org_name + variable: Topic.supervisoryOrgName + value: =First(Topic.parsedOrg.data).descriptor + + # Step 5 - Confirm and submit + - kind: Question + id: confirm_transfer + interruptionPolicy: + allowInterruption: false + variable: Topic.confirmTransfer + prompt: |- + Please confirm the transfer details: + - **Employee:** {Topic.targetWorkerName} + - **New manager:** {Topic.newManagerName} + - **New supervisory org:** {Topic.supervisoryOrgName} + - **Effective date:** {Topic.effectiveDate} + + Proceed with the transfer? + entity: BooleanPrebuiltEntity + + - kind: ConditionGroup + id: handle_confirm + conditions: + - id: not_confirmed + condition: =Topic.confirmTransfer = false + actions: + - kind: SendActivity + id: not_confirmed_msg + activity: Transfer cancelled. Let me know if you need anything else. + + - kind: CancelAllDialogs + id: cancel_not_confirmed + + - kind: BeginDialog + id: submit_transfer + displayName: Submit employee transfer + input: + binding: + parameters: ="{""workerID"":""" & Topic.targetWorkerID & """,""supervisoryOrganizationId"":""" & Topic.supervisoryOrgId & """" & If(IsBlank(Topic.TransferReasonId), "", ",""jobChangeReasonId"":""" & Topic.TransferReasonId & """") & ",""effective"":""" & Topic.effectiveDate & """,""moveManagersTeam"":false}" + operationName: TransferEmployee + + dialog: msdyn_copilotforemployeeselfservice.topic.WorkdaySystemGetRESTExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ConditionGroup + id: show_result + conditions: + - id: success + condition: =Topic.isSuccess = true + actions: + - kind: ParseValue + id: parse_transfer_result + variable: Topic.parsedTransfer + valueType: + kind: Record + properties: + id: String + descriptor: String + effective: String + + value: =Topic.workdayResponse + + - kind: SendActivity + id: success_card + activity: + attachments: + - kind: AdaptiveCardTemplate + cardContent: |- + ={ + type: "AdaptiveCard", + '$schema': "http://adaptivecards.io/schemas/adaptive-card.json", + version: "1.5", + body: [ + { + type: "TextBlock", + text: "Transfer submitted", + weight: "Bolder", + size: "Medium", + wrap: true + }, + { + type: "TextBlock", + text: "The job change has been submitted in Workday and is pending processing.", + wrap: true, + spacing: "Small" + }, + { + type: "FactSet", + spacing: "Medium", + facts: [ + { title: "Employee", value: Topic.targetWorkerName }, + { title: "New manager", value: Topic.newManagerName }, + { title: "Effective date", value: Coalesce(Topic.parsedTransfer.effective, Topic.effectiveDate) }, + { title: "Job change ID", value: Topic.parsedTransfer.id } + ] + } + ], + actions: [] + } + + - kind: CancelAllDialogs + id: end_success + + elseActions: + - kind: AnswerQuestionWithAI + id: generate_error + autoSend: false + variable: Topic.friendlyError + userInput: =Topic.errorResponse + additionalInstructions: Reframe this Workday error for a manager in one short sentence. Do not apologize or include technical IDs. + + - kind: SendActivity + id: error_msg + activity: "{If(IsBlank(Topic.friendlyError), \"The transfer could not be submitted. Please verify the details in Workday.\", Topic.friendlyError)}" + + - kind: CancelAllDialogs + id: end_error + +inputType: + properties: + ReporteeName: + displayName: ReporteeName + description: Name of the direct report to transfer, if mentioned in the trigger message + type: String + +outputType: {} diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetInboxTasks.png b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetInboxTasks.png new file mode 100644 index 00000000..433a500a Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetInboxTasks.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetPaySlips.png b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetPaySlips.png new file mode 100644 index 00000000..77323790 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/GetPaySlips.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/RequestFeedback.png b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/RequestFeedback.png new file mode 100644 index 00000000..7bed0699 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/RequestFeedback.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/TransferEmployee.png b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/TransferEmployee.png new file mode 100644 index 00000000..3e60dd18 Binary files /dev/null and b/EmployeeSelfServiceAgent/WorkdayDA/ExtendedTopics/assets/TransferEmployee.png differ diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/README.md b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/README.md new file mode 100644 index 00000000..89ed91da --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/README.md @@ -0,0 +1,11 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Manager Scenarios + +Copilot Studio topics for manager self-service actions in Workday. + +| Folder | Description | +| --- | --- | +| [WorkdayGetManagerReporteesTimeInPosition/](./WorkdayGetManagerReporteesTimeInPosition/) | View reportees' time in position | diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/README.md b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/README.md new file mode 100644 index 00000000..da976131 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/README.md @@ -0,0 +1,110 @@ +--- +nav_exclude: true +search_exclude: false +--- +# Workday Get Manager Reportees Time In Position + +## Overview +This scenario enables managers to view their direct reports along with how long each employee has been in their current position. It retrieves team member information from Workday HCM and calculates the time in position for each reportee. + +## Files + +| File | Description | +|------|-------------| +| `topic.yaml` | Copilot Studio topic definition with trigger phrases and Power Fx logic | +| `msdyn_GetManagerReportees.xml` | XML template for Workday Get_Workers API call | + +## Prerequisites + +### Global Variables Required +- `Global.ESS_UserContext_ManagerOrganizationId` - The manager's organization ID in Workday (used to filter direct reports) + +### Workday API +- **Service**: Human_Resources +- **Operation**: Get_Workers +- **Version**: v42.0 + +## Scenario Details + +### What It Does +1. Retrieves all employees in the manager's organization (direct reports) +2. Extracts key employee information (Name, Title, Position Start Date, etc.) +3. Calculates Time in Position for each employee using Power Fx DateDiff functions +4. Presents results with tenure information formatted as "X years, Y months, Z days" + +### Data Retrieved +| Field | Description | +|-------|-------------| +| EmployeeID | Workday Employee ID | +| Name | Employee's legal formatted name | +| BusinessTitle | Current job title | +| WorkerType | Employee or Contingent Worker | +| JobProfile | Job profile name | +| Location | Business site/location name | +| PositionStartDate | Date employee started current position | +| HireDate | Original hire date | +| Status | Active/Inactive status | +| TimeInPositionYears | Calculated years in current position | +| TimeInPositionMonths | Calculated remaining months | +| TimeInPositionDays | Calculated remaining days | +| TimeInPosition | Formatted string (e.g., "2 years, 3 months, 15 days") | + +### Trigger Phrases +- "Show me my team's time in position" +- "How long have my direct reports been in their roles" +- "Get reportees time in current position" +- "List my team members with their position tenure" +- "Who on my team has been in their role the longest" +- "Show tenure for my direct reports" +- "My reportees job tenure" +- "Time in position for my team" + +## Time In Position Calculation + +The scenario uses Power Fx formulas to calculate time in position: + +``` +TimeInPositionYears = Int(DateDiff(DateValue(PositionStartDate), Now(), TimeUnit.Months) / 12) +TimeInPositionMonths = Mod(DateDiff(DateValue(PositionStartDate), Now(), TimeUnit.Months), 12) +TimeInPositionDays = DateDiff(DateAdd(DateAdd(DateValue(PositionStartDate), Years, TimeUnit.Years), Months, TimeUnit.Months), Now(), TimeUnit.Days) +``` + +## Response Group Configuration + +The XML template is optimized for performance by: +- **Including**: Reference, Personal Information, Employment Information +- **Excluding**: Compensation, Benefits, Documents, Photos, and other unnecessary data +- **Filtering**: Only active workers (`Exclude_Inactive_Workers: true`) + +## Setup Instructions + +1. **Import the Topic**: Import `topic.yaml` into your Copilot Studio agent +2. **Add XML Template**: Upload `msdyn_GetManagerReportees.xml` to your Workday connector configuration +3. **Configure Connection**: Ensure your Workday connector connection reference is properly set in the topic +4. **Set Global Variable**: Make sure `Global.ESS_UserContext_ManagerOrganizationId` is populated (typically from user authentication context) + +## Model Instructions + +The topic includes model instructions for the AI to: +- Display results as a nested markdown list +- Format Time in Position clearly +- Show Name, Job Title, Time in Position, Start Date, and Status for each reportee +- Sort or group results based on user's question context + +## Example Output + +When a manager asks "Show me my team's time in position", the agent displays: + +``` +Here are your direct reports and their time in current position: + +- **John Smith** - Senior Developer + - Time in Position: 2 years, 5 months, 12 days + - Position Start: 2022-07-15 + - Status: Active + +- **Jane Doe** - Product Manager + - Time in Position: 1 year, 2 months, 3 days + - Position Start: 2023-10-20 + - Status: Active +``` diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/msdyn_GetManagerReportees.xml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/msdyn_GetManagerReportees.xml new file mode 100644 index 00000000..72a67a03 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/msdyn_GetManagerReportees.xml @@ -0,0 +1,124 @@ + + + + + User + + Template_GetManagerReporteesRequest + Human_Resources + v42.0 + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + EmployeeID + + + //*[local-name()='Worker_Descriptor']/text() + Name + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Business_Title']/text() + BusinessTitle + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Worker_Type_Reference']/@*[local-name()='Descriptor'] + WorkerType + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Job_Profile_Summary_Data']/*[local-name()='Job_Profile_Reference']/@*[local-name()='Descriptor'] + JobProfile + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Business_Site_Summary_Data']/*[local-name()='Location_Reference']/@*[local-name()='Descriptor'] + Location + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Start_Date']/text() + PositionStartDate + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Status_Data']/*[local-name()='Hire_Date']/text() + HireDate + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Status_Data']/*[local-name()='Active']/text() + Status + + + + + + + + + + + {Manager_Org_ID} + + + + true + false + false + false + true + false + false + true + true + true + true + true + true + true + true + true + true + true + true + true + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + true + true + true + true + true + true + true + true + true + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/topic.yaml new file mode 100644 index 00000000..b7ddb5e5 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayGetManagerReporteesTimeInPosition/topic.yaml @@ -0,0 +1,182 @@ +kind: AdaptiveDialog +modelDescription: |- + You will respond to requests about time in position for direct reports of the user making the request. + + For info on single direct report's time in position: + Heading - "[Report]'s time in position" + Verbatim line after heading: "[Report] has been in their current role as a [Position] for [LengthOfService]. I pulled this from Workday, an HR platform your company uses." + Sections as bold headers and non-bold bullets under: More about [Direct Report]'s position + + For info on all direct reports time in position: Display in Table + Heading - "Team roles and time in position" + Verbatim line after heading: "Here's a list of your team members and how long they've been in their positions. I pulled this from Workday, an HR platform your company uses." + Table columns: Name, Title, Time in Position + + Common Rules ALWAYS FOLLOW: + - ALWAYS add large sized heading with sentence case + - Introduction (Relevant) RIGHT AFTER HEADING & ADD SECTION LINE AFTER IT, + - Double check if rules are followed + Invalid: non-direct reports. +beginDialog: + kind: OnRecognizedIntent + id: main + intent: {} + actions: + - kind: BeginDialog + id: jJpz3n + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{Manager_Org_ID}"",""value"":""" & Global.ESS_UserContext_ManagerOrganizationId & """}]}" + scenarioName: msdyn_GetManagerReportees + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: aXVFNu + displayName: Parse value to a record + variable: Topic.workdayResponseRecord + valueType: + kind: Record + properties: + BusinessTitle: + type: + kind: Table + properties: + Value: String + + EmployeeID: + type: + kind: Table + properties: + Value: String + + HireDate: + type: + kind: Table + properties: + Value: String + + JobProfile: + type: + kind: Table + properties: + Value: String + + Location: + type: + kind: Table + properties: + Value: String + + Name: + type: + kind: Table + properties: + Value: String + + PositionStartDate: + type: + kind: Table + properties: + Value: String + + Status: + type: + kind: Table + properties: + Value: String + + WorkerType: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_ztXmui + displayName: Extract data to table + variable: Topic.workdayResponseTable + value: |- + =ForAll( + Sequence(CountRows(Topic.workdayResponseRecord.EmployeeID)), + { + EmployeeID: Last(FirstN(Topic.workdayResponseRecord.EmployeeID, Value)).Value, + Name: Last(FirstN(Topic.workdayResponseRecord.Name, Value)).Value, + BusinessTitle: Last(FirstN(Topic.workdayResponseRecord.BusinessTitle, Value)).Value, + WorkerType: Last(FirstN(Topic.workdayResponseRecord.WorkerType, Value)).Value, + JobProfile: Last(FirstN(Topic.workdayResponseRecord.JobProfile, Value)).Value, + Location: Last(FirstN(Topic.workdayResponseRecord.Location, Value)).Value, + PositionStartDate: Last(FirstN(Topic.workdayResponseRecord.PositionStartDate, Value)).Value, + HireDate: Last(FirstN(Topic.workdayResponseRecord.HireDate, Value)).Value, + Status: If(Last(FirstN(Topic.workdayResponseRecord.Status, Value)).Value = "1", "Active", "Inactive") + } + ) + + - kind: SetVariable + id: setVariable_AddTimeInPosition + displayName: Add Time in Position calculations + variable: Topic.workdayResponseTableWithTimeInPosition + value: |- + =ForAll( + Topic.workdayResponseTable, + With( + { + positionDate: DateValue(PositionStartDate), + yearsCalc: RoundDown(DateDiff(DateValue(PositionStartDate), Today(), TimeUnit.Months) / 12, 0), + monthsCalc: Mod(DateDiff(DateValue(PositionStartDate), Today(), TimeUnit.Months), 12), + daysCalc: DateDiff( + DateAdd(DateValue(PositionStartDate), DateDiff(DateValue(PositionStartDate), Today(), TimeUnit.Months), TimeUnit.Months), + Today(), + TimeUnit.Days + ) + }, + { + EmployeeID: EmployeeID, + Name: Name, + BusinessTitle: BusinessTitle, + WorkerType: WorkerType, + JobProfile: JobProfile, + Location: Location, + PositionStartDate: PositionStartDate, + HireDate: HireDate, + Status: Status, + TimeInPositionYears: yearsCalc, + TimeInPositionMonths: monthsCalc, + TimeInPositionDays: daysCalc, + TimeInPosition: + If(yearsCalc > 0, yearsCalc & " year" & If(yearsCalc > 1, "s", "") & " ", "") & + If(monthsCalc > 0, monthsCalc & " month" & If(monthsCalc > 1, "s", "") & " ", "") & + daysCalc & " day" & If(daysCalc <> 1, "s", "") + } + ) + ) + +inputType: {} +outputType: + properties: + workdayResponseTableWithTimeInPosition: + displayName: workdayResponseTableWithTimeInPosition + type: + kind: Table + properties: + BusinessTitle: String + EmployeeID: String + HireDate: String + JobProfile: String + Location: String + Name: String + PositionStartDate: String + Status: String + TimeInPosition: String + TimeInPositionDays: Number + TimeInPositionMonths: Number + TimeInPositionYears: Number + WorkerType: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/msdyn_HRWorkdayHCMManagerServiceAnniversary.xml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/msdyn_HRWorkdayHCMManagerServiceAnniversary.xml new file mode 100644 index 00000000..54aa264c --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/msdyn_HRWorkdayHCMManagerServiceAnniversary.xml @@ -0,0 +1,80 @@ + + + + + User + + msdyn_HRWorkdayHCMManagerServiceAnniversary_GetWorkersManagerRequest + Human_Resources + v41.0 + + + + Include_Employment_Information + true + + + Include_Personal_Information + true + + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + WorkerID + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='First_Name']/text() + + FirstName + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='Last_Name']/text() + LastName + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Status_Data']/*[local-name()='Hire_Date']/text() + HireDate + + + + + + + + + + + {ManagerOrgId} + + + + {As_Of_Effective_Date} + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/topic.yaml new file mode 100644 index 00000000..9d78b295 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagerServiceAnniversary/topic.yaml @@ -0,0 +1,233 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when a MANAGER asks about the SERVICE ANNIVERSARY / work anniversary / employment anniversary / tenure milestone / hire-date anniversary of THEIR DIRECT REPORTS in Workday. ONLY for direct reports — NOT for self, manager, peers, spouse, sibling. + + Triggers: + - "Service anniversary of my direct reports" + - "Upcoming work anniversaries on my team" + - "Service anniversaries of my reports next month" + - "Which of my reports has an anniversary coming up?" + - "Team tenure milestones" + + Filter: + - "next month" → use isAnniversaryNextMonth + - Default: future anniversaries only, unless otherwise specified + + Invalid: non-direct-reports. Self-anniversary belongs to the ServiceAnniversary topic. + + Output rules: + - Output MUST be a markdown table based ONLY on {Topic.response} + - Columns: Employee Name, Hire Date, Upcoming Service Anniversary Date, Upcoming Milestone + - Do NOT return data if you don't have enough info +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: Employee name whose service anniversary needs to be retrieved + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + + - kind: AutomaticTaskInput + propertyName: duration + description: Duration used to calculate nth service anniversary date + entity: NumberPrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - When are the service anniversaries of all my directs? + - What are the service anniversaries of my entire team? + - Show me the service anniversaries of my reports? + - What are the upcoming service anniversaries of my team? + - Any upcoming service anniversaries of my reports? + - Show me service anniversaries of my directs? + - What is [EmployeeName]'s next service anniversary? + - When is [EmployeeName]'s next year service anniversary? + + actions: + - kind: BeginDialog + id: WmAHrc + displayName: Check manager criteria + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdayManagerCheck + + - kind: SetVariable + id: setVariable_FrkyzI + displayName: Set current date + variable: Topic.currentDate + value: =Now() + + - kind: BeginDialog + id: HZDypL + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{ManagerOrgId}"",""value"":""" & Global.ESS_UserContext_ManagerOrganizationId & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMManagerServiceAnniversary + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: Nh88X0 + displayName: Parse Workday response + variable: Topic.parsedWorkdayResponse + valueType: + kind: Record + properties: + FirstName: + type: + kind: Table + properties: + Value: String + + HireDate: + type: + kind: Table + properties: + Value: String + + LastName: + type: + kind: Table + properties: + Value: String + + WorkerID: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_eJUuES + displayName: Build Workday response table + variable: Topic.workdayResponseTable + value: |- + =ForAll( + Sequence(CountRows(Topic.parsedWorkdayResponse.WorkerID)), + { + FirstName: Last(FirstN(Topic.parsedWorkdayResponse.FirstName, Value)).Value, + LastName: Last(FirstN(Topic.parsedWorkdayResponse.LastName, Value)).Value, + HireDate: Last(FirstN(Topic.parsedWorkdayResponse.HireDate, Value)).Value + } + ) + + - kind: ConditionGroup + id: conditionGroup_P6QlNE + conditions: + - id: conditionItem_3RSvtM + condition: =!IsBlank(Topic.EmployeeName) + displayName: Check if EmployeeName provided + actions: + - kind: SetVariable + id: setVariable_yRTs4g + displayName: Filter for the given EmployeeName + variable: Topic.workdayResponseTable + value: |- + =Filter( + Topic.workdayResponseTable, + FirstName = Topic.EmployeeName Or + LastName = Topic.EmployeeName Or + Concatenate(FirstName, " ", LastName) = Topic.EmployeeName Or + Concatenate(LastName, " ", FirstName) = Topic.EmployeeName + ) + + - kind: ConditionGroup + id: conditionGroup_5aSdys + conditions: + - id: conditionItem_U6JgqP + condition: =IsEmpty(Topic.workdayResponseTable) + displayName: Check is filtered data empty + actions: + - kind: SendActivity + id: sendActivity_ToPxHZ + displayName: Respond with no access message + activity: It looks like you don't have access to this information. Try making a new request. + + - kind: SetVariable + id: setVariable_bqlpWp + displayName: Clear response table + variable: Topic.workdayResponseTable + value: =[] + + - kind: CancelAllDialogs + id: PsP6gr + + - kind: SetVariable + id: setVariable_CxBy4Y + displayName: Set response to formatted Workday response + variable: Topic.response + value: |- + =ForAll(Topic.workdayResponseTable, { + EmployeeName: 'FirstName' & " " & 'LastName', + HireDate: DateValue('HireDate'), + UpcomingServiceAnniversaryDate: If( + Today() <= DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years) + 1, TimeUnit.Years) + ), + UpcomingMilestone: DateDiff(DateValue('HireDate'), If( + Today() <= DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years) + 1, TimeUnit.Years) + ), TimeUnit.Years), + AnniversaryMonth: Month(If( + Today() <= DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years) + 1, TimeUnit.Years) + )), + isAnniversaryNextMonth: Month(If( + Today() <= DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years), TimeUnit.Years), + DateAdd(DateValue('HireDate'), DateDiff(DateValue('HireDate'), Today(), TimeUnit.Years) + 1, TimeUnit.Years) + )) = Month(Now()) + 1 + }) + + - kind: SetVariable + id: setVariable_AnybBj + displayName: Clear workday response + variable: Topic.workdayResponse + value: "\"\"" + + - kind: EndDialog + id: Y2VqKn + +inputType: + properties: + duration: + displayName: duration + description: Duration used to calculate nth service anniversary date + type: Number + + EmployeeName: + displayName: EmployeeName + description: Employee name whose service anniversary needs to be retrieved + type: String + +outputType: + properties: + response: + displayName: response + type: + kind: Table + properties: + AnniversaryMonth: Number + EmployeeName: String + HireDate: Date + isAnniversaryNextMonth: Boolean + UpcomingMilestone: Number + UpcomingServiceAnniversaryDate: Date \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/msdyn_HRWorkdayHCMManagerDirectCompanyCode.xml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/msdyn_HRWorkdayHCMManagerDirectCompanyCode.xml new file mode 100644 index 00000000..4773a2e3 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/msdyn_HRWorkdayHCMManagerDirectCompanyCode.xml @@ -0,0 +1,84 @@ + + + + + User + + Template_GetWorkersManagerRequest + Human_Resources + v41.0 + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + WorkerID + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='First_Name']/text() + + FirstName + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='Last_Name']/text() + LastName + + + //*[local-name()='Worker_Data']/*[local-name()='Organization_Data']/*[local-name()='Worker_Organization_Data'][*[local-name()='Organization_Data']/*[local-name()='Organization_Subtype_Reference']/*[local-name()='ID'][@*[local-name()='type']='Organization_Subtype_ID' and text()='Company']]/*[local-name()='Organization_Data']/*[local-name()='Organization_Code']/text() + CompanyCode + + + //*[local-name()='Worker_Data']/*[local-name()='Organization_Data']/*[local-name()='Worker_Organization_Data'][*[local-name()='Organization_Data']/*[local-name()='Organization_Subtype_Reference']/*[local-name()='ID'][@*[local-name()='type']='Organization_Subtype_ID' and text()='Company']]/*[local-name()='Organization_Data']/*[local-name()='Organization_Name']/text() + CompanyName + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Position_ID']/text() + PositionID + + + + + + + + + + + {ManagerOrgId} + + + + {As_Of_Effective_Date} + + + true + false + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/topic.yaml new file mode 100644 index 00000000..1874681c --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CompanyCode/topic.yaml @@ -0,0 +1,170 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when a MANAGER asks about the COMPANY CODE / company name / legal entity / employing entity of THEIR DIRECT REPORTS in Workday. ONLY for direct reports (people who report to the user) — NOT for self, manager, peers, spouse, sibling, or any non-direct-report. + + Triggers: + - "What is the company code of my direct reports?" + - "What's the company / legal entity for my reports?" + - "Show the employing company of my team" + - "Direct reports' company code" + - "My reportees' company / legal entity" + - "Which company is each of my reports employed by?" + + do NOT trigger for self-company-code questions belong to the WorkdayCompanyCode topic. + + Output rules: + - Output MUST be a nested list in markdown + - Use ONLY data from {Topic.workdayResponseTable} +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: Employee name whose company code needs to be fetched + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show me my team’s Company codes? + - What are the company codes for my reports? + - What company codes are mapped to my team members? + + actions: + - kind: BeginDialog + id: dAhS4T + displayName: Check manager status + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdayManagerCheck + + - kind: BeginDialog + id: Gt044B + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{ManagerOrgId}"",""value"":""" & Global.ESS_UserContext_ManagerOrganizationId & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMManagerDirectCompanyCode + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: aIxWzK + displayName: Parse value to a table + variable: Topic.WorkdayResponseRecord + valueType: + kind: Record + properties: + CompanyCode: + type: + kind: Table + properties: + Value: String + + CompanyName: + type: + kind: Table + properties: + Value: String + + FirstName: + type: + kind: Table + properties: + Value: String + + LastName: + type: + kind: Table + properties: + Value: String + + WorkerID: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: HC2h7w + displayName: Merge all records in table + variable: Topic.workdayResponseTable + value: |- + =ForAll( + Sequence(CountRows(Topic.WorkdayResponseRecord.WorkerID)), + { + FirstName: Last(FirstN(Topic.WorkdayResponseRecord.FirstName, Value)).Value, + LastName: Last(FirstN(Topic.WorkdayResponseRecord.LastName, Value)).Value, + CompanyCode: Last(FirstN(Topic.WorkdayResponseRecord.CompanyCode, Value)).Value, + CompanyName: Last(FirstN(Topic.WorkdayResponseRecord.CompanyName, Value)).Value + } + ) + + - kind: ConditionGroup + id: conditionGroup_yepfHF + conditions: + - id: conditionItem_86c1ij + condition: =!IsBlank(Topic.EmployeeName) + displayName: Check if EmployeeName provided + actions: + - kind: SetVariable + id: setVariable_z1fKB1 + displayName: Filter for the given EmployeeName + variable: Topic.workdayResponseTable + value: | + =Filter( + Topic.workdayResponseTable, + FirstName = Topic.EmployeeName Or + LastName = Topic.EmployeeName Or + Concatenate(FirstName, " ", LastName) = Topic.EmployeeName Or + Concatenate(LastName, " ", FirstName) = Topic.EmployeeName + ) + + - kind: ConditionGroup + id: conditionGroup_BVYTlV + conditions: + - id: conditionItem_QmVzq2 + condition: =IsEmpty(Topic.workdayResponseTable) + displayName: Check is filtered data empty + actions: + - kind: SendActivity + id: SN9frD + displayName: Respond with no access message + activity: It looks like you don't have access to this information. Try making a new request. + + - kind: SetVariable + id: setVariable_wd0UBE + displayName: Clear workday response + variable: Topic.workdayResponse + value: ="" + + - kind: EndDialog + id: xVALae + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: Employee name whose company code needs to be fetched + type: String + +outputType: + properties: + workdayResponseTable: + displayName: workdayResponseTable + type: + kind: Table + properties: + CompanyCode: String + CompanyName: String + FirstName: String + LastName: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/msdyn_HRWorkdayHCMManagerDirectCostCenter.xml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/msdyn_HRWorkdayHCMManagerDirectCostCenter.xml new file mode 100644 index 00000000..291556b8 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/msdyn_HRWorkdayHCMManagerDirectCostCenter.xml @@ -0,0 +1,77 @@ + + + + + User + + Tempalte_ManagerDirectCostCenter_GetWorkersManagerRequest + Human_Resources + v42.0 + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + WorkerID + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data'] + LegalNameData + + + //*[local-name()='Worker_Organization_Data'][*[local-name()='Organization_Data']/*[local-name()='Organization_Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Organization_Type_ID' and text()='Cost_Center']]/*[local-name()='Organization_Data']/*[local-name()='Organization_Code']/text() + CostCenterCode + + + //*[local-name()='Worker_Organization_Data'][*[local-name()='Organization_Data']/*[local-name()='Organization_Type_Reference']/*[local-name()='ID'][@*[local-name()='type']='Organization_Type_ID' and text()='Cost_Center']]/*[local-name()='Organization_Data']/*[local-name()='Organization_Name']/text() + CostCenterName + + + //*[local-name()='Worker_Data']/*[local-name()='Employment_Data']/*[local-name()='Worker_Job_Data']/*[local-name()='Position_Data']/*[local-name()='Position_ID']/text() + PositionID + + + + + + + + + + + {ManagerOrgId} + + + + {As_Of_Effective_Date} + + + true + false + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/topic.yaml new file mode 100644 index 00000000..517f7ae0 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-CostCenter/topic.yaml @@ -0,0 +1,167 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when a MANAGER asks about the COST CENTER / cost center code / cost center name / cost center number of THEIR DIRECT REPORTS in Workday. ONLY for direct reports (people who report to the user) — NOT for self, manager, peers, spouse, sibling, or any non-direct-report. + + Triggers: + - "What is the cost center of my direct reports?" + - "Cost center of my reports / team" + - "Show cost centers for my direct reports" + - "Which cost center is each of my reports under?" + - "Cost center and company for my team" + - "Direct reports' cost center" + + Result data contains a list of direct reports with their company name and cost center. + + Invalid (do NOT trigger): questions about another person's cost center who isn't a direct report — manager, sister, peer. Self-cost-center questions belong to the WorkdayCostCenter topic. + + Output rules: + - Output MUST be a nested list in markdown + - Use ONLY data from {Topic.workdayResponseTable} + - Do NOT return data if you don't have enough info + +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: Employee name whose cost center needs to be retrieved + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show me my team's cost center data? + - What cost centers are assigned to my reports? + - What are my team's cost centers? + + actions: + - kind: BeginDialog + id: 3VGa9O + displayName: Check manager status + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdayManagerCheck + + - kind: BeginDialog + id: jJpz3n + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{ManagerOrgId}"",""value"":""" & Global.ESS_UserContext_ManagerOrganizationId & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: ="msdyn_HRWorkdayHCMManagerDirectCostCenter" + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: aXVFNu + displayName: Parse value to a record + variable: Topic.workdayResponseRecord + valueType: + kind: Record + properties: + CostCenterCode: + type: + kind: Table + properties: + Value: String + + CostCenterName: + type: + kind: Table + properties: + Value: String + + LegalNameData: + type: + kind: Table + properties: + Name_Detail_Data: + type: + kind: Record + properties: + "@Formatted_Name": String + First_Name: String + Last_Name: String + + WorkerID: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: SetVariable + id: setVariable_ztXmui + displayName: Extract data to table + variable: Topic.workdayResponseTable + value: |- + =ForAll( + Sequence(CountRows(Topic.workdayResponseRecord.WorkerID)), + { + FirstName: Last(FirstN(Topic.workdayResponseRecord.LegalNameData, Value)).Name_Detail_Data.First_Name, + LastName: Last(FirstN(Topic.workdayResponseRecord.LegalNameData, Value)).Name_Detail_Data.Last_Name, + CostCenterCode: Last(FirstN(Topic.workdayResponseRecord.CostCenterCode, Value)).Value, + CostCenterName: Last(FirstN(Topic.workdayResponseRecord.CostCenterName, Value)).Value + } + ) + + - kind: ConditionGroup + id: conditionGroup_eEjEWp + conditions: + - id: conditionItem_yv6Ggl + condition: =!IsBlank(Topic.EmployeeName) + displayName: Check if EmployeeName provided + actions: + - kind: SetVariable + id: setVariable_njPUra + displayName: Filter for the given EmployeeName + variable: Topic.workdayResponseTable + value: =Filter(Topic.workdayResponseTable,'FirstName' = Topic.EmployeeName Or 'LastName' = Topic.EmployeeName Or Concatenate('FirstName', " ", 'LastName') = Topic.EmployeeName Or Concatenate('LastName', " ", 'FirstName') = Topic.EmployeeName) + + - kind: ConditionGroup + id: conditionGroup_tAWzhS + conditions: + - id: conditionItem_sV8WXF + condition: =IsEmpty(Topic.workdayResponseTable) + displayName: Check is filtered data empty + actions: + - kind: SendActivity + id: sendActivity_3owxH6 + displayName: Respond with no access message + activity: It looks like you don't have access to this information. Try making a new request. + + - kind: SetVariable + id: setVariable_wd0UBE + displayName: Clear workday response + variable: Topic.workdayResponse + value: ="" + + - kind: EndDialog + id: HsUkV7 + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: Employee name whose cost center needs to be retrieved + type: String + +outputType: + properties: + workdayResponseTable: + displayName: workdayResponseTable + type: + kind: Table + properties: + CostCenterCode: String + CostCenterName: String + FirstName: String + LastName: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/msdyn_HRWorkdayHCMManagerJobTaxonomy.xml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/msdyn_HRWorkdayHCMManagerJobTaxonomy.xml new file mode 100644 index 00000000..a447ffeb --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/msdyn_HRWorkdayHCMManagerJobTaxonomy.xml @@ -0,0 +1,77 @@ + + + + + User + + msdyn_HRWorkdayHCMManagerJobTaxonomy_GetWorkersManagerRequest + Human_Resources + v41.0 + + + + //*[local-name()='Worker_Data']/*[local-name()='Worker_ID']/text() + WorkerID + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='First_Name']/text() + + FirstName + + + //*[local-name()='Worker_Data']/*[local-name()='Personal_Data']/*[local-name()='Name_Data']/*[local-name()='Legal_Name_Data']/*[local-name()='Name_Detail_Data']/*[local-name()='Last_Name']/text() + LastName + + + //*[local-name()="Position_Title"]/text() + JobTitle + + + //*[local-name()="Business_Title"]/text() + BusinessTitle + + + //*[local-name()="Job_Profile_Name"]/text() + JobProfile + + + //*[local-name()='Job_Profile_Summary_Data']/*[local-name()='Job_Family_Reference']/*[local-name()='ID' and @*[local-name()='type']='Job_Family_ID']/text() + JobFamilyId + + + + + + + + + + + {ManagerOrgId} + + + + {As_Of_Effective_Date} + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + + + + \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/topic.yaml b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/topic.yaml new file mode 100644 index 00000000..4babb274 --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/ManagerScenarios/WorkdayManagersdirect-Jobtaxanomy/topic.yaml @@ -0,0 +1,210 @@ +kind: AdaptiveDialog +modelDescription: |- + Use this topic when a MANAGER asks about the JOB FUNCTION / job family / job profile / job classification / job taxonomy / job code / external title / internal title / job category of THEIR DIRECT REPORTS in Workday. ONLY for direct reports — NOT for self, manager, peers, spouse, sibling, or any non-direct-report. + + Triggers: + - "What is the external title of my direct reports?" + - "What is [EmployeeName]'s job title?" + - "Job function / job family for my reports" + - "Show job classifications for my team" + - "Direct reports' job taxonomy" + + Result data is a list of direct reports with their job function fields. + + do NOT trigger for self-job-taxonomy questions belong to the ViewJobTaxonomy topic. + + Output rules: + - Output MUST be a nested list in markdown + - Use ONLY data from {Topic.workdayResponseTable} + - Do NOT return data if you don't have enough info + +inputs: + - kind: AutomaticTaskInput + propertyName: EmployeeName + description: Employee name whose job data needs to be retrieved + entity: PersonNamePrebuiltEntity + shouldPromptUser: false + inputSettings: + repeatCount: 0 + defaultValue: =Blank() + +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + triggerQueries: + - Show me my team's job title? + - What is the internal title of my direct report X? + - What is the job function of my team? + - What job profile is mapped to my team members? + - What are the external titles of my team members? + - What is the internal title of my direct report [EmployeeName]? + - Show me my team's job taxonomy? + - What is the job function of my team? + - What job profile is mapped to my team members? + - What are the external titles of my team members? + - Get job data for [EmployeeName]. + - What is the job title of [EmployeeName]? + + actions: + - kind: BeginDialog + id: Y8gqWh + displayName: Check manager status + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdayManagerCheck + + - kind: BeginDialog + id: 7Va4UU + displayName: Redirect to Workday Get Common Execution + input: + binding: + parameters: ="{""params"":[{""key"":""{ManagerOrgId}"",""value"":""" & Global.ESS_UserContext_ManagerOrganizationId & """},{""key"":""{As_Of_Effective_Date}"",""value"":"""& Text(Today(), "yyyy-MM-dd") &"""}]}" + scenarioName: msdyn_HRWorkdayHCMManagerJobTaxonomy + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemGetCommonExecution + output: + binding: + errorResponse: Topic.errorResponse + isSuccess: Topic.isSuccess + workdayResponse: Topic.workdayResponse + + - kind: ParseValue + id: mq6jr0 + displayName: Parse workdayResponse into table + variable: Topic.WorkdayResponseRecord + valueType: + kind: Record + properties: + BusinessTitle: + type: + kind: Table + properties: + Value: String + + FirstName: + type: + kind: Table + properties: + Value: String + + JobFamilyId: + type: + kind: Table + properties: + Value: String + + JobProfile: + type: + kind: Table + properties: + Value: String + + JobTitle: + type: + kind: Table + properties: + Value: String + + LastName: + type: + kind: Table + properties: + Value: String + + WorkerID: + type: + kind: Table + properties: + Value: String + + value: =Topic.workdayResponse + + - kind: BeginDialog + id: QdE53O + displayName: Refresh JobFamily reference data + input: + binding: + IsTableEmpty: =IsBlank(Global.JobFamilyLookupTable) + ReferenceDataKey: Job_Family_ID + + dialog: msdyn_copilotforemployeeselfservicehr.topic.WorkdaySystemRefreshReferenceData + + - kind: SetVariable + id: setVariable_EFFdlM + displayName: Merge all records in table + variable: Topic.workdayResponseTable + value: |- + =ForAll( + Sequence(CountRows(Topic.WorkdayResponseRecord.WorkerID)), + { + FirstName: Last(FirstN(Topic.WorkdayResponseRecord.FirstName, Value)).Value, + LastName: Last(FirstN(Topic.WorkdayResponseRecord.LastName, Value)).Value, + JobTitle: Last(FirstN(Topic.WorkdayResponseRecord.JobTitle, Value)).Value, + BusinessTitle: Last(FirstN(Topic.WorkdayResponseRecord.BusinessTitle, Value)).Value, + JobProfile: Last(FirstN(Topic.WorkdayResponseRecord.JobProfile, Value)).Value, + JobFamily: LookUp(Global.JobFamilyLookupTable, + ID = Last(FirstN(Topic.WorkdayResponseRecord.JobFamilyId, Value)).Value + ).Referenced_Object_Descriptor + } + ) + + - kind: ConditionGroup + id: conditionGroup_yepfHF + conditions: + - id: conditionItem_86c1ij + condition: =!IsBlank(Topic.EmployeeName) + displayName: Check if EmployeeName provided + actions: + - kind: SetVariable + id: setVariable_z1fKB1 + displayName: Filter for the given EmployeeName + variable: Topic.workdayResponseTable + value: | + =Filter( + Topic.workdayResponseTable, + FirstName = Topic.EmployeeName Or + LastName = Topic.EmployeeName Or + Concatenate(FirstName, " ", LastName) = Topic.EmployeeName Or + Concatenate(LastName, " ", FirstName) = Topic.EmployeeName + ) + + - kind: ConditionGroup + id: conditionGroup_BVYTlV + conditions: + - id: conditionItem_QmVzq2 + condition: =IsEmpty(Topic.workdayResponseTable) + displayName: Check is filtered data empty + actions: + - kind: SendActivity + id: sendActivity_m8xRtz + displayName: Respond with no access message + activity: It looks like you don't have access to this information. Try making a new request. + + - kind: SetVariable + id: setVariable_wd0UBE + displayName: Clear workday response + variable: Topic.workdayResponse + value: ="" + + - kind: EndDialog + id: xa93ze + +inputType: + properties: + EmployeeName: + displayName: EmployeeName + description: Employee name whose job data needs to be retrieved + type: String + +outputType: + properties: + workdayResponseTable: + displayName: workdayResponseTable + type: + kind: Table + properties: + BusinessTitle: String + FirstName: String + JobFamily: String + JobProfile: String + JobTitle: String + LastName: String \ No newline at end of file diff --git a/EmployeeSelfServiceAgent/WorkdayDA/README.md b/EmployeeSelfServiceAgent/WorkdayDA/README.md new file mode 100644 index 00000000..141c9e0a --- /dev/null +++ b/EmployeeSelfServiceAgent/WorkdayDA/README.md @@ -0,0 +1,29 @@ +--- +title: Workday +parent: Employee Self-Service +nav_order: 1 +--- +# ESS Workday Scenarios + +This folder contains sample topic definitions and ESS Template configurations XML that customers can use to extend the functionality of their ESS Agent setup. Use the topic definitions (`topic.yaml`) and the accompanying Template configuration XML file to create new topics in your environment or to customize the behavior of existing topics for the scenarios listed below. + +Usage notes: +- Each scenario folder contains a `topic.yaml` (the Copilot/ESS topic) and a template configuration used by the topic. +- Copy `topic.yaml` into your Copilot topic catalog and ensure the template configuration is added to the Employee Self Service Template Configuration. +- Update parameter bindings (for example employee id, manager org id, effective date) to match your runtime context. +- The topic `.yaml` files include trigger queries (sample prompts). Use those as seeds for testing. + +Below is a consolidated table that lists each scenario, a short description, and sample prompt(s) you can use to test the topic. + +| Scenario | Description | Sample prompt(s) | +|---|---|---| +| `EmployeeGetVacationBalance` | Returns the requesting user's vacation balance information from Workday. Displays available time off that can be taken. | "What is my vacation balance?"
"How much time off can I take?"
"What is my workday vacation balance?" | +| `WorkdayEmployeeRequestTimeOff` | Allows employees to submit time off requests for themselves through Workday. Prompts for necessary details like dates and hours. | "Request 8 hours vacation on 2025-09-15"
"I want to request time off"
"Submit vacation request" | +| `WorkdayEmployeesviewtheirjobtaxonomy` | Responds to requests about the requesting user's job taxonomy (job title, job function, job profile). | "What is my job title?"
"What is my external title?" | +| `WorkdayGetContactInformation` | Returns the requesting user's contact information (work/home phones, emails, addresses). | "What is my Work Phone?"
"Show my Home Email" | +| `WorkdayGetEducation` | Returns the requesting user's education history (school, degree, field of study, years attended). | "Show my Education Details"
"What was my field of study?" | +| `WorkdayGetGovernmentIDs` | Returns government ID information associated with the requesting user's profile (ID types, issued/expiration dates, country). | "What are my Government Ids?" | +| `WorkdayManagersdirect-CompanyCode` | Returns company code and company name for employees who directly report to the requesting user (manager view). Output is produced as a nested markdown list. | "What are the company codes for my reports?" | +| `WorkdayManagersdirect-CostCenter` | Returns cost center details for direct reports of the requesting user. Output is produced as a nested markdown list. | "What is the cost center of my direct reports?" | +| `WorkdayManagersdirect-Jobtaxanomy` | Returns job taxonomy (job title, business title, job profile, job family) for the manager's direct reports. Output is produced as a nested markdown list. | "Show me my team's job title"
"What is the job title of [EmployeeName]?" | +| `WorkdayManagerServiceAnniversary` | Returns upcoming service anniversaries for a manager's direct reports. The topic returns a markdown table with Employee Name, Hire Date, Upcoming Service Anniversary Date, Upcoming Milestone. | "When are the service anniversaries of all my directs?"
"What is [EmployeeName]'s next service anniversary?" | \ No newline at end of file