Task
Configure all EntityFields, whose values are calculated, i.e., not directly read from a database column. Particularly, these are the following EntityFields:
- Car_entity:
- mileage (mileage at return of latest completed reservation)
- damages (simply concatenate single damages)
- carValue (The value of the car depends on its price and on its age. Per day, the value decreases by 0.03%. Furthermore, if the car has any damage, its value should be reduced by another 10%.)
- availability (current availability; calculated from reservations)
- CarDriver_entity:
- age (calculated from PERSON.DATEOFBIRTH)
- drivingExperience (calculated from DRIVINGLICENSEISSUEDATE)
- parkingTicketFinesSum
- speedingFinesSum
- CarReservation_entity:
- mileageStart (mileage at return of previous reservation)
Here you can find a generic description of calculated fields.
Always keep in mind that a value/displayValue calculated via the RecordContainer (RecordFieldMapping's property expression) shows, in most cases, a better performance than a calculation via the EntityField's valueProcess/displayValueProcess. However, you still need the valueProcess, if an EntityField should be preselected (initialized) with a specific value in the EditView, when inserting a new dataset that is not yet in the database.
Solution
Car_entity
mileage
The current milage of a car can be calculated in various ways. One would be to retrieve the maximum MILEAGERETURN value of all reservations of a specific car. Enter the following valueProcess process in CarReservation_entity's field mileageStart:
import { result, vars } from "@aditosoftware/jdito-types";
var carId = vars.get("$field.CARID");
if (carId)
{
var maxMileage = newSelect("max(MILEAGERETURN)")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID", carId)
.cell();
result.string(maxMileage);
}
Car_entity.mileage.valueProcess.js
An alternative would be to search for the MILEAGERETURN of the latest reservation of a specific car, having a MILEAGERETURN value:
import { result, vars } from "@aditosoftware/jdito-types";
import { newSelect } from "SqlBuilder_lib";
var carId = vars.get("$field.CARID");
if (carId) {
var maxMileage = newSelect("MILEAGERETURN")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID", carId)
.and("CARRESERVATION.ENDDATE is not null")
.and("CARRESERVATION.ENDDATE",
newSelect("max(CARRESERVATION.ENDDATE)")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID", carId))
.cell();
result.string(maxMileage);
}
Car_entity.mileage.valueProcess.js (alternative)
damages
We simply concatenate the single damages included in CARRESERVATION:
import { result } from "@aditosoftware/jdito-types";
import { newSelect } from "SqlBuilder_lib";
let subSelect = newSelect("GROUP_CONCAT(CARRESERVATION.DAMAGE)")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID like CAR.CARID");
result.string(subSelect);
Car_entity.db.damages.value.expression.js
carValue
As given in the task description: The value of the car depends on its price and on its age. Per day, the value decreases by 0.03%. Furthermore, if the car has any damage, its value should be reduced by another 10% (overall).
import { result, vars } from "@aditosoftware/jdito-types";
import { DateUtils } from "DateUtils_lib";
let price = vars.get("$field.PRICE");
let dateOfManufacture = vars.get("$field.MANUFACTUREDATE");
let damages = vars.get("$field.damages");
if (price && dateOfManufacture) {
let value = price;
let ageInDays = Math.floor(DateUtils.getDayDifference(dateOfManufacture));
// just a random percentage as example
let valueLossPercent = -0.03;
// random formula, similar to
// calculation of "compound interest"
value *= Math.pow((1 + (valueLossPercent / 100)), ageInDays);
if (damages) {
// just a random factor as example
value *= 0.9;
}
result.string(value);
}
Car_entity.carValue.valueProcess.js
import { result, text, vars } from "@aditosoftware/jdito-types";
let carValue = vars.get("$this.value");
let currency = vars.get("$field.CURRENCY");
if (carValue && currency) {
result.string(text.formatDouble(carValue, "#,##0.00") + " " + currency);
}
Car_entity.carValue.displayValueProcess.js
availability
As given in the task description: This is meant to be the current availability, which we can calculate from car reservations:
import { datetime, result, translate } from "@aditosoftware/jdito-types";
import { newSelect, SqlBuilder } from "SqlBuilder_lib";
let currentDate = datetime.date();
let subSelect = SqlBuilder.caseWhen("((" +
newSelect("count(*)")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID = CAR.CARID")
.and("CARRESERVATION.STARTDATE", currentDate, SqlBuilder.LESS)
.and("CARRESERVATION.ENDDATE", currentDate, SqlBuilder.GREATER)
+ ") = 0 )")
.thenString(translate.text("Yes"))
.elseString(translate.text("No"))
.toString();
result.string(subSelect);
Car_entity.db.availability.value.expression.js
CarDriver_entity
age
If you test the below code, first make sure that the contact persons you use have all a date of birth set.
The age of the car driver can be calculated from Person_entity's field DATEOFBIRTH. To simplify the calculation via SQL, we modify CarDriver_entity's RecordContainer by
- adding the database table PERSON in property
linkInformation; - joining the database tables CARDRIVER, CONTACT, and PERSON in property
fromClauseProcess:
import { result } from "@aditosoftware/jdito-types";
import { SqlBuilder } from "SqlBuilder_lib";
var sql = new SqlBuilder()
.from("CARDRIVER")
.join("CONTACT", "CARDRIVER.CONTACT_ID = CONTACT.CONTACTID")
.join("PERSON", "CONTACT.PERSON_ID = PERSON.PERSONID");
result.string(sql.toString());
After this preparation, we can now use the following expression process:
import { result } from "@aditosoftware/jdito-types";
import { SqlBuilder } from "SqlBuilder_lib";
import { SqlMaskingUtils } from "SqlMaskingUtils_lib";
var sqlUtils = new SqlMaskingUtils();
var sql = sqlUtils.yearFromDate("CURRENT_DATE")
+ " - "
// Prerequisite: tables CONTACT and PERSON are joined
+ sqlUtils.yearFromDate("DATEOFBIRTH")
+ " - "
+ SqlBuilder.caseWhen(
"("
+ sqlUtils.monthFromDate("CURRENT_DATE")
+ " < " + sqlUtils.monthFromDate("DATEOFBIRTH")
+ "OR ("
+ sqlUtils.monthFromDate("CURRENT_DATE")
+ " = " + sqlUtils.monthFromDate("DATEOFBIRTH")
+ " AND "
+ sqlUtils.dayFromDate("CURRENT_DATE")
+ " < " + sqlUtils.dayFromDate("DATEOFBIRTH")
+ ")"
+ ")"
)
.then("1")
.elseValue("0")
.toString();
result.string(sql);
CarDriver_entity.db.age.value.expression.js
As the age should also be shown in CarDriverEdit_view, after selecting the contact person (CONTACT_ID), we also insert a valueProcess:
import { result, vars } from "@aditosoftware/jdito-types";
import { DateUtils } from "DateUtils_lib";
import { newSelect } from "SqlBuilder_lib";
let contactId = vars.get("$field.CONTACT_ID");
if (contactId)
{
let dateOfBirth = newSelect("DATEOFBIRTH")
.from("PERSON")
.join("CONTACT", "PERSON.PERSONID = CONTACT.PERSON_ID")
.where("CONTACT.CONTACTID", contactId)
.cell();
if (dateOfBirth)
{ // just a rough approximation
let age = Math.floor(DateUtils.getDayDifference(dateOfBirth) / 365);
result.string(age);
}
}
CarDriver_entity.age.valueProcess.js
drivingExperience
The EntityField "drivingExperience" is handled very similar to "age" (see previous sub-chapter): The driving experience can be calculated from DRIVINGLICENSEISSUEDATE, both via SQL (expression) as well as via JDito (valueProcess):
import { result } from "@aditosoftware/jdito-types";
import { SqlBuilder } from "SqlBuilder_lib";
import { SqlMaskingUtils } from "SqlMaskingUtils_lib";
var sqlUtils = new SqlMaskingUtils();
var sql = sqlUtils.yearFromDate("CURRENT_DATE")
+ " - "
+ sqlUtils.yearFromDate("DRIVINGLICENSEISSUEDATE")
+ " - "
+ SqlBuilder.caseWhen(
"("
+ sqlUtils.monthFromDate("CURRENT_DATE")
+ " < " + sqlUtils.monthFromDate("DRIVINGLICENSEISSUEDATE")
+ "OR ("
+ sqlUtils.monthFromDate("CURRENT_DATE")
+ " = " + sqlUtils.monthFromDate("DRIVINGLICENSEISSUEDATE")
+ " AND "
+ sqlUtils.dayFromDate("CURRENT_DATE")
+ " < " + sqlUtils.dayFromDate("DRIVINGLICENSEISSUEDATE")
+ ")"
+ ")"
)
.then("1")
.elseValue("0")
.toString();
result.string(sql);
CarDriver_entity.db.drivingExperience.value.expression.js
As the driving experience should also be shown in CarDriverEdit_view, after selecting the issue date of the driving license (DRIVINGLICENSEISSUEDATE), we also insert a valueProcess:
import { result, vars } from "@aditosoftware/jdito-types";
import { DateUtils } from "DateUtils_lib";
let drivingLicenseIssueDate = vars.get("$field.DRIVINGLICENSEISSUEDATE");
if (drivingLicenseIssueDate)
{ // just a rough approximation
let drivingExperience = Math.floor(DateUtils.getDayDifference(drivingLicenseIssueDate) / 365);
result.string(drivingExperience);
}
CarDriver_entity.drivingExperience.valueProcess.js
parkingTicketFinesSum
import { result } from "@aditosoftware/jdito-types";
import { newSelect } from "SqlBuilder_lib";
let parkingTicketFinesSum = newSelect("SUM(CARRESERVATION.PARKINGTICKETFINE)")
.from("CARRESERVATION")
.where("CARRESERVATION.CARDRIVER_ID = CARDRIVER.CARDRIVERID");
result.string(parkingTicketFinesSum);
CarDriver_entity.db.parkingTicketFinesSum.value.expression.js
A valueProcess is not required in this case, as this EntityField is not required for the EditView.
speedingFinesSum
The calculation of EntityField "speedingFinesSum" is the same as for "parkingTicketFinesSum":
import { result } from "@aditosoftware/jdito-types";
import { newSelect } from "SqlBuilder_lib";
let speedingFinesSum = newSelect("SUM(CARRESERVATION.SPEEDINGFINE)")
.from("CARRESERVATION")
.where("CARRESERVATION.CARDRIVER_ID = CARDRIVER.CARDRIVERID");
result.string(speedingFinesSum);
CarDriver_entity.db.speedingFinesSum.value.expression.js
A valueProcess is not required in this case, as this EntityField is not required for the EditView.
CarReservation_entity
mileageStart
EntityField "mileageStart" is the mileage at return of the previous reservation of the same car:
import { result, vars } from "@aditosoftware/jdito-types";
import { newSelect, SqlBuilder } from "SqlBuilder_lib";
let mileageStartSql = newSelect("IFNULL(MAX(cr.MILEAGERETURN), 0)")
.from("CARRESERVATION", "cr")
.where("cr.CAR_ID = CAR.CARID")
.and("cr.ENDDATE < CARRESERVATION.STARTDATE")
.toString();
result.string(mileageStartSql);
CarReservation_entity.db.mileageStart.value.expression.js
In order to see the mileage at start also when creating a new dataset (as soon as a car has been selected), we need a valueProcess:
import { result, vars } from "@aditosoftware/jdito-types";
import { newSelect, SqlBuilder } from "SqlBuilder_lib";
let carId = vars.get("$field.CAR_ID");
let startDate = vars.get("$field.STARTDATE");
if (carId && startDate)
{
let mileageStart = newSelect("MAX(MILEAGERETURN)")
.from("CARRESERVATION")
.where("CARRESERVATION.CAR_ID", carId)
.and("CARRESERVATION.ENDDATE", startDate, SqlBuilder.LESS)
.cell();
result.string(mileageStart || 0);
}
CarReservation_entity.mileageStart.valueProcess.js