Skip to main content

Project task assigned

Fires when a resource is assigned to a project task. The main source is the work breakdown structure: when a published plan's task resources are saved, or a draft plan with resource assignments is published, one event is sent per resource that holds hours on the task. It also fires for a new ProjActivityAssignment row, or when an existing row's resource or load percentage changes; standard pages don't write that table, so this source covers customizations and imports only. Subscribers can use the event to notify the assignee through Power Automate or Teams, or to keep an external resource-planning or staffing tool in step with project assignments.

Event classMGCLatticeProjectTaskAssignedBusinessEvent
Contract classMGCLatticeProjectTaskAssignedBusinessEventContract
CategoryProjects
ModuleModuleAxapta::Project
Label IDsProjectTaskAssignedBusinessEventName / ProjectTaskAssignedBusinessEventDescription
TriggerProjTask::reserveResourceActivity (CoC, MGCLatticeProjTask_Class_Extension) → one event per reserved resource with hard-booked hours on a project task; plus ProjActivityAssignment Inserted data event, and Updated data event when WrkCtrId or LoadPct changed

Trigger / Hook​

  • Work breakdown structure: MGCLatticeProjTask_Class_Extension, [ExtensionOf(classStr(ProjTask))], wraps public static void reserveResourceActivity(ActivityRecId _activity, str _assignedResourcesRecId, TransDate _startDate, TransDate _endDate, real _effort, boolean _skipResourceFeedback). This method turns a task's resource list into effective assignments: the work breakdown structure page calls it when the resources of a published plan's task are saved, and publishing a draft plan calls it for every task with resource assignments. After next, the wrapper calls MGCLatticeProjectTaskAssignedEventHandlers::raiseForReservedTaskResources(_activity, _assignedResourcesRecId), which sends one event per project team member on the list that holds hard-booked hours on the task afterwards (ActivityResourceFacade::getAssignedCapacity(..., ResCommitType::HardBook, ...), converted from seconds to hours). A reservation that failed or holds no hours is not reported.
    • Saving a task's resources re-reserves every resource on it, so a resource that stays on the task is reported again; de-duplicate on projectId + activityNumber + resourceId.
    • These raise nothing: resource assignments on a draft plan (until it is published), reservations under the resource request workflow (also not when the request is approved), assignments made on the Resource assignments page, by resource request approval or by quotation transfer, and quotation tasks (only tasks whose activity type is PSARSEntityType::Project are reported).
    • For work breakdown structure events assignmentAction is "Assigned", loadPercent is 0, isCapacityLimited is false and legalEntity is the current company.
  • ProjActivityAssignment data events: handler class MGCLatticeProjectTaskAssignedEventHandlers (static data event handlers). No standard code writes this table (the Activities page shows it read-only and no data entity covers it), so these handlers serve customizations and imports that write it:
    • [DataEventHandler(tableStr(ProjActivityAssignment), DataEventType::Inserted)] public static void ProjActivityAssignment_onInserted(Common _sender, DataEventArgs _e) - every inserted assignment raises the event with assignmentAction = "Assigned".
    • [DataEventHandler(tableStr(ProjActivityAssignment), DataEventType::Updated)] public static void ProjActivityAssignment_onUpdated(Common _sender, DataEventArgs _e) - raises the event with assignmentAction = "Reassigned", but only when the assigned resource (WrkCtrId) or the allocation (LoadPct) differs from the record's original values (orig()). Any other update of the assignment (for example only the capacity-limited flag) does not raise it. The table marks WrkCtrId as not editable, so in normal use an update raises the event when the load percentage changes.
  • Deleting an assignment raises nothing.
  • Transaction: the data events are raised during the insert or update. When that write runs inside a transaction (as form saves do), send() only writes to the business events commit log and delivery happens after commit, so a rolled-back write never delivers.
  • Writes that bypass table events (for example set-based operations run with skipEvents(true)) do not raise the data-event variant.
  • Enablement gate: the work breakdown structure path checks MGCLatticeBusinessEventCompany::isPublishable(classStr(MGCLatticeProjectTaskAssignedBusinessEvent)) (framework on, event activated in the current legal entity) before reading any hours; both data event handlers check BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProjectTaskAssignedBusinessEvent)) before sending.
  • No Lattice parameters or other setup are required.
public static void reserveResourceActivity(ActivityRecId _activity, str _assignedResourcesRecId,
TransDate _startDate, TransDate _endDate, real _effort, boolean _skipResourceFeedback)
{
next reserveResourceActivity(_activity, _assignedResourcesRecId, _startDate, _endDate, _effort, _skipResourceFeedback);

MGCLatticeProjectTaskAssignedEventHandlers::raiseForReservedTaskResources(_activity, _assignedResourcesRecId);
}

public static int raiseForReservedTaskResources(ActivityRecId _activity, str _activityResources)
{
int sent;

if (!_activity
|| !_activityResources
|| !MGCLatticeBusinessEventCompany::isPublishable(classStr(MGCLatticeProjectTaskAssignedBusinessEvent))
|| ActivityFacade::getActivityType(_activity) != PSARSEntityType::Project)
{
return 0;
}

ListEnumerator activityResources = strSplit(_activityResources, ',').getEnumerator();

while (activityResources.moveNext())
{
ActivityResourceRecId activityResource = str2Int64(strLTrim(strRTrim(activityResources.current())));
real assignedHours = MGCLatticeProjectTaskAssignedEventHandlers::assignedHours(_activity, activityResource);

if (activityResource && assignedHours > 0)
{
MGCLatticeProjectTaskAssignedBusinessEvent::newFromTaskResource(_activity, activityResource, assignedHours).send();
sent++;
}
}

return sent;
}

[DataEventHandler(tableStr(ProjActivityAssignment), DataEventType::Inserted)]
public static void ProjActivityAssignment_onInserted(Common _sender, DataEventArgs _e)
{
ProjActivityAssignment assignment = _sender as ProjActivityAssignment;

if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProjectTaskAssignedBusinessEvent)))
{
MGCLatticeProjectTaskAssignedBusinessEvent::newFromAssignment(assignment, false).send();
}
}

[DataEventHandler(tableStr(ProjActivityAssignment), DataEventType::Updated)]
public static void ProjActivityAssignment_onUpdated(Common _sender, DataEventArgs _e)
{
ProjActivityAssignment assignment = _sender as ProjActivityAssignment;
ProjActivityAssignment origAssignment = assignment.orig();

if ((assignment.WrkCtrId != origAssignment.WrkCtrId || assignment.LoadPct != origAssignment.LoadPct)
&& BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(MGCLatticeProjectTaskAssignedBusinessEvent)))
{
MGCLatticeProjectTaskAssignedBusinessEvent::newFromAssignment(assignment, true).send();
}
}

Payload Contract​

One row per [DataMember], in declaration order. Work breakdown structure events are mapped in initializeFromTaskResource() from the task (smmActivities) and the project team member (PSAProjSchedRole); data events are mapped in initialize() from the inserted or updated ProjActivityAssignment record.

JSON fieldDisplay labelType / EDTSourceNotes
legalEntityLegal EntityDataAreaIdWork breakdown structure: curExt(); data events: ProjActivityAssignment.DataAreaId (both in lower case)Company of the assignment, lower case (for example usmf)
projectIdProject IDstrActivityFacade::getProjId for the taskThe task's project
assignedHoursAssigned HoursrealWork breakdown structure: hard-booked ResAssignment capacity of the team member on the task, in hours0 for ProjActivityAssignment events
activityNumberActivity NumberstrWork breakdown structure: ActivityFacade::getActivityNumber; data events: ProjActivityAssignment.ActivityNumberThe project activity (task) the resource is assigned to
resourceIdResource IDstrWork breakdown structure: ResourceFacade::getId of the team member's resource; data events: ProjActivityAssignment.WrkCtrIdID of the assigned resource (WrkCtrTable)
workerPersonnelNumberWorker Personnel NumberHcmPersonnelNumberIdWork breakdown structure: ResourceFacade::getPersonnelId; data events: HcmWorker.PersonnelNumber of WrkCtrTable.Worker for the resourcePersonnel number of the worker linked to the resource; empty when the resource is not linked to a worker (for example a machine or a vendor resource)
loadPercentLoad PercentWrkCtrLoadPctData events: ProjActivityAssignment.LoadPctPercentage of the resource's capacity allocated to the activity; 0 for work breakdown structure events
isCapacityLimitedIs Capacity LimitedbooleanData events: ProjActivityAssignment.CapLimited == NoYes::YesThe assignment's capacity-limited flag; serialized as true/false; false for work breakdown structure events
assignmentActionAssignment ActionstrSet by the handler"Assigned" for a work breakdown structure reservation or a new assignment row; "Reassigned" when an existing assignment row's resource or load percentage changed

Every Lattice event also carries the Lattice ordering and correlation envelope, inherited from MGCLatticeBusinessEventContractBase: eventSequence (Int64 - increasing across the whole catalog and all companies, allocated when the event is raised inside the business transaction, not when it commits; it orders the events of one business record, gaps are permanent and normal, and it must not be used to re-sequence the whole stream), correlationId (String, always Scope:Key - groups emissions that belong to one logical run; an event that joins no run carries Event: followed by a lower-case GUID unique to this emission) and schemaVersion (Int - the payload shape version; 1 for this contract). D365 F&O also adds its standard envelope fields to every business event payload: BusinessEventId, BusinessEventLegalEntity, ContextRecordSubject, ControlNumber, EventId, EventTime, EventTimeIso8601, InitiatingUserAADObjectId, MajorVersion, MinorVersion, and ParentContextRecordSubjects. Do not rely on the order of members in the delivered JSON.

Example Payload​

{
"BusinessEventId": "MGCLatticeProjectTaskAssignedBusinessEvent",
"ControlNumber": 5637149013,
"EventId": "AE93D4C3-B23B-417A-ACA5-D8AC7C12E9D6",
"EventTime": "\/Date(1790327550000)\/",
"EventTimeIso8601": "2026-09-25T09:12:30.9083925Z",
"MajorVersion": 0,
"MinorVersion": 0,
"BusinessEventLegalEntity": "USMF",
"ContextRecordSubject": "",
"InitiatingUserAADObjectId": "{5878EC9F-CA57-6EE9-7873-1B46DAAF5179}",
"ParentContextRecordSubjects": [],
"eventSequence": 48266,
"correlationId": "Event:28b8bdd4-2884-4506-9c4d-dc0f54846041",
"schemaVersion": 1,
"legalEntity": "usmf",
"projectId": "000184",
"activityNumber": "00413",
"resourceId": "000183",
"workerPersonnelNumber": "000183",
"loadPercent": 0,
"isCapacityLimited": false,
"assignedHours": 16,
"assignmentAction": "Assigned"
}

Activation​

In the D365FO client, go to System administration > Setup > Business events > Business events catalog. Find this event by name ("Project task assigned") or by filtering the Category column to Projects. Select it and use Activate to bind it to a configured endpoint (for example, an Azure Service Bus queue, Event Grid topic, or HTTPS webhook).