pse.TimecardMatchServiceglobal with sharing class TimecardMatchService Global service for matching unassigned timecards to eligible assignments. Methods
matchTimecardsglobal static pse.TimecardMatchService.Result matchTimecards(List<Id> projectIds) Matches unassigned timecards in the given projects to active assignments using default options. Input Parameters
Return ValueResult with matched pairs and unmatched timecard IDs. matchTimecardsglobal static pse.TimecardMatchService.Result matchTimecards(List<Id> projectIds, pse.TimecardMatchService.Options matchOptions) Matches unassigned timecards in the given projects to active assignments. Input Parameters
Return ValueResult with matched pairs and unmatched timecard IDs Sample Code
//Note: This sample code is for demonstration purposes only. It is not intended for
//use in a production environment, is not guaranteed against defects or errors, and
//is in no way optimized or streamlined.
// Acme Corp imports timecards each night from an external time-tracking tool.
// Entries arrive without an assignment, so we match them after import.
List<Id> projectIds = new List<Id>();
for (pse__Proj__c project : [
SELECT Id
FROM pse__Proj__c
WHERE pse__Stage__c = 'In Progress'
]) {
projectIds.add(project.Id);
}
pse.TimecardMatchService.Options options = new pse.TimecardMatchService.Options();
options.shouldExtendAssignmentDates = true;
pse.TimecardMatchService.Result result = pse.TimecardMatchService.matchTimecards(
projectIds,
options
);
for (Id timecardId : result.unmatchedTimecardIds) {
// No active assignment exists for this resource and project combination.
// Someone will need to create one before the timecard can be linked.
System.debug(LoggingLevel.WARN, 'No assignment found for timecard ' + timecardId);
}
for (Id timecardId : result.errorByTimecardId.keySet()) {
// A match was found but the update failed — check whether the running user
// has timecard entry permission for this resource.
System.debug(
LoggingLevel.ERROR,
'Failed to link timecard ' + timecardId + ': ' + result.errorByTimecardId.get(timecardId)
);
}
matchTimecardsAsyncglobal static Id matchTimecardsAsync(List<Id> projectIds) Enqueues async matching jobs for the given projects using default options. Jobs are grouped under a single fferpcore__AsyncJobGroup__c — one fferpcore__AsyncJobRecord__c per project. Input Parameters
Return ValueID of the root fferpcore__AsyncJobGroup__c for this operation. matchTimecardsAsyncglobal static Id matchTimecardsAsync(List<Id> projectIds, pse.TimecardMatchService.Options matchOptions) Enqueues async matching jobs for the given projects. One job is created per project. A notification is sent to the calling user when all matching jobs complete. Input Parameters
Return ValueID of the root job group. To monitor individual matching jobs, query fferpcore__AsyncJobRecord__c where fferpcore__AsyncJobGroup__r.fferpcore__RootAsyncJobGroup__c equals this ID and fferpcore__CallableName__c equals TimecardMatchCallable. Sample Code
//Note: This sample code is for demonstration purposes only. It is not intended for
//use in a production environment, is not guaranteed against defects or errors, and
//is in no way optimized or streamlined.
// Acme Corp runs nightly maintenance to match any timecards that arrived without
// an assignment. The sync version can hit governor limits on large orgs, so they
// use the async overload instead, which enqueues one background job per project.
List<Id> projectIds = new List<Id>();
for (pse__Proj__c project : [
SELECT Id
FROM pse__Proj__c
WHERE pse__Stage__c = 'In Progress'
]) {
projectIds.add(project.Id);
}
pse.TimecardMatchService.Options options = new pse.TimecardMatchService.Options();
options.shouldExtendAssignmentDates = true;
Id jobGroupId = pse.TimecardMatchService.matchTimecardsAsync(projectIds, options);
// matchTimecardsAsync returns immediately — the jobs run in the background.
// Store jobGroupId (to the caller) so you can check progress later. In practice
// you would poll after a delay.
// To check whether all jobs in the group have finished, query the job records:
List<fferpcore__AsyncJobRecord__c> jobs = [
SELECT
fferpcore__Status__c,
fferpcore__Complete__c,
fferpcore__Succeeded__c,
fferpcore__Failed__c,
fferpcore__Error__c
FROM fferpcore__AsyncJobRecord__c
WHERE fferpcore__AsyncJobGroup__c = :jobGroupId
];
Integer pending = 0;
Integer failed = 0;
for (fferpcore__AsyncJobRecord__c job : jobs) {
if (!job.fferpcore__Complete__c) {
pending++;
} else if (job.fferpcore__Failed__c) {
failed++;
System.debug(LoggingLevel.ERROR, 'Matching job failed: ' + job.fferpcore__Error__c);
}
}
if (pending > 0) {
System.debug(LoggingLevel.INFO, pending + ' job(s) still running — check back later.');
} else if (failed > 0) {
System.debug(LoggingLevel.WARN, failed + ' job(s) failed. See error details above.');
} else {
System.debug(LoggingLevel.INFO, 'All matching jobs completed successfully.');
}
pse.TimecardMatchService.Optionsglobal with sharing class Options Configuration options for a matching run. Properties
MethodsOptionsglobal Options() pse.TimecardMatchService.Resultglobal with sharing class Result Result returned after a matching run. Properties
|