Question

It seems that the underlying nature of NAV is to resist requiring the populating of a field to be mandatory. In the case of our business logic, certain fields must be populated in order for the data to be valid. For example, a customer record must have at least a name and phone number. I've searched a number of places but have not found a suitable solution. So how can this be accomplished?

Was it helpful?

Solution

After struggling to find a succinct way to require certain fields on a card to be populated, I have come up with the following and it (so far) is working for me. I started to sense that NAV was not meant to have mandatory fields, but I need them for our business logic. Anyways, here we go...

Step One: - We have a codeunit for various validation logic, in which I've added the function to read a custom table listing the tables and their fields that are mandatory. This function takes the table number, key field, and a "create mode". It returns a text "completion status" value. I find the table for the record I am validating. I loop through the mandatory fields, if the field is not populated, I add it to a list of incomplete fields. If the list of incomplete fields is empty, the completion status is "done". If the list of incomplete fields is populated, a message is displayed indicating the missing fields and allows the user an option to cancel the create of a new record or to stay on the (new or existing) record and enter the missing data, and the completion status is set to "delete" to cancel a create, or "return" to stay on the record. Logic follows:

CheckMadatoryFields(TableNumber : Integer;KeyField : Code[10];CreateMode : Boolean)   Completion Status : Text[30]

// Read the 'LockoutFields' table to find the manditory fields for the table passed in.
LockoutFields.RESET;
LockoutFields.SETRANGE("Table No.", TableNumber);
LockoutFields.SETFILTER("Filter ID", 'MANDITORY_FIELD');

// Get a record reference for the table passed in
RecRef.OPEN(TableNumber);
RecRef.SETVIEW('WHERE("No." = FILTER(' + KeyField + '))');

// Set this to done, i.e. data is complete (don't delete by mistake).
CompletionStatus := 'done';

IF RecRef.FINDFIRST THEN BEGIN

// Check the record's manditory field(s) listed in the 'LockoutFields' table to see if they're blank.
  IF LockoutFields.FINDSET THEN BEGIN
    REPEAT
    FldRef := RecRef.FIELD(LockoutFields."Field No.");

    IF FORMAT(FldRef.VALUE) = '' THEN
      FldList := FldList + ' - ' + FldRef.CAPTION + '\';

  UNTIL LockoutFields.NEXT = 0;

END;

IF FldList <> '' THEN BEGIN
  // If creating the record, add the 'Cancel Create' message, otherwise don't.
  IF CreateMode THEN
    DeleteRecord := CONFIRM(Text_ManditoryField + '\' + FldList + '\' + Text_CancelCreate, FALSE)
  ELSE BEGIN
    DeleteRecord := FALSE;
    MESSAGE(Text_ManditoryField + '\' + FldList, FALSE);
  END;

  // Return a 'delete' status when deleting, or a 'return' status to stay on the record.
  IF DeleteRecord THEN
    CompletionStatus := 'delete'
  ELSE
    CompletionStatus := 'return';
  END;
END;

RecRef.CLOSE;`

Step 2: - On the card you want to check for mandatory fields, in my case the Customer card, I added a function to call the validation function in the codeunit described above. I also added logic to the OnQueryClosePage trigger to call my local function. This all worked fine, as the user could not close the Customer card without completing the mandatory fields or cancelling the create of the customer, except if the user were to use Ctrl+PgUp or Ctrl+PgDn, which took them off the record. The trick was putting the the correct logic in the OnNextRecord trigger so that the validation was executed and the Ctrl+PgUp or Ctrl+PgDn still functioned (note: I found this bit somewhere on mibuso, many thanks!). Logic follows:

OnNextRecord(...)
IF CheckManditoryFields = TRUE THEN BEGIN
  Customer := Rec;
  CurrentSteps := Customer.NEXT(Steps);
  IF CurrentSteps <> 0 THEN
    Rec := Customer;
  EXIT(CurrentSteps);
END;

OnQueryClosePage(...)
EXIT(CheckManditoryFields);

CheckMandatoryFields() ExitValue : Boolean
// Check for manditory fields on this table.  If there are missing manditory
// fields, the user can cancel this create, in which case, a 'TRUE' value is
// returned and we'll delete this record.

ExitValue := TRUE;

IF Rec."No." <> '' THEN BEGIN  // This is blank if user quits immediately after page starts.
  CompletionStatus := HHValidation.CheckManditoryFields(18,Rec."No.",CreateMode);

  IF (CompletionStatus = 'delete')
  AND (CreateMode = TRUE) THEN  // User cancelled create (not edit), delete the record and exit.
    Rec.DELETE(TRUE)
  ELSE
    IF CompletionStatus = 'done' THEN  // User completed manditory fields, OK to exit.
      ExitValue := TRUE
    ELSE
      ExitValue := FALSE;  //User did not complete manditory fields and wants to return and add them.
END;

I think that's about it. The details of the custom table are really up to how you want to code it. The validation code unit can be what you want it to be. Using a table allows the mandatory fields to added or removed without changing any logic, and this "generic" validation logic could be put on any page. The key is the two triggers on the card and having a common validation routine to call.

OTHER TIPS

I used this code on a form (classic client) but there is one error in the OnNextRecord code.

OnNextRecord(...)
IF CheckManditoryFields = TRUE THEN BEGIN
  Customer := Rec;
  CurrentSteps := Customer.NEXT(Steps);
  IF CurrentSteps <> 0 THEN
    Rec := Customer;
  EXIT(CurrentSteps);
END;

You should also cover the situation where CheckMandatoryFields returns FALSE. Otherwise when I request the next record it shows me an empty record. It should be like this:

OnNextRecord(...)
IF CheckManditoryFields = TRUE THEN BEGIN
  Customer := Rec;
  CurrentSteps := Customer.NEXT(Steps);
  IF CurrentSteps <> 0 THEN
    Rec := Customer;
  EXIT(CurrentSteps);
END ELSE
  EXIT(Steps);

There is another error in the OnNextRecord function:


    OnNextRecord(...)
    IF CheckManditoryFields = TRUE THEN BEGIN
      Customer := Rec;
      CurrentSteps := Customer.NEXT(Steps);
      IF CurrentSteps  0 THEN
        Rec := Customer;
      EXIT(CurrentSteps);
    END ELSE
      EXIT(Steps);

The filters set on the sourcetable of the page (or form) are not copied to the record on which the steps are taken. So you can navigate to a record that is not in your filterset.

Instead assigning the Rec to the Customer, you should copy it:


    OnNextRecord(...)
    IF CheckManditoryFields THEN BEGIN
      Customer.COPY(Rec);
      CurrentSteps := Customer.NEXT(Steps);
      IF CurrentSteps  0 THEN
        Rec := Customer;
      EXIT(CurrentSteps);
    END ELSE
      EXIT(Steps);

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top