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.
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:
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)))
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).
Result of ValueIsFilter property
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.:
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.
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.
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.
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.
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.