New type of export file for Payroll system

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";
    }
}

Leave a Comment