In Finance and Operations (D365FO) there is a Transfer to pay job under Time and attendance / Maintain payroll / Transfer to pay, that exports the pay lines to a csv file.
I needed to make a new export type here for Visma files.
And under the Setup / Time and attendance parameters, there is a Field “File format”
So to do that first I had to create a new File format to select under “File format”
That is done by extending the Base Enum JmgPayEventsExportType and adding the new element Visma.
After that I made a copy of the class JmgPayEventsExport_Std that is the class that is run when Standard is selected in the field “File format”
I will not go into the details of changes that I made to the class, but this is the declaration of my new class.
[JmgPayEventsExportTypeFactoryAttribute(JmgPayEventsExportType::Visma)]
class JmgPayEventsExport_Visma extends JmgPayEventsExport
{
}
So what you have to do it set the JmgPayEventsExportTypeFactoryAttribute to your new enum element, and the class has to extend JmgPayEventsExport.
Now you can change the code of the class to do the export in the new format.
But there is one important step before this will work, you need to clear the AOD cache, or you will get an error, this can be done under System administration / Setup / Refresh elements
Just in case you are curious about how the entire class looks like, here is the code in its entirety.
/// <summary>
/// The <c>JmgPayEventsExport_Visma</c> class is used to export pay events to a csv file.
/// </summary>
[JmgPayEventsExportTypeFactoryAttribute(JmgPayEventsExportType::Visma)]
class JmgPayEventsExport_Visma extends JmgPayEventsExport
{
static private readonly Decimals hoursDecimals = new DictType(extendedTypeNum(Hours)).effectivePrecision();
JmgTmpPayExport jmgTmpPayExport;
/// <summary>
/// Returns a class that contains the methods that are described by the <c>RunBaseDialogable</c>
/// interface.
/// </summary>
/// <returns>
/// A class that contains the methods that are described by the <c>RunBaseDialogable</c> interface.
/// </returns>
/// <remarks>
/// A dialog can be either built by using the <c>Dialog</c> class or by using a class that is created
/// in the Application Object Tree (AOT).
/// </remarks>
Object dialog()
{
DialogRunbase dialog = super();
dialog.caption(JmgPayEventsExport_Visma::description());
return dialog;
}
/// <summary>
/// Inserts a record in the <c>JmgTmpPayExport</c> temporary buffer.
/// </summary>
/// <param name="_emplExportId">
/// The ID of the worker to use when exporting.
/// </param>
/// <param name="_profileDate">
/// The date to assign to the pay event export.
/// </param>
/// <param name="_payCostType">
/// The pay cost type to assign to the pay event export.
/// </param>
/// <param name="_hours">
/// The number of hours to assign to the pay event export.
/// </param>
/// <param name="_price">
/// The cost to assign to the pay event export.
/// </param>
void doInsertTmp(JmgEmplExportId _emplExportId, StartDate _profileDate, JmgPayCostType _payCostType, Hours _hours, Price _price)
{
jmgTmpPayExport.Hours = _hours;
jmgTmpPayExport.EmplExportId = _emplExportId;
jmgTmpPayExport.ProfileDate = _profileDate;
jmgTmpPayExport.PayCostType = _payCostType;
jmgTmpPayExport.Price = _price;
jmgTmpPayExport.VismaCategory = JmgPayTable::find(_payCostType).VismaCategory;
jmgTmpPayExport.VismaId = JmgParameters::find().VismaId;
jmgTmpPayExport.VismaEmplId = JmgParameters::find().VismaEmplId;
jmgTmpPayExport.insert();
}
/// <summary>
/// Contains the code that does the actual job of the class.
/// </summary>
[
SuppressBPWarning('BPCheckNestedLoopinCode', 'Nested loop was a part of the code in the standard code that this was copyed from, so we did not change it.'),
SuppressBPWarning('BPCheckSelectForUpdateAbsent', 'Select for update was a part of the code in the standard code that this was copyed from, so we did not change it.')
]
void run()
{
Counter cnt;
JmgEmployee jmgEmployee;
JmgPayEvents jmgPayEvents;
JmgPayTable jmgPayTable;
this.setDateToAndDateFrom();
CommaTextStreamIo io = this.constructCommaTextStreamIo();
this.addPayrollPeriodRange();
this.checkForJournalLines(queryRun);
this.progressInit(JmgPayEventsExport_Std::description(), SysQuery::countLoops(queryRun), #AviUpdate);
ttsbegin;
while (queryRun.next())
{
ttsbegin;
jmgEmployee = queryRun.get(tableNum(JmgEmployee));
setPrefix(#PreFixField(jmgEmployee, Worker));
progress.setText(HcmWorker::worker2Name(jmgEmployee.Worker));
if (jmgEmployee.FlexProfile == NoYes::Yes)
{
JmgFlex::createAutomaticFlexCorrections(jmgEmployee, dateFrom, dateTo);
}
if (!datePay)
{
while select Worker, ProfileDate, PayCostType, Price, PayTransferred, sum(PaySeconds) from jmgPayEvents
group by Worker, ProfileDate, PayCostType, Price, PayTransferred
where jmgPayEvents.Worker == jmgEmployee.Worker &&
jmgPayEvents.ProfileDate >= dateFrom &&
jmgPayEvents.ProfileDate <= dateTo &&
(reTransmit || jmgPayEvents.PayTransferred == false)
exists join jmgPayTable
where jmgPayTable.PayCostType == jmgPayEvents.PayCostType &&
jmgPayTable.IncludeInExport == NoYes::Yes
{
this.doInsertTmp(
jmgEmployee.EmplExportId ? jmgEmployee.EmplExportId : HcmWorker::find(jmgEmployee.Worker).PersonnelNumber,
jmgPayEvents.ProfileDate,
jmgPayEvents.PayCostType,
decRound((jmgPayEvents.PaySeconds/3600), hoursDecimals),
jmgPayEvents.Price);
}
}
else
{
while select Worker, PayCostType, Price, PayTransferred, sum(PaySeconds) from jmgPayEvents
group by Worker, PayCostType, Price, PayTransferred
where jmgPayEvents.Worker == jmgEmployee.Worker &&
jmgPayEvents.ProfileDate >= dateFrom &&
jmgPayEvents.ProfileDate <= dateTo &&
(reTransmit || jmgPayEvents.PayTransferred == false)
exists join jmgPayTable
where jmgPayTable.PayCostType == jmgPayEvents.PayCostType &&
jmgPayTable.IncludeInExport == NoYes::Yes
{
this.doInsertTmp(
jmgEmployee.EmplExportId ? jmgEmployee.EmplExportId : HcmWorker::find(jmgEmployee.Worker).PersonnelNumber,
datePay,
jmgPayEvents.PayCostType,
decRound((jmgPayEvents.PaySeconds/3600), hoursDecimals),
jmgPayEvents.Price);
}
}
while select forupdate jmgPayEvents
where jmgPayEvents.Worker == jmgEmployee.Worker &&
jmgPayEvents.ProfileDate >= dateFrom &&
jmgPayEvents.ProfileDate <= dateTo
join jmgPayTable
where jmgPayTable.PayCostType == jmgPayEvents.PayCostType &&
jmgPayTable.IncludeInExport == NoYes::Yes
{
this.updateJgmPayEvents(jmgPayEvents);
}
cnt++;
progress.setCount(cnt);
ttscommit;
}
boolean anythingExported = false;
while select jmgTmpPayExport
order by EmplExportId, ProfileDate
where jmgTmpPayExport.PayCostType
{
anythingExported = true;
StartDate profileStartDate;
EndDate profileEndDate;
JmgWorkerRecId jmgWorkerRecId = HcmWorker::findByPersonnelNumber(jmgTmpPayExport.EmplExportId).RecId;
select firstonly jmgPayEvents
order by jmgPayEvents.ProfileDate
where jmgPayEvents.Worker == jmgWorkerRecId
&& jmgPayEvents.PayCostType == jmgTmpPayExport.PayCostType;
profileStartDate = jmgPayEvents.ProfileDate;
select firstonly jmgPayEvents
order by jmgPayEvents.ProfileDate desc
where jmgPayEvents.Worker == jmgWorkerRecId
&& jmgPayEvents.PayCostType == jmgTmpPayExport.PayCostType;
profileEndDate = jmgPayEvents.ProfileDate;
io.writeExp([
jmgTmpPayExport.VismaId,
jmgTmpPayExport.VismaEmplId,
jmgTmpPayExport.VismaCategory,
jmgTmpPayExport.EmplExportId,
profileStartDate,
profileEndDate,
JmgPayTable::payCostTypeExport(jmgTmpPayExport.PayCostType),
'',
jmgTmpPayExport.Hours,
(jmgTmpPayExport.Price == 0 ? '' : num2Text(jmgTmpPayExport.Price))
]);
}
ttscommit;
if (anythingExported)
{
System.DateTime exportTime = System.DateTime::UtcNow;
if (true)
{
FileUploadTemporaryStorageResult result = File::SendFileToTempStore_GetResult(io.getStream(),
strfmt('TransferToPay_%1.csv', exportTime.get_Ticks()),
classstr(FileUploadTemporaryStorageStrategy));
if (result && result.getFileId())
{
// Set up the notification
const RuleId excelStaticExportRuleId = 'ExcelStaticExport';
SystemNotificationDataContract notification = new SystemNotificationDataContract();
notification.Users().value(1, curUserId());
notification.Title("@ApplicationPlatform:ExportToExcel_ActionCenterCompletedTitle");
notification.RuleId(excelStaticExportRuleId);
notification.Message(strFmt("@ApplicationPlatform:ExportToExcel_ActionCenterCompletedMessage", "@SYS38536"));
notification.ExpirationDateTime(DateTimeUtil::addMinutes(DateTimeUtil::utcNow(), real2int(FileUploadTemporaryStorageStrategy::FileTimeOutInMinutes)));
// Set up the action associated with the notification
SystemNotificationActionDataContract action = new SystemNotificationActionDataContract();
action.Message("@ApplicationPlatform:ExportToExcel_ActionCenterDownloadLinkMessage");
action.Type(SystemNotificationActionType::AxActionMenuFunction);
SystemNotificationMenuFunctionDataContract actionData = new SystemNotificationMenuFunctionDataContract();
actionData.MenuItemName(menuItemActionStr(ExportToExcelStaticOpenFileAction_AppSuite));
actionData.Data(result.getFileId());
action.Data(FormJsonSerializer::serializeClass(actionData));
notification.Actions().value(1, action);
SystemNotificationsManager::AddSystemNotification(notification);
}
}
else
{
File::SendFileToUser(io.getStream(), strfmt('TransferToPay_%1.csv', exportTime.get_Ticks()));
}
}
else
{
info("@GLS952");
}
}
/// <summary>
/// Updates the pay event.
/// </summary>
/// <param name="_jmgPayEvents">
/// A <c>JmgPayEvents</c> table buffer.
/// </param>
protected void updateJgmPayEvents(JmgPayEvents _jmgPayEvents)
{
_jmgPayEvents.PayTransferred = true;
_jmgPayEvents.update();
}
/// <summary>
/// Bla
/// </summary>
/// <returns>Bla</returns>
protected CommaTextStreamIo constructCommaTextStreamIo()
{
return CommaTextStreamIo::constructForWrite();
}
/// <summary>
/// Bla
/// </summary>
/// <param name = "_dateFrom">Bla</param>
/// <returns>Bla</returns>
protected JmgDate parmDateFrom(JmgDate _dateFrom = dateFrom)
{
dateFrom = _dateFrom;
return dateFrom;
}
/// <summary>
/// Bla
/// </summary>
/// <param name = "_dateTo">Bla</param>
/// <returns>Bla</returns>
protected JmgDate parmDateTo(JmgDate _dateTo = dateTo)
{
dateTo = _dateTo;
return dateTo;
}
/// <summary>
/// Returns the description of the class.
/// </summary>
/// <returns>The description</returns>
static public ClassDescription description()
{
return "@CUS:VismaTransferToPay";
}
}