Compare commits
7 Commits
dkom
...
CAA265-nod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae57008c5 | ||
|
|
2c1079bc5b | ||
|
|
1e28cb217f | ||
|
|
2fc2cea260 | ||
|
|
bfaaf36e14 | ||
|
|
33b24c8d96 | ||
|
|
387b25b1bf |
@@ -6,7 +6,7 @@
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
"globals": {
|
||||
"SELECT": true,
|
||||
|
||||
37
.vscode/launch.json
vendored
37
.vscode/launch.json
vendored
@@ -1,37 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "bookshop", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ],
|
||||
"args": [ "--", "cds", "run", "--in-memory" ],
|
||||
"cwd": "${workspaceFolder}/packages/bookshop",
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
{
|
||||
"name": "cds run ...", "request": "launch", "type": "node", "runtimeExecutable": "npx", "runtimeArgs": [ "-n" ],
|
||||
"args": [ "--", "cds", "run", "--with-mocks", "--in-memory?" ],
|
||||
"cwd": "${workspaceFolder}/packages/${input:service}",
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "pickString",
|
||||
"id": "service",
|
||||
"description": "Which service do you want to start?",
|
||||
"options": [
|
||||
"bookshop",
|
||||
"bookstore",
|
||||
"media-server",
|
||||
"office-supplies",
|
||||
"reviews-service"
|
||||
],
|
||||
"default": "bookshop"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.gitignore": true,
|
||||
"**/.vscode": true
|
||||
}
|
||||
}
|
||||
17
.vscode/tasks.json
vendored
17
.vscode/tasks.json
vendored
@@ -1,17 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm", "script": "watch", "path": "packages/bookshop/",
|
||||
"options": { "env": { "PORT": "4004" }},
|
||||
"presentation": { "group": "A" }
|
||||
},
|
||||
{
|
||||
"type": "npm", "script": "watch", "path": "packages/reviews-service/",
|
||||
"options": { "env": { "PORT": "5005" }},
|
||||
"presentation": { "group": "A" }
|
||||
}
|
||||
]
|
||||
}
|
||||
74
README.md
74
README.md
@@ -1,73 +1,3 @@
|
||||
# cloud-cap-samples
|
||||
# Final state of exercise 1 for CAA265 - Rapid Service Development with SAP Cloud Application Programming Model
|
||||
|
||||
This is a monorepository for sample projects on [SAP Cloud Application Programming Model](https://cap.cloud.sap).
|
||||
|
||||
## Description
|
||||
|
||||
This repository provides a list of samples and reusable packages created based on SAP Cloud Application Programming Model.
|
||||
The SAP Cloud Application Programming Model enables you to quickly create business applications by allowing you to focus on your domain logic. It offers a consistent end-to-end programming model that includes languages, libraries and APIs tailored for full-stack development on SAP Cloud Platform.
|
||||
|
||||
The samples provided can be run in a local setup on SQLite Database.
|
||||
|
||||
|
||||
## Requirements
|
||||
* [Node.js](https://nodejs.org/en/) v8 or higher
|
||||
* [Git](https://git-scm.com)
|
||||
* [SQLite DB](https://www.sqlite.org/download.html) (Windows only; pre-installed on Mac/Linux)
|
||||
|
||||
#### Optional (if you want to import the code into an editor)
|
||||
* [VS Code](https://code.visualstudio.com)
|
||||
* [Add CDS extension to VS](https://cap.cloud.sap/docs/get-started/in-vscode#add-cds-editor)
|
||||
|
||||
## Download and Installation
|
||||
|
||||
#### Install `cds` development kit
|
||||
```sh
|
||||
# sets the registry for `@sap` packages
|
||||
npm set @sap:registry=https://npm.sap.com
|
||||
|
||||
npm install -g @sap/cds-dk
|
||||
cds #> test-run it
|
||||
```
|
||||
Got issues? Check out the [documentation](https://cap.cloud.sap/docs/get-started/).
|
||||
|
||||
#### Clone and build the application
|
||||
`git clone https://github.com/SAP-samples/cloud-cap-samples samples && cd samples && npm i`
|
||||
|
||||
#### Run the samples
|
||||
|
||||
With that you're ready to run the samples, e.g. start the [_bookshop_](./packages/bookshop) sample as follows:
|
||||
|
||||
`npm run bookshop`
|
||||
|
||||
## Test
|
||||
|
||||
For example, try these links in your browser:
|
||||
- <http://localhost:4004> to test with generic index page.
|
||||
- <http://localhost:4004/fiori.html> to test with Fiori sandbox.
|
||||
|
||||
|
||||
## Debug
|
||||
|
||||
For example, in [VS Code](https://code.visualstudio.com) switch to _Debug_ view and launch one of the prepared _cds run_ launch configurations.
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
None
|
||||
|
||||
## Known Issues
|
||||
|
||||
None
|
||||
|
||||
## How to obtain support
|
||||
|
||||
Check out the documentation on https://cap.cloud.sap. In case you find a bug, or you need additional support, please open an issue [here](https://github.com/SAP-samples/cloud-cap-samples/issues/new) in GitHub.
|
||||
|
||||
## To-Do (upcoming changes)
|
||||
|
||||
None
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under SAP Sample Code License Agreement, except as noted otherwise in the [LICENSE](/LICENSE) file.
|
||||
**DO NOT MERGE IN MASTER**
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
{"packages":["packages/*"],"version":"1.0.0"}
|
||||
{
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"shared/*"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
||||
28
package.json
28
package.json
@@ -1,28 +1,16 @@
|
||||
{
|
||||
"name": "@sap/capire-samples",
|
||||
"description": "The umbrella project for all samples to easily setup for local development and tests.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"repository": "https://github.wdf.sap.corp/capire/samples.git",
|
||||
"author": "daniel.hutzel@sap.com",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lerna": "npx --no-install lerna -v > /dev/null || npm i lerna --no-save",
|
||||
"install": "(npm -s run lerna) && lerna bootstrap --hoist",
|
||||
"cleanup": "lerna clean -y && rm -fr node_modules",
|
||||
"bookshop": "cds watch packages/bookshop",
|
||||
"bookshop-enhanced": "cds watch packages/bookshop-enhanced",
|
||||
"reviews-service": "cds watch packages/reviews-service",
|
||||
"bookstore": "cds watch packages/bookstore"
|
||||
"install": "(npx --no-install lerna -v || npm i lerna --no-save) && lerna bootstrap --hoist --ignore tests-env --loglevel warn",
|
||||
"reset": "lerna clean -y && rm -fr node_modules",
|
||||
"test": "jest",
|
||||
"bookstore": "cds run packages/bookstore --in-memory",
|
||||
"products-service": "cds run packages/products-service --in-memory",
|
||||
"reviews-service": "cds run packages/reviews-service --in-memory"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sqlite3": "*"
|
||||
},
|
||||
"--add-these-to-devDependencies-for-tests": {
|
||||
"@types/jest": "*",
|
||||
"jest": "*"
|
||||
},
|
||||
"license": "SAP SAMPLE CODE LICENSE"
|
||||
"license": "ISC"
|
||||
}
|
||||
|
||||
3
packages/.cdsrc.json
Normal file
3
packages/.cdsrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cds_version": "^3.17.4"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
using from '@sap/capire-bookshop/app';
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
In this model we demonstrate how to add Genres to Books in
|
||||
as if it was an external extension. For example we use
|
||||
CDS Aspects' to extend the core domain model's Books entity
|
||||
as well as the AdminService.
|
||||
*/
|
||||
|
||||
namespace sap.capire.bookshop;
|
||||
using { sap.capire.reviews.ReviewsService as external } from '@sap/capire-reviews';
|
||||
using { sap.capire.bookshop.Books } from '@sap/capire-bookshop/db/schema';
|
||||
using { sap.common.CodeList } from '@sap/cds/common';
|
||||
|
||||
// Extending Books by Reviews and Genres
|
||||
extend Books with {
|
||||
reviews : Composition of many external.Reviews on reviews.subject = ID;
|
||||
rating : external.Reviews.rating;
|
||||
genre : Association to Genres;
|
||||
}
|
||||
|
||||
// Hierarchical Code List for Genres
|
||||
entity Genres : CodeList {
|
||||
key ID : Integer;
|
||||
children : Composition of many Genres on children.parent = $self;
|
||||
parent : Association to Genres;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-bookshop-enhanced",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample for extending a base application package, in this case bookshop, e.g. in context of verticalization or customization.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/capire-bookshop": "^1.0.0",
|
||||
"@sap/capire-reviews": "^1.0.0",
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"reviews-service": "PORT=5005 cds run ../reviews-service --bind --in-memory?",
|
||||
"start": "cds run --in-memory?",
|
||||
"watch": "cds watch"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"sap.capire.reviews.ReviewsService": {
|
||||
"kind": "odata",
|
||||
"model": "@sap/capire-reviews"
|
||||
},
|
||||
"messaging": {
|
||||
"kind": "file-based-messaging"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace sap.capire.bookshop;
|
||||
|
||||
using { AdminService } from '@sap/capire-bookshop/srv/admin-service';
|
||||
using { sap.capire.bookshop } from '../db/schema';
|
||||
|
||||
@impl:'srv/services'
|
||||
extend service AdminService with {
|
||||
entity Genres as projection on bookshop.Genres;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
module.exports = cds.service.impl (async()=>{
|
||||
|
||||
const ReviewsService = await cds.connect.to ('sap.capire.reviews.ReviewsService')
|
||||
const CatalogService = await cds.connect.to ('CatalogService')
|
||||
const db = await cds.connect.to ('db')
|
||||
// import model definitions from connected services to work with subsequently
|
||||
const { Books } = db.entities
|
||||
const { Reviews } = ReviewsService.entities
|
||||
|
||||
CatalogService.impl (srv => {
|
||||
// delegate requests to read reviews to ReviewsService
|
||||
srv.on ('READ', 'Books/reviews', (req) => {
|
||||
const [ subject ] = req.params
|
||||
const tx = ReviewsService.transaction (req)
|
||||
return tx.run (SELECT.from (Reviews) .where ({subject}))
|
||||
})
|
||||
})
|
||||
|
||||
// react on event messages from reviews service
|
||||
ReviewsService.on ('reviewed', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
const { subject, rating } = msg.data
|
||||
const tx = db // TODO: db.transaction (msg)
|
||||
return tx.run (UPDATE (Books, subject) .with ({rating}))
|
||||
// return tx.update (Books, subject) .with ({rating})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
#################################################
|
||||
#
|
||||
# Genres
|
||||
#
|
||||
|
||||
GET http://localhost:4004/admin/Genres?
|
||||
###
|
||||
|
||||
POST http://localhost:4004/admin/Genres?
|
||||
Content-Type: application/json
|
||||
|
||||
{ "ID":100, "name":"Some Sample Genres...", "children":[
|
||||
{ "ID":101, "name":"Cat", "children":[
|
||||
{ "ID":102, "name":"Kitty", "children":[
|
||||
{ "ID":103, "name":"Kitty Cat", "children":[
|
||||
{ "ID":104, "name":"Aristocat" } ]},
|
||||
{ "ID":105, "name":"Kitty Bat" } ]},
|
||||
{ "ID":106, "name":"Catwoman", "children":[
|
||||
{ "ID":107, "name":"Catalina" } ]} ]},
|
||||
{ "ID":108, "name":"Catweazle" }
|
||||
]}
|
||||
###
|
||||
|
||||
GET http://localhost:4004/admin/Genres(100)?
|
||||
# &$expand=children
|
||||
# &$expand=children($expand=children($expand=children($expand=children)))
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/admin/Genres(103)
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/admin/Genres(100)
|
||||
###
|
||||
@@ -1,32 +0,0 @@
|
||||
#################################################
|
||||
#
|
||||
# Reviews Service
|
||||
#
|
||||
|
||||
|
||||
### Use this one for ReviewsService running as a separate process
|
||||
# Note: use 5005 instead of 4004 in case of separate service
|
||||
POST http://localhost:5005/reviews/Reviews
|
||||
# POST http://localhost:4004/reviews/Reviews
|
||||
Content-Type: application/json;IEEE754Compatible=true
|
||||
|
||||
{"subject":"201", "rating":"5", "title":"boo"}
|
||||
|
||||
### Direct Request to ReviewsService
|
||||
# Note: use 5005 instead of 4004 in case of separate service
|
||||
GET http://localhost:5005/reviews/Reviews?
|
||||
# GET http://localhost:4004/reviews/Reviews?
|
||||
# &$filter=subject eq '201'
|
||||
|
||||
|
||||
### Request to CatalogService > delegated to ReviewsService
|
||||
GET http://localhost:4004/browse/Books(201)/reviews
|
||||
|
||||
### Alternative OData URL
|
||||
GET http://localhost:4004/browse/Books/201/reviews
|
||||
|
||||
###
|
||||
GET http://localhost:4004/browse/Books(201)?
|
||||
&$select=ID,title,rating
|
||||
# &$expand=reviews
|
||||
# Note: the latter only works in case of ReviewsService in same process
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"printWidth": 120
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
# Bookshop With Address Data From SAP S/4HANA
|
||||
|
||||
This is an extended bookshop with business-partner address data from SAP S/4HANA.
|
||||
When the user creates an order and uses the value help of the shipping address,
|
||||
a synchronous request to SAP S/4HANA is triggered yielding all possible addresses
|
||||
belonging to this business partner. Once an address is selected, its data
|
||||
is replicated into a local database. To keep data in sync, an event handler
|
||||
is registered which listens to all changes of business partners and updates the
|
||||
local database table.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
`@sap/cds` >= 1.30
|
||||
|
||||
|
||||
## Running With Mocks
|
||||
Just execute the following command in the `bookshop` folder.
|
||||
```
|
||||
cds run --in-memory --with-mocks
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Running With an S/4HANA Backend
|
||||
|
||||
To run your app in non-mock mode you need an SAP S/4HANA Cloud system and connect it to your SAP Cloud Platform. You can use the
|
||||
[SAP Cloud Platform Extension Factory](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/346864df64f24011b49abee07bbd79af.html) to automate parts of this task. You need to enable synchronous APIs as well as events that are sent whenever business partners are changed.
|
||||
|
||||
To run the app locally, you need to create a `default-env.json` file in the `bookshop` folder containing the binding information (credentials of Enterprise Messaging as well as the destination to the business-partner service).
|
||||
|
||||
Provide the credentials in the `cds.requires` section of the `package.json` file in the `bookshop` folder, e.g.
|
||||
|
||||
```json
|
||||
"cds": {
|
||||
"requires": {
|
||||
"API_BUSINESS_PARTNER": {
|
||||
"kind": "odata",
|
||||
"model": "srv/external",
|
||||
"credentials": {
|
||||
"destination": "cap-api098"
|
||||
}
|
||||
},
|
||||
"messaging": {
|
||||
"kind": "enterprise-messaging",
|
||||
"credentials": {
|
||||
"prefix": "sap/S4HANAOD/c098/BO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, `destination` is the destination of your business-partner service and `prefix` is the prefix
|
||||
of the topic of the events.
|
||||
|
||||
Then simply run the following command in the `bookshop` folder.
|
||||
```
|
||||
cds run --in-memory
|
||||
```
|
||||
|
||||
## User Flow
|
||||
After starting the app, go to http://localhost:4004/fiori.html#Shell-home and open the app `Manage Orders` to create an order.
|
||||
Use the value help of the shipping address to select an address. Create an order item and save the order.
|
||||
Then change the address of your business partner (in the mocked case you can trigger the PATCH request in `req.http` ). Refresh
|
||||
the object page of your order and see the change.
|
||||
@@ -1,22 +0,0 @@
|
||||
Books = Books
|
||||
Book = Book
|
||||
ID = ID
|
||||
Title = Title
|
||||
Author = Author
|
||||
AuthorID = Author ID
|
||||
Stock = Stock
|
||||
Name = Name
|
||||
AuthorName = Author's Name
|
||||
Authors = Authors
|
||||
Order = Order
|
||||
OrderItems = Order Items
|
||||
Orders = Orders
|
||||
Price = Price
|
||||
shippingAddress = Shipping Address
|
||||
cityName = City Name
|
||||
houseNumber = House Number
|
||||
streetName = Street Name
|
||||
postalCode = Postal Code
|
||||
country = Country
|
||||
AddressID = Address ID
|
||||
contact = Contact
|
||||
@@ -1,13 +0,0 @@
|
||||
Books = Bücher
|
||||
Book = Buch
|
||||
ID = ID
|
||||
Title = Titel
|
||||
Authors = Autoren
|
||||
Author = Autor
|
||||
AuthorID = ID des Autors
|
||||
AuthorName = Name des Autors
|
||||
Name = Name
|
||||
Stock = Bestand
|
||||
Order = Bestellung
|
||||
Orders = Bestellungen
|
||||
Price = Preis
|
||||
@@ -1,37 +0,0 @@
|
||||
using AdminService from '../../srv/admin-service';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Object Page
|
||||
//
|
||||
annotate AdminService.Books with @(
|
||||
UI: {
|
||||
Facets: [
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
|
||||
],
|
||||
FieldGroup#General: {
|
||||
Data: [
|
||||
{Value: title},
|
||||
{Value: author_ID},
|
||||
{Value: descr},
|
||||
]
|
||||
},
|
||||
FieldGroup#Details: {
|
||||
Data: [
|
||||
{Value: stock},
|
||||
{Value: price},
|
||||
{Value: currency_code, Label: '{i18n>Currency}'},
|
||||
]
|
||||
},
|
||||
FieldGroup#Admin: {
|
||||
Data: [
|
||||
{Value: createdBy},
|
||||
{Value: createdAt},
|
||||
{Value: modifiedBy},
|
||||
{Value: modifiedAt}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("admin.Component", {
|
||||
metadata:{ manifest:'json' }
|
||||
}))
|
||||
|
||||
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
|
||||
// jQuery.sap.declare("bookshop.Component");
|
||||
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
|
||||
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
|
||||
|
||||
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
|
||||
// metadata: {
|
||||
// manifest: "json"
|
||||
// }
|
||||
// });
|
||||
|
||||
/* eslint no-undef:0 */
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
@@ -1,128 +0,0 @@
|
||||
{
|
||||
"_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": {}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using CatalogService from '../../srv/cat-service';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Object Page
|
||||
//
|
||||
annotate CatalogService.Books with @(
|
||||
UI: {
|
||||
HeaderInfo: {
|
||||
Description: {Value: author}
|
||||
},
|
||||
HeaderFacets: [
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'},
|
||||
],
|
||||
Facets: [
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Price'},
|
||||
],
|
||||
FieldGroup#Descr: {
|
||||
Data: [
|
||||
{Value: descr},
|
||||
]
|
||||
},
|
||||
FieldGroup#Price: {
|
||||
Data: [
|
||||
{Value: price},
|
||||
{Value: currency.symbol, Label: '{i18n>Currency}'},
|
||||
]
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Object Page
|
||||
//
|
||||
annotate CatalogService.Books with @(
|
||||
UI: {
|
||||
SelectionFields: [ ID, price, currency_code ],
|
||||
LineItem: [
|
||||
{Value: title},
|
||||
{Value: author, Label:'{i18n>Author}'},
|
||||
{Value: price},
|
||||
{Value: currency.symbol, Label:' '},
|
||||
]
|
||||
},
|
||||
);
|
||||
@@ -1,22 +0,0 @@
|
||||
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("bookshop.Component", {
|
||||
metadata:{ manifest:'json' }
|
||||
}))
|
||||
|
||||
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
|
||||
// jQuery.sap.declare("bookshop.Component");
|
||||
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
|
||||
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
|
||||
|
||||
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
|
||||
// metadata: {
|
||||
// manifest: "json"
|
||||
// }
|
||||
// });
|
||||
|
||||
/* eslint no-undef:0 */
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"_version": "1.8.0",
|
||||
"sap.app": {
|
||||
"id": "bookshop",
|
||||
"type": "application",
|
||||
"title": "Browse Books",
|
||||
"description": "Sample Application",
|
||||
"i18n": "i18n/i18n.properties",
|
||||
"dataSources": {
|
||||
"CatalogService": {
|
||||
"uri": "/browse/",
|
||||
"type": "OData",
|
||||
"settings": {
|
||||
"odataVersion": "4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"-sourceTemplate": {
|
||||
"id": "ui5template.basicSAPUI5ApplicationProject",
|
||||
"-id": "ui5template.smartTemplate",
|
||||
"-version": "1.40.12"
|
||||
}
|
||||
},
|
||||
"sap.ui5": {
|
||||
"dependencies": {
|
||||
"libs": {
|
||||
"sap.fe": {}
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"i18n": {
|
||||
"type": "sap.ui.model.resource.ResourceModel",
|
||||
"uri": "i18n/i18n.properties"
|
||||
},
|
||||
"": {
|
||||
"dataSource": "CatalogService",
|
||||
"settings": {
|
||||
"synchronizationMode": "None",
|
||||
"operationMode": "Server",
|
||||
"autoExpandSelect": true,
|
||||
"earlyRequests": true,
|
||||
"groupProperties": {
|
||||
"default": {
|
||||
"submit": "Auto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": ":?query:",
|
||||
"name": "BooksList",
|
||||
"target": "BooksList"
|
||||
},
|
||||
{
|
||||
"pattern": "Books({key}):?query:",
|
||||
"name": "BooksDetails",
|
||||
"target": "BooksDetails"
|
||||
}
|
||||
],
|
||||
"targets": {
|
||||
"BooksList": {
|
||||
"type": "Component",
|
||||
"id": "BooksList",
|
||||
"name": "sap.fe.templates.ListReport",
|
||||
"options": {
|
||||
"settings": {
|
||||
"entitySet": "Books",
|
||||
"navigation": {
|
||||
"Books": {
|
||||
"detail": {
|
||||
"route": "BooksDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"BooksDetails": {
|
||||
"type": "Component",
|
||||
"id": "BooksDetailsList",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings": {
|
||||
"entitySet": "Books"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"contentDensities": {
|
||||
"compact": true,
|
||||
"cozy": true
|
||||
}
|
||||
},
|
||||
"sap.ui": {
|
||||
"technology": "UI5",
|
||||
"fullWidth": false
|
||||
},
|
||||
"sap.fiori": {
|
||||
"registrationIds": [],
|
||||
"archeType": "transactional"
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
Common Annotations shared by all apps
|
||||
*/
|
||||
|
||||
using { sap.capire.bookshop as my } from '../db/schema';
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Books Lists
|
||||
//
|
||||
annotate my.Books with @(
|
||||
UI: {
|
||||
Identification: [{Value:title}],
|
||||
SelectionFields: [ ID, author_ID, price, currency_code ],
|
||||
LineItem: [
|
||||
{Value: ID},
|
||||
{Value: title},
|
||||
{Value: author.name, Label:'{i18n>Author}'},
|
||||
{Value: stock},
|
||||
{Value: price},
|
||||
{Value: currency.symbol, Label:' '},
|
||||
]
|
||||
}
|
||||
) {
|
||||
author @ValueList.entity:'Authors';
|
||||
};
|
||||
|
||||
annotate my.Authors with @(
|
||||
UI: {
|
||||
Identification: [{Value:name}],
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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}';
|
||||
author @title:'{i18n>AuthorID}';
|
||||
price @title:'{i18n>Price}';
|
||||
stock @title:'{i18n>Stock}';
|
||||
descr @UI.MultiLineText;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Authors Elements
|
||||
//
|
||||
annotate my.Authors with {
|
||||
ID @title:'{i18n>ID}' @UI.HiddenFilter;
|
||||
name @title:'{i18n>AuthorName}';
|
||||
}
|
||||
|
||||
annotate my.Addresses with {
|
||||
ID @title:'{i18n>AddressID}';
|
||||
contact @title:'{i18n>contact}';
|
||||
@readonly cityName @title:'{i18n>cityName}';
|
||||
@readonly streetName @title:'{i18n>streetName}';
|
||||
@readonly postalCode @title:'{i18n>postalCode}';
|
||||
@readonly country @title:'{i18n>country}';
|
||||
@readonly houseNumber @title:'{i18n>houseNumber}';
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<!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: "... testing FE v42",
|
||||
additionalInformation: "SAPUI5.Component=bookshop",
|
||||
applicationType : "URL",
|
||||
url: "/browse/webapp",
|
||||
navigationMode: "embedded"
|
||||
},
|
||||
"manage-books": {
|
||||
title: "Manage Books",
|
||||
description: "... testing FE v42",
|
||||
additionalInformation: "SAPUI5.Component=admin",
|
||||
applicationType : "URL",
|
||||
url: "/admin/webapp",
|
||||
navigationMode: "embedded"
|
||||
},
|
||||
"manage-orders": {
|
||||
title: "Manage Orders",
|
||||
description: "... testing FE v42",
|
||||
additionalInformation: "SAPUI5.Component=orders",
|
||||
applicationType : "URL",
|
||||
url: "/orders/webapp",
|
||||
navigationMode: "embedded"
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
|
||||
<script 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>
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
This model controls what gets served to Fiori frontends...
|
||||
*/
|
||||
|
||||
using from './admin/fiori-service';
|
||||
using from './browse/fiori-service';
|
||||
using from './orders/fiori-service';
|
||||
using from './common';
|
||||
@@ -1,243 +0,0 @@
|
||||
using AdminService from '../../srv/admin-service';
|
||||
|
||||
annotate AdminService.Books with {
|
||||
price @Common.FieldControl : #ReadOnly;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Common
|
||||
//
|
||||
annotate AdminService.OrderItems with {
|
||||
book @(
|
||||
Common : {
|
||||
Text : book.title,
|
||||
FieldControl : #Mandatory
|
||||
},
|
||||
ValueList.entity : 'Books',
|
||||
);
|
||||
amount @(Common.FieldControl : #Mandatory);
|
||||
}
|
||||
|
||||
annotate AdminService.Orders with {
|
||||
shippingAddress @(Common : {
|
||||
FieldControl : #Mandatory,
|
||||
ValueList : {
|
||||
CollectionPath : 'Addresses',
|
||||
Label : 'Addresses',
|
||||
SearchSupported : 'true',
|
||||
Parameters : [
|
||||
{
|
||||
$Type : 'Common.ValueListParameterOut',
|
||||
LocalDataProperty : 'shippingAddress_ID',
|
||||
ValueListProperty : 'ID'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterOut',
|
||||
LocalDataProperty : 'shippingAddress_contact',
|
||||
ValueListProperty : 'contact'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty : 'postalCode'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty : 'cityName'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty : 'country'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty : 'streetName'
|
||||
},
|
||||
{
|
||||
$Type : 'Common.ValueListParameterDisplayOnly',
|
||||
ValueListProperty : 'houseNumber'
|
||||
},
|
||||
]
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// UI
|
||||
//
|
||||
annotate AdminService.Orders with @(UI : {
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Lists of Orders
|
||||
//
|
||||
SelectionFields : [
|
||||
createdAt,
|
||||
createdBy
|
||||
],
|
||||
LineItem : [
|
||||
{
|
||||
Value : createdBy,
|
||||
Label : 'Customer'
|
||||
},
|
||||
{
|
||||
Value : createdAt,
|
||||
Label : 'Date'
|
||||
}
|
||||
],
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Order Details
|
||||
//
|
||||
HeaderInfo : {
|
||||
TypeName : 'Order',
|
||||
TypeNamePlural : 'Orders',
|
||||
Title : {
|
||||
Label : 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
|
||||
Value : OrderNo
|
||||
},
|
||||
Description : {Value : createdBy}
|
||||
},
|
||||
Identification : [ //Is the main field group
|
||||
// labels not considered
|
||||
{
|
||||
Value : createdBy,
|
||||
Label : 'Customer'
|
||||
},
|
||||
{
|
||||
Value : createdAt,
|
||||
Label : 'Date'
|
||||
},
|
||||
{Value : OrderNo},
|
||||
{
|
||||
Value : 'shippingAddress_ID',
|
||||
Label : 'Address ID'
|
||||
}
|
||||
],
|
||||
HeaderFacets : [
|
||||
{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>Created}',
|
||||
Target : '@UI.FieldGroup#Created'
|
||||
},
|
||||
{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>Modified}',
|
||||
Target : '@UI.FieldGroup#Modified'
|
||||
},
|
||||
],
|
||||
Facets : [
|
||||
{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>shippingAddress}',
|
||||
Target : '@UI.FieldGroup#ShippingAddress'
|
||||
},
|
||||
{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>Details}',
|
||||
Target : '@UI.FieldGroup#Details'
|
||||
},
|
||||
{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>OrderItems}',
|
||||
Target : 'Items/@UI.LineItem'
|
||||
},
|
||||
],
|
||||
FieldGroup #Details : {Data : [{
|
||||
Value : currency_code,
|
||||
Label : 'Currency'
|
||||
}]},
|
||||
FieldGroup #Created : {Data : [
|
||||
{Value : createdBy},
|
||||
{Value : createdAt},
|
||||
]},
|
||||
FieldGroup #Modified : {Data : [
|
||||
{Value : modifiedBy},
|
||||
{Value : modifiedAt},
|
||||
]},
|
||||
FieldGroup #ShippingAddress : {Data : [
|
||||
{
|
||||
Value : shippingAddress_ID,
|
||||
Label : '{i18n>shippingAddress}'
|
||||
},
|
||||
{
|
||||
Value : shippingAddress.houseNumber,
|
||||
Label : '{i18n>houseNumber}'
|
||||
},
|
||||
{
|
||||
Value : shippingAddress.streetName,
|
||||
Label : '{i18n>streetName}'
|
||||
},
|
||||
{
|
||||
Value : shippingAddress.cityName,
|
||||
Label : '{i18n>cityName}'
|
||||
},
|
||||
{
|
||||
Value : shippingAddress.postalCode,
|
||||
Label : '{i18n>postalCode}'
|
||||
},
|
||||
]},
|
||||
},
|
||||
Common.SideEffects : {
|
||||
EffectTypes : #ValueChange,
|
||||
SourceProperties : [shippingAddress_ID],
|
||||
TargetProperties : [
|
||||
shippingAddress.country,
|
||||
shippingAddress.houseNumber,
|
||||
shippingAddress.streetName,
|
||||
shippingAddress.cityName,
|
||||
shippingAddress.postalCode
|
||||
]
|
||||
},
|
||||
) {
|
||||
createdAt @UI.HiddenFilter : false;
|
||||
createdBy @UI.HiddenFilter : false;
|
||||
};
|
||||
|
||||
//The enity types name is AdminService.my_bookshop_OrderItems
|
||||
//The annotations below are not generated in edmx WHY?
|
||||
annotate AdminService.OrderItems with @(UI : {
|
||||
HeaderInfo : {
|
||||
TypeName : 'Order Item',
|
||||
TypeNamePlural : ' ',
|
||||
Title : {Value : book.title},
|
||||
Description : {Value : book.descr}
|
||||
},
|
||||
// There is no filterbar for items so the selctionfileds is not needed
|
||||
SelectionFields : [book_ID],
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Lists of OrderItems
|
||||
//
|
||||
LineItem : [
|
||||
{
|
||||
Value : book_ID,
|
||||
Label : 'Book'
|
||||
},
|
||||
//The following entry is only used to have the assoication followed in the read event
|
||||
{
|
||||
Value : book.price,
|
||||
Label : 'Book Price'
|
||||
},
|
||||
{
|
||||
Value : amount,
|
||||
Label : 'Quantity'
|
||||
},
|
||||
],
|
||||
Identification : [ //Is the main field group
|
||||
//{Value: ID, Label:'ID'}, //A guid shouldn't be on the UI
|
||||
{
|
||||
Value : book_ID,
|
||||
Label : 'Book'
|
||||
},
|
||||
{
|
||||
Value : amount,
|
||||
Label : 'Amount'
|
||||
},
|
||||
],
|
||||
Facets : [{
|
||||
$Type : 'UI.ReferenceFacet',
|
||||
Label : '{i18n>OrderItems}',
|
||||
Target : '@UI.Identification'
|
||||
}, ],
|
||||
}, );
|
||||
@@ -1,22 +0,0 @@
|
||||
sap.ui.define(["sap/fe/AppComponent"], ac => ac.extend("orders.Component", {
|
||||
metadata:{ manifest:'json' }
|
||||
}))
|
||||
|
||||
// sap.ui.define (["sap/ui/core/UIComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
// sap.ui.define (["sap/ui/generic/app/AppComponent"], ui5 => ui5.extend("bookshop.Component", {
|
||||
// metadata: { manifest: "json" }
|
||||
// }))
|
||||
|
||||
// jQuery.sap.declare("bookshop.Component");
|
||||
// sap.ui.getCore().loadLibrary("sap.ui.generic.app");
|
||||
// jQuery.sap.require("sap.ui.generic.app.AppComponent");
|
||||
|
||||
// sap.ui.generic.app.AppComponent.extend("bookshop.Component", {
|
||||
// metadata: {
|
||||
// manifest: "json"
|
||||
// }
|
||||
// });
|
||||
|
||||
/* eslint no-undef:0 */
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
@@ -1,170 +0,0 @@
|
||||
{
|
||||
"_version": "1.8.0",
|
||||
"sap.app": {
|
||||
"id": "orders",
|
||||
"type": "application",
|
||||
"title": "Manage Orders",
|
||||
"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": {}
|
||||
}
|
||||
},
|
||||
"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": "OrdersList",
|
||||
"target": "OrdersList"
|
||||
},
|
||||
{
|
||||
"pattern": "Orders({key}):?query:",
|
||||
"name": "OrdersDetails",
|
||||
"target": "OrdersDetails"
|
||||
},
|
||||
{
|
||||
"pattern": "Orders({boo})/Items({boo2}):?query:",
|
||||
"name": "OrderItemsDetails",
|
||||
"target": "OrderItemsDetails"
|
||||
},
|
||||
{
|
||||
"pattern": "Books({key}):?query:",
|
||||
"name": "BooksDetails",
|
||||
"target": "BooksDetails"
|
||||
}
|
||||
],
|
||||
"targets": {
|
||||
"OrdersList": {
|
||||
"type": "Component",
|
||||
"id": "OrdersList",
|
||||
"name": "sap.fe.templates.ListReport",
|
||||
"options": {
|
||||
"settings" : {
|
||||
"entitySet" : "Orders",
|
||||
"navigation" : {
|
||||
"Orders" : {
|
||||
"detail" : {
|
||||
"route" : "OrdersDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"OrdersDetails": {
|
||||
"type": "Component",
|
||||
"id": "OrdersDetails",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings" : {
|
||||
"entitySet": "Orders",
|
||||
"navigation" : {
|
||||
"Items": {
|
||||
"detail": {
|
||||
"route": "OrderItemsDetails"
|
||||
}
|
||||
},
|
||||
"book": {
|
||||
"detail": {
|
||||
"route": "BooksDetails"
|
||||
}
|
||||
},
|
||||
"dummy": {
|
||||
"detail": {
|
||||
"route": "BooksDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"OrderItemsDetails": {
|
||||
"type": "Component",
|
||||
"id": "OrderItemsDetails",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings" : {
|
||||
"entitySet": "OrderItems"
|
||||
}
|
||||
}
|
||||
},
|
||||
"BooksDetails": {
|
||||
"type": "Component",
|
||||
"id": "BooksDetails",
|
||||
"name": "sap.fe.templates.ObjectPage",
|
||||
"options": {
|
||||
"settings" : {
|
||||
"entitySet": "Books",
|
||||
"navigation": {
|
||||
"author": {
|
||||
"detail": {
|
||||
"route": "AuthorsDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AuthorsDetails": {
|
||||
"type": "Component",
|
||||
"id": "AuthorsDetails",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
ID;title;descr;author_ID;stock;price;currency_code
|
||||
201;Wuthering Heights;"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.";101;12;11.11;GBP
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
@@ -1,4 +0,0 @@
|
||||
ID;locale;title;descr
|
||||
201;de;Sturmhöhe;Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts.
|
||||
207;de;Jane Eyre;Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte
|
||||
252;de;Eleonora;“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit.
|
||||
|
@@ -1,4 +0,0 @@
|
||||
ID;amount;parent_ID;book_ID;netAmount
|
||||
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201;11.11
|
||||
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271;15
|
||||
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252;28
|
||||
|
@@ -1,3 +0,0 @@
|
||||
ID;modifiedAt;createdAt;createdBy;modifiedBy;OrderNo;currency_code
|
||||
7e2f2640-6866-4dcf-8f4d-3027aa831cad;;2019-01-31;john.doe@test.com;;1;EUR
|
||||
64e718c9-ff99-47f1-8ca3-950c850777d4;;2019-01-30;jane.doe@test.com;;2;EUR
|
||||
|
@@ -1,35 +0,0 @@
|
||||
namespace sap.capire.bookshop;
|
||||
using { Currency, managed, cuid } from '@sap/cds/common';
|
||||
|
||||
entity Books : managed {
|
||||
key ID : Integer;
|
||||
title : localized String(111);
|
||||
descr : localized String(1111);
|
||||
author : Association to Authors;
|
||||
stock : Integer;
|
||||
price : Decimal(9,2);
|
||||
currency : Currency;
|
||||
}
|
||||
|
||||
entity Authors : managed {
|
||||
key ID : Integer;
|
||||
name : String(111);
|
||||
dateOfBirth : Date;
|
||||
dateOfDeath : Date;
|
||||
placeOfBirth : String;
|
||||
placeOfDeath : String;
|
||||
books : Association to many Books on books.author = $self;
|
||||
}
|
||||
|
||||
entity Orders : cuid, managed {
|
||||
OrderNo : String @title:'Order Number'; //> readable key
|
||||
Items : Composition of many OrderItems on Items.parent = $self;
|
||||
total : Decimal(9,2) @readonly;
|
||||
currency : Currency;
|
||||
}
|
||||
entity OrderItems : cuid {
|
||||
parent : Association to Orders;
|
||||
book : Association to Books;
|
||||
amount : Integer;
|
||||
netAmount : Decimal(9,2);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-bookshop",
|
||||
"version": "1.0.0",
|
||||
"description": "A simple bookshop application, build in a self-contained all-in-one fashion, i.e. w/o reusing other packages.",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest",
|
||||
"@sap/xb-msg-amqp-v100": "^0.9.31-SNAPSHOT",
|
||||
"express": "*",
|
||||
"passport": "^0.4.0",
|
||||
"sqlite3": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cds run --in-memory?",
|
||||
"watch": "cds watch"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"API_BUSINESS_PARTNER": {
|
||||
"kind": "odata",
|
||||
"model": "srv/external",
|
||||
"--credentials": {
|
||||
"destination": "cap-api098"
|
||||
}
|
||||
},
|
||||
"--messaging": {
|
||||
"kind": "enterprise-messaging",
|
||||
"credentials": {
|
||||
"prefix": "sap/S4HANAOD/c098/BO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"passport": {
|
||||
"strategy": "mock",
|
||||
"users": {
|
||||
"alice": {
|
||||
"roles": [
|
||||
"admin"
|
||||
],
|
||||
"ID": "ALICE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
PATCH http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ALICE',AddressID='62640')
|
||||
Content-Type: application/json
|
||||
Authorization: Basic QUxJQ0Utc2VjcmV0
|
||||
|
||||
{
|
||||
"PostalCode": "123456",
|
||||
"CityName": "AlteredTown"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:4004/admin/Orders(ID=7e2f2640-6866-4dcf-8f4d-3027aa831cad,IsActiveEntity=false)?
|
||||
&$expand=shippingAddress($select=cityName,houseNumber,postalCode,streetName)
|
||||
Authorization: Basic QUxJQ0U6c2VjcmV0
|
||||
|
||||
|
||||
###
|
||||
|
||||
DELETE http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ALICE',AddressID='62640')
|
||||
@@ -1,16 +0,0 @@
|
||||
using { sap.capire.bookshop as my } from '../db/schema';
|
||||
|
||||
service AdminService @(requires:'admin') {
|
||||
entity Books as projection on my.Books;
|
||||
entity Authors as projection on my.Authors;
|
||||
entity Orders as select from my.Orders;
|
||||
}
|
||||
|
||||
// Enable Fiori Draft for Orders
|
||||
annotate AdminService.Orders with @odata.draft.enabled;
|
||||
// annotate AdminService.Books with @odata.draft.enabled;
|
||||
|
||||
// Temporary workaround -> https://github.wdf.sap.corp/cap/issues/issues/3121
|
||||
extend service AdminService with {
|
||||
entity OrderItems as select from my.OrderItems;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
const cds = require('@sap/cds')
|
||||
module.exports = cds.service.impl(async () => {
|
||||
// We are mashing up three services...
|
||||
const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
|
||||
const admin = await cds.connect.to('AdminService')
|
||||
const db = await cds.connect.to('db')
|
||||
|
||||
// Using reflected definitions from connected services/database
|
||||
const { Addresses: externalAddresses } = bupa.entities // projection on external addresses
|
||||
const { Books, Addresses } = db.entities('sap.capire.bookshop') // entities in local database
|
||||
|
||||
// Delegate ValueHelp requests to S/4 backend, fetching current user's addresses from there
|
||||
admin.on('READ', 'Addresses', req => {
|
||||
console.log('Delegating to S/4 bupa service...')
|
||||
const UsersAddresses = SELECT.from(externalAddresses)
|
||||
.where({ contact: req.user.id })
|
||||
.and(req.query.SELECT.where)
|
||||
return bupa.tx(req).run(UsersAddresses)
|
||||
})
|
||||
|
||||
// Replicate chosen addresses from S/4 when filling orders.
|
||||
admin.before('PATCH', 'Orders', async req => {
|
||||
const assigned = { ID: req.data.shippingAddress_ID, contact: req.user.id }
|
||||
if (!assigned.ID) return //> something else
|
||||
const local = db.transaction(req)
|
||||
const [replica] = await local.read(Addresses).where(assigned)
|
||||
if (replica) return //> already replicated
|
||||
const [address] = await bupa.tx(req).run(SELECT.from(externalAddresses).where(assigned))
|
||||
if (address) return local.create(Addresses).entries(address)
|
||||
})
|
||||
|
||||
// Subscribe to S/4 event to update local replicas when sources change in S/4.
|
||||
bupa.on('BusinessPartner/Changed', async msg => {
|
||||
console.log('>> received:', msg.data)
|
||||
|
||||
const BuPaID = msg.data.KEY[0].BUSINESSPARTNER //> S/4HANA's weird payload format
|
||||
const { SELECT, UPDATE } = cds.ql(msg) //> convenient alternative to <srv>.transaction(req).run(SELECT...)
|
||||
|
||||
// fetch affected entries from local replicas
|
||||
const replicas = await SELECT.from(Addresses).where({ contact: BuPaID })
|
||||
if (replicas.length === 0) return //> not affected
|
||||
|
||||
// fetch changed data from S/4 -> might be less than local due to deletes
|
||||
const changed = await SELECT.from(externalAddresses).where({
|
||||
contact: BuPaID,
|
||||
ID: replicas.map(({ ID }) => ID)
|
||||
})
|
||||
|
||||
// update local replicas with changes from S/4
|
||||
const local = db.transaction(msg) //> using that variant to benefit from bulk runs
|
||||
return local.run(changed.map(a => UPDATE(Addresses, a.ID).with(a)))
|
||||
})
|
||||
|
||||
// Validate incoming orders and reduce books' stocks.
|
||||
admin.before('CREATE', 'Orders', async req => {
|
||||
const { Items } = req.data
|
||||
|
||||
// validate input...
|
||||
if (!Items || Items.length === 0) return req.reject('Please order at least one item.')
|
||||
if (!req.data.shippingAddress_ID) return req.reject('Please enter a valid shipping address.', 'shippingAddress_ID')
|
||||
|
||||
// reduce stock on ordered books...
|
||||
const all = await db.tx(req).run(
|
||||
Items.map(each =>
|
||||
UPDATE(Books)
|
||||
.where('ID =', each.book_ID)
|
||||
.and('stock >=', each.amount)
|
||||
.set('stock -=', each.amount)
|
||||
)
|
||||
)
|
||||
all.forEach(
|
||||
(affectedRows, i) =>
|
||||
affectedRows > 0 || req.error(409, `${Items[i].amount} exceeds stock for book #${Items[i].book_ID}`)
|
||||
)
|
||||
})
|
||||
})
|
||||
require('./utils')
|
||||
@@ -1,13 +0,0 @@
|
||||
using { sap.capire.bookshop as my } from '../db/schema';
|
||||
|
||||
@path:'/browse'
|
||||
service CatalogService {
|
||||
|
||||
@readonly entity Books as SELECT from my.Books {*,
|
||||
author.name as author
|
||||
} excluding { createdBy, modifiedBy };
|
||||
|
||||
@requires_: 'authenticated-user'
|
||||
@insertonly entity Orders as projection on my.Orders;
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
const cds = require('@sap/cds')
|
||||
const { Books } = cds.entities
|
||||
|
||||
/** Service implementation for CatalogService */
|
||||
module.exports = cds.service.impl(function () {
|
||||
this.after('READ', 'Books', each => each.stock > 111 && _addDiscount2(each, 11))
|
||||
this.before('CREATE', 'Orders', _reduceStock)
|
||||
})
|
||||
|
||||
/** Add some discount for overstocked books */
|
||||
function _addDiscount2 (each, discount) {
|
||||
each.title += ` -- ${discount}% discount!`
|
||||
}
|
||||
|
||||
/** Reduce stock of ordered books if available stock suffices */
|
||||
async function _reduceStock (req) {
|
||||
const { Items: OrderItems } = req.data
|
||||
const all = await cds.transaction(req).run(() =>
|
||||
OrderItems.map(order =>
|
||||
UPDATE(Books)
|
||||
.set('stock -=', order.amount)
|
||||
.where('ID =', order.book_ID)
|
||||
.and('stock >=', order.amount)
|
||||
)
|
||||
)
|
||||
all.forEach((affectedRows, i) => {
|
||||
if (affectedRows === 0) req.error(409, `${OrderItems[i].amount} exceeds stock for book #${OrderItems[i].book_ID}`)
|
||||
})
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using { API_BUSINESS_PARTNER as external } from './external/API_BUSINESS_PARTNER.csn';
|
||||
|
||||
/**
|
||||
* Tailor the imported API to our needs...
|
||||
*/
|
||||
extend service external with {
|
||||
|
||||
/**
|
||||
* Simplified view on external addresses
|
||||
*/
|
||||
// @cds.persistence.skip: false
|
||||
@mashup entity Addresses as projection on external.A_BusinessPartnerAddress {
|
||||
key AddressID as ID,
|
||||
key BusinessPartner as contact,
|
||||
Country as country,
|
||||
CityName as cityName,
|
||||
PostalCode as postalCode,
|
||||
StreetName as streetName,
|
||||
HouseNumber as houseNumber
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an entity to replicate external address data for quick access,
|
||||
* e.g. when displaying lists of orders.
|
||||
*/
|
||||
@cds.persistence:{table,skip:false} //> create a table with the view's inferred signature
|
||||
@cds.autoexpose //> auto-expose in services as targets for ValueHelps and joins
|
||||
entity sap.capire.bookshop.Addresses as projection on external.Addresses;
|
||||
|
||||
|
||||
/**
|
||||
* Extend Orders with references to replicated external Addresses
|
||||
*/
|
||||
using { sap.capire.bookshop } from '../db/schema';
|
||||
extend bookshop.Orders with {
|
||||
shippingAddress : Association to bookshop.Addresses;
|
||||
}
|
||||
2426
packages/bookshop/srv/external/API_BUSINESS_PARTNER.csn
vendored
2426
packages/bookshop/srv/external/API_BUSINESS_PARTNER.csn
vendored
File diff suppressed because it is too large
Load Diff
3261
packages/bookshop/srv/external/API_BUSINESS_PARTNER.edmx
vendored
3261
packages/bookshop/srv/external/API_BUSINESS_PARTNER.edmx
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
module.exports = srv => {
|
||||
srv.on(['CREATE', 'UPDATE', 'DELETE'], req => {
|
||||
|
||||
const payload = {
|
||||
KEY: [{ BUSINESSPARTNER: req.data.BusinessPartner }]
|
||||
}
|
||||
console.log('<< emitting:', payload)
|
||||
srv.emit('BusinessPartner/Changed', payload)
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
BusinessPartner;AddressID;CityName;PostalCode;Country;StreetName;HouseNumber
|
||||
ALICE;62640;Walldorf;69190;GER;Dietmer-Hopp-Allee;16
|
||||
ALICE;62641;Berlin;69390;GER;Berlin-Street;19
|
||||
BOB;62341;Karlsruhe;61390;GER;Karlsruhe-Street;19
|
||||
anonymous;61321;Sometown;61290;GER;Sometown-Street;19
|
||||
|
@@ -1,27 +0,0 @@
|
||||
// Hack for SAP Application Studio
|
||||
process.env['http_proxy'] = ''
|
||||
process.env['https_proxy'] = ''
|
||||
process.env['HTTP_PROXY'] = ''
|
||||
process.env['HTTPS_PROXY'] = ''
|
||||
|
||||
const diff = (obj1, obj2) =>
|
||||
Object.keys(obj1).reduce((res, curr) => (obj1[curr] === obj2[curr] ? res : (res[curr] = obj2[curr]) && res), {})
|
||||
|
||||
const queriesToUpdateDifferences = (entity, ownEntries, otherEntries) =>
|
||||
ownEntries
|
||||
.map(ownEntry => {
|
||||
const otherEntry = otherEntries.find(otherEntry =>
|
||||
Object.keys(entity.keys).reduce((res, curr) => res && otherEntry[curr] === ownEntry[curr], true)
|
||||
)
|
||||
if (otherEntry) {
|
||||
const differences = diff(ownEntry, otherEntry)
|
||||
if (Object.keys(differences).length) {
|
||||
return UPDATE(entity)
|
||||
.set(differences)
|
||||
.where(Object.keys(entity.keys).reduce((res, curr) => (res[curr] = ownEntry[curr]) && res, {}))
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter(el => el)
|
||||
|
||||
module.exports = { diff, queriesToUpdateDifferences }
|
||||
@@ -1,18 +0,0 @@
|
||||
### Service Document
|
||||
GET http://localhost:4004/browse
|
||||
|
||||
### Service $metadata document
|
||||
GET http://localhost:4004/browse/$metadata
|
||||
|
||||
### Browsing Books
|
||||
GET http://localhost:4004/browse/Books?
|
||||
# &$select=title,author
|
||||
# &$expand=currency
|
||||
# &sap-language=de
|
||||
|
||||
### Browsing Authors
|
||||
GET http://localhost:4004/admin/Authors?
|
||||
# &$select=name,dateOfBirth,placeOfBirth
|
||||
# &$expand=books($select=title;$expand=currency)
|
||||
# &$filter=ID eq 101
|
||||
# &sap-language=de
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
### List Books with their current stocks
|
||||
GET http://localhost:4004/admin/Books?$select=ID,stock
|
||||
|
||||
### List all Orders
|
||||
GET http://localhost:4004/admin/Orders?
|
||||
&$expand=Items
|
||||
|
||||
### Submit Orders
|
||||
POST http://localhost:4004/browse/Orders
|
||||
Content-Type: application/json
|
||||
|
||||
{ "OrderNo":"2019-09...", "Items":[
|
||||
{ "book_ID":201, "amount":5 },
|
||||
{ "book_ID":207, "amount":3 }
|
||||
]}
|
||||
|
||||
# Sending this three times should result in a 409: 5 exceeds stock for book #201
|
||||
6
packages/bookstore/.cdsrc.json
Normal file
6
packages/bookstore/.cdsrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"target": ".",
|
||||
"tasks": []
|
||||
}
|
||||
}
|
||||
15
packages/bookstore/.gitignore
vendored
Normal file
15
packages/bookstore/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.che/
|
||||
.gen/
|
||||
gen/
|
||||
mta_archives/
|
||||
node_modules/
|
||||
target/
|
||||
|
||||
.cds_gen.log
|
||||
connection.properties
|
||||
*.db
|
||||
.DS_Store
|
||||
*.orig
|
||||
_out
|
||||
default-*.json
|
||||
package-lock.json
|
||||
0
packages/bookshop/db/data/sap.capire.bookshop-Authors.csv → packages/bookstore/db/csv/sap.capire.bookstore-Authors.csv
Normal file → Executable file
0
packages/bookshop/db/data/sap.capire.bookshop-Authors.csv → packages/bookstore/db/csv/sap.capire.bookstore-Authors.csv
Normal file → Executable file
4
packages/bookstore/db/csv/sap.capire.bookstore-OrderItems.csv
Executable file
4
packages/bookstore/db/csv/sap.capire.bookstore-OrderItems.csv
Executable file
@@ -0,0 +1,4 @@
|
||||
ID;amount;parent_ID;book_ID;netAmount
|
||||
301;1;401;201;11.11
|
||||
302;1;401;271;15
|
||||
303;2;402;252;28
|
||||
|
3
packages/bookstore/db/csv/sap.capire.bookstore-Orders.csv
Executable file
3
packages/bookstore/db/csv/sap.capire.bookstore-Orders.csv
Executable file
@@ -0,0 +1,3 @@
|
||||
ID;modifiedAt;createdAt;createdBy;modifiedBy;OrderNo;currency_code
|
||||
401;;2019-01-31;john.doe@test.com;;1;EUR
|
||||
402;;2019-01-30;christian.georgi@sap.com;;2;EUR
|
||||
|
0
packages/bookstore/db/data/sap.capire.products-Categories.csv → packages/bookstore/db/csv/sap.capire.products-Categories.csv
Normal file → Executable file
0
packages/bookstore/db/data/sap.capire.products-Categories.csv → packages/bookstore/db/csv/sap.capire.products-Categories.csv
Normal file → Executable file
0
packages/bookstore/db/data/sap.capire.products-Products.csv → packages/bookstore/db/csv/sap.capire.products-Products.csv
Normal file → Executable file
0
packages/bookstore/db/data/sap.capire.products-Products.csv → packages/bookstore/db/csv/sap.capire.products-Products.csv
Normal file → Executable file
2
packages/bookshop/db/data/sap.common-Currencies.csv → packages/bookstore/db/csv/sap.common-Currencies.csv
Normal file → Executable file
2
packages/bookshop/db/data/sap.common-Currencies.csv → packages/bookstore/db/csv/sap.common-Currencies.csv
Normal file → Executable file
@@ -3,5 +3,5 @@ EUR;€;Euro;European Euro
|
||||
USD;$;US Dollar;United States Dollar
|
||||
CAD;$;Canadian Dollar;Canadian Dollar
|
||||
AUD;$;Australian Dollar;Australian Dollar
|
||||
GBP;£;Pound;Great Britain Pound
|
||||
GBP;£;Pound;Great Britian Pound
|
||||
ILS;₪;Shekel;Israeli New Shekel
|
||||
|
0
packages/bookshop/db/data/sap.common-Currencies_texts.csv → packages/bookstore/db/csv/sap.common-Currencies_texts.csv
Normal file → Executable file
0
packages/bookshop/db/data/sap.common-Currencies_texts.csv → packages/bookstore/db/csv/sap.common-Currencies_texts.csv
Normal file → Executable file
@@ -1,5 +0,0 @@
|
||||
ID;firstname;lastname;dateOfBirth;placeOfBirth;dateOfDeath;placeOfDeath
|
||||
101;Emily;Brontë;1818-07-30;Thornton, Yorkshire;1848-12-19;Haworth, Yorkshire
|
||||
107;Charlotte;Brontë;1818-04-21;Thornton, Yorkshire;1855-03-31;Haworth, Yorkshire
|
||||
150;Edgar Allen;Poe;1809-01-19;Boston, Massachusetts;1849-10-07;Baltimore, Maryland
|
||||
170;Richard;Carpenter;1929-08-14;King’s Lynn, Norfolk;2012-02-26;Hertfordshire, England
|
||||
|
@@ -2,17 +2,34 @@ namespace sap.capire.bookstore;
|
||||
|
||||
// We reuse Products, which are Books in our domain
|
||||
using { sap.capire.products.Products as Books } from '@sap/capire-products';
|
||||
using { Currency, managed } from '@sap/cds/common';
|
||||
|
||||
extend Books with {
|
||||
author : Association to Authors;
|
||||
rating : Decimal(2,1);
|
||||
}
|
||||
|
||||
// We reuse aspect Person to define Authors
|
||||
using { sap.capire.contacts.Person } from '@sap/capire-contacts';
|
||||
entity Authors : Person {
|
||||
key ID : UUID;
|
||||
entity Authors : managed {
|
||||
key ID : Integer;
|
||||
name : String(111);
|
||||
dateOfBirth : Date;
|
||||
dateOfDeath : Date;
|
||||
placeOfBirth : String;
|
||||
placeOfDeath : String;
|
||||
books : Association to many Books on books.author = $self;
|
||||
}
|
||||
|
||||
// we use enhanced currencies code lists
|
||||
using from '@sap/capire-currencies';
|
||||
entity Orders : managed {
|
||||
key ID : Integer;
|
||||
OrderNo : String @title:'Order Number'; //> readable key
|
||||
Items : Composition of many OrderItems on Items.parent = $self;
|
||||
total : Decimal(9,2) @readonly;
|
||||
currency : Currency;
|
||||
}
|
||||
|
||||
entity OrderItems {
|
||||
key ID : Integer;
|
||||
parent : Association to Orders;
|
||||
book : Association to Books;
|
||||
amount : Integer;
|
||||
netAmount : Decimal(9,2);
|
||||
}
|
||||
@@ -1,48 +1,20 @@
|
||||
{
|
||||
"name": "@sap/capire-bookstore",
|
||||
"version": "1.0.0",
|
||||
"description": "A variant of the bookshop application, built on top of products-service and common reuse packages.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/capire-products": "^1.0.0",
|
||||
"@sap/capire-reviews": "^1.0.0",
|
||||
"@sap/capire-orders": "^1.0.0",
|
||||
"@sap/capire-media": "^1.0.0",
|
||||
"@sap/capire-users": "^1.0.0",
|
||||
"@sap/capire-contacts": "^1.0.0",
|
||||
"@sap/capire-currencies": "^1.0.0",
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"@sap/capire-products",
|
||||
"@sap/capire-reviews",
|
||||
"@sap/capire-orders",
|
||||
"@sap/capire-media",
|
||||
"@sap/capire-users",
|
||||
"@sap/capire-contacts",
|
||||
"@sap/capire-currencies"
|
||||
],
|
||||
"files": [
|
||||
"app", "srv", "db"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "cds run --in-memory?",
|
||||
"watch": "cds watch"
|
||||
},
|
||||
"cds": {
|
||||
"requires": {
|
||||
"db": {
|
||||
"kind": "sql"
|
||||
},
|
||||
"sap.capire.media.MediaServer": {
|
||||
"kind": "rest"
|
||||
},
|
||||
"sap.capire.reviews.ReviewsService": {
|
||||
"model": "@sap/capire-reviews",
|
||||
"kind": "odata"
|
||||
}
|
||||
"name": "bookstore",
|
||||
"description": "Generated by cds init",
|
||||
"repository": "<Add your repository here>",
|
||||
"license": "ISC",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": "^8.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sap/capire-products": "^1.0.0",
|
||||
"@sap/cds": "^3.17.4",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cds build/all --clean",
|
||||
"deploy": "cds deploy",
|
||||
"start": "cds run"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//----------------------
|
||||
// workarounds -> should be done by @cds-compiler
|
||||
|
||||
annotate cds.UUID with @odata.Type: 'Edm.String';
|
||||
|
||||
using from '@sap/capire-products';
|
||||
annotate sap.capire.products.Products with { texts @odata.contained }
|
||||
annotate sap.capire.products.Categories with { texts @odata.contained }
|
||||
|
||||
annotate sap.capire.reviews.ReviewsService with @imported;
|
||||
annotate sap.capire.reviews.Reviews with @cds.persistence.skip;
|
||||
annotate sap.capire.reviews.Likes with @cds.persistence.skip;
|
||||
@@ -1,35 +1,17 @@
|
||||
namespace sap.capire.bookstore;
|
||||
|
||||
// Service for all users to browse books
|
||||
using { sap.capire.products } from '../db/schema';
|
||||
using { sap.capire.products.Products, sap.capire.bookstore as my } from '../db/schema';
|
||||
|
||||
service CatalogService @(path:'browse'){
|
||||
|
||||
@readonly entity Books as select from products.Products { *,
|
||||
author.firstname ||' '|| author.lastname as author : String,
|
||||
category.name as genre,
|
||||
service CatalogService {
|
||||
@readonly entity Books as projection on Products {
|
||||
*, category.name as category, author.name
|
||||
} excluding { createdBy, modifiedBy };
|
||||
|
||||
@readonly entity Genres as projection on products.Categories;
|
||||
|
||||
@insertonly entity Orders as projection on my.Orders;
|
||||
}
|
||||
|
||||
// Reuse AdminService from @sap/capire-products...
|
||||
// Reuse services from @sap/capire-products...
|
||||
using { sap.capire.products.AdminService } from '@sap/capire-products';
|
||||
using { sap.capire.bookstore as my } from '../db/schema';
|
||||
|
||||
extend service AdminService with @(impl:'srv/services.js') {
|
||||
extend service AdminService with {
|
||||
entity Authors as projection on my.Authors;
|
||||
}
|
||||
|
||||
// Adding reviews via @sap/capire-reviews service
|
||||
using { sap.capire.reviews.ReviewsService as external } from '@sap/capire-reviews';
|
||||
extend service CatalogService with {
|
||||
@readonly entity Reviews as projection on external.Reviews;
|
||||
}
|
||||
|
||||
|
||||
// Adding images via @sap/capire-media service
|
||||
using from '@sap/capire-media';
|
||||
// using from '@sap/capire-orders';
|
||||
// using from '@sap/capire-users';
|
||||
}
|
||||
@@ -1,38 +1,18 @@
|
||||
const cds = require('@sap/cds')
|
||||
module.exports['sap.capire.bookstore.CatalogService'] = cds.service.impl (async (srv) => {
|
||||
module.exports = async (srv) => {
|
||||
|
||||
const ReviewsService = await cds.connect.to ('sap.capire.reviews.ReviewsService')
|
||||
const { Reviews } = ReviewsService.entities
|
||||
const { Books } = srv.entities
|
||||
|
||||
// delegate requests to reviews service
|
||||
srv.on('READ', 'Reviews', async (req) => {
|
||||
const { SELECT } = cds.ql(req)
|
||||
const results = await SELECT.from (Reviews)
|
||||
|
||||
// TODO: Should actually be using .where of fluent query API
|
||||
if (req.query.SELECT.where) {
|
||||
return results.filter (row => row.subject === req.query.SELECT.where[2].val)
|
||||
}
|
||||
|
||||
return results
|
||||
})
|
||||
|
||||
// react on event messages from reviews service
|
||||
ReviewsService.on ('reviewed', (msg) => {
|
||||
console.debug ('> received message:', msg.event, msg.data)
|
||||
const {subject,rating} = msg.data
|
||||
const tx = cds // cds.transaction(msg) // TODO: how to add multi-tenancy?
|
||||
return tx.run (UPDATE(Books).set({rating}) .where ({ID:subject})) //.then (console.log)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
// FIXME: pls remove this...
|
||||
process.env.destinations = JSON.stringify([{
|
||||
name: 'reviewsDest',
|
||||
url: 'http://localhost:4005/reviews',
|
||||
username: 'dummy',
|
||||
password: 'dummy'
|
||||
}])
|
||||
const { Books } = srv.entities
|
||||
// Check all amounts against stock before activating
|
||||
srv.before(['CREATE', 'UPDATE'], 'Orders', (req) => {
|
||||
const tx = cds.transaction(req), order = req.data
|
||||
return Promise.all(order.Items.map(each => tx.run(
|
||||
UPDATE(Books).where({ ID: each.book_ID })
|
||||
.and(`stock >=`, each.amount)
|
||||
.set(`stock -=`, each.amount)
|
||||
).then(affectedRows => {
|
||||
if (!affectedRows) {
|
||||
req.error(409, `${each.amount} exceeds stock for book #${each.book_ID}`)
|
||||
}
|
||||
})))
|
||||
})
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using { sap.capire.products as my } from '../db/schema';
|
||||
|
||||
service BooksService {
|
||||
entity Books as SELECT from my.Products;
|
||||
}
|
||||
|
||||
annotate cds.UUID with @odata.Type: 'Edm.String';
|
||||
@@ -1,190 +0,0 @@
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
describe('Localized data on db level', ()=>{
|
||||
|
||||
let db, Books
|
||||
|
||||
it ('should deploy the db schema to sqlite in-memory', async()=>{
|
||||
db = await cds.deploy (__dirname+'/books') .to ('sqlite::memory:')
|
||||
expect (db.model) .toBeDefined()
|
||||
Books = db.entities('sap.capire.products').Products
|
||||
expect (Books) .toBeDefined()
|
||||
})
|
||||
|
||||
it ('should list all books with default language', async ()=>{
|
||||
const books = await SELECT.from (Books, b=>b.title)
|
||||
expect (books) .toMatchObject([
|
||||
{ title: 'Wuthering Heights' },
|
||||
{ title: 'Jane Eyre' },
|
||||
{ title: 'The Raven' },
|
||||
{ title: 'Eleonora' },
|
||||
{ title: 'Catweazle' }
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
it ('should read translated texts from Books_texts', async ()=>{
|
||||
const texts = await SELECT ('locale','title').from (Books+'_texts')
|
||||
expect (texts) .toMatchObject ([
|
||||
{ locale: 'de', title: 'Sturmhöhe' },
|
||||
{ locale: 'de', title: 'Jane Eyre' },
|
||||
{ locale: 'de', title: 'Eleonora' }
|
||||
])
|
||||
})
|
||||
|
||||
it ('should read translated texts from Books.texts', async ()=>{
|
||||
const book = await SELECT.one.from (Books, b=>{
|
||||
b.ID, b.title, b.texts(t=> {
|
||||
t.locale, t.title
|
||||
})
|
||||
}) .where ({title:'Wuthering Heights'})
|
||||
expect (book) .toMatchObject ({
|
||||
title: 'Wuthering Heights', texts:[
|
||||
{locale:'de',title:'Sturmhöhe'}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it ('should insert books with translated texts', async ()=>{
|
||||
const n = await INSERT.into (Books) .entries ({ ID:444, title:'A New Book', texts:[
|
||||
{locale:'de', title:'Ein Neues Buch'},
|
||||
{locale:'fr', title:'Un Nouveau Livre'},
|
||||
]})
|
||||
expect(n).toBe(3)
|
||||
})
|
||||
|
||||
it ('should delete books w/ cascaded delete to texts', async()=>{
|
||||
const n = await DELETE.from(Books) .where ({ID:444})
|
||||
expect(n).toBe(3)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
describe('Localized data on service level', ()=>{
|
||||
|
||||
let srv, Books
|
||||
|
||||
it ('should serve BooksService', async()=>{
|
||||
srv = await cds.serve('BooksService').from(__dirname+'/books')
|
||||
expect (srv.model) .toBeDefined()
|
||||
Books = srv.entities.Books
|
||||
expect (Books) .toBeDefined()
|
||||
})
|
||||
|
||||
it ('should list all books with default language', async ()=>{
|
||||
const books = await srv.read (Books, b=>b.title)
|
||||
expect (books) .toMatchObject([
|
||||
{ title: 'Wuthering Heights' },
|
||||
{ title: 'Jane Eyre' },
|
||||
{ title: 'The Raven' },
|
||||
{ title: 'Eleonora' },
|
||||
{ title: 'Catweazle' }
|
||||
])
|
||||
})
|
||||
|
||||
it ('should read Books with translated texts', async ()=>{
|
||||
const book = await srv.run (
|
||||
SELECT.from (Books, b=>{ b.ID, b.title, b.texts(t=> {
|
||||
t.locale, t.title
|
||||
})}) .where ({title:'Wuthering Heights'})
|
||||
)
|
||||
expect (book) .toMatchObject ([{
|
||||
title: 'Wuthering Heights', texts:[
|
||||
{locale:'de',title:'Sturmhöhe'}
|
||||
]
|
||||
}])
|
||||
})
|
||||
|
||||
it ('should do the same with convenient method', async ()=>{
|
||||
const book = await srv.read (Books, b=>{ b.ID, b.title, b.texts(t=> {
|
||||
t.locale, t.title
|
||||
})}) .where ({title:'Wuthering Heights'})
|
||||
expect (book) .toMatchObject ([{
|
||||
title: 'Wuthering Heights', texts:[
|
||||
{locale:'de',title:'Sturmhöhe'}
|
||||
]
|
||||
}])
|
||||
})
|
||||
|
||||
it ('should read single Book with translated texts', async ()=>{
|
||||
const book = await srv.run (
|
||||
SELECT.one.from (Books, b=>{ b.ID, b.title, b.texts(t=> {
|
||||
t.locale, t.title
|
||||
})}) .where ({title:'Wuthering Heights'})
|
||||
)
|
||||
expect (book) .toMatchObject ({
|
||||
title: 'Wuthering Heights', texts:[
|
||||
{locale:'de',title:'Sturmhöhe'}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it ('should insert books with translated texts', async ()=>{
|
||||
const book = { ID:444, title:'A New Book', texts:[
|
||||
{locale:'de', title:'Ein Neues Buch'},
|
||||
{locale:'fr', title:'Un Nouveau Livre'},
|
||||
]}
|
||||
const response = await srv.create (Books) .entries (book)
|
||||
expect(response).toMatchObject(book)
|
||||
})
|
||||
|
||||
it ('should delete books w/ cascaded delete to texts', async()=>{
|
||||
await srv.delete('Books') .where ({ID:444})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('Localized data on OData level', () => {
|
||||
|
||||
const app = require('express')()
|
||||
const srv = require('supertest')(app)
|
||||
|
||||
it ('should serve BooksService', async ()=>{
|
||||
await cds.serve('BooksService').from(__dirname+'/books') .in (app)
|
||||
})
|
||||
|
||||
it('should list all books with default language', async () => {
|
||||
const books = await srv.get('/books/Books/201/title')
|
||||
expect(books.body).toMatchObject({'value': 'Wuthering Heights'})
|
||||
})
|
||||
|
||||
it('should read books with translated texts', async () => {
|
||||
const books = await srv.get('/books/Books/201/title'). set('Accept-Language', 'de')
|
||||
expect(books.body).toMatchObject({value: 'Sturmhöhe'})
|
||||
})
|
||||
|
||||
it('should expand translated texts in Book', async () => {
|
||||
const books = await srv. get('/books/Books/201?$select=title&$expand=texts($select=locale,title)')
|
||||
expect(books.body).toMatchObject({
|
||||
title: 'Wuthering Heights',
|
||||
texts: [
|
||||
{ locale: 'de', title: 'Sturmhöhe', },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
const book = {
|
||||
title: 'New Book', descr: 'Lorem Ipsum',
|
||||
texts: [
|
||||
{ locale: 'de', title: 'Neues Buch', descr: 'Dolor sit amet' },
|
||||
{ locale: 'fr', title: 'Nouveau Livre', descr: 'consetetur sadipscing elitr' }
|
||||
],
|
||||
}
|
||||
|
||||
it('should insert books with translated texts', async () => {
|
||||
const {body} = await srv.post('/books/Books').send(book)
|
||||
expect(body).toMatchObject(book)
|
||||
book.ID = body.ID
|
||||
})
|
||||
|
||||
it ('should read the newly created book', async()=>{
|
||||
const {body} = await srv.get('/books/Books/'+book.ID+'?$expand=texts').send(book)
|
||||
expect(body).toMatchObject(book)
|
||||
})
|
||||
|
||||
it ('should delete books w/ cascaded delete to texts', async()=>{
|
||||
await srv.delete('/books/Books/'+book.ID)
|
||||
.expect(204)
|
||||
})
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
using { sap.capire.contacts.PostalAddress } from './schema';
|
||||
using { sap } from '@sap/cds/common';
|
||||
namespace sap.capire.contacts;
|
||||
|
||||
/**
|
||||
* The Code Lists below are designed as optional extensions to
|
||||
* the base schema. Switch them on by adding an Association to
|
||||
* one of the code list entities in your models or by:
|
||||
* annotate sap.common.Countries with @cds.persistence.skip:false;
|
||||
*/
|
||||
|
||||
entity Countries as select from sap.common.Countries;
|
||||
extend sap.common.Countries {
|
||||
regions : Composition of many Regions on regions._parent = $self.code;
|
||||
}
|
||||
entity Regions : sap.common.CodeList {
|
||||
key code : String(5); // ISO 3166-2 alpha5 codes, e.g. DE-BW
|
||||
children : Composition of many Regions on children._parent = $self.code;
|
||||
cities : Composition of many Cities on cities.region = $self;
|
||||
_parent : String(11);
|
||||
}
|
||||
entity Cities : sap.common.CodeList {
|
||||
key code : String(11);
|
||||
region : Association to Regions;
|
||||
districts : Composition of many Districts on districts.city = $self;
|
||||
}
|
||||
entity Districts : sap.common.CodeList {
|
||||
key code : String(11);
|
||||
city : Association to Cities;
|
||||
}
|
||||
|
||||
annotate PostalAddress with {
|
||||
district @ref: sap.capire.contacts.Districts;
|
||||
city @ref: sap.capire.contacts.Cities;
|
||||
region @ref: sap.capire.contacts.Regions;
|
||||
country @ref: sap.capire.contacts.Countries;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
code;name;descr
|
||||
AU;Australia;Commonwealth of Australia
|
||||
CA;Canada;Canada
|
||||
CN;China;People's Republic of China (PRC)
|
||||
FR;France;French Republic
|
||||
DE;Germany;Federal Republic of Germany
|
||||
IN;India;Republic of India
|
||||
IL;Israel;State of Israel
|
||||
MM;Myanmar;Republic of the Union of Myanmar
|
||||
GB;United Kingdom;United Kingdom of Great Britain and Northern Ireland
|
||||
US;United States;United States of America (USA)
|
||||
EU;European Union;European Union
|
||||
|
@@ -1,12 +0,0 @@
|
||||
code;locale;name;descr
|
||||
AU;de;Australien;Commonwealth Australien
|
||||
CA;de;Kanada;Canada
|
||||
CN;de;China;Volksrepublik China
|
||||
FR;de;Frankreich;Republik Frankreich
|
||||
DE;de;Deutschland;Bundesrepublik Deutschland
|
||||
IN;de;Indien;Republik Indien
|
||||
IL;de;Israel;Staat Israel
|
||||
MM;de;Myanmar;Republik der Union Myanmar
|
||||
GB;de;Vereinigtes Königreich;Vereinigtes Königreich Großbritannien und Nordirland
|
||||
US;de;Vereinigte Staaten;Vereinigte Staaten von Amerika
|
||||
EU;de;Europäische Union;Europäische Union
|
||||
|
@@ -1,58 +0,0 @@
|
||||
namespace sap.capire.contacts;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Aspects
|
||||
|
||||
|
||||
aspect Organization {
|
||||
orgname : String(111);
|
||||
}
|
||||
|
||||
aspect Person {
|
||||
firstname : String(111);
|
||||
lastname : String(111);
|
||||
prefix : String(11);
|
||||
suffix : String(11);
|
||||
middle : String(11);
|
||||
dateOfBirth : Date; placeOfBirth : String;
|
||||
dateOfDeath : Date; placeOfDeath : String;
|
||||
}
|
||||
|
||||
aspect PostalAddress {
|
||||
street : String(222) @multiline;
|
||||
postCode : String(11);
|
||||
district : String(111);
|
||||
city : String(111);
|
||||
region : String(111);
|
||||
country : String(111);
|
||||
}
|
||||
|
||||
aspect ContactOptions {
|
||||
email : String @JSON:[{ kind:String, address: EmailAddress }];
|
||||
phone : String @JSON:[{ kind:String, number: PhoneNumber }];
|
||||
// phone : array of { kind:String; number: PhoneNumber };
|
||||
// addresses : Composition of many PostalAddress;
|
||||
}
|
||||
|
||||
type EmailAddress : String;
|
||||
type PhoneNumber : String;
|
||||
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Entities
|
||||
|
||||
@cds.persistence.skip:'if-unused'
|
||||
entity Contacts : Person, Organization, ContactOptions {
|
||||
key ID : UUID;
|
||||
isOrg : Boolean;
|
||||
addresses : Composition of many PostalAddresses on addresses.contact = $self;
|
||||
}
|
||||
|
||||
@cds.persistence.skip:'if-unused'
|
||||
entity PostalAddresses : PostalAddress {
|
||||
contact : Association to Contacts;
|
||||
kind : String;
|
||||
key ID : UUID;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
using from './db/code-lists';
|
||||
using from './db/schema';
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-contacts",
|
||||
"version": "1.0.0",
|
||||
"description": "A reuse package providing common domain models and services for contacts-related data.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
# Common Contacts Sample
|
||||
|
||||
This sample provides a reuse package with common domain models and services for contacts-related data.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
#### Import to your project
|
||||
|
||||
npm install @sap/capire-contacts
|
||||
|
||||
> e.g. see: [bookstore](../bookstore/package.json)
|
||||
|
||||
#### Reusing aspects
|
||||
|
||||
Define own entities derived from the pre-defined aspects as in [_bookstore_](../bookstore/db/schema.cds):
|
||||
|
||||
```swift
|
||||
using { sap.capire.contacts.Person } from '@sap/capire-contacts';
|
||||
entity Authors : contacts.Person { ... }
|
||||
```
|
||||
|
||||
> **Note:** All entities in this package are annotated with _`@cds.persistence.skip`:'if-unused'_, so they will be ignored if not referred to from other entities in your models.
|
||||
|
||||
|
||||
#### Reusing entities
|
||||
|
||||
Reuse the entities as in this example from [_users-service_](../users-service/srv/services.cds):
|
||||
```swift
|
||||
using { sap.capire.contacts.Contacts } from '@sap/capire-contacts';
|
||||
service UsersService @(requires:'authenticated-user') {
|
||||
entity MyProfile as select from Contacts where ID=$user;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Reusing code lists
|
||||
|
||||
Reuse the code lists as in [_./tests/index.cds_](./tests/index.cds):
|
||||
|
||||
```swift
|
||||
service Sue { ...
|
||||
// expose Countries to activate provided code lists
|
||||
@readonly entity Countries as projection on sap.capire.contacts.Countries;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### Reuse code list service
|
||||
|
||||
```js
|
||||
const { intercept } = require ('@sap/capire-contacts/srv/code-lists')
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Content
|
||||
|
||||
|
||||
|
||||
## Concepts
|
||||
|
||||
* [Reuse of packages](https://cap.cloud.sap/docs/get-started/projects#reuse)
|
||||
* Code Lists, `@sap/cds/common` and `@cds.persistence.skip`: 'if-unused'
|
||||
* Using `aspects` vs `entities`
|
||||
@@ -1,70 +0,0 @@
|
||||
const cds = require ('@sap/cds')
|
||||
const READ='READ', WRITE = ['CREATE','UPDATE']
|
||||
|
||||
const intercept = exports.intercept = cds.service.impl (async (srv) => {
|
||||
|
||||
for (let each in srv.entities) {
|
||||
|
||||
// intercept JSON-encoded elements
|
||||
const jsons = await jsonsIn (srv.entities[each].elements)
|
||||
if (jsons) {
|
||||
srv.before (WRITE, each, ({data:row})=>{
|
||||
for (let e of jsons) if (row[e]) row[e] = JSON.stringify (row[e])
|
||||
})
|
||||
srv.after (READ, each, (row)=>{
|
||||
for (let e of jsons) if (row[e]) row[e] = JSON.parse (row[e])
|
||||
})
|
||||
}
|
||||
|
||||
// intercept references
|
||||
const refs = await refsIn (srv.entities[each].elements, srv.model)
|
||||
if (refs) srv.after (READ, each, (rows, req)=>{
|
||||
for (let row of rows) {
|
||||
for (let {element,codelist} of refs) {
|
||||
const entry = codelist [row[element]]
|
||||
if (entry) {
|
||||
const localized = entry.texts [req.user.locale || intercept.locale]
|
||||
row[element] = localized ? localized.name : entry.name
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
function jsonsIn (elements) {
|
||||
const jsons=[]; for (let e in elements) {
|
||||
if (elements[e]['@JSON']) jsons.push(e)
|
||||
}
|
||||
return jsons.length && jsons
|
||||
}
|
||||
|
||||
async function refsIn (elements, model) {
|
||||
const refs=[]; for (let e in elements) {
|
||||
const $ref = elements[e]['@ref']
|
||||
if ($ref) {
|
||||
const d = model.definitions [$ref['=']]
|
||||
refs.push({
|
||||
element:e,
|
||||
codelist: CodeLists[d.name] || (CodeLists[d.name] = await load(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
return refs.length && refs
|
||||
}
|
||||
|
||||
const load = exports.load = async (codelist) => {
|
||||
const all = {}
|
||||
const [entries,texts] = await Promise.all ([
|
||||
SELECT.from (codelist),
|
||||
SELECT.from (codelist.elements.texts.target)
|
||||
])
|
||||
for (let {code,name,descr} of entries) all[code] = {name,descr}
|
||||
for (let {code,locale,name,descr} of texts) (all[code].texts || (all[code].texts={})) [locale] = {name,descr}
|
||||
return all
|
||||
}
|
||||
|
||||
const CodeLists = {}
|
||||
@@ -1,68 +0,0 @@
|
||||
const {load,intercept} = require ('../srv/code-lists')
|
||||
const cds = require ('@sap/cds')
|
||||
|
||||
// patch-enhance cds.ql
|
||||
const select = SELECT.from('.').__proto__.__proto__, query = select.__proto__
|
||||
query.then = function (r,e) { return db.run(this) .then (r,e || ((e)=>{throw e})) }
|
||||
|
||||
let db, Countries, Australia = {
|
||||
name: 'Australia', descr: 'Commonwealth of Australia', texts: {
|
||||
de: { name: 'Australien', descr: 'Commonwealth Australien' }
|
||||
}
|
||||
}
|
||||
|
||||
describe ('code list tests', ()=>{
|
||||
|
||||
it ('should deploy the db schema to sqlite in-memory', async()=>{
|
||||
db = await cds.deploy (__dirname) .to ('sqlite::memory:', {silent:true,primary:true})
|
||||
Countries = db.model.entities ['sap.common.Countries']
|
||||
expect (Countries) .toBeDefined()
|
||||
})
|
||||
|
||||
it ('should read Countries', async()=>{
|
||||
const countries = await SELECT ('code','name') .from (Countries)
|
||||
expect (countries) .toContainEqual ({ code: 'AU', name: 'Australia' })
|
||||
})
|
||||
|
||||
it ('should read Countries_texts', async()=>{
|
||||
const countries = await SELECT ('locale','code','name') .from ('sap.common.Countries_texts')
|
||||
expect (countries) .toContainEqual ({ locale: 'de', code: 'AU', name: 'Australien' })
|
||||
})
|
||||
|
||||
it ('should read code lists with translated texts', async()=>{
|
||||
const {AU} = await load (Countries)
|
||||
expect (AU) .toEqual (Australia)
|
||||
})
|
||||
|
||||
cds.env.singletenant = true
|
||||
|
||||
it ('should serve services with localized data', async()=>{
|
||||
const { Sue:sue } = await cds.serve (__dirname)
|
||||
const { Foos } = sue.entities
|
||||
await sue.create (Foos) .entries ({country:'Avalon'})
|
||||
await sue.create (Foos) .entries ({country:'AU'})
|
||||
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'AU' } ])
|
||||
})
|
||||
|
||||
it ('should resolve countries', async()=>{
|
||||
const sue = await cds.connect.to ('Sue')
|
||||
await intercept (sue)
|
||||
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'Australia' } ])
|
||||
intercept.locale = 'de'
|
||||
expect (await sue.read('Foos')) .toEqual ([ { ID: 1, country: 'Avalon' }, { ID: 2, country: 'Australien' } ])
|
||||
console.log (await sue.read('Foos'))
|
||||
})
|
||||
|
||||
it ('should read countries with expand to translated texts', async()=>{
|
||||
const countries = await cds.read (Countries, c=>{
|
||||
c.name, c.texts (t => {
|
||||
t.locale, t.name
|
||||
})
|
||||
})
|
||||
console.log (countries)
|
||||
})
|
||||
|
||||
it ('should disconnect from db', ()=> db.disconnect())
|
||||
//> FIXME: that should not be required!
|
||||
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
using { sap } from '..';
|
||||
|
||||
entity Foo {
|
||||
key ID : Integer;
|
||||
country : String @ref: sap.capire.contacts.Countries;
|
||||
}
|
||||
service Sue {
|
||||
entity Foos as projection on Foo;
|
||||
// expose Countries to activate provided code lists
|
||||
@readonly entity Countries as projection on sap.capire.contacts.Countries;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
code;symbol;name;descr;numcode;minor;exponent
|
||||
EUR;€;Euro;European Euro;978;Cent;2
|
||||
USD;$;US Dollar;United States Dollar;840;Cent;2
|
||||
CAD;$;Canadian Dollar;Canadian Dollar;124;Cent;2
|
||||
AUD;$;Australian Dollar;Canadian Dollar;036;Cent;2
|
||||
GBP;£;British Pound;Great Britain Pound;826;Penny;2
|
||||
ILS;₪;Shekel;Israeli New Shekel;376;Agorat;2
|
||||
INR;₹;Rupee;Indian Rupee;356;Paise;2
|
||||
QAR;﷼;Riyal;Katar Riyal;356;Dirham;2
|
||||
SAR;﷼;Riyal;Saudi Riyal;682;Halala;2
|
||||
JPY;¥;Yen;Japanese Yen;392;Sen;2
|
||||
CNY;¥;Yuan;Chinese Yuan Renminbi;156;Jiao;1
|
||||
|
@@ -1,13 +0,0 @@
|
||||
code;locale;name;descr
|
||||
EUR;de;Euro;European Euro
|
||||
USD;de;US-Dollar;United States Dollar
|
||||
CAD;de;Kanadischer Dollar;Kanadischer Dollar
|
||||
AUD;de;Australischer Dollar;Australischer Dollar
|
||||
GBP;de;Pfund;Britische Pfund
|
||||
ILS;de;Schekel;Israelische Schekel
|
||||
EUR;fr;euro;de la Zone euro
|
||||
USD;fr;dollar;dollar des États-Unis
|
||||
CAD;fr;dollar canadien;dollar canadien
|
||||
AUD;fr;dollar australien;dollar australien
|
||||
GBP;fr;livre sterling;pound sterling
|
||||
ILS;fr;Shekel;shekel israelien
|
||||
|
@@ -1,17 +0,0 @@
|
||||
namespace sap.capire.currencies;
|
||||
using { sap.common.Currencies } from '@sap/cds/common';
|
||||
|
||||
extend Currencies with {
|
||||
// Currencies.code = ISO 4217 alphabetic three-letter code
|
||||
// with the first two letters being equal to ISO 3166 alphabetic country codes
|
||||
numcode : Integer;
|
||||
exponent : Integer; //> e.g. 2 --> 1 Dollar = 10^2 Cent
|
||||
minor : String; //> e.g. 'Cent'
|
||||
// country : String; //> country or region
|
||||
}
|
||||
|
||||
|
||||
// see also
|
||||
// [1] https://www.iso.org/iso-4217-currency-codes.html
|
||||
// [2] https://www.currency-iso.org/en/home/tables/table-a1.html
|
||||
// [3] https://www.ibm.com/support/knowledgecenter/en/SSZLC2_7.0.0/com.ibm.commerce.payments.developer.doc/refs/rpylerl2mst97.htm
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-currencies",
|
||||
"version": "1.0.0",
|
||||
"description": "A reuse package providing common domain models and services for currencies-related data.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace sap.capire.media;
|
||||
|
||||
service MediaServer {
|
||||
entity Images {
|
||||
key url : URL;
|
||||
type : String enum { jpeg; png; gif; };
|
||||
content : Image;
|
||||
}
|
||||
}
|
||||
|
||||
type ImageURL : URL;
|
||||
// type ImageURL : Association to MediaServer.Images;
|
||||
//> would need Assotiations targeting off service to turn into references w/ foreign keys
|
||||
type Image : LargeBinary @stream;
|
||||
type URL : String(222);
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-media",
|
||||
"version": "1.0.0",
|
||||
"description": "A generic platform service to manage and serve media content on behalf of other services and apps.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
ID;amount;parent_ID;article
|
||||
58040e66-1dcd-4ffb-ab10-fdce32028b79;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;201
|
||||
64e718c9-ff99-47f1-8ca3-950c850777d4;1;7e2f2640-6866-4dcf-8f4d-3027aa831cad;271
|
||||
e9641166-e050-4261-bfee-d1e797e6cb7f;2;64e718c9-ff99-47f1-8ca3-950c850777d4;252
|
||||
|
@@ -1,3 +0,0 @@
|
||||
ID;modifiedAt;createdAt;createdBy;modifiedBy;OrderNo;currency_code
|
||||
7e2f2640-6866-4dcf-8f4d-3027aa831cad;;2019-01-31;john.doe@test.com;;1;EUR
|
||||
64e718c9-ff99-47f1-8ca3-950c850777d4;;2019-01-30;christian.georgi@sap.com;;2;EUR
|
||||
|
@@ -1,15 +0,0 @@
|
||||
namespace sap.capire.orders;
|
||||
using { Currency, cuid, managed } from '@sap/cds/common';
|
||||
|
||||
|
||||
entity Orders : cuid, managed {
|
||||
OrderNo : String @title:'Order Number'; //> readable key
|
||||
Items : Composition of many OrderItems on Items.parent = $self;
|
||||
currency : Currency;
|
||||
}
|
||||
|
||||
entity OrderItems : cuid {
|
||||
parent : Association to Orders not null;
|
||||
article : String;
|
||||
amount : Integer;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
namespace sap.capire.orders;
|
||||
using from './srv/orders-service';
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "@sap/capire-orders",
|
||||
"version": "1.0.0",
|
||||
"description": "A reuse package providing domain models and services to submit and manage purchase orders.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace sap.capire.orders;
|
||||
using { sap.capire.orders as my } from '../db/schema';
|
||||
|
||||
service OrdersService {
|
||||
entity Orders as projection on my.Orders;
|
||||
}
|
||||
12
packages/products-service/.gitignore
vendored
Normal file
12
packages/products-service/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
gen/
|
||||
.gen/
|
||||
node_modules/
|
||||
target/
|
||||
*.db
|
||||
.DS_Store
|
||||
_out
|
||||
.che/
|
||||
.cds_gen.log
|
||||
package-lock.json
|
||||
*.orig
|
||||
mta_archives/
|
||||
@@ -3,6 +3,7 @@ namespace sap.capire.products;
|
||||
using { Currency, cuid, managed, sap.common.CodeList } from '@sap/cds/common';
|
||||
|
||||
entity Products : cuid, managed {
|
||||
key ID : String(36);
|
||||
title : localized String(111);
|
||||
descr : localized String(1111);
|
||||
stock : Integer;
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
"name": "@sap/capire-products",
|
||||
"version": "1.0.0",
|
||||
"description": "A reuse package providing domain models and services to manage product catalogs.",
|
||||
"repository": "https://github.com/SAP-samples/cloud-cap-samples.git",
|
||||
"license": "SAP SAMPLE CODE LICENSE",
|
||||
"repository": "https://github.wdf.sap.corp/caps/products-service.git",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sap/cds": "latest",
|
||||
"express": "*"
|
||||
"@sap/cds": "^3.17.4",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"files": [
|
||||
"db",
|
||||
"srv",
|
||||
"index.cds"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"raw": "{ \"ID\":0, \"name\":\"Some Sample Categories...\", \"children\":[\n { \"ID\":1, \"name\":\"Cat\", \"children\":[\n { \"ID\":2, \"name\":\"Kitty\", \"children\":[\n { \"ID\":3, \"name\":\"Kitty Cat\", \"children\":[\n { \"ID\":4, \"name\":\"Aristocat\" }\n ]},\n { \"ID\":5, \"name\":\"Kitty Bat\" }\n ]},\n { \"ID\":6, \"name\":\"Catwoman\", \"children\":[\n { \"ID\":7, \"name\":\"Catalina\" }\n ]}\n ] },\n { \"ID\":8, \"name\":\"Catweazle\" }\n]}\n"
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:4004/admin/Categories",
|
||||
"raw": "http://localhost:4004/admin/cats",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
@@ -28,7 +28,7 @@
|
||||
"port": "4004",
|
||||
"path": [
|
||||
"admin",
|
||||
"Categories"
|
||||
"cats"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -44,7 +44,7 @@
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:4004/admin/Categories/0?$expand=children($expand=children($expand=children($expand=children)))",
|
||||
"raw": "http://localhost:4004/admin/cats/0?$expand=children($expand=children($expand=children($expand=children)))",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
@@ -52,7 +52,7 @@
|
||||
"port": "4004",
|
||||
"path": [
|
||||
"admin",
|
||||
"Categories",
|
||||
"cats",
|
||||
"0"
|
||||
],
|
||||
"query": [
|
||||
@@ -74,7 +74,7 @@
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:4004/admin/Categories/0?$expand=children($expand=children($expand=children($expand=children)))",
|
||||
"raw": "http://localhost:4004/admin/cats/0?$expand=children($expand=children($expand=children($expand=children)))",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
@@ -82,7 +82,7 @@
|
||||
"port": "4004",
|
||||
"path": [
|
||||
"admin",
|
||||
"Categories",
|
||||
"cats",
|
||||
"0"
|
||||
],
|
||||
"query": [
|
||||
@@ -137,7 +137,7 @@
|
||||
"raw": ""
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:4004/admin/Categories/0",
|
||||
"raw": "http://localhost:4004/admin/cats/0",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
@@ -145,7 +145,7 @@
|
||||
"port": "4004",
|
||||
"path": [
|
||||
"admin",
|
||||
"Categories",
|
||||
"cats",
|
||||
"0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace sap.capire.reviews;
|
||||
using { User } from '@sap/cds/common';
|
||||
|
||||
// Reviewed subjects can be any entity that is uniquely identified
|
||||
// by a single key element such as a UUID
|
||||
type ReviewedSubject : String(111);
|
||||
|
||||
entity Reviews {
|
||||
key ID : UUID;
|
||||
subject : ReviewedSubject;
|
||||
reviewer : User;
|
||||
rating : Rating;
|
||||
title : String(111);
|
||||
text : String(1111);
|
||||
date : DateTime;
|
||||
likes : Composition of many Likes on likes.review = $self;
|
||||
liked : Integer default 0; // counter for likes as helpful review (count of all _likes belonging to this review)
|
||||
}
|
||||
|
||||
type Rating : Decimal(3,2) enum {
|
||||
Best = 5;
|
||||
Good = 4;
|
||||
Avg = 3;
|
||||
Poor = 2;
|
||||
Worst = 1;
|
||||
}
|
||||
|
||||
entity Likes {
|
||||
key review : Association to Reviews;
|
||||
key user : User;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
using from './srv/reviews-service';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user