13 Şubat 2019 Çarşamba

AX 2012 ve Power BI

Bu yazımda Power BI öğrenmenin en kolay yolunu anlatıp bazı pürüz çıkaracak noktalar ve Power BI ile AX datası kullanırken dikkat edilmesi gereken noktaları maddeleyeceğim:

-Power BI eğitimi için benim bulduğum en iyi kaynak EDX sitesindeki Analyzing and Visualizing Data with Power BI eğitimiydi. Microsoft tarafından hazırlanan bu eğitim video ile altyazının mükemmel harmanlanmış bir hali. Video akarken yanda da altyazı komple akıyor ve altyazıda bir yere tıkladığınızda video da o bölüme gidiyor (Altyazı Türkçe değil). Arada testlerle devam eden eğitim hiçbir konuyu atlamıyor; Year-to-Day, gateway vs...

-Devreden bakiyeli rapor veya Waterfall rapor oluşturabilmek için tarih tablosuna ihtiyacınız var. Bunun için de ben Power BI İstanbul'da bulduğum bir scripti kullandım:

let fnTarihTablosu = (BaşlangıçTarihi as date, BitişTarihi as date, MaliYılBaşlamaAyı as number) as table =>
  let
    DayCount = Duration.Days(Duration.From(BitişTarihi - BaşlangıçTarihi)) + 1,
    Source = List.Dates(BaşlangıçTarihi,DayCount,#duration(1,0,0,0)),
    TableFromList = Table.FromList(Source, Splitter.SplitByNothing()),
    ChangedType = Table.TransformColumnTypes(TableFromList,{{"Column1", type date}}),
    RenamedColumns = Table.RenameColumns(ChangedType,{{"Column1", "Tarih"}}),
    YılEkle = Table.AddColumn(RenamedColumns, "Yıl", each Date.Year([Tarih]),type text),
    CeyrekEkle = Table.AddColumn(YılEkle, "Çeyrek No", each Date.QuarterOfYear([Tarih])),
    AyEkle = Table.AddColumn(CeyrekEkle, "Ay No", each Date.Month([Tarih]), type text),
    GünEkle = Table.AddColumn(AyEkle, "Ayın Günü No", each Date.Day([Tarih])),
    AyEkleIsim = Table.AddColumn(GünEkle, "Ay", each Date.ToText([Tarih], "MMMM"), type text),
    AyEkleKısaIsim = Table.AddColumn(AyEkleIsim, "Ay Kısa", each Date.ToText([Tarih], "MMM"), type text),
    HaftanınGünüEkle = Table.AddColumn(AyEkleKısaIsim, "Haftanın Günü No", each Date.DayOfWeek([Tarih])+1),
    GünIsmiEkle = Table.AddColumn(HaftanınGünüEkle, "Haftanın Günü", each Date.ToText([Tarih], "dddd"), type text),
    HaftaNoEkle= Table.AddColumn(GünIsmiEkle, "Hafta No", each Date.WeekOfYear([Tarih])),
    ChangedType1 = Table.TransformColumnTypes(HaftaNoEkle,{{"Hafta No", Int64.Type},{"Yıl", type text},{"Ayın Günü No", Int64.Type}, {"Ay No", Int64.Type}, {"Çeyrek No", Int64.Type}, {"Haftanın Günü No", Int64.Type}}),
    KısaYılEkle = Table.AddColumn(ChangedType1, "Yıl Kısa", each Text.End(Text.From([Yıl]), 2), type text),
    MaliYıl = Table.AddColumn(KısaYılEkle, "Mali Yıl", each "MY"&(if [Ay No]>=MaliYılBaşlamaAyı then Text.From(Number.From([Yıl Kısa])+1) else [Yıl Kısa]))


in
    MaliYıl
in
    fnTarihTablosu

-Türkçe'nin klasik sorunu i ve ı sorununu burada da yaşayabilirsiniz. Ben DataAreaId'de bu sorunu yaşadım. Company tablosunda DEIM yazarken diğer tablolarda deim yazıyordu ve bunları eşleştiremiyordu. Ayrıca Adres tabloları birincil adres, geçerliliğini yitirmiş adres, isim tabloları da farklı diller yüzünden çoklamaya sebep olabilir. Bunlar için de ayarlama yapmak gerekiyor:

select c.accountnum,d.name,c.custgroup,l.state,l.COUNTRYREGIONID,lc.SHORTNAME,  iif(c.dataareaID = 'deim','DEIM',c.dataareaid) as DATAAREAID
from custtable c left join DIRPARTYTABLE d on c.PARTY = d.RECID
left join DIRPARTYLOCATION dl on d.RECID = dl.PARTY
 left join LOGISTICSPOSTALADDRESS l on  dl.LOCATION = l.LOCATION
left join LOGISTICSADDRESSCOUNTRYREGIONTRANSLATION lc on lc.COUNTRYREGIONID = l.COUNTRYREGIONID
where
lc.LANGUAGEID = 'EN-AU'
and Dl.ISPRIMARY = 1 and L.VALIDTO IN (select top 1 VALIDTO from LOGISTICSPOSTALADDRESS a 
where a.LOCATION = dl.LOCATION order by VALIDTO desc)

Yukarıda ilk işaretli komut parçası Türkçe harf sorununu engellemek için, ikincisi farklı dillerden kaynaklanan çoklamayı engellemek için, üçüncü de tek bir birincil geçerli adresi alabilmek için.

-Dataların düzenli olarak otomatik cloud'a aktarılabilmesi için sürekli açık kalan bir bilgisayara (Tercihen SQL sunucuya) gateway kurmalısınız. Yukarıda bahsettiğim EDX eğitiminde gareway kurulumu konusu da geçiyor.

-Ayrıca raporlarınızı geliştirdikten sonra Phone view kısmına girip mobil dizayn yapmayı da unutmayın. Çok kolay bir işlem olan bu adımı atladığınızda raporlarınız telefonda beklediğiniz etkiyi veremeyecektir.

-Power BI relation kurarken sadece birebir field eşleştirebiliyor. Bunu iki kolonu merge ederek aşabilirsiniz. Eğitimlerde bunun da konusu geçiyor.

-Birçok AX tablosunda aynı key alan farklı DataAreaId'ler için tekrarlanıyor. Bu durum 1:N relationlarda sorun çıkaracaktır. Bunu da yukarıdaki yöntemle aşabilirsiniz. Örneğin CustTable için AccountNum & DataAreaId ve faturaları normalize ettiğiniz tablo için de CustAccount & DataAreaId gibi...

30 Ocak 2019 Çarşamba

AX - 2012 Satınalma sipariş raporunu direk email olarak göndermek

Satınlama sipariş raporunu (PurchPurchaseOrder) direk mail olarak göndermek:

    SrsReportRunController          controller = new SrsReportRunController();
    PurchPurchaseOrderContract      Contract = new PurchPurchaseOrderContract();
    SRSPrintDestinationSettings     printSettings;
    VendPurchOrderJour              orderJour;
    SrsReportEMailDataContract      emailContract = new SrsReportEMailDataContract();

   select firstonly orderJour
            order by PurchOrderDate Desc,CreatedDateTime Desc
            where orderJour.PurchId == "ST000181";

    emailContract.parmAttachmentFileFormat(SRSReportFileFormat::PDF);
    emailContract.parmSubject("Purchase Order");
    emailContract.parmTo("test@hotmail.com");
    
    
    controller.parmReportName(ssrsReportStr(PurchPurchaseOrder, Report));
    controller.parmShowDialog(false);
    Contract.parmRecordId(orderJour.RecId);
    controller.parmReportContract().parmRdpContract(Contract);

    printSettings = controller.parmReportContract().parmPrintSettings();
    printSettings.printMediumType(SRSPrintMediumType::Email);
    printSettings.fileFormat(SRSReportFileFormat::PDF);
    printSettings.parmEMailContract(emailContract);
    printSettings.overwriteFile(true);

    controller.runReport();


Yukarıdaki yöntemde sistem AX mail göndericisi yerine Outlook'u kullanmak isteyecektir. Aşağıdaki yöntem alternatif olabilir. Yukarıdaki yöntemin dezavantajı Outlook'u kullanması iken aşağıdakinin de dezavantajı dosyayı öncelikle diskte bir yere yazmanızın gerekmesi:


  PurchTable          purchTable;
    str                 body;
    PurchLine           purchLine;
    boolean             found;
    VendPurchOrderJour  orderJour;
    Filename            filename;
    SysMailer           mailer;
    SysEmailParameters  parameters;
      SrsReportRunController  controller = new SrsReportRunController();
    PurchPurchaseOrderContract  contract = new PurchPurchaseOrderContract();
 SRSPrintDestinationSettings printSettings;   
    
    controller.parmReportName(ssrsReportStr(PurchPurchaseOrder,Report));
    controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);
printSettings = controller.parmReportContract().parmPrintSettings();
printSettings.fileFormat(SRSReportFileFormat::PDF);
printSettings.printMediumType(SRSPrintMediumType::File);
printSettings.overwriteFile(true);

printSettings.fileName(@filename);
filename = strFmt(@"%1%2.PDF",WinAPI::getTempPath(),"PO00001");

     select firstonly orderJour
            order by PurchOrderDate Desc,CreatedDateTime Desc
            where orderJour.PurchId == "PO00001"
    contract.parmRecordId(orderJour.RecId);
    controller.parmReportContract().parmRdpContract(contract);
    
    controller.parmShowDialog(false);
    controller.runReport();
    infolog.clear();
    
    new InteropPermission(InteropKind::ComInterop).assert();
    mailer = new SysMailer();
    parameters = SysEmailParameters::find();


    if (parameters.SMTPRelayServerName)
    {
        mailer.SMTPRelayServer(parameters.SMTPRelayServerName,
                            parameters.SMTPPortNumber,
                            parameters.SMTPUserName,
                            SysEmailParameters::password(),
                            parameters.NTLM);
    }
    else
    {
        mailer.SMTPRelayServer(parameters.SMTPServerIPAddress,
                            parameters.SMTPPortNumber,
                            parameters.SMTPUserName,
                            SysEmailParameters::password(),
                            parameters.NTLM);
    }
    body = '<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-9">';    
    body += strFmt(@"<br><caption>%1</caption><br><br>","Satınalma sipariş formunuz ektedir.");      
    
    mailer.fromAddress("sender@hotmail.com");
    mailer.tos().appendAddress("test@hotmail.com");
    mailer.htmlBody(body);
    mailer.subject(title);
    mailer.attachments().add(filename);
    mailer.bodyCharSet("Windows-1254");
    mailer.sendMail();

24 Ocak 2019 Perşembe

AX 2012 - Kur girişi, hesaplama, kurdan TL tutar hesaplama

Kur hesaplama:

ExchangeRateCurrencyPair    pair;

//TCMB : Döviz kur tipi (ExchangeRateType) tablosu Name anahtar alanı
select firstOnly pair
                    where pair.ExchangeRateType == ExchangeRateType::findByName("TCMB").RecId &&
                        pair.FromCurrencyCode == "USD" &&
                        pair.ToCurrencyCode == Ledger::accountingCurrency(CompanyInfo::current());


this.ExchangeRate = ExchangeRate::findByDate(pair.RecId,systemDateGet()).ExchangeRate;

Kur girişi için edit metod ( SalesTable gibi bir çok formda örneği var ):

public edit CurrencyExchangeRate editExchRate(boolean set, CurrencyExchangeRate _exchRate)
{
    ExchangeRateHelper exchangeRateHelper = ExchangeRateHelper::newCurrency(Ledger::primaryLedger(CompanyInfo::findDataArea(curext()).RecId), this.Currency);

    if (set)
    {
        this.ExchangeRate = exchangeRateHelper.prepareExchangeRateForStorage(_exchRate);
    }
    else
    {
        _exchRate = exchangeRateHelper.displayStoredExchangeRate(this.ExchangeRate);
    }

    return _exchRate;
}

Girilen kur değerinden TL tutar hesaplama:
ExchangeRateHelper exchangeRateHelper = ExchangeRateHelper::newCurrency(Ledger::primaryLedger(CompanyInfo::findDataArea(curext()).RecId), Ledger::accountingCurrency(CompanyInfo::current()));
 CurrencyExchangeHelper  cur = CurrencyExchangeHelper::construct();
   
 cur.parmLedgerRecId(Ledger::primaryLedger(CompanyInfo::current()));
 cur.parmExchangeDate(today());
//Eğer bugünün kurunu otomatik bulmasını istiyorsanız aşağıdaki iki satırı silin
     cur.parmExchangeRate1(exchangeRateHelper.prepareExchangeRateForStorage(1));
cur.parmExchangeRate2(this.ExchangeRate);
    this.BudgetAmountMST = cur.calculateTransactionToAccounting(this.Currency,this.BudgetAmount,true);

veya yukarıdaki işlemin kısa hali:

info(strFmt("%1", Currency::curAmount(100,"usd",today(),UnknownNoYes::Yes,500,100)));
    info(strFmt("%1", Currency::curAmount2CurAmount(100,"usd","try",today())));

18 Ocak 2019 Cuma

AX 2012 - Test/Dev ortamı/farklı şirketler için arka plan rengini değiştirmek

SysSetupFormRun sınıfının Run metoduna aşağıdakine benzer birşeyler yazabilirsiniz:


 this.design().colorScheme(FormColorScheme::RGB);
 this.design().backgroundColor(WinAPI::RGB2int(71,216,86));


veya curext() ile bir switch/case yazarak her şirkete ayrı renk verebilirsiniz.

12 Aralık 2018 Çarşamba

AX 2012 - Son maliyet fiyatı

Metodu InventTable veya ItemId alanı olan herhangi bir tabloya koyabilirsiniz:

public CostPrice SNBfindLastPrice(DatePhysical _date = systemDateGet(),InventDim _inventdim = null)
{

    InventTrans           inventTrans;

    InventTransOrigin     origin;
    InventDim             inventdim;
    LineAmount            purchprice;
    RecId                 OK = false;
    InventDimParm         dimParm;
    InventDim             dimJoin;


    dimparm.initFromInventDim(_inventdim);


    select firstOnly CostAmountAdjustment,CostAmountPosted,Qty from inventTrans

                order by inventTrans.DateClosed desc,inventTrans.DateFinancial desc
            where inventTrans.ItemId == this.ItemId &&
                  !inventTrans.ReturnInventTransOrigin &&
                 inventTrans.DateClosed != dateNull() &&
                 inventTrans.DateClosed <= _date
        exists join origin
            where origin.RecId == inventTrans.InventTransOrigin &&
                  origin.ReferenceCategory == InventTransType::SummedUp
        #InventDimExistsJoin(inventTrans.InventDimId,dimJoin,_inventdim,dimParm);
    OK = inventTrans.RecId;

    if (!OK)

    {
        select firstOnly CostAmountAdjustment,CostAmountPosted,Qty from inventTrans
                    order by inventTrans.DateFinancial desc
                where inventTrans.ItemId == this.ItemId &&
                      !inventTrans.ReturnInventTransOrigin &&
                      inventTrans.Qty > 0 &&
                      inventTrans.DateFinancial <= _date
            exists join origin
                where origin.RecId == inventTrans.InventTransOrigin &&
                      origin.ReferenceCategory == InventTransType::Purch
            #InventDimExistsJoin(inventTrans.InventDimId,dimJoin,_inventdim,dimParm);
        OK = inventTrans.RecId;
    }
    if (!OK)
    {
        select firstOnly CostAmountAdjustment,CostAmountPosted,Qty from inventTrans
                    order by inventTrans.DateFinancial desc
                where inventTrans.ItemId == this.ItemId &&
                      inventTrans.Qty > 0 &&
                      inventTrans.DateFinancial <= _date
            exists join origin
                where origin.RecId == inventTrans.InventTransOrigin &&
                      origin.ReferenceCategory == InventTransType::SummedUp
            #InventDimExistsJoin(inventTrans.InventDimId,dimJoin,_inventdim,dimParm);
        OK = inventTrans.RecId;
    }
    if (!OK)
    {
        select firstOnly CostAmountAdjustment,CostAmountPosted,Qty from inventTrans
                    order by inventTrans.DateFinancial desc
                where inventTrans.ItemId == this.ItemId &&
                      inventTrans.Qty > 0 &&
                      inventTrans.DateFinancial <= _date
            exists join origin
                where origin.RecId == inventTrans.InventTransOrigin &&
                      ( origin.ReferenceCategory == InventTransType::InventTransaction ||
                        origin.ReferenceCategory == InventTransType::BOMMain)
            #InventDimExistsJoin(inventTrans.InventDimId,dimJoin,_inventdim,dimParm);
    }
    if (inventTrans.Qty != 0)
        return  abs( (inventTrans.CostAmountAdjustment+inventTrans.CostAmountPosted ) / inventTrans.Qty );
    else
        return 0;
}

11 Ocak 2018 Perşembe

AX 2012 - Uyumsoft E-fatura servisini kullanma

Önce Uyumsoft'un WSDL adresine (https://efatura.uyumsoft.com.tr/Services/Integration?wsdl) daha önce bloğumda bahsettiğim şekilde bağlanan bir servis referansı oluşturun. Servis referansındaki App.Config dosyasına aşağıda kırmızı ile işaretlediğim parametreleri ekleyin. Bunları eklemediğiniz takdirde Uyumsoft varsayılan değer olan 65.536'dan büyük bloklar göndereceği için hata alacaksınız:

 <binding name="BasicHttpBinding_IIntegration"  maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">

class SNBuyum
{
    uyumAX.uyumServis.IntegrationClient wsClient;
    uyumAX.uyumServis.PagedQueryContext pq;
    uyumAX.uyumServis.SystemUsersResponse res;
    uyumAX.uyumServis.PagedResponseOfSystemUser s;
    uyumAX.uyumServis.SystemUser[] u;
    uyumAX.uyumServis.SystemUser u2;

    System.ServiceModel.Description.ClientCredentials clientCredentials;
    System.ServiceModel.Security.UserNamePasswordClientCredential userPass;

    EInvoiceTRParameters                param;
}


void getEinvoiceUserList()
{
    CLRObject                             clientType;
    int i,j,k,l;
    System.Exception ex;


    new InteropPermission(InteropKind::ClrInterop).assert();

    param = EInvoiceTRParameters::find();

    clientType = CLRInterop::getType("uyumAX.uyumServis.IntegrationClient");
    wsClient = AifUtil::createServiceClient(clientType,"https://efatura.uyumsoft.com.tr/services/Integration");

    clientCredentials = wsClient.get_ClientCredentials();

    userPass = clientCredentials.get_UserName();
    userPass.set_UserName(param.UserID);
    userPass.set_Password(param.Password);

    pq = new uyumAX.uyumServis.PagedQueryContext();
    pq.set_PageIndex(0);


    try
    {
        res = wsClient.GetEInvoiceUsers(pq);

        s = Res.get_Value();

         i =   s.get_TotalPages();



        info(strfmt("%1",i));
        for (l=1;l<=i;l++)
        {
            pq.set_PageIndex(l);
            res = wsClient.GetEInvoiceUsers(pq);
            s = Res.get_Value();
            U = s.get_Items();
            k = u.get_Count();
            info(strFmt("%1",k));

            for (j=0;j<k;j++)
            {
                u2 = u.get_Item(j);
                info(u2.get_Title());
            }
            break; //sadece ilk sayfayı al ve çık (örnek dökümü hızlı almak için konuldu.)...
        }
    }
    catch(Exception::CLRError)
    {
        ex = ClrInterop::getLastException();
        if (ex != null)
        {
            ex = ex.get_InnerException();
            if (ex != null)
            {
                error(ex.ToString());
            }
        }

    }
}

Yukarıdaki örnek sisteme kayıtlı efatura kullanıcılarının listesini almak içindi. Diğer fonksiyonları da örnekten yararlanarak yapabilirsiniz.

Not: Sizin kodunuzda uyumAX.uyumServis yerine oluşturduğunuz referansa verdiğiniz isme göre farklı bir adres yazmanız gerekebilir.

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();