It annoys me that I keep forgetting it. You have a Code/Text field, usually some sort of identifier f.e. Pallet Number, you put in numeric values like 1, 2, 3, 10, 12.
(But I kept it a Code field instead of Integer because maybe later on they will have alphanumeric identifiers. And this is why I don't just set the SQLDataType to Integer.)
What happens when the customer printes a report that's sorted by this field? It will be sorted like 1, 10, 12, 2, 3 and it's damn hard to fix on a reporting level, it's a hack (temporary tables and all).
The correction solution is fixed-width, left-padded values: 000001, 000002, 000010, 000003, 000012, which will always be sorted correctly.
In other words, if you ever have an identifier field you are not completely sure that it will always be numeric and thus don't dare to make it Integer, but it will be Code, if the field is populated by NAV code, do it the full NAV way and use numbering series. Set up a No. Series Code in some setup table and use NoSeriesMgmt.GetNextNo. It's not hard at all.
If these fields are imported from an external source, just pad them from the left, like:
Function PadInt(Int : Integer; Length : Integer) Text[1024]:
ReturnTxt:=FORMAT(Int);
IF STRLEN(ReturnTxt) = Length THEN EXIT(ReturnTxt);
IF STRLEN(ReturnTxt) > Length THEN BEGIN
ERROR(TxtTooLongError);
END;
IF STRLEN(ReturnTxt) < Length THEN BEGIN
FOR i:=1 TO Length-STRLEN(ReturnTxt) DO BEGIN
ReturnText:= '0' + ReturnTxt;
END;
END;
EXIT(ReturnTxt);
Monday, March 23, 2009
If you put numeric data into a Code/Text field, DO make it fixed-width
Labels:
Microsoft Dynamics-NAV,
Navision
Thursday, April 20, 2006
Implementing Mandatory Fields
Probably this is the feature most missing from Navision. As a record is instantly saved when it's primary key is populated, it's almost impossible to make fields mandatory. There are simply no perfect solutions for this problem. Some of the wrong ones I managed to come up with are discussed here.
Managerial control
Set important fields red. Create a report that shows records that have missing fields, f.e. :
(OnPreSection trigger)
Global variables:
ShowRecord - Boolean
ErrorText: Text - 1024
Global Text Constants:
Text001: "is missing."
Code:
Then tell managers to massacre people with a two-handed battleaxe or with a chainsaw on steroids if this report ever shows any records :-)
Pros: only slight change to standard objects.
Cons: dangerous, because it still does not enforce filling in important data.
Implement a workflow
Set important fields red. Create a new Boolean field called Confirmed. Put all checking in the OnValidate of that field (TESTFIELD this, TESTFIELD that). Tell users to check this boolean field and don't give until they manage it.
For ALL related Orders and Journals, check whether the master data record is Confirmed with a TESTFIELD in the OnValidate trigger.
Typically:
Customers:
Sales Header, Sell-To Customer No., Bill-To Customer No.
General Journal Line, Account No. and Bal. Account No. if Account Type / Bal. Account Type = Customer
Vendors:
Purchase Header, Buy-From Vendor No., Pay-To Vendor No.
General Journal Line, Account No. and Bal. Account No. if Account Type / Bal. Account Type = Vendor
Items:
Sales Line, No. if Type=Item
Purchase Line, No. if Type=Item
Transfer Line, Item No.
Prod. Order Line, Item No.
Prod. Order Component Line, Item No.
Item Journal Line, Item No.
Warehouse Journal Line, Item No.
Warehouse Activity Line, Item No.
Pros: very effective, forbids basically all transactions with a master data record that is not filled perfectly.
Cons: a lot of modification, harder to upgrade.
Create a Wizard
You can also try creating a wizard, where the user enters data in variables and it gets programatically saved when the users pushes a button.
Pros: does not modify standard objects.
Cons: hell of a lot work to do.
Create another kind of wizard
Setup the master data in a way that the No. Series is TEMPCUST0001, TEMPCUST0002... Have the user press a button that performs all the checks and then changes the No. to some other, sane thing.
Pros: it's easy.
Cons: What if so dimwit happily enters an order for TEMPCUST001, without pressing that button?
Create yet another kind of wizard
Save the master data table to another number. The form too. Create a button that performs the validations and then transfers data to the real tables with a simple TRANSFERFIELDS.
Pros: very elegant and simple, and does not modify the standard objects.
Cons: The client needs to buy a lot of tables - and what about subtables (Item Unit of Measure etc.) ?
Use the built-in wizards
Have Customers and Vendors created through Contacts and put all important fields in Customer Templates and Vendor Templates. Have all Items created through Non-Stock Items and add important fields to Nonstock Item Card, and put code to check them and transfer them to Items in Codeunit Nonstock Item Management, function NonStockAutoItem.
Pros: It's easy.
Cons: modifies standard objects. The client needs to buy granules that otherwise might be unnecessary.
Do it the simple way
Pat users on the shoulder and say in a jovial voice: "Don't worry, my friends, it's but a year or two and you will get accustomed to what fields you need to fill..." and THEN prepare to run in zig-zag when they draw a shotgun... :-)
UPDATE: Jacob's idea
Set the form to DelayedInsert. Add checkings to OnInsert.
My opinion: very good, but also add a Save button with an INSERT(TRUE); to avoid confusing users - it might be hard for them to understand why they need to navigate to the next record to insert this one.
Managerial control
Set important fields red. Create a report that shows records that have missing fields, f.e. :
(OnPreSection trigger)
Global variables:
ShowRecord - Boolean
ErrorText: Text - 1024
Global Text Constants:
Text001: "is missing."
Code:
If Description='' then begin
ShowRec:=TRUE;
ErrorText+= FIELDCAPTION(Description) + ' ,';
end;
If "Unit Price"=0 then begin
ShowRec:=TRUE;
ErrorText+= FIELDCAPTION("Unit Price") + ' ';
end;
ErrorText+= Text001;
Then tell managers to massacre people with a two-handed battleaxe or with a chainsaw on steroids if this report ever shows any records :-)
Pros: only slight change to standard objects.
Cons: dangerous, because it still does not enforce filling in important data.
Implement a workflow
Set important fields red. Create a new Boolean field called Confirmed. Put all checking in the OnValidate of that field (TESTFIELD this, TESTFIELD that). Tell users to check this boolean field and don't give until they manage it.
For ALL related Orders and Journals, check whether the master data record is Confirmed with a TESTFIELD in the OnValidate trigger.
Typically:
Customers:
Sales Header, Sell-To Customer No., Bill-To Customer No.
General Journal Line, Account No. and Bal. Account No. if Account Type / Bal. Account Type = Customer
Vendors:
Purchase Header, Buy-From Vendor No., Pay-To Vendor No.
General Journal Line, Account No. and Bal. Account No. if Account Type / Bal. Account Type = Vendor
Items:
Sales Line, No. if Type=Item
Purchase Line, No. if Type=Item
Transfer Line, Item No.
Prod. Order Line, Item No.
Prod. Order Component Line, Item No.
Item Journal Line, Item No.
Warehouse Journal Line, Item No.
Warehouse Activity Line, Item No.
Pros: very effective, forbids basically all transactions with a master data record that is not filled perfectly.
Cons: a lot of modification, harder to upgrade.
Create a Wizard
You can also try creating a wizard, where the user enters data in variables and it gets programatically saved when the users pushes a button.
Pros: does not modify standard objects.
Cons: hell of a lot work to do.
Create another kind of wizard
Setup the master data in a way that the No. Series is TEMPCUST0001, TEMPCUST0002... Have the user press a button that performs all the checks and then changes the No. to some other, sane thing.
Pros: it's easy.
Cons: What if so dimwit happily enters an order for TEMPCUST001, without pressing that button?
Create yet another kind of wizard
Save the master data table to another number. The form too. Create a button that performs the validations and then transfers data to the real tables with a simple TRANSFERFIELDS.
Pros: very elegant and simple, and does not modify the standard objects.
Cons: The client needs to buy a lot of tables - and what about subtables (Item Unit of Measure etc.) ?
Use the built-in wizards
Have Customers and Vendors created through Contacts and put all important fields in Customer Templates and Vendor Templates. Have all Items created through Non-Stock Items and add important fields to Nonstock Item Card, and put code to check them and transfer them to Items in Codeunit Nonstock Item Management, function NonStockAutoItem.
Pros: It's easy.
Cons: modifies standard objects. The client needs to buy granules that otherwise might be unnecessary.
Do it the simple way
Pat users on the shoulder and say in a jovial voice: "Don't worry, my friends, it's but a year or two and you will get accustomed to what fields you need to fill..." and THEN prepare to run in zig-zag when they draw a shotgun... :-)
UPDATE: Jacob's idea
Set the form to DelayedInsert. Add checkings to OnInsert.
My opinion: very good, but also add a Save button with an INSERT(TRUE); to avoid confusing users - it might be hard for them to understand why they need to navigate to the next record to insert this one.
Another Navision usability trick: colors
Sometimes it's hard to define what should an ERP system allow users to do and what not. For example: should it allow users enter Sales Orders for products that are currently not on inventory? It's hard to answer. For some products, yes, for some other, no. For some customers, yes, for some others, not.
If we can't really decide it, then we can simply leave the decision to the users. However, users often forget to check things, and adding pop-up warnings would only annoy them, so we need to find other ways to warn users if they want to do things that are potentically dangerous to the business.
On of them is coloring.
It's very easy to do. For example, in order to color the Description of all those Items in the Item List bold red where the Qty. on Sales Orders is greater or equal than the current Inventory (the total inventory of all Locations), we go to the Item List Form (no. 31), Description field and OnFormat trigger and type:
Very useful trick. Of course it means modifying standard objects - even if you do a Save As, you still need to transfer the change to a new version manually if the Form has changed - but that's not a big problem. Instead of using a text compare tool, you can just write upgrade instructions in the Documentation trigger, like "UPGRADE: copy-paste code in the OnFormat of the Description textbox" and then upgrading will be easy.
Hint: try to avoid more than two coloring on a given form - f.e. a red and a green is OK, but no more - because forms that look like parrots don't exactly improve usability.
Colors can also be used in a static way. For big forms like master data or Orders, users often forget which fields they have to fill. I often set those fields to be filled by Purchasers red on a PO form and those fields to be filled by the warehouse green. It's very useful.
If we can't really decide it, then we can simply leave the decision to the users. However, users often forget to check things, and adding pop-up warnings would only annoy them, so we need to find other ways to warn users if they want to do things that are potentically dangerous to the business.
On of them is coloring.
It's very easy to do. For example, in order to color the Description of all those Items in the Item List bold red where the Qty. on Sales Orders is greater or equal than the current Inventory (the total inventory of all Locations), we go to the Item List Form (no. 31), Description field and OnFormat trigger and type:
CALCFIELDS(Inventory);
CALCFIELDS("Qty. on Sales Order");
IF "Qty. on Sales Order">=Inventory THEN BEGIN
CurrForm.Description.UPDATEFORECOLOR(255);
CurrForm.Description.UPDATEFONTBOLD(TRUE);
END;
Very useful trick. Of course it means modifying standard objects - even if you do a Save As, you still need to transfer the change to a new version manually if the Form has changed - but that's not a big problem. Instead of using a text compare tool, you can just write upgrade instructions in the Documentation trigger, like "UPGRADE: copy-paste code in the OnFormat of the Description textbox" and then upgrading will be easy.
Hint: try to avoid more than two coloring on a given form - f.e. a red and a green is OK, but no more - because forms that look like parrots don't exactly improve usability.
Colors can also be used in a static way. For big forms like master data or Orders, users often forget which fields they have to fill. I often set those fields to be filled by Purchasers red on a PO form and those fields to be filled by the warehouse green. It's very useful.
Improving the usability of Navision
The most annoying fault of all ERP systems I know is that they are not user-friendly enough, the vendors do not put enough effort in improving usability. I mean all the development effort put in creating rich, portal-like user experience is nice, but it would be more important to uphold the most basic, most important rule of software engineering: the computer should not ask questions it could figure out by itself. This was originally called the Macintosh Principle, but is now a generally accepted in the Windows, GTK, KDE etc. communities too.
Let's review now the functionality we developed in the previous post! Our little automatism shows the user the list of all vendors when the user presses F6 on the Vendor No. field. Is that user-friendly? What sense does it make to the user to browser to the list of all vendors and then report an error later if she chooses a wrong one? Shouldn't "the computer" think proactively and show the list of only those vendors, who have items assigned?
Let's see how to accomplish this.
We need to create a new field in the Vendor table, called Has Items Assigned, Type is Boolean, FieldClass is FlowField, with CalcFormula being Exists, Item, No., where Vendor No. = No.
(Won't that make Navision run slower? It won't, because FlowFields are only calculated if they are shown on a Report or Form, CALCFIELDS -ed, or filtered for.)
Then, go to our Report, to the Request Form, and enter the following code to the OnLookUp trigger of our VendorNo textbox:
(Local variables: Vendor - Record - Vendor)
Save it and try it out. Sweet, isn't it?
But we are not yet finished: if the user enters the VendorNo manually instead of choosing from the list, it will still accepts vendors who have no items assigned.
So, we also need to override the validation logic as well. Let's enter this code to the OnValidate trigger of our textbox:
(Local variables: Vendor - Record - Vendor
Local TextConstants: Text001 - Vendor No. %1 has no Items assigned! )
We don't need to throw an error if the Vendor does not exist, as the TableRelation property of our textbox does this automatically.
However, our simple solution has some drawbacks:
There are ways to solve these problems, because it's technically possible to filter for records on forms programatically, but it's quite hard. I won't explain it here, because I already did in the form of a MiBuSo download - get that and and you will see how.
Let's review now the functionality we developed in the previous post! Our little automatism shows the user the list of all vendors when the user presses F6 on the Vendor No. field. Is that user-friendly? What sense does it make to the user to browser to the list of all vendors and then report an error later if she chooses a wrong one? Shouldn't "the computer" think proactively and show the list of only those vendors, who have items assigned?
Let's see how to accomplish this.
We need to create a new field in the Vendor table, called Has Items Assigned, Type is Boolean, FieldClass is FlowField, with CalcFormula being Exists, Item, No., where Vendor No. = No.
(Won't that make Navision run slower? It won't, because FlowFields are only calculated if they are shown on a Report or Form, CALCFIELDS -ed, or filtered for.)
Then, go to our Report, to the Request Form, and enter the following code to the OnLookUp trigger of our VendorNo textbox:
(Local variables: Vendor - Record - Vendor)
CLEAR(Vendor);
Vendor.CALCFIELDS("Has Items Assigned");
Vendor.SETRANGE("Has Items Assigned",TRUE);
IF FORM.RUNMODAL(FORM::"Vendor List",Vendor) = ACTION::LookupOK THEN BEGIN
VendorNo:=Vendor."No.";
END;
Save it and try it out. Sweet, isn't it?
But we are not yet finished: if the user enters the VendorNo manually instead of choosing from the list, it will still accepts vendors who have no items assigned.
So, we also need to override the validation logic as well. Let's enter this code to the OnValidate trigger of our textbox:
(Local variables: Vendor - Record - Vendor
Local TextConstants: Text001 - Vendor No. %1 has no Items assigned! )
CLEAR(Vendor);
IF Vendor.GET(VendorNo) THEN BEGIN
Vendor.CALCFIELDS("Has Items Assigned");
IF NOT Vendor."Has Items Assigned" THEN BEGIN
ERROR(Text001,VendorNo);
END;
END;
We don't need to throw an error if the Vendor does not exist, as the TableRelation property of our textbox does this automatically.
However, our simple solution has some drawbacks:
- We had to modify a standard Navision object as well.
- If users also want to have N:N relation between Vendors and Items through the Item Vendor table, it won't work, as we would need to create a FlowField for that too, and filter for both of them with an OR relation, and that is not possible.
There are ways to solve these problems, because it's technically possible to filter for records on forms programatically, but it's quite hard. I won't explain it here, because I already did in the form of a MiBuSo download - get that and and you will see how.
Sustainable Navision customizations
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?
My opinion 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 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
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):
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:
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.
My opinion 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 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.
Tuesday, April 11, 2006
Navision interfacing and integration guidelines
Lucky is the company, who can solve all business needs in one system. More often than not, Navision will be used for backoffice and some different solution for frontoffice - or vica versa, and we didn't even mention the POS, web shop, GPS tracking and other applications...
The effort required to develop a good interface is often underestimated. One thing is sure: "Oh, this is just a Dataport!" is the surest way to blow it real bad.
Interface development is extremely challenging, because you are trying to do the almost impossible: take apart an integrated system and "simulate" the removed parts - using data from a different database designed to fulfill different requirements...
The first thing to help you is to adopt the "recipient principle". The "recipient principle" says:
While the help of the transmitting party is surely needed, the receiving party is ultimately responsible for the interface to work.
The reason is simple: if you are the receiving party and find out that something went wrong, you can hack it until it gets working. But if you are the transmitting party? Surely you have a lot harder problem.
The "recipient principle" consists of two important parts.
1) Although the transmitting party should be consulted with, interface must always be developed by the recieving party.
2) If possible, all the software code should be written within the limits - the development environment - of the receiving software. The transmitting database should ideally just sit idly while it's records are being read by ODBC/ADO, or if it's not possible, the most it must is to export data to text or XML without doing any transformation except for providing a change log (discussed later).
Using a Perl script or something like that to transform data exported from the transmitting party to the format required by the receiving party might look tempting, but means a high risk - what if you reinstall the server but forget to reinstall the script? And what if your only guy who knew how to hack Perl quits? But if the code is fully within the receiving software, such risks can be avoided, in that case the interface is a firm part of the receiving software and not some kind unreliable external force. And in case Navision is the receiving party, you can be fairly sure that if there is any trouble, you generally need to look no further to solve it than Navision.
Also, you need to decide on the frequency and method of transfering data.
A) The easiest way is a manually initiated transfer - if someone presses a button in Software A, data is exported to a text file, then presses a button in Software B, and it reads the file. While this is easy, a lot of customers don't really want to do this - especially if they need fresh data every 15 minutes or so, like in the case of a POS or a web store.
B) The next possibility is batch transfer - initiated by an automatic scheduler, Software B periodically reads data from software A (usually directly from the database, through ODBC or ADO). A lot better solution than the previous one, but harder to do. I'd generally advise this solution.
C) On-line synchronisation - inserting/modifying/deleting a record in Software A initiates an instant change in Software B. This is clearly the best solution, but the hardest to do - at least until triggers cannot be fired from outside Navision.
The next question is deciding what you need to transfer - master or transactional data.
With master data transfer, the first issue is whether one and only one system can be made solely responsible for maintaing one kind of master data record or not. Generally, it should be, but it's not always possible. Even when it's possible, there are two, equally challenging ways to go. One is on-line synchronization. The other is keeping a change log - what records were inserted, modified and deleted. And if it's not enough, if a system uses visible primary keys, like Navision, which means that a user can chang the No. of a Customer, then you need a fourth kind of event for changing this primary key. (This doesn't even work in Landsteinar Scheduler, so don't expect it to be easy.)
If one system cannot be made solely responsible for maintaining one kind of master data record, things are a lot more complicated, because you need a two-way integration - and even keeping the change log in both systems and put the two together AND decide what you do if one record was modified in one system and also modified in the other one, or modified in one system but deleted in the other one, and if it was deleted in one system maybe the other one still needs it...
This is about the time to give up. I mean this is completely impossible from a sales point of view: who wants to pay for such an outrageous amount of work?
So, I'd suggest either decide one system is responsible for maintaining a given master data record or forget interfacing and maintain both manually (highly risky, although).
Interfacing transactional data is easier because generally transactions are only inserted, and never modified or deleted - at least in Navision. If you encounter any system that does modify transactions, that's sure sign of trouble, because it generally means incompetent design.
So your change log is easier - there are only inserts. Generally, it's enough to record the last entry number of transactions imported in the receiving system and slurp all entries with an entry number higher than that. (One another good reason why using GUIDs for identifiers should be avoided.)
But rejoice not yet, my friend. What does you interface do if there are 100 000 transactions to be imported and 100 are faulty? Will it complain and not import any of the 100 000? That's clearly not the way to go.
An interface either needs to wrap all inserts and postings in a try - catch exception handling, (possible in MS CRM, but in Navision only with separate codeunits, but it won't disclose the reason of the error, so actually it's not possible in a practical way) or duplicate all validations done by the recipient system BEFORE importing... which means a lot of work, of course. In both cases, you need to write an error list to a text file or to a table showing faulty, therefore not imported records AND the reason of the fault...
And if you need a bit more challenge, you need to think about interfacing master data records as well, because missing master data in the receiving system can make importing transactions impossible...
Huh. Interesting, isn't it?
Actually, the technical part is easier. If Navision is the receiving part and you go for B) a codeunit scheduled by Scheduler or Application Server with the Navision Timer OCX, reading another applications database through ODBC/ADO (MDAC, ActiveX Data Objects Library), performing checks, writing an error list to a text file or table, reading transactions to a journal, posting it, and storing the last entry number imported should do the trick. For master data, the same is true for reading the change log.
C) is almost impossible - unless you REALLY want to hack deep with Application Server - and A) is a Dataport or normal text file operatioins.
And if Navision is the transmitting part, leave it all to the other guys :-)
The effort required to develop a good interface is often underestimated. One thing is sure: "Oh, this is just a Dataport!" is the surest way to blow it real bad.
Interface development is extremely challenging, because you are trying to do the almost impossible: take apart an integrated system and "simulate" the removed parts - using data from a different database designed to fulfill different requirements...
The first thing to help you is to adopt the "recipient principle". The "recipient principle" says:
While the help of the transmitting party is surely needed, the receiving party is ultimately responsible for the interface to work.
The reason is simple: if you are the receiving party and find out that something went wrong, you can hack it until it gets working. But if you are the transmitting party? Surely you have a lot harder problem.
The "recipient principle" consists of two important parts.
1) Although the transmitting party should be consulted with, interface must always be developed by the recieving party.
2) If possible, all the software code should be written within the limits - the development environment - of the receiving software. The transmitting database should ideally just sit idly while it's records are being read by ODBC/ADO, or if it's not possible, the most it must is to export data to text or XML without doing any transformation except for providing a change log (discussed later).
Using a Perl script or something like that to transform data exported from the transmitting party to the format required by the receiving party might look tempting, but means a high risk - what if you reinstall the server but forget to reinstall the script? And what if your only guy who knew how to hack Perl quits? But if the code is fully within the receiving software, such risks can be avoided, in that case the interface is a firm part of the receiving software and not some kind unreliable external force. And in case Navision is the receiving party, you can be fairly sure that if there is any trouble, you generally need to look no further to solve it than Navision.
Also, you need to decide on the frequency and method of transfering data.
A) The easiest way is a manually initiated transfer - if someone presses a button in Software A, data is exported to a text file, then presses a button in Software B, and it reads the file. While this is easy, a lot of customers don't really want to do this - especially if they need fresh data every 15 minutes or so, like in the case of a POS or a web store.
B) The next possibility is batch transfer - initiated by an automatic scheduler, Software B periodically reads data from software A (usually directly from the database, through ODBC or ADO). A lot better solution than the previous one, but harder to do. I'd generally advise this solution.
C) On-line synchronisation - inserting/modifying/deleting a record in Software A initiates an instant change in Software B. This is clearly the best solution, but the hardest to do - at least until triggers cannot be fired from outside Navision.
The next question is deciding what you need to transfer - master or transactional data.
With master data transfer, the first issue is whether one and only one system can be made solely responsible for maintaing one kind of master data record or not. Generally, it should be, but it's not always possible. Even when it's possible, there are two, equally challenging ways to go. One is on-line synchronization. The other is keeping a change log - what records were inserted, modified and deleted. And if it's not enough, if a system uses visible primary keys, like Navision, which means that a user can chang the No. of a Customer, then you need a fourth kind of event for changing this primary key. (This doesn't even work in Landsteinar Scheduler, so don't expect it to be easy.)
If one system cannot be made solely responsible for maintaining one kind of master data record, things are a lot more complicated, because you need a two-way integration - and even keeping the change log in both systems and put the two together AND decide what you do if one record was modified in one system and also modified in the other one, or modified in one system but deleted in the other one, and if it was deleted in one system maybe the other one still needs it...
This is about the time to give up. I mean this is completely impossible from a sales point of view: who wants to pay for such an outrageous amount of work?
So, I'd suggest either decide one system is responsible for maintaining a given master data record or forget interfacing and maintain both manually (highly risky, although).
Interfacing transactional data is easier because generally transactions are only inserted, and never modified or deleted - at least in Navision. If you encounter any system that does modify transactions, that's sure sign of trouble, because it generally means incompetent design.
So your change log is easier - there are only inserts. Generally, it's enough to record the last entry number of transactions imported in the receiving system and slurp all entries with an entry number higher than that. (One another good reason why using GUIDs for identifiers should be avoided.)
But rejoice not yet, my friend. What does you interface do if there are 100 000 transactions to be imported and 100 are faulty? Will it complain and not import any of the 100 000? That's clearly not the way to go.
An interface either needs to wrap all inserts and postings in a try - catch exception handling, (possible in MS CRM, but in Navision only with separate codeunits, but it won't disclose the reason of the error, so actually it's not possible in a practical way) or duplicate all validations done by the recipient system BEFORE importing... which means a lot of work, of course. In both cases, you need to write an error list to a text file or to a table showing faulty, therefore not imported records AND the reason of the fault...
And if you need a bit more challenge, you need to think about interfacing master data records as well, because missing master data in the receiving system can make importing transactions impossible...
Huh. Interesting, isn't it?
Actually, the technical part is easier. If Navision is the receiving part and you go for B) a codeunit scheduled by Scheduler or Application Server with the Navision Timer OCX, reading another applications database through ODBC/ADO (MDAC, ActiveX Data Objects Library), performing checks, writing an error list to a text file or table, reading transactions to a journal, posting it, and storing the last entry number imported should do the trick. For master data, the same is true for reading the change log.
C) is almost impossible - unless you REALLY want to hack deep with Application Server - and A) is a Dataport or normal text file operatioins.
And if Navision is the transmitting part, leave it all to the other guys :-)
Thursday, March 23, 2006
Understanding Navision Inventory & Warehouse, Part Two
Possibility II - Using Warehouse Documents
You need warehouse documents if you want to achieve either of the following:
Inventory Pick & Put-Away
Inventory Pick is a document showing multiple Pick Lines from multiple Bins for one Sales (or Transfer etc. ) Order Line. You release a Sales Order (Functions/Release), and then go to Inventory Pick, choose the Source Document, you got the lines, then you can Post it. (You can also directly create the pick on the Functions menu on the Order.)
Inventory Pick implements a one-step picking and shipping process - it automatically posts the Shipment.
You usually want to use it your warehouse picking process is managed on paper - the Warehouse Manager assigs tasks to workers just by printing the picking list and handing it over to them.
Highlights:
If your inventory is not 100% correct, it can mean lot bigger problem than when you are not using warehouse documents. For example, if you want to ship 5 PCS but you physicall have only 4 PCS, and you find it out only during shipping and then modify the Pick and Post it, it will always stay there as open and the order will also not be deleted when fully invoiced.
Solution: a customization that generates a Pick, prints it and deletes it. Then you use the printed Pick to pick the Items, change the quantity on the order if you don't physically have have all the items, and then create the Pick again and post it.
Note that Inventory Put-Away is mostly unnecessary - it assumes you directly receive items to shelves which is not the case, is it?
Just receive goods to a Bin called RCPTAREA directly on a Purchase Order and then you can later on go to the Item Reclass. Journals and use Functions, Calculate Bin and then you can put them away - choose New Bin Code and Post it.
Warehouse Receipt/Put-Away
These functionalities implement a two-level receiving process - first you receive and then you put away with a different document. They provide break-bulk functionality (receive 30 pallets but put away 300 boxes), otherwise, without "Directed Put-Away and Pick" doesn't provide much of a value compared to what I wrote in the previous section. I would advise against them unless you have a really big organization - it's a lot better to keep to those simple functionalities that allow you to Undo a Receipt.
Warehouse Shipment/Pick
These functionalities provide a two-level shipping process. First you create a Warehouse Shipment based on outgoing orders. You can put more than one orders in one shipment and even create saved filters for getting orders quickly Very useful - usually, if you got freight routes (one truck visiting multiple customers) you put all the Orders belonging to that route onto one Warehouse Shipment.
Then, you create a Pick based on the Warehouse Shipment. You assign the Pick to workers. Then they print it, pick the goods and put them on the shipping area of your warehouse, and then Register the Pick. So, these functionalitites allow that the employees notify the warehouse manager not by handing back the printed pick but right in Navision. It can be useful, but only if you have some smart, computer-literate employees in the warehouse...
Then you package the goods and load them to the truck, and when it's ready, you post the Warehouse Shipment. It will automatically post the Sales Shipments too.
Highlights:
Directed Pick and Put-Away
The pinnacle of warehouse management - but often too big and too complicated for smaller companies.
It allows defining put-away rules - so Navision automatically suggest Bins to put goods away.
It also allows Picking by optimal picking routes - represented as Bin Rankings.
It also introduces the concept of Zones - Bins belong to Zones and you can only receive goods to a Receiving Zone etc. It helps in maintaining order and discipline.
However, Zones can make things complicated.
For example, without this granule you can put Items from one Location to another by a simple Item Reclassification Journal - Location Code, New Location Code, Bin Code, New Bin Code and there you go. Very handy for small corrections. With "Directed Pick and Put-Away", you can only do it by creating a Transfer Order, releasing it, picking it, shipping it, then receiving it and putting it away. Not very comfortable...
Also, you cannot make, say, a Negative Adjustment in an Item Journal, because it does not contain a Zone code - you have to use a Warehouse Journal and then go to an Item Journal and pull the data there by "Calculate Whse. Adjustment" and post it again.
So beware of this granule, it can make things really complicated if your warehouse is not working like a Swiss clock.
You need warehouse documents if you want to achieve either of the following:
- Pick one Sales/Transfer Order Line from multiple shelves, pallets etc.
- Create a shipping workflow
Inventory Pick & Put-Away
Inventory Pick is a document showing multiple Pick Lines from multiple Bins for one Sales (or Transfer etc. ) Order Line. You release a Sales Order (Functions/Release), and then go to Inventory Pick, choose the Source Document, you got the lines, then you can Post it. (You can also directly create the pick on the Functions menu on the Order.)
Inventory Pick implements a one-step picking and shipping process - it automatically posts the Shipment.
You usually want to use it your warehouse picking process is managed on paper - the Warehouse Manager assigs tasks to workers just by printing the picking list and handing it over to them.
Highlights:
- It introduces a shipping workflow: salesmen can notify the warehouse that they can start picking an order by releasing it, so they no more need to call them by phone or write an e-mail. It's a good idea to send an e-mail to the warehouse manager when an order gets released (you can implement it by the SMPT OCX on MiBuSo, or on SQL Server with xp_sendmail in a trigger, or with Business Notifications).
- Every order has a different picking document - bulk shipping is only possible by more advanced granules.
- Optimal picking route management is only possible by the more advanced granules.
- Can only ship Items, Resources/Charges not! It can be a problem if you ship products together with services (such as printing an emblem on products).
- Cannot Undo a Shipment anymore!
If your inventory is not 100% correct, it can mean lot bigger problem than when you are not using warehouse documents. For example, if you want to ship 5 PCS but you physicall have only 4 PCS, and you find it out only during shipping and then modify the Pick and Post it, it will always stay there as open and the order will also not be deleted when fully invoiced.
Solution: a customization that generates a Pick, prints it and deletes it. Then you use the printed Pick to pick the Items, change the quantity on the order if you don't physically have have all the items, and then create the Pick again and post it.
Note that Inventory Put-Away is mostly unnecessary - it assumes you directly receive items to shelves which is not the case, is it?
Just receive goods to a Bin called RCPTAREA directly on a Purchase Order and then you can later on go to the Item Reclass. Journals and use Functions, Calculate Bin and then you can put them away - choose New Bin Code and Post it.
Warehouse Receipt/Put-Away
These functionalities implement a two-level receiving process - first you receive and then you put away with a different document. They provide break-bulk functionality (receive 30 pallets but put away 300 boxes), otherwise, without "Directed Put-Away and Pick" doesn't provide much of a value compared to what I wrote in the previous section. I would advise against them unless you have a really big organization - it's a lot better to keep to those simple functionalities that allow you to Undo a Receipt.
Warehouse Shipment/Pick
These functionalities provide a two-level shipping process. First you create a Warehouse Shipment based on outgoing orders. You can put more than one orders in one shipment and even create saved filters for getting orders quickly Very useful - usually, if you got freight routes (one truck visiting multiple customers) you put all the Orders belonging to that route onto one Warehouse Shipment.
Then, you create a Pick based on the Warehouse Shipment. You assign the Pick to workers. Then they print it, pick the goods and put them on the shipping area of your warehouse, and then Register the Pick. So, these functionalitites allow that the employees notify the warehouse manager not by handing back the printed pick but right in Navision. It can be useful, but only if you have some smart, computer-literate employees in the warehouse...
Then you package the goods and load them to the truck, and when it's ready, you post the Warehouse Shipment. It will automatically post the Sales Shipments too.
Highlights:
- Besides the shipping workflow, it introduces the picking workflow - you can assign picks to workers in Navision and they notify you that it's done by registering the Pick.
- Bulk shipment - more than one Order on Shipments.
- Optimal picking route management is only possible by the more advanced granules. ("Directed Pick and Put-Away")
- Can only ship Items, Resources/Charges not!
- Cannot Undo a Shipment/Pick anymore.
- Incorrect Inventory causes the same problems as before.
Directed Pick and Put-Away
The pinnacle of warehouse management - but often too big and too complicated for smaller companies.
It allows defining put-away rules - so Navision automatically suggest Bins to put goods away.
It also allows Picking by optimal picking routes - represented as Bin Rankings.
It also introduces the concept of Zones - Bins belong to Zones and you can only receive goods to a Receiving Zone etc. It helps in maintaining order and discipline.
However, Zones can make things complicated.
For example, without this granule you can put Items from one Location to another by a simple Item Reclassification Journal - Location Code, New Location Code, Bin Code, New Bin Code and there you go. Very handy for small corrections. With "Directed Pick and Put-Away", you can only do it by creating a Transfer Order, releasing it, picking it, shipping it, then receiving it and putting it away. Not very comfortable...
Also, you cannot make, say, a Negative Adjustment in an Item Journal, because it does not contain a Zone code - you have to use a Warehouse Journal and then go to an Item Journal and pull the data there by "Calculate Whse. Adjustment" and post it again.
So beware of this granule, it can make things really complicated if your warehouse is not working like a Swiss clock.
Wednesday, March 22, 2006
Understanding Navision Inventory and Warehouse, Part One
Navision provides many different levels of Inventory/Warehousing functions, which can be confusing, so let's make them clear.
General limitations - what you cannot do
1. Strictly order-based
To make a Purchase Receipt or Sales Shipment, you need to write an order and ship/receive it based on the order - no way to receive something and then later on renconcile it with a Purchase Order later. So insist that your vendors print Order No. on Shipment Notes.
2. Expects exact deliveries
You cannot ship or receive more quantity than was ordered.
On one hand it can be justified: to ship more, you need to decide whether you will invoice more or not. If yes, then you need to talk with the customer, and if he agrees it means the order is modified so you modify the quantity on the order. On the other hand, if it won't be invoiced, then from a financial viewpoint it's not a shipment, but a negative adjustment. And Navision is thinking in strictly financial terms - remember, a few years back it was called Navision Financials and even an FDB file extension means Financials Database... so don't expect it to think in a rather operations-oriented way.
So in some way it does make sense, but it makes life hard for weight-based shipping, f.e. the customer order 3000 kg or fertilizers, you pour it on the truck, drive on the platform balance, and you see it's 3001 kg then you either shovel 1 kg down or call the sales dept to modify the order because a warehouse guy is usually not authorized to do that. Solution: a customization that allows, say, 1% modification of the quantity on the order for the warehouse guys.
If you ship or receive less than was ordered, that's also a problem, although of a smaller scale: unless you modify the order, it will forever stay as demand/supply in you Inventory Availability. (And othe order won't be deleted upon invoicing.) So if you order 1000 but receive 500 and don't modify the quantity on the purchase order, a careless salesman might reserve it on a sales order and promise the delivery to the customer, which can be a problem.
Solution: a customization that allows "closing" an order: sets Outstanding Quantity and Outstanding Amount to zero and makes the order read-only of course.
3. Mandatory Accounting
Even if you don't use Navision fog G/L Accounting, you have to set up G/L Accounts for Locations and Inventory Posting Groups, no way to avoid that. Look at the bright side: by setting up a "fake" Chart of Accounts, you can see your inventory value even though you don't actually use Navision for G/L accounting. That's useful, isn't it?
4. Limited levels of Item Tracking
Although you can set up Item Tracking to don't allow shipping Items from expired Lots, but FEFO-based picking is not supported by standard.
There are two levels of tracking: Lot No. and Serial No. Now, for example in a Food & Agro company you need to track shipments by individual bags of food, then boxes, then pallets and then trucks. It's four levels, which is not supported by standard.
5. No functionality for advanced logistics such as handling returnable packagings, automatic consumption of packing materials, managing freight routes etc.
All such functionality needs to be custom-developed or a suitable add-on found.
Now let's look at the features offered.
Possibility I - Ship/Receive directly on orders
If you buy just the usual granules, you can ship/receive directly on sales/purchase/transfer orders. You can do it on three levels:
A) Without Locations.
To put it short: don't do that. Always tick "Location Mandatory". If you have just one Location, you can set it up in the Company Information, and from that on it will be automatic almost everywhere. Of course it means you have to buy Multiple Locations granule.
B) Using Locations, but not Bins.
The important thing is that the Locations functionality in Navision is different from the usual "Warehouse Code" in other ERP systems: a Location is generally meant to represent a site, not a storage room within a site. Automatic purchase/transfer by Requisition Worksheets and automatic manufacturing MPS/MRP by Planning Worksheets is strictly Locations-based, so if you set up multiple Locations withint a site, then supply won't meet demand and you are in trouble.
So if you ever plan to use these functionality you should set up the major materials/shop floor/finished productswarehouses in one Location at a site, and you can further divide it by Bins by the Bins granule or by Zones as Bins by the "Directed put-away and pick" granule. Of course those warehouses that do not partake in such calculations, such as a returns or scrap or service warehouse can be a different Location.
Location Code inheritance flow: Company Information or Customer/Vendor Card -> Order Headers -> Order Lines. You can change it at any of these places. You can also bind a Location to employees or business streams by assigning them to a Responsibility Center and the RC to a Location but it won't exactly forbid the selling from the wrong Location if they truly want.
You cannot assign Locations to Items.
You cannot limit user rights to Locations without custom development or using the more advanced warehousing granules. (For a customization, just make a new table to assign Users to Locations andone error message to Codeunit Item Jnl.-Post Line if they try to post an Item Journal Line with a wrong Location. All shipping and receiving functionality does use this codeunit so it's a good place to put this error message in.)
C) Using Bins.
Bins is a great feature - available from version 3.7 and up. It allows you to further divide a Location. You can use it to represent rooms or you can use it to represent shelves or pallets or basically anything from gas tanks to trucks or even workers on a shop floor who have individual inventory assigned.
Bins can be set as standard for an Item (the first receipt will set it automatically) or manually
choosed in the order line. However, if you ship on orders (no Picking granules), if a Bin represents a Finished Products
Warehouse, that's fine. However if it represents a shelf within the warehouse then you can be in trouble as one order line might need Items from multiple shelves - exactly this is when you need some or other of the Picking granules.
For Purchasing, there is no such problem - you don't usually receive items directly to shelves, don't you? So you can receive to a RCPTAREA bin and use Get Bin Content function on Item Reclass. Journals to get all the inventory of RCPTAREA and put it away to manually assigned Bin. You cannot filter for a Receipt No. but you can filter for a Vendor No. which should generally be enough.
Advantages of shipping/receiving directly on orders
- It's cheap on granules
- You can ship services (Resources, Item Charges) together with Items- All Shipment/Receiptmay be undone by going to the Shipment/Receipt, selecting lines and Functions, Undo Receipt/Shipment. Very important, especially for Receipts!
- It's easy to change the ordered quantity if needed (more is shipped/received) - if something is incorrect but you still want the order to be finished and deleted.
Disadvantages of shipping/receiving directly on orders:
Shipping from multiple shelves is out of question- Ship and receive orders one-by-one, not in a batch
- Warehouse guys might find Sales/Purchase Order forms too confusing and they can see data they are
not authorized to see (like prices), however, Ctrl+F2, Save As and feel free to dumb order forms down
for the warehouse :-) (Note that if you really want to hide information, disable Zooming in User
Rights) - Limiting user rights to Locations needs customization.
I will continue in the next part of this essay with the more advanced warehousing functionalities.
Saturday, March 18, 2006
Building a succesful Navision team
Most ERP implementor companies tend to separate and specialise their implementation consultants both horizontally and vertically. Horizontal separation means specialising for a module, or a set of modules, or in Navision terms, a set of application areas, while vertical separation means specialising in being either as a "business consultant" and a "technical consultant" (or developer).
Well, if you've got a big team it *might* work (although don't bet on it) but it certainly won't for smaller teams with a headcount less than five. The reason why it won't work is the "Navision pyramid", which I will explain in a later post, now I just put it short:
- Nowadays SMB clients are not very interested in financials, they usually buy ERP to improve their day-to-day operations, therefore operations advantage is the key selling point.
- The implementation of Navision's logistics/operations application areas need to be done a lot more differently than the "traditional" G/L, financials and accounting analysis & controlling application areas.
Teambuilding in the Navision way
Let's first make some terms clear:
A consultant knows a software only from the GUI and spends most of it's time working from the business perspective.
A developer knows a software deep and well but does not always understands business requirements and generally works from the technical perspective.
An expert knows a software both from the user's viewpoint and down to source code level, both understands business requirements and how to satisfy, and spends approximately 50% of his time working from the business perspective and 50% of his time hacking Navision.
So, based on these definitions, I'd like to suggest you a diagonal separation of concerns: Navision financial consultants and Navision operations/logistics experts.
As customer requirements tend to be fairly standard in the financial application areas, therefore usually only a few customization (in the sense of object modification) is needed, and as this is the oldest, most mature and mostly bug-free application area of Navision, those who work these application areas can be traditional (business) consultants, because their job is basically the same as any typical ERP consultant.
Sales, Purchase, Inventory, Warehouse, Manufacturing, Service, Resource Management application areas - in short: operations/logistics - differ a lot. First, customer requirements can vary a lot, and usually you either need an add-on or lot of custom development to satisfy it.
Second, these application areas - save for Service, maybe - well, are not exactly rock-solid. You need to correct bugs (cost adjustment, reservations...) and design errors and develop new functionality. What's more important, if you don't understand it down to source code level, you might easily make wrong assumptions during the system design phase on how it works as the documentation does not really go in the details and does not explain every possible combination.
For example, if you set up many Locations for one physical location, site, building, whatever, for example three Locations for materials warehouses, a Location production shop floor, and two finished products Locations, then you might get into big trouble running Requsition Worksheets for automatic stock reordering and/or mfg. Planning Worksheets for MPS/MRP as they work *strictly* on a per-Location way! Therefore demand will not meet the supply and these functionalities will become simply useless, which could even make the whole project fall. (OK, actually, not, but cause a lot of headache.) And don't even try to customize the mathematical engine (codeunit Inventory Profile Offsetting) as it is extremely complicated. It's a lot better practice to use one Location for one physical site and use Bins (or Zones if you purchase the full WMS granules) for separating it into different warehouses.
Reading the documentation and clicking around on the GUI won't warn you of such potentatial pitfalls. The only way to find them out is to study table definitions and Navision program code.
So, those consultants who work with the operations application areas need to understand Navision down to a source code level, just like a developer, this is why the term "expert" describe this job better than the term "consultant".
Experts instead of of only-consultants and only-developers
On the other hand, you don't need dedicated developers who can only program and do not consult. As 90% of the development tends to be in the operations/logistics application areas, aforementioned experts can handle it themselves. If you make consultants who don't understand C/AL code formally specifiy customization needs to developers who don't understand business procedures, the situation can easily evolve into a series of tragic misunderstanding. So if you separate these two roles then you will waste an amazing amount of time on communicaton, testing, and change management.
When an operations/logistics expert can customize a given function in a X days, because he knows
- what needs to be done
- how does it work by standard
- how can one change it,
if you have only-consultants and only-developers, then the consultant will know 1) and some parts of 2), the developer will know 3) and some parts of 2), and they will spend a 3X-10X days until it finally becomes solid. Do think about that. This is the ultimate difference between a profitable project and a loss-making one.
Hiring and training experts
What is the ideal background for a Navision operations expert? Preferably, both a business and programming background. However, I have seen programmers without any previous business experience pick up the concept of operations business procedures quite quickly, and I have also seen business-oriented consultants turning into professional Navision "hackers". The reason is simple: consultancy in operations tends to be simpler than in financials, and understanding Navision programming is also very easy, something like a simple scripting language which almost directly translates into business rules. Navision programming is rather like algorythmical configuration than real software engineering. One can learn it without any programming background. So both business and programming backgrounds are acceptable, I think.
The really important thing is dedication - there is no way to save the countless nights spent on debugging Adjust Costs - Item Entries for example...
The optimal ratio
For every one Navision financial consultants, you should have two or three Navision operations/logistics experts - there is a lot more of this kind of work out there... Especially that on your first three projects you will spend 40% of your time on finding out why something does not work as expected. And the same ratio goes for estimating consultant days on a typical project...
(Of course, if your customers are multinational companies who eat half dozen Accounting Schedules for breakfast then you can invert the ratio - I'm talking about the typical SMB market...)
Subscribe to:
Posts (Atom)