web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Small and medium business | Business Central, N...
Suggested Answer

Creating invoice with AL in Business Central

(0) ShareShare
ReportReport
Posted on by 42

I have been given task to create an API (unbound code unit) that will create and post an invoice. I have a bit of experience NAV over the years but not much code development and am new to AL. 

For the most part it works, however I assumed it would populate a lot of the posting and tax fields and calculate totals based on customer passed in.

I noticed there are a lot of functions such as CalcLineAmount and CalcFields that can help with it but I am looking for an existing more complete example - have searched for AL and tried to find older C/AL examples since they seem relevant. 

What would be really great is to find the source for the Application Test Toolkit , it seems to have a lot of routines in LibrarySales to do this such as CreateSalesDocument and CreateSalesLine etc.  

Below is a snippet of the main section to create the invoice - a customer is retrieved based on a Customer Business Relation linked to a particular contact and an invoice is built:

                Cust.RESET();
                Cust.SETRANGE("No.", CBR."No.");
                Cust.FindFirst();

                SalesHeader.Init();
                SalesHeader."Sell-to Customer No." := CBR."No.";
                SalesHeader."Bill-to Customer No." := CBR."No.";
                SalesHeader."Document Date" := Today();
                SalesHeader."Due Date" := Today();
                SalesHeader."Document Type" := SalesHeader."Document Type"::Invoice;
                SalesHeader."External Document No." := referenceNo;

                //
                SalesHeader."Gen. Bus. Posting Group" := Cust."Gen. Bus. Posting Group";
                SalesHeader."Customer Posting Group" := Cust."Customer Posting Group";
                SalesHeader.Insert(True);
                SalesLine.Init();

                SalesLine."Document Type" := SalesLine."Document Type"::Invoice;
                SalesLine."Document No." := SalesHeader."No.";

                //set GL account 
                SalesLine.Type := SalesLine.Type::"G/L Account";
                SalesLine."No." := '1001';
                SalesLine."Line No." := 10000;

                SalesLine."Unit of Measure Code" := 'PCS';

                SalesLine.Quantity := 1;
                SalesLine."Qty. to Ship" := SalesLine.Quantity;
                SalesLine."Qty. to Invoice" := SalesLine.Quantity;
                SalesLine."Qty. Shipped Not Invoiced" := 0;

                SalesLine."Unit Price" := amount;

                //
                SalesLine."Gen. Bus. Posting Group" := Cust."Gen. Bus. Posting Group";
                SalesLine."Gen. Prod. Posting Group" := 'SERVICES'; //Cust."Customer Posting Group"; //'SERVICES';

                SalesLine."VAT Bus. Posting Group" := Cust."VAT Bus. Posting Group";
                SalesLine."VAT Prod. Posting Group" := 'GST10';

                SalesLine."Line Amount" := SalesLine.CalcLineAmount();
                SalesLine.Insert(True);
                SalesHeader.CalcFields(Amount, "Amount Including VAT");
                Codeunit.Run(Codeunit::"Sales-Post", SalesHeader);

I have the same question (0)
  • Lars Lohndorf-Larsen Profile Picture
    on at

    Hi Xmeister,

    First of all I notice that the code is doing assign-equal := for everything, whicih indeed will not call any of the existing business logic. To reuse existing logic, use VALIDATE instead, so instead of this:

    SalesHeader."Sell-to Customer No." := CBR."No.";

    change it to this:

    SalesHeader.VALIDATE("Sell-to Customer No.",CBR."No.");

    You could do that for every field where there is code on the field's OnValidate trigger.

    I would do it manually step by step, and then repeat that is happening in code. Notice that page behaviour is that when you insert a new record, it places the cursor in a primary key field, and as soon as you leave this field the page will call INSERT. In Code to call the trigger, add TRUE, for example INSERT(TRUE). In the case of a Sales Header, then the OnInsert trigger then gets a number from the No. Series etc. Then the user will fill in other fields, and then the record gets updated (MODIFY'ed). This code is all you need to create a Sales Order - but of course you can then add fields that you also need to assign:

            SalesHeader.init;
            SalesHeader."Document Type" := SalesHeader."Document Type"::Order;
            SalesHeader.Insert(true);
            SalesHeader.validate("Sell-to Customer No.", '10000');
            SalesHeader.Modify;
            Message(SalesHeader."No.");

    This will create this order:

    pastedimage1593427895122v1.png

    Does this help you forward?

  • Suggested answer
    Xmeister Profile Picture
    42 on at

    Yes, thanks, much better. 

    Following works, populates all underlying related properties. 

    I needed to add the following to update the totals after creating line item - the following CalcFields seemed to do the job - but worked without a SalesHeader.Modify() - is this expected? 
    SalesHeader.CalcFields(Amount, "Amount Including VAT");

    SalesHeader.Init();
    SalesHeader.Validate("Sell-to Customer No.", "CT00001");
    SalesHeader.Validate("Document Date", Today());
    
    SalesHeader."Document Type" := SalesHeader."Document Type"::Invoice;
    SalesHeader.Insert(True);
    
    SalesLine.Init();
    
    SalesLine."Document Type" := SalesLine."Document Type"::Invoice;
    SalesLine."Document No." := SalesHeader."No.";
    
    //set GL account 
    SalesLine.Type := SalesLine.Type::"G/L Account";
    SalesLine."Line No." := 10000;
    SalesLine.Validate("No.", '1001');
    
    SalesLine.Validate(Quantity, 1);
    SalesLine.Validate("Unit Price", 1000);
    SalesLine.Insert(True);
    
    //calculate header totals
    SalesHeader.CalcFields(Amount, "Amount Including VAT");
    
    Codeunit.Run(Codeunit::"Sales-Post", SalesHeader);

  • Lars Lohndorf-Larsen Profile Picture
    on at

    Hi,

    Without analyzing it indepth I would say yes that would be expected because you pass on the SalesHeader record to the posting routine. And what gets posted is that record, as opposed to re-reading the record from the database. I hope that makes sense?

  • Suggested answer
    tanya07 Profile Picture
    1,638 on at

    Hi Xmeister ,

    This set of code I wrote for the same situation as yours :-

    SalesHeaderG.INIT;
        SalesHeaderG."Document Type" := SalesHeaderG."Document Type"::Order;
        SalesHeaderG.INSERT(TRUE);
        CustomerL.GET("Customer No.");
        SalesHeaderG.VALIDATE("Sell-to Customer No.","Customer No.");
        SalesHeaderG.MODIFY(TRUE);
    
        SalesLineG.RESET;
        SalesLineG.SETRANGE("Document No.",SalesHeaderG."No.");
        IF SalesLineG.FINDLAST THEN
            LineNoL := SalesLineG."Line No."   10000
        ELSE
            LineNoL := 10000;
        SalesLineG.INIT;
        SalesLineG."Document Type" := SalesLineG."Document Type"::Order;
        SalesLineG."Document No." := SalesHeaderG."No.";
        SalesLineG.Type := SalesLineG.Type::Item;
    
        SalesLineG."Line No." := LineNoL;
        SalesLineG.Description := ItemL.Description;
        SalesLineG.VALIDATE(Quantity,1);
        SalesLineG.INSERT(TRUE);

    Hope this helps , thanks :)

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > Small and medium business | Business Central, NAV, RMS

#1
OussamaSabbouh Profile Picture

OussamaSabbouh 3,143

#2
Jainam M. Kothari Profile Picture

Jainam M. Kothari 1,694 Super User 2025 Season 2

#3
YUN ZHU Profile Picture

YUN ZHU 1,067 Super User 2025 Season 2

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans