Changing jobs

In this chapter, we have used some changes to the Job functionality in order to make it work for CRONUS International Ltd. to sell Microsoft Dynamics NAV.

Quantity budgeting

For some companies, it is very important to know the total number of hours required for a job and the number of hours used rather than the exact amounts.

For this, we have created new flow fields in the Job Task table:

Quantity budgeting

The flow field definition is quite special.

Sum("Job Planning Line"."Quantity (Base)" 
  WHERE (Job No. = FIELD(Job No.),
         Job Task No. = FIELD(Job Task No.),
         Job Task No. = FIELD(FILTER(Totaling)),
         Contract Line = CONST(Yes),
         Planning Date = FIELD(Planning Date Filter)))
Quantity budgeting

The Totaling field is for the lines of type End-Total. The ValueIsFilter property ensures that the field will be interpreted as a filter instead of a value.

The result is visible in the Job Task page (1002).

Quantity budgeting

Result of ValueIsFilter property

Resource Groups

For scheduling, we have implemented the possibility of using Resource Groups in the Job Planning Lines as well as Calculations. This is done by adding two new fields, Add-on Type and Add-on No.:

Resource Groups

These fields replace the standard Type and No. fields on the pages allowing users to select these new options. The caption of the new fields matches the replacement fields.

Add-on No. - OnValidate()
CASE "Add-on Type" OF
  "Add-on Type"::Resource, "Add-on Type"::Item, "Add-on Type"::"G/L Account", "Add-on Type"::Text:
    BEGIN
      VALIDATE(Type, "Add-on Type");
      VALIDATE("No.", "Add-on No.");
    END;
  "Add-on Type"::"Resource Group":
    BEGIN
      TESTFIELD("Line Type", "Line Type"::Schedule);
      VALIDATE(Type, Type::Text);
      VALIDATE("No.", ''),
      ResGroup.GET("Add-on No.");
      Description := ResGroup.Name;
      "Resource Group No." := ResGroup."No.";
      GetJob;
      ResCost.SETRANGE(Type, 
        ResPrice.Type::"Group(Resource)");
      ResCost.SETRANGE(Code, ResGroup."No.");
      IF ResCost.FINDFIRST THEN BEGIN
        "Unit Cost" := ROUND(
            CurrExchRate.ExchangeAmtLCYToFCY(
              "Currency Date","Currency Code",
              ResCost."Unit Cost","Currency Factor"),
            UnitAmountRoundingPrecision);

In the C/AL code, we can make sure that when users select the values available in the standard product, the normal code is executed. If a user selects a Resource Group, we execute our own business logic.

To make sure everything works as expected we use the Type Text in the background. Line Type is set to Schedule because we do not want to invoice Resource Groups, we just want them to be budgeted.

The Unit Cost and Unit Price are calculated using the Resource Cost and Resource Price tables, which support the use of Resource Groups. This is an inheritance from the previous Job functionality prior to Version 5.0.

The page Job Planning List (1007) is changed to show our add-on fields instead of the normal fields.

To completely finish this functionality, we would also need to change the reports that show the Job Planning Lines and the C/AL code that creates the Job Planning Lines when posting a Job Journal Line. This is not done in the example code for this chapter.

Calculations

Some companies using the Job functionality have a need for flexible calculations. In our example, we use it to calculate the price of a computer system but other examples are book publishers or construction companies.

They want to know what it costs to create a product without exactly knowing which screws, hinges, or color of chipboard is used. For these companies, we designed a simple but effective calculation module.

In our database, there are two example calculations: a server, and a desktop system.

Calculations

The calculation is designed using a header/line construction with a Number Series and a Line Number. The calculation lines are items.

When a new calculation is created some lines are automatically inserted. This is done in a C/AL function that is called from the OnInsert trigger.

The OnInsert trigger will also copy the default Unit Price for Hours from our setup table.

OnInsert()
CalcSetup.GET;

IF "No." = '' THEN BEGIN
  CalcSetup.TESTFIELD("Calculation Nos.");
  NoSeriesMgt.InitSeries(CalcSetup."Calculation Nos.",xRec."No. Series",0D,"No.","No. Series");
END;

"Unit Price Hours (LCY)" := CalcSetup."Unit Price Hours";
InitLines;

The InitLines function creates a calculation line for each item marked as Calculation Item. This is a new field that we added to the item table:

InitLines()
CalcLn.RESET;

i := 0;
Item.SETRANGE("Calculation Item", TRUE);
IF Item.FINDSET THEN REPEAT
  i += 10000;
  CalcLn."Calculation No." := "No.";
  CalcLn."Line No." := i;
  CalcLn.VALIDATE("Item No.", Item."No.");
  CalcLn.INSERT;
UNTIL Item.NEXT = 0;

In the calculation, we can choose how many we will use from each item and the system will calculate the cost and price but also the required number of Hours that is required. The Unit Cost and Unit Price are used from the item table. Hours is calculated from a new field, and we added Minutes on the item table as well.

Calculate()
CalcLn.RESET;
CalcLn.SETRANGE("Calculation No.","No.");
CalcLn.CALCSUMS("Unit Cost", "Unit Price", Profit, Hours);
CalcLn.FIND('-'),
CalcLn.MODIFYALL(Changed,Calculated::Calculated);

CalcLn.CALCSUMS("Unit Cost", "Unit Price", Hours);

"Unit Cost" := CalcLn."Unit Cost";
"Unit Price" := CalcLn."Unit Price";
Profit := "Unit Price" - "Unit Cost";
Hours := CalcLn.Hours;

Correct;
"Total Price Hours (LCY)" := "Hours (After Correction)" * "Unit Price Hours (LCY)";
"Total Price" := "Total Price Hours (LCY)" + 
  "Unit Price (After Correction)";
Calculated := Calculated::Calculated;
MODIFY;

Correct()
"Unit Price (After Correction)" := "Unit Price" + ("Unit Price" * ("Correction % Items" / 100));
"Profit (After Correction)" := 
  "Unit Price (After Correction)" - "Unit Cost";
"Hours (After Correction)" := 
  Hours + (Hours * ("Correction % Hours" / 100));

When we now use the Calculate function, the system will generate a total Unit Cost, Unit Price, and Hours for this product to be created. Flexibility is added to the system by allowing users to correct hours and usage with a percentage.

The calculation can be used in a Job Planning Line the same way as the Resource Groups earlier; the only difference is that we use the G/L Account type in the background to invoice a calculation fixed price. Let's look at the C/AL code in the OnValidate trigger of the Add-On No. field in the Job Planning Line:

Add-on No. - OnValidate()
CASE "Add-on Type" OF
  "Add-on Type"::Resource ... "Add-on Type"::Text:
      ...
  "Add-on Type"::"Resource Group":  
      ...
  "Add-on Type"::Calculation:
    BEGIN
      Calc.GET("Add-on No.");
      IF Calc."Turnover Account No." = '' THEN BEGIN
        TESTFIELD("Line Type", "Line Type"::Schedule);
        VALIDATE(Type, Type::Text);
        VALIDATE("No.", ''),
      END ELSE BEGIN
        TESTFIELD("Line Type", 
          "Line Type"::"Both Schedule and Contract");
        VALIDATE(Type, Type::"G/L Account");
        VALIDATE("No.", Calc."Turnover Account No.");

      END;
      Description := Calc.Description;
      GetJob;

To complete this functionality, we will create a method to use the hours in the calculation for the Resource planning. This can be done using Job Planning Lines of line type Schedule with no unit cost and unit price.

Issue registration

For our support team, we have implemented an issue registration solution. This allows them to have a simple application where they can register issues for all customers and keep track of the status without going in and out of each job.

Issue registration

The issue registration is a header/line construction with a Number Series and a line number. The lines can be used to phrase questions and answers.

When a support engineer creates a new issue, the system will create the Job Task automatically. Let's have a look at the C/AL code that does that:

CreateJobTask()
TESTFIELD("Job No.");
TESTFIELD("Job Task No.", ''),

OldJobTask.SETRANGE("Job No.", "Job No.");
OldJobTask.SETRANGE("Job Task Type", 
  OldJobTask."Job Task Type"::Posting);
IF OldJobTask.ISEMPTY THEN
  OldJobTask.SETRANGE("Job Task Type",
    OldJobTask."Job Task Type"::"Begin-Total");
OldJobTask.FINDLAST;

JobTask."Job No." := "Job No.";
JobTask."Job Task No." := INCSTR(OldJobTask."Job Task No.");
JobTask.Description := Description;
JobTask."Job Task Type" := JobTask."Job Task Type"::Posting;
JobTask.INSERT(TRUE);
CODEUNIT.RUN(CODEUNIT::"Job Task-Indent Direct", JobTask);

"Job Task No." := JobTask."Job Task No.";

The system searches for the last Job Task of the type Posting in the Job. If that cannot be found, it searches for the last Begin-Total line.

Assuming this line exists, we create a new Job Task line using the INCSTR function to increment the number. The description is copied to the Job Task. The support engineers can now register their hours on this Job Task.

This piece of C/AL code is very simple but shows how effective a small solution can be without even touching any of the standard Microsoft Dynamics NAV objects. This is a very safe way of developing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset