Friday, December 1, 2023

Sysoperation framework with file upload and UI builder D365FO

class ImportSveaFileService_Custom extends SysOperationServiceBase
{
    private Map companyLedgerJournalTableMap;
    public void run(ImportSveaFileContract_Custom _contract)
    {
        if (_contract.parmStorageResult() != conNull())
        {
            FileUploadTemporaryStorageResult fileUploadResult = new FileUploadTemporaryStorageResult();

            fileUploadResult.unpack(_contract.parmStorageResult());

            if (fileUploadResult != null)
            {
                try
                {
                    System.IO.Stream fileStream = File::UseFileFromURL(fileUploadResult.getDownloadUrl());

                    XmlDocument doc;
                    XmlNodeList data;
                    XmlElement nodeTable;
                    XmlElement nodeId;
                    XMLParseError xmlError;
                    doc = XmlDocument::newFromStream(fileStream);

                    int lineNum = 0;
                    real totalamountHeader, totalamountTrans = 0;
                    real totalDiscfee, totalAdmfee;
                    CurrencyCode currencyCode;

                    // Verify XML Document Structure
                    xmlError  = doc.parseError();
                    if(xmlError && xmlError.errorCode() != 0)
                    {
                        throw error(strFmt("XML Error: %1", xmlError.reason()));
                    }

                    BankAccountTable bankAccountTable = BankAccountTable::find(_contract.parmBankAccount());
                    LedgerJournalTable ledgerJournalTable = LedgerJournalTable::find(_contract.parmJournalId());
                    data = doc.selectNodes('//'+"header");
                    nodeTable = data.nextNode();
                    while (nodeTable)
                    {
                        nodeId = nodeTable.selectSingleNode("totalamount");
                        totalamountHeader = str2Num(nodeId.text());
                        nodeId = nodeTable.selectSingleNode("batchcurrency");
                        currencyCode = nodeId.text();
                      
                        nodeTable = data.nextNode();
                    }

                    data = doc.selectNodes('//'+"transaction");
                    nodeTable = data.nextNode();
                    //LedgerJournalTrans custLedgerJournalTrans;
                    //RecordInsertList   recordInsertListLedgerJournalTrans = new RecordInsertList(tableNum(LedgerJournalTrans));
                   
                    while (nodeTable)
                    {
                        ttsbegin;
                        real  discfee, admfee, invoiceamount;
                        LedgerJournalTrans custLedgerJournalTrans;

                        custLedgerJournalTrans.JournalNum   = ledgerJournalTable.JournalNum;
                        custLedgerJournalTrans.LineNum      = lineNum;

                        lineNum++;

                        Voucher     voucher;
                        NumberSeq   numberSeq;
                        numberSeq   = NumberSeq::newGetVoucherFromId(ledgerJournalTable.NumberSequenceTable);
                        voucher     = numberSeq.voucher();
                        custLedgerJournalTrans.Voucher          = voucher;

                        custLedgerJournalTrans.CurrencyCode     = currencyCode;
                        custLedgerJournalTrans.AccountType      = LedgerJournalACType::Cust;
                        custLedgerJournalTrans.TransactionType  = LedgerTransType::Payment;

                        nodeId = nodeTable.selectSingleNode("invoicedate");
                        custLedgerJournalTrans.TransDate = str2Date(nodeId.text(),321);
                     
                        nodeId = nodeTable.selectSingleNode("customernumber");
                        CustAccount custAccountNum = CustTable::findRecId(str2recId(nodeId.text())).AccountNum;
                        custLedgerJournalTrans.parmAccount(custAccountNum, LedgerJournalACType::Cust);
                    
                        CustTable custTable = CustTable::find(custLedgerJournalTrans.parmAccount());

                        if (custTable)
                        {
                            custLedgerJournalTrans.DefaultDimension = custTable.DefaultDimension;
                            custLedgerJournalTrans.Payment = custTable.PaymTermId;
                        }
             
                        nodeId = nodeTable.selectSingleNode("invoiceid");
                        custLedgerJournalTrans.MarkedInvoice        = nodeId.text();
                        custLedgerJournalTrans.MarkedInvoiceCompany = curExt();

                        nodeId = nodeTable.selectSingleNode("invoiceamount");
                        invoiceamount = str2Num(nodeId.text());
                        custLedgerJournalTrans.amountCur2DebCred(invoiceamount * -1);

                        custLedgerJournalTrans.OffsetAccountType            = LedgerJournalACType::Bank;
                        custLedgerJournalTrans.OffsetLedgerDimension        = bankAccountTable.LedgerDimension;
                        custLedgerJournalTrans.Triangulation                = Currency::triangulation(custLedgerJournalTrans.CurrencyCode, custLedgerJournalTrans.TransDate);
                        custLedgerJournalTrans.ExchRate                     = ExchangeRateHelper::exchRate(custLedgerJournalTrans.CurrencyCode, custLedgerJournalTrans.TransDate);
                        custLedgerJournalTrans.ExchRateSecond               = ExchangeRateHelper::exchRateSecond(custLedgerJournalTrans.CurrencyCode, custLedgerJournalTrans.TransDate);
                        custLedgerJournalTrans.PostingProfile               = CustParameters::find().PostingProfile;
                        custLedgerJournalTrans.BankCentralBankPurposeCode   = custTable.BankCentralBankPurposeCode;
                        custLedgerJournalTrans.BankCentralBankPurposeText   = custTable.BankCentralBankPurposeText;
                        custLedgerJournalTrans.Txt                          = this.determineTransactionText(LedgerTransTxt::CustVendNetVendor, custLedgerJournalTrans);
                        custLedgerJournalTrans.OffsetTxt                    = this.determineTransactionText(LedgerTransTxt::CustVendNetLedger, custLedgerJournalTrans);

                        nodeId  = nodeTable.selectSingleNode("discfee");
                        discfee = str2Num(nodeId.text());

                        nodeId  = nodeTable.selectSingleNode("admfee");
                        admfee   = str2Num(nodeId.text());

                        CustInvoiceJour CustInvoiceJour;
                       
                        select firstonly custInvoiceJour
                            where custInvoiceJour.InvoiceId == custLedgerJournalTrans.MarkedInvoice &&
                                    custInvoiceJour.InvoiceDate == custLedgerJournalTrans.TransDate &&
                                        custInvoiceJour.InvoiceAccount == custTable.AccountNum;

                        if (invoiceamount != custInvoiceJour.InvoiceAmount)
                        {
                            warning(strFmt("Invoice amount mismatch on %1", custInvoiceJour.InvoiceId));
                        }

                        totalamountTrans += invoiceamount;
                        totalDiscfee     += discfee;
                        totalAdmfee      += admfee;

                        nodeTable = data.nextNode();

                        custLedgerJournalTrans.insert();
                        
                        LedgerJournalEngine_CustPayment::updateMarkedInvoiceSpecTrans(custLedgerJournalTrans);
                        ttscommit;
                        //recordInsertListLedgerJournalTrans.add(custLedgerJournalTrans);
                    }
                   // recordInsertListLedgerJournalTrans.insertDatabase();
                    info(strFmt("Total amount from file: %1 - total from journal: %2 -Total discfee: %3 – Total admfee: %4",totalamountHeader, totalamountTrans, totalDiscfee, totalAdmfee));
                }
                catch (Exception::Error)
                {
                    error("@RET433");
                }
            }
        }
    }

    private TransactionTextLarge determineTransactionText(
        LedgerTransTxt _ledgerTransTxt,
        LedgerJournalTrans _ledgerJournalTrans)
    {
        TransactionTxt transactionTxt = TransactionTxt::construct();

        transactionTxt.setType(_ledgerTransTxt);
        transactionTxt.setVoucher(_ledgerJournalTrans.Voucher);
        transactionTxt.setDate(_ledgerJournalTrans.TransDate);
        transactionTxt.setKey1(_ledgerJournalTrans.parmAccount());
        transactionTxt.setFormLetter(_ledgerJournalTrans.Voucher);

        return transactionTxt.txt();
    }

}
===========
class ImportSveaFileUIBuilder_Custom extends SysOperationAutomaticUIBuilder
{
    private str                 availableTypes = ".xml";
    private const str           OkButtonName = 'CommandButton';
    private const str           FileUploadName = 'FileUpload';

    ImportSveaFileContract_Custom   contract;
    /// <summary>
    /// Overriden the <c>postBuild</c> method to add a <c>FileUpload</c> control
    /// </summary>
    public void postBuild()
    {
        DialogGroup      dialogGroup;
        FormBuildControl formBuildControl;
        FileUploadBuild  dialogFileUpload;

        super();

        contract = this.dataContractObject();
        
        dialogGroup = dialog.addGroup("File path");
        formBuildControl = dialog.formBuildDesign().control(dialogGroup.name());
       
        dialogFileUpload = formBuildControl.addControlEx(classstr(FileUpload), FileUploadName);
        dialogFileUpload.style(FileUploadStyle::MinimalWithFilename);
        dialogFileUpload.baseFileUploadStrategyClassName(classstr(FileUploadTemporaryStorageStrategy));
        dialogFileUpload.fileTypesAccepted(availableTypes);
        dialogFileUpload.fileNameLabel("@SYS308842");
    }

    /// <summary>
    /// Subscribes events to the dialog form
    /// </summary>
    /// <param name = "_formRun">The instance of the dialog form</param>
    private void dialogEventsSubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(FileUploadName));
        fileUpload.notifyUploadCompleted += eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted += eventhandler(this.uploadStarted);
        _formRun.onClosing += eventhandler(this.dialogClosing);
    }

    /// <summary>
    /// Executes logic for unsubscribing the registered events on the form
    /// </summary>
    /// <param name = "sender"></param>
    /// <param name = "e"></param>
    [SuppressBPWarningAttribute('BPParameterNotUsed', 'This is event parameter not required to use')]
    private void dialogClosing(xFormRun sender, FormEventArgs e)
    {
        this.dialogEventsUnsubscribe(sender as FormRun);
    }

    /// <summary>
    /// Unsubscribes events from the dialog form
    /// </summary>
    /// <param name = "_formRun">The instance of the dialog form</param>
    private void dialogEventsUnsubscribe(FormRun _formRun)
    {
        FileUpload fileUpload = _formRun.control(_formRun.controlId(FileUploadName));
        fileUpload.notifyUploadCompleted -= eventhandler(this.uploadCompleted);
        fileUpload.notifyUploadAttemptStarted -= eventhandler(this.uploadStarted);
        _formRun.onClosing -= eventhandler(this.dialogClosing);
    }

    /// <summary>
    /// Executes additional logic once the upload of the file is completed
    /// </summary>
    protected void uploadCompleted()
    {
        var formRun = this.dialog().dialogForm().formRun();
        FileUpload fileUpload = formRun.control(formRun.controlId(FileUploadName));
        FileUploadTemporaryStorageResult uploadResult = fileUpload.getFileUploadResult();

        if (uploadResult != null && uploadResult.getUploadStatus())
        {
            contract.parmStorageResult(uploadResult.pack());
        }

        this.setDialogOkButtonEnabled(formRun, true);
    }

    /// <summary>
    /// Additional logic which is executed once the upload of the file has started
    /// </summary>
    private void uploadStarted()
    {
        var formRun = this.dialog().dialogForm().formRun();
        this.setDialogOkButtonEnabled(formRun, false);
    }

    /// <summary>
    /// Enables/Disables the OK button of the dialog
    /// </summary>
    /// <param name = "_formRun">The instance of the dialog form</param>
    /// <param name = "_isEnabled">Should the OK button be enabled?</param>
    protected void setDialogOkButtonEnabled(FormRun _formRun, boolean _isEnabled)
    {
        FormControl okButtonControl = _formRun.control(_formRun.controlId(OkButtonName));
        if (okButtonControl)
        {
            okButtonControl.enabled(_isEnabled);
        }
    }

    /// <summary>
    /// Override of the <c>postRun</c> method in order to add events subscriptions
    /// </summary>
    public void postRun()
    {
        super();

        FormRun formRun = this.dialog().dialogForm().formRun();
        this.dialogEventsSubscribe(formRun);

        this.setDialogOkButtonEnabled(formRun, false);
    }

}
===
class ImportSveaFileController_Custom extends SysOperationServiceController
{
    /// <summary>
    ///     Constructs an instance
    /// </summary>
    /// <returns>an instance of class</returns>
    public static ImportSveaFileController_Custom construct(Args _args)
    {
        ImportSveaFileController_Custom controller = new ImportSveaFileController_Custom(classstr(ImportSveaFileService_Custom),
                                                                                           methodstr(ImportSveaFileService_Custom, run),
                                                                                           SysOperationExecutionMode::Synchronous);
        controller.parmArgs(_args);
        controller.initializeContract(controller);

        return controller;
    }

    /// <summary>
    /// Initializes the contract parameters.
    /// </summary>
    /// <param name = "_controller">An instance of the <c>CAMPeriodCalculationController</c> whose contract to initialize.</param>
    protected void initializeContract(ImportSveaFileController_Custom _controller)
    {
        LedgerJournalTable ledgerJournalTable = this.parmArgs().record();

        if (ledgerJournalTable)
        {
            ImportSveaFileContract_Custom contract = _controller.getDataContractObject() as ImportSveaFileContract_Custom;

            if (contract)
            {
                contract.parmJournalId(ledgerJournalTable.JournalNum);
            }
        }
    }

    /// <summary>
    /// Instantiate controller.
    /// </summary>
    //protected void new()
    //{
    //    super(classStr(ImportSveaFileService_Custom),
    //    methodStr(ImportSveaFileService_Custom,run ));
    //    this.parmDialogCaption("Import Svea file into payment journal");
    //}

    /// <summary>
    /// Runs the class with the specified arguments.
    /// </summary>
    /// <param name = "_args">The specified arguments.</param>
    public static void main(Args _args)
    {
        ImportSveaFileController_Custom::construct(_args).startOperation();
        //ImportSveaFileController_Custom controller = ImportSveaFileController_Custom::newFromArgs();
        //controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);
        //controller.startOperation();
    }

    /// <summary>
    /// Instantiate and initialize controller class.
    /// </summary>
    /// <returns>
    /// returns controller class.
    /// </returns>
    //public static ImportSveaFileController_Custom newFromArgs()
    //{
    //    ImportSveaFileController_Custom controller = ImportSveaFileController_Custom::construct();
    //    return controller;
    //}

}
====
[DataContractAttribute,
SysOperationContractProcessing(classStr(ImportSveaFileUIBuilder_Custom))]
class ImportSveaFileContract_Custom
{
    LedgerJournalId   journalId;
    CompanyBankAccountId bankAccount;
    container       storageResult;

    /// <summary>
    ///     Get/Set the email address
    /// </summary>
    /// <param name = "_emailAddress">email address</param>
    /// <returns>email address</returns>
    [DataMemberAttribute,
    SysOperationLabelAttribute(literalStr("Bank account")),
    SysOperationDisplayOrderAttribute('1')]
    public CompanyBankAccountId parmBankAccount(CompanyBankAccountId _bankAccount = bankAccount)
    {
        bankAccount = _bankAccount;

        return bankAccount;
    }

    /// <summary>
    ///     Get/Set the email address
    /// </summary>
    /// <param name = "_emailAddress">email address</param>
    /// <returns>email address</returns>
    [DataMemberAttribute,
    SysOperationLabelAttribute(literalStr("Journal num")),
    SysOperationDisplayOrderAttribute('3'),
        SysOperationControlVisibility(false)]
    public LedgerJournalId parmJournalId(LedgerJournalId _journalId = journalId)
    {
        journalId = _journalId;

        return journalId;
    }

    /// <summary>
    /// Parameter method which holds values of the packed variables from <c>FileUploadTemporaryStorageResult</c> class
    /// </summary>
    /// <param name = "_storageResult">Packed instance of <c>FileUploadTemporaryStorageResult</c> class</param>
    /// <returns>Container with packed values</returns>
    [DataMemberAttribute('StorageResult'),
        SysOperationDisplayOrderAttribute('2')]
    public container parmStorageResult(container _storageResult =  storageResult)
    {
        storageResult = _storageResult;
        return storageResult;
    }

}

No comments:

Post a Comment

Table browser URL in D365FO

Critical Thinking icon icon by Icons8