Compare commits

..

10 Commits

Author SHA1 Message Date
Lothar Bender
8c5a20bdb0 Merge branch 'main' into compose-with-cds-plugins 2023-08-25 17:33:56 +02:00
Daniel Schlachter
afd4df3f39 similar behavior cds w bookstore cds w compose 2023-08-18 15:45:22 +02:00
Daniel Schlachter
0228841260 Merge branch 'main' into compose-with-cds-plugins 2023-08-18 11:48:30 +02:00
Daniel Schlachter
24ad8a84da Merge branch 'compose-with-cds-plugins' of https://github.com/SAP-samples/cloud-cap-samples into compose-with-cds-plugins 2023-08-18 11:47:46 +02:00
Daniel Schlachter
e3acbb9832 compose adjustments 2023-08-18 11:46:27 +02:00
Daniel Schlachter
2e0a5aeb73 Merge branch 'main' into compose-with-cds-plugins 2023-08-03 10:02:33 +02:00
Daniel Schlachter
191e577e51 Merge branch 'main' into compose-with-cds-plugins 2023-08-01 10:47:09 +02:00
Daniel Hutzel
a9f040edcc Merge branch 'main' into compose-with-cds-plugins 2023-07-26 17:10:21 +02:00
Daniel Hutzel
86fb1d20d3 my-project -> compose 2023-07-26 17:06:08 +02:00
Daniel Hutzel
dfb7d80849 Compose with cds-plugins 2023-07-26 17:01:15 +02:00
97 changed files with 5267 additions and 4286 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]
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/

29
.reuse/dep5 Normal file
View File

@@ -0,0 +1,29 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: cloud-cap-samples
Upstream-Contact: <Christian Georgi (christian.georgi@sap.com)>
Source: https://github.com/SAP-samples/cloud-cap-samples
Disclaimer: The code in this project may include calls to APIs (“API Calls”) of
SAP or third-party products or services developed outside of this project
(“External Products”).
“APIs” means application programming interfaces, as well as their respective
specifications and implementing code that allows software to communicate with
other software.
API Calls to External Products are not licensed under the open source license
that governs this project. The use of such API Calls and related External
Products are subject to applicable additional agreements with the relevant
provider of the External Products. In no event shall the open source license
that governs this project grant any rights in or to any External Products,or
alter, expand or supersede any terms of the applicable additional agreements.
If you have a valid license agreement with SAP for the use of a particular SAP
External Product, then you may make use of any API Calls included in this
projects code for that SAP External Product, subject to the terms of such
license agreement. If you do not have a valid license agreement for the use of
a particular SAP External Product, then you may only make use of any API Calls
in this project for that SAP External Product for your internal, non-productive
and non-commercial test and evaluation of such API Calls. Nothing herein grants
you any rights to use or access any SAP External Product, or provide any third
parties the right to use of access any SAP External Product, through API Calls.
Files: *
Copyright: 2019-2020 SAP SE or an SAP affiliate company and cap-cloud-samples
License: Apache-2.0

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,22 +1,13 @@
[![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)
# 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). &rarr; 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
@@ -39,7 +30,7 @@ cd samples
In the samples folder run: In the samples folder run:
```sh ```sh
npm ci npm install
``` ```
### Run ### Run

View File

@@ -1,11 +0,0 @@
version = 1
SPDX-PackageName = "cloud-cap-samples"
SPDX-PackageSupplier = "<CAP (cap@sap.com)>"
SPDX-PackageDownloadLocation = "https://github.com/SAP-samples/cloud-cap-samples"
SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n projects code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
[[annotations]]
path = "**.**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2019-2025 SAP SE or an SAP affiliate company and cap-cloud-samples"
SPDX-License-Identifier = "Apache-2.0"

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

3
bookshop/cds-plugin.js Normal file
View File

@@ -0,0 +1,3 @@
cds.once('bootstrap',(app)=>{
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
})

View File

@@ -1,5 +1,3 @@
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
@@ -8,7 +6,7 @@ const cds = require('@sap/cds')
// NOTE: We use cds.on('served') to delay the UPSERTs after the db init // NOTE: We use cds.on('served') to delay the UPSERTs after the db init
// to run after all INSERTs from .csv files happened. // to run after all INSERTs from .csv files happened.
module.exports = cds.on('served', ()=> module.exports = cds.on('served', ()=> cds.run(
UPSERT.into ('sap.common.Currencies') .columns ( UPSERT.into ('sap.common.Currencies') .columns (
[ 'code', 'symbol', 'name' ] [ 'code', 'symbol', 'name' ]
) .rows ( ) .rows (
@@ -18,4 +16,4 @@ module.exports = cds.on('served', ()=>
[ 'ILS', '₪', 'Shekel' ], [ 'ILS', '₪', 'Shekel' ],
[ 'JPY', '¥', 'Yen' ], [ 'JPY', '¥', 'Yen' ],
) )
) ))

View File

@@ -2,25 +2,25 @@ using { Currency, managed, sap } from '@sap/cds/common';
namespace sap.capire.bookshop; namespace sap.capire.bookshop;
entity Books : managed { entity Books : managed {
key ID : Integer; key ID : Integer;
title : localized String(111) @mandatory; title : localized String(111) @mandatory ;
descr : localized String(1111); descr : localized String(1111);
author : Association to Authors @mandatory; author : Association to Authors @mandatory;
genre : Association to Genres; genre : Association to Genres;
stock : Integer; stock : Integer;
price : Decimal; price : Decimal;
currency : Currency; currency : Currency;
image : LargeBinary @Core.MediaType: 'image/png'; image : LargeBinary @Core.MediaType : 'image/png';
} }
entity Authors : managed { entity Authors : managed {
key ID : Integer; key ID : Integer;
name : String(111) @mandatory; name : String(111) @mandatory;
dateOfBirth : Date; dateOfBirth : Date;
dateOfDeath : Date; dateOfDeath : Date;
placeOfBirth : String; placeOfBirth : String;
placeOfDeath : String; placeOfDeath : String;
books : Association to many Books on books.author = $self; books : Association to many Books on books.author = $self;
} }
/** Hierarchically organized Code List for Genres */ /** Hierarchically organized Code List for Genres */

2
bookshop/index.js Normal file
View File

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

View File

@@ -13,12 +13,20 @@
"@cap-js/sqlite": "*" "@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": {
"bookshop-services": {
"model": "@capire/bookshop"
}
}
} }
} }

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

@@ -1,38 +1,29 @@
const cds = require('@sap/cds') const cds = require('@sap/cds')
class CatalogService extends cds.ApplicationService { init() { 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 })
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
// Reduce stock in database and return updated stock value return { stock }
await UPDATE (Books, id) .with ({ stock: book.stock -= quantity })
return book
}) })
// Emit event when an order has been submitted // Add some discount for overstocked books
this.after('submitOrder', async (_,req) => { this.after ('READ', ListOfBooks, each => {
let { book, quantity } = req.data if (each.stock > 111) each.title += ` -- 11% discount!`
await this.emit('OrderedBook', { book, quantity, buyer: req.user.id })
}) })
// Delegate requests to the underlying generic service
return super.init() return super.init()
}} }}
module.exports = CatalogService module.exports = { CatalogService }

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

@@ -6,6 +6,7 @@
"@capire/reviews": "*", "@capire/reviews": "*",
"@capire/orders": "*", "@capire/orders": "*",
"@capire/common": "*", "@capire/common": "*",
"@capire/fiori": "*",
"@capire/data-viewer": "*", "@capire/data-viewer": "*",
"@sap/cds": ">=5", "@sap/cds": ">=5",
"express": "^4.17.1" "express": "^4.17.1"

View File

@@ -1,20 +1,18 @@
const cds = require ('@sap/cds') const cds = require ('@sap/cds')
// Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{
app.serve ('/admin-authors') .from ('@capire/fiori','app/admin-authors')
app.serve ('/admin-books') .from ('@capire/fiori','app/admin-books')
app.serve ('/browse-books') .from ('@capire/fiori','app/browse')
})
// Add mashup logic // Add mashup logic
cds.once('served', require('./srv/mashup')) cds.once('served', require('./srv/mashup'))
// Add routes to UIs from imported packages
cds.once('bootstrap',(app)=>{
try {
app.serve ('/bookshop') .from ('@capire/bookshop','app/vue')
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
app.serve ('/orders') .from('@capire/orders','app/orders')
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')
// 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,8 @@ 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 {
book : Association to Books on product.ID = book.ID extend Items with {
book : Association to Books on product.ID = book.ID
}
} }
// Ensure models from all imported packages are loaded
using from '@capire/orders/app/fiori';
using from '@capire/data-viewer';
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 }
@@ -42,7 +41,7 @@ GET {{bookshop}}/browse/Books(201)?
### ###
GET {{bookshop}}/browse/Books? GET {{bookshop}}/browse/Books?
&$select=title,author&$expand=currency &$select=title,author&$expand=currency
Accept-Language: de Accept-Language: de
################################################# #################################################
@@ -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

@@ -7,7 +7,7 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"@capire/common/data": { "common-data": {
"model": "@capire/common" "model": "@capire/common"
} }
} }

View File

@@ -0,0 +1,168 @@
{
"services": {
"LaunchPage": {
"adapter": {
"config": {
"catalogs": [],
"groups": [
{
"id": "Bookshop",
"title": "Bookshop",
"isPreset": true,
"isVisible": true,
"isGroupLocked": false,
"tiles": [
{
"id": "BrowseBooks",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Books",
"targetURL": "#Books-display"
}
},
{
"id": "BrowseGenres",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Browse Genres (OData v2)",
"targetURL": "#Genres-display"
}
}
]
},
{
"id": "Administration",
"title": "Administration",
"isPreset": true,
"isVisible": true,
"isGroupLocked": false,
"tiles": [
{
"id": "ManageBooks",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Books",
"targetURL": "#Books-manage"
}
},
{
"id": "ManageAuthors",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Authors",
"targetURL": "#Authors-display"
}
},
{
"id": "ManageOrders",
"tileType": "sap.ushell.ui.tile.StaticTile",
"properties": {
"title": "Manage Orders",
"targetURL": "#Orders-manage"
}
}
]
}
]
}
}
},
"NavTargetResolution": {
"config": {
"enableClientSideTargetResolution": true
}
},
"ClientSideTargetResolution": {
"adapter": {
"config": {
"inbounds": {
"BrowseBooks": {
"semanticObject": "Books",
"action": "display",
"title": "Browse Books",
"signature": {
"parameters": {
"Books.ID": {
"renameTo": "ID"
},
"Authors.books.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=bookshop",
"url": "/browse/webapp"
}
},
"BrowseAuthors": {
"semanticObject": "Authors",
"action": "display",
"title": "Browse Authors",
"signature": {
"parameters": {
"Books.author.ID":{
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=authors",
"url": "/admin-authors/webapp"
}
},
"BrowseGenres": {
"semanticObject": "Genres",
"action": "display",
"title": "Browse Genres",
"signature": {
"parameters": {
"Genre.ID": {
"renameTo": "ID"
}
},
"additionalParameters": "ignored"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=genres",
"url": "/genres/webapp"
}
},
"ManageBooks": {
"semanticObject": "Books",
"action": "manage",
"title": "Manage Books",
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=books",
"url": "/admin-books/webapp"
}
},
"ManageOrders": {
"semanticObject": "Orders",
"action": "manage",
"signature": {
"parameters": {},
"additionalParameters": "allowed"
},
"resolutionResult": {
"applicationType": "SAPUI5",
"additionalInformation": "SAPUI5.Component=orders",
"url": "/orders/webapp"
}
}
}
}
}
}
}
}

12
compose/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "@capire/bookstore",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@capire/fiori": "*",
"@capire/orders": "*",
"@sap/cds": ">=5",
"express": "^4.17.1"
}
}

8
compose/server.js Normal file
View File

@@ -0,0 +1,8 @@
const cds = require ('@sap/cds')
// Add Swagger UI
require('./srv/swagger-ui')
// For didactic reasons in capire
const { ReviewsService, OrdersService } = cds.requires
if (!ReviewsService?.credentials && !OrdersService?.credentials) cds.requires.messaging = false

10
compose/srv/swagger-ui.js Normal file
View File

@@ -0,0 +1,10 @@
// -----------------------------------------------------------------------
// Adding Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
const cds = require ('@sap/cds')
try {
const cds_swagger = require ('cds-swagger-ui-express')
cds.once ('bootstrap', app => app.use (cds_swagger()) )
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') throw err
}

View File

@@ -0,0 +1,3 @@
cds.once('bootstrap',(app)=>{
app.serve ('/data') .from('@capire/data-viewer','app/viewer')
})

View File

@@ -9,5 +9,12 @@
"app", "app",
"srv", "srv",
"index.cds" "index.cds"
] ],
"cds": {
"requires": {
"orders-service": {
"model": "@capire/data-viewer"
}
}
}
} }

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>

9
fiori/cds-plugin.js Normal file
View File

@@ -0,0 +1,9 @@
// 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.once('bootstrap', app => {
app.use(proxy(opts)) // install proxy
// cds.log('cov2ap','silent') // suppress anoying log outpout, e.g. for `npm run mocha -- --reporter nyan`
app.serve ('/fiori-apps') .from ('@capire/fiori','app/fiori-apps.html')
})

View File

@@ -1 +1,8 @@
using from './db/sqlite/index';
using from './app/services';
using from './app/genres/fiori-service';
using from './app/browse/fiori-service';
using from './app/admin-books/fiori-service';
using from './app/admin-authors/fiori-service';
using from './db/common'; using from './db/common';
using from './app/common';

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": {
@@ -24,6 +22,9 @@
"kind": "odata", "kind": "odata",
"model": "@capire/orders" "model": "@capire/orders"
}, },
"self": {
"model": "@capire/fiori"
},
"messaging": { "messaging": {
"[production]": { "[production]": {
"kind": "enterprise-messaging" "kind": "enterprise-messaging"
@@ -50,10 +51,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,3 +1,3 @@
service say { service say @(path: '/say') {
function hello (to:String) returns String; function hello (to:String) returns String;
} }

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

3
orders/cds-plugin.js Normal file
View File

@@ -0,0 +1,3 @@
cds.once('bootstrap',(app)=>{
app.serve ('/orders') .from('@capire/orders','app/orders')
})

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

@@ -4,5 +4,12 @@
"dependencies": { "dependencies": {
"@capire/common": "*", "@capire/common": "*",
"@sap/cds": ">=5" "@sap/cds": ">=5"
},
"cds": {
"requires": {
"orders-service": {
"model": "@capire/orders"
}
}
} }
} }

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

7520
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": ">=7"
}, },
"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,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.' }

3
reviews/cds-plugin.js Normal file
View File

@@ -0,0 +1,3 @@
cds.once('bootstrap',(app)=>{
app.serve ('/reviews') .from ('@capire/reviews','app/vue')
})

View File

@@ -12,6 +12,10 @@
}, },
"cds": { "cds": {
"requires": { "requires": {
"ReviewsService": {
"kind": "odata",
"model": "@capire/reviews"
},
"messaging": { "messaging": {
"[development]": { "kind": "file-based-messaging" }, "[development]": { "kind": "file-based-messaging" },
"[hybrid]": { "kind": "enterprise-messaging-shared" }, "[hybrid]": { "kind": "enterprise-messaging-shared" },

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() {

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,89 +33,83 @@ 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: { SELECT.one.from (Cats, c=>{
from: { ref:[ "Categories" ] }, c.ID, c.name.as('parent'), c.children (c=>{
columns: [ c.name.as('child')
{ ref: [ "ID" ] }, })
{ ref: [ "name" ] }, }) .where ({name:'Cat'})
{ ref: [ "children" ], expand: [ {ref:['name']} ] }, ) .to.eql (
] { ID:101, parent:'Cat', children:[
} { child:'Kitty' },
}) { child:'Catwoman' },
if (q.forSQL) expect (q.forSQL()) .to.eql ({ ]}
SELECT: { )
from: { ref:[ "Categories" ], as: "Categories" }, return
columns: [ }
{ ref: [ "Categories", "ID" ] }, expect (await
{ ref: [ "Categories", "name" ] }, SELECT.one.from (Cats, c=>{
{ as: "children", SELECT: { expand: true, c.ID, c.name.as('parent'), c.children (c=>{
one: false, c.name.as('child')
columns: [{ ref: [ "children", "name" ]}], })
from: { ref:["Categories"], as: "children" }, }) .where ({name:'Cat'})
where: [ ) .to.eql (
{ref:[ "Categories", "ID" ]}, "=", {ref:[ "children", "parent_ID" ]} { ID:101, parent:'Cat', children:[
], { ID:102, child:'Kitty' },
}}, { ID:106, child:'Catwoman' },
], ]}
}
})
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 ( it ('supports deeply nested reads', async()=>{
SELECT.one.from (Cats, c=>{ if (require('semver').gte(cds.version, '5.9.0')) {
c.ID, c.name.as('parent'), c.children (c=>{ expect (await SELECT.one.from (Cats, c=>{
c.name.as('child') c.ID, c.name, c.children (
}) c => { c.name },
}) .where ({name:'Cat'}) {levels:3}
) .to.eventually.eql ( )
{ ID:101, parent:'Cat', children:[ }) .where ({name:'Cat'})
{ child:'Kitty' }, ) .to.eql (
{ child:'Catwoman' }, { ID:101, name:'Cat', children:[
]} { name:'Kitty', children:[
)) { name:'Kitty Cat', children:[
{ name:'Aristocat' }, ]}, // level 3
it ('supports deeply nested reads', ()=> expect ( { name:'Kitty Bat', children:[] }, ]},
SELECT.one.from (Cats, c=>{ { name:'Catwoman', children:[
{ name:'Catalina', children:[] } ]},
]}
)
return
}
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:[ { ID:102, name:'Kitty', children:[
{ name:'Kitty Cat', children:[ { ID:103, name:'Kitty Cat', children:[
{ name:'Aristocat' }, ]}, // level 3 { ID:104, name:'Aristocat' }, ]}, // level 3
{ name:'Kitty Bat', children:[] }, ]}, { ID:105, name:'Kitty Bat', children:[] }, ]},
{ name:'Catwoman', children:[ { ID:106, name:'Catwoman', children:[
{ name:'Catalina', 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,66 +28,63 @@ describe('cap/samples - Bookshop APIs', () => {
]) ])
}) })
describe('query options...', () => { it('supports $search in multiple fields', async () => {
const { data } = await GET `/browse/Books ${{
params: { $search: 'Po', $select: `title,author` },
}}`
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
])
})
it('supports $search in multiple fields', async () => { it('supports $select', async () => {
const { data } = await GET `/browse/Books ${{ const { data } = await GET(`/browse/Books`, {
params: { $search: 'Po', $select: `title,author` }, params: { $select: `ID,title` },
}}`
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' },
{ ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' },
{ ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' },
{ ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' },
])
}) })
expect(data.value).to.containSubset([
{ ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
it('supports $select', async () => { it('supports $expand', async () => {
const { data } = await GET(`/browse/Books`, { const { data } = await GET(`/admin/Authors`, {
params: { $select: `ID,title` }, params: {
}) $select: `name`,
expect(data.value).to.containSubset([ $expand: `books($select=title)`,
{ ID: 201, title: 'Wuthering Heights' }, },
{ ID: 207, title: 'Jane Eyre' },
{ ID: 251, title: 'The Raven' },
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
}) })
expect(data.value).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('supports $expand', async () => { it('supports $value requests', async () => {
const { data } = await GET(`/admin/Authors`, { const { data } = await GET`/admin/Books/201/stock/$value`
params: { expect(data).to.equal(12)
$select: `name`, })
$expand: `books($select=title)`,
},
})
expect(data.value).to.containSubset([
{ name: 'Emily Brontë', books: [{ title: 'Wuthering Heights' }] },
{ name: 'Charlotte Brontë', books: [{ title: 'Jane Eyre' }] },
{ name: 'Edgar Allen Poe', books: [{ title: 'The Raven' }, { title: 'Eleonora' }] },
{ name: 'Richard Carpenter', books: [{ title: 'Catweazle' }] },
])
})
it('supports $value requests', async () => { it('supports $top/$skip paging', async () => {
const { data } = await GET`/admin/Books/201/stock/$value` const { data: p1 } = await GET`/browse/Books?$select=title&$top=3`
expect(data).to.equal(12) expect(p1.value).to.containSubset([
}) { ID: 201, title: 'Wuthering Heights' },
{ ID: 207, title: 'Jane Eyre' },
it('supports $top/$skip paging', async () => { { ID: 251, title: 'The Raven' },
const { data: p1 } = await GET`/browse/Books?$select=title&$top=3` ])
expect(p1.value).to.containSubset([ const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
{ ID: 201, title: 'Wuthering Heights' }, expect(p2.value).to.containSubset([
{ ID: 207, title: 'Jane Eyre' }, { ID: 252, title: 'Eleonora' },
{ ID: 251, title: 'The Raven' }, { ID: 271, title: 'Catweazle' },
]) ])
const { data: p2 } = await GET`/browse/Books?$select=title&$skip=3`
expect(p2.value).to.containSubset([
{ ID: 252, title: 'Eleonora' },
{ ID: 271, title: 'Catweazle' },
])
})
}) })
it('serves user info', async () => { it('serves user info', async () => {