Wednesday, January 13, 2010

How to Load Fixed Assets

Task: load about 2000 fixed assets into Dynamics AX 4.0.  Some assets have been previously scrapped, sold, and fully depreciated.  Otherwise, the assets are generally part way through straight-line depreciation.  Assets fall into 6 groups (i.e. buildings, machinery, etc.) and need to be associated with a financial dimension indicating the department (i.e. shipping, production, etc.).

Minimum Data that you need to import...and where it maps to in AX)
Fixed Asset Number  (AssetTable.AssetId, AssetBook.AssetId)
Asset Name (AssetTable.Name)
Asset Group (AssetTable.AssetGroup, AssetBook.AssetGroup)
Placed in Service Date (AssetBook.AcquisitionDate, AssetTable.GuaranteeDate) - GuaranteeDate is temporary, use whatever field you like
Aquisition Price (AssetBook.AcquisitionPrice)
Service Life in Years (AssetBook.ServiceLife)
Service Life in Months (AssetBook.LifeTime) - I could have calculated this during import
Depreciation Periods Remaining (AssetBook.LifeTimeRest)
Accumulated depreciation from legacy system (AssetTable.AssetReplaceCost) - this is temporary, use whatever field you like
plus other data that you may or may not have

Step 1 - Import
Define custom definition group for AssetTable and AssetBook.  Field setup as described above.
(Administration, Periodic, Data Export/Import, Definition Groups)

I used the Conversion tab to set some values:
AssetBook book;
;
book.data(assetBook);
book.BookId = 'OurBookId';
book.PostingProfile = 'OurPostingProfileId';
book.Depreciation = NoYes::Yes;
book.LastDepreciationDate = mkDate(31,10,2009);
book.DepreciationConvention = AssetDepreciationConvention::MidMonth1st;
assetBook.data(book);
Note: the LastDepreciationDate is the date that accumulated depreciation was calculated through in the legacy system.

Step 2 - Post Journal for Acquisition
Once you have the Fixed Asset records that you imported in step 1, you will see that they have the status 'Not yet acquired'.  To properly record the aquisition, you have to post a Fixed Asset acquisition journal. 

In our case, the Ledger balances already reflected the prior purchase of the assets.  We needed to record the acquisition, but didn't want it to affect our ledger balances.  The simple work-around was to temporarily set the aquisition ledger account and offset account to the same ledger account so there is no net affect.  (GL, Setup, Fixed Assets, Posting Profiles)
It's possible to use Proposal > Acquisition to create lines in the Fixed Asset journal and then change the dates* on all the lines (they are defaulted to the acquisition date - but those periods were closed for us).  Instead, I wrote a little job to create the journals.  (see below)
*when you change this date, the AssetBook.AcquisitionDate is overwritten.  see solution below.

Step 3 - Post Journal for (legacy system) Accumulated Depreciation
Since most of our fixed assets were partially depreciated at the time of import, we needed some way to record the prior depreciation.  To do this you could use Proposal > Depreciation to get 'caught up'.  There are two small issues: 1) if acquisition date was changed during step 2, the depreciation proposal will not know the correct aquisition date.  2) in our case, again, the Ledger balances already reflected the depreciation prior to import.
Our solution was to create a Depreciation Adjustment journal that has a matching ledger account and offset account.  Since there isn't a Proposal > Depreciation Adjustment, I wrote a job to do this.  (see below)

Step 4 - Ready To Go
The key fields that allow depreciation to 'pick up where it left off' are AssetBook.LifeTimeRest (remaining depreciation periods) and AssetBook.LastDepreciationDate.  We set these during the import, so there shouldn't be anything further to do.

Our Journal Creation Job
As noted in Step 2 and 3 - we used a job to create the acquisition journal and depreciation adjustment journal.  Here it is:

static void AssetCreateJournal(Args _args)
{
    LedgerJournalName   ledgerJournalName;
    LedgerJournalTable  ledgerJournalTable;
    LedgerJournalTrans  ledgerJournalTrans;
    NumberSeq           numberSeq;
    AssetTable          assetTable;
    AssetBook           assetBook;
    int                 lineNum;
    Date                transDate;
    ;

    transDate = mkDate(31,10,2009);  //Oct 31, 2009 = our conversion date

    ttsbegin;

    //Record the acquisition
    select firstonly ledgerjournalName where ledgerJournalName.JournalName == 'OurJournalName';
    ledgerJournalTable.JournalName = LedgerJournalName.JournalName;
    ledgerJournalTable.initFromLedgerJournalName();
    ledgerJournalTable.insert();

    numberSeq = NumberSeq::newGetVoucherFromCode(ledgerJournalName.VoucherSeries);

    while select assetBook where assetBook.Status == AssetStatus::NoAcquisition
    {
        lineNum++;
        ledgerJournalTrans.clear();
        ledgerJournalTrans.voucher = numberSeq.voucher();
        ledgerJournalTrans.LineNum = lineNum;
        ledgerJournalTrans.TransDate = transDate;
        ledgerJournalTrans.JournalNum = ledgerJournalTable.JournalNum;
        ledgerJournalTrans.initValue();  //sets currencyCode and exchRate, etc
        ledgerJournalTrans.AccountType = LedgerJournalACType::FixedAssets;
        ledgerJournalTrans.AccountNum = assetBook.AssetId;
        ledgerJournalTrans.initAssetBookid();  //sets assetBookId
        ledgerJournalTrans.initAssetPostingProfile(); //sets postingProfile
        ledgerJournalTrans.Txt = "Acquisition";
        ledgerJournalTrans.AssetTransType = AssetTransTypeJournal::Acquisition;
        ledgerJournalTrans.AmountCurDebit = assetBook.AcquisitionPrice;
        ledgerJournalTrans.OffsetAccountType = LedgerJournalACType::Ledger;
        ledgerJournalTrans.OffsetAccount = ledgerJournalTrans.findOffsetAccount();
        ledgerJournalTrans.Dimension = assetBook.Dimension;
        ledgerJournalTrans.insert();
    }

    ttscommit;
    info (strfmt("Fixed Asset Journal %1 was created with %2 lines",ledgerJournalTable.JournalNum,lineNum));

    ttsbegin;

    //Now do the same thing for depreciation adjustment
    lineNum = 0;

    select firstonly ledgerjournalName where ledgerJournalName.JournalName == 'FA';
    ledgerJournalTable.clear();
    ledgerJournalTable.JournalName = LedgerJournalName.JournalName;
    ledgerJournalTable.initFromLedgerJournalName();
    ledgerJournalTable.insert();

    numberSeq = NumberSeq::newGetVoucherFromCode(ledgerJournalName.VoucherSeries);

    //during import, the accumulated depreciation was temporarily stored in the assetReplaceCost field
    while select assetTable where assetTable.AssetReplaceCost > 0
    {
        lineNum++;
        ledgerJournalTrans.clear();
        ledgerJournalTrans.voucher = numberSeq.voucher();
        ledgerJournalTrans.LineNum = lineNum;
        ledgerJournalTrans.TransDate = transDate;
        ledgerJournalTrans.JournalNum = ledgerJournalTable.JournalNum;
        ledgerJournalTrans.initValue();  //sets currencyCode and exchRate, etc
        ledgerJournalTrans.AccountType = LedgerJournalACType::FixedAssets;
        ledgerJournalTrans.AccountNum = assetTable.AssetId;
        ledgerJournalTrans.initAssetBookid();  //sets assetBookId
        ledgerJournalTrans.initAssetPostingProfile(); //sets postingProfile
        ledgerJournalTrans.Txt = strfmt("Depreciation through %1",transDate);
        ledgerJournalTrans.AssetTransType = AssetTransTypeJournal::DepreciationAdj;
        ledgerJournalTrans.AmountCurCredit = assetTable.AssetReplaceCost; //using our temporary data from import
        ledgerJournalTrans.OffsetAccountType = LedgerJournalACType::Ledger;
        ledgerJournalTrans.OffsetAccount = ledgerJournalTrans.findOffsetAccount();
        ledgerJournalTrans.Dimension = AssetBook::Find(ledgerJournalTrans.AccountNum,ledgerJournalTrans.AssetBookId).Dimension;
        ledgerJournalTrans.insert();
    }

    ttscommit;

    info (strfmt("Fixed Asset Journal %1 was created with %2 lines",ledgerJournalTable.JournalNum,lineNum));
}
After reviewing and posting the journals, we ran the following job to clean up the records - remove temporary data and fix the aquisition date.

static void AssetFinalizeImport(Args _args)
{
    AssetTable          assetTable;
    AssetBook           assetBook;
    ;

    ttsbegin;

    //during import, the accumulated depreciation was temporarily stored in the assetReplaceCost field
    while select forupdate assetTable
    {
        assetBook = AssetBook::Find(assetTable.AssetId,'OurBookId',true);

        if (assetBook)
        {
            if (assetTable.GuaranteeDate != dateNull())
            {
                assetBook.AcquisitionDate = assetTable.GuaranteeDate;
            }
            //if fully depreciated
            if (assetTable.AssetReplaceCost == assetBook.AcquisitionPrice)
            {
                assetBook.Status = AssetStatus::Closed;
            }
            assetBook.update();

            //clear the temporary fields
            assetTable.AssetReplaceCost = 0;
            assetTable.GuaranteeDate = datenull();
            assetTable.update();
        }
    }

    ttscommit;
}
That's all - hope it's a help.

No comments:

Post a Comment