Compare commits

..

1 Commits

Author SHA1 Message Date
Christian Georgi
18f49c9de8 Test: CSV UPSERT from different layers 2023-06-06 10:29:09 +02:00
96 changed files with 4663 additions and 4305 deletions

31
.eslintrc Normal file
View File

@@ -0,0 +1,31 @@
{
"extends": [
"plugin:@sap/cds/recommended",
"eslint:recommended"
],
"env": {
"browser": true,
"es2022": true,
"node": true,
"jest": true,
"mocha": true
},
"globals": {
"SELECT": true,
"INSERT": true,
"UPSERT": true,
"UPDATE": true,
"DELETE": true,
"CREATE": true,
"DROP": true,
"CDL": true,
"CQL": true,
"cds": true
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off",
"require-await":"warn",
"no-unused-vars": ["warn", { "argsIgnorePattern": "_" }]
}
}

View File

@@ -5,16 +5,4 @@ updates:
directory: / directory: /
versioning-strategy: increase-if-necessary versioning-strategy: increase-if-necessary
schedule: schedule:
interval: weekly interval: daily
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
ignore:
- dependency-name: "chai"
# chai 5 doesn't work atm w/ cds.test, TODO fix that in cds.test
versions: ["5.x"]
- dependency-name: "chai-as-promised"
# chai-as-promised 8 doesn't work atm w/ cds.test, TODO fix that in cds.test
versions: ["8.x"]

View File

@@ -11,28 +11,19 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [22.x, 20.x, 18.x] node-version: [18.x, 16.x, 14.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm i -g npm@8
- run: npm ci - run: npm ci
- run: npm test - run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- run: npm run lint

3
.gitignore vendored
View File

@@ -21,5 +21,4 @@ reviews/db/test.db
*.sqlite *.sqlite
*.db *.db
@types/ **/@types
@cds-models/

View File

@@ -4,11 +4,13 @@
// List of extensions which should be recommended for users of this workspace. // List of extensions which should be recommended for users of this workspace.
"recommendations": [ "recommendations": [
"qwtel.sqlite-viewer",
"sapse.vscode-cds", "sapse.vscode-cds",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mechatroner.rainbow-csv", "mechatroner.rainbow-csv",
"humao.rest-client", "humao.rest-client",
"alexcvzz.vscode-sqlite",
"hbenl.vscode-mocha-test-adapter",
"sdras.night-owl", "sdras.night-owl",
"vsls-contrib.codetour" "vsls-contrib.codetour"
], ],

12
.vscode/settings.json vendored
View File

@@ -13,5 +13,15 @@
"**/cds/lib/req/cds-context.js", "**/cds/lib/req/cds-context.js",
"**/odata-v4/okra/**" "**/odata-v4/okra/**"
] ]
} },
"mochaExplorer.debuggerConfig": "Debug Mocha Tests",
"mochaExplorer.parallel": true,
"eslint.probe": [
"cds",
"csn",
"csv",
"csv (semicolon)",
"tsv",
"tab"
]
} }

View File

@@ -1,19 +1,13 @@
# Welcome to cap/samples # Welcome to cap/samples
Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). Find here a collection of samples for the [SAP Cloud Application Programming Model](https://cap.cloud.sap) organized in a simplistic [monorepo setup](samples.md#all-in-one-monorepo). → See [**Overview** of contained samples](samples.md)
[See **Overview** of contained samples](samples.md):
![](etc/samples.drawio.svg)
![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg) ![](https://github.com/SAP-samples/cloud-cap-samples/workflows/CI/badge.svg)
[![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-cap-samples)](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples) [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-cap-samples)](https://api.reuse.software/info/github.com/SAP-samples/cloud-cap-samples)
### Preliminaries ### Preliminaries
1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/jumpstart)) 1. Ensure you have the latest LTS version of Node.js installed (see [Getting Started](https://cap.cloud.sap/docs/get-started/))
2. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally: 2. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
```sh ```sh
@@ -36,7 +30,7 @@ cd samples
In the samples folder run: In the samples folder run:
```sh ```sh
npm ci npm install
``` ```
### Run ### Run
@@ -60,6 +54,24 @@ npx jest
``` ```
> While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests. > While mocha is a bit smaller and faster, jest runs tests in parallel and isolation, which allows to run all tests.
### Serve `npm`
We've included a simple npm registry mock, which allows you to do an `npm install @capire/<package>` locally. Use it as follows:
1. Start the @capire registry:
```sh
npm run registry
```
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
2. Install one of the @capire packages wherever you like, for example:
```sh
npm add @capire/common @capire/bookshop
```
## Code Tours ## Code Tours
Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned! Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned!

View File

@@ -19,7 +19,7 @@ const books = Vue.createApp ({
search: ({target:{value:v}}) => books.fetch(v && '&$search='+v), search: ({target:{value:v}}) => books.fetch(v && '&$search='+v),
async fetch (etc='') { async fetch (etc='') {
const {data} = await GET(`/ListOfBooks?$expand=genre($select=name),currency($select=symbol)${etc}`) const {data} = await GET(`/ListOfBooks?$expand=genre,currency${etc}`)
books.list = data.value books.list = data.value
}, },
@@ -77,7 +77,7 @@ function csrfToken (request) {
document.csrfToken = token document.csrfToken = token
request.headers['x-csrf-token'] = document.csrfToken request.headers['x-csrf-token'] = document.csrfToken
return request return request
}).catch(() => { }).catch(_ => {
document.csrfToken = null // set mark to not try again document.csrfToken = null // set mark to not try again
return request return request
}) })

View File

@@ -1,5 +1,5 @@
ID,locale,title,descr ID_texts;ID;locale;title;descr
201,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (18181848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts." a5fe2682-fe60-4dc0-9218-695e501e42e6;201;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (18181848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
201,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme dEllis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal." 4edebff3-8cde-4761-8cd3-bae7834fc5f5;201;fr;Les Hauts de Hurlevent;Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme dEllis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal.
207,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte" b649031b-aacc-4dc3-838f-c36bae5d266e;207;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
252,de,Eleonora,"“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit." aa62f0e1-763c-4c92-a304-50c2b86e21f4;252;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.
1 ID_texts ID locale title descr
2 a5fe2682-fe60-4dc0-9218-695e501e42e6 201 de Sturmhöhe Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
3 4edebff3-8cde-4761-8cd3-bae7834fc5f5 201 fr Les Hauts de Hurlevent Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal.
4 b649031b-aacc-4dc3-838f-c36bae5d266e 207 de Jane Eyre Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
5 aa62f0e1-763c-4c92-a304-50c2b86e21f4 252 de Eleonora “Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.

View File

@@ -0,0 +1,3 @@
code;descr
EUR;EUR from bookshop
USD;USD from bookshop
1 code descr
2 EUR EUR from bookshop
3 USD USD from bookshop

View File

@@ -1,15 +1,18 @@
const cds = require('@sap/cds')
/** /**
* In order to keep basic bookshop sample as simple as possible, we don't add * In order to keep basic bookshop sample as simple as possible, we don't add
* reuse dependencies. This db/init.js ensures we still have a minimum set of * reuse dependencies. This db/init.js ensures we still have a minimum set of
* currencies, if not obtained through @capire/common. * currencies, if not obtained through @capire/common.
*/ */
// NOTE: We use cds.on('served') to delay the UPSERTs after the db init module.exports = async (tx)=>{
// to run after all INSERTs from .csv files happened.
module.exports = cds.on('served', ()=> const has_common = tx.model.definitions['sap.common.Currencies']?.elements.numcode
UPSERT.into ('sap.common.Currencies') .columns ( if (has_common) return
const already_filled = await tx.exists('sap.common.Currencies',{code:'EUR'})
if (already_filled) return
await tx.run (INSERT.into ('sap.common.Currencies') .columns (
[ 'code', 'symbol', 'name' ] [ 'code', 'symbol', 'name' ]
) .rows ( ) .rows (
[ 'EUR', '€', 'Euro' ], [ 'EUR', '€', 'Euro' ],
@@ -17,5 +20,5 @@ module.exports = cds.on('served', ()=>
[ 'GBP', '£', 'British Pound' ], [ 'GBP', '£', 'British Pound' ],
[ 'ILS', '₪', 'Shekel' ], [ 'ILS', '₪', 'Shekel' ],
[ 'JPY', '¥', 'Yen' ], [ 'JPY', '¥', 'Yen' ],
) ))
) }

View File

@@ -3,9 +3,9 @@ namespace sap.capire.bookshop;
entity Books : managed { entity Books : managed {
key ID : Integer; key ID : Integer;
title : localized String(111) @mandatory; @mandatory title : localized String(111);
descr : localized String(1111); descr : localized String(1111);
author : Association to Authors @mandatory; @mandatory author : Association to Authors;
genre : Association to Genres; genre : Association to Genres;
stock : Integer; stock : Integer;
price : Decimal; price : Decimal;
@@ -15,7 +15,7 @@ entity Books : managed {
entity Authors : managed { entity Authors : managed {
key ID : Integer; key ID : Integer;
name : String(111) @mandatory; @mandatory name : String(111);
dateOfBirth : Date; dateOfBirth : Date;
dateOfDeath : Date; dateOfDeath : Date;
placeOfBirth : String; placeOfBirth : String;

2
bookshop/index.js Normal file
View File

@@ -0,0 +1,2 @@
const { CatalogService } = require('./srv/cat-service')
module.exports = { CatalogService }

View File

@@ -9,16 +9,19 @@
"index.cds", "index.cds",
"index.js" "index.js"
], ],
"devDependencies": {
"@cap-js/sqlite": "*"
},
"dependencies": { "dependencies": {
"@sap/cds": ">=7", "@sap/cds": ">=5.9",
"express": "^4.17.1" "express": "^4.17.1",
"passport": ">=0.4.1"
}, },
"scripts": { "scripts": {
"genres": "cds serve test/genres.cds", "genres": "cds serve test/genres.cds",
"start": "cds-serve", "start": "cds run",
"watch": "cds watch" "watch": "cds watch"
},
"cds": {
"requires": {
"db": "sql"
}
} }
} }

View File

@@ -20,12 +20,12 @@ npm run watch
| Links to capire | Sample files / folders | | Links to capire | Sample files / folders |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ | | --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/jumpstart#project-structure) | [`./`](./) | | [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
| [Domain Modeling with CDS](https://cap.cloud.sap/docs/guides/domain-modeling) | [`./db/schema.cds`](./db/schema.cds) | | [Domain Modeling with CDS](https://cap.cloud.sap/docs/guides/domain-models) | [`./db/schema.cds`](./db/schema.cds) |
| [Defining Services](https://cap.cloud.sap/docs/guides/providing-services#modeling-services) | [`./srv/*.cds`](./srv) | | [Defining Services](https://cap.cloud.sap/docs/guides/services#defining-services) | [`./srv/*.cds`](./srv) |
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services) | [`./srv/*.cds`](./srv) | | [Single-purposed Services](https://cap.cloud.sap/docs/guides/services#single-purposed-services) | [`./srv/*.cds`](./srv) |
| [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 | | [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
| [Using Databases](https://cap.cloud.sap/docs/guides/databases) | [`./db/data/*.csv`](./db/data) | | [Using Databases](https://cap.cloud.sap/docs/guides/databases) | [`./db/data/*.csv`](./db/data) |
| [Adding Custom Logic](https://cap.cloud.sap/docs/guides/providing-services#adding-custom-logic) | [`./srv/*.js`](./srv) | | [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl) | [`./srv/*.js`](./srv) |
| Adding Tests | [`./test`](./test) | | Adding Tests | [`./test`](./test) |
| [Sharing for Reuse](https://cap.cloud.sap/docs/guides/extensibility/composition) | [`./index.cds`](./index.cds) | | [Sharing for Reuse](https://cap.cloud.sap/docs/guides/reuse-and-compose) | [`./index.cds`](./index.cds) |

View File

@@ -1,5 +1,5 @@
using { sap.capire.bookshop as my } from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(requires:'admin', path:'/admin') { service AdminService @(requires:'admin') {
entity Books as projection on my.Books; entity Books as projection on my.Books;
entity Authors as projection on my.Authors; entity Authors as projection on my.Authors;
} }

View File

@@ -1,14 +1,13 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
module.exports = class AdminService extends cds.ApplicationService { init(){ module.exports = class AdminService extends cds.ApplicationService { init(){
this.before (['NEW','CREATE'],'Authors', genid) this.before ('NEW','Authors', genid)
this.before (['NEW','CREATE'],'Books', genid) this.before ('NEW','Books', genid)
return super.init() return super.init()
}} }}
/** Generate primary keys for target entity in request */ /** Generate primary keys for target entity in request */
async function genid (req) { async function genid (req) {
if (req.data.ID) return const {ID} = await cds.tx(req).run (SELECT.one.from(req.target).columns('max(ID) as ID'))
const {id} = await SELECT.one.from(req.target).columns('max(ID) as id') req.data.ID = ID - ID % 100 + 100 + 1
req.data.ID = id + 4 // Note: that is not safe! ok for this sample only.
} }

View File

@@ -5,34 +5,25 @@ class CatalogService extends cds.ApplicationService { init() {
const { Books } = cds.entities ('sap.capire.bookshop') const { Books } = cds.entities ('sap.capire.bookshop')
const { ListOfBooks } = this.entities const { ListOfBooks } = this.entities
// Add some discount for overstocked books
this.after('each', ListOfBooks, book => {
if (book.stock > 111) book.title += ` -- 11% discount!`
})
// Reduce stock of ordered books if available stock suffices // Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => { this.on ('submitOrder', async req => {
let { book:id, quantity } = req.data const {book,quantity} = req.data
let book = await SELECT.from (Books, id, b => b.stock) if (quantity < 1) return req.reject (400,`quantity has to be 1 or more`)
let b = await SELECT `stock` .from (Books,book)
// Validate input data if (!b) return req.error (404,`Book #${book} doesn't exist`)
if (!book) return req.error (404, `Book #${id} doesn't exist`) let {stock} = b
if (quantity < 1) return req.error (400, `quantity has to be 1 or more`) if (quantity > stock) return req.reject (409,`${quantity} exceeds stock for book #${book}`)
if (quantity > book.stock) return req.error (409, `${quantity} exceeds stock for book #${id}`) await UPDATE (Books,book) .with ({ stock: stock -= quantity })
// Reduce stock in database and return updated stock value
await UPDATE (Books, id) .with ({ stock: book.stock -= quantity })
return book
})
// Emit event when an order has been submitted
this.after('submitOrder', async (_,req) => {
let { book, quantity } = req.data
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id }) await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
return { stock }
})
// Add some discount for overstocked books
this.after ('READ', ListOfBooks, each => {
if (each.stock > 111) each.title += ` -- 11% discount!`
}) })
// Delegate requests to the underlying generic service
return super.init() return super.init()
}} }}
module.exports = CatalogService module.exports = { CatalogService }

View File

@@ -1,7 +1,7 @@
/** /**
* Exposes user information * Exposes user information
*/ */
service UserService @(path: '/user') { service UserService {
/** /**
* The current user * The current user
*/ */

View File

@@ -3,15 +3,15 @@
# Genres # Genres
# #
GET http://localhost:4004/odata/v4/test/Genres? GET http://localhost:4004/test/Genres?
### ###
GET http://localhost:4004/odata/v4/test/Genres? GET http://localhost:4004/test/Genres?
&$filter=parent_ID eq null&$select=name &$filter=parent_ID eq null&$select=name
&$expand=children($select=name) &$expand=children($select=name)
### ###
POST http://localhost:4004/odata/v4/test/Genres? POST http://localhost:4004/test/Genres?
Content-Type: application/json Content-Type: application/json
{ "ID":100, "name":"Some Sample Genres...", "children":[ { "ID":100, "name":"Some Sample Genres...", "children":[
@@ -26,13 +26,13 @@ Content-Type: application/json
]} ]}
### ###
GET http://localhost:4004/odata/v4/test/Genres(100)? GET http://localhost:4004/test/Genres(100)?
# &$expand=children # &$expand=children
# &$expand=children($expand=children($expand=children($expand=children))) # &$expand=children($expand=children($expand=children($expand=children)))
### ###
DELETE http://localhost:4004/odata/v4/test/Genres(103) DELETE http://localhost:4004/test/Genres(103)
### ###
DELETE http://localhost:4004/odata/v4/test/Genres(100) DELETE http://localhost:4004/test/Genres(100)
### ###

View File

@@ -1,12 +0,0 @@
Books = Bücher
Book = Buch
Title = Titel
Description = Beschreibung
Stock = Bestand
Image = Bild
Price = Preis
Currency = Währung
Authors = Autoren
Author = Autor
AuthorID = ID des Autors

View File

@@ -1,21 +0,0 @@
Books = Books
Book = Book
ID = ID
Title = Title
Description = Description
Stock = Stock
Image = Image
Price = Price
Currency = Currency
Authors = Authors
Author = Author
AuthorID = Author''s ID
Name = Name
DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Genres = Genres
Genre = Genre

View File

@@ -1,20 +0,0 @@
Books = Livres
Book = Livre
Title = Titre
Description = Description
Stock = Action
Image = Image
Price = Prix
Currency = Devise
Authors = Auteurs
Author = Auteur
AuthorID = ID de l''auteur
Name = Nom
DateOfBirth = Date de naissance
DateOfDeath = Date de décès
PlaceOfBirth = Lieu de naissance
PlaceOfDeath = Lieu de décès
Genres = Genre
Genre = Genre

View File

@@ -1,34 +0,0 @@
400 = Ungültige Anfrage
401 = Nicht autorisiert
403 = Verboten
404 = Nicht gefunden
405 = Methode nicht zulässig
406 = Nicht akzeptabel
407 = Proxy-Authentifizierung erforderlich
408 = Anfrage-Timeout
409 = Konflikt
410 = Weg
411 = Erforderliche Länge
412 = Vorbedingung fehlgeschlagen
413 = Nutzlast zu groß
414 = URI zu lang
415 = Nicht unterstützter Medientyp
416 = Bereich nicht erfüllbar
417 = Erwartung fehlgeschlagen
422 = Nicht verarbeitbarer Inhalt
424 = Fehlgeschlagene Abhängigkeit
428 = Vorbedingung erforderlich
429 = Zu viele Anfragen
431 = Anfrage-Headerfelder zu groß
451 = Aus rechtlichen Gründen nicht verfügbar
500 = Interner Serverfehler
501 = Der Server unterstützt die zur Erfüllung der Anfrage erforderliche Funktionalität nicht
502 = Ungültiges Gateway
503 = Dienst nicht verfügbar
504 = Gateway-Timeout
ASSERT_RANGE = Wert {0} liegt nicht im angegebenen Bereich [{1}, {2}]
ASSERT_FORMAT = Wert "{0}" liegt nicht im angegebenen Format "{1}"
ASSERT_ARRAY = Wert muss ein Array sein
ASSERT_ENUM = Wert {0} ist gemäß Enumerationsdeklaration {{1}} ungültig
ASSERT_NOT_NULL = Wert ist erforderlich

View File

@@ -1,34 +0,0 @@
400 = Bad Request
401 = Unauthorized
403 = Forbidden
404 = Not Found
405 = Method Not Allowed
406 = Not Acceptable
407 = Proxy Authentication Required
408 = Request Timeout
409 = Conflict
410 = Gone
411 = Length Required
412 = Precondition Failed
413 = Payload Too Large
414 = URI Too Long
415 = Unsupported Media Type
416 = Range Not Satisfiable
417 = Expectation Failed
422 = Unprocessable Content
424 = Failed Dependency
428 = Precondition Required
429 = Too Many Requests
431 = Request Header Fields Too Large
451 = Unavailable For Legal Reasons
500 = Internal Server Error
501 = The server does not support the functionality required to fulfill the request
502 = Bad Gateway
503 = Service Unavailable
504 = Gateway Timeout
ASSERT_RANGE = Value {0} is not in specified range [{1}, {2}]
ASSERT_FORMAT = Value "{0}" is not in specified format "{1}"
ASSERT_ARRAY = Value must be an array
ASSERT_ENUM = Value {0} is invalid according to enum declaration {{1}}
ASSERT_NOT_NULL = Value is required

View File

@@ -1,34 +0,0 @@
400 = Requête incorrecte
401 = Non autorisée
403 = Interdite
404 = Introuvable
405 = Méthode non autorisée
406 = Non acceptable
407 = Authentification proxy requise
408 = Délai d''expiration de la requête
409 = Conflit
410 = Disparu
411 = Longueur requise
412 = Échec de la condition préalable
413 = Charge utile trop importante
414 = URI trop longue
415 = Type de média non pris en charge
416 = Plage non satisfaisante
417 = Échec de l''attente
422 = Contenu non traitable
424 = Dépendance échouée
428 = Condition préalable requise
429 = Trop de requêtes
431 = Champs d''en-tête de requête trop importants
451 = Indisponible pour des raisons juridiques
500 = Erreur interne du serveur
501 = Le serveur ne prend pas en charge la fonctionnalité requise pour répondre à la requête
502 = Passerelle incorrecte
503 = Service indisponible
504 = Délai d''attente de la passerelle
ASSERT_RANGE = La valeur {0} n''est pas dans la plage spécifiée [{1}, {2}]
ASSERT_FORMAT = La valeur "{0}" n''est pas au format spécifié "{1}"
ASSERT_ARRAY = La valeur doit être un tableau
ASSERT_ENUM = La valeur {0} n''est pas valide selon la déclaration d''énumération {{1}}
ASSERT_NOT_NULL = La valeur est obligatoire

View File

@@ -20,11 +20,15 @@
"kind": "odata", "kind": "odata",
"model": "@capire/orders" "model": "@capire/orders"
}, },
"messaging": true, "messaging": {
"db": true "[development]": { "kind": "file-based-messaging" },
"[hybrid]": { "kind": "enterprise-messaging-shared" },
"[production]": { "kind": "enterprise-messaging" }
}, },
"log": { "db": {
"service": true "kind": "sql"
} }
},
"log": { "service": true }
} }
} }

View File

@@ -5,16 +5,18 @@ cds.once('served', require('./srv/mashup'))
// Add routes to UIs from imported packages // Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{ cds.once('bootstrap',(app)=>{
try {
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue') app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders') app.serve ('/orders') .from('@capire/orders','app/orders')
app.serve ('/data') .from('@capire/data-viewer','app/viewer') app.serve ('/data') .from('@capire/data-viewer','app/viewer')
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') throw new Error('Run "npm ci" to install the required dependencies', { cause: err })
throw err
}
}) })
// Add Swagger UI // Add Swagger UI
require('./srv/swagger-ui') require('./srv/swagger-ui')
// Returning cds.server
module.exports = cds.server
// For didactic reasons in capire
const { ReviewsService, OrdersService } = cds.requires
if (!ReviewsService?.credentials && !OrdersService?.credentials) cds.requires.messaging = false

View File

@@ -4,16 +4,20 @@
// respective reuse packages and services // respective reuse packages and services
// //
using { sap.capire.bookshop.Books } from '@capire/bookshop';
// //
// Extend Books with access to Reviews and average ratings // Extend Books with access to Reviews and average ratings
// //
using { sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews'; using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with { extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID; reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : type of Reviews:rating; // average rating
numberOfReviews : Integer @title : '{i18n>NumberOfReviews}'; @Common.Label : '{i18n>Rating}'
rating : Decimal;
@Common.Label : '{i18n>NumberOfReviews}'
numberOfReviews : Integer;
} }
@@ -21,11 +25,18 @@ extend Books with {
// Extend Orders with Books as Products // Extend Orders with Books as Products
// //
using { sap.capire.orders.Orders } from '@capire/orders'; using { sap.capire.orders.Orders } from '@capire/orders';
extend Orders:Items with { extend Orders with {
extend Items with {
book : Association to Books on product.ID = book.ID book : Association to Books on product.ID = book.ID
} }
}
// Ensure models from all imported packages are loaded
// Add orders fiori app (in case of embedded orders service)
using from '@capire/orders/app/fiori'; using from '@capire/orders/app/fiori';
// Add data browser
using from '@capire/data-viewer'; using from '@capire/data-viewer';
// Incorporate pre-build extensions from...
using from '@capire/common'; using from '@capire/common';

View File

@@ -18,7 +18,7 @@ module.exports = async()=>{ // called by server.js
// Note: prepend is neccessary to intercept generic default handler // Note: prepend is neccessary to intercept generic default handler
// //
CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => {
console.debug ('> delegating request to ReviewsService') // eslint-disable-line no-console console.debug ('> delegating request to ReviewsService')
const [id] = req.params, { columns, limit } = req.query.SELECT const [id] = req.params, { columns, limit } = req.query.SELECT
return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)}) return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)})
})) }))
@@ -28,8 +28,8 @@ module.exports = async()=>{ // called by server.js
// //
CatalogService.on ('OrderedBook', async (msg) => { CatalogService.on ('OrderedBook', async (msg) => {
const { book, quantity, buyer } = msg.data const { book, quantity, buyer } = msg.data
const { title, price } = await db.read (Books, book, b => { b.title, b.price }) const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
return OrdersService.create ('Orders').entries({ return OrdersService.tx(msg).create ('Orders').entries({
OrderNo: 'Order at '+ (new Date).toLocaleString(), OrderNo: 'Order at '+ (new Date).toLocaleString(),
Items: [{ product:{ID:`${book}`}, title, price, quantity }], Items: [{ product:{ID:`${book}`}, title, price, quantity }],
buyer, createdBy: buyer buyer, createdBy: buyer
@@ -40,7 +40,7 @@ module.exports = async()=>{ // called by server.js
// Update Books' average ratings when ReviewsService signals updated reviews // Update Books' average ratings when ReviewsService signals updated reviews
// //
ReviewsService.on ('reviewed', (msg) => { ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console console.debug ('> received:', msg.event, msg.data)
const { subject, count, rating } = msg.data const { subject, count, rating } = msg.data
return UPDATE(Books,subject).with({ numberOfReviews:count, rating }) return UPDATE(Books,subject).with({ numberOfReviews:count, rating })
}) })
@@ -49,7 +49,7 @@ module.exports = async()=>{ // called by server.js
// Reduce stock of ordered books for orders are created from Orders admin UI // Reduce stock of ordered books for orders are created from Orders admin UI
// //
OrdersService.on ('OrderChanged', (msg) => { OrdersService.on ('OrderChanged', (msg) => {
console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console console.debug ('> received:', msg.event, msg.data)
const { product, deltaQuantity } = msg.data const { product, deltaQuantity } = msg.data
return UPDATE (Books) .where ('ID =', product) return UPDATE (Books) .where ('ID =', product)
.and ('stock >=', deltaQuantity) .and ('stock >=', deltaQuantity)

View File

@@ -12,12 +12,11 @@
# #
GET {{reviews-service}}/Reviews GET {{reviews-service}}/Reviews
Authorization: Basic me:
### ###
POST {{reviews-service}}/Reviews POST {{reviews-service}}/Reviews
Authorization: Basic me: Authorization: Basic {{$processEnv USER}}:
Content-Type: application/json Content-Type: application/json
{"subject":"201", "title":"boo", "rating":3 } {"subject":"201", "title":"boo", "rating":3 }
@@ -51,23 +50,23 @@ Accept-Language: de
# #
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e @newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
GET {{bookshop}}/odata/v4/orders/Orders GET {{bookshop}}/orders/Orders
### Create order, still inactive ### Create order, still inactive
POST {{bookshop}}/odata/v4/orders/Orders POST {{bookshop}}/orders/Orders
Content-Type: application/json Content-Type: application/json
{"ID": "{{newOrderID}}"} {"ID": "{{newOrderID}}"}
### Get inactive order. We have to specify `IsActiveEntity`. ### Get inactive order. We have to specify `IsActiveEntity`.
GET {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=false) GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
### Activate order using `.../<servicename>.draftActivate` ### Activate order using `.../<servicename>.draftActivate`
POST {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
Content-Type: application/json Content-Type: application/json
### Get active order ### Get active order
GET {{bookshop}}/odata/v4/orders/Orders(ID={{newOrderID}},IsActiveEntity=true) GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
### Create author ### Create author
POST {{bookshop}}/admin/Authors POST {{bookshop}}/admin/Authors

View File

@@ -1 +0,0 @@
// dummy to auto-load the plugin

View File

@@ -4,12 +4,5 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@sap/cds": "*" "@sap/cds": "*"
},
"cds": {
"requires": {
"@capire/common/data": {
"model": "@capire/common"
}
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* global Vue axios */ //> from vue.html /* global Vue axios */ //> from vue.html
const GET = (url) => axios.get('/odata/v4/-data'+url) const GET = (url) => axios.get('/-data'+url)
const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def const storageGet = (key, def) => localStorage.getItem('data-viewer:'+key) || def
const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val) const storageSet = (key, val) => localStorage.setItem('data-viewer:'+key, val)
const columnKeysFirst = (c1, c2) => { const columnKeysFirst = (c1, c2) => {

View File

@@ -20,7 +20,6 @@
* The actual data, organized by column name * The actual data, organized by column name
*/ */
entity Data @cds.persistence.skip { entity Data @cds.persistence.skip {
key ID : String; // to be OData-compliant
record : array of { record : array of {
column : String; column : String;
data : String; data : String;

View File

@@ -18,7 +18,7 @@ class DataService extends cds.ApplicationService { init(){
.sort((e1, e2) => e1.name.localeCompare(e2.name)) .sort((e1, e2) => e1.name.localeCompare(e2.name))
.map(e => { .map(e => {
const columns = Object.entries(e.elements) const columns = Object.entries(e.elements)
.filter(([,el]) => !(el instanceof cds.Association)) // exclude assocs+compositions .filter(([_, el]) => !(el instanceof cds.Association)) // exclude assocs+compositions
.map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }}) .map(([name, el]) => { return { name, type: el.type, isKey:!!el.key }})
return { name: e.name, columns } return { name: e.name, columns }
}) })
@@ -36,11 +36,8 @@ class DataService extends cds.ApplicationService { init(){
const dataSource = findDataSource(dataSourceName, entityName) const dataSource = findDataSource(dataSourceName, entityName)
const res = await dataSource.run(query) const res = await dataSource.run(query)
return res.map((line) => { return res.map((line) => {
const record = Object.entries(line).map(([column, data]) => ({ column, data })) const record = Object.entries(line).map(([column, data]) => {return {column, data}})
return { return { record }
record,
ID: cds.utils.uuid() // just to be OData-compliant
}
}) })
}) })
@@ -49,7 +46,6 @@ class DataService extends cds.ApplicationService { init(){
module.exports = { DataService } module.exports = { DataService }
/** @returns {cds.Service} */
function findDataSource(dataSourceName, entityName) { function findDataSource(dataSourceName, entityName) {
for (let srv of Object.values(cds.services)) { // all connected services for (let srv of Object.values(cds.services)) { // all connected services
if (!srv.name) continue // FIXME intermediate/pending in cds.services ? if (!srv.name) continue // FIXME intermediate/pending in cds.services ?

View File

@@ -1,20 +0,0 @@
import cds from '@sap/cds/eslint.config.mjs'
export default [ ...cds.recommended ]
//
// The above is the recommended minimalistic eslint config, just using
// recommended defaults provided by cds. Alternatively, go for enhanced
// project-specific options, but only if really required, like this:
//
// export default [ ...cds.recommended, {
// rules: {
// 'complexity': [ 'warn', 66 ],
// 'require-await': 'warn',
// 'no-await-in-loop': 'warn',
// },
// }, {
// ignores: [
// '**/webapp/**'
// ]
// }]
//

View File

@@ -1,13 +1,13 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="357px" height="283px" viewBox="-0.5 -0.5 357 283" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7VrZcqM4FP0av7q0L4+T9DIvU9VVeZhnFtlQwRYly7HTX98XgzBgOpNJG7eZGlKVwNECuudwdSSyoI+b41cXldlfNjXFgqD0uKCfFoQIIuF3BbzWACO6BtYuT2sInYGn/LupQRzQfZ6aXYPVkLe28HnZBxO73ZrE97DIOXvoV1vZIu0BZbQ2vceogKckKsxFtb/z1Gc1qsKwKvxPk6+zcGcsmvHFUfK8dna/be63IBQxXpdtotBRc9NdFqX20IHo5wV9dNb6+mxzfDRFFdd+zL78pLR9aGe2/j0NSN3gJSr2zbgXDCVRmTtTNSIi2pQL+rCNd9UfuC6g24fYwdnad5EAxNY+7zJbhgLoJT5XPg3Zv4YYw+jL6jQzx2htt1ClNC7fGG/cGf0WIAjOwyHLvXkqo6RqdgDhAZb5TQFXGE5X+dEELVXXu2fjkyri6FRYFI+2sO50c7paGZEkVSXv7LPplKRSxwi1JYF+0g6hG98m5C/GeXPsQE28vxoLD+9eoUpTSiSrm4QXQ9L6+nCWmW7kkXUUphosaoS9bns+8wsnDcXjdNNJ6Pa2an73fKvEjPMdK874zfimktyMb3Ztvp15yQ2k1rtnO+VGpWyMbUViKsRUbFOB+2xjfTO2+bXZTuxmA8TdPdkrXv1UuN36Dl4fYyIQp+NWImDhRbyBCMS1RWBdatwc3vjIqNVofheJMvFqKrKxEr+NbHlJ9pATs03/qOwwXCVFtNvlST/EvZBeBgiaN/GXp/LI+d71INRfTsdbgd3ZvUtML2FBp2vjew7FpMGd/yT4neDykeAGzJki8vlL39OPRby5wzebw/OeuSW4P3lzRPp91MNpmnWN9kVPYmD7+KCnOgoXPZ0k0A78XapQ81aFuHdVwKu75Iq2B+knAKrJslNKqfyYZIhmS4ZFewzXDQgvO6VC0Kn0pOetJzJzPTH+NtHX0hN7W7bX01PY4pmroNjMBcWHgppET3ygJzaZnIJlu5rl3e3Lssj/d73/wvXecp2LyUj+GFD4BJ2A4tD9M2hwys1oVtJC0miyRaoeEHjDbSk8sg85pwkgCLA7A4QsNI8pYOgp5CA7T+QpyGCqueIkMLLVOXdJibtSFAaqJVKcICw1ZIuw+dAuRzQoQSKGKFeSsY+aVIyXRGMmtSJIEBk+jrRrKw2yJpprwTAYmek86shm6qz0FL6Z3q1JxVpAClKnL8MaCc4HmUMuYaUDVhXWuUroD267EFClolhKLhHSlA1uwtmSUsGY4lphitRkarr6ruwqty7/T5ubCyczovl3f3Qjt3Q3974rO0gA/5hL6IjbEb/V3Ei9FFIpyBuKcKF0f5KozA3lEqYQLDimUvyCuaGQSSFzYMkoHtwFvI1gkGGwokwJWHBdKXvA5fkfQOrq5/+woZ9/AA==&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(0, 68, 85);"> <svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="347px" height="279px" viewBox="-0.5 -0.5 347 279" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vlbb+I4FP41vCLfL4/T7szuy0oj9WGfczEkasCRMYXur98TEufeqpoBCtUGCezv+Hq+z/bBWdDHzfFPF5XZ3zY1xYKg9LigfywI0QLBdwW81oDUrAbWLk9rCHXAU/6vqUEc0H2eml2D1ZC3tvB5OQQTu92axA+wyDl7GBZb2SIdAGW0NoNhVMBTEhVmUuyfPPVZjSoiO/wvk6+z0DMWurbEUfK8dna/bfpbEIq/VZ/avIlCW02/uyxK7aEH0e8L+uis9XVqc3w0ReXaodt+vGFtx+3M1n+kAqkrvETFvpn6gqEkKnNnqkpERJtyQR+28a76gXwBzT7EDlJr30cCEFv7vMtsGQzQStwVPk3ZvwY3w+zLKpmZY7S2WyhSGpdvjDeuQ38GCJzzcMhyb57KKKmqHUB7gGV+U0AOQ3KVH02QU5XfPRufVB5HJ2NRPNrCulPndLUyIkmqQt7ZZ9OzpFLHCLWWoADSTuHFOG+Ob7oct0TC+jAWBu9eoUhTgcimSrM2KG9UdeiUpht5ZD2RqQaLGm2v25Y7fiHRUDxPNz033avcuvwOuFaJmec6Vpzxq3FNrsg1OzfXzrzkBnbWm2c75UalbI5tRWIqxKXYppwPVza6Htv83GwndrMB4m6e7BWvPhVut76H18+cCMTpuZQIsFIDEbArikCcWwTWpcbdw4qPjFrN7u8iUSZeXY5s+WkrXk7JHnNitum3KhqGXFJEu12eDF08cOnUQVC98b882SPnB/mRq3+cntaxJg0x9ltuhbHavUvMYAeDXtbG96LTqfN7zuUzzg2YM0Xk85fhIOY83vTw0+YwvI5bCOuH5AayQxv16Jtq/SB70pIYtURHLdWTnrR0kkA78Q+pQn0xVYipKuhnqoJIueSKtk8Y4Gtn7lknRH9UMkTjJcOifSQbdqPEsmcVgl5KT/qL6Yncl54ofZ/oM+mJkvdlez49hRueLyModl+CYtcRFLueoPBUUL8X9O72ZVnk/8e978a9o2MvLOsrxL145s5yTOETNAKCRLfPoMEpN7P7khaSRpf7m4rQkEFBrsfgzDXkXZ8BQZL9QwC/4f+bOAXGYUXYvS8cVoRRXOAUmLnt/HKSEp+pKAxUS6Q4QVhqKtv7h7B/aAZKkIghypVk7FfDCqSWIComtSJIgIrpsBfFQNZEcy0YJpxfLqqYuU+9bz2F16i3EqdiTWELUlWoqDUS46tzSpYQm3KOGYJgQ5NflZOADQhLySVCmrJxJ3oJOyFjimuFKVJnUhNkuze4dfHuLTn9/h8=&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(26, 26, 26);">
<defs/> <defs/>
<g> <g>
<path d="M 199 202 L 249 202 L 269 242 L 249 282 L 199 282 L 179 242 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/> <path d="M 192 148 L 242 148 L 262 188 L 242 228 L 192 228 L 172 188 Z" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 242px; margin-left: 180px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 188px; margin-left: 173px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/ @capire/
<br/> <br/>
<b> <b>
@@ -17,156 +17,18 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="224" y="246" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="217" y="192" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/... @capire/...
</text> </text>
</switch> </switch>
</g> </g>
<path d="M 199 101 L 249 101 L 269 141 L 249 181 L 199 181 L 179 141 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/> <path d="M 192 48 L 242 48 L 262 88 L 242 128 L 192 128 L 172 88 Z" fill="#f8cecc" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 141px; margin-left: 180px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 173px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
bookstore
</b>
</div>
</div>
</div>
</foreignObject>
<text x="224" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 286 48 L 336 48 L 356 88 L 336 128 L 286 128 L 266 88 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 267px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
@capire/
<br/>
<b>
reviews
</b>
</div>
</div>
</div>
</foreignObject>
<text x="311" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 286 153 L 336 153 L 356 193 L 336 233 L 286 233 L 266 193 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 193px; margin-left: 267px;">
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
@capire/
<br/>
<b>
common
</b>
</div>
</div>
</div>
</foreignObject>
<text x="311" y="197" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 111 153 L 161 153 L 181 193 L 161 233 L 111 233 L 91 193 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 193px; margin-left: 92px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
@capire/
<br/>
<b>
orders
</b>
</div>
</div>
</div>
</foreignObject>
<text x="136" y="197" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 276.35 172.29 L 266.36 166.32" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 260.57 162.86 L 270.6 163.61 L 266.36 166.32 L 265.98 171.34 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 170.74 172.47 L 181.53 166.1" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 187.34 162.66 L 181.88 171.12 L 181.53 166.1 L 177.3 163.37 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 224 202 L 224 189.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 224 183.24 L 228.5 192.24 L 224 189.99 L 219.5 192.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 276.51 109.01 L 266.17 115.31" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 260.4 118.82 L 265.75 110.3 L 266.17 115.31 L 270.43 117.98 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 111 48 L 161 48 L 181 88 L 161 128 L 111 128 L 91 88 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 88px; margin-left: 92px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
@capire/
<br/>
<b>
suppliers
</b>
</div>
</div>
</div>
</foreignObject>
<text x="136" y="92" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 21 101 L 71 101 L 91 141 L 71 181 L 21 181 L 1 141 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 141px; margin-left: 2px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
S/4
</b>
</div>
</div>
</div>
</foreignObject>
<text x="46" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
S/4
</text>
</switch>
</g>
<path d="M 80.76 120.53 L 93.49 113.03" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 99.31 109.61 L 93.84 118.05 L 93.49 113.03 L 89.27 110.3 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 80.91 161.17 L 93.31 168.33" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 99.15 171.71 L 89.11 171.1 L 93.31 168.33 L 93.61 163.31 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 170.59 108.83 L 181.72 115.53" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 187.5 119.02 L 177.47 118.23 L 181.72 115.53 L 182.11 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 199 1 L 249 1 L 269 41 L 249 81 L 199 81 L 179 41 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 41px; margin-left: 180px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
@capire/ @capire/
<br/> <br/>
<b> <b>
@@ -176,19 +38,134 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="224" y="45" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="217" y="92" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/... @capire/...
</text> </text>
</switch> </switch>
</g> </g>
<path d="M 224 101 L 224 89.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/> <path d="M 276 98 L 326 98 L 346 138 L 326 178 L 276 178 L 256 138 Z" fill="#d5e8d4" stroke="#82b366" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 224 83.24 L 228.5 92.24 L 224 89.99 L 219.5 92.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/> <g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 257px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
reviews
</b>
</div>
</div>
</div>
</foreignObject>
<text x="301" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 109 198 L 159 198 L 179 238 L 159 278 L 109 278 L 89 238 Z" fill="#f5f5f5" stroke="#666666" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 238px; margin-left: 90px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
common
</b>
</div>
</div>
</div>
</foreignObject>
<text x="134" y="242" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 108 98 L 158 98 L 178 138 L 158 178 L 108 178 L 88 138 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 138px; margin-left: 89px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
orders
</b>
</div>
</div>
</div>
</foreignObject>
<text x="133" y="142" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 168.58 217.17 L 174.72 213.47" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.5 209.99 L 175.11 218.49 L 174.72 213.47 L 170.47 210.78 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.68 117.36 L 174.6 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180.4 109.79 L 174.97 118.26 L 174.6 113.24 L 170.36 110.52 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 217 148 L 217 136.99" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 217 130.24 L 221.5 139.24 L 217 136.99 L 212.5 139.24 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 266.32 117.36 L 259.4 113.24" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 253.6 109.79 L 263.64 110.52 L 259.4 113.24 L 259.03 118.26 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 107 1 L 157 1 L 177 41 L 157 81 L 107 81 L 87 41 Z" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 41px; margin-left: 88px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
@capire/
<br/>
<b>
suppliers
</b>
</div>
</div>
</div>
</foreignObject>
<text x="132" y="45" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
@capire/...
</text>
</switch>
</g>
<path d="M 21 53 L 71 53 L 91 93 L 71 133 L 21 133 L 1 93 Z" fill="#e1d5e7" stroke="#9673a6" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 93px; margin-left: 2px;">
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b>
S/4
</b>
</div>
</div>
</div>
</foreignObject>
<text x="46" y="97" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
S/4
</text>
</switch>
</g>
<path d="M 80.55 72.11 L 89.76 66.54" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.53 63.05 L 90.16 71.56 L 89.76 66.54 L 85.5 63.86 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 81.75 111.49 L 89.27 115.38" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 95.26 118.48 L 85.2 118.34 L 89.27 115.38 L 89.33 110.35 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 167.25 60.49 L 173.88 64.16" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 179.79 67.42 L 169.74 67.01 L 173.88 64.16 L 174.09 59.13 Z" fill="#ffffff" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/>
</g> </g>
<switch> <switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/> <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"> <a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%"> <text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display Viewer does not support full SVG 1.1
</text> </text>
</a> </a>
</switch> </switch>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,13 +1,13 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="569px" height="428px" viewBox="-0.5 -0.5 569 428" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vpbc9soFP41fqxHCF0f7VyanWl2O3W3fcYSltkg4UH41l+/EIEtGdl1HTlRM508RBzDMfB9h/NxrAG8yTcfOVrMH1mK6cB10s0A3g5cFwAQy3/Ksq0sgQcrQ8ZJWpmcvWFCfmA90liXJMWltlUmwRgVZNE0JqwocCIaNsQ5Wze7zRhNG4YFynBjGsowSRDFVrfvJBXzyhq54d7+gEk2N98MAr3gHJnO2nE5Rylb10zwbgBvOGOieso3N5iqzWvuy/2RT3cT47gQ5wxwqwErRJd6bRPZ2ZOmh9HfIz1HsTUL52xZpFiNdQZwvJ4TgScLlKhP1xJqaZuLnMoWkI8Jy0miu5ZPWCRz3fiPZNmzR082ZqwQ9ygnVHHhAdMVFiRBaojg7Gm3v3JnxoiSrJDPvNra8Qpz1ZeOtF0wNYNSTogUmWz7Oy83jDL+vAQYuepPzZRx8kN+OaJmjtXALxq4UE9uopcPdLvmy7tVf9Jub7tGQk0Rb2omDcNHzHIs+FZ20Z9+AIHmhA4KYDiy3lMMxH5lm9fpZVBEmtbZzvkeefmgwW8nArSIMF6WpMBlOVDzCKja8CmXT5l6+oy4KDA/QRDwSgSpwTGNfM93WqkzI5TWes6iBCdJV8CBaOg3ofNjGzoDZx0633k5cp6F3A4ug9ZfRSIPTLlC13nMclFDVDrf9bJRvtsIXJSEFXLEaLHo6XFA8eyc0yA+eRpcMbLPiGvohTY5oBe/nB2+xQ5DhrJfwRskEZ7OzgneFOFo1lHwun7YBCd27cgFLYduF5EbtETuASS4SEdKsMhWSlDOivTrnEh2j+UH94QaMGRLqyQQNGG6KgClkGnAzO/uS46KrbHqyTmmracXn4KtZEueGIml90KOzbDZ88qEU6POjmBbx84EH8cUCbJqCrg28LS7z4w8n5cmhr0GTTzgmyPf+Kgmr4fVFdeBp92BfdxVtWbL1TOddss8i2GhxbCbZSmkB96z6E/DeOqcl7pnOOgqdcdNKFqD/1ppO7KgmSyTROqte5QIdhKf3197t0htS46/pfZ2g8BmQhwMWxKB63SgvmOLDN8Zf5oxdRy2ym8ZwEqV9SmEe6O+oQNfL4xNSaIG3V2+oGyL25H7SnJczjEWf8BrBy9qibyrgQcs8E4psCllyZNceYokggaovRBzmkLMvVSIdZkL65IK2ooqfENF1RTePggv1VM/cdSdmjKatEaXR5mwUYZ7pqb6cZeCDrBD+Vp3KWAXsE6Fcl8vU7Vr0nmXq7Mi3+9V5LshbPIEwgtD3/yOcMxRh6FvV9kmmK/IEX2m1FvfqqO9uGK9rjazi1/vOL3HdpAb1r5FlINDYQeDSxN8fOAq8A9m02Gc/1pNrmAFPkWS3uQPtsDFpdnDsKgnpTgQuEMnigNVlAO+E0O3QQ43iC/jGQjg0AMODEPoR17sBFHTL7hecrHLdCaBtOSW0QoRiqaEErH9k2HsDPOqF0i7iveOM4ypdTRSzBsKSWDlGO/CHAMc77SjDoPdrvWNUfIkcZdGrSnt66Qpt35CU0w/s5II9ausZBQTguUKavMiiVPnl1Or3CYSWnmkNDh1pIpr0UZ6X6iZ5JtMvdEzzCWPl4thjrj6lyw53Y758zFzorT0wuOGM4H0qp0TZakZJYtvenkdnC4fwMHvNb7Tcr5AYJ8vbgfni2tXF/9V9QZp+kbw+l3xZHdqXZcn5sTrmCfRYSHzdXliFzLr727cIoHeE1V26viqVNkp646p4nrxGzLFrmFazDDQJFtKpJrk8OdSclrpzk/TnUHmtOxZjf6zFNIN1vayEjfuMIaxH4cQ+B4MXAgOATx+yzn/ptQBViBqYgXi0MYqtqEC/i9DJZv79z4rYbF/exbe/Q8=&lt;/diagram&gt;&lt;/mxfile&gt;"> <svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="569px" height="428px" viewBox="-0.5 -0.5 569 428" content="&lt;mxfile&gt;&lt;diagram id=&quot;QQJxv4aCTC7ZgE7HHOvM&quot; name=&quot;Page-1&quot;&gt;7Vpdd9o4EP01PJZjWf7SY8jHds9pd3tKt30WtjDayBZHFknor18ployNDCXEELbtyUPsQRpLvndmrgZG8Lp4+kPg5eIjzwgb+V72NII3I98HMPLUP21Z15YYBbUhFzQzgzaGKf1OjNHMy1c0I1VnoOScSbrsGlNeliSVHRsWgj92h8056z51iXPzRG9jmKaYEWfYN5rJRW1N/Hhjf09ovrBPBhGqPymwHWwcVwuc8ceWCd6O4LXgXNZXxdM1Yfrl2fdSz7vb8WmzMEFKecgEv57wgNnK7G2qBis0vPdXf12ZNcq13bjgqzIjeq43gpPHBZVkusSp/vRRQa1sC1kwdQfUZcoLmpqh1T2R6cLc/Evz/NljoG7mvJR3uKBMc+E9YQ9E0hTrKVLw++b9qjczwYzmpboW9audPBChx7IrY5dcr6BSC6Jlru7Dxss1Z1w8bwEmvv7TK+WCflcPx8yusZ742QAXm8VNzfaBuW/5Cm70n7Kbt6jWQ552IgEafFVgEF4QKdZqiJnwDtioMEEBLEceNxQDKKxtiza9LIrY0DpvnG+QVxcG/H4iQIcIk1VFS1JVI72OiOkXPhPqKtdXn7CQJRF7CALORJAWHLMkDEKvlzpzylhr5DxJSZoOBRxIxmEXuhC50Fk429CF3uuRCxzkGrgsWn+WqUqYaju+9zEvZAtR5bwZ5aJ8+yRJWVFeqhlXy+WFpgNG5odkA7Q3G5wwsg+IaxjELjlggF7PjtBhhyVDdVnBG6UJmc0PCd4Mk2Q+UPD6YdwFB/lu5IKepDtE5EY9kbsFCSmzKy1Y1F1GccHL7MuCKnZP1Ad3lFkw1J1RSSDqwnRSACqpyoBd3+3nApdrazWL8+y9WR5qYCOZ1Vi7QFMvgq9EakeZl6Oc5cSC0I9tGzsbfIIwLOlD95F94Bl3nzh9zpc2hoMOTQIQ2pRvfdRrNdPaimvLU5Owd7uqt+i4eqZTs82DGBY7DLteVVJ5EBcW/VmMZt5hpXtOoqFKN+pC0Rv8pyrbiQPNdJWmSm/d4VTyvfj8/7V3j9R25Phbam8/ilwmoGjcUwh8bwD1jRwyfOPifs519uuV3yqAtSq7pBC+GPUNPXi+MLYtiRZ0t8WS8TXpR+4LLUi1IET+Bq8fvKQn8k4GHniRApsxnt6rnWdYIWiB2ggxryvE/GOF2NG18KWSCrqKKj6fouoK7xDEx+qpHzgaTk1ZCdqiy0dVsHFOLkxNXcZZCnrADeVTnaWA28DaF8qXephqHZMOO1wdF/nhW0a+H8MuTyA8MvQB2u9owNB3u2xTIh7oDn2m1duldUcv4oh1Xm3mNr9+pfKO3CCvaXyWKAfbwg5GxxZ4tOUqCrdWM2Ccv6wnV/KS7CPJxdQPviTlYNXDJsO3acWByB97CYp0Uw6EHoJ+hxx+hI7jGYjgOAAejGMYJgHyoqTrF5yuuLhtOltAemrL1QOmDM8oo3L9u8K4FeasB0i3i/crVRjb/OiUmPMJSeDUmODIGgO8YL+jAYPd7fVNcHqvcFdGoynd46Rtt37AM8I+8YpK/a2sYhSXkhcaavtDEq/NL6/VuU0VkiqldDi1o4vr0EZ5X+qVFE+5/kXPuFA8Xi3HBRb6X7oSbD0Rz2lmT2vplelGcInNrr09bak5o8uvZnsDZJd3YOv7mtDryS8QuPnFHyC/+G538R/db1Cmr5Q8/lQ8abLWaXliM97APEm2G5nn5YnbyGz/duMGS/wzUaVRxyelSqOsB6aKH6A3ZIrbw3SYYaFJ14wqNSngj6XkrNadH2aNQdW0/FmN/r2Syg0x9qoWN/4YQRSiGIIwgJEPwTaAu085h5+UBsAKJF2sAIpdrJALFQhfDJW63fzusxYWm1/Pwtv/AA==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/> <defs/>
<g> <g>
<rect x="1" y="1" width="195" height="122" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/> <rect x="1" y="1" width="195" height="122" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 13)"> <g transform="translate(-0.5 -0.5)rotate(-90 11 13)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 107px; height: 1px; padding-top: 13px; margin-left: -96px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 107px; height: 1px; padding-top: 13px; margin-left: -96px;">
<div data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: right;"> <div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
S/4 HANA S/4 HANA
</div> </div>
</div> </div>
@@ -21,10 +21,10 @@
<rect x="42.5" y="40" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/> <rect x="42.5" y="40" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 65px; margin-left: 44px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 65px; margin-left: 44px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Business Business
<br/> <br/>
Partner Partner
@@ -32,18 +32,18 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="103" y="69" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="103" y="69" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Business... Business...
</text> </text>
</switch> </switch>
</g> </g>
<rect x="221" y="1" width="347" height="349" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/> <rect x="221" y="1" width="347" height="349" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 331px; height: 1px; padding-top: 15px; margin-left: 230px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 331px; height: 1px; padding-top: 15px; margin-left: 230px;">
<div data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> <div style="box-sizing: border-box; font-size: 0; text-align: left; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
<b> <b>
Incident Mgmt Incident Mgmt
</b> </b>
@@ -61,16 +61,16 @@
<rect x="418" y="73" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/> <rect x="418" y="73" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 98px; margin-left: 419px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 98px; margin-left: 419px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Incidents Incidents
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="476" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="476" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Incidents Incidents
</text> </text>
</switch> </switch>
@@ -81,27 +81,27 @@
<rect x="255" y="73" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/> <rect x="255" y="73" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 98px; margin-left: 256px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 98px; margin-left: 256px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Customers Customers
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="315" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="315" y="102" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Customers Customers
</text> </text>
</switch> </switch>
</g> </g>
<rect x="1" y="147" width="196.5" height="202" fill="rgb(255, 255, 255)" stroke="#828282" stroke-width="2" pointer-events="all"/> <rect x="1" y="147" width="196.5" height="202" fill="#ffffff" stroke="#828282" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)rotate(-90 11 159)"> <g transform="translate(-0.5 -0.5)rotate(-90 11 159)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 187px; height: 1px; padding-top: 159px; margin-left: -176px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-end; width: 187px; height: 1px; padding-top: 159px; margin-left: -176px;">
<div data-drawio-colors="color: #4D4D4D; " style="box-sizing: border-box; font-size: 0px; text-align: right;"> <div style="box-sizing: border-box; font-size: 0; text-align: right; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(77, 77, 77); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #4D4D4D; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
SuccessFactors SuccessFactors
</div> </div>
</div> </div>
@@ -115,10 +115,10 @@
<rect x="42.5" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/> <rect x="42.5" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 44px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 44px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Workforce Workforce
<br/> <br/>
Person Person
@@ -126,7 +126,7 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="103" y="213" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="103" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Workforce... Workforce...
</text> </text>
</switch> </switch>
@@ -134,10 +134,10 @@
<rect x="42.5" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/> <rect x="42.5" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#f8cecc" stroke="#b85450" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 44px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 44px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Employee Employee
<br/> <br/>
Timesheet Timesheet
@@ -145,7 +145,7 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="103" y="296" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="103" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Employee... Employee...
</text> </text>
</switch> </switch>
@@ -155,16 +155,16 @@
<rect x="418" y="182" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/> <rect x="418" y="182" width="115" height="50" rx="7.5" ry="7.5" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 207px; margin-left: 419px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 113px; height: 1px; padding-top: 207px; margin-left: 419px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Messages Messages
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="476" y="211" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="476" y="211" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Messages Messages
</text> </text>
</switch> </switch>
@@ -175,10 +175,10 @@
<rect x="255" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/> <rect x="255" y="184" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 256px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 209px; margin-left: 256px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Service Service
<br/> <br/>
Worker Worker
@@ -186,7 +186,7 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="315" y="213" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="315" y="213" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Service... Service...
</text> </text>
</switch> </switch>
@@ -198,10 +198,10 @@
<rect x="255" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/> <rect x="255" y="267" width="120" height="50" rx="7.5" ry="7.5" fill="#ffe6cc" stroke="#d79b00" stroke-width="2" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 256px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 292px; margin-left: 256px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
Worker Worker
<br/> <br/>
Availability Availability
@@ -209,7 +209,7 @@
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="315" y="296" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="315" y="296" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Worker... Worker...
</text> </text>
</switch> </switch>
@@ -219,16 +219,16 @@
<path d="M 37 407 C 37 401.48 41.48 397 47 397 L 92.5 397 C 98.02 397 102.5 392.52 102.5 387 C 102.5 392.52 106.98 397 112.5 397 L 158 397 C 163.52 397 168 401.48 168 407" fill="none" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/> <path d="M 37 407 C 37 401.48 41.48 397 47 397 L 92.5 397 C 98.02 397 102.5 392.52 102.5 387 C 102.5 392.52 106.98 397 112.5 397 L 158 397 C 163.52 397 168 401.48 168 407" fill="none" stroke="#b85450" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 103px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 103px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Backend Services Backend Services
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="103" y="426" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="103" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Backend Services Backend Services
</text> </text>
</switch> </switch>
@@ -236,16 +236,16 @@
<path d="M 249.5 407 C 249.5 401.48 253.98 397 259.5 397 L 305 397 C 310.52 397 315 392.52 315 387 C 315 392.52 319.48 397 325 397 L 370.5 397 C 376.02 397 380.5 401.48 380.5 407" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/> <path d="M 249.5 407 C 249.5 401.48 253.98 397 259.5 397 L 305 397 C 310.52 397 315 392.52 315 387 C 315 392.52 319.48 397 325 397 L 370.5 397 C 376.02 397 380.5 401.48 380.5 407" fill="none" stroke="#d79b00" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 315px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 315px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Usage Views Usage Views
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="315" y="426" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="315" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Usage Views Usage Views
</text> </text>
</switch> </switch>
@@ -253,16 +253,16 @@
<path d="M 410 407 C 410 401.48 414.48 397 420 397 L 465.5 397 C 471.02 397 475.5 392.52 475.5 387 C 475.5 392.52 479.98 397 485.5 397 L 531 397 C 536.52 397 541 401.48 541 407" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/> <path d="M 410 407 C 410 401.48 414.48 397 420 397 L 465.5 397 C 471.02 397 475.5 392.52 475.5 387 C 475.5 392.52 479.98 397 485.5 397 L 531 397 C 536.52 397 541 401.48 541 407" fill="none" stroke="#6c8ebf" stroke-width="2" stroke-miterlimit="10" transform="translate(0,397)scale(1,-1)translate(0,-397)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)"> <g transform="translate(-0.5 -0.5)">
<switch> <switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 476px;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 414px; margin-left: 476px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"> <div style="box-sizing: border-box; font-size: 0; text-align: center; ">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"> <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
Extension Data Extension Data
</div> </div>
</div> </div>
</div> </div>
</foreignObject> </foreignObject>
<text x="476" y="426" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"> <text x="476" y="426" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
Extension Data Extension Data
</text> </text>
</switch> </switch>
@@ -274,7 +274,7 @@
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/> <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"> <a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%"> <text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display Viewer does not support full SVG 1.1
</text> </text>
</a> </a>
</switch> </switch>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,43 @@
Books = Books
Book = Book
ID = ID
Title = Title
Author = Author
AuthorID = Author ID
Stock = Stock
Name = Name
Description = Description
Image = Image
AuthorName = Author's Name
DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Age = Age
Lifetime = Lifetime
Authors = Authors
Order = Order
Orders = Orders
OrderNo = Order Number
OrderItems = Order Items
Customer = Customer
Product = Product
ProductID = Product ID
ProductTitle = Product Title
UnitPrice = Unit Price
Quantity = Quantity
Price = Price
Currency = Currency
Date = Date
Rating = Rating
NumberOfReviews = Number of Reviews
Genre = Genre
Genres = Genres
SubGenres = Sub Genres
NumCode = Numeric Code
MinorUnit = Minor Unit
Exponent = Exponent

View File

@@ -1,5 +1,14 @@
Books = Bücher
Book = Buch
ID = ID
Title = Titel
Authors = Autoren
Author = Autor
AuthorID = ID des Autors
AuthorName = Name des Autors AuthorName = Name des Autors
Age = Alter Age = Alter
rder = Bestellung Name = Name
Stock = Bestand
Order = Bestellung
Orders = Bestellungen Orders = Bestellungen
Price = Preis Price = Preis

View File

@@ -1,8 +0,0 @@
Age = Age
Lifetime = Lifetime
SubGenres = Sub Genres
NumCode = Numeric Code
MinorUnit = Minor Unit
Exponent = Exponent

View File

@@ -2,10 +2,10 @@
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459 # __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
# JCI app descriptor contains lower case TITLE # JCI app descriptor contains lower case TITLE
appTitle=Manage Authors appTitle=Bookshop Authors
# JCI app descriptor contains lower case DESCRIPTION # JCI app descriptor contains lower case DESCRIPTION
appSubTitle=CAP Sample Application appSubTitle=Bookshop Authors
# JCI app descriptor contains lower case DESCRIPTION # JCI app descriptor contains lower case DESCRIPTION
appDescription=Bookshop Authors appDescription=Bookshop Authors

View File

@@ -35,7 +35,7 @@
"additionalParameters": "ignored" "additionalParameters": "ignored"
}, },
"semanticObject": "Authors", "semanticObject": "Authors",
"action": "manage", "action": "display",
"title": "{{appTitle}}", "title": "{{appTitle}}",
"info": "{{appInfo}}", "info": "{{appInfo}}",
"subTitle": "{{appSubTitle}}", "subTitle": "{{appSubTitle}}",

View File

@@ -2,7 +2,7 @@
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459 # __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
# JCI app descriptor contains lower case TITLE # JCI app descriptor contains lower case TITLE
appTitle=Manage Books appTitle=Bookshop Sample
# JCI app descriptor contains lower case DESCRIPTION # JCI app descriptor contains lower case DESCRIPTION
appSubTitle=CAP Sample Application appSubTitle=CAP Sample Application

View File

@@ -19,22 +19,6 @@
"id": "ui5template.basicSAPUI5ApplicationProject", "id": "ui5template.basicSAPUI5ApplicationProject",
"-id": "ui5template.smartTemplate", "-id": "ui5template.smartTemplate",
"-version": "1.40.12" "-version": "1.40.12"
},
"crossNavigation": {
"inbounds": {
"intent1": {
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"semanticObject": "Books",
"action": "manage",
"title": "{{appTitle}}",
"info": "{{appInfo}}",
"subTitle": "{{appSubTitle}}",
"icon": "sap-icon://course-book"
}
}
} }
}, },
"sap.ui5": { "sap.ui5": {

View File

@@ -50,7 +50,7 @@
"tileType": "sap.ushell.ui.tile.StaticTile", "tileType": "sap.ushell.ui.tile.StaticTile",
"properties": { "properties": {
"title": "Manage Authors", "title": "Manage Authors",
"targetURL": "#Authors-manage" "targetURL": "#Authors-display"
} }
}, },
{ {
@@ -99,7 +99,7 @@
}, },
"BrowseAuthors": { "BrowseAuthors": {
"semanticObject": "Authors", "semanticObject": "Authors",
"action": "manage", "action": "display",
"title": "Browse Authors", "title": "Browse Authors",
"signature": { "signature": {
"parameters": { "parameters": {

View File

@@ -6,8 +6,8 @@ using CatalogService from '@capire/bookstore';
// //
annotate CatalogService.Books with @(UI : { annotate CatalogService.Books with @(UI : {
HeaderInfo : { HeaderInfo : {
TypeName : '{i18n>Book}', TypeName : 'Book',
TypeNamePlural : '{i18n>Books}', TypeNamePlural : 'Books',
Description : {Value : author} Description : {Value : author}
}, },
HeaderFacets : [{ HeaderFacets : [{

View File

@@ -2,7 +2,7 @@
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459 # __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
# JCI app descriptor contains lower case TITLE # JCI app descriptor contains lower case TITLE
appTitle=Browse Books appTitle=Bookshop Sample
# JCI app descriptor contains lower case DESCRIPTION # JCI app descriptor contains lower case DESCRIPTION
appSubTitle=CAP Sample Application appSubTitle=CAP Sample Application

View File

@@ -1,7 +1,7 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -10,22 +10,21 @@
<script> <script>
window["sap-ushell-config"] = { window["sap-ushell-config"] = {
defaultRenderer: "fiori2", defaultRenderer: "fiori2",
applications: {}, applications: {}
}; };
</script> </script>
<script id="sap-ushell-bootstrap" src="https://ui5.sap.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script> <script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js" <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout" data-sap-ui-compatVersion="edge" data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-theme="sap_horizon"></script> data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_horizon"
data-sap-ui-frameOptions="allow"
></script>
<script> <script>
sap.ui.getCore().attachInit(() => sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
sap.ushell.Container.createRenderer().placeAt("content")
);
</script> </script>
</head> </head>
<body class="sapUiBody" id="content"></body>
<body class="sapUiBody" id="content">
</body>
</html> </html>

View File

@@ -33,7 +33,7 @@
"resources": "resources.json", "resources": "resources.json",
"dataSources": { "dataSources": {
"main": { "main": {
"uri": "/odata/v2/browse", "uri": "/v2/browse",
"type": "OData", "type": "OData",
"settings": { "settings": {
"annotations": ["localAnnotations"], "annotations": ["localAnnotations"],

View File

@@ -0,0 +1,3 @@
code;name
EUR;EUR from fiori
USD;USD from fiori
1 code name
2 EUR EUR from fiori
3 USD USD from fiori

View File

@@ -5,13 +5,11 @@
"@capire/bookstore": "*", "@capire/bookstore": "*",
"@sap/cds": ">=5", "@sap/cds": ">=5",
"@cap-js-community/odata-v2-adapter": "^1", "@cap-js-community/odata-v2-adapter": "^1",
"express": "^4.17.1" "express": "^4.17.1",
}, "passport": ">=0.4.1"
"devDependencies": {
"@cap-js/sqlite": "^1"
}, },
"scripts": { "scripts": {
"start": "cds-serve", "start": "cds run --in-memory?",
"watch": "cds watch" "watch": "cds watch"
}, },
"cds": { "cds": {
@@ -36,7 +34,8 @@
} }
}, },
"db": { "db": {
"kind": "sql" "kind": "sqlite",
"schema_evolution": "auto"
}, },
"db-ext": { "db-ext": {
"[development]": { "[development]": {
@@ -50,10 +49,5 @@
"hana": { "hana": {
"deploy-format": "hdbtable" "deploy-format": "hdbtable"
} }
}, }
"sapux": [
"app/admin-authors",
"app/admin-books",
"app/browse"
]
} }

8
fiori/server.js Normal file
View File

@@ -0,0 +1,8 @@
// install OData v2 adapter
const cds = require("@sap/cds")
const proxy = require('@cap-js-community/odata-v2-adapter')
const opts = global.it ? { target:'auto' } : {} // for tests, set 'auto' to detect port dynamically
cds.on('bootstrap', app => app.use(proxy(opts))) // install proxy
cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan`
module.exports = require('@capire/bookstore/server.js')

View File

@@ -3,10 +3,40 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"test": "npx jest --silent", "test": "npx jest --silent",
"start": "cds-serve srv/world.cds", "start": "cds serve srv/world.cds",
"start:ts": "cds-ts serve srv/world.cds" "start:ts": "cds-ts serve srv/world.cds"
}, },
"dependencies": { "dependencies": {
"@sap/cds": ">=5.0.4" "@sap/cds": ">=5.0.4"
},
"devDependencies": {
"@types/jest": "*",
"@types/node": "*",
"typescript": ">=4.3.5"
},
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"es2020": true,
"node": true,
"jest": true,
"mocha": true
},
"globals": {
"SELECT": true,
"INSERT": true,
"UPDATE": true,
"DELETE": true,
"CREATE": true,
"DROP": true,
"CDL": true,
"CQL": true,
"CXL": true,
"cds": true
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off"
}
} }
} }

View File

@@ -1,4 +1,4 @@
import { Request } from "@sap/cds" import type { Request } from "@sap/cds/apis/services"
module.exports = class say { module.exports = class say {
hello(req: Request) { hello(req: Request) {

View File

@@ -0,0 +1,13 @@
const cds = require ('@sap/cds')
describe('Hello world!', () => {
beforeAll (()=> process.env.CDS_TYPESCRIPT = true)
afterAll (()=> delete process.env.CDS_TYPESCRIPT)
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds')
it('should say hello with class impl', async () => {
const {data} = await GET`/say/hello(to='world')`
expect(data.value).toMatch(/Hello world.*typescript.*/i)
})
})

View File

@@ -1,5 +1 @@
GET http://localhost:4004/odata/v4/say/hello GET http://localhost:4004/say/hello(to='world')
###
GET http://localhost:4004/odata/v4/say/hello(to='me')
###

View File

@@ -11,7 +11,12 @@
"express": "^4.17.1" "express": "^4.17.1"
}, },
"scripts": { "scripts": {
"start": "cds-serve", "start": "cds run",
"watch": "cds watch" "watch": "cds watch"
},
"cds": {
"requires": {
"db": "sql"
}
} }
} }

View File

@@ -1,4 +1,4 @@
const cds = require ('@sap/cds') const cds = require ('@sap/cds/lib')
const LOG = cds.log('cds.log') const LOG = cds.log('cds.log')
module.exports = class LogService extends cds.Service { module.exports = class LogService extends cds.Service {

View File

@@ -1,7 +1,6 @@
using { sap.capire.media as db } from '../db/data-model'; using { sap.capire.media as db } from '../db/data-model';
namespace sap.capire.media; namespace sap.capire.media;
@path: '/media-server'
service MediaServer { service MediaServer {
entity Media as projection on db.Media ; entity Media as projection on db.Media ;
} }

View File

@@ -10,7 +10,7 @@ module.exports = srv => {
}) })
srv.on('UPDATE', 'Media', (req, next) => { srv.on('UPDATE', 'Media', (req, next) => {
const url = req.path const url = req._.req.path
if (url.includes('content')) { if (url.includes('content')) {
const id = req.data.id const id = req.data.id
const obj = mediaDB.get(id) const obj = mediaDB.get(id)
@@ -32,7 +32,7 @@ module.exports = srv => {
}) })
srv.on('READ', 'Media', (req, next) => { srv.on('READ', 'Media', (req, next) => {
const url = req.path const url = req._.req.path
if (url.includes('content')) { if (url.includes('content')) {
const id = req.data.id const id = req.data.id
const mediaObj = mediaDB.get(id) const mediaObj = mediaDB.get(id)

View File

@@ -1 +1,2 @@
cds.requires.messaging.kind = file-based-messaging
PORT = 4006 PORT = 4006

View File

@@ -1,3 +0,0 @@
Order = Bestellung
Orders = Bestellungen
Price = Preis

View File

@@ -1,10 +0,0 @@
Order = Order
Orders = Orders
OrderNo = Order Number
OrderItems = Order Items
Customer = Customer
Product = Product
ProductID = Product ID
ProductTitle = Product Title
UnitPrice = Unit Price
Quantity = Quantity

View File

@@ -1,10 +0,0 @@
Order = Order
Orders = Orders
OrderNo = Numéro de commande
OrderItems = Articles de la commande
Customer = Client
Product = Produit
ProductID = ID du produit
ProductTitle = Titre du produit
UnitPrice = Prix unitaire
Quantity = Quantité

View File

@@ -2,7 +2,7 @@
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459 # __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
# JCI app descriptor contains lower case TITLE # JCI app descriptor contains lower case TITLE
appTitle=Manage Orders appTitle=Bookshop Sample
# JCI app descriptor contains lower case DESCRIPTION # JCI app descriptor contains lower case DESCRIPTION
appSubTitle=CAP Sample Application appSubTitle=CAP Sample Application

View File

@@ -8,7 +8,7 @@
"i18n": "i18n/i18n.properties", "i18n": "i18n/i18n.properties",
"dataSources": { "dataSources": {
"OrdersService": { "OrdersService": {
"uri": "/odata/v4/orders/", "uri": "/orders/",
"type": "OData", "type": "OData",
"settings": { "settings": {
"odataVersion": "4.0" "odataVersion": "4.0"

View File

@@ -2,7 +2,7 @@ using { Currency, User, managed, cuid } from '@sap/cds/common';
namespace sap.capire.orders; namespace sap.capire.orders;
entity Orders : cuid, managed { entity Orders : cuid, managed {
OrderNo : String(44) @title:'Order Number'; //> readable key OrderNo : String(22) @title:'Order Number'; //> readable key
Items : Composition of many { Items : Composition of many {
key ID : UUID; key ID : UUID;
product : Association to Products; product : Association to Products;

View File

@@ -3,12 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": "*" "@sap/cds": ">=5"
},
"cds": {
"requires": {
"messaging": true,
"db": true
}
} }
} }

View File

@@ -29,7 +29,7 @@ class OrdersService extends cds.ApplicationService {
/** order changed -> broadcast event */ /** order changed -> broadcast event */
orderChanged (product, deltaQuantity) { orderChanged (product, deltaQuantity) {
// Emit events to inform subscribers about changes in orders // Emit events to inform subscribers about changes in orders
console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity }) // eslint-disable-line no-console console.log ('> emitting:', 'OrderChanged', { product, deltaQuantity })
return this.emit ('OrderChanged', { product, deltaQuantity }) return this.emit ('OrderChanged', { product, deltaQuantity })
} }

3324
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{ {
"name": "@capire/samples", "name": "@capire/samples",
"version": "2.1.0", "version": "2.0.0",
"description": "A monorepo with several samples for CAP.", "description": "A monorepo with several samples for CAP.",
"repository": "https://github.com/sap-samples/cloud-cap-samples.git", "repository": "https://github.com/sap-samples/cloud-cap-samples.git",
"author": "daniel.hutzel@sap.com", "author": "daniel.hutzel@sap.com",
"dependencies": { "dependencies": {
"@sap/cds": ">=8" "@sap/cds": ">=5.5.3"
}, },
"workspaces": [ "workspaces": [
"./bookshop", "./bookshop",
@@ -20,27 +20,32 @@
"./reviews" "./reviews"
], ],
"devDependencies": { "devDependencies": {
"@cap-js/cds-types": "^0", "@sap/eslint-plugin-cds": "^2.6.1",
"@cap-js/sqlite": "^1",
"axios": "^1", "axios": "^1",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0", "chai-subset": "^1.6.0",
"eslint": "^9", "semver": "^7",
"semver": "^7" "sqlite3": "^5"
}, },
"scripts": { "scripts": {
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
"bookshop": "cds watch bookshop", "bookshop": "cds watch bookshop",
"start": "cds watch fiori",
"fiori": "cds watch fiori", "fiori": "cds watch fiori",
"hello": "cds watch hello", "hello": "cds watch hello",
"media": "cds watch media", "media": "cds watch media",
"lint": "eslint",
"test": "npx jest --silent",
"jest": "npx jest --silent",
"mocha": "CDS_TEST_SILENT=y npx mocha", "mocha": "CDS_TEST_SILENT=y npx mocha",
"jest": "npx jest --silent",
"start": "cds watch fiori",
"test": "npm run jest -- --silent",
"test:hello": "cd hello && npm test" "test:hello": "cd hello && npm test"
}, },
"jest": {
"testTimeout": 20000,
"testMatch": [
"**/*.test.js"
]
},
"mocha": { "mocha": {
"recursive": true, "recursive": true,
"parallel": true, "parallel": true,

View File

@@ -1 +1,2 @@
# cds.requires.messaging.kind = file-based-messaging
PORT = 4005 PORT = 4005

View File

@@ -1,3 +0,0 @@
Date = Datum
Rating = Bewertung
NumberOfReviews = Anzahl der Reviews

View File

@@ -1,5 +0,0 @@
Reviews = Reviews
Review = Review
Date = Date
Rating = Rating
NumberOfReviews = Number of Reviews

View File

@@ -1,5 +0,0 @@
Reviews = Avis
Review = Avis
Date = Date
Rating = Note
NumberOfReviews = Nombre d''avis

View File

@@ -50,6 +50,7 @@ const reviews = Vue.createApp ({
const res = await POST(`/Reviews`,review) const res = await POST(`/Reviews`,review)
reviews.ID = res.data.ID reviews.ID = res.data.ID
} else { } else {
console.trace()
await PUT(`/Reviews/${review.ID}`,review) await PUT(`/Reviews/${review.ID}`,review)
} }
reviews.message = { succeeded: 'Your review was submitted successfully. Thanks.' } reviews.message = { succeeded: 'Your review was submitted successfully. Thanks.' }

View File

@@ -1,5 +1,5 @@
ID;subject;rating;reviewer;title;text subject;rating;reviewer;title;text
0;201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 201;5;bob;Intriguing;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
1;201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum. 201;4;bob;Fascinating;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
2;207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius. 207;2;bob;What is this?;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
3;251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse. 251;3;bob;It's dark...;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.
1 ID subject rating reviewer title text
2 0 201 5 bob Intriguing Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
3 1 201 4 bob Fascinating Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Id diam maecenas ultricies mi eget mauris pharetra et. Risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Pulvinar mattis nunc sed blandit libero. Facilisis magna etiam tempor orci eu. Nec sagittis aliquam malesuada bibendum arcu. Eu consequat ac felis donec. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Tortor posuere ac ut consequat semper viverra nam libero. Amet nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Scelerisque purus semper eget duis at tellus. Elementum tempus egestas sed sed risus pretium. Arcu dictum varius duis at. Amet luctus venenatis lectus magna fringilla urna. Eget velit aliquet sagittis id consectetur purus ut faucibus. Vitae auctor eu augue ut lectus. Fermentum iaculis eu non diam phasellus vestibulum.
4 2 207 2 bob What is this? Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Libero justo laoreet sit amet cursus sit amet dictum. Nunc faucibus a pellentesque sit. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Enim nunc faucibus a pellentesque. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien. Cras ornare arcu dui vivamus. Facilisi etiam dignissim diam quis enim lobortis. Et molestie ac feugiat sed. Urna neque viverra justo nec ultrices dui. Ullamcorper a lacus vestibulum sed arcu non. Volutpat ac tincidunt vitae semper quis. Dignissim sodales ut eu sem. Feugiat in fermentum posuere urna nec. At augue eget arcu dictum varius.
5 3 251 3 bob It's dark... Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Suscipit tellus mauris a diam. Velit aliquet sagittis id consectetur purus ut. Viverra adipiscing at in tellus integer. Vitae elementum curabitur vitae nunc. Mattis ullamcorper velit sed ullamcorper morbi. Diam quis enim lobortis scelerisque. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Semper auctor neque vitae tempus. Quis eleifend quam adipiscing vitae proin. Neque convallis a cras semper auctor neque vitae. Imperdiet massa tincidunt nunc pulvinar sapien et ligula. Sit amet consectetur adipiscing elit ut aliquam purus. Pretium quam vulputate dignissim suspendisse.

View File

@@ -12,8 +12,12 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"messaging": true, "messaging": {
"db": true "[development]": { "kind": "file-based-messaging" },
"[hybrid]": { "kind": "enterprise-messaging-shared" },
"[production]": { "kind": "enterprise-messaging" }
},
"db": { "kind": "sql" }
} }
} }
} }

View File

@@ -1,6 +1,6 @@
using { sap.capire.reviews as my } from '../db/schema'; using { sap.capire.reviews as my } from '../db/schema';
service ReviewsService @(path:'/reviews') { service ReviewsService {
// Sync API // Sync API
entity Reviews as projection on my.Reviews excluding { likes } entity Reviews as projection on my.Reviews excluding { likes }

View File

@@ -15,7 +15,7 @@ module.exports = cds.service.impl (function(){
const { count, rating } = await cds.tx(req) .run ( const { count, rating } = await cds.tx(req) .run (
SELECT.one `round(avg(rating),2) as rating, count(*) as count` .from (Reviews) .where ({subject}) SELECT.one `round(avg(rating),2) as rating, count(*) as count` .from (Reviews) .where ({subject})
) )
global.it || console.log ('< emitting:', 'reviewed', { subject, count, rating }) // eslint-disable-line no-console global.it || console.log ('< emitting:', 'reviewed', { subject, count, rating })
await this.emit ('reviewed', { subject, count, rating }) await this.emit ('reviewed', { subject, count, rating })
}) })

View File

@@ -3,8 +3,6 @@
The following list gives an overview of the samples provided in subdirectories. The following list gives an overview of the samples provided in subdirectories.
Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](#all-in-one-monorepo) umbrella setup. Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](#all-in-one-monorepo) umbrella setup.
![](etc/samples.drawio.svg)
## [@capire/hello-world](hello) ## [@capire/hello-world](hello)
@@ -55,7 +53,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
## [@capire/bookstore](bookstore) ## [@capire/bookstore](bookstore)
- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/extensibility/composition) these packages: - A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:
- [@capire/bookshop](bookshop) - [@capire/bookshop](bookshop)
- [@capire/reviews](reviews) - [@capire/reviews](reviews)
- [@capire/orders](orders) - [@capire/orders](orders)

View File

@@ -1,8 +1,7 @@
const cds = require('@sap/cds')
const { expect } = cds.test
describe('cds.ql → cqn', () => { describe('cds.ql → cqn', () => {
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const Foo = { name: 'Foo' } const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' } const Books = { name: 'capire.bookshop.Books' }
@@ -677,7 +676,7 @@ describe('cds.ql → cqn', () => {
.to.eql(INSERT.into(Foo).entries(...entries)) .to.eql(INSERT.into(Foo).entries(...entries))
.to.eql(INSERT.into(Foo).entries(entries)) .to.eql(INSERT.into(Foo).entries(entries))
.to.eql({ .to.eql({
INSERT: { into: { ref: ['Foo'] }, entries }, INSERT: { into: 'Foo', entries },
}) })
}) })
@@ -693,7 +692,7 @@ describe('cds.ql → cqn', () => {
.to.eql(INSERT.into(Foo).columns('a', 'b').rows([1, 2], [3, 4])) .to.eql(INSERT.into(Foo).columns('a', 'b').rows([1, 2], [3, 4]))
.to.eql({ .to.eql({
INSERT: { INSERT: {
into: { ref: ['Foo'] }, into: 'Foo',
columns: ['a', 'b'], columns: ['a', 'b'],
rows: [ rows: [
[1, 2], [1, 2],
@@ -707,7 +706,7 @@ describe('cds.ql → cqn', () => {
expect(INSERT.into(Foo).columns('a', 'b').values([1, 2])) expect(INSERT.into(Foo).columns('a', 'b').values([1, 2]))
.to.eql(INSERT.into(Foo).columns('a', 'b').values(1, 2)) .to.eql(INSERT.into(Foo).columns('a', 'b').values(1, 2))
.to.eql({ .to.eql({
INSERT: { into: { ref: ['Foo'] }, columns: ['a', 'b'], values: [1, 2] }, INSERT: { into: 'Foo', columns: ['a', 'b'], values: [1, 2] },
}) })
}) })
@@ -722,7 +721,7 @@ describe('cds.ql → cqn', () => {
test('entity (..., <key>)', () => { test('entity (..., <key>)', () => {
const cqnWhere = { const cqnWhere = {
UPDATE: { UPDATE: {
entity: { ref: ['capire.bookshop.Books'] }, entity: 'capire.bookshop.Books',
where: [{ ref: ['ID'] }, '=', { val: 4711 }], where: [{ ref: ['ID'] }, '=', { val: 4711 }],
}, },
} }
@@ -766,7 +765,7 @@ describe('cds.ql → cqn', () => {
.to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } })) .to.eql(UPDATE(Foo).with({ foo: 11, bar: { '-=': 22 } }))
.to.eql({ .to.eql({
UPDATE: { UPDATE: {
entity: { ref: ['Foo'] }, entity: 'Foo',
data: { foo: 11 }, data: { foo: 11 },
with: { with: {
bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] }, bar: { xpr: [{ ref: ['bar'] }, '-', { val: 22 }] },
@@ -777,7 +776,7 @@ describe('cds.ql → cqn', () => {
// some more // some more
expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({ expect(UPDATE(Foo).with(`bar = coalesce(x,y), car = 'foo''s bar, car'`)).to.eql({
UPDATE: { UPDATE: {
entity: { ref: ['Foo'] }, entity: 'Foo',
data: { data: {
car: "foo's bar, car", car: "foo's bar, car",
}, },
@@ -797,7 +796,7 @@ describe('cds.ql → cqn', () => {
test('from (..., <key>)', () => { test('from (..., <key>)', () => {
const cqnWhere = { const cqnWhere = {
DELETE: { DELETE: {
from: { ref: ['capire.bookshop.Books'] }, from: 'capire.bookshop.Books',
where: [{ ref: ['ID'] }, '=', { val: 4711 }], where: [{ ref: ['ID'] }, '=', { val: 4711 }],
}, },
} }

View File

@@ -1,53 +0,0 @@
const cds = require("@sap/cds");
const { expect } = cds.test(
"serve",
"CatalogService",
"--from",
"@capire/bookshop,@capire/common",
"--in-memory"
);
describe("Consuming actions locally", () => {
let cats, CatalogService, Books, stockBefore;
const BOOK_ID = 251;
const QUANTITY = 1;
before("bootstrap the database", async () => {
CatalogService = cds.services.CatalogService;
expect(CatalogService).not.to.be.undefined;
Books = CatalogService.entities.Books;
expect(Books).not.to.be.undefined;
cats = await cds.connect.to("CatalogService");
});
beforeEach(async () => {
// Read the stock before the action is called
stockBefore = (await cats.get(Books, BOOK_ID)).stock;
});
it("calls unbound actions - basic variant using srv.send", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res1 = await cats.tx({ user: "alice" }, () => {
return cats.send("submitOrder", { book: BOOK_ID, quantity: QUANTITY });
});
expect(res1.stock).to.eql(stockBefore - QUANTITY);
});
it("calls unbound actions - named args variant", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res2 = await cats.tx({ user: "alice" }, () => {
return cats.submitOrder({ book: BOOK_ID, quantity: QUANTITY });
});
expect(res2.stock).to.eql(stockBefore - QUANTITY);
});
it("calls unbound actions - positional args variant", async () => {
// Use a managed transaction to create a continuation with an authenticated user
const res3 = await cats.tx({ user: "alice" }, () => {
return cats.submitOrder(BOOK_ID, QUANTITY);
});
expect(res3.stock).to.eql(stockBefore - QUANTITY);
});
});

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Consuming Services locally', () => { describe('cap/samples - Consuming Services locally', () => {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Custom Handlers', () => { describe('cap/samples - Custom Handlers', () => {
@@ -8,10 +8,9 @@ describe('cap/samples - Custom Handlers', () => {
}) })
it('should reject out-of-stock orders', async () => { it('should reject out-of-stock orders', async () => {
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.fulfilled await POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`
await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith( await expect(POST `/browse/submitOrder ${{ book: 201, quantity: 5 }}`).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
/409 - 5 exceeds stock for book #201/)
const { data } = await GET`/admin/Books/201/stock/$value` const { data } = await GET`/admin/Books/201/stock/$value`
expect(data).to.equal(2) expect(data).to.equal(2)
}) })

View File

@@ -1,8 +1,4 @@
// Quick hack: suppress deprecation warnings w/ Node22 caused by http-proxy (used by OData v2 proxy) const cds = require('@sap/cds/lib')
// See also: https://github.com/http-party/node-http-proxy/pull/1666
require('util')._extend = Object.assign
const cds = require('@sap/cds')
describe('cap/samples - Fiori APIs - v2', function() { describe('cap/samples - Fiori APIs - v2', function() {
@@ -12,7 +8,7 @@ describe('cap/samples - Fiori APIs - v2', function() {
// if (this.timeout) this.timeout(1e6) // if (this.timeout) this.timeout(1e6)
it('serves $metadata documents in v2', async () => { it('serves $metadata documents in v2', async () => {
const { headers, data } = await GET `/odata/v2/browse/$metadata` const { headers, data } = await GET `/v2/browse/$metadata`
expect(headers).to.contain({ expect(headers).to.contain({
'content-type': 'application/xml', 'content-type': 'application/xml',
'dataserviceversion': '2.0', 'dataserviceversion': '2.0',
@@ -21,7 +17,7 @@ describe('cap/samples - Fiori APIs - v2', function() {
}) })
it('serves Books in v2', async () => { it('serves Books in v2', async () => {
const { data } = await GET `/odata/v2/browse/Books` const { data } = await GET `/v2/browse/Books`
expect(data).to.containSubset({d:{results:[]}}) expect(data).to.containSubset({d:{results:[]}})
expect(data.d.results.length).to.be.greaterThanOrEqual(5) expect(data.d.results.length).to.be.greaterThanOrEqual(5)
}) })

View File

@@ -1,11 +1,11 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Hello world!', () => { describe('cap/samples - Hello world!', () => {
const { GET, expect } = cds.test (__dirname+'/../hello') const { GET, expect } = cds.test (__dirname+'/../hello')
it('should say hello with class impl', async () => { it('should say hello with class impl', async () => {
const {data} = await GET `/odata/v4/say/hello(to='world')` const {data} = await GET `/say/hello(to='world')`
expect(data.value).to.eql('Hello world!') expect(data.value).to.eql('Hello world!')
}) })

View File

@@ -1,5 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
const { expect } = cds.test.in(__dirname,'..')
describe('cap/samples - Hierarchical Data', ()=>{ describe('cap/samples - Hierarchical Data', ()=>{
@@ -13,6 +12,7 @@ describe('cap/samples - Hierarchical Data', ()=>{
` `
const model = cds.compile.for.nodejs(csn) const model = cds.compile.for.nodejs(csn)
const {Categories:Cats} = model.definitions const {Categories:Cats} = model.definitions
const {expect} = cds.test
before ('bootstrap sqlite in-memory db...', async()=>{ before ('bootstrap sqlite in-memory db...', async()=>{
await cds.deploy (csn) .to ('sqlite::memory:') // REVISIT: cds.compile.to.sql should accept cds.compiled.for.nodejs models await cds.deploy (csn) .to ('sqlite::memory:') // REVISIT: cds.compile.to.sql should accept cds.compiled.for.nodejs models
@@ -33,71 +33,45 @@ describe('cap/samples - Hierarchical Data', ()=>{
]} ]}
)) ))
it ('should generate correct queries for expands', ()=>{ it ('supports nested reads', async()=>{
let q = SELECT.from (Cats, c => { c.ID, c.name, c.children (c => c.name) }) if (require('semver').gte(cds.version, '5.9.0')) {
expect (q) .to.eql ({ expect (await
SELECT: {
from: { ref:[ "Categories" ] },
columns: [
{ ref: [ "ID" ] },
{ ref: [ "name" ] },
{ ref: [ "children" ], expand: [ {ref:['name']} ] },
]
}
})
if (q.forSQL) expect (q.forSQL()) .to.eql ({
SELECT: {
from: { ref:[ "Categories" ], as: "Categories" },
columns: [
{ ref: [ "Categories", "ID" ] },
{ ref: [ "Categories", "name" ] },
{ as: "children", SELECT: { expand: true,
one: false,
columns: [{ ref: [ "children", "name" ]}],
from: { ref:["Categories"], as: "children" },
where: [
{ref:[ "Categories", "ID" ]}, "=", {ref:[ "children", "parent_ID" ]}
],
}},
],
}
})
if (q.toSql) expect (q.toSql()) .to.eql (
`SELECT json_insert('{}',` +
`'$."ID"',ID,` +
`'$."name"',name,` +
`'$."children"',children->'$'` +
`) as _json_ FROM (` +
`SELECT Categories.ID,Categories.name,(` +
`SELECT jsonb_group_array(jsonb_insert('{}','$."name"',name)) as _json_ FROM (` +
`SELECT children.name FROM Categories as children WHERE Categories.ID = children.parent_ID` +
`)` +
`) as children FROM Categories as Categories` +
`)`
)
})
it ('supports nested reads', ()=> expect (
SELECT.one.from (Cats, c=>{ SELECT.one.from (Cats, c=>{
c.ID, c.name.as('parent'), c.children (c=>{ c.ID, c.name.as('parent'), c.children (c=>{
c.name.as('child') c.name.as('child')
}) })
}) .where ({name:'Cat'}) }) .where ({name:'Cat'})
) .to.eventually.eql ( ) .to.eql (
{ ID:101, parent:'Cat', children:[ { ID:101, parent:'Cat', children:[
{ child:'Kitty' }, { child:'Kitty' },
{ child:'Catwoman' }, { child:'Catwoman' },
]} ]}
)) )
return
it ('supports deeply nested reads', ()=> expect ( }
expect (await
SELECT.one.from (Cats, c=>{ SELECT.one.from (Cats, c=>{
c.ID, c.name.as('parent'), c.children (c=>{
c.name.as('child')
})
}) .where ({name:'Cat'})
) .to.eql (
{ ID:101, parent:'Cat', children:[
{ ID:102, child:'Kitty' },
{ ID:106, child:'Catwoman' },
]}
)
})
it ('supports deeply nested reads', async()=>{
if (require('semver').gte(cds.version, '5.9.0')) {
expect (await SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children ( c.ID, c.name, c.children (
c => { c.name }, c => { c.name },
{levels:3} {levels:3}
) )
}) .where ({name:'Cat'}) }) .where ({name:'Cat'})
) .to.eventually.eql ( ) .to.eql (
{ ID:101, name:'Cat', children:[ { ID:101, name:'Cat', children:[
{ name:'Kitty', children:[ { name:'Kitty', children:[
{ name:'Kitty Cat', children:[ { name:'Kitty Cat', children:[
@@ -106,16 +80,36 @@ describe('cap/samples - Hierarchical Data', ()=>{
{ name:'Catwoman', children:[ { name:'Catwoman', children:[
{ name:'Catalina', children:[] } ]}, { name:'Catalina', children:[] } ]},
]} ]}
)) )
return
}
expect (await SELECT.one.from (Cats, c=>{
c.ID, c.name, c.children (
c => { c.name },
{levels:3}
)
}) .where ({name:'Cat'})
) .to.eql (
{ ID:101, name:'Cat', children:[
{ ID:102, name:'Kitty', children:[
{ ID:103, name:'Kitty Cat', children:[
{ ID:104, name:'Aristocat' }, ]}, // level 3
{ ID:105, name:'Kitty Bat', children:[] }, ]},
{ ID:106, name:'Catwoman', children:[
{ ID:107, name:'Catalina', children:[] } ]},
]}
)
})
it ('supports cascaded deletes', async()=>{ it ('supports cascaded deletes', async()=>{
const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]}) const affectedRows = await DELETE.from (Cats) .where ({ID:[102,106]})
expect (affectedRows) .to.be.greaterThan (0) expect (affectedRows) .to.be.greaterThan (0)
await expect (SELECT`ID,name`.from(Cats) ).to.eventually.eql ([ const expected = [
{ ID:100, name:'Some Cats...' }, { ID:100, name:'Some Cats...' },
{ ID:101, name:'Cat' }, { ID:101, name:'Cat' },
{ ID:108, name:'Catweazle' } { ID:108, name:'Catweazle' }
]) ]
expect ( await SELECT`ID,name`.from(Cats) ).to.eql (expected)
}) })
}) })

View File

@@ -1,5 +0,0 @@
{
"devDependencies": {
"@cap-js/sqlite": "^1"
}
}

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Localized Data', () => { describe('cap/samples - Localized Data', () => {

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Messaging', ()=>{ describe('cap/samples - Messaging', ()=>{

View File

@@ -1,4 +1,4 @@
const cds = require('@sap/cds') const cds = require('@sap/cds/lib')
describe('cap/samples - Bookshop APIs', () => { describe('cap/samples - Bookshop APIs', () => {
const { GET, expect, axios } = cds.test ('@capire/bookshop') const { GET, expect, axios } = cds.test ('@capire/bookshop')
@@ -8,10 +8,9 @@ describe('cap/samples - Bookshop APIs', () => {
const { headers, status, data } = await GET `/browse/$metadata` const { headers, status, data } = await GET `/browse/$metadata`
expect(status).to.equal(200) expect(status).to.equal(200)
expect(headers).to.contain({ expect(headers).to.contain({
// 'content-type': 'application/xml', //> fails with 'application/xml;charset=utf-8', which is set by express 'content-type': 'application/xml',
'odata-version': '4.0', 'odata-version': '4.0',
}) })
expect(headers['content-type']).to.match(/application\/xml/)
expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">') expect(data).to.contain('<EntitySet Name="Books" EntityType="CatalogService.Books">')
expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>') expect(data).to.contain('<Annotation Term="Common.Label" String="Currency"/>')
}) })
@@ -29,8 +28,6 @@ describe('cap/samples - Bookshop APIs', () => {
]) ])
}) })
describe('query options...', () => {
it('supports $search in multiple fields', async () => { it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{ const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` }, params: { $search: 'Po', $select: `title,author` },
@@ -89,7 +86,6 @@ describe('cap/samples - Bookshop APIs', () => {
{ ID: 271, title: 'Catweazle' }, { ID: 271, title: 'Catweazle' },
]) ])
}) })
})
it('serves user info', async () => { it('serves user info', async () => {
const { data: alice } = await GET `/user/me` const { data: alice } = await GET `/user/me`