Compare commits
179 Commits
cds.deploy
...
adding-sup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67643b8d95 | ||
|
|
cd3aad59e1 | ||
|
|
bcbf65f95e | ||
|
|
1189374860 | ||
|
|
9f2eb8fa83 | ||
|
|
7b18e15e5b | ||
|
|
00bfc1507e | ||
|
|
47a58e7393 | ||
|
|
c04b972d27 | ||
|
|
85b9b9d3a3 | ||
|
|
3655eef971 | ||
|
|
87dd356204 | ||
|
|
a89ec34f7c | ||
|
|
444dcacb14 | ||
|
|
2011a1ca7a | ||
|
|
7e0c36ede5 | ||
|
|
e8d08d039e | ||
|
|
5e3258913e | ||
|
|
44e14926e4 | ||
|
|
592e945c28 | ||
|
|
5d5c787365 | ||
|
|
bb79def1d9 | ||
|
|
bd4a38bf54 | ||
|
|
abf18d74db | ||
|
|
f144354229 | ||
|
|
97670bbc84 | ||
|
|
82d6f54337 | ||
|
|
8ba8381ca7 | ||
|
|
e7fbcc1ac1 | ||
|
|
d977c21483 | ||
|
|
2e10b1ac4d | ||
|
|
cabcd404cf | ||
|
|
5141df8e0f | ||
|
|
785ecbda8f | ||
|
|
2b9ceba62f | ||
|
|
c6e2fac860 | ||
|
|
f2877db34f | ||
|
|
d2bd4c5bb1 | ||
|
|
732b6b081c | ||
|
|
db595a9635 | ||
|
|
4a8b85f227 | ||
|
|
0cb7349e62 | ||
|
|
ba3eeff58b | ||
|
|
86e6983bed | ||
|
|
9bbc431448 | ||
|
|
097b74db03 | ||
|
|
870f8d063d | ||
|
|
4a0db3e259 | ||
|
|
5e76c7f52e | ||
|
|
31c110e9e2 | ||
|
|
a4baee6e3b | ||
|
|
95b01e7d7a | ||
|
|
c2437fc419 | ||
|
|
9381459458 | ||
|
|
8d67c0bf62 | ||
|
|
21359a7ad3 | ||
|
|
649b9c8ca8 | ||
|
|
11c54b29d0 | ||
|
|
80302a0a3d | ||
|
|
c8c10b7c8a | ||
|
|
d9d7203e49 | ||
|
|
4a8379e953 | ||
|
|
dd28bd04f6 | ||
|
|
28a7c16895 | ||
|
|
5397df5e81 | ||
|
|
f57ae79e37 | ||
|
|
6412df75dd | ||
|
|
9277aa12da | ||
|
|
d41a9e10ab | ||
|
|
0b2182afcb | ||
|
|
6b74c23aa0 | ||
|
|
c854717359 | ||
|
|
9dfe62c5c7 | ||
|
|
5b4210bb38 | ||
|
|
48ee934b00 | ||
|
|
254bb40e38 | ||
|
|
fb5d00bbe0 | ||
|
|
7ae992c5bb | ||
|
|
016587094f | ||
|
|
d04cb801c4 | ||
|
|
902afd8a76 | ||
|
|
c4bee1f09a | ||
|
|
278258c436 | ||
|
|
8601bd8a46 | ||
|
|
98113c46fd | ||
|
|
eb4bc703dd | ||
|
|
de806a1e38 | ||
|
|
2999c8df83 | ||
|
|
87dbb4552a | ||
|
|
0bdf4bb93e | ||
|
|
a4810c3a7b | ||
|
|
c899843319 | ||
|
|
43e5f6faef | ||
|
|
1b5cc62d1c | ||
|
|
4b48d68ba6 | ||
|
|
d21a0e08ca | ||
|
|
50fd83fd19 | ||
|
|
3f20f4b9a2 | ||
|
|
5031779be8 | ||
|
|
ad385cbc6c | ||
|
|
2650db4ee9 | ||
|
|
e04e9d231d | ||
|
|
476724792e | ||
|
|
5ae31f7c67 | ||
|
|
edbca44a43 | ||
|
|
02f19e295d | ||
|
|
c1141e6c87 | ||
|
|
05cdb67329 | ||
|
|
4877386e86 | ||
|
|
4bd4446975 | ||
|
|
a54b0124ea | ||
|
|
944afe3bc8 | ||
|
|
92a83f71a1 | ||
|
|
5e5ace0dd4 | ||
|
|
6e686c6c8b | ||
|
|
1ccddb2155 | ||
|
|
7c8e52d5e9 | ||
|
|
593228a51e | ||
|
|
624cea6343 | ||
|
|
0dc1dc198d | ||
|
|
20bfb5c5f7 | ||
|
|
be87f5617a | ||
|
|
3117df1282 | ||
|
|
adfe170e8d | ||
|
|
6b08826af5 | ||
|
|
c6239f0375 | ||
|
|
be685437aa | ||
|
|
0d19e56509 | ||
|
|
ff3801be71 | ||
|
|
702c3245bd | ||
|
|
14002300c5 | ||
|
|
ad417ec061 | ||
|
|
a6ec296129 | ||
|
|
1b10bbbfe4 | ||
|
|
aaa1b2d6c7 | ||
|
|
fc088015d3 | ||
|
|
1cb169e886 | ||
|
|
9b9bb4c114 | ||
|
|
2536f36596 | ||
|
|
c9f7dc68b8 | ||
|
|
5f160c0927 | ||
|
|
0c4658a3ef | ||
|
|
33cd70065a | ||
|
|
450577d5a7 | ||
|
|
5a23ba0f76 | ||
|
|
7d9303635e | ||
|
|
b724ae900c | ||
|
|
064ce32b8a | ||
|
|
2c064d0200 | ||
|
|
2f96b92854 | ||
|
|
99dc8d31d7 | ||
|
|
c672e0f1fa | ||
|
|
51ee6d167c | ||
|
|
0ddd70acbc | ||
|
|
3e52a9a102 | ||
|
|
b44701ef62 | ||
|
|
c23ddc7e54 | ||
|
|
66bd2f707c | ||
|
|
3320c7e5a2 | ||
|
|
a35782e775 | ||
|
|
e5bd8ec5a5 | ||
|
|
0aa95a0a67 | ||
|
|
5015eb8c52 | ||
|
|
6d3f4c689f | ||
|
|
f0fead2bc2 | ||
|
|
f1d780d6d9 | ||
|
|
796bf62bde | ||
|
|
5f176a0b88 | ||
|
|
a5c8b5101e | ||
|
|
d72ff809b0 | ||
|
|
5e829ae7bb | ||
|
|
d265a385f8 | ||
|
|
80f469b5b6 | ||
|
|
8e8ae949df | ||
|
|
3d24dc491d | ||
|
|
f593265687 | ||
|
|
6d488b042c | ||
|
|
d5ef630743 | ||
|
|
b47c9d75df |
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
@@ -24,5 +24,5 @@ jobs:
|
|||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm install
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,3 +15,5 @@ default-env.json
|
|||||||
packages/messageBox
|
packages/messageBox
|
||||||
reviews/msg-box
|
reviews/msg-box
|
||||||
reviews/db/test.db
|
reviews/db/test.db
|
||||||
|
|
||||||
|
*.openapi3.json
|
||||||
|
|||||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
|
||||||
|
@sap:registry=https://registry.npmjs.org/
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
@@ -5,7 +5,7 @@ const app = express()
|
|||||||
|
|
||||||
const { PORT=4444 } = process.env
|
const { PORT=4444 } = process.env
|
||||||
const [,,port=PORT] = process.argv
|
const [,,port=PORT] = process.argv
|
||||||
process.chdir(__dirname)
|
const cwd = __dirname
|
||||||
|
|
||||||
app.use('/-/:tarball', (req,res,next) => {
|
app.use('/-/:tarball', (req,res,next) => {
|
||||||
console.debug ('GET', req.params)
|
console.debug ('GET', req.params)
|
||||||
@@ -13,7 +13,7 @@ app.use('/-/:tarball', (req,res,next) => {
|
|||||||
const { tarball } = req.params
|
const { tarball } = req.params
|
||||||
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
|
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
|
||||||
fs.lstat(tarball,(err => {
|
fs.lstat(tarball,(err => {
|
||||||
if (err) exec(`npm pack ../${pkg}`,next)
|
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
||||||
else next()
|
else next()
|
||||||
}))
|
}))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
37
.vscode/launch.json
vendored
37
.vscode/launch.json
vendored
@@ -4,28 +4,31 @@
|
|||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
|
||||||
"name": "Attach by Process ID",
|
|
||||||
"processId": "${command:PickProcess}",
|
|
||||||
"request": "attach",
|
|
||||||
"skipFiles": [
|
|
||||||
"<node_internals>/**"
|
|
||||||
],
|
|
||||||
"type": "pwa-node"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "bookshop",
|
"name": "bookshop",
|
||||||
"command": "cds watch bookshop",
|
"command": "npx cds watch bookshop",
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal",
|
"type": "node-terminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"request": "launch",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Fiori app",
|
"name": "Fiori App",
|
||||||
"command": "cds watch fiori",
|
"command": "npx cds watch fiori",
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal",
|
"type": "node-terminal",
|
||||||
"skipFiles": ["<node_internals>/**"]
|
"request": "launch",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
@@ -33,7 +36,7 @@
|
|||||||
"type": "pickString",
|
"type": "pickString",
|
||||||
"id": "sample",
|
"id": "sample",
|
||||||
"description": "Which sample do you want to start?",
|
"description": "Which sample do you want to start?",
|
||||||
"options": ["bookshop", "fiori", "reviews", "reviews/test/bookshop"],
|
"options": [ "bookshop", "fiori", "reviews", "reviews" ],
|
||||||
"default": "bookshop"
|
"default": "bookshop"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -4,5 +4,14 @@
|
|||||||
"**/.gitignore": true,
|
"**/.gitignore": true,
|
||||||
"**/.vscode": true,
|
"**/.vscode": true,
|
||||||
"LICENSES/**": true
|
"LICENSES/**": true
|
||||||
|
},
|
||||||
|
"debug.javascript.terminalOptions": {
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/cds/lib/lazy.js",
|
||||||
|
"**/cds/lib/req/cls.js",
|
||||||
|
"**/odata-v4/okra/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -83,4 +83,36 @@ In case you've a question, find a bug, or otherwise need support, use our [commu
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file.
|
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.
|
||||||
|
|
||||||
|
# Suppliers - in progress for Messaging & Service Consumption -
|
||||||
|
|
||||||
|
## TODOs
|
||||||
|
|
||||||
|
1. Fix issues when running in same process
|
||||||
|
2. Automated tests
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Run:
|
||||||
|
|
||||||
|
```
|
||||||
|
CDS_ENV=local-hybrid cds mock API_BUSINESS_PARTNER -p 5001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Wait until startup is completed
|
||||||
|
3. Run in a 2nd terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
CDS_ENV=local-hybrid cds serve all --with-mocks --in-memory
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Now, you can issues the requests listed in `suppliers/requests.http`
|
||||||
|
|
||||||
|
## Request Sequence
|
||||||
|
|
||||||
|
* TODO
|
||||||
|
## URLs
|
||||||
|
|
||||||
|
* Get books with their replicated supplier: http://localhost:4004/browse/Books?$expand=supplier
|
||||||
|
* Get remote suppliers: http://localhost:4004/admin/Suppliers?$top=11
|
||||||
@@ -35,7 +35,7 @@ const books = new Vue ({
|
|||||||
try {
|
try {
|
||||||
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
||||||
book.stock = res.data.stock
|
book.stock = res.data.stock
|
||||||
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` }
|
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
books.order = { amount, failed: e.response.data.error.message }
|
books.order = { amount, failed: e.response.data.error.message }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ ID;title;descr;author_ID;stock;price;currency_code;genre_ID
|
|||||||
207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
|
207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
|
||||||
251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
|
251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
|
||||||
252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16
|
252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16
|
||||||
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;15;EUR;13
|
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"description": "A simple self-contained bookshop service.",
|
"description": "A simple self-contained bookshop service.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@sap/cds": ">=4",
|
"@sap/cds": "^5.0.4",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": "0.4.1"
|
"passport": "0.4.1"
|
||||||
},
|
},
|
||||||
@@ -16,8 +16,7 @@
|
|||||||
"cds": {
|
"cds": {
|
||||||
"requires": {
|
"requires": {
|
||||||
"db": {
|
"db": {
|
||||||
"kind": "sqlite",
|
"kind": "sql"
|
||||||
"model": "*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1,13 +1,16 @@
|
|||||||
using { sap.capire.bookshop as my } from '../db/schema';
|
using { sap.capire.bookshop as my } from '../db/schema';
|
||||||
service CatalogService @(path:'/browse') {
|
service CatalogService @(path:'/browse') {
|
||||||
|
|
||||||
@readonly entity Books as SELECT from my.Books { *,
|
/** For displaying lists of Books */
|
||||||
|
@readonly entity ListOfBooks as projection on Books {
|
||||||
|
ID, title, author, genre, price, currency
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For display in details pages */
|
||||||
|
@readonly entity Books as projection on my.Books { *,
|
||||||
author.name as author
|
author.name as author
|
||||||
} excluding { createdBy, modifiedBy };
|
} excluding { createdBy, modifiedBy };
|
||||||
|
|
||||||
@readonly entity ListOfBooks as SELECT from Books
|
|
||||||
excluding { descr };
|
|
||||||
|
|
||||||
@requires: 'authenticated-user'
|
@requires: 'authenticated-user'
|
||||||
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
||||||
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ class CatalogService extends cds.ApplicationService { init(){
|
|||||||
|
|
||||||
// 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 => {
|
||||||
const {book,amount} = req.data, tx = cds.tx(req)
|
const {book,amount} = req.data
|
||||||
let {stock} = await tx.read('stock').from(Books,book)
|
let {stock} = await SELECT `stock` .from (Books,book)
|
||||||
if (stock >= amount) {
|
if (stock >= amount) {
|
||||||
await tx.update (Books,book).with ({ stock: stock -= amount })
|
await UPDATE (Books,book) .with (`stock -=`, amount)
|
||||||
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
||||||
return { stock }
|
return { stock }
|
||||||
}
|
}
|
||||||
@@ -16,10 +16,8 @@ class CatalogService extends cds.ApplicationService { init(){
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Add some discount for overstocked books
|
// Add some discount for overstocked books
|
||||||
this.after ('READ','Books', each => {
|
this.after ('READ','ListOfBooks', each => {
|
||||||
if (each.stock > 111) {
|
if (each.stock > 111) each.title += ` -- 11% discount!`
|
||||||
each.title += ` -- 11% discount!`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return super.init()
|
return super.init()
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
const cds = require ('@sap/cds')
|
|
||||||
|
|
||||||
cds.on('listening', async ()=>{ try { //> just to see the output at the end; will later on be in db service impl
|
|
||||||
console.log ('\nFilling database with initial data...')
|
|
||||||
const m = cds.model.minified() //> we likely should do that by default
|
|
||||||
const tx = cds.tx()
|
|
||||||
tx.run ([ //> this will be replaced by
|
|
||||||
DELETE.from ('sap.capire.bookshop.Authors'),
|
|
||||||
DELETE.from ('sap.capire.bookshop.Books'),
|
|
||||||
DELETE.from ('sap.capire.bookshop.Books.texts'),
|
|
||||||
DELETE.from ('sap.capire.bookshop.Genres'),
|
|
||||||
DELETE.from ('sap.common.Currencies'),
|
|
||||||
DELETE.from ('sap.common.Currencies.texts'),
|
|
||||||
])
|
|
||||||
await cds.deploy(m).to(tx,{ddl:false})
|
|
||||||
await tx.commit()
|
|
||||||
} catch(e) { console.error(e) }})
|
|
||||||
|
|
||||||
module.exports = cds.server
|
|
||||||
@@ -5,9 +5,11 @@ CAD;de;Kanadischer Dollar;Kanadischer Dollar
|
|||||||
AUD;de;Australischer Dollar;Australischer Dollar
|
AUD;de;Australischer Dollar;Australischer Dollar
|
||||||
GBP;de;Pfund;Britische Pfund
|
GBP;de;Pfund;Britische Pfund
|
||||||
ILS;de;Schekel;Israelische Schekel
|
ILS;de;Schekel;Israelische Schekel
|
||||||
|
JPY;de;Yen;Japanische Yen
|
||||||
EUR;fr;euro;de la Zone euro
|
EUR;fr;euro;de la Zone euro
|
||||||
USD;fr;dollar;dollar des États-Unis
|
USD;fr;dollar;dollar des États-Unis
|
||||||
CAD;fr;dollar canadien;dollar canadien
|
CAD;fr;dollar canadien;dollar canadien
|
||||||
AUD;fr;dollar australien;dollar australien
|
AUD;fr;dollar australien;dollar australien
|
||||||
GBP;fr;livre sterling;pound sterling
|
GBP;fr;livre sterling;pound sterling
|
||||||
ILS;fr;Shekel;shekel israelien
|
ILS;fr;Shekel;shekel israelien
|
||||||
|
JPY;fr;Yen;Yen japonais
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
# cds.requires.messaging.kind = file-based-messaging
|
cds.requires.messaging.kind = file-based-messaging
|
||||||
PORT = 4004
|
PORT = 4004
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using { AdminService } from '../../db/schema';
|
using { AdminService, sap.capire.bookshop } from '../../db/schema';
|
||||||
|
using from '../common'; // to help UI linter get the complete annotations
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -70,7 +71,7 @@ annotate AdminService.Authors with @(
|
|||||||
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
|
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
|
||||||
annotate AdminService.Books with @odata.draft.enabled;
|
annotate AdminService.Books with @odata.draft.enabled;
|
||||||
|
|
||||||
annotate AdminService.Books_texts with @(
|
annotate AdminService.Books.texts with @(
|
||||||
UI: {
|
UI: {
|
||||||
Identification: [{Value:title}],
|
Identification: [{Value:title}],
|
||||||
SelectionFields: [ locale, title ],
|
SelectionFields: [ locale, title ],
|
||||||
@@ -83,11 +84,12 @@ annotate AdminService.Books_texts with @(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Add Value Help for Locales
|
// Add Value Help for Locales
|
||||||
annotate AdminService.Books_texts {
|
annotate AdminService.Books.texts {
|
||||||
locale @ValueList:{entity:'Languages',type:#fixed}
|
locale @ValueList:{entity:'Languages',type:#fixed}
|
||||||
}
|
}
|
||||||
// In addition we need to expose Languages through AdminService
|
// In addition we need to expose Languages and Books.texts through AdminService
|
||||||
using { sap } from '@sap/cds/common';
|
using { sap } from '@sap/cds/common';
|
||||||
extend service AdminService {
|
extend service AdminService {
|
||||||
entity Languages as projection on sap.common.Languages;
|
entity Languages as projection on sap.common.Languages;
|
||||||
|
entity Books.texts as projection on bookshop.Books.texts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
|
|
||||||
</head>
|
|
||||||
@@ -54,7 +54,7 @@ annotate my.Books with {
|
|||||||
title @title:'{i18n>Title}';
|
title @title:'{i18n>Title}';
|
||||||
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
|
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
|
||||||
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
|
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
|
||||||
price @title:'{i18n>Price}';
|
price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code;
|
||||||
stock @title:'{i18n>Stock}';
|
stock @title:'{i18n>Stock}';
|
||||||
descr @UI.MultiLineText;
|
descr @UI.MultiLineText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0;url=reviews/index.html">
|
|
||||||
</head>
|
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capire/bookshop": "*",
|
"@capire/bookshop": "*",
|
||||||
"@capire/reviews": "*",
|
|
||||||
"@capire/orders": "*",
|
|
||||||
"@capire/common": "*",
|
"@capire/common": "*",
|
||||||
"@sap/cds": ">=4",
|
"@capire/orders": "*",
|
||||||
|
"@capire/reviews": "*",
|
||||||
|
"@capire/suppliers": "*",
|
||||||
|
"@sap/cds": ">=5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"passport": "^0.4.1"
|
"passport": "^0.4.1"
|
||||||
},
|
},
|
||||||
@@ -19,6 +20,10 @@
|
|||||||
"deploy-format": "hdbtable"
|
"deploy-format": "hdbtable"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"API_BUSINESS_PARTNER": {
|
||||||
|
"kind": "odata",
|
||||||
|
"model": "@capire/suppliers"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"strategy": "dummy"
|
"strategy": "dummy"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
const cds = require ('@sap/cds')
|
const cds = require ('@sap/cds')
|
||||||
|
module.exports = cds.server
|
||||||
|
|
||||||
cds.once('bootstrap',(app)=>{
|
cds.once('bootstrap',(app)=>{
|
||||||
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
|
app.serve ('/orders/webapp').from('@capire/orders','app/orders/webapp')
|
||||||
app.use ('/bookshop', _from('@capire/bookshop/app/vue/index.html'))
|
app.serve ('/bookshop').from('@capire/bookshop','app/vue')
|
||||||
app.use ('/reviews', _from('@capire/reviews/app/vue/index.html'))
|
app.serve ('/reviews').from('@capire/reviews','app/vue')
|
||||||
})
|
})
|
||||||
|
|
||||||
cds.once('served', require('./srv/mashup'))
|
cds.once('served', require('./srv/mashup'))
|
||||||
|
cds.once('served', require('@capire/suppliers/srv/mashup'))
|
||||||
|
|
||||||
module.exports = cds.server
|
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
cds.once ('bootstrap', app => app.use (require ('cds-swagger-ui-express')()) )
|
||||||
// -----------------------------------------------------------------------
|
}
|
||||||
// Helper for serving static content from npm-installed packages
|
|
||||||
const {static} = require('express')
|
|
||||||
const {dirname} = require('path')
|
|
||||||
const _from = target => static (dirname (require.resolve(target)))
|
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
// Mashing up imported models...
|
// Mashing up imported models...
|
||||||
//
|
//
|
||||||
|
|
||||||
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 { CatalogService.ListOfBooks, 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 : Decimal;
|
rating : Reviews:rating;
|
||||||
}
|
}
|
||||||
|
extend projection ListOfBooks with { rating }
|
||||||
|
|
||||||
//
|
//
|
||||||
// Extend Orders with Books as Products
|
// Extend Orders with Books as Products
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ GET {{bookshop}}/browse/Books(201)?
|
|||||||
&$select=ID,title,rating
|
&$select=ID,title,rating
|
||||||
&$expand=reviews
|
&$expand=reviews
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
GET {{bookshop}}/browse/Books?
|
||||||
|
&$select=title,author&$expand=currency
|
||||||
|
Accept-Language: de
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -2,6 +2,28 @@
|
|||||||
"name": "@capire/hello-world",
|
"name": "@capire/hello-world",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "cds serve world.cds"
|
"test": "npx jest --silent",
|
||||||
|
"watch": "cds serve world.cds",
|
||||||
|
"watch:ts": "cds-ts serve world.cds"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^26.0.23",
|
||||||
|
"@types/node": "^15.12.0",
|
||||||
|
"ts-jest": "^27.0.2",
|
||||||
|
"typescript": "^4.3.5"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"globals": {
|
||||||
|
"ts-jest": {
|
||||||
|
"diagnostics": {
|
||||||
|
"_comment": "see https://githubmemory.com/repo/kulshekhar/ts-jest/issues/2722",
|
||||||
|
"ignoreCodes": [
|
||||||
|
151001
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
hello/test/hello-world-ts.test.ts
Normal file
15
hello/test/hello-world-ts.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
process.env.CDS_TYPESCRIPT = 'true';
|
||||||
|
import * as cds from '@sap/cds';
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
const {GET} = cds.test.in(__dirname,'..').run('serve', 'world.cds');
|
||||||
|
|
||||||
|
describe('Hello world!', () => {
|
||||||
|
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
||||||
|
|
||||||
|
it('should say hello with class impl from a typescript file', async () => {
|
||||||
|
const {data} = await GET`/say/hello(to='world')`
|
||||||
|
expect(data.value).toMatch(/Hello world.*typescript.*/i)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
5
hello/world.ts
Normal file
5
hello/world.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = class say {
|
||||||
|
hello(req: any) {
|
||||||
|
return `Hello ${req.data.to} from a typescript file!`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ entity Orders_Items {
|
|||||||
up_ : Association to Orders;
|
up_ : Association to Orders;
|
||||||
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
|
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
|
||||||
amount : Integer;
|
amount : Integer;
|
||||||
title : String;
|
title : String; //> intentionally replicated as snapshot from product.title
|
||||||
price : Double;
|
price : Double;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
"name": "@capire/orders",
|
"name": "@capire/orders",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=4.3.0"
|
"@sap/cds": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
32038
package-lock.json
generated
Normal file
32038
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -11,29 +11,36 @@
|
|||||||
"@capire/hello": "./hello",
|
"@capire/hello": "./hello",
|
||||||
"@capire/media": "./media",
|
"@capire/media": "./media",
|
||||||
"@capire/orders": "./orders",
|
"@capire/orders": "./orders",
|
||||||
"@capire/reviews": "./reviews"
|
"@capire/reviews": "./reviews",
|
||||||
|
"@capire/suppliers": "./suppliers",
|
||||||
|
"@sap/cds": "^5",
|
||||||
|
"express": "^4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"cds-swagger-ui-express": "^0.2.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-subset": "^1.6.0",
|
"chai-subset": "^1.6.0",
|
||||||
"sqlite3": "^5.0.0"
|
"sqlite3": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
|
||||||
"registry": "node .registry/server.js",
|
"registry": "node .registry/server.js",
|
||||||
"bookshop": "cds watch bookshop",
|
"bookshop": "cds watch bookshop",
|
||||||
"fiori": "cds watch fiori",
|
"fiori": "cds watch fiori",
|
||||||
"media": "cds watch media",
|
"media": "cds watch media",
|
||||||
"mocha": "npx mocha || echo",
|
"mocha": "npx mocha || echo",
|
||||||
"jest": "npx jest",
|
"jest": "npx jest",
|
||||||
"test": "npm run jest --silent"
|
"test": "npm run jest --silent && npm run test:hello",
|
||||||
|
"test:hello": "cd hello && npm test"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testMatch": ["**/*.test.js"]
|
||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"parallel": true
|
"parallel": true
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"testEnvironment": "node"
|
|
||||||
},
|
|
||||||
"license": "SAP SAMPLE CODE LICENSE",
|
"license": "SAP SAMPLE CODE LICENSE",
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"index.cds"
|
"index.cds"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sap/cds": ">=4",
|
"@sap/cds": "^5",
|
||||||
"express": "^4.17.1"
|
"express": "^4.17.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
## Run all-in-one
|
## Run all-in-one
|
||||||
|
|
||||||
Open a terminal window and run the bookshop in it:
|
Open a terminal window and run the `fiori` app in it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run bookshop
|
npm run fiori
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ Open two terminal windows. In the first one start the reviews service stand-alon
|
|||||||
npm run reviews-service
|
npm run reviews-service
|
||||||
```
|
```
|
||||||
|
|
||||||
In the second one start the bookshop:
|
In the second one start the `fiori` app:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run bookshop
|
npm run fiori
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ service ReviewsService {
|
|||||||
action unlike (review: type of Reviews:ID);
|
action unlike (review: type of Reviews:ID);
|
||||||
|
|
||||||
// Async API
|
// Async API
|
||||||
event reviewed : {
|
event reviewed : projection on Reviews {
|
||||||
subject: type of Reviews:subject;
|
subject, //> recieved new reviews
|
||||||
rating: Decimal(2,1)
|
rating //> new avg rating
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input validation
|
// Input validation
|
||||||
|
|||||||
2
reviews/test/bookshop/.env
Normal file
2
reviews/test/bookshop/.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
cds.requires.messaging.kind = file-based-messaging
|
||||||
|
PORT = 4004
|
||||||
19
reviews/test/bookshop/package.json
Normal file
19
reviews/test/bookshop/package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@capire/fiori",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@capire/bookshop": "*",
|
||||||
|
"@capire/reviews": "*",
|
||||||
|
"@sap/cds": "^5",
|
||||||
|
"express": "^4.17.1"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"auth": { "strategy": "dummy" },
|
||||||
|
"ReviewsService": {
|
||||||
|
"kind": "odata",
|
||||||
|
"model": "@capire/reviews"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
reviews/test/bookshop/server.js
Normal file
20
reviews/test/bookshop/server.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const cds = require ('@sap/cds')
|
||||||
|
|
||||||
|
cds.once('bootstrap',(app)=>{
|
||||||
|
// Delegate to imported apps (reviews only when mocked)
|
||||||
|
app.serve ('/bookshop').from ('@capire/bookshop','app/vue')
|
||||||
|
app.serve ('/reviews',).from ('@capire/reviews','app/vue')
|
||||||
|
})
|
||||||
|
|
||||||
|
cds.once('served', async ()=>{
|
||||||
|
// Update Books' average ratings when ReviewsService signals updated reviews
|
||||||
|
const ReviewsService = await cds.connect.to ('ReviewsService')
|
||||||
|
ReviewsService.on ('reviewed', (msg) => {
|
||||||
|
console.debug ('> received:', msg.event, msg.data)
|
||||||
|
const { subject, rating } = msg.data
|
||||||
|
return UPDATE('Books',subject).with({rating})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = cds.server
|
||||||
11
reviews/test/bookshop/services.cds
Normal file
11
reviews/test/bookshop/services.cds
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace sap.capire.bookshop; //> allows UPDATE('Books')...
|
||||||
|
//
|
||||||
|
// Extend Books with access to Reviews and average ratings
|
||||||
|
//
|
||||||
|
using { CatalogService.ListOfBooks, sap.capire.bookshop.Books } from '@capire/bookshop';
|
||||||
|
using { ReviewsService.Reviews } from '@capire/reviews';
|
||||||
|
extend Books with {
|
||||||
|
reviews : Composition of many Reviews on reviews.subject = $self.ID;
|
||||||
|
rating : Reviews:rating;
|
||||||
|
}
|
||||||
|
extend projection ListOfBooks with { rating }
|
||||||
14
samples.md
14
samples.md
@@ -7,6 +7,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
|
|||||||
## [@capire/hello-world](hello)
|
## [@capire/hello-world](hello)
|
||||||
|
|
||||||
- A simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).
|
- A simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).
|
||||||
|
- [Typescript support](https://cap.cloud.sap/docs/get-started/using-typescript)
|
||||||
|
|
||||||
|
|
||||||
## [@capire/bookshop](bookshop)
|
## [@capire/bookshop](bookshop)
|
||||||
@@ -49,20 +50,29 @@ Each sub directory essentially is an individual npm package arranged in an [all-
|
|||||||
- Late-cut Micro Services
|
- Late-cut Micro Services
|
||||||
- As well as managed data, input validations, and authorization
|
- As well as managed data, input validations, and authorization
|
||||||
|
|
||||||
|
## [@capire/suppliers](suppliers)
|
||||||
|
|
||||||
|
- Shows how to integrate remote services, in this case the BusinessPartner service from SAP S/4HANA.
|
||||||
|
- Extending [@capire/bookshop](bookshop) with suppliers from SAP S/4HANA
|
||||||
|
- Providing that as a pre-built integration & extension package
|
||||||
|
- Used in [@capire/fiori](fiori)
|
||||||
|
|
||||||
|
|
||||||
## [@capire/fiori](fiori)
|
## [@capire/fiori](fiori)
|
||||||
|
|
||||||
- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) 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/orders](orders)
|
|
||||||
- [@capire/common](common)
|
- [@capire/common](common)
|
||||||
|
- [@capire/orders](orders)
|
||||||
|
- [@capire/reviews](reviews)
|
||||||
|
- [@capire/suppliers](suppliers)
|
||||||
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
|
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
|
||||||
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
|
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
|
||||||
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
|
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
|
||||||
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
|
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
|
||||||
- Serving SAP Fiori apps locally
|
- Serving SAP Fiori apps locally
|
||||||
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well
|
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well
|
||||||
|
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|||||||
1
suppliers/.env
Normal file
1
suppliers/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PORT = 4006
|
||||||
31
suppliers/app/_i18n/i18n.properties
Normal file
31
suppliers/app/_i18n/i18n.properties
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Books = Books
|
||||||
|
Book = Book
|
||||||
|
ID = ID
|
||||||
|
Title = Title
|
||||||
|
Author = Author
|
||||||
|
AuthorID = Author ID
|
||||||
|
Stock = Stock
|
||||||
|
Name = Name
|
||||||
|
AuthorName = Author's Name
|
||||||
|
DateOfBirth = Date of Birth
|
||||||
|
DateOfDeath = Date of Death
|
||||||
|
PlaceOfBirth = Place of Birth
|
||||||
|
PlaceOfDeath = Place of Death
|
||||||
|
Age = Age
|
||||||
|
Authors = Authors
|
||||||
|
Order = Order
|
||||||
|
Orders = Orders
|
||||||
|
Price = Price
|
||||||
|
|
||||||
|
Genre = Genre
|
||||||
|
Genres = Genres
|
||||||
|
SubGenres = Sub Genres
|
||||||
|
|
||||||
|
NumCode = Numeric Code
|
||||||
|
MinorUnit = Minor Unit
|
||||||
|
Exponent = Exponent
|
||||||
|
|
||||||
|
Supplier = Supplier
|
||||||
|
SupplierName = Supplier Name
|
||||||
|
Id = Id
|
||||||
|
Name = Name
|
||||||
18
suppliers/app/_i18n/i18n_de.properties
Normal file
18
suppliers/app/_i18n/i18n_de.properties
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Books = Bücher
|
||||||
|
Book = Buch
|
||||||
|
ID = ID
|
||||||
|
Title = Titel
|
||||||
|
Authors = Autoren
|
||||||
|
Author = Autor
|
||||||
|
AuthorID = ID des Autors
|
||||||
|
AuthorName = Name des Autors
|
||||||
|
Age = Alter
|
||||||
|
Name = Name
|
||||||
|
Stock = Bestand
|
||||||
|
Order = Bestellung
|
||||||
|
Orders = Bestellungen
|
||||||
|
Price = Preis
|
||||||
|
Supplier = Lieferant
|
||||||
|
SupplierName = Lieferantenname
|
||||||
|
Id = Id
|
||||||
|
Name = Name
|
||||||
55
suppliers/app/admin-fiori.html
Normal file
55
suppliers/app/admin-fiori.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Bookshop</title>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window["sap-ushell-config"] = {
|
||||||
|
defaultRenderer: "fiori2",
|
||||||
|
applications: {
|
||||||
|
"browse-books": {
|
||||||
|
title: "Browse Books",
|
||||||
|
description: "w/ SAP Fiori Elements",
|
||||||
|
additionalInformation: "SAPUI5.Component=bookshop",
|
||||||
|
applicationType : "URL",
|
||||||
|
url: "/browse/webapp",
|
||||||
|
navigationMode: "embedded"
|
||||||
|
},
|
||||||
|
"manage-books": {
|
||||||
|
title: "Manage Books",
|
||||||
|
description: "w/ SAP Fiori Elements",
|
||||||
|
additionalInformation: "SAPUI5.Component=admin",
|
||||||
|
applicationType : "URL",
|
||||||
|
url: "/admin/webapp",
|
||||||
|
navigationMode: "embedded"
|
||||||
|
},
|
||||||
|
"manage-orders": {
|
||||||
|
title: "Manage Orders",
|
||||||
|
description: "w/ SAP Fiori Elements",
|
||||||
|
additionalInformation: "SAPUI5.Component=orders",
|
||||||
|
applicationType : "URL",
|
||||||
|
url: "/orders/webapp",
|
||||||
|
navigationMode: "embedded"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</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://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-theme="sap_fiori_3"
|
||||||
|
data-sap-ui-frameOptions="allow"
|
||||||
|
></script>
|
||||||
|
<script>
|
||||||
|
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="sapUiBody" id="content"></body>
|
||||||
|
</html>
|
||||||
105
suppliers/app/admin/fiori-service.cds
Normal file
105
suppliers/app/admin/fiori-service.cds
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
//using { AdminService } from '../../db/schema';
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Books Object Page
|
||||||
|
//
|
||||||
|
|
||||||
|
annotate AdminService.Books with @(
|
||||||
|
UI: {
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Supplier}', Target: '@UI.FieldGroup#Supplier'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
|
||||||
|
],
|
||||||
|
FieldGroup#General: {
|
||||||
|
Data: [
|
||||||
|
{Value: title},
|
||||||
|
{Value: author_ID},
|
||||||
|
{Value: genre_ID},
|
||||||
|
{Value: descr},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: stock},
|
||||||
|
{Value: price},
|
||||||
|
{Value: currency_code, Label: '{i18n>Currency}'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Supplier: {
|
||||||
|
Data: [
|
||||||
|
{Value: supplier_ID, Label: '{i18n>Id}'},
|
||||||
|
{Value: supplier.name, Label: '{i18n>Name}'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Admin: {
|
||||||
|
Data: [
|
||||||
|
{Value: createdBy},
|
||||||
|
{Value: createdAt},
|
||||||
|
{Value: modifiedBy},
|
||||||
|
{Value: modifiedAt}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
annotate AdminService.Authors with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
Description: {Value: lifetime}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: placeOfBirth},
|
||||||
|
{Value: placeOfDeath},
|
||||||
|
{Value: dateOfBirth},
|
||||||
|
{Value: dateOfDeath},
|
||||||
|
{Value: age, Label: '{i18n>Age}'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Draft for Localized Data
|
||||||
|
//
|
||||||
|
|
||||||
|
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
|
||||||
|
annotate AdminService.Books with @odata.draft.enabled;
|
||||||
|
|
||||||
|
annotate AdminService.Books.texts with @(
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:title}],
|
||||||
|
SelectionFields: [ locale, title ],
|
||||||
|
LineItem: [
|
||||||
|
{Value: locale, Label: 'Locale'},
|
||||||
|
{Value: title, Label: 'Title'},
|
||||||
|
{Value: descr, Label: 'Description'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add Value Help for Locales
|
||||||
|
annotate AdminService.Books.texts {
|
||||||
|
locale @ValueList:{entity:'Languages',type:#fixed}
|
||||||
|
}
|
||||||
|
// In addition we need to expose Languages through AdminService
|
||||||
|
using { sap } from '@sap/cds/common';
|
||||||
|
extend service AdminService {
|
||||||
|
entity Languages as projection on sap.common.Languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
annotate AdminService.Suppliers with
|
||||||
|
@(Capabilities.SearchRestrictions : {
|
||||||
|
Searchable : false
|
||||||
|
});
|
||||||
8
suppliers/app/admin/webapp/Component.js
Normal file
8
suppliers/app/admin/webapp/Component.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
|
||||||
|
"use strict";
|
||||||
|
return AppComponent.extend("admin.Component", {
|
||||||
|
metadata: { manifest: "json" }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint no-undef:0 */
|
||||||
11
suppliers/app/admin/webapp/i18n/i18n.properties
Normal file
11
suppliers/app/admin/webapp/i18n/i18n.properties
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# This is the resource bundle of itelo
|
||||||
|
# __ldi.translation.uuid=c3431418-9caf-11e8-98d0-529269fb1459
|
||||||
|
|
||||||
|
# JCI app descriptor contains lower case TITLE
|
||||||
|
appTitle=Bookshop Sample
|
||||||
|
|
||||||
|
# JCI app descriptor contains lower case DESCRIPTION
|
||||||
|
appSubTitle=CAP Sample Application
|
||||||
|
|
||||||
|
# JCI app descriptor contains lower case DESCRIPTION
|
||||||
|
appDescription=CDS Sample Service
|
||||||
128
suppliers/app/admin/webapp/manifest.json
Normal file
128
suppliers/app/admin/webapp/manifest.json
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"_version": "1.8.0",
|
||||||
|
"sap.app": {
|
||||||
|
"id": "admin",
|
||||||
|
"type": "application",
|
||||||
|
"title": "Manage Books",
|
||||||
|
"description": "Sample Application",
|
||||||
|
"i18n": "i18n/i18n.properties",
|
||||||
|
"dataSources": {
|
||||||
|
"AdminService": {
|
||||||
|
"uri": "/admin/",
|
||||||
|
"type": "OData",
|
||||||
|
"settings": {
|
||||||
|
"odataVersion": "4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"-sourceTemplate": {
|
||||||
|
"id": "ui5template.basicSAPUI5ApplicationProject",
|
||||||
|
"-id": "ui5template.smartTemplate",
|
||||||
|
"-version": "1.40.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sap.ui5": {
|
||||||
|
"dependencies": {
|
||||||
|
"libs": {
|
||||||
|
"sap.fe.templates": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"i18n": {
|
||||||
|
"type": "sap.ui.model.resource.ResourceModel",
|
||||||
|
"uri": "i18n/i18n.properties"
|
||||||
|
},
|
||||||
|
"": {
|
||||||
|
"dataSource": "AdminService",
|
||||||
|
"settings": {
|
||||||
|
"synchronizationMode": "None",
|
||||||
|
"operationMode": "Server",
|
||||||
|
"autoExpandSelect" : true,
|
||||||
|
"earlyRequests": true,
|
||||||
|
"groupProperties": {
|
||||||
|
"default": {
|
||||||
|
"submit": "Auto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"pattern": ":?query:",
|
||||||
|
"name": "BooksList",
|
||||||
|
"target": "BooksList"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "Books({key}):?query:",
|
||||||
|
"name": "BooksDetails",
|
||||||
|
"target": "BooksDetails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "Books({key}/author({key2}):?query:",
|
||||||
|
"name": "AuthorsDetails",
|
||||||
|
"target": "AuthorsDetails"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"targets": {
|
||||||
|
"BooksList": {
|
||||||
|
"type": "Component",
|
||||||
|
"id": "BooksList",
|
||||||
|
"name": "sap.fe.templates.ListReport",
|
||||||
|
"options": {
|
||||||
|
"settings" : {
|
||||||
|
"entitySet" : "Books",
|
||||||
|
"navigation" : {
|
||||||
|
"Books" : {
|
||||||
|
"detail" : {
|
||||||
|
"route" : "BooksDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BooksDetails": {
|
||||||
|
"type": "Component",
|
||||||
|
"id": "BooksDetailsList",
|
||||||
|
"name": "sap.fe.templates.ObjectPage",
|
||||||
|
"options": {
|
||||||
|
"settings" : {
|
||||||
|
"entitySet" : "Books",
|
||||||
|
"navigation" : {
|
||||||
|
"Authors" : {
|
||||||
|
"detail" : {
|
||||||
|
"route" : "AuthorsDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AuthorsDetails": {
|
||||||
|
"type": "Component",
|
||||||
|
"id": "AuthorsDetailsList",
|
||||||
|
"name": "sap.fe.templates.ObjectPage",
|
||||||
|
"options": {
|
||||||
|
"settings" : {
|
||||||
|
"entitySet" : "Authors"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentDensities": {
|
||||||
|
"compact": true,
|
||||||
|
"cozy": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sap.ui": {
|
||||||
|
"technology": "UI5",
|
||||||
|
"fullWidth": false
|
||||||
|
},
|
||||||
|
"sap.fiori": {
|
||||||
|
"registrationIds": [],
|
||||||
|
"archeType": "transactional"
|
||||||
|
}
|
||||||
|
}
|
||||||
265
suppliers/app/common.cds
Normal file
265
suppliers/app/common.cds
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
Common Annotations shared by all apps
|
||||||
|
*/
|
||||||
|
|
||||||
|
using { sap.capire.bookshop as my } from '@capire/bookshop';
|
||||||
|
using { sap.common } from '@capire/common';
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Books Lists
|
||||||
|
//
|
||||||
|
annotate my.Books with @(
|
||||||
|
Common.SemanticKey: [title],
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:title}],
|
||||||
|
SelectionFields: [ ID, author_ID, price, currency_code, supplier.ID, supplier.name ],
|
||||||
|
LineItem: [
|
||||||
|
{Value: ID},
|
||||||
|
{Value: title},
|
||||||
|
{Value: author.name, Label:'{i18n>Author}'},
|
||||||
|
{Value: genre.name},
|
||||||
|
{Value: stock},
|
||||||
|
{Value: price},
|
||||||
|
{Value: currency.symbol, Label:' '},
|
||||||
|
{Value: supplier.ID},
|
||||||
|
{Value: supplier.name}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
author @ValueList.entity:'Authors';
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Books Details
|
||||||
|
//
|
||||||
|
annotate my.Books with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Book}',
|
||||||
|
TypeNamePlural: '{i18n>Books}',
|
||||||
|
Title: {Value: title},
|
||||||
|
Description: {Value: author.name}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Books Elements
|
||||||
|
//
|
||||||
|
annotate my.Books with {
|
||||||
|
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
||||||
|
title @title:'{i18n>Title}';
|
||||||
|
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
|
||||||
|
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
|
||||||
|
price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code;
|
||||||
|
stock @title:'{i18n>Stock}';
|
||||||
|
descr @UI.MultiLineText;
|
||||||
|
supplier_ID @title:'{i18n>Supplier}';
|
||||||
|
}
|
||||||
|
|
||||||
|
annotate my.Suppliers with {
|
||||||
|
name @title:'{i18n>SupplierName}';
|
||||||
|
ID @title:'{i18n>Supplier}';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genres List
|
||||||
|
//
|
||||||
|
annotate my.Genres with @(
|
||||||
|
Common.SemanticKey: [name],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: name},
|
||||||
|
{Value: parent.name, Label: 'Main Genre'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genre Details
|
||||||
|
//
|
||||||
|
annotate my.Genres with @(
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:name}],
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Genre}',
|
||||||
|
TypeNamePlural: '{i18n>Genres}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: ID}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Genres Elements
|
||||||
|
//
|
||||||
|
annotate my.Genres with {
|
||||||
|
ID @title: '{i18n>ID}';
|
||||||
|
name @title: '{i18n>Genre}';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Authors List
|
||||||
|
//
|
||||||
|
annotate my.Authors with @(
|
||||||
|
Common.SemanticKey: [name],
|
||||||
|
UI: {
|
||||||
|
Identification: [{Value:name}],
|
||||||
|
SelectionFields: [ name ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: ID},
|
||||||
|
{Value: name},
|
||||||
|
{Value: dateOfBirth},
|
||||||
|
{Value: dateOfDeath},
|
||||||
|
{Value: placeOfBirth},
|
||||||
|
{Value: placeOfDeath},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Author Details
|
||||||
|
//
|
||||||
|
annotate my.Authors with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Author}',
|
||||||
|
TypeNamePlural: '{i18n>Authors}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: dateOfBirth}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Authors Elements
|
||||||
|
//
|
||||||
|
annotate my.Authors with {
|
||||||
|
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
||||||
|
name @title:'{i18n>Name}';
|
||||||
|
dateOfBirth @title:'{i18n>DateOfBirth}';
|
||||||
|
dateOfDeath @title:'{i18n>DateOfDeath}';
|
||||||
|
placeOfBirth @title:'{i18n>PlaceOfBirth}';
|
||||||
|
placeOfDeath @title:'{i18n>PlaceOfDeath}';
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Languages List
|
||||||
|
//
|
||||||
|
annotate common.Languages with @(
|
||||||
|
Common.SemanticKey: [code],
|
||||||
|
Identification: [{Value:code}],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name, descr ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: code},
|
||||||
|
{Value: name},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Language Details
|
||||||
|
//
|
||||||
|
annotate common.Languages with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Language}',
|
||||||
|
TypeNamePlural: '{i18n>Languages}',
|
||||||
|
Title: {Value: name},
|
||||||
|
Description: {Value: descr}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: code},
|
||||||
|
{Value: name},
|
||||||
|
{Value: descr}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currencies List
|
||||||
|
//
|
||||||
|
annotate common.Currencies with @(
|
||||||
|
Common.SemanticKey: [code],
|
||||||
|
Identification: [{Value:code}],
|
||||||
|
UI: {
|
||||||
|
SelectionFields: [ name, descr ],
|
||||||
|
LineItem:[
|
||||||
|
{Value: descr},
|
||||||
|
{Value: symbol},
|
||||||
|
{Value: code},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currency Details
|
||||||
|
//
|
||||||
|
annotate common.Currencies with @(
|
||||||
|
UI: {
|
||||||
|
HeaderInfo: {
|
||||||
|
TypeName: '{i18n>Currency}',
|
||||||
|
TypeNamePlural: '{i18n>Currencies}',
|
||||||
|
Title: {Value: descr},
|
||||||
|
Description: {Value: code}
|
||||||
|
},
|
||||||
|
Facets: [
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||||
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
|
||||||
|
],
|
||||||
|
FieldGroup#Details: {
|
||||||
|
Data: [
|
||||||
|
{Value: name},
|
||||||
|
{Value: symbol},
|
||||||
|
{Value: code},
|
||||||
|
{Value: descr}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
FieldGroup#Extended: {
|
||||||
|
Data: [
|
||||||
|
{Value: numcode},
|
||||||
|
{Value: minor},
|
||||||
|
{Value: exponent}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Currencies Elements
|
||||||
|
//
|
||||||
|
annotate common.Currencies with {
|
||||||
|
numcode @title:'{i18n>NumCode}';
|
||||||
|
minor @title:'{i18n>MinorUnit}';
|
||||||
|
exponent @title:'{i18n>Exponent}';
|
||||||
|
}
|
||||||
12
suppliers/app/services.cds
Normal file
12
suppliers/app/services.cds
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
This model controls what gets served to Fiori frontends...
|
||||||
|
*/
|
||||||
|
|
||||||
|
using from './admin/fiori-service';
|
||||||
|
//using from './browse/fiori-service';
|
||||||
|
using from './common';
|
||||||
|
|
||||||
|
using from '@capire/common';
|
||||||
|
|
||||||
|
// only works in case of embedded orders service
|
||||||
|
using from '@capire/orders/app/orders/fiori-service';
|
||||||
1
suppliers/index.cds
Normal file
1
suppliers/index.cds
Normal file
@@ -0,0 +1 @@
|
|||||||
|
using from './srv/mashup';
|
||||||
1
suppliers/model.drawio
Normal file
1
suppliers/model.drawio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2021-06-16T08:41:31.745Z" agent="5.0 (Macintosh; Intel Mac OS X 11_4_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="keOnkQccb51SB06o_xsB" version="14.6.13" type="device"><diagram id="kIZwyAu_b989F7xyfxsf" name="Page-1">7Z1bc6M2GIZ/TabthXdAEpK4zGbTwzSdbutOu3u1g0G2mWBwAcdJf31FQNhI2NY6BuSWm10ksDDo4TvpxbmBd6vnH1JvvfwlCVh0A6zg+QZ+uAHAti3M/yt6Xqoe4pKyZ5GGQdln7Tqm4T+sOlD0bsKAZVVf2ZUnSZSH62ann8Qx8/NGn5emybZ52DyJgkbH2luwxtcoOqa+FzHlsL/CIF+WvRSQXf+PLFwsxZlt7JZ7Vp44uBo4W3pBst3rgvc38C5NkrzcWj3fsai4e8378v2BvfUXS1mc63zA/Xj/2wZtYn/rfdnmf9wv8gxOqlGevGhTXfCUj4B41/tNFsYsyz56aR6z9KYYHUf8TO9nvIEXxZboydZeXFxh/lLdNvz3pris9/Mkzifb6v7c8kPiJF150e4AMdK3fN/vbJXkjG9MWfoU+nwLe6s13xnPsuK/78Tp+Dcszyi+BWicHKTJJg5Ycdk2371dhjmbrj2/2LvlmPK+Zb6Kqt3ZI8v9YqYs3nhiaR7yyb+NwkXM+/KkONqrWhGbF+fzUr/ilJTX7ofxgrforvXweuQHXN2CafXdxN3mZ2HPB6fRruHgjxVLVixPX/gh1QeAWwFWPVEurNrbHZ7ARWXfch9NpwLRqx6JRT32jhq+UYHTDtGXT7/gzfa36f2E/BxMH371pg94golC0UPiF9NsffByb+Zl7BLsIMtqByereHk9Kpjxf/4zqChctNBzEBUM7QYqNkAKKsgmKirQsjtCRRhDLYPzzWhxTMCopqHCiFCsaXEQ6Qoj1W/VrIhJuv2iEHWIJv4dZnJfED61Q7fDJ1ytE34TA755+/Gno2an7m4MezGKBC8+n2V+oU0GQNEOo+guiZL09UxwTn3m+8UoeZo8sr09M+ogx6r3iJgH1N/3bShh0HReVLVINnVVlHBXvguA0yRNN+t1FLI0uzhBJTbZZpaxnG9ssleYrgejOcPtGAXEnVm9YUSxPTRGsAUjaV5YHNwWGQlvzaLEf+T3gndVht3GZfP7sDjx62zszdLBe5Ylm9RnGnznXrpguYZJZYFIiA7Mwf49FvczZZGXh0/NnKntJlfDfUzCON/NJxGBSTWfDoTNIcrLrD61n+NIA3GX0wTDlSa8vA3KQK9zXl/jGzBACga3wSqMd/FFp/GMiLrr042BcGsEg62WQJg4LRGMg7syGI5Cyh3PlaJkcUFWDmZMIyjtoNTVqSOg8JSpV1CwAsqYXBuACgans6K+k2u1DnMwlv0pnicXD2dTxsf2vTIlCjid1xTMBh6j89ZgFvuUzeZdBbNExI7HciLc4py6C2Zpp8Esv1Xpy6f9xuf9xofn6iNl60W0nsP80972573t3UeKhvhE4GXLmpw3RNBEN4IGw0XQkEqJtWxkdCNoCMjxgbqOoN3T7GVLb11s+i9RyI1DCk9bhllpRh5mdYfnPy5ejcuvm5wPw4QJKQlGsjW5wIPuulLWaqlZq/D2+8856Oo5h2oxVvEX75Pk8fJ1j6iKYEYfcZ6PaC149OojxNLCHjtZI7Q47C+SNYtlj7DvKqQw73zLLfg+bbmJpuWupmNivUNAxHvCUCoTItbb9ufDuYy5l1d2XAucZ+4pbBZMbBtIIx2w97uRxIHJfP5axby8T4DgIGtv4awPsHRDgh1YPOVseuCJDfpEi0J6mUiC0vMiiV7Jaivb9lb9n3PnAaw/i3P/yKL1VTlCh9EAtTlCCmYQ464cISRNe4Wx+nD07AjVku8xo9Rf5V+wbXTegokU2FBpyVjX2jjEPj5Qx3kLVOu5PcXS7HmdXNm64VDWwyHNmM0A66FWdxVsvn0Is4L0ZM4n2BohGhgiKsXeBkDUVvc1wgU5mi5IBNRDuCBE8DunMaMEofOckDoUdXp2Q92Wbt+AAr4CFAjE75BlE4hsC1lIUpmcjYULrHeOSwF2qOu4jpRg9Y0Iaqv6DZJFa1sHqInELot2MGnKpCe2ujzXYRZNoLSoc24W7WC50mNeFi0WxMe3CK5m9RraTawmiELl+WgV9Xa3fI1Ai2EaRb3mi3pJ009OuEtTWOpXjokGreuNqt7LcIQsOjRHphb3kG5xTxjVYcLpZrF2QqCkktPW9UoCYZ7yulph0MVCHLW8Nwp7DQxjMFZdT8/KXqSW9EZl7+CkUIucJqVnaS8a35s1khVZXdGaHPWs7XU0qjZD6OIMEUg5wJGmbGhxnaPxiuoorjOBHblW15539Lqi46j567CKJ0Hz6Xzj62u1gyueULMgP4GYNsfQljxJrw+pIxlQrXXactpR86Rhn4bSPFEp+cUilh3OPrWpXUyoiwi4L2enuhA9Ucnh1G9vfPXqELVPjNRxXcTR0K+MipWhZU+SVN0EA6LxquKoezKKIorkWtvwFJmqdnF01S4irh5E+ERltdKkTqO/WvmkjoVEFN+XK9J4c3AYGHRfIB0SBoKOSZ/OB8OFx7RPvUOCTRE/6VsIpAmFQeInJMe35ybUsqrfyIQaq5W+Uf5kdoVfXjds+2XMntVPwuKO6qfrVj9B4VEGE63gUfx0feInIv2ckAEYmap9wrraJ2FSjdA+2eIXAt6qfQJALwS6WHgzSp+uIYSpiyHDKZ/wqHwyEBRF+dQCSs/CJ6xRt/sfi1moHFKKH0AZSsuC20pro5blCrQstvhrIYPV6IlacRtWyiJgPh0+Xr+UBcAzg05ZyaIMZEDdjah1t1HIYvIKIhz8l02FYzUupxUsX84o9aBbAecuCsmyFWWgjlNaolEhG/UGpqhWhrMWGkrJUaRiFDS1SGU4aEyVRhLtsqk7nItRdSTnSiPVkWy3Zy/Tpo00ggRdkeyQJBwXpJxLxXE5Su+EfN0Pd3WXE2vbBhGnXrEa5eycWBaj9JsT8+bubwWXh+/+5DK8/xc=</diagram></mxfile>
|
||||||
40
suppliers/package.json
Normal file
40
suppliers/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@capire/suppliers",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Shows integration with SAP S/4HANA, in turn provided as a reusable extension package to bookshop.",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@capire/common": "*",
|
||||||
|
"@sap/cds": ">=4",
|
||||||
|
"express": "^4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "cds run --in-memory?",
|
||||||
|
"watch": "cds watch",
|
||||||
|
"mocked-s4": "cds mock API_BUSINESS_PARTNER"
|
||||||
|
},
|
||||||
|
"cds": {
|
||||||
|
"requires": {
|
||||||
|
"API_BUSINESS_PARTNER": {
|
||||||
|
"kind": "odata",
|
||||||
|
"model": "srv/external/API_BUSINESS_PARTNER",
|
||||||
|
"[hybrid]": {
|
||||||
|
"credentials": {
|
||||||
|
"destination": "cap-api098",
|
||||||
|
"path": "/sap/opu/odata/sap/API_BUSINESS_PARTNER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"[local-hybrid]": {
|
||||||
|
"messaging": {
|
||||||
|
"kind": "file-based-messaging"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"[hybrid]": {
|
||||||
|
"messaging": {
|
||||||
|
"kind": "enterprise-messaging-shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
suppliers/requests.http
Normal file
51
suppliers/requests.http
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
@server = http://localhost:4006
|
||||||
|
@bpServer = http://localhost:5001
|
||||||
|
@authAlice = Authorization: Basic alice:
|
||||||
|
|
||||||
|
###
|
||||||
|
### Replication on changed Business Partner
|
||||||
|
###
|
||||||
|
|
||||||
|
### 1. Check supplier name before update ("A Company Making Everything")
|
||||||
|
|
||||||
|
GET {{server}}/admin/Books(299)?$expand=supplier
|
||||||
|
{{authAlice}}
|
||||||
|
|
||||||
|
### 2. Change Business Partner -> Triggers event that updates supplier replication
|
||||||
|
|
||||||
|
PATCH {{bpServer}}/api-business-partner/A_BusinessPartner('ACME')
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"BusinessPartnerFullName": "A Company Making Everything *better*"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 3. Check supplier name after update ("A Company Making Everything *better*")
|
||||||
|
|
||||||
|
GET {{server}}/admin/Books(299)?$expand=supplier
|
||||||
|
{{authAlice}}
|
||||||
|
|
||||||
|
###
|
||||||
|
### Replication on new assigned supplier
|
||||||
|
###
|
||||||
|
|
||||||
|
### 1. No supplier is assigned to "Wuthering Heights"
|
||||||
|
|
||||||
|
GET {{server}}/admin/Books(201)?$expand=supplier
|
||||||
|
{{authAlice}}
|
||||||
|
|
||||||
|
### 2. Assign supplier ID "PNG"
|
||||||
|
|
||||||
|
PATCH {{server}}/admin/Books(201)
|
||||||
|
{{authAlice}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"supplier_ID": "PNG"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 3. Supplier information is replicated
|
||||||
|
|
||||||
|
GET {{server}}/admin/Books(201)?$expand=supplier
|
||||||
|
{{authAlice}}
|
||||||
3
suppliers/server.js
Normal file
3
suppliers/server.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const cds = require ('@sap/cds')
|
||||||
|
cds.once('served', require('./srv/mashup'))
|
||||||
|
module.exports = cds.server
|
||||||
2
suppliers/srv/data/sap.capire.bookshop-Books.csv
Normal file
2
suppliers/srv/data/sap.capire.bookshop-Books.csv
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ID;title;descr;author_ID;stock;price;currency_code;genre_ID;supplier_ID;
|
||||||
|
299;Mobby Dick;"""Moby-Dick""" or """The Whale""" is an 1851 novel by American writer Herman Melville. The book is the sailor Ishmael's narrative of the obsessive quest of Ahab, captain of the whaling ship Pequod, for revenge on Moby Dick, the giant white sperm whale that on the ship's previous voyage bit off Ahab's leg at the knee. A contribution to the literature of the American Renaissance, Moby-Dick was published to mixed reviews, was a commercial failure, and was out of print at the time of the author's death in 1891.;105;99;15.20;GBP;11;ACME
|
||||||
|
Can't render this file because it contains an unexpected character in line 2 and column 30.
|
3
suppliers/srv/data/sap.capire.bookshop-Suppliers.csv
Normal file
3
suppliers/srv/data/sap.capire.bookshop-Suppliers.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ID;name
|
||||||
|
ACME;A Company Making Everything (replicated)
|
||||||
|
|
||||||
|
2425
suppliers/srv/external/API_BUSINESS_PARTNER.csn
vendored
Normal file
2425
suppliers/srv/external/API_BUSINESS_PARTNER.csn
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3261
suppliers/srv/external/API_BUSINESS_PARTNER.edmx
vendored
Normal file
3261
suppliers/srv/external/API_BUSINESS_PARTNER.edmx
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10
suppliers/srv/external/API_BUSINESS_PARTNER.js
vendored
Normal file
10
suppliers/srv/external/API_BUSINESS_PARTNER.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const cds = require('@sap/cds');
|
||||||
|
|
||||||
|
module.exports = cds.service.impl(function () {
|
||||||
|
const { A_BusinessPartner } = this.entities;
|
||||||
|
|
||||||
|
// https://api.sap.com/event/SAPS4HANACloudBusinessEvents_BusinessPartner/resource
|
||||||
|
this.after('UPDATE', A_BusinessPartner, async data => {
|
||||||
|
await this.emit("BusinessPartner.Changed", { BusinessPartner: data.BusinessPartner });
|
||||||
|
});
|
||||||
|
});
|
||||||
7
suppliers/srv/external/data/API_BUSINESS_PARTNER-A_BusinessPartner.csv
vendored
Normal file
7
suppliers/srv/external/data/API_BUSINESS_PARTNER-A_BusinessPartner.csv
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
BusinessPartner;BusinessPartnerFullName
|
||||||
|
ACME;A Company Making Everything
|
||||||
|
B4U;Books for You
|
||||||
|
S&C;Shakespeare & Co.
|
||||||
|
WSL;Waterstones
|
||||||
|
TLD;Thalia
|
||||||
|
PNG;Penguin Books
|
||||||
|
72
suppliers/srv/mashup.cds
Normal file
72
suppliers/srv/mashup.cds
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using { API_BUSINESS_PARTNER as S4 } from './external/API_BUSINESS_PARTNER.csn';
|
||||||
|
using { CatalogService, sap.capire.bookshop.Books } from '@capire/bookshop';
|
||||||
|
namespace sap.capire.bookshop;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add projections to external entities to capture the subset of fields we're
|
||||||
|
actually interested in. This fosters both: (a) minimized network traffic as
|
||||||
|
well as (b) options to dynamically add extension fields by SaaS tenants.
|
||||||
|
*/
|
||||||
|
entity Suppliers as projection on S4.A_BusinessPartner {
|
||||||
|
key BusinessPartner as ID,
|
||||||
|
BusinessPartnerFullName as name,
|
||||||
|
// to_BusinessPartnerAddress[1: ValidityStartDate <= $now and $now < ValidityEndDate].CityName,
|
||||||
|
// to_BusinessPartnerAddress[1: ValidityStartDate <= $now and $now < ValidityEndDate].Country,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
We can mashup entities from external services, or projections thereof, with
|
||||||
|
our project's own entities, e.g. by adding relationships as below.
|
||||||
|
*/
|
||||||
|
extend Books with {
|
||||||
|
supplier : Association to Suppliers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Optionally expose external entities through own services, e.g. for Value Helps,
|
||||||
|
or to display details fetched on demand.
|
||||||
|
For this to work, we need to delegate the respective calls addressed to our
|
||||||
|
services into calls to the external service.
|
||||||
|
*/
|
||||||
|
extend service AdminService with {
|
||||||
|
@cds.odata.valuelist
|
||||||
|
entity Suppliers as projection on bookshop.Suppliers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Optionally add local persistency to replicate data for fast access,
|
||||||
|
e.g. to display lists containing remote data.
|
||||||
|
*/
|
||||||
|
annotate Suppliers with @cds.persistence: {table,skip:false};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Having locally cached replicas also allows to display supplier data in lists
|
||||||
|
of books, which otherwise would generate unwanted traffic on S4 backends.
|
||||||
|
*/
|
||||||
|
extend projection CatalogService.ListOfBooks with {
|
||||||
|
supplier.name as supplier
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Optionally declare events emitted from the source, but not included in
|
||||||
|
imported APIs (e.g. as in case of EDMXes from API Hub).
|
||||||
|
|
||||||
|
This allows CAP's advanced support for events and messaging to kick in,
|
||||||
|
e.g. to automatically emit to and subscribe to events from message brokers
|
||||||
|
behind the scenes.
|
||||||
|
|
||||||
|
Note: as sync and async APIs from S/4 sources are not correlated, we have
|
||||||
|
to specify the event type names, e.g. as be found at:
|
||||||
|
https://api.sap.com/event/SAPS4HANACloudBusinessEvents_BusinessPartner/resource
|
||||||
|
*/
|
||||||
|
extend service S4 {
|
||||||
|
event BusinessPartner.Changed @(type: 'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1') {
|
||||||
|
BusinessPartner: S4.A_BusinessPartner:BusinessPartner;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
suppliers/srv/mashup.js
Normal file
50
suppliers/srv/mashup.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Mashing up provided and required services...
|
||||||
|
//
|
||||||
|
module.exports = async()=>{ // called by server.js
|
||||||
|
|
||||||
|
if (!cds.services.AdminService) return //> mocking S4 service only
|
||||||
|
|
||||||
|
// Connect to services we want to mashup below...
|
||||||
|
const S4bupa = await cds.connect.to('API_BUSINESS_PARTNER') //> external S4 service
|
||||||
|
const admin = await cds.connect.to('AdminService') //> local domain service
|
||||||
|
const db = await cds.connect.to('db') //> our primary database
|
||||||
|
|
||||||
|
// Reflect CDS definition of the Suppliers entity
|
||||||
|
const { Suppliers } = db.entities
|
||||||
|
|
||||||
|
admin.prepend (()=>{ //> to ensure our .on handlers below go before the default ones
|
||||||
|
|
||||||
|
// Delegate Value Help reads for Suppliers to S4 backend
|
||||||
|
admin.on ('READ', 'Suppliers', req => {
|
||||||
|
console.log ('>> delegating to S4 service...')
|
||||||
|
return S4bupa.run(req.query)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Replicate Supplier data when edited Books have suppliers
|
||||||
|
admin.after (['CREATE','UPDATE'], 'Books', async data => {
|
||||||
|
const { supplier_ID: ID } = data
|
||||||
|
if (ID) {
|
||||||
|
let replicated = await db.exists (Suppliers,ID)
|
||||||
|
if (!replicated) { // initially replicate Supplier info
|
||||||
|
let supplier = await S4bupa.read (Suppliers,ID)
|
||||||
|
await INSERT(supplier) .into (Suppliers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subscribe to changes in the S4 origin of Suppliers data
|
||||||
|
S4bupa.on ('BusinessPartner.Changed', async msg => { //> would be great if we had batch events from S/4
|
||||||
|
console.log(">>", msg.event, msg.data)
|
||||||
|
const ID = msg.data.BusinessPartner;
|
||||||
|
let replicated = await db.exists (Suppliers,ID)
|
||||||
|
if (replicated) { // update replicated Supplier info
|
||||||
|
let supplier = await S4bupa.read (Suppliers,ID)
|
||||||
|
await UPDATE(Suppliers,ID) .with (supplier)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
22
test/cds.events.test.js
Normal file
22
test/cds.events.test.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const cds = require ('@sap/cds/lib')
|
||||||
|
const { expect } = cds.test.in (__dirname)
|
||||||
|
|
||||||
|
describe('cds.events tests', ()=>{
|
||||||
|
|
||||||
|
let m; before (async ()=> m = await cds.load('events.cds'))
|
||||||
|
|
||||||
|
it ('should have the model loaded', ()=>{
|
||||||
|
expect(m.definitions).to.have.property('Sue.Foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('should compile the model to edmx', ()=>{
|
||||||
|
const edmx = cds.compile(m).to.edmx({service:'Sue'})
|
||||||
|
expect(edmx).to.match(/<EntitySet Name="Foo" EntityType="Sue.Foo"\/>/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('should compile the model to sql', ()=>{
|
||||||
|
const sql = cds.compile(m).to.sql().join(';\n')
|
||||||
|
expect(sql).not.to.match(/CREATE TABLE Sue_Foo/)
|
||||||
|
expect(sql).to.match(/CREATE TABLE Sue_Bar/)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -280,7 +280,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
).to.eql({
|
).to.eql({
|
||||||
SELECT: {
|
SELECT: {
|
||||||
from: { ref: ['Foo'] },
|
from: { ref: ['Foo'] },
|
||||||
where: cdr
|
where: cds.version >= '5.3.0'
|
||||||
? [
|
? [
|
||||||
// '(', //> this one is not required
|
// '(', //> this one is not required
|
||||||
{ ref: ['ID'] },
|
{ ref: ['ID'] },
|
||||||
@@ -289,7 +289,7 @@ describe('cds.ql → cqn', () => {
|
|||||||
'and',
|
'and',
|
||||||
{ ref: ['args'] },
|
{ ref: ['args'] },
|
||||||
'in',
|
'in',
|
||||||
{ val: args },
|
{ list: args.map(val => ({ val })) },
|
||||||
'and',
|
'and',
|
||||||
'(', //> this one is missing, and that's changing the logic -> that's a BUG
|
'(', //> this one is missing, and that's changing the logic -> that's a BUG
|
||||||
{ ref: ['x'] },
|
{ ref: ['x'] },
|
||||||
|
|||||||
@@ -6,13 +6,9 @@ else cds.User = cds.User.Privileged // hard core monkey patch for older cds rele
|
|||||||
describe('Custom Handlers', () => {
|
describe('Custom Handlers', () => {
|
||||||
|
|
||||||
it('should reject out-of-stock orders', async () => {
|
it('should reject out-of-stock orders', async () => {
|
||||||
await expect(
|
await POST('/browse/submitOrder', { book: 201, amount: 5 })
|
||||||
Promise.all([
|
await POST('/browse/submitOrder', { book: 201, amount: 5 })
|
||||||
POST('/browse/submitOrder', { book: 201, amount: 5 }),
|
await expect(POST('/browse/submitOrder', { book: 201, amount: 5 })).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
|
||||||
POST('/browse/submitOrder', { book: 201, amount: 5 }),
|
|
||||||
POST('/browse/submitOrder', { book: 201, amount: 5 }),
|
|
||||||
])
|
|
||||||
).to.be.rejectedWith(/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)
|
||||||
})
|
})
|
||||||
|
|||||||
6
test/events.cds
Normal file
6
test/events.cds
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
service Sue {
|
||||||
|
@cds.persistence.skip
|
||||||
|
entity Foo { key ID:Integer; title:String; status:String(1); }
|
||||||
|
entity Bar { key ID:Integer; foo: Association to Foo }
|
||||||
|
event Foo.changed : projection on Foo { ID, status }
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
const {expect} = require('../test')
|
const {expect} = require('../test')
|
||||||
const cds = require('@sap/cds/lib')
|
const cds = require('@sap/cds/lib')
|
||||||
|
|
||||||
// monkey patching older releases:
|
|
||||||
if (!cds.compile.cdl) cds.compile.cdl = cds.parse
|
|
||||||
const { parse:cdr } = cds.ql
|
const { parse:cdr } = cds.ql
|
||||||
|
|
||||||
const model = cds.compile.cdl (`
|
// should become cds.compile(...) when cds5 is released
|
||||||
|
const model = cds.compile.to.csn (`
|
||||||
entity Categories {
|
entity Categories {
|
||||||
key ID : Integer;
|
key ID : Integer;
|
||||||
name : String;
|
name : String;
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
|
const cds = require('@sap/cds')
|
||||||
const test = require('@sap/cds/lib/utils/tests').in(__dirname,'..')
|
module.exports = cds.test.in(__dirname,'..')
|
||||||
module.exports = Object.assign(test,{run:test})
|
|
||||||
|
|
||||||
// REVISIT: With upcoming release of @sap/cds this should become:
|
|
||||||
// module.exports = require('@sap/cds/tests').in(__dirname,'..')
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ describe('Localized Data', () => {
|
|||||||
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
|
{ title: 'Jane Eyre', author: 'Charlotte Brontë', currency: { name: 'Pfund' } },
|
||||||
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
|
{ title: 'The Raven', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
|
||||||
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
|
{ title: 'Eleonora', author: 'Edgar Allen Poe', currency: { name: 'US-Dollar' } },
|
||||||
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Euro' } },
|
{ title: 'Catweazle', author: 'Richard Carpenter', currency: { name: 'Yen' } },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ describe('Localized Data', () => {
|
|||||||
{ title: 'Jane Eyre', currency: { name: 'British Pound' } },
|
{ title: 'Jane Eyre', currency: { name: 'British Pound' } },
|
||||||
{ title: 'The Raven', currency: { name: 'US Dollar' } },
|
{ title: 'The Raven', currency: { name: 'US Dollar' } },
|
||||||
{ title: 'Eleonora', currency: { name: 'US Dollar' } },
|
{ title: 'Eleonora', currency: { name: 'US Dollar' } },
|
||||||
{ title: 'Catweazle', currency: { name: 'Euro' } },
|
{ title: 'Catweazle', currency: { name: 'Yen' } },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ describe('Messaging', ()=>{
|
|||||||
|
|
||||||
let N=0, received=[], M=0
|
let N=0, received=[], M=0
|
||||||
it ('should add messaging event handlers', ()=>{
|
it ('should add messaging event handlers', ()=>{
|
||||||
srv.on('reviewed', (msg,next)=> { received.push(msg); return next() })
|
srv.on('reviewed', (msg)=> received.push(msg))
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('should add more messaging event handlers', ()=>{
|
it ('should add more messaging event handlers', ()=>{
|
||||||
srv.on('reviewed', (_,next)=> { ++M; return next() })
|
srv.on('reviewed', ()=> ++M)
|
||||||
})
|
})
|
||||||
|
|
||||||
it ('should add review', async ()=>{
|
it ('should add review', async ()=>{
|
||||||
|
|||||||
Reference in New Issue
Block a user