Blog Listem

15 Kasım 2017 Çarşamba

AX 2012 - Multiselect Lookup

Bunun için AOT altında örnek bir form (tutorial_LookupMultiSelectGrid) ve hazır bir class var:

public class FormRun extends ObjectRun
{
    SysLookupMultiSelectCtrl msCtrl;
}


public void init()
{
    
    ...  
    super();
    ...
    
    msCtrl = SysLookupMultiSelectCtrl::construct(element, InventLocationIdExc, queryStr(InventLocationSRS));
    msCtrl.set(this.InventLocationList());
    ...
}

Listeyi aşağıdaki gibi alıp yukarıdaki init metod örneğindeki gibi okuyabiliriz.

container InventLocationList()
{
    Container           Ids,Names;
    InventLocation      location;
    int i;
    RecId               recId;

    for (i=1;i<=conLen(vInventLocationIdExc);i++)
    {
        recId = conPeek(vInventLocationIdExc,i);
        select firstOnly location
            where location.RecId == recId;
        Ids += location.RecId;
        Names += location.InventLocationId;
    }

    return [Ids,Names];
}

Pack metodda aşağıdaki gibi saklayabiliriz:

container pack()
{
    ...
    vInventLocationIdExc    = msCtrl.get();
    ...
}

Yukarıdaki MsCtrl.Get() metodu  RecId değerlerini verirken aşağıdaki de Field değerlerini verir:

container               c = msCtrl.getSelectedFieldValues();

14 Kasım 2017 Salı

AX 2012 - Form Grid kontrolüne InventDim ekleme

InventDim tablosunu form data source kısmına ekleyip inventDimId alanının bulunduğu tabloyla (bu örnekte adı SNBProductDemonte) InnerJoin yapın.

Form gride aşağıdaki gibi bir Group kontrol ekleyin:




Forma aşağıdaki kodları ekleyin:

public class FormRun extends ObjectRun
{
    InventDimCtrl_Frm_EditDimensions        inventDimFormSetup;
}

public void init()
{
    super();
    ...
    element.updateDesign(InventDimFormDesignUpdate::Init);
...

}


Object inventDimSetupObject()
{
    return inventDimFormSetup;
}

void updateDesign(InventDimFormDesignUpdate mode)
{
   InventDimParm inventDimParmVisible;

    switch (mode)
    {
        // Form Init
        case InventDimFormDesignUpdate::Init    :
            if (!inventDimFormSetup)
                inventDimFormSetup  = InventDimCtrl_Frm_EditDimensions::newFromForm(element);
                inventDimFormSetup.parmSkipOnHandLookUp( true);

                // Use the methods on InventDimParm
                // to set which dimensions to show when form is initialized
         //       inventdimparmvisible.inventsiteidflag       = true;
           //     inventdimparmvisible.InventLocationIdFlag   = true;
                inventDimFormSetup.parmDimParmVisibleGrid(inventDimParmVisible);

        // Datasource Active
        case InventDimFormDesignUpdate::Active  :
            inventDimFormSetup.formActiveSetup(InventDimGroupSetup::newItemId(SNBProductDemonte.ItemId)); 
            inventDimFormSetup.formSetControls( true);
            break;

        // Datasource Field change
        case InventDimFormDesignUpdate::FieldChange :
            inventDimFormSetup.formActiveSetup(InventDimGroupSetup::newItemId(SNBProductDemonte.ItemId)); 
            InventDim.clearNotSelectedDim(inventDimFormSetup.parmDimParmEnabled()); // InventDim is referring to datasource name
            inventDimFormSetup.formSetControls( true);
            break;

        default :
            throw error(strFmt ("@SYS54195", funcName()));
    }
}

InventDimId alanının olduğu tabloya aşağıdaki kodları ekleyin:

public boolean validateWrite()
{
    boolean ret;

    SNBProductDemonte.InventDimId = InventDim::findOrCreate(InventDim).InventDimId;
    ret = super();
  
    return ret;
}

public int active()
{
    int ret;
    ret = super();

    element.updateDesign(InventDimFormDesignUpdate::Active);
    return ret;
}

ItemId alanı modified metoduna aşağıdaki kodu ekleyin:

public void modified()
{
    super();

    element.updateDesign(InventDimFormDesignUpdate::FieldChange);
    InventDim.clearNotSelectedDim(element.inventDimSetupObject().parmDimParmEnabled());
}


Sistemde hazır bulunan InventDimParmFixed isimli Display menu item'i forma ekleyin.

Kaynak:
https://daxbeginners.wordpress.com/2014/08/05/how-to-dynamically-display-inventory-dimension/

Axapta - DateTime Alanlarda İki Gün Arasıda Saniye Kaçırmamak

DateTime alanlarda iki tarih arası rapor alırken ikinci tarihi bir sonraki günün ilk saniyesi olarak alıp <= yerine < kullanmak bir saniye dahi kaçırmamamızı sağlar:

fromDate = DateTimeUtil::newDateTime(FromDateEdit.dateValue(),0,DateTimeUtil::getCompanyTimeZone());
    toDate   = DateTimeUtil::newDateTime(ToDateEdit.dateValue()+1,0,DateTimeUtil::getCompanyTimeZone());

    while select count(RecId) from serviceOrderTable
                where serviceOrderTable.createdDateTime >= fromDate
                    && serviceOrderTable.createdDateTime < toDate



10 Kasım 2017 Cuma

AX 2012 - SSRS Raporda Nakli/Devreden Yekün ve Sayfa Toplamı

Maalesef SSRS'te nakli yekün ve sayfa toplamı olması gerekenden çetrefilli. Nakli yekün için bir blogdan (Peter ?), sayfa toplamı için de başka bir blogdan (Annette Theißen) faydalandım.

Nakli yekün ve sayfa toplamı alacağımız alan AccountingCurrencyAmountDebit olsun.

Devreden yekün için tablix kontrolümüze adı dCarry, valuesi aşağıdaki gibi olan bir alan ekliyoruz:

=RunningValue(Fields!AccountingCurrencyAmountDebit.Value,SUM,"LedgerTransListAccountDS")

veya koda göre gruplayacaksak:

=RunningValue(Fields!AccountingCurrencyAmountDebit.Value,SUM,"AccountNum_0")


Nakli yekün için de adı dCarryH olan aşağıdaki gibi bir alan ekliyoruz:

=RunningValue(Fields!AccountingCurrencyAmountDebit.Value,SUM,"AccountNum_0") - Fields!AccountingCurrencyAmountDebit.Value

Her iki alanın da visibility değerini no yapıyoruz. Alanlar gizli de olsa hesaplama yapacak.

Nakli yekün için Page Header kısmına valuesi aşağıdaki gibi olan bir alan ekliyoruz:

=First(ReportItems!dCarryH.Value)

Visibility değerini şarta bağlıyoruz (Böylece ilk sayfada nakli yekün olmayacak):

=Globals!PageNumber=1

Devreden yekün için de Page Footer kısmına da valuesi aşağıdaki gibi olan bir alan ekliyoruz:

=last(ReportItems!dCarry.Value)

Visibility değerini şarta bağlıyoruz (Böylece son sayfada devreden yekün olmayacak):

=Globals!PageNumber=Globals!TotalPages

Sayfa toplamı için iki data method ve variable eklememiz gerekiyor. Data method eklemek için treeview'de Designs altındaki Data Methods nodunda sağ klik - Add Data Method yapıyoruz. Ardından üzerine çift klik yaparak kod ekranını açıyoruz. Eklediğimiz kod aşağıda kırmızı çizili olan parça:

using System;
using System.Collections.Generic;
using System.Security.Permissions;
using System.Data;
using Microsoft.Dynamics.Framework.Reports;
using Microsoft.Dynamics.AX.Application.Reports;
public partial class LedgerTransListAccount
{
    static double previousTotal;
    static double pageTotal;
    static bool NewAccount;
    static string lastAccount;

     ...
   
 [DataMethod(), PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
    public static double PageTotal(double total)
    {
         if (NewAccount)
            return total;
         else
            return total - previousTotal;
    }

    [DataMethod(), PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
    public static double PreviousTotal(double total)
    {
        previousTotal = total;

        return 0;
    }
[DataMethod(), PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
    public static string Reset()
    {
        NewAccount = false;
        lastAccount = "";

        return "";
    }

    [DataMethod(), PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
    public static string FollowAccount(string account)
    {
        if (lastAccount != "" && lastAccount != account)
            NewAccount = true;
        lastAccount = account;

        return account;

    }
      
}

Page Footera Bir textbox ekliyoruz:

=PreviousTotal(First(ReportItems!dCarryH.Value))

Bu textbox'un visible değerini false yaparsak maalesef hesaplama yapmaz. Display formatı number seçip Show zero as combobox değerini boşluk yapın (None değil!!).

Sayfa toplamını göstermek için bir alan daha ekliyoruz:

=PageTotal(last(ReportItems!dCarry.Value))

Neden yukarıdaki gibi fazladan bir textbox kullandık diyenler için; SSRS malesef expression parametre olarak iki farklı alanı kabul etmiyor.

Sayfa başına bir Textbox ekliyoruz:

=Reset()




8 Kasım 2017 Çarşamba

AX 2012 - Ürün reçetesi açılımı

Bunun için Christian Silva'nın yazdığı kodları kullandım. Yazar birim çevrimlerini ve renk /bedeni dikkate almamış. Reçetede gram olarak kullanılan bir yarı-mamulün alt reçetede kg'ya çevrilmesi gerekebiliyor. Ben bunları da ekledim:

class SNBBOMExplode
{
    SNBBOMExplodeTmp    tmpBOM;
    SNBBOMExplodeData   bomdata;
    ItemId              itemId;
    EcoResItemSizeName  inventSizeId;
    EcoResItemColorName inventColorId;
    InventSiteId        inventSiteId;
}

boolean hasChild(ItemId _itemId)
{
    BOMVersion  bomVersion;
    ;

    //Check if the item is also a BOM item.
    select firstonly bomVersion
            where bomVersion.ItemId == _itemid
            && bomVersion.Active
            && bomVersion.FromDate <= systemdateget ()
            && (!bomVersion.ToDate || bomVersion.ToDate >= systemdateget ());

    if (bomVersion.RecId)
        return true ;

    return false ;
}

private int InsertParentItem(ItemId _itemId, int _level)
{
    InventTable inventTable;
    ;

    //Gets the parent information and then insert on TmpBOMExplode Table with level 0.
    select firstOnly inventTable where inventTable.ItemId == _itemId;

    tmpBOM.ItemId = _itemId;
    tmpBOM.Level = _level;
    tmpBOM.BOMQty = 1 ;
    tmpBOM.HasChild = this.hasChild(_ItemId);
    tmpBOM.insert();

    return _level+ 1 ;
}

void itemExplode(ItemId _ItemId, int _level = 0, BOMQty _bomQty = 1)
{
    BOM         bomTable;
    InventTable inventTable;
    BOMVersion  bomVersion;
    InventDim   inventDim;
    BOMQty      qtyS;

    ;

    //Insert parent Item
    if (_level == 0)
        _level = this.InsertParentItem(_ItemId, _level);

    //Verifies if the Item exists in BOMVersion Table.
    //The item must be active and not expired.
    select firstonly bomVersion
            where bomVersion.ItemId == _itemid
            && bomVersion.Active
        exists join inventDim
            where inventDim.inventDimId == bomVersion.InventDimId &&
                  (inventDim.InventColorId == inventColorId || inventColorId == "") &&
                  (inventDim.InventSizeId == inventSizeId || inventSizeId == "");
        //    && bomVersion.FromDate <= systemdateget()
        //    && (!bomVersion.ToDate || bomVersion.ToDate >= systemdateget());

    if (bomVersion.RecId)
    {
        //Every item on BOMVersion has a BOMId which is used to show
        //which products belong to a BOM Item.
        While select bomTable
            where bomTable.BOMId == bomVersion.BOMId
            join inventTable
            where bomTable.ItemId == inventTable.ItemId
        {
            //Insert the items that compose the BOM Item with level 1.
            tmpBOM.ItemId    = bomTable.ItemId;
            tmpBOM.RefItemId = bomVersion.ItemId;
            tmpBOM.BOMQty    = bomTable.BOMQty / bomTable.BOMQtySerie * _bomQty;
            tmpBOM.Level     = _level;
            tmpBOM.UnitId    = bomTable.UnitId;
            tmpBOM.HasChild  = this.hasChild(bomTable.ItemId);
            tmpBOM.MainItemId = this.parmItemId();
            tmpBOM.insert();

            //This method is used to check if the BOM Item is composed by
            //another BOM Item, case true it will call the method recursively
            // with level 2.
            if (tmpbom.HasChild == NoYes::Yes)//--- unit conversion ---
            {

                qtyS =  UnitOfMeasureConverter::convert(
                            tmpBOM.BOMQty,
                            UnitOfMeasure::unitOfMeasureIdBySymbol(tmpBOM.UnitId),
                            UnitOfMeasure::unitOfMeasureIdBySymbol(bomTable.inventTable().inventUnitId()),
                            NoYes::Yes,
                            InventTable::itemProduct(tmpBOM.ItemId));

                this.itemExplode(bomTable.ItemId, _level+ 1, qtyS);
            }
        }
    }
}

void linkTables(SNBBOMExplodeTmp _tmp)
{
   tmpBOM.linkPhysicalTableInstance(_tmp);
}

EcoResItemColorName parmInventColorId(EcoResItemColorName _inventColorId = inventColorId)
{
    inventColorId = _inventColorId;

    return inventColorId;
}

EcoResItemSizeName parmInventSizeId(EcoResItemSizeName  _inventSizeId = inventSizeId)
{
    inventSizeId = _inventSizeId;

    return inventSizeId;
}

ItemId parmItemId(ItemId _itemId = ItemId)
{
    ItemId = _itemId;

    return ItemId;
}

SNBBOMExplodeTmp  parmSNBBOMExplodeTmp()
{
    select * from tmpBOM;
    return TmpBOM;
}

void run()
{
    delete_from tmpBOM;
    this.itemExplode(ItemId,0);
}


Class'da kullanılan SNBBOMExplodeTmp isimli tempDB tipli tablonun yapısı:

BOMQty     BOMQty
HasChild   NoYesId
ItemId     ItemId
Level      BOMLevel
MainItemId ItemId
RefItemId  ItemId
UnitId     UnitOfMeasureSymbol

Örnek kullanımı:

         SNBBOMExplodeTmp    tmpBOM;
    SNBBOMExplode       bomExp = new SNBBOMExplode();
    

    bomExp.parmItemId(list.ItemId);
    bomExp.linkTables(tmpBOM);
    bomExp.parmInventColorId(list.InventColorId);
    bomExp.parmInventSizeId(list.InventSizeId);
    bomExp.run();


13 Ekim 2017 Cuma

AX 2012 - Bir Finansal Boyuta Göre Sıralama

AX 2012'de mali boyutlar bir RecId alanında saklı olduğu ve çeşitli tabloları refere ettiği için sıralama olayı biraz karışık. Bunun için işimizi kolaylaştırıcı bir sınıf var:

DimensionProvider           dimProvider = new DimensionProvider();
    dimProvider.addOrderByAttribute(BorAXJournalTrans_DS.query(),BorAXJournalTrans_DS.query().dataSourceNo(1).name(),
       FieldStr(BorAXJournalTrans,DefaultDimension),
        DimensionComponent::DimensionAttribute,SortOrder::Ascending,
"Departmanı");
BorAXJournalTrans_DS.executeQuery();
  
Bu sınıfı kullanmak istemiyorum derseniz bir de şöyle bir seçenek var:

RecId  depRecId = 5637152827; //departman boyutunun DimensionAttribute tablosundaki recId değeri
    BorAXJournalTrans   boraxJournalTrans;
    DimensionAttributeValueSet dimensionAttributeValueSet;
    DimensionAttributeValueSetItem dimensionAttributeValueSetItem;
    DimensionAttributeValue dimensionAttributeValue;
    DimensionAttribute dimensionAttribute;

while SELECT firstOnly10 borAXJournalTrans 
    ORDER BY DimensionAttributeValueSetItem.DisplayValue
 JOIN dimensionAttributeValueSet 
    where borAXJournalTrans.DefaultDimension == dimensionAttributeValueSet.RecId 
 JOIN dimensionAttributeValueSetItem 
    where dimensionAttributeValueSet.RecId == dimensionAttributeValueSetItem.DimensionAttributeValueSet 
 JOIN dimensionAttributeValue 
    where dimensionAttributeValueSetItem.DimensionAttributeValue == dimensionAttributeValue.RecId 
 JOIN dimensionAttribute 
    where dimensionAttributeValue.DimensionAttribute == dimensionAttribute.RecId && 
     dimensionAttribute.RecId == depRecId
    {
        info(dimensionAttributeValueSetItem.DisplayValue);
    }

9 Ağustos 2017 Çarşamba

AX 2012 - Query'ye tek bir finansal boyut için filtre eklemek

Bunu tabloları ekleyerek yapmaya kalktığınızda bir çok tabloyu birbirine bağlamak zorunda olduğunuzu göreceksiniz. Bunu sizin yerinize yapan DimensionProvider adlı bir sınıf eklenmiş AX 2012'ye:

DimensionProvider           dimProvider = new DimensionProvider();


        dimProvider.addAttributeRangeToQuery(element.query(),element.query().dataSourceNo(1).name(),
       FieldStr(MyTable,DefaultDimension),
        DimensionComponent::DimensionAttribute,
        OMOperatingUnit::find(depRecId,OMOperatingUnitType::OMDepartment).OMOperatingUnitNumber,
            "Departmanı",true);

Bu arada Unknown type: RefRecId gibi bir hata alırsanız sebebi kullandığınız tablodaki EDT'nin yanlış olması olabilir. Benim başıma gelen durumda sorun yerel bir çözümde DefaultDimension field'ına DimensionDefault yerine RefRecId EDT kullanılmasıydı. Sizinkinde RefRecId yerine başka birşey de olabilir. AX orijinal tablosu HcmEmployment tablosunda da böyle bir bug varmış.

8 Ağustos 2017 Salı

AX 2012 - Dönem kapanış kontrolü

Muhasebe için:

FiscalCalendars::checkModuleIsOpen(SysModule::Ledger, myTable.TransDate,FiscalCalendars::findPeriodByPeriodCodeDate(Ledger::fiscalCalendar(CompanyInfo::find().RecId),myTable.ETGTransDate),false)

veya

select firstOnly period
        where transDate >= period.StartDate  && transDate <= period.EndDate
            exists join ledgerPeriod
                where ledgerPeriod.FiscalCalendarPeriod == period.RecId &&
                      ledgerPeriod.Status != FiscalPeriodStatus::Open;
    if (period.RecId != 0)
        throw error("Yeni tarih için ilgili mali dönem kapalı!..");

Satış için:

FiscalCalendars::checkModuleIsOpen(SysModule::Sales, CustInvoiceJour.InvoiceDate,FiscalCalendars::findPeriodByPeriodCodeDate(Ledger::fiscalCalendar(CompanyInfo::find().RecId),CustInvoiceJour.InvoiceDate),false)

Stok kapanışı için:

InventClosing::findClosingDate(endmth(InventJournalTrans.TransDate))

2 Ağustos 2017 Çarşamba

AX 2012 - Security Development Tool Kaynaklı Derleme Hatası

Security Development Tool yetkilendirme işlemlerini oldukça kolaylaştıran harika bir araç. Ancak bizim başımıza gelen can sıkıcı bir soruna yol açabiliyor. Aşağıdaki hatayı alırsanız bilin ki sebebi bu tool:

Error executing code: SysSecurityRecorder_**** object does not have method 'MenuItemInvoked'.

Konuyla ilgili bulabildiğim tek kaynak Google translate ile fransızcadan ingilizceye çevirerek anlayabildiğim bir blog'du (Şahsen Google Translate'yi türkçe çeviriler için çok başarısız buluyorum). Buradaki iki yöntem de işime yaradı:

1. yöntem: Client bilgisayardaki AOD dosyalarını silmek.

2.yöntem: AX DB'de bu kodu çalıştırınca tek tek tüm clientlarda temizlik yapmaya gerek kalmıyor:

UPDATE SYSSQMSETTINGS SET GLOBALGUID = '{00000000-0000-0000-0000-000000000000}'


7 Temmuz 2017 Cuma

AX 2012 - TempDB ile server metoddan forma veri göndermek

TempDB tablosunda direk parametre ile gönderip doldurmaya çalıştığınızda kayıt gelmediğini göreceksiniz. Kayıtların gelmesi için ya In Memory tablo türünü seçmelisiniz ya da linkPhysicalTableInstance metodu ile iki TempDB tablosunu birbirine bağlamalısınız:

Class:

public static server void populateData(MyTempTable _tmp)
{
MyTempTable tmp;
...
...
tmp.linkPhysicalTableInstance(_tmp);
...
...
return;
}

Form:


MyClass::populateData(MyTempTable);
MyTempTable_DS.research();
MyTempTable_DS.refresh();

4 Ocak 2017 Çarşamba

AXAPTA - Tabloları eşitle

İş arkadaşımın internetten bulduğu kod, kaynağını bilmiyorum ama birçok durumda işe yarıyor:

    Dictionary              dict;
    int                     idx, lastIdx, totalTables;
    TableId                 tableId;
    Application             application;
    SysOperationProgress    progress;
    StackBase               errorStack;
    ErrorTxt                errorTxt;
    ;

    application = new Application();
    dict = new Dictionary();
    totalTables = dict.tableCnt();
    progress = new SysOperationProgress();
    progress.setTotal(totalTables);
    progress.setCaption("@SYS90206");
    errorStack = new StackBase(Types::String);

    lastIdx = 3000;
    try
    {
        for (idx = lastIdx+1; idx <= totalTables; idx++)
        {
            tableId = dict.tableCnt2Id(idx);
            progress.setText(dict.tableName(tableId));

            lastIdx = idx;
            application.dbSynchronize(tableId, false, true, false);
            progress.incCount();
        }
    }
    catch (Exception::Error)
    {
        errorTxt = strFmt("'%1' (%2) tablosunda hata var!", tableId, dict.tableName(tableId));
        errorStack.push(errorTxt);
        retry;
    }

    setPrefix("@SYS86407");
    errorTxt = errorStack.pop();
    while (errorTxt)
    {
        error(errorTxt);
        errorTxt = errorStack.pop();
    }