Compare commits
113 Commits
uom
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc69311ebe | ||
|
|
67643b8d95 | ||
|
|
8ba8381ca7 | ||
|
|
e7fbcc1ac1 | ||
|
|
d977c21483 | ||
|
|
2e10b1ac4d | ||
|
|
cabcd404cf | ||
|
|
5141df8e0f | ||
|
|
785ecbda8f | ||
|
|
2b9ceba62f | ||
|
|
c6e2fac860 | ||
|
|
f2877db34f | ||
|
|
d2bd4c5bb1 | ||
|
|
732b6b081c | ||
|
|
db595a9635 | ||
|
|
4a8b85f227 | ||
|
|
0cb7349e62 | ||
|
|
ba3eeff58b | ||
|
|
86e6983bed | ||
|
|
9bbc431448 | ||
|
|
097b74db03 | ||
|
|
870f8d063d | ||
|
|
4a0db3e259 | ||
|
|
5e76c7f52e | ||
|
|
31c110e9e2 | ||
|
|
8d67c0bf62 | ||
|
|
21359a7ad3 | ||
|
|
649b9c8ca8 | ||
|
|
11c54b29d0 | ||
|
|
80302a0a3d | ||
|
|
c8c10b7c8a | ||
|
|
d9d7203e49 | ||
|
|
4a8379e953 | ||
|
|
dd28bd04f6 | ||
|
|
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 | ||
|
|
05cdb67329 | ||
|
|
4877386e86 | ||
|
|
4bd4446975 | ||
|
|
a54b0124ea | ||
|
|
944afe3bc8 | ||
|
|
92a83f71a1 | ||
|
|
5e5ace0dd4 | ||
|
|
593228a51e | ||
|
|
624cea6343 | ||
|
|
adfe170e8d | ||
|
|
6b08826af5 | ||
|
|
c6239f0375 | ||
|
|
0d19e56509 | ||
|
|
fc088015d3 | ||
|
|
c9f7dc68b8 | ||
|
|
7d9303635e | ||
|
|
b724ae900c | ||
|
|
2f96b92854 | ||
|
|
99dc8d31d7 | ||
|
|
c672e0f1fa | ||
|
|
0ddd70acbc | ||
|
|
3e52a9a102 | ||
|
|
b44701ef62 | ||
|
|
c23ddc7e54 | ||
|
|
66bd2f707c | ||
|
|
3320c7e5a2 | ||
|
|
a35782e775 | ||
|
|
e5bd8ec5a5 | ||
|
|
0aa95a0a67 | ||
|
|
5015eb8c52 | ||
|
|
6d3f4c689f | ||
|
|
f0fead2bc2 | ||
|
|
f1d780d6d9 | ||
|
|
796bf62bde | ||
|
|
5f176a0b88 | ||
|
|
a5c8b5101e | ||
|
|
d72ff809b0 |
32
README.md
32
README.md
@@ -84,3 +84,35 @@ 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](LICENSE.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
|
||||||
@@ -2,8 +2,9 @@ using { sap.capire.bookshop as my } from '../db/schema';
|
|||||||
service CatalogService @(path:'/browse') {
|
service CatalogService @(path:'/browse') {
|
||||||
|
|
||||||
/** For displaying lists of Books */
|
/** For displaying lists of Books */
|
||||||
@readonly entity ListOfBooks as projection on Books
|
@readonly entity ListOfBooks as projection on Books {
|
||||||
excluding { descr };
|
ID, title, author, genre, price, currency
|
||||||
|
}
|
||||||
|
|
||||||
/** For display in details pages */
|
/** For display in details pages */
|
||||||
@readonly entity Books as projection on my.Books { *,
|
@readonly entity Books as projection on my.Books { *,
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# cds.requires.messaging.kind = file-based-messaging
|
cds.requires.messaging.kind = file-based-messaging
|
||||||
PORT = 4004
|
PORT = 4004
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<head>
|
|
||||||
<meta http-equiv="refresh" content="0;url=bookshop/index.html">
|
|
||||||
</head>
|
|
||||||
@@ -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": "^5",
|
"@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,15 +20,19 @@
|
|||||||
"deploy-format": "hdbtable"
|
"deploy-format": "hdbtable"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"API_BUSINESS_PARTNER": {
|
||||||
|
"kind": "odata-v2",
|
||||||
|
"model": "@capire/suppliers"
|
||||||
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"strategy": "dummy"
|
"strategy": "dummy"
|
||||||
},
|
},
|
||||||
"ReviewsService": {
|
"ReviewsService": {
|
||||||
"kind": "odata",
|
"kind": "odata-v4",
|
||||||
"model": "@capire/reviews"
|
"model": "@capire/reviews"
|
||||||
},
|
},
|
||||||
"OrdersService": {
|
"OrdersService": {
|
||||||
"kind": "odata",
|
"kind": "odata-v4",
|
||||||
"model": "@capire/orders"
|
"model": "@capire/orders"
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
|
|||||||
@@ -2,22 +2,15 @@ const cds = require ('@sap/cds')
|
|||||||
module.exports = cds.server
|
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'))
|
||||||
|
|
||||||
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
const cds_swagger = require ('cds-swagger-ui-express')
|
cds.once ('bootstrap', app => app.use (require ('cds-swagger-ui-express')()) )
|
||||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// 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
|
||||||
@@ -23,3 +23,9 @@ using { sap.capire.orders.Orders_Items } from '@capire/orders';
|
|||||||
extend Orders_Items with {
|
extend Orders_Items with {
|
||||||
book : Association to Books on product.ID = book.ID
|
book : Association to Books on product.ID = book.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using { OrdersService.Orders } from '@capire/orders';
|
||||||
|
// extend service CatalogService with {
|
||||||
|
// entity OrdereredBooks as projection on Orders;
|
||||||
|
// }
|
||||||
|
|||||||
9
orders/api/external.cds
Normal file
9
orders/api/external.cds
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
context external {
|
||||||
|
|
||||||
|
/** This is a stand-in for arbitrary ordered Products */
|
||||||
|
entity Products @(cds.persistence.skip:'always') {
|
||||||
|
key ID : String;
|
||||||
|
data : String;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
orders/api/orders-service-comp.cds
Normal file
21
orders/api/orders-service-comp.cds
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using { cuid, managed, User, Currency } from '@sap/cds/common';
|
||||||
|
using { sap.capire.orders as my } from '../db/schema';
|
||||||
|
using { external.Products } from './external';
|
||||||
|
|
||||||
|
|
||||||
|
service OrdersService {
|
||||||
|
|
||||||
|
entity Orders : cuid, managed {
|
||||||
|
OrderNo : String @title:'Order Number'; //> readable key
|
||||||
|
Items : Composition of many {
|
||||||
|
key ID : UUID;
|
||||||
|
product : Association to Products;
|
||||||
|
amount : Integer;
|
||||||
|
title : String; //> intentionally replicated as snapshot from product.title
|
||||||
|
price : Double;
|
||||||
|
};
|
||||||
|
buyer : User;
|
||||||
|
currency : Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
40
orders/api/orders-service.cds
Normal file
40
orders/api/orders-service.cds
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using { cuid, managed, User, Currency } from '@sap/cds/common';
|
||||||
|
using { sap.capire.orders as my } from '../db/schema';
|
||||||
|
using { external.Products } from './external';
|
||||||
|
|
||||||
|
|
||||||
|
// GET Orders
|
||||||
|
// GET Orders()/Items
|
||||||
|
// GET Orders?$Items
|
||||||
|
|
||||||
|
service OwnService {
|
||||||
|
entity Orders as projection on OrdersService.Orders;
|
||||||
|
entity Products as projection on OrdersService.Products;
|
||||||
|
}
|
||||||
|
|
||||||
|
service OrdersService {
|
||||||
|
|
||||||
|
entity Orders : cuid, managed {
|
||||||
|
OrderNo : String @title:'Order Number'; //> readable key
|
||||||
|
Items : Composition of many Orders_Items; // on Items.up_ = $self;
|
||||||
|
// MoreItems : Composition of many Orders_Items; // on Items.up_ = $self;
|
||||||
|
// Items_ID
|
||||||
|
buyer : User;
|
||||||
|
currency : Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Orders_Items {
|
||||||
|
key ID : UUID;
|
||||||
|
// up_ : Association to Orders;
|
||||||
|
product : Association to Products; //> {ID}
|
||||||
|
// product_ID
|
||||||
|
amount : Integer;
|
||||||
|
title : String; //> intentionally replicated as snapshot from product.title
|
||||||
|
price : Double;
|
||||||
|
}
|
||||||
|
|
||||||
|
// entity Products {
|
||||||
|
// key ID : UUID;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"@capire/media": "./media",
|
"@capire/media": "./media",
|
||||||
"@capire/orders": "./orders",
|
"@capire/orders": "./orders",
|
||||||
"@capire/reviews": "./reviews",
|
"@capire/reviews": "./reviews",
|
||||||
|
"@capire/suppliers": "./suppliers",
|
||||||
"@sap/cds": "^5",
|
"@sap/cds": "^5",
|
||||||
"express": "^4"
|
"express": "^4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 }
|
||||||
12
samples.md
12
samples.md
@@ -50,14 +50,22 @@ 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)
|
||||||
|
|||||||
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/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">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</diagram></mxfile>
|
||||||
32
suppliers/package.json
Normal file
32
suppliers/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
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
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
500 Internal Server Error
Gitea Version: 1.24.4 |