Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
lukcad
Explorer

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`

  • in this version action will return the information pop-up with total cost per current order.

Prerequisites:

As a developer to go through steps of this article:

  • you have an account in SAP BTP.
  • you know SAP BTP and know how to create dev Full-Stack environment.
  • you have GitHub where you can save your project
  • you added application SAP Build Code to your subaccount
  • you know how to develop CAP and UI by Fiori elements
  • you understand CDS and know about any consequences if model in CDS should be changed.
  • you have heard about `Joule`.

Creating project for orders

In this document, you will create project according to our goals and the business requirements, steps will be like these:

  • Prepare project
    • creating project
  • Generate components of project
    • generate data model and data by Joule
    • generate FIORI UI
  • Implement business requirements
    • enhance data model
    • create persistence model for development
    • modify FIORI pages
    • modify srv/service.js
    • create srv/code/orderdetails_draft_create_logic.js
    • create srv/code/orderdetails_draft_update_logic.js
    • manual testing business requirements
  • Add additional functionality(action)
    • add action
    • commit to GitHub version 1.0.0
    • generate data logic of action by Joule
    • testing action
    • commit to GitHub version 1.0.1

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

lukcad_0-1713550070760.png

Once project is prepared in your Lobby, open it by hitting Name from list of projects:

lukcad_1-1713550070761.png

You will have opened SAP build tool with generated application which you will develop:

lukcad_2-1713550070761.png

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

lukcad_3-1713550070762.png

Once Joule is finished, you have to accept changes and you can verify your model by CDS Graphical Modeler:

lukcad_4-1713550070763.png

For generating data use this road map:

Joule->Open Guide->Sample Data->Generate Sample Data

Tell to Joule:

Generate sample data for model.

lukcad_5-1713550070764.png

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)

lukcad_6-1713550070765.png

make one record manually with empty value for `ShippedDate` in `Orders` entity.

open terminal and run `cds w`

lukcad_7-1713550070766.png

Open proposed link, usually it is http://localhost:4004

lukcad_8-1713550070767.png

Check that there are data, try open any service, for example Orders:

lukcad_9-1713550070767.png

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`

lukcad_10-1713550070768.png

choose template  List Report Page -> Use local CAP project

lukcad_11-1713550070769.png

Press Next

Choose main entity `Orders` and Navigation entity `orderDetails`

lukcad_12-1713550070769.png

Press next

Give these parameters:

Module name

orders

Application title

Orders

Description

Orders management

Allow FLP configuration

Yes

lukcad_13-1713550070770.png

Press next

Give these parameters for FLP:

Semantic Object

zmlorders

Action

Management

Title

Orders Management

 

lukcad_14-1713550070771.png

Press Finish

Finally you should be able for app/products by opening context menu choose `Show Page Map`

lukcad_15-1713550070771.png

Verify application, by start prepared run configuration or start cds server via terminal.

lukcad_16-1713550070772.png

This Home will be opened for our application:

lukcad_17-1713550070773.png

Open our Fiori application `Orders`

lukcad_18-1713550070774.png

Open details for order:

lukcad_19-1713550070775.png

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`

  • in this version action will return the information pop-up with total cost per current order.

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;
		}

 

 

 

lukcad_20-1713550070775.png

 

You can open SQL Preview of schema.cds file and check how it looks like on DB level:

lukcad_21-1713550070777.png

Preview CDS on service layer after added fields:

lukcad_22-1713550070778.png

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" } 
		        }
      }

 

 

 

lukcad_23-1713550070779.png

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.

lukcad_24-1713550070780.png

 

modify FIORI pages

Execute `Show Page Map` by using context menu for folder `app/orders`

Delete third Page, we need only two pages:

  • List Report
  • Object Page

lukcad_25-1713550070780.png

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

lukcad_26-1713550070781.png

For `Ship Status` set Criticality to `criticality` field.

lukcad_27-1713550070782.png

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

lukcad_28-1713550070782.png

You should have 2 sections added:

lukcad_29-1713550070783.png

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

lukcad_30-1713550070783.png

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

lukcad_31-1713550070784.png

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`

lukcad_32-1713550070785.png

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?

lukcad_33-1713550070785.png 

After your change of product:

 

lukcad_34-1713550070786.png

 

 

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
		};

 

 

lukcad_35-1713550070786.png

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
		      }
}

 

 

lukcad_36-1713550070787.png

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
		            }
		        }
		    }
}

 

 

lukcad_37-1713550070787.png

manual testing business requirements

Now you can go and test application.

lukcad_38-1713550070788.png

You can see that all fields in list and Ship Status should Shipped with criticality level 3 (green)

lukcad_39-1713550070789.png

You add new order without pointing shipment date:

lukcad_40-1713550070790.png

 

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).

lukcad_41-1713550070790.png

 

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:

lukcad_42-1713550070791.png

You can change product `Chai` to `Chef Anton's Cajun Seasoning` which has different unit price:

lukcad_43-1713550070791.png

After saving your change of product you can find that `Order Items` has modified product with proper Unit Price:

lukcad_44-1713550070792.png

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 `

lukcad_45-1713550070792.png

Add `Action1` and choose details for action to set up return type to `LargeString`

lukcad_46-1713550070793.png

If you look at `srv/service.cds` file of your project you will find where and how your action has been added.

lukcad_47-1713550070794.png

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

lukcad_48-1713550070795.png

Add action `Northwind.Action1`

lukcad_49-1713550070795.png

Change Label of action from `Action1` to `Loyalty`

lukcad_50-1713550070796.png

Go back to Graphical representation of `srv/service.cds` and start applying logic to Action1.

lukcad_51-1713550070797.png

Add application logic with name `orders-loyalty-logic`

lukcad_52-1713550070798.png

And open code editor for implementation logic

lukcad_53-1713550070798.png

You are in position where you can ask Joule to find for you code to implement your idea.

lukcad_54-1713550070799.png

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.

lukcad_55-1713550070800.png

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}`);
}

 

 

lukcad_56-1713550070801.png

testing action

run your application

lukcad_57-1713550070802.png

lukcad_58-1713550070803.png

go to details, find in `Order header` link 'Loyalty`

lukcad_59-1713550070803.png

press loyalty, you should have `Information` message about total cost of order.

lukcad_60-1713550070804.png

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:

  • the DB model can be enhanced by materialized calculation fields.
  • on level Fiori pages you can provide value help with out determinations to modify related data.
  • how you can modify related data in draft records.
  • how to add action and expose result of action to pop-up.
  • how to use Joule for creating model, samples of data and for generating code.

 

Happy programming!

Yours sincerely,

Mikhail.

PS: you can find code on GitHub here:

https://github.com/lukcad/zorders.git

1 Comment