One of the hardest decisions during a Navision implementation project is whether to create a really good solution through a lot of customization, thereby rendering Navision hard to support and even harder to upgrade, or stick to the skeletal functionality provided by the standard system?

Our school of that  is that while customization is often unavoidable, it’s a lot better idea to automate Navision instead of customizing it: develop new functionality separately, which automate processes otherwise done manually by users.

I will give you a typical example: the customer wants a Purchase Order autogenerated for all Items shipped by a given Vendor.

You don’t have to modify the PO form or table to achieve it. You can either

  • Create a new form and write code behind a button (either directly or calling a new codeunit).
  • Or write code into a processing-only report.

If you are a Navision customer and bought the Report/Dataport Designer granule, you can write code in reports (and dataports). This is very important. You cannot write code to forms, tables, codeunits without buying the expensive development granules, but in reports you can!

So, if you are a customer, you can even do it yourself.

Let’s assume now that every Item has only one Vendor, so Items are assigned to Vendors only through the Vendor No. field on the Item Card.

Let’s see how to develop this useful automatism!

  1. Go to Object Designer, create a new blank report, without any dataitems.
  2. Set Properties to Processing Only.
  3. Create the following global variable: VendorNo, which is Code20.
  4. Go to the Request Form designer (View menu), add a TextBox with a Label.
  5. Set the CaptionML property of the TextBox to Vendor No. in ENU and whatever it is called in your language , set SourceExpression property to VendorNo, and TableRelation property to Vendor.”No.”
  6. Close the Request Form and go to the OnPreReport trigger of the report.
  7. Create the following local variables (View Menu):
  • Item – Record – Item
  • PurchaseHeader – Record – Purchase Header
  • PurchaseLine – Record – Purchase Line
  • LineNo – Integer
  • Window – Dialog

8. Create the following local Text Constants (it’s just besides where local variables are) in ENU and in your language:
Text001: “Vendor No.: %1 has no Items assigned!”
Text002: “Processing Item:”

9. Type the following code into OnPreReport:

CLEAR(Item);
CLEAR(PurchaseHeader);
CLEAR(PurchaseLine);

Item.SETCURRENTKEY("Vendor No.");
Item.SETRANGE("Vendor No.",VendorNo);
IF Item.COUNT=0 THEN BEGIN
  ERROR(Text001,VendorNo);
END;

PurchaseHeader.VALIDATE("Document Type",PurchaseHeader."Document Type"::Order);
PurchaseHeader.INSERT(TRUE);
PurchaseHeader.VALIDATE("Buy-from Vendor No.",VendorNo);
PurchaseHeader.MODIFY(TRUE);

Window.OPEN(Text002+'#1##################################');
IF Item.FIND('-') THEN REPEAT
  Window.UPDATE(1,Item."No.");
  LineNo := LineNo + 10000;
  CLEAR(PurchaseLine);
  PurchaseLine.VALIDATE("Document Type",PurchaseHeader."Document Type");
  PurchaseLine.VALIDATE("Document No.",PurchaseHeader."No.");
  PurchaseLine.VALIDATE("Line No.",LineNo);
  PurchaseLine.INSERT(TRUE);
  PurchaseLine.VALIDATE(Type,PurchaseLine.Type::Item);
  PurchaseLine.VALIDATE("No.",Item."No.");
  PurchaseLine.MODIFY(TRUE);
UNTIL Item.NEXT=0;
Window.CLOSE;
COMMIT;
FORM.RUNMODAL(FORM::"Purchase Order",PurchaseHeader);

10. Save the report, run it, choose Vendor No. 10000 (if you are in a CRONUS database), press OK, and enjoy!

This way you managed to fulfill a customer requirement without hacking into Navision.

Let’s analyse that code a bit to make sure we understand it correctly.

The first block is about reseting all variables – not exactly necessary here,
but it’s good to make it a habit, you can avoid some very nasty bugs this way.

Then we tell the Item record variable to use the Vendor No. key, because we will filter for that field and this way filtering is a lot faster. (Homework: read a bit about how ISAM databases work.)

Then we filter for it, and check whether the filter contains any record – does the Vendor have any Items assigned. If not, we complain to the user.

The next block is very interesting and needs some explanation. The key to painless Navision development is to emulate exactly what the end-user does manually. What happens when the user presses F3 on a Purchase Order?

The primary key of the Purchase Header table is Document Type, No. When the user presses F3, Document Type is autofilled. Then when he leaves the No. field without entering data, the record is inserted. The code in the OnInsert trigger in the Purchase Header table sees that the No. is empty and therefore it needs to pull a new number from the associated No. Series. Then when user chooses the Buy-From Vendor No., it’s a modification of an already inserted record. The code in the OnValidate trigger of this field in the table happily populates Vendor Name, Address, Payment Method etc.

So in this block we are exactly emulating what the user is doing on the user interface.

Then we open a dialog. The reason it’s necessary is that if we don’t do that, Navision would look frozen during it processes the items. So it’s a kind of courtesy towards the user.

Then we insert the lines with a similar logic than before.

Then comes another interesting part. We run the Purchase Order form to show the user the order that got recently created. For some obscure reason, FORM.RUN does not work from reports – I guess reports are modal, or something like that – but no problem, FORM.RUNMODAL works nice. However, RUNMODAL requires that we explicitly save the changes we’ve done to the database by issuing a COMMIT. It’s usually not necessary for such small developments as Navision automatically issues a COMMIT whenever our code completed running, but RUNMODAL wants it explicit, so – *shrug* – let it have it.

Thanks to Miklos Holender for this post.