
Goals:
1-- create one Full-Stack application for management orders based on model from Northwind service http://services.odata.org
2-- enhance application to meet the business requirements manually to avoid impact of Low Code generator for custom code.
3-- enhance application by new logic using Joule with Low code generator
4-- test code that no regression in code by using Low code generator and Joule.
Minimal Business requirements:
1-- Main list of orders should have at least these columns: Order ID, Order Created, City, Country, Status
Order ID | value of Order ID |
Order Created | value of the Creation Date |
Ship City | value of shipment City |
Ship Country | value of Shipment Country |
Ship Status | Calculated: criticality + value ( shipped or not) |
2-- Each record can be editable on Details page and have two sections `Order Header` which contains general information about order and `Order Items` which is list of item positions in orders.
3-- Each position `Order items` has columns:
Product | Editable (chosen from list) |
Quantity | Editable value of quantity |
Unit price | Read only: should be taken from Unit Price of Product |
Discount | Editable value of discount |
Cost | Calculated field: (Quantity * Unit Price - Discount) |
Status Product | Calculated: criticality + value (Active or Discontinued) |
4-- For additional enhancement you can add action `Loyalty`
Prerequisites:
As a developer to go through steps of this article:
Creating project for orders
In this document, you will create project according to our goals and the business requirements, steps will be like these:
Preparation of project
Go to SAP BTP and open tool `SAP Build Code` which you previously adjusted by Booster or by documentation.
Go to `Lobby` of your applications and create new application with name `zorders` (you can use your name).
Use this road map to create project:
Create -> Build Application -> SAP Build Code -> Full-Stack Application
Once project is prepared in your Lobby, open it by hitting Name from list of projects:
You will have opened SAP build tool with generated application which you will develop:
Before go with `Joule` I recommend use GitHub to save your versions (if something wrongly generated and you did accept it you will be able to restore everything to save time):
we add our project to GitHub by creating repository, in this example it is `zorders`
Preparation is done.
Generate components of project
Generate model and data by Joule
For generating model use this road map:
Go to Joule ->Open Guide->Data Model and Service Creation
Paste for Joule: | create model from Northwind service which is placed on http://services.odata.org |
Once Joule is finished, you have to accept changes and you can verify your model by CDS Graphical Modeler:
For generating data use this road map:
Joule->Open Guide->Sample Data->Generate Sample Data
Tell to Joule: | Generate sample data for model. |
once sample data are generated by Joule, and you accept it, you will find all of those into Sample Data editor (you can open it later on by command palette: PT Open Sample Data Editor)
make one record manually with empty value for `ShippedDate` in `Orders` entity.
open terminal and run `cds w`
Open proposed link, usually it is http://localhost:4004
Check that there are data, try open any service, for example Orders:
Model and data are prepared by Joule and verified by you. You can go to the next step to generate UI by `Fiori elements`.
Generate Fiori UI
On this step you generate UI.
Use this road map:
Open Palette->FIORI: Open Application Generator
Choose `Worklist Page`
choose template List Report Page -> Use local CAP project
Press Next
Choose main entity `Orders` and Navigation entity `orderDetails`
Press next
Give these parameters:
Module name | orders |
Application title | Orders |
Description | Orders management |
Allow FLP configuration | Yes |
Press next
Give these parameters for FLP:
Semantic Object | zmlorders |
Action | Management |
Title | Orders Management |
Press Finish
Finally you should be able for app/products by opening context menu choose `Show Page Map`
Verify application, by start prepared run configuration or start cds server via terminal.
This Home will be opened for our application:
Open our Fiori application `Orders`
Open details for order:
Our application contains all needing components of application to start implementation of business requirements.
Implement business requirements
On this phase you have to implement the business requirements:
1-- Main list of orders should have at least these columns: Order ID, Order Created, City, Country, Status
Order ID | value of Order ID |
Order Created | value of the Creation Date |
Ship City | value of shipment City |
Ship Country | value of Shipment Country |
Ship Status | Calculated: criticality + value ( shipped or not) |
2-- Each record can be editable on Details page and have two sections `Order Header` which contains general intormation about order and `Order Items` which is list of item positions in orders.
3-- Each position `Order items` has columns:
Product | Editable (chosen from list) |
Quantity | Editable value of quantity |
Unit price | Read only: should be taken from Unit Price of Product |
Discount | Editable value of discount |
Cost | Calculated field: (Quantity * Unit Price - Discount) |
Status Product | Calculated: criticality + value (Active or Discontinued) |
4-- For additional enhancement you can add action `Loyalty`
enhance data model
On level data model we need to apply so named materialized calculated fields. fields that we add as extension to our data model and those will be recalculated and saved in database each time when values in record were changed. This is most effective way to keep calculated fields in database.
We need according to business requirements 3 calculated fields:
Into `db/schema.cds` file we add these 3 calculated fields using extend of the correspondent database:
/**
*
* Products has crtiticality positive if it is not discontinued
*/
extend Products with {
criticality : Integer = (case when Discontinued = False then 3 else 2 end ) stored;
ProductStatus : String(15) = (case when Discontinued = False then 'Active' else 'Discontinued' end ) stored;
}
/**
*
* Orders has crtiticality positive if it is shipped
*/
extend Orders with {
criticality : Integer = (case when ShippedDate is not null then 3 else 2 end ) stored;
ShipStatus : String(15) = (case when ShippedDate is not null then 'Shipped' else 'Not' end ) stored;
}
/**
* Cost per each element is needing to easily calculate total price
*/
extend OrderDetails with {
cost : Decimal(10,4) = ( UnitPrice * Quantity - Discount ) stored;
}
You can open SQL Preview of schema.cds file and check how it looks like on DB level:
Preview CDS on service layer after added fields:
create persistence model for development
Use persistence data base instead of `in-memory` for this example.
You should add this JSON fragment into `package.json` of project to cluster of settings under `cds/requires`:
,
"[development]": {
"db": {
"kind": "sqlite",
"credentials": { "url": "zorders.sqlite" }
}
}
And run this command in terminal to create your persistence in the SQLite based database.
cds deploy
You will find that database for persistence is appeared in your project.
modify FIORI pages
Execute `Show Page Map` by using context menu for folder `app/orders`
Delete third Page, we need only two pages:
Open page `List Report` in Edit mode and for Columns of Table add these fields and remove not necessary.
Field: | Label: |
OrderID | Order ID |
OrderDate | Order Created |
ShipCIty | Ship City |
ShipCountry | Ship Country |
ShipName | Ship Name |
ShipStatus | Ship Status |
For `Ship Status` set Criticality to `criticality` field.
Return to `Page Map` and start Edit `Object Page`
Rename Section `General Information` to `Order Header`
Add section `Order Items` by using `Add Table Section` with parameters:
Label | Order Items |
Value Source | orderDetails |
You should have 2 sections added:
Open Section `Order Header` and change fields in Fields of Form :
Field: | Label: |
OrderId | Order ID |
OrderDate | Order Created |
RequiredDate | Order Required |
ShippedDate | Order Shipped |
ShipVia | Ship Via |
Freight | Freight |
ShipName | Ship Name |
ShipAddress | Ship Address |
ShipCity | Ship City |
ShipRegion | Ship Region |
ShipPostalCode | Ship Postal Code |
ShipCountry | Ship Country |
Open Section `Order Items` and add fields in columns of Table:
Field: | Label: | Text | Restriction | Text Arragement | criticality |
product_ID | Product | product/ProductName |
| Text Only |
|
Quantity | Quantity |
|
|
|
|
UnitPrice | Unit Price |
| Read only |
|
|
Discount | Discount |
|
|
|
|
cost | Cost |
| Read Only |
|
|
ProductStatus | Product Status |
| Read Only |
| product/criticality |
For field Product you should make more adjustments to let user choose Product by name from value help name with two additional fields `Unit Price` and `Discontinued`
If you now start use this application and go to for modification of product in position of order (section `Order Items` ), you will have inconsistency for `Unit Price` after saving, even you will be able to see correct price by dropdown list `out` determination.
As example of wrong behavior of currently achieved UI:
Before your change: | What is wrong? |
After your change of product: |
|
| Unit price has not changed together with product during editing `Order Items`, so Cost is wrong as well |
It will be not acceptable by customer.
So what you can do with it, we have to extend manually code in srv/service.js and create in folder `srv/code` files with manual methods of our code, you can't use here Joule in this piece of programming currently, because we create code witch suppose to interact with drafts records of table `OrderDetails`.
This is what you can do:
-- modify srv/service.js | you add new two event handlers here to feel modification of `OrderDerails.drafts` (notice name of entity with draft information against service looks like adding suffix `.drafts`) |
-- add srv/code/orderdetails_draft_create_logic.js | your custom code for creating draft record, here your code does simple set up of filed `Discount` if it is null. |
-- add srv/code/orderdetails_draft_update_logic.js | your custom code for updating draft record, here your code does modification of `UnitPrice` according to chosen `Product` |
modify srv/service.js
Put this code into your file:
/**
* Code is auto-generated by Application Logic, DO NOT EDIT.
* @version(2.0)
*/
const LCAPApplicationService = require('@sap/low-code-event-handler');
const orders_Loyalty_Logic = require('./code/orders-loyalty-logic');
const orderdetails_draft_update_Logic = require('./code/orderdetails_draft_update_logic');
const orderdetails_draft_create_Logic = require('./code/orderdetails_draft_create_logic.js');
class NorthwindSrv extends LCAPApplicationService {
async init() {
this.on('Action1', 'Orders', async (request, next) => {
await orders_Loyalty_Logic(request);
return next();
});
this.before(['UPDATE'], 'OrderDetails.drafts', async (request) => {
await orderdetails_draft_update_Logic(request);
});
this.before(['CREATE'], 'OrderDetails.drafts', async (request) => {
await orderdetails_draft_create_Logic(request);
});
return super.init();
}
}
module.exports = {
NorthwindSrv
};
create srv/code/orderdetails_draft_create_logic.js
/**
* This initiate default value of Discount to avoid null for new records
* @Before(event = { "CREATE" }, entity = "NorthwindSrv.OrderDetails.drafts")
* {Object} req - User information, tenant-specific CDS model, headers and query parameters
*/
module.exports = async function (req) {
// Your code here
if (!req.data.Discount) {
req.data.Discount = 0
}
}
create srv/code/orderdetails_draft_update_logic.js
/**
* This changes UnitPrice in draft record to expose it when record is applied
* @Before(event = { "UPDATE" }, entity = "NorthwindSrv.OrderDetails.drafts")
* {Object} req - User information, tenant-specific CDS model, headers and query parameters
*/
module.exports = async function(req) {
// Your code here
if (req) {
if (req.data) {
if (req.data.product_ID) {
const northSrv = await cds.connect.to("NorthwindSrv");
let prod_id = req.data.product_ID
let sqlprod = SELECT.from`Products`.where` ID=${prod_id}`
let productstm = await northSrv.get(sqlprod)
req.data.UnitPrice = productstm[0].UnitPrice
}
}
}
}
manual testing business requirements
Now you can go and test application.
You can see that all fields in list and Ship Status should Shipped with criticality level 3 (green)
You add new order without pointing shipment date:
As a result of adding new record which is not not shipped (date of shipment was not entered) you can see that orders has `Ship status` with value Not and criticality 2 (orange).
You can add new order Item in Edit mode by pressing `Create` for section `Order Items` and you see that `Discount` has value by default 0:
You can change product `Chai` to `Chef Anton's Cajun Seasoning` which has different unit price:
After saving your change of product you can find that `Order Items` has modified product with proper Unit Price:
Add additional functionality(action).
We will add action which will be used in future to let manager of orders press action `Loyalty` to understand total cost of order and add any another information (this will be added by another document, so here just for understanding approach will be added in information message after pressing action just simple total cost of order which will be dynamically calculated by code which Joule will be able to generate).
add action
Open `srv/service.cds` by `CDS Graphical Modeler` and for `Orders` entity choose `Add Action `
Add `Action1` and choose details for action to set up return type to `LargeString`
If you look at `srv/service.cds` file of your project you will find where and how your action has been added.
Open for folder app/orders context menu and choose `Show Page Map` and open `Object page` for edit mode
and go to sections `Order header` and `Actions` of Form
Add action `Northwind.Action1`
Change Label of action from `Action1` to `Loyalty`
Go back to Graphical representation of `srv/service.cds` and start applying logic to Action1.
Add application logic with name `orders-loyalty-logic`
And open code editor for implementation logic
You are in position where you can ask Joule to find for you code to implement your idea.
commit your project to GitHub with version 1.0.0
generate data logic of action by Joule
Tell to Joule: | select all rows from `OrderDetails` that associated to selected `ID` from `Orders`. and then calculate sum of by field `cost` as `totalcost`. show result into `info` pop-up. |
You should just slightly change code which Joule is generated, use order ID from parameter of request instead of selection:
/**
*
* (event = { "Action1" }, entity = "NorthwindSrv.Orders")
* {Object} req - User information, tenant-specific CDS model, headers and query parameters
*/
module.exports = async function(req) {
const tx = cds.transaction(req);
const orderID = req._params[0].ID; // req.data.ID;
const orderDetails = await tx.run(
SELECT
.from('NorthwindSrv.OrderDetails')
.where({ order_ID: orderID })
);
let totalCost = 0;
for (const detail of orderDetails) {
totalCost += detail.cost;
}
req.info(`The total cost of order ${orderID} is ${totalCost}`);
}
testing action
run your application
go to details, find in `Order header` link 'Loyalty`
press loyalty, you should have `Information` message about total cost of order.
commit your project to GitHub with version 1.0.1
Thank you,
you have ready-to-use simplified application for management orders.
Main take aways from this example:
Happy programming!
Yours sincerely,
Mikhail.
PS: you can find code on GitHub here:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
5 | |
3 | |
2 | |
2 | |
2 | |
2 | |
2 | |
2 | |
1 | |
1 |