diff --git a/fiori/.env b/fiori/.env
new file mode 100644
index 00000000..36644fa6
--- /dev/null
+++ b/fiori/.env
@@ -0,0 +1,2 @@
+# cds.requires.messaging.kind = file-based-messaging
+PORT = 4004
\ No newline at end of file
diff --git a/fiori/app/_i18n/i18n.properties b/fiori/app/_i18n/i18n.properties
new file mode 100644
index 00000000..83681bcf
--- /dev/null
+++ b/fiori/app/_i18n/i18n.properties
@@ -0,0 +1,26 @@
+Books = Books
+Book = Book
+ID = ID
+Title = Title
+Author = Author
+AuthorID = Author ID
+Stock = Stock
+Name = Name
+AuthorName = Author's Name
+DateOfBirth = Date of Birth
+DateOfDeath = Date of Death
+PlaceOfBirth = Place of Birth
+PlaceOfDeath = Place of Death
+Age = Age
+Authors = Authors
+Order = Order
+Orders = Orders
+Price = Price
+
+Genre = Genre
+Genres = Genres
+SubGenres = Sub Genres
+
+NumCode = Numeric Code
+MinorUnit = Minor Unit
+Exponent = Exponent
\ No newline at end of file
diff --git a/fiori/app/_i18n/i18n_de.properties b/fiori/app/_i18n/i18n_de.properties
new file mode 100644
index 00000000..7724f685
--- /dev/null
+++ b/fiori/app/_i18n/i18n_de.properties
@@ -0,0 +1,14 @@
+Books = Bücher
+Book = Buch
+ID = ID
+Title = Titel
+Authors = Autoren
+Author = Autor
+AuthorID = ID des Autors
+AuthorName = Name des Autors
+Age = Alter
+Name = Name
+Stock = Bestand
+Order = Bestellung
+Orders = Bestellungen
+Price = Preis
diff --git a/fiori/app/admin-fiori.html b/fiori/app/admin-fiori.html
new file mode 100644
index 00000000..6c229e6b
--- /dev/null
+++ b/fiori/app/admin-fiori.html
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+ Bookshop
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fiori/app/admin/fiori-service.cds b/fiori/app/admin/fiori-service.cds
new file mode 100644
index 00000000..8e97fdbe
--- /dev/null
+++ b/fiori/app/admin/fiori-service.cds
@@ -0,0 +1,93 @@
+using { AdminService } from '../../db/schema';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Object Page
+//
+
+annotate AdminService.Books with @(
+ UI: {
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
+ ],
+ FieldGroup#General: {
+ Data: [
+ {Value: title},
+ {Value: author_ID},
+ {Value: genre_ID},
+ {Value: descr},
+ ]
+ },
+ FieldGroup#Details: {
+ Data: [
+ {Value: stock},
+ {Value: price},
+ {Value: currency_code, Label: '{i18n>Currency}'},
+ ]
+ },
+ FieldGroup#Admin: {
+ Data: [
+ {Value: createdBy},
+ {Value: createdAt},
+ {Value: modifiedBy},
+ {Value: modifiedAt}
+ ]
+ }
+ }
+);
+
+annotate AdminService.Authors with @(
+ UI: {
+ HeaderInfo: {
+ Description: {Value: lifetime}
+ },
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
+ ],
+ FieldGroup#Details: {
+ Data: [
+ {Value: placeOfBirth},
+ {Value: placeOfDeath},
+ {Value: dateOfBirth},
+ {Value: dateOfDeath},
+ {Value: age, Label: '{i18n>Age}'},
+ ]
+ },
+ }
+);
+
+
+
+////////////////////////////////////////////////////////////
+//
+// Draft for Localized Data
+//
+
+annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
+annotate AdminService.Books with @odata.draft.enabled;
+
+annotate AdminService.Books_texts with @(
+ UI: {
+ Identification: [{Value:title}],
+ SelectionFields: [ locale, title ],
+ LineItem: [
+ {Value: locale, Label: 'Locale'},
+ {Value: title, Label: 'Title'},
+ {Value: descr, Label: 'Description'},
+ ]
+ }
+);
+
+// Add Value Help for Locales
+annotate AdminService.Books_texts {
+ locale @ValueList:{entity:'Languages',type:#fixed}
+}
+// In addition we need to expose Languages through AdminService
+using { sap } from '@sap/cds/common';
+extend service AdminService {
+ entity Languages as projection on sap.common.Languages;
+}
diff --git a/fiori/app/admin/webapp/Component.js b/fiori/app/admin/webapp/Component.js
new file mode 100644
index 00000000..c3137017
--- /dev/null
+++ b/fiori/app/admin/webapp/Component.js
@@ -0,0 +1,8 @@
+sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
+ "use strict";
+ return AppComponent.extend("admin.Component", {
+ metadata: { manifest: "json" }
+ });
+});
+
+/* eslint no-undef:0 */
\ No newline at end of file
diff --git a/fiori/app/admin/webapp/i18n/i18n.properties b/fiori/app/admin/webapp/i18n/i18n.properties
new file mode 100644
index 00000000..28b03dff
--- /dev/null
+++ b/fiori/app/admin/webapp/i18n/i18n.properties
@@ -0,0 +1,11 @@
+# This is the resource bundle of itelo
+# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
+
+# JCI app descriptor contains lower case TITLE
+appTitle=Bookshop Sample
+
+# JCI app descriptor contains lower case DESCRIPTION
+appSubTitle=CAP Sample Application
+
+# JCI app descriptor contains lower case DESCRIPTION
+appDescription=CDS Sample Service
diff --git a/fiori/app/admin/webapp/manifest.json b/fiori/app/admin/webapp/manifest.json
new file mode 100644
index 00000000..25047c29
--- /dev/null
+++ b/fiori/app/admin/webapp/manifest.json
@@ -0,0 +1,128 @@
+{
+ "_version": "1.8.0",
+ "sap.app": {
+ "id": "admin",
+ "type": "application",
+ "title": "Manage Books",
+ "description": "Sample Application",
+ "i18n": "i18n/i18n.properties",
+ "dataSources": {
+ "AdminService": {
+ "uri": "/admin/",
+ "type": "OData",
+ "settings": {
+ "odataVersion": "4.0"
+ }
+ }
+ },
+ "-sourceTemplate": {
+ "id": "ui5template.basicSAPUI5ApplicationProject",
+ "-id": "ui5template.smartTemplate",
+ "-version": "1.40.12"
+ }
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "libs": {
+ "sap.fe.templates": {}
+ }
+ },
+ "models": {
+ "i18n": {
+ "type": "sap.ui.model.resource.ResourceModel",
+ "uri": "i18n/i18n.properties"
+ },
+ "": {
+ "dataSource": "AdminService",
+ "settings": {
+ "synchronizationMode": "None",
+ "operationMode": "Server",
+ "autoExpandSelect" : true,
+ "earlyRequests": true,
+ "groupProperties": {
+ "default": {
+ "submit": "Auto"
+ }
+ }
+ }
+ }
+ },
+ "routing": {
+ "routes": [
+ {
+ "pattern": ":?query:",
+ "name": "BooksList",
+ "target": "BooksList"
+ },
+ {
+ "pattern": "Books({key}):?query:",
+ "name": "BooksDetails",
+ "target": "BooksDetails"
+ },
+ {
+ "pattern": "Books({key}/author({key2}):?query:",
+ "name": "AuthorsDetails",
+ "target": "AuthorsDetails"
+ }
+ ],
+ "targets": {
+ "BooksList": {
+ "type": "Component",
+ "id": "BooksList",
+ "name": "sap.fe.templates.ListReport",
+ "options": {
+ "settings" : {
+ "entitySet" : "Books",
+ "navigation" : {
+ "Books" : {
+ "detail" : {
+ "route" : "BooksDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "BooksDetails": {
+ "type": "Component",
+ "id": "BooksDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings" : {
+ "entitySet" : "Books",
+ "navigation" : {
+ "Authors" : {
+ "detail" : {
+ "route" : "AuthorsDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "AuthorsDetails": {
+ "type": "Component",
+ "id": "AuthorsDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings" : {
+ "entitySet" : "Authors"
+ }
+ }
+ }
+ }
+ },
+ "contentDensities": {
+ "compact": true,
+ "cozy": true
+ }
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "fullWidth": false
+ },
+ "sap.fiori": {
+ "registrationIds": [],
+ "archeType": "transactional"
+ }
+}
\ No newline at end of file
diff --git a/fiori/app/bookshop.html b/fiori/app/bookshop.html
new file mode 100644
index 00000000..e7c07e25
--- /dev/null
+++ b/fiori/app/bookshop.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/fiori/app/browse/fiori-service.cds b/fiori/app/browse/fiori-service.cds
new file mode 100644
index 00000000..f59a36b4
--- /dev/null
+++ b/fiori/app/browse/fiori-service.cds
@@ -0,0 +1,50 @@
+using CatalogService from '@capire/bookshop';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Object Page
+//
+annotate CatalogService.Books with @(
+ UI: {
+ HeaderInfo: {
+ TypeName: 'Book',
+ TypeNamePlural: 'Books',
+ Description: {Value: author}
+ },
+ HeaderFacets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'},
+ ],
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Price'},
+ ],
+ FieldGroup#Descr: {
+ Data: [
+ {Value: descr},
+ ]
+ },
+ FieldGroup#Price: {
+ Data: [
+ {Value: price},
+ {Value: currency.symbol, Label: '{i18n>Currency}'},
+ ]
+ },
+ }
+);
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Object Page
+//
+annotate CatalogService.Books with @(
+ UI: {
+ SelectionFields: [ ID, price, currency_code ],
+ LineItem: [
+ {Value: title},
+ {Value: author, Label:'{i18n>Author}'},
+ {Value: genre.name},
+ {Value: price},
+ {Value: currency.symbol, Label:' '},
+ ]
+ },
+);
diff --git a/fiori/app/browse/webapp/Component.js b/fiori/app/browse/webapp/Component.js
new file mode 100644
index 00000000..7914d295
--- /dev/null
+++ b/fiori/app/browse/webapp/Component.js
@@ -0,0 +1,7 @@
+sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
+ "use strict";
+ return AppComponent.extend("bookshop.Component", {
+ metadata: { manifest: "json" }
+ });
+});
+/* eslint no-undef:0 */
\ No newline at end of file
diff --git a/fiori/app/browse/webapp/i18n/i18n.properties b/fiori/app/browse/webapp/i18n/i18n.properties
new file mode 100644
index 00000000..28b03dff
--- /dev/null
+++ b/fiori/app/browse/webapp/i18n/i18n.properties
@@ -0,0 +1,11 @@
+# This is the resource bundle of itelo
+# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
+
+# JCI app descriptor contains lower case TITLE
+appTitle=Bookshop Sample
+
+# JCI app descriptor contains lower case DESCRIPTION
+appSubTitle=CAP Sample Application
+
+# JCI app descriptor contains lower case DESCRIPTION
+appDescription=CDS Sample Service
diff --git a/fiori/app/browse/webapp/manifest.json b/fiori/app/browse/webapp/manifest.json
new file mode 100644
index 00000000..4a2e0a62
--- /dev/null
+++ b/fiori/app/browse/webapp/manifest.json
@@ -0,0 +1,106 @@
+{
+ "_version": "1.8.0",
+ "sap.app": {
+ "id": "bookshop",
+ "type": "application",
+ "title": "Browse Books",
+ "description": "Sample Application",
+ "i18n": "i18n/i18n.properties",
+ "dataSources": {
+ "CatalogService": {
+ "uri": "/browse/",
+ "type": "OData",
+ "settings": {
+ "odataVersion": "4.0"
+ }
+ }
+ },
+ "-sourceTemplate": {
+ "id": "ui5template.basicSAPUI5ApplicationProject",
+ "-id": "ui5template.smartTemplate",
+ "-version": "1.40.12"
+ }
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "libs": {
+ "sap.fe.templates": {}
+ }
+ },
+ "models": {
+ "i18n": {
+ "type": "sap.ui.model.resource.ResourceModel",
+ "uri": "i18n/i18n.properties"
+ },
+ "": {
+ "dataSource": "CatalogService",
+ "settings": {
+ "synchronizationMode": "None",
+ "operationMode": "Server",
+ "autoExpandSelect": true,
+ "earlyRequests": true,
+ "groupProperties": {
+ "default": {
+ "submit": "Auto"
+ }
+ }
+ }
+ }
+ },
+ "routing": {
+ "routes": [
+ {
+ "pattern": ":?query:",
+ "name": "BooksList",
+ "target": "BooksList"
+ },
+ {
+ "pattern": "Books({key}):?query:",
+ "name": "BooksDetails",
+ "target": "BooksDetails"
+ }
+ ],
+ "targets": {
+ "BooksList": {
+ "type": "Component",
+ "id": "BooksList",
+ "name": "sap.fe.templates.ListReport",
+ "options": {
+ "settings": {
+ "entitySet": "Books",
+ "navigation": {
+ "Books": {
+ "detail": {
+ "route": "BooksDetails"
+ }
+ }
+ }
+ }
+ }
+ },
+ "BooksDetails": {
+ "type": "Component",
+ "id": "BooksDetailsList",
+ "name": "sap.fe.templates.ObjectPage",
+ "options": {
+ "settings": {
+ "entitySet": "Books"
+ }
+ }
+ }
+ }
+ },
+ "contentDensities": {
+ "compact": true,
+ "cozy": true
+ }
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "fullWidth": false
+ },
+ "sap.fiori": {
+ "registrationIds": [],
+ "archeType": "transactional"
+ }
+}
diff --git a/fiori/app/common.cds b/fiori/app/common.cds
new file mode 100644
index 00000000..614f03b3
--- /dev/null
+++ b/fiori/app/common.cds
@@ -0,0 +1,257 @@
+/*
+ Common Annotations shared by all apps
+*/
+
+using { sap.capire.bookshop as my } from '@capire/bookshop';
+using { sap.common } from '@capire/common';
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Lists
+//
+annotate my.Books with @(
+ Common.SemanticKey: [title],
+ UI: {
+ Identification: [{Value:title}],
+ SelectionFields: [ ID, author_ID, price, currency_code ],
+ LineItem: [
+ {Value: ID},
+ {Value: title},
+ {Value: author.name, Label:'{i18n>Author}'},
+ {Value: genre.name},
+ {Value: stock},
+ {Value: price},
+ {Value: currency.symbol, Label:' '},
+ ]
+ }
+) {
+ author @ValueList.entity:'Authors';
+};
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Details
+//
+annotate my.Books with @(
+ UI: {
+ HeaderInfo: {
+ TypeName: '{i18n>Book}',
+ TypeNamePlural: '{i18n>Books}',
+ Title: {Value: title},
+ Description: {Value: author.name}
+ },
+ }
+);
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Books Elements
+//
+annotate my.Books with {
+ ID @title:'{i18n>ID}' @UI.HiddenFilter;
+ title @title:'{i18n>Title}';
+ genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
+ author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
+ price @title:'{i18n>Price}';
+ stock @title:'{i18n>Stock}';
+ descr @UI.MultiLineText;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genres List
+//
+annotate my.Genres with @(
+ Common.SemanticKey: [name],
+ UI: {
+ SelectionFields: [ name ],
+ LineItem:[
+ {Value: name},
+ {Value: parent.name, Label: 'Main Genre'},
+ ],
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genre Details
+//
+annotate my.Genres with @(
+ UI: {
+ Identification: [{Value:name}],
+ HeaderInfo: {
+ TypeName: '{i18n>Genre}',
+ TypeNamePlural: '{i18n>Genres}',
+ Title: {Value: name},
+ Description: {Value: ID}
+ },
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
+ ],
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Genres Elements
+//
+annotate my.Genres with {
+ ID @title: '{i18n>ID}';
+ name @title: '{i18n>Genre}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Authors List
+//
+annotate my.Authors with @(
+ Common.SemanticKey: [name],
+ UI: {
+ Identification: [{Value:name}],
+ SelectionFields: [ name ],
+ LineItem:[
+ {Value: ID},
+ {Value: name},
+ {Value: dateOfBirth},
+ {Value: dateOfDeath},
+ {Value: placeOfBirth},
+ {Value: placeOfDeath},
+ ],
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Author Details
+//
+annotate my.Authors with @(
+ UI: {
+ HeaderInfo: {
+ TypeName: '{i18n>Author}',
+ TypeNamePlural: '{i18n>Authors}',
+ Title: {Value: name},
+ Description: {Value: dateOfBirth}
+ },
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
+ ],
+ }
+);
+
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Authors Elements
+//
+annotate my.Authors with {
+ ID @title:'{i18n>ID}' @UI.HiddenFilter;
+ name @title:'{i18n>Name}';
+ dateOfBirth @title:'{i18n>DateOfBirth}';
+ dateOfDeath @title:'{i18n>DateOfDeath}';
+ placeOfBirth @title:'{i18n>PlaceOfBirth}';
+ placeOfDeath @title:'{i18n>PlaceOfDeath}';
+}
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Languages List
+//
+annotate common.Languages with @(
+ Common.SemanticKey: [code],
+ Identification: [{Value:code}],
+ UI: {
+ SelectionFields: [ name, descr ],
+ LineItem:[
+ {Value: code},
+ {Value: name},
+ ],
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Language Details
+//
+annotate common.Languages with @(
+ UI: {
+ HeaderInfo: {
+ TypeName: '{i18n>Language}',
+ TypeNamePlural: '{i18n>Languages}',
+ Title: {Value: name},
+ Description: {Value: descr}
+ },
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
+ ],
+ FieldGroup#Details: {
+ Data: [
+ {Value: code},
+ {Value: name},
+ {Value: descr}
+ ]
+ },
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Currencies List
+//
+annotate common.Currencies with @(
+ Common.SemanticKey: [code],
+ Identification: [{Value:code}],
+ UI: {
+ SelectionFields: [ name, descr ],
+ LineItem:[
+ {Value: descr},
+ {Value: symbol},
+ {Value: code},
+ ],
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Currency Details
+//
+annotate common.Currencies with @(
+ UI: {
+ HeaderInfo: {
+ TypeName: '{i18n>Currency}',
+ TypeNamePlural: '{i18n>Currencies}',
+ Title: {Value: descr},
+ Description: {Value: code}
+ },
+ Facets: [
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
+ {$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
+ ],
+ FieldGroup#Details: {
+ Data: [
+ {Value: name},
+ {Value: symbol},
+ {Value: code},
+ {Value: descr}
+ ]
+ },
+ FieldGroup#Extended: {
+ Data: [
+ {Value: numcode},
+ {Value: minor},
+ {Value: exponent}
+ ]
+ },
+ }
+);
+
+////////////////////////////////////////////////////////////////////////////
+//
+// Currencies Elements
+//
+annotate common.Currencies with {
+ numcode @title:'{i18n>NumCode}';
+ minor @title:'{i18n>MinorUnit}';
+ exponent @title:'{i18n>Exponent}';
+}
diff --git a/fiori/app/reviews.html b/fiori/app/reviews.html
new file mode 100644
index 00000000..75af8860
--- /dev/null
+++ b/fiori/app/reviews.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/fiori/app/services.cds b/fiori/app/services.cds
new file mode 100644
index 00000000..595023e9
--- /dev/null
+++ b/fiori/app/services.cds
@@ -0,0 +1,12 @@
+/*
+ This model controls what gets served to Fiori frontends...
+*/
+
+using from './admin/fiori-service';
+using from './browse/fiori-service';
+using from './common';
+
+using from '@capire/common';
+
+// only works in case of embedded orders service
+using from '@capire/orders/app/orders/fiori-service';
diff --git a/fiori/db/hana/index.cds b/fiori/db/hana/index.cds
new file mode 100644
index 00000000..04822ad0
--- /dev/null
+++ b/fiori/db/hana/index.cds
@@ -0,0 +1,10 @@
+//
+// Add Author.age and .lifetime with a DB-specific function
+//
+
+using { AdminService } from '../schema';
+
+extend projection AdminService.Authors with {
+ YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,
+ YEAR(dateOfBirth) || ' – ' || YEAR(dateOfDeath) as lifetime : String
+}
diff --git a/fiori/db/schema.cds b/fiori/db/schema.cds
new file mode 100644
index 00000000..479fdbfb
--- /dev/null
+++ b/fiori/db/schema.cds
@@ -0,0 +1,8 @@
+using { sap.capire.bookshop } from '@capire/bookshop';
+
+// Forward-declare calculated fields to be filled in database-specific ways
+// TODO find a better way to have 'default' fields that still can be overwritten.
+extend bookshop.Authors with {
+ virtual age: Integer;
+ virtual lifetime: String;
+}
diff --git a/fiori/db/sqlite/index.cds b/fiori/db/sqlite/index.cds
new file mode 100644
index 00000000..019335ef
--- /dev/null
+++ b/fiori/db/sqlite/index.cds
@@ -0,0 +1,10 @@
+//
+// Add Author.age and .lifetime with a DB-specific function
+//
+
+using { AdminService } from '../schema';
+
+extend projection AdminService.Authors with {
+ strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer,
+ strftime('%Y',dateOfBirth) || ' – ' || strftime('%Y',dateOfDeath) as lifetime : String
+}
diff --git a/fiori/package.json b/fiori/package.json
new file mode 100644
index 00000000..414541d3
--- /dev/null
+++ b/fiori/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@capire/fiori",
+ "version": "1.0.0",
+ "dependencies": {
+ "@capire/bookshop": "*",
+ "@capire/reviews": "*",
+ "@capire/orders": "*",
+ "@capire/common": "*",
+ "@sap/cds": ">=4",
+ "express": "^4.17.1",
+ "passport": "^0.4.1"
+ },
+ "scripts": {
+ "start": "cds run --in-memory?",
+ "watch": "cds watch"
+ },
+ "cds": {
+ "hana": {
+ "deploy-format": "hdbtable"
+ },
+ "requires": {
+ "auth": {
+ "strategy": "dummy"
+ },
+ "ReviewsService": {
+ "kind": "odata",
+ "model": "@capire/reviews"
+ },
+ "OrdersService": {
+ "kind": "odata",
+ "model": "@capire/orders"
+ },
+ "db": {
+ "kind": "sql",
+ "[development]": {
+ "model": "db/sqlite"
+ },
+ "[production]": {
+ "model": "db/hana"
+ }
+ }
+ }
+ }
+}
diff --git a/fiori/server.js b/fiori/server.js
new file mode 100644
index 00000000..a8dc4298
--- /dev/null
+++ b/fiori/server.js
@@ -0,0 +1,18 @@
+const cds = require ('@sap/cds')
+
+cds.once('bootstrap',(app)=>{
+ app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
+ app.use ('/bookshop', _from('@capire/bookshop/app/vue/index.html'))
+ app.use ('/reviews', _from('@capire/reviews/app/vue/index.html'))
+})
+
+cds.once('served', require('./srv/mashup'))
+
+module.exports = cds.server
+
+
+// -----------------------------------------------------------------------
+// Helper for serving static content from npm-installed packages
+const {static} = require('express')
+const {dirname} = require('path')
+const _from = target => static (dirname (require.resolve(target)))
diff --git a/fiori/srv/mashup.cds b/fiori/srv/mashup.cds
new file mode 100644
index 00000000..97f21771
--- /dev/null
+++ b/fiori/srv/mashup.cds
@@ -0,0 +1,25 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Mashing up imported models...
+//
+
+using { sap.capire.bookshop.Books } from '@capire/bookshop';
+
+//
+// Extend Books with access to Reviews and average ratings
+//
+
+using { ReviewsService.Reviews } from '@capire/reviews';
+extend Books with {
+ reviews : Composition of many Reviews on reviews.subject = $self.ID;
+ rating : Decimal;
+}
+
+//
+// Extend Orders with Books as Products
+//
+
+using { sap.capire.orders.Orders_Items } from '@capire/orders';
+extend Orders_Items with {
+ book : Association to Books on product.ID = book.ID
+}
diff --git a/fiori/srv/mashup.js b/fiori/srv/mashup.js
new file mode 100644
index 00000000..e8acf174
--- /dev/null
+++ b/fiori/srv/mashup.js
@@ -0,0 +1,59 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Mashing up provided and required services...
+//
+module.exports = async()=>{ // called by server.js
+
+ const cds = require('@sap/cds')
+ const CatalogService = await cds.connect.to ('CatalogService')
+ const ReviewsService = await cds.connect.to ('ReviewsService')
+ const OrdersService = await cds.connect.to ('OrdersService')
+ const db = await cds.connect.to ('db')
+
+ // reflect entity definitions used below...
+ const { Books } = db.entities ('sap.capire.bookshop')
+
+ //
+ // Delegate requests to read reviews to the ReviewsService
+ // Note: prepend is neccessary to intercept generic default handler
+ //
+ CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
+ console.debug ('> delegating request to ReviewsService')
+ const [id] = req.params, { columns, limit } = req.query.SELECT
+ return ReviewsService.tx(req).read ('Reviews',columns).limit(limit).where({subject:String(id)})
+ }))
+
+ //
+ // Create an order with the OrdersService when CatalogService signals a new order
+ //
+ CatalogService.on ('OrderedBook', async (msg) => {
+ const { book, amount, buyer } = msg.data
+ const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
+ return OrdersService.tx(msg).create ('Orders').entries({
+ OrderNo: 'Order at '+ (new Date).toLocaleString(),
+ Items: [{ product:{ID:`${book}`}, title, price, amount }],
+ buyer, createdBy: buyer
+ })
+ })
+
+ //
+ // Update Books' average ratings when ReviewsService signals updatd reviews
+ //
+ ReviewsService.on ('reviewed', (msg) => {
+ console.debug ('> received:', msg.event, msg.data)
+ const { subject, rating } = msg.data
+ return UPDATE(Books,subject).with({rating})
+ // ^ Note: the framework will execute this and take care for db.tx
+ })
+
+ //
+ // Reduce stock of ordered books for orders are created from Orders admin UI
+ //
+ OrdersService.on ('OrderChanged', (msg) => {
+ console.debug ('> received:', msg.event, msg.data)
+ const { product, deltaAmount } = msg.data
+ return UPDATE (Books) .where ('ID =', product)
+ .and ('stock >=', deltaAmount)
+ .set ('stock -=', deltaAmount)
+ })
+}
diff --git a/fiori/test/requests.http b/fiori/test/requests.http
new file mode 100644
index 00000000..badacbfe
--- /dev/null
+++ b/fiori/test/requests.http
@@ -0,0 +1,77 @@
+
+@bookshop = http://localhost:4004
+@reviews-service = {{bookshop}}/reviews
+# Uncomment this when running a separate reviews service
+@reviews-service = http://localhost:4005/reviews
+
+
+
+#################################################
+#
+# Reviews Service
+#
+
+GET {{reviews-service}}/Reviews
+
+###
+
+POST {{reviews-service}}/Reviews
+Authorization: Basic {{$processEnv USER}}:
+Content-Type: application/json
+
+{"subject":"201", "title":"boo", "rating":3 }
+
+
+
+#################################################
+#
+# Bookshop Services
+#
+
+GET {{bookshop}}/browse/Books/201/reviews?
+&$select=rating,date,title
+&$top=3
+
+###
+
+GET {{bookshop}}/browse/Books(201)?
+&$select=ID,title,rating
+&$expand=reviews
+
+
+
+#################################################
+#
+# Orders Service, incl. draft choreography
+#
+@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
+
+GET {{bookshop}}/orders/Orders
+
+### Create order, still inactive
+POST {{bookshop}}/orders/Orders
+Content-Type: application/json
+
+{"ID": "{{newOrderID}}"}
+
+### Get inactive order. We have to specify `IsActiveEntity`.
+GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
+
+### Activate order using `.../.draftActivate`
+POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
+Content-Type: application/json
+
+### Get active order
+GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
+
+### Create author
+POST {{bookshop}}/admin/Authors
+Content-Type: application/json
+Authorization: Basic alice:
+
+{
+ "ID": 200,
+ "name": "William Shakespeare",
+ "dateOfBirth": "1564-04-26",
+ "dateOfDeath": "1616-04-23"
+}
diff --git a/multitenancy/logs-recent_4.txt b/multitenancy/logs-recent_4.txt
new file mode 100644
index 00000000..210d5c21
Binary files /dev/null and b/multitenancy/logs-recent_4.txt differ