Compare commits

..

6 Commits

Author SHA1 Message Date
Daniel
f425a91c2d Adjusted usage of Orders_Items 2020-11-21 00:40:24 +01:00
Daniel
108e886b52 ... 2020-11-20 19:56:04 +01:00
Daniel
d9fb33a523 Investigated issues 2020-11-20 18:53:04 +01:00
Daniel
8429d14bfe Merge branch 'master' of https://github.com/sap-samples/cloud-cap-samples into managed-compositions 2020-11-20 17:26:24 +01:00
Daniel
67c95d9d48 . 2020-11-20 17:25:23 +01:00
Daniel
7a776c53d2 . 2020-11-20 17:02:57 +01:00
106 changed files with 2008 additions and 39636 deletions

View File

@@ -21,7 +21,6 @@
},
"rules": {
"no-console": "off",
"require-atomic-updates": "off",
"require-await":"warn"
"require-atomic-updates": "off"
}
}

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x]
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
@@ -24,5 +24,5 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm install
- run: npm test

2
.gitignore vendored
View File

@@ -15,5 +15,3 @@ default-env.json
packages/messageBox
reviews/msg-box
reviews/db/test.db
*.openapi3.json

3
.npmrc
View File

@@ -1,3 +0,0 @@
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
@sap:registry=https://registry.npmjs.org/
registry=https://registry.npmjs.org/

View File

@@ -1 +0,0 @@
*.tgz

View File

@@ -5,15 +5,15 @@ const app = express()
const { PORT=4444 } = process.env
const [,,port=PORT] = process.argv
const cwd = __dirname
app.use('/-/:tarball', (req,res,next) => {
const url = decodeURIComponent(req.url)
console.debug ('GET', req.params)
try {
const { tarball } = req.params
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
fs.lstat(tarball,(err => {
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
if (err) exec(`npm pack ../${pkg}`,next)
else next()
}))
} catch (e) {

View File

@@ -1,109 +0,0 @@
{
"$schema": "https://aka.ms/codetour-schema",
"title": "Database Functions",
"steps": [
{
"title": "Introduction",
"description": "### Database Functions in CDS Models\n\nIn this tour, you'll learn how to add database-specific functions to CDS models in your application."
},
{
"file": "bookshop/db/schema.cds",
"description": "#### Basic Schema\n\nWe want to add two fields to the `Authors` entity, one for the author's age and one for the span of years that she or he lived.\n\nThese two fields can be computed out of the existing `dateOfBirth` and `dateOfDeath` fields.",
"selection": {
"start": {
"line": 19,
"character": 1
},
"end": {
"line": 21,
"character": 1
}
},
"title": "Base fields in Author"
},
{
"file": "bookshop/srv/admin-service.cds",
"description": "This is how the `Authors` entity gets exposed in an OData or REST service.\n\nIn the next step, you'll see how we extend this projection.",
"selection": {
"start": {
"line": 4,
"character": 1
},
"end": {
"line": 5,
"character": 1
}
},
"title": "Authors service"
},
{
"file": "fiori/db/sqlite/index.cds",
"description": "#### SQLite Implementation\n\nHere's the first implementation for SQLite. It computes the two fields `age` and `lifetime` through SQLite's [strftime](https://sqlite.org/lang_datefunc.html) function.\n\nThrough the [`extend projection`](https://cap.cloud.sap/docs/cds/cdl#extend-view) clause you can add additional fields to projection entities. These are deployed as database views, which is why we can integrate the database functions in the first place.\n",
"selection": {
"start": {
"line": 7,
"character": 1
},
"end": {
"line": 11,
"character": 1
}
},
"title": "SQLite implementation"
},
{
"file": "fiori/db/hana/index.cds",
"description": "#### SAP HANA Implementation\n\nThis is the second implementation for SAP HANA. It computes the same two fields `age` and `lifetime` through the [YEARS_BETWEEN](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/7c0d2c161ea34def86de3f5eadd6a0af.html) and [YEAR](https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/20f5fac6751910148dabd3c6821f907d.html) functions of SAP HANA.\n\n#### File Layout and Code Structure\n\nNote the path of the `.cds` file we are in: it's in a subfolder of `db`, so that it's _not_ automatically picked up when we start the application. The same is true for the SQLite implementation: it's in a separate `db/sqlite/` folder as well. In the next step, you'll see how these files are loaded.\n\nAlso, we choose to implement all of that as an extension of the original bookshop here in the _fiori_ package. See the first [CAP Samples] code tour for more details on the different packages of this repository.",
"selection": {
"start": {
"line": 7,
"character": 1
},
"end": {
"line": 11,
"character": 1
}
},
"title": "SAP HANA implementation"
},
{
"file": "fiori/package.json",
"description": "#### Configuration\n\nThe `cds` section in `package.json` is a place to configure which of the `db/sqlite` and `db/hana` folders are used for which database.\nWe use [Node.js profiles](https://cap.cloud.sap/docs/node.js/cds-env#profiles) to separate the configuration.\nIn the `development` profile, you can see that `db/sqlite` is set as the model, while the `db/hana` folder is configured in the `production` profile.",
"line": 17,
"title": "Configuration"
},
{
"file": "fiori/package.json",
"description": "#### Run with SQLite\n\nTo run with `development` and an in-memory SQLite database, you don't need to do anything special, because it's activated by default. Just run:\n\n>> cds watch fiori\n\nThen open [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) to see the two new fields.\n",
"line": 28,
"title": "Run with SQLite"
},
{
"file": "fiori/package.json",
"description": "#### Deploy the CDS Model to SAP HANA\n\nTo 'activate' SAP HANA through the `production` profile, you can use the global `--production` flag:\n\n>> cd fiori; cds deploy --to hana --production\n\n[Learn more about SAP HANA deployment](https://cap.cloud.sap/docs/guides/databases#get-hana)\n\n#### Run the Application\n\n>> cd fiori; cds watch --production\n\nThe service on [http://localhost:4004/admin/Authors](http://localhost:4004/admin/Authors) is the same as before, but this time the `Authors` entity is backed by a database view with an SAP HANA function.\n\n#### More\n\nIf you don't see data, you can add some in the next step.",
"line": 31,
"title": "Run with SAP HANA"
},
{
"file": "fiori/test/requests.http",
"description": "### Add More Data\n\nOptionally you can add some `Authors` data by clicking on the _Send Request_ link (provided by the [REST client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension).",
"line": 68,
"selection": {
"start": {
"line": 67,
"character": 1
},
"end": {
"line": 73,
"character": 1
}
},
"title": "Add Data"
},
{
"title": "Wrap-up",
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
}
],
"ref": "master"
}

View File

@@ -1,136 +0,0 @@
{
"$schema": "https://aka.ms/codetour-schema",
"title": "CAP Samples",
"steps": [
{
"title": "Welcome",
"file": "README.md",
"description": "### Welcome to CAP Samples!\n\nThis tour leads you through a collection of samples for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap).\nYou will learn which features of the programming model are demonstrated in which sample.\n\nLet's start!",
"line": 2,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 3,
"character": 108
}
}
},
{
"file": "hello/world.cds",
"description": "### Hello World!\n\nThis is a simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).",
"line": 2,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 4,
"character": 1
}
},
"title": "Hello World!"
},
{
"file": "bookshop/db/schema.cds",
"description": "### A Bookshop!\n\nIntroduces:\n- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)\n- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)\n- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)\n- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)\n- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)\n- [Using Databases](https://cap.cloud.sap/docs/guides/databases)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 32,
"character": 1
}
},
"title": "Bookshop"
},
{
"file": "common/index.cds",
"description": "### Extend and Reuse\n\nShowcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:\n- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)\n- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)\n- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)\n- Using [Aspects](https://cap.cloud.sap/docs/cds/cdl#aspects)\n- Used in the [fiori app sample](#fiori)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 46,
"character": 1
}
},
"title": "Common"
},
{
"file": "orders/db/schema.cds",
"description": "### Compositions and Serving Documents\n\nA standalone orders management service, demonstrating:\n- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with\n- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 27,
"character": 1
}
},
"title": "Orders"
},
{
"file": "reviews/db/schema.cds",
"description": "### More Modularity\n\nShows how to implement a modular service to manage product reviews, including:\n- Consuming other services synchronously and asynchronously\n- Serving requests synchronously\n- Emitting events asynchronously\n- Grow as you go, with:\n- Mocking app services\n- Running service meshes\n- Late-cut Micro Services\n- As well as managed data, input validations, and authorization\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 39,
"character": 1
}
},
"title": "Reviews"
},
{
"file": "fiori/app/index.cds",
"description": "### Annotations for SAP Fiori Elements\n\nA [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:\n - [@capire/bookshop](bookshop)\n - [@capire/reviews](reviews)\n - [@capire/orders](orders)\n - [@capire/common](common)\n\n[Adds a SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:\n - [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files\n - Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)\n - Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)\n - Serving SAP Fiori apps locally\n\n[The Vue.js app](bookshop/app/vue) imported from bookshop is served as well.\n",
"line": 1,
"selection": {
"start": {
"line": 1,
"character": 1
},
"end": {
"line": 13,
"character": 1
}
},
"title": "Fiori"
},
{
"file": "package.json",
"description": "### All-in-one Monorepo\n\nEach sample sub directory essentially is a standard npm package, some with standard npm dependencies to other samples. The root folder's [package.json](package.json) has local links to the sub folders, such that an `npm install` populates a local `node_modules` folder acts like a local npm registry to the individual sample packages.\n",
"line": 8,
"selection": {
"start": {
"line": 8,
"character": 1
},
"end": {
"line": 15,
"character": 1
}
},
"title": "Packages"
}
],
"isPrimary": true,
"description": "Overview of CAP Samples for Node.js"
}

View File

@@ -4,15 +4,14 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"sapse.vscode-cds",
"SAPSE.vscode-cds",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mechatroner.rainbow-csv",
"humao.rest-client",
"alexcvzz.vscode-sqlite",
"hbenl.vscode-mocha-test-adapter",
"sdras.night-owl",
"vsls-contrib.codetour"
"sdras.night-owl"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [

39
.vscode/launch.json vendored
View File

@@ -5,30 +5,27 @@
"version": "0.2.0",
"configurations": [
{
"name": "bookshop",
"command": "npx cds watch bookshop",
"type": "node-terminal",
"request": "launch",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}",
"request": "attach",
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Fiori App",
"command": "npx cds watch fiori",
"type": "node-terminal",
"name": "bookshop",
"command": "cds watch bookshop",
"request": "launch",
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
"type": "node-terminal",
"skipFiles": ["<node_internals>/**"]
},
{
"name": "Fiori app",
"command": "cds watch fiori",
"request": "launch",
"type": "node-terminal",
"skipFiles": ["<node_internals>/**"]
}
],
"inputs": [
@@ -36,7 +33,7 @@
"type": "pickString",
"id": "sample",
"description": "Which sample do you want to start?",
"options": [ "bookshop", "fiori", "reviews", "reviews" ],
"options": ["bookshop", "fiori", "reviews", "reviews/test/bookshop"],
"default": "bookshop"
}
]

13
.vscode/settings.json vendored
View File

@@ -1,17 +1,6 @@
{
"files.exclude": {
".reuse/**": true,
"**/.gitignore": true,
"**/.vscode": true,
"LICENSES/**": true
},
"debug.javascript.terminalOptions": {
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/cds/lib/lazy.js",
"**/cds/lib/req/cls.js",
"**/odata-v4/okra/**"
]
"**/.vscode": true
}
}

208
LICENSE Normal file
View File

@@ -0,0 +1,208 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION,
AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution
as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct
or indirect, to cause the direction or management of such entity, whether
by contract or otherwise, or (ii) ownership of fifty percent (50%) or more
of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions
granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled object
code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that
is included in or attached to the work (an example is provided in the Appendix
below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative
Works shall not include works that remain separable from, or merely link (or
bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative
Works thereof, that is intentionally submitted to Licensor for inclusion in
the Work by the copyright owner or by an individual or Legal Entity authorized
to submit on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication
sent to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor
for the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently incorporated
within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and distribute
the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License,
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise
transfer the Work, where such license applies only to those patent claims
licensable by such Contributor that are necessarily infringed by their Contribution(s)
alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against
any entity (including a cross-claim or counterclaim in a lawsuit) alleging
that the Work or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses granted to You
under this License for that Work shall terminate as of the date such litigation
is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and
in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy
of this License; and
(b) You must cause any modified files to carry prominent notices stating that
You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source
form of the Work, excluding those notices that do not pertain to any part
of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy
of the attribution notices contained within such NOTICE file, excluding those
notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part
of the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works
that You distribute, alongside or as an addendum to the NOTICE text from the
Work, provided that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction,
or distribution of Your modifications, or for any such Derivative Works as
a whole, provided Your use, reproduction, and distribution of the Work otherwise
complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any
Contribution intentionally submitted for inclusion in the Work by You to the
Licensor shall be under the terms and conditions of this License, without
any additional terms or conditions. Notwithstanding the above, nothing herein
shall supersede or modify the terms of any separate license agreement you
may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
in writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness
of using or redistributing the Work and assume any risks associated with Your
exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether
in tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to
in writing, shall any Contributor be liable to You for damages, including
any direct, indirect, special, incidental, or consequential damages of any
character arising as a result of this License or out of the use or inability
to use the Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all other commercial
damages or losses), even if such Contributor has been advised of the possibility
of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work
or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such obligations,
You may act only on Your own behalf and on Your sole responsibility, not on
behalf of any other Contributor, and only if You agree to indemnify, defend,
and hold each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such warranty
or additional liability. END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own identifying
information. (Don't include the brackets!) The text should be enclosed in
the appropriate comment syntax for the file format. We also recommend that
a file or class name and description of purpose be included on the same "printed
page" as the copyright notice for easier identification within third-party
archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -7,17 +7,14 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
### Preliminaries
1. Install [**@sap/cds-dk**](https://cap.cloud.sap/docs/get-started/) globally:
1. [Install @sap/cds-dk](https://cap.cloud.sap/docs/get-started/) as documented in [capire](https://cap.cloud.sap)
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/in-vscode)
```sh
npm i -g @sap/cds-dk
```
2. _Optional:_ [Use Visual Studio Code](https://cap.cloud.sap/docs/get-started/tools#vscode)
### Download
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
Clone this repo as shown below, if you have [git](https://git-scm.com/downloads) installed,
otherwise [download as zip file](archive/master.zip).
```sh
git clone https://github.com/sap-samples/cloud-cap-samples samples
@@ -42,12 +39,9 @@ cds watch bookshop
After that open this link in your browser: [http://localhost:4004](http://localhost:4004)
When asked to log in, type `alice` as user and leave the password field blank, which is the [default user](https://cap.cloud.sap/docs/node.js/authentication#mocked).
### Testing
Run the provided tests with [_jest_](http://jestjs.io) or [_mocha_](http://mochajs.org), for example:
```sh
npx jest
```
@@ -56,7 +50,7 @@ npx jest
### Serve `npm`
We've included a simple npm registry mock, which allows you to do an `npm install @capire/<package>` locally. Use it as follows:
We've simple npm registry mock included which allows you to do an `npm install @capire/<package>` anywhere locally. Use it as follows:
1. Start the @capire registry:
```sh
@@ -64,55 +58,18 @@ npm run registry
```
> While running this will have `@capire:registry=http://localhost:4444` set with npmrc.
2. Install one of the @capire packages wherever you like, for example:
2. Install one of the @capire packages wherever you like, e.g.:
```sh
npm add @capire/common @capire/bookshop
```
## Code Tours
Take one of the [guided tours](.tours) in VS Code through our CAP samples and learn which CAP features are showcased by the different parts of the repository. Just install the [CodeTour extension](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.codetour) for VS Code. We'll add more code tours in the future. Stay tuned!
## Get Support
Check out the documentation at [https://cap.cloud.sap](https://cap.cloud.sap). <br>
In case you've a question, find a bug, or otherwise need support, use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce) to get more visibility.
In case you have a question, find a bug, or otherwise need support, please use our [community](https://answers.sap.com/tags/9f13aee1-834c-4105-8e43-ee442775e5ce).
## License
Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE.txt) file.
# Suppliers - in progress for Messaging & Service Consumption -
## TODOs
1. Fix issues when running in same process
2. Automated tests
## Usage
1. Run:
```
CDS_ENV=local-hybrid cds mock API_BUSINESS_PARTNER -p 5001
```
2. Wait until startup is completed
3. Run in a 2nd terminal:
```
CDS_ENV=local-hybrid cds serve all --with-mocks --in-memory
```
4. Now, you can issues the requests listed in `suppliers/requests.http`
## Request Sequence
* TODO
## URLs
* Get books with their replicated supplier: http://localhost:4004/browse/Books?$expand=supplier
* Get remote suppliers: http://localhost:4004/admin/Suppliers?$top=11
Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file.

View File

@@ -35,7 +35,7 @@ const books = new Vue ({
try {
const res = await POST(`/submitOrder`, { amount, book: book.ID })
book.stock = res.data.stock
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
books.order = { amount, succeeded: `Successfully orderd ${amount} item(s).` }
} catch (e) {
books.order = { amount, failed: e.response.data.error.message }
}

View File

@@ -36,7 +36,7 @@
<td class="rating-stars">
{{ ('★'.repeat(Math.round(book.rating))+'☆☆☆☆☆').slice(0,5) }}
</td>
<td>{{ book.currency && book.currency.symbol }} {{ book.price }}</td>
<td>{{ book.currency.symbol }} {{ book.price }}</td>
</tr>
</table>

View File

@@ -3,4 +3,4 @@ ID;title;descr;author_ID;stock;price;currency_code;genre_ID
207;Jane Eyre;"Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name ""Currer Bell"", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism.";107;11;12.34;GBP;11
251;The Raven;"""The Raven"" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word ""Nevermore"". The poem makes use of folk, mythological, religious, and classical references.";150;333;13.13;USD;16
252;Eleonora;"""Eleonora"" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively ""happy"" ending.";150;555;14;USD;16
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;150;JPY;13
271;Catweazle;Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts.;170;22;15;EUR;13
1 ID title descr author_ID stock price currency_code genre_ID
3 207 Jane Eyre Jane Eyre /ɛər/ (originally published as Jane Eyre: An Autobiography) is a novel by English writer Charlotte Brontë, published under the pen name "Currer Bell", on 16 October 1847, by Smith, Elder & Co. of London. The first American edition was published the following year by Harper & Brothers of New York. Primarily a bildungsroman, Jane Eyre follows the experiences of its eponymous heroine, including her growth to adulthood and her love for Mr. Rochester, the brooding master of Thornfield Hall. The novel revolutionised prose fiction in that the focus on Jane's moral and spiritual development is told through an intimate, first-person narrative, where actions and events are coloured by a psychological intensity. The book contains elements of social criticism, with a strong sense of Christian morality at its core and is considered by many to be ahead of its time because of Jane's individualistic character and how the novel approaches the topics of class, sexuality, religion and feminism. 107 11 12.34 GBP 11
4 251 The Raven "The Raven" is a narrative poem by American writer Edgar Allan Poe. First published in January 1845, the poem is often noted for its musicality, stylized language, and supernatural atmosphere. It tells of a talking raven's mysterious visit to a distraught lover, tracing the man's slow fall into madness. The lover, often identified as being a student, is lamenting the loss of his love, Lenore. Sitting on a bust of Pallas, the raven seems to further distress the protagonist with its constant repetition of the word "Nevermore". The poem makes use of folk, mythological, religious, and classical references. 150 333 13.13 USD 16
5 252 Eleonora "Eleonora" is a short story by Edgar Allan Poe, first published in 1842 in Philadelphia in the literary annual The Gift. It is often regarded as somewhat autobiographical and has a relatively "happy" ending. 150 555 14 USD 16
6 271 Catweazle Catweazle is a British fantasy television series, starring Geoffrey Bayldon in the title role, and created by Richard Carpenter for London Weekend Television. The first series, produced and directed by Quentin Lawrence, was screened in the UK on ITV in 1970. The second series, directed by David Reid and David Lane, was shown in 1971. Each series had thirteen episodes, most but not all written by Carpenter, who also published two books based on the scripts. 170 22 150 15 JPY EUR 13

View File

@@ -4,7 +4,7 @@
"description": "A simple self-contained bookshop service.",
"dependencies": {
"@capire/common": "*",
"@sap/cds": "^5.0.4",
"@sap/cds": "^4",
"express": "^4.17.1",
"passport": "0.4.1"
},

View File

@@ -5,10 +5,10 @@ This stand-alone sample introduces the essential tasks in the development of CAP
## Hypothetical Use Cases
1. Build a service that allows to browse _Books_ and _Authors_.
2. Books have assigned _Genres_, which are organized hierarchically.
2. Books have assigned _Genres_ which are organized hierarchically.
3. All users may browse books without login.
4. All entries are maintained by Administrators.
5. End users may order books (the actual order mgmt being out of scope).
5. End users may order books (the actual order mgmt being out of scope)
## Running the Sample
@@ -20,12 +20,12 @@ npm run watch
| Links to capire | Sample files / folders |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| [Project Setup & Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
| [Domain Modeling with CDS](https://cap.cloud.sap/docs/guides/domain-models) | [`./db/schema.cds`](./db/schema.cds) |
| [Defining Services](https://cap.cloud.sap/docs/guides/services#defining-services) | [`./srv/*.cds`](./srv) |
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/services#single-purposed-services) | [`./srv/*.cds`](./srv) |
| [Providing & Consuming Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
| [Using Databases](https://cap.cloud.sap/docs/guides/databases) | [`./db/data/*.csv`](./db/data) |
| [Project Setup and Layouts](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./`](./) |
| [Defining Domain Models](https://cap.cloud.sap/docs/guides/domain-models) | [`./db/schema.cds`](./db/schema.cds) |
| [Defining Services](https://cap.cloud.sap/docs/guides/providing-services) | [`./srv/*.cds`](./srv) |
| [Single-purposed Services](https://cap.cloud.sap/docs/guides/providing-services#single-purposed-services) | [`./srv/*.cds`](./srv) |
| [Generic Providers](https://cap.cloud.sap/docs/guides/providing-services) | http://localhost:4004 |
| Using Databases | [`./db/data/*.csv`](./db/data) |
| [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl) | [`./srv/*.js`](./srv) |
| Adding Tests | [`./test`](./test) |
| [Sharing for Reuse](https://cap.cloud.sap/docs/guides/reuse-and-compose) | [`./index.cds`](./index.cds) |
| [Sharing for Reuse](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content) | [`./index.cds`](./index.cds) |

View File

@@ -1,16 +1,13 @@
using { sap.capire.bookshop as my } from '../db/schema';
service CatalogService @(path:'/browse') {
/** For displaying lists of Books */
@readonly entity ListOfBooks as projection on Books {
ID, title, author, genre, price, currency
}
/** For display in details pages */
@readonly entity Books as projection on my.Books { *,
@readonly entity Books as SELECT from my.Books {*,
author.name as author
} excluding { createdBy, modifiedBy };
@readonly entity ListOfBooks as SELECT from Books
excluding { descr, stock };
@requires: 'authenticated-user'
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };

View File

@@ -1,23 +1,25 @@
const cds = require('@sap/cds')
const { Books } = cds.entities ('sap.capire.bookshop')
class CatalogService extends cds.ApplicationService { init(){
class CatalogService extends cds.ApplicationService { async init(){
// Reduce stock of ordered books if available stock suffices
this.on ('submitOrder', async req => {
const {book,amount} = req.data
let {stock} = await SELECT `stock` .from (Books,book)
const {book,amount} = req.data, tx = cds.tx(req)
let {stock} = await tx.read('stock').from(Books,book)
if (stock >= amount) {
await UPDATE (Books,book) .with (`stock -=`, amount)
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
await tx.update (Books,book).with ({ stock: stock -= amount })
this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
return { stock }
}
else return req.error (409,`${amount} exceeds stock for book #${book}`)
})
// Add some discount for overstocked books
this.after ('READ','ListOfBooks', each => {
if (each.stock > 111) each.title += ` -- 11% discount!`
this.after ('READ','Books', each => {
if (each.stock > 111) {
each.title += ` -- 11% discount!`
}
})
return super.init()

View File

@@ -5,11 +5,9 @@ CAD;de;Kanadischer Dollar;Kanadischer Dollar
AUD;de;Australischer Dollar;Australischer Dollar
GBP;de;Pfund;Britische Pfund
ILS;de;Schekel;Israelische Schekel
JPY;de;Yen;Japanische Yen
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
JPY;fr;Yen;Yen japonais
1 code locale name descr
5 AUD de Australischer Dollar Australischer Dollar
6 GBP de Pfund Britische Pfund
7 ILS de Schekel Israelische Schekel
JPY de Yen Japanische Yen
8 EUR fr euro de la Zone euro
9 USD fr dollar dollar des États-Unis
10 CAD fr dollar canadien dollar canadien
11 AUD fr dollar australien dollar australien
12 GBP fr livre sterling pound sterling
13 ILS fr Shekel shekel israelien
JPY fr Yen Yen japonais

View File

@@ -1,8 +1,4 @@
{
"name": "@capire/common",
"description": "Provides a pre-built extension package for std @sap/cds/common",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "*"
}
"version": "1.0.0"
}

View File

@@ -1,2 +1,3 @@
cds.requires.messaging.kind = file-based-messaging
# cds.requires.messaging.kind = file-based-messaging
cds.cdsc.severities.extend-for-generated = info
PORT = 4004

View File

@@ -11,7 +11,6 @@ DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Age = Age
Authors = Authors
Order = Order
Orders = Orders

View File

@@ -6,7 +6,6 @@ Authors = Autoren
Author = Autor
AuthorID = ID des Autors
AuthorName = Name des Autors
Age = Alter
Name = Name
Stock = Bestand
Order = Bestellung

View File

@@ -13,7 +13,7 @@
applications: {
"browse-books": {
title: "Browse Books",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
@@ -21,7 +21,7 @@
},
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
@@ -29,7 +29,7 @@
},
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
description: "... testing FE v42",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
@@ -40,7 +40,8 @@
</script>
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
<!-- <script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" -->
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/1.78.6/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"

View File

@@ -1,5 +1,4 @@
using { AdminService, sap.capire.bookshop } from '../../db/schema';
using from '../common'; // to help UI linter get the complete annotations
using AdminService from '@capire/bookshop';
////////////////////////////////////////////////////////////////////////////
//
@@ -40,27 +39,6 @@ annotate AdminService.Books with @(
}
);
annotate AdminService.Authors with @(
UI: {
HeaderInfo: {
Description: {Value: lifetime}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: placeOfBirth},
{Value: placeOfDeath},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: age, Label: '{i18n>Age}'},
]
},
}
);
////////////////////////////////////////////////////////////
@@ -71,7 +49,7 @@ annotate AdminService.Authors with @(
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
annotate AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books.texts with @(
annotate AdminService.Books_texts with @(
UI: {
Identification: [{Value:title}],
SelectionFields: [ locale, title ],
@@ -84,12 +62,11 @@ annotate AdminService.Books.texts with @(
);
// Add Value Help for Locales
annotate AdminService.Books.texts {
annotate AdminService.Books_texts {
locale @ValueList:{entity:'Languages',type:#fixed}
}
// In addition we need to expose Languages and Books.texts through AdminService
// In addition we need to expose Languages through AdminService
using { sap } from '@sap/cds/common';
extend service AdminService {
entity Languages as projection on sap.common.Languages;
entity Books.texts as projection on bookshop.Books.texts;
}

3
fiori/app/bookshop.html Normal file
View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=vue/bookshop/index.html">
</head>

View File

@@ -54,7 +54,7 @@ annotate my.Books with {
title @title:'{i18n>Title}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code;
price @title:'{i18n>Price}';
stock @title:'{i18n>Stock}';
descr @UI.MultiLineText;
}

3
fiori/app/reviews.html Normal file
View File

@@ -0,0 +1,3 @@
<head>
<meta http-equiv="refresh" content="0;url=vue/reviews/index.html">
</head>

View File

@@ -1,10 +0,0 @@
//
// Add Author.age and .lifetime with a DB-specific function
//
using { AdminService } from '../schema';
extend projection AdminService.Authors with {
YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age: Integer,
YEAR(dateOfBirth) || ' ' || YEAR(dateOfDeath) as lifetime : String
}

View File

@@ -1,8 +0,0 @@
using { sap.capire.bookshop } from '@capire/bookshop';
// Forward-declare calculated fields to be filled in database-specific ways
// TODO find a better way to have 'default' fields that still can be overwritten.
extend bookshop.Authors with {
virtual age: Integer;
virtual lifetime: String;
}

View File

@@ -1,10 +0,0 @@
//
// Add Author.age and .lifetime with a DB-specific function
//
using { AdminService } from '../schema';
extend projection AdminService.Authors with {
strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age: Integer,
strftime('%Y',dateOfBirth) || ' ' || strftime('%Y',dateOfDeath) as lifetime : String
}

View File

@@ -3,46 +3,27 @@
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/common": "*",
"@capire/orders": "*",
"@capire/reviews": "*",
"@capire/suppliers": "*",
"@sap/cds": ">=5",
"@capire/orders": "*",
"@capire/common": "*",
"@sap/cds": "^4",
"express": "^4.17.1",
"passport": "^0.4.1"
"passport": "0.4.1"
},
"scripts": {
"start": "cds run --in-memory?",
"watch": "cds watch"
},
"cds": {
"hana": {
"deploy-format": "hdbtable"
},
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "@capire/suppliers"
},
"auth": {
"strategy": "dummy"
},
"ReviewsService": {
"kind": "odata",
"model": "@capire/reviews"
"kind": "odata", "model": "@capire/reviews"
},
"OrdersService": {
"kind": "odata",
"model": "@capire/orders"
"kind": "odata", "model": "@capire/orders"
},
"db": {
"kind": "sql",
"[development]": {
"model": "db/sqlite"
},
"[production]": {
"model": "db/hana"
}
"kind": "sql"
}
}
}

View File

@@ -1,16 +1,19 @@
const express = require ('express')
const cds = require ('@sap/cds')
module.exports = cds.server
cds.once('bootstrap',(app)=>{
app.serve ('/orders/webapp').from('@capire/orders','app/orders/webapp')
app.serve ('/bookshop').from('@capire/bookshop','app/vue')
app.serve ('/reviews').from('@capire/reviews','app/vue')
const {dirname} = require ('path')
// serving the orders app imported from @capire/orders
const orders_app = dirname (require.resolve('@capire/orders/app/orders/webapp/manifest.json'))
app.use ('/orders/webapp', express.static(orders_app))
// serving the vue.js app imported from @capire/bookshop
const bookshop_app = dirname (require.resolve('@capire/bookshop/app/vue/index.html'))
app.use ('/vue/bookshop', express.static(bookshop_app))
// serving the vue.js app imported from @capire/reviews
const reviews_app = dirname (require.resolve('@capire/reviews/app/vue/index.html'))
app.use ('/vue/reviews', express.static(reviews_app))
})
cds.once('served', require('./srv/mashup'))
cds.once('served', require('@capire/suppliers/srv/mashup'))
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
if (process.env.NODE_ENV !== 'production') {
cds.once ('bootstrap', app => app.use (require ('cds-swagger-ui-express')()) )
}
module.exports = cds.server

View File

@@ -3,17 +3,17 @@
// Mashing up imported models...
//
using { sap.capire.bookshop.Books } from '@capire/bookshop';
//
// Extend Books with access to Reviews and average ratings
//
using { CatalogService.ListOfBooks, sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Reviews:rating;
rating : Decimal;
}
extend projection ListOfBooks with { rating }
//
// Extend Orders with Books as Products

View File

@@ -49,7 +49,7 @@ module.exports = async()=>{ // called by server.js
//
// Reduce stock of ordered books for orders are created from Orders admin UI
//
OrdersService.on ('OrderChanged', (msg) => {
OrdersService.on ('OrderChanged', async (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { product, deltaAmount } = msg.data
return UPDATE (Books) .where ('ID =', product)

View File

@@ -38,44 +38,11 @@ GET {{bookshop}}/browse/Books(201)?
&$select=ID,title,rating
&$expand=reviews
###
GET {{bookshop}}/browse/Books?
&$select=title,author&$expand=currency
Accept-Language: de
#################################################
#
# Orders Service, incl. draft choreography
# Orders Service
#
@newOrderID = e939604c-ab83-4d4f-bdb6-95fe30b3773e
GET {{bookshop}}/orders/Orders
### Create order, still inactive
POST {{bookshop}}/orders/Orders
Content-Type: application/json
{"ID": "{{newOrderID}}"}
### Get inactive order. We have to specify `IsActiveEntity`.
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)
### Activate order using `.../<servicename>.draftActivate`
POST {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=false)/OrdersService.draftActivate
Content-Type: application/json
### Get active order
GET {{bookshop}}/orders/Orders(ID={{newOrderID}},IsActiveEntity=true)
### Create author
POST {{bookshop}}/admin/Authors
Content-Type: application/json
Authorization: Basic alice:
{
"ID": 200,
"name": "William Shakespeare",
"dateOfBirth": "1564-04-26",
"dateOfDeath": "1616-04-23"
}

View File

@@ -2,28 +2,6 @@
"name": "@capire/hello-world",
"version": "1.0.0",
"scripts": {
"test": "npx jest --silent",
"watch": "cds serve world.cds",
"watch:ts": "cds-ts serve world.cds"
},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^15.12.0",
"ts-jest": "^27.0.2",
"typescript": "^4.3.5"
},
"jest": {
"testEnvironment": "node",
"preset": "ts-jest",
"globals": {
"ts-jest": {
"diagnostics": {
"_comment": "see https://githubmemory.com/repo/kulshekhar/ts-jest/issues/2722",
"ignoreCodes": [
151001
]
}
}
}
"watch": "cds serve world.cds"
}
}

View File

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

View File

@@ -1,5 +0,0 @@
module.exports = class say {
hello(req: any) {
return `Hello ${req.data.to} from a typescript file!`
}
}

View File

@@ -121,7 +121,7 @@
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings" : {
"entitySet": "Orders_Items"
"entitySet": "OrderItems"
}
}
},

View File

@@ -1,23 +1,2 @@
using { Currency, User, managed, cuid } from '@sap/cds/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
}
entity Orders_Items {
key ID : UUID;
up_ : Association to Orders;
product : Association to Products @assert.integrity:false; // REVISIT: this is a temporary workaround for a glitch in cds-runtime
amount : Integer;
title : String; //> intentionally replicated as snapshot from product.title
price : Double;
}
/** This is a stand-in for arbitrary ordered Products */
entity Products @(cds.persistence.skip:'always') {
key ID : String;
}
// using from './without-managed-composition/schema';
using from './with-managed-composition/schema';

View File

@@ -0,0 +1,23 @@
using { Currency, User, managed, cuid } from '@sap/cds/common';
using from '@capire/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many {
key ID : UUID;
@assert.integrity:false // REVISIT: this is a temporary workaround for a glitch in cds-runtime
product : Association to Products;
amount : Integer;
title : String;
price : Double;
};
buyer : User;
currency : Currency;
}
/** This is a stand-in for arbitrary ordered Products */
@cds.persistence.skip:'always'
entity Products {
key ID : String;
}

View File

@@ -0,0 +1,26 @@
using { Currency, User, managed, cuid } from '@sap/cds/common';
using from '@capire/common';
namespace sap.capire.orders;
entity Orders : cuid, managed {
OrderNo : String @title:'Order Number'; //> readable key
Items : Composition of many Orders_Items on Items.up_ = $self;
buyer : User;
currency : Currency;
}
entity Orders_Items {
up_ : Association to Orders not null; //> IMPORTANT for Draft: not key(!)
key ID : UUID;
@assert.integrity:false // REVISIT: this is a temporary workaround for a glitch in cds-runtime
product : Association to Products;
amount : Integer;
title : String;
price : Double;
}
/** This is a stand-in for arbitrary ordered Products */
@cds.persistence.skip:'always'
entity Products {
key ID : String;
}

465
orders/managed-comp.xml Normal file
View File

@@ -0,0 +1,465 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="OrdersService" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Currencies" EntityType="OrdersService.Currencies">
<NavigationPropertyBinding Path="texts" Target="Currencies_texts"/>
<NavigationPropertyBinding Path="localized" Target="Currencies_texts"/>
</EntitySet>
<EntitySet Name="Currencies_texts" EntityType="OrdersService.Currencies_texts"/>
<EntitySet Name="Orders" EntityType="OrdersService.Orders">
<NavigationPropertyBinding Path="Items" Target="Orders_Items"/>
<NavigationPropertyBinding Path="currency" Target="Currencies"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Orders"/>
</EntitySet>
<EntitySet Name="Orders_Items" EntityType="OrdersService.Orders_Items">
<NavigationPropertyBinding Path="up_" Target="Orders"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Orders_Items"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Currencies">
<Key>
<PropertyRef Name="code"/>
</Key>
<Property Name="name" Type="Edm.String" MaxLength="255"/>
<Property Name="descr" Type="Edm.String" MaxLength="1000"/>
<Property Name="code" Type="Edm.String" MaxLength="3" Nullable="false"/>
<Property Name="symbol" Type="Edm.String" MaxLength="5"/>
<Property Name="numcode" Type="Edm.Int32"/>
<Property Name="exponent" Type="Edm.Int32"/>
<Property Name="minor" Type="Edm.String"/>
<NavigationProperty Name="texts" Type="Collection(OrdersService.Currencies_texts)">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<NavigationProperty Name="localized" Type="OrdersService.Currencies_texts">
<ReferentialConstraint Property="code" ReferencedProperty="code"/>
</NavigationProperty>
</EntityType>
<EntityType Name="Currencies_texts">
<Key>
<PropertyRef Name="locale"/>
<PropertyRef Name="code"/>
</Key>
<Property Name="locale" Type="Edm.String" MaxLength="14" Nullable="false"/>
<Property Name="name" Type="Edm.String" MaxLength="255"/>
<Property Name="descr" Type="Edm.String" MaxLength="1000"/>
<Property Name="code" Type="Edm.String" MaxLength="3" Nullable="false"/>
</EntityType>
<EntityType Name="DraftAdministrativeData">
<Key>
<PropertyRef Name="DraftUUID"/>
</Key>
<Property Name="DraftUUID" Type="Edm.Guid" Nullable="false"/>
<Property Name="CreationDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="CreatedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsCreatedByMe" Type="Edm.Boolean"/>
<Property Name="LastChangeDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="LastChangedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="InProcessByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsProcessedByMe" Type="Edm.Boolean"/>
</EntityType>
<EntityType Name="Orders">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="createdAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="createdBy" Type="Edm.String" MaxLength="255"/>
<Property Name="modifiedAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="modifiedBy" Type="Edm.String" MaxLength="255"/>
<Property Name="OrderNo" Type="Edm.String"/>
<NavigationProperty Name="Items" Type="Collection(OrdersService.Orders_Items)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<Property Name="buyer" Type="Edm.String" MaxLength="255"/>
<NavigationProperty Name="currency" Type="OrdersService.Currencies">
<ReferentialConstraint Property="currency_code" ReferencedProperty="code"/>
</NavigationProperty>
<Property Name="currency_code" Type="Edm.String" MaxLength="3"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="OrdersService.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="OrdersService.Orders"/>
</EntityType>
<EntityType Name="Orders_Items">
<Key>
<PropertyRef Name="up__ID"/>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="up_" Type="OrdersService.Orders" Nullable="false" Partner="Items">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="amount" Type="Edm.Int32"/>
<Property Name="title" Type="Edm.String"/>
<Property Name="price" Type="Edm.Double"/>
<Property Name="product_ID" Type="Edm.String"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="OrdersService.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="OrdersService.Orders_Items"/>
</EntityType>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders_Items"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="OrdersService.Orders_Items"/>
</Action>
<Action Name="draftActivate" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Action Name="draftEdit" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<Parameter Name="PreserveChanges" Type="Edm.Boolean"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Annotations Target="OrdersService.Currencies">
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="name"/>
</Record>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Currencies/name">
<Annotation Term="Common.Label" String="{i18n>Name}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/descr">
<Annotation Term="Common.Label" String="{i18n>Description}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/code">
<Annotation Term="Common.Label" String="{i18n>CurrencyCode}"/>
<Annotation Term="Common.Text" Path="name"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/symbol">
<Annotation Term="Common.Label" String="{i18n>CurrencySymbol}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/name">
<Annotation Term="Common.Label" String="{i18n>Name}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/descr">
<Annotation Term="Common.Label" String="{i18n>Description}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/code">
<Annotation Term="Common.Label" String="{i18n>CurrencyCode}"/>
<Annotation Term="Common.Text" Path="name"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftAdministrativeData}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftUUID">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftUUID}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/CreationDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_CreationDateTime}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/CreatedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_CreatedByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftIsCreatedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsCreatedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/LastChangeDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangeDateTime}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/LastChangedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangedByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/InProcessByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_InProcessByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftIsProcessedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsProcessedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders">
<Annotation Term="UI.Facets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Details}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Details"/>
</Record>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>OrderItems}"/>
<PropertyValue Property="Target" AnnotationPath="Items/@UI.LineItem"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Created">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Details">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="currency/code"/>
<PropertyValue Property="Label" String="Currency"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Modified">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="modifiedBy"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="modifiedAt"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.HeaderFacets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Created}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Created"/>
</Record>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Modified}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Modified"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.HeaderInfo">
<Record Type="UI.HeaderInfoType">
<PropertyValue Property="Description">
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
</Record>
</PropertyValue>
<PropertyValue Property="Title">
<Record Type="UI.DataField">
<PropertyValue Property="Label" String="Order number "/>
<PropertyValue Property="Value" Path="OrderNo"/>
</Record>
</PropertyValue>
<PropertyValue Property="TypeName" String="Order"/>
<PropertyValue Property="TypeNamePlural" String="Orders"/>
</Record>
</Annotation>
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
<PropertyValue Property="Label" String="Customer"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
<PropertyValue Property="Label" String="Date"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="OrderNo"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.LineItem">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="OrderNo"/>
<PropertyValue Property="Label" String="OrderNo"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="buyer"/>
<PropertyValue Property="Label" String="Customer"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
<PropertyValue Property="Label" String="Date"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.SelectionFields">
<Collection>
<PropertyPath>createdAt</PropertyPath>
<PropertyPath>createdBy</PropertyPath>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.EntityContainer/Orders">
<Annotation Term="Common.DraftRoot">
<Record Type="Common.DraftRootType">
<PropertyValue Property="ActivationAction" String="OrdersService.draftActivate"/>
<PropertyValue Property="EditAction" String="OrdersService.draftEdit"/>
<PropertyValue Property="PreparationAction" String="OrdersService.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Orders/createdAt">
<Annotation Term="Common.Label" String="{i18n>CreatedAt}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Immutable" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="false"/>
</Annotations>
<Annotations Target="OrdersService.Orders/createdBy">
<Annotation Term="Common.Label" String="{i18n>CreatedBy}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
<Annotation Term="Core.Immutable" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="false"/>
</Annotations>
<Annotations Target="OrdersService.Orders/modifiedAt">
<Annotation Term="Common.Label" String="{i18n>ChangedAt}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/modifiedBy">
<Annotation Term="Common.Label" String="{i18n>ChangedBy}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/OrderNo">
<Annotation Term="Common.Label" String="Order Number"/>
</Annotations>
<Annotations Target="OrdersService.Orders/buyer">
<Annotation Term="Common.Label" String="{i18n>UserID}"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/currency">
<Annotation Term="Common.Label" String="{i18n>Currency}"/>
<Annotation Term="Core.Description" String="{i18n>CurrencyCode.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/currency_code">
<Annotation Term="Common.Label" String="{i18n>Currency}"/>
<Annotation Term="Common.ValueList">
<Record Type="Common.ValueListType">
<PropertyValue Property="Label" String="{i18n>Currency}"/>
<PropertyValue Property="CollectionPath" String="Currencies"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="Common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty" PropertyPath="currency_code"/>
<PropertyValue Property="ValueListProperty" String="code"/>
</Record>
<Record Type="Common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty" String="name"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="Core.Description" String="{i18n>CurrencyCode.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items">
<Annotation Term="UI.Facets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>OrderItems}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.Identification"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="amount"/>
<PropertyValue Property="Label" String="Amount"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="title"/>
<PropertyValue Property="Label" String="Product"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="price"/>
<PropertyValue Property="Label" String="Unit Price"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.LineItem">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="product_ID"/>
<PropertyValue Property="Label" String="Product ID"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="title"/>
<PropertyValue Property="Label" String="Product Title"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="price"/>
<PropertyValue Property="Label" String="Unit Price"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="amount"/>
<PropertyValue Property="Label" String="Quantity"/>
</Record>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.EntityContainer/Orders_Items">
<Annotation Term="Common.DraftNode">
<Record Type="Common.DraftNodeType">
<PropertyValue Property="PreparationAction" String="OrdersService.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/amount">
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/Mandatory"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

464
orders/metadata.xml Normal file
View File

@@ -0,0 +1,464 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="OrdersService" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Currencies" EntityType="OrdersService.Currencies">
<NavigationPropertyBinding Path="texts" Target="Currencies_texts"/>
<NavigationPropertyBinding Path="localized" Target="Currencies_texts"/>
</EntitySet>
<EntitySet Name="Currencies_texts" EntityType="OrdersService.Currencies_texts"/>
<EntitySet Name="Orders" EntityType="OrdersService.Orders">
<NavigationPropertyBinding Path="Items" Target="Orders_Items"/>
<NavigationPropertyBinding Path="currency" Target="Currencies"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Orders"/>
</EntitySet>
<EntitySet Name="Orders_Items" EntityType="OrdersService.Orders_Items">
<NavigationPropertyBinding Path="up_" Target="Orders"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Orders_Items"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Currencies">
<Key>
<PropertyRef Name="code"/>
</Key>
<Property Name="name" Type="Edm.String" MaxLength="255"/>
<Property Name="descr" Type="Edm.String" MaxLength="1000"/>
<Property Name="code" Type="Edm.String" MaxLength="3" Nullable="false"/>
<Property Name="symbol" Type="Edm.String" MaxLength="5"/>
<Property Name="numcode" Type="Edm.Int32"/>
<Property Name="exponent" Type="Edm.Int32"/>
<Property Name="minor" Type="Edm.String"/>
<NavigationProperty Name="texts" Type="Collection(OrdersService.Currencies_texts)">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<NavigationProperty Name="localized" Type="OrdersService.Currencies_texts">
<ReferentialConstraint Property="code" ReferencedProperty="code"/>
</NavigationProperty>
</EntityType>
<EntityType Name="Currencies_texts">
<Key>
<PropertyRef Name="locale"/>
<PropertyRef Name="code"/>
</Key>
<Property Name="locale" Type="Edm.String" MaxLength="14" Nullable="false"/>
<Property Name="name" Type="Edm.String" MaxLength="255"/>
<Property Name="descr" Type="Edm.String" MaxLength="1000"/>
<Property Name="code" Type="Edm.String" MaxLength="3" Nullable="false"/>
</EntityType>
<EntityType Name="DraftAdministrativeData">
<Key>
<PropertyRef Name="DraftUUID"/>
</Key>
<Property Name="DraftUUID" Type="Edm.Guid" Nullable="false"/>
<Property Name="CreationDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="CreatedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsCreatedByMe" Type="Edm.Boolean"/>
<Property Name="LastChangeDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="LastChangedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="InProcessByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsProcessedByMe" Type="Edm.Boolean"/>
</EntityType>
<EntityType Name="Orders">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="createdAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="createdBy" Type="Edm.String" MaxLength="255"/>
<Property Name="modifiedAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="modifiedBy" Type="Edm.String" MaxLength="255"/>
<Property Name="OrderNo" Type="Edm.String"/>
<NavigationProperty Name="Items" Type="Collection(OrdersService.Orders_Items)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<Property Name="buyer" Type="Edm.String" MaxLength="255"/>
<NavigationProperty Name="currency" Type="OrdersService.Currencies">
<ReferentialConstraint Property="currency_code" ReferencedProperty="code"/>
</NavigationProperty>
<Property Name="currency_code" Type="Edm.String" MaxLength="3"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="OrdersService.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="OrdersService.Orders"/>
</EntityType>
<EntityType Name="Orders_Items">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid"/>
<NavigationProperty Name="up_" Type="OrdersService.Orders" Partner="Items">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="amount" Type="Edm.Int32"/>
<Property Name="title" Type="Edm.String"/>
<Property Name="price" Type="Edm.Double"/>
<Property Name="product_ID" Type="Edm.String"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="OrdersService.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="OrdersService.Orders_Items"/>
</EntityType>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders_Items"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="OrdersService.Orders_Items"/>
</Action>
<Action Name="draftActivate" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Action Name="draftEdit" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="OrdersService.Orders"/>
<Parameter Name="PreserveChanges" Type="Edm.Boolean"/>
<ReturnType Type="OrdersService.Orders"/>
</Action>
<Annotations Target="OrdersService.Currencies">
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="name"/>
</Record>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Currencies/name">
<Annotation Term="Common.Label" String="{i18n>Name}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/descr">
<Annotation Term="Common.Label" String="{i18n>Description}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/code">
<Annotation Term="Common.Label" String="{i18n>CurrencyCode}"/>
<Annotation Term="Common.Text" Path="name"/>
</Annotations>
<Annotations Target="OrdersService.Currencies/symbol">
<Annotation Term="Common.Label" String="{i18n>CurrencySymbol}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/name">
<Annotation Term="Common.Label" String="{i18n>Name}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/descr">
<Annotation Term="Common.Label" String="{i18n>Description}"/>
</Annotations>
<Annotations Target="OrdersService.Currencies_texts/code">
<Annotation Term="Common.Label" String="{i18n>CurrencyCode}"/>
<Annotation Term="Common.Text" Path="name"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftAdministrativeData}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftUUID">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftUUID}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/CreationDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_CreationDateTime}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/CreatedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_CreatedByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftIsCreatedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsCreatedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/LastChangeDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangeDateTime}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/LastChangedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangedByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/InProcessByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_InProcessByUser}"/>
</Annotations>
<Annotations Target="OrdersService.DraftAdministrativeData/DraftIsProcessedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsProcessedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders">
<Annotation Term="UI.Facets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Details}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Details"/>
</Record>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>OrderItems}"/>
<PropertyValue Property="Target" AnnotationPath="Items/@UI.LineItem"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Created">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Details">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="currency/code"/>
<PropertyValue Property="Label" String="Currency"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.FieldGroup" Qualifier="Modified">
<Record Type="UI.FieldGroupType">
<PropertyValue Property="Data">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="modifiedBy"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="modifiedAt"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="UI.HeaderFacets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Created}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Created"/>
</Record>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>Modified}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.FieldGroup#Modified"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.HeaderInfo">
<Record Type="UI.HeaderInfoType">
<PropertyValue Property="Description">
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
</Record>
</PropertyValue>
<PropertyValue Property="Title">
<Record Type="UI.DataField">
<PropertyValue Property="Label" String="Order number "/>
<PropertyValue Property="Value" Path="OrderNo"/>
</Record>
</PropertyValue>
<PropertyValue Property="TypeName" String="Order"/>
<PropertyValue Property="TypeNamePlural" String="Orders"/>
</Record>
</Annotation>
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdBy"/>
<PropertyValue Property="Label" String="Customer"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
<PropertyValue Property="Label" String="Date"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="OrderNo"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.LineItem">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="OrderNo"/>
<PropertyValue Property="Label" String="OrderNo"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="buyer"/>
<PropertyValue Property="Label" String="Customer"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="createdAt"/>
<PropertyValue Property="Label" String="Date"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.SelectionFields">
<Collection>
<PropertyPath>createdAt</PropertyPath>
<PropertyPath>createdBy</PropertyPath>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.EntityContainer/Orders">
<Annotation Term="Common.DraftRoot">
<Record Type="Common.DraftRootType">
<PropertyValue Property="ActivationAction" String="OrdersService.draftActivate"/>
<PropertyValue Property="EditAction" String="OrdersService.draftEdit"/>
<PropertyValue Property="PreparationAction" String="OrdersService.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Orders/createdAt">
<Annotation Term="Common.Label" String="{i18n>CreatedAt}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Immutable" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="false"/>
</Annotations>
<Annotations Target="OrdersService.Orders/createdBy">
<Annotation Term="Common.Label" String="{i18n>CreatedBy}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
<Annotation Term="Core.Immutable" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="false"/>
</Annotations>
<Annotations Target="OrdersService.Orders/modifiedAt">
<Annotation Term="Common.Label" String="{i18n>ChangedAt}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/modifiedBy">
<Annotation Term="Common.Label" String="{i18n>ChangedBy}"/>
<Annotation Term="Core.Computed" Bool="true"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
<Annotation Term="UI.HiddenFilter" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/OrderNo">
<Annotation Term="Common.Label" String="Order Number"/>
</Annotations>
<Annotations Target="OrdersService.Orders/buyer">
<Annotation Term="Common.Label" String="{i18n>UserID}"/>
<Annotation Term="Core.Description" String="{i18n>UserID.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/currency">
<Annotation Term="Common.Label" String="{i18n>Currency}"/>
<Annotation Term="Core.Description" String="{i18n>CurrencyCode.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/currency_code">
<Annotation Term="Common.Label" String="{i18n>Currency}"/>
<Annotation Term="Common.ValueList">
<Record Type="Common.ValueListType">
<PropertyValue Property="Label" String="{i18n>Currency}"/>
<PropertyValue Property="CollectionPath" String="Currencies"/>
<PropertyValue Property="Parameters">
<Collection>
<Record Type="Common.ValueListParameterInOut">
<PropertyValue Property="LocalDataProperty" PropertyPath="currency_code"/>
<PropertyValue Property="ValueListProperty" String="code"/>
</Record>
<Record Type="Common.ValueListParameterDisplayOnly">
<PropertyValue Property="ValueListProperty" String="name"/>
</Record>
</Collection>
</PropertyValue>
</Record>
</Annotation>
<Annotation Term="Core.Description" String="{i18n>CurrencyCode.Description}"/>
</Annotations>
<Annotations Target="OrdersService.Orders/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items">
<Annotation Term="UI.Facets">
<Collection>
<Record Type="UI.ReferenceFacet">
<PropertyValue Property="Label" String="{i18n>OrderItems}"/>
<PropertyValue Property="Target" AnnotationPath="@UI.Identification"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.Identification">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="amount"/>
<PropertyValue Property="Label" String="Amount"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="title"/>
<PropertyValue Property="Label" String="Product"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="price"/>
<PropertyValue Property="Label" String="Unit Price"/>
</Record>
</Collection>
</Annotation>
<Annotation Term="UI.LineItem">
<Collection>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="product_ID"/>
<PropertyValue Property="Label" String="Product ID"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="title"/>
<PropertyValue Property="Label" String="Product Title"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="price"/>
<PropertyValue Property="Label" String="Unit Price"/>
</Record>
<Record Type="UI.DataField">
<PropertyValue Property="Value" Path="amount"/>
<PropertyValue Property="Label" String="Quantity"/>
</Record>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.EntityContainer/Orders_Items">
<Annotation Term="Common.DraftNode">
<Record Type="Common.DraftNodeType">
<PropertyValue Property="PreparationAction" String="OrdersService.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/amount">
<Annotation Term="Common.FieldControl" EnumMember="Common.FieldControlType/Mandatory"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="OrdersService.Orders_Items/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -2,6 +2,12 @@
"name": "@capire/orders",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "^5"
"@capire/common": "*",
"@sap/cds": "^4.3.0"
},
"cds": {
"odata": {
"--containment": true
}
}
}

View File

@@ -11,7 +11,7 @@ class OrdersService extends cds.ApplicationService {
const { amount:before } = await cds.tx(req).run (
SELECT.one.from (OrderItems, oi => oi.amount) .where ({up__ID:ID, product_ID})
)
if (amount != before) await this.orderChanged (product_ID, amount-before)
if (amount != before) this.orderChanged (product_ID, amount-before)
}
})
@@ -20,7 +20,7 @@ class OrdersService extends cds.ApplicationService {
const Items = await cds.tx(req).run (
SELECT.from (OrderItems, oi => { oi.product_ID, oi.amount }) .where ({up__ID:ID})
)
if (Items) await Promise.all (Items.map(it => this.orderChanged (it.product_ID, -it.amount)))
if (Items) for (let it of Items) this.orderChanged (it.product_ID, -it.amount)
})
return super.init()

14
orders/test/model.cds Normal file
View File

@@ -0,0 +1,14 @@
service WithDraft {
@odata.draft.enabled
entity Boo as projection on Foo;
}
service WithoutDraft {
entity Boo as projection on Foo;
}
entity Foo {
key ID : UUID;
bar : Composition of many {
key pos : Integer; //> meant to be a local key only
}
}

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="WithDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithDraft.Boo">
<NavigationPropertyBinding Path="SiblingEntity" Target="Boo"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithDraft.Foo_bar)" Partner="up_" ContainsTarget="true"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Boo"/>
</EntityType>
<EntityType Name="DraftAdministrativeData">
<Key>
<PropertyRef Name="DraftUUID"/>
</Key>
<Property Name="DraftUUID" Type="Edm.Guid" Nullable="false"/>
<Property Name="CreationDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="CreatedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsCreatedByMe" Type="Edm.Boolean"/>
<Property Name="LastChangeDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="LastChangedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="InProcessByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsProcessedByMe" Type="Edm.Boolean"/>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="pos"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<NavigationProperty Name="up_" Type="WithDraft.Boo" Nullable="false" Partner="bar"/>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Foo_bar"/>
</EntityType>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Foo_bar"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Foo_bar"/>
</Action>
<Action Name="draftActivate" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftEdit" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="PreserveChanges" Type="Edm.Boolean"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Annotations Target="WithDraft.EntityContainer/Boo">
<Annotation Term="Common.DraftRoot">
<Record Type="Common.DraftRootType">
<PropertyValue Property="ActivationAction" String="WithDraft.draftActivate"/>
<PropertyValue Property="EditAction" String="WithDraft.draftEdit"/>
<PropertyValue Property="PreparationAction" String="WithDraft.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="WithDraft.Boo/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftAdministrativeData}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftUUID">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftUUID}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreationDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_CreationDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreatedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_CreatedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsCreatedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsCreatedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangeDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangeDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/InProcessByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_InProcessByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsProcessedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsProcessedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="WithoutDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithoutDraft.Boo"/>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithoutDraft.Foo_bar)" Partner="up_" ContainsTarget="true"/>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="pos"/>
</Key>
<NavigationProperty Name="up_" Type="WithoutDraft.Boo" Nullable="false" Partner="bar"/>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="WithDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithDraft.Boo">
<NavigationPropertyBinding Path="bar" Target="Foo_bar"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Boo"/>
</EntitySet>
<EntitySet Name="Foo_bar" EntityType="WithDraft.Foo_bar">
<NavigationPropertyBinding Path="up_" Target="Boo"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Foo_bar"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithDraft.Foo_bar)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Boo"/>
</EntityType>
<EntityType Name="DraftAdministrativeData">
<Key>
<PropertyRef Name="DraftUUID"/>
</Key>
<Property Name="DraftUUID" Type="Edm.Guid" Nullable="false"/>
<Property Name="CreationDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="CreatedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsCreatedByMe" Type="Edm.Boolean"/>
<Property Name="LastChangeDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="LastChangedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="InProcessByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsProcessedByMe" Type="Edm.Boolean"/>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="pos"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid"/>
<NavigationProperty Name="up_" Type="WithDraft.Boo" Nullable="false" Partner="bar">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Foo_bar"/>
</EntityType>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Foo_bar"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Foo_bar"/>
</Action>
<Action Name="draftActivate" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftEdit" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="PreserveChanges" Type="Edm.Boolean"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Annotations Target="WithDraft.EntityContainer/Boo">
<Annotation Term="Common.DraftRoot">
<Record Type="Common.DraftRootType">
<PropertyValue Property="ActivationAction" String="WithDraft.draftActivate"/>
<PropertyValue Property="EditAction" String="WithDraft.draftEdit"/>
<PropertyValue Property="PreparationAction" String="WithDraft.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="WithDraft.Boo/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftAdministrativeData}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftUUID">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftUUID}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreationDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_CreationDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreatedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_CreatedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsCreatedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsCreatedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangeDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangeDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/InProcessByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_InProcessByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsProcessedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsProcessedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.EntityContainer/Foo_bar">
<Annotation Term="Common.DraftNode">
<Record Type="Common.DraftNodeType">
<PropertyValue Property="PreparationAction" String="WithDraft.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="WithoutDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithoutDraft.Boo">
<NavigationPropertyBinding Path="bar" Target="Foo_bar"/>
</EntitySet>
<EntitySet Name="Foo_bar" EntityType="WithoutDraft.Foo_bar">
<NavigationPropertyBinding Path="up_" Target="Boo"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithoutDraft.Foo_bar)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="up__ID"/>
<PropertyRef Name="pos"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="up_" Type="WithoutDraft.Boo" Nullable="false" Partner="bar">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
<edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
</edmx:Reference>
<edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
<edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
</edmx:Reference>
<edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/UI.xml">
<edmx:Include Alias="UI" Namespace="com.sap.vocabularies.UI.v1"/>
</edmx:Reference>
<edmx:DataServices>
<Schema Namespace="WithDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithDraft.Boo">
<NavigationPropertyBinding Path="bar" Target="Foo_bar"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Boo"/>
</EntitySet>
<EntitySet Name="Foo_bar" EntityType="WithDraft.Foo_bar">
<NavigationPropertyBinding Path="up_" Target="Boo"/>
<NavigationPropertyBinding Path="SiblingEntity" Target="Foo_bar"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithDraft.Foo_bar)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Boo"/>
</EntityType>
<EntityType Name="DraftAdministrativeData">
<Key>
<PropertyRef Name="DraftUUID"/>
</Key>
<Property Name="DraftUUID" Type="Edm.Guid" Nullable="false"/>
<Property Name="CreationDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="CreatedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsCreatedByMe" Type="Edm.Boolean"/>
<Property Name="LastChangeDateTime" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="LastChangedByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="InProcessByUser" Type="Edm.String" MaxLength="256"/>
<Property Name="DraftIsProcessedByMe" Type="Edm.Boolean"/>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="up__ID"/>
<PropertyRef Name="pos"/>
<PropertyRef Name="IsActiveEntity"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="up_" Type="WithDraft.Boo" Nullable="false" Partner="bar">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
<Property Name="IsActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasActiveEntity" Type="Edm.Boolean" Nullable="false"/>
<Property Name="HasDraftEntity" Type="Edm.Boolean" Nullable="false"/>
<NavigationProperty Name="DraftAdministrativeData" Type="WithDraft.DraftAdministrativeData" ContainsTarget="true"/>
<NavigationProperty Name="SiblingEntity" Type="WithDraft.Foo_bar"/>
</EntityType>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftPrepare" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Foo_bar"/>
<Parameter Name="SideEffectsQualifier" Type="Edm.String"/>
<ReturnType Type="WithDraft.Foo_bar"/>
</Action>
<Action Name="draftActivate" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Action Name="draftEdit" IsBound="true" EntitySetPath="in">
<Parameter Name="in" Type="WithDraft.Boo"/>
<Parameter Name="PreserveChanges" Type="Edm.Boolean"/>
<ReturnType Type="WithDraft.Boo"/>
</Action>
<Annotations Target="WithDraft.EntityContainer/Boo">
<Annotation Term="Common.DraftRoot">
<Record Type="Common.DraftRootType">
<PropertyValue Property="ActivationAction" String="WithDraft.draftActivate"/>
<PropertyValue Property="EditAction" String="WithDraft.draftEdit"/>
<PropertyValue Property="PreparationAction" String="WithDraft.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="WithDraft.Boo/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Boo/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftAdministrativeData}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftUUID">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftUUID}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreationDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_CreationDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/CreatedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_CreatedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsCreatedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsCreatedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangeDateTime">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangeDateTime}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/LastChangedByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_LastChangedByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/InProcessByUser">
<Annotation Term="Common.Label" String="{i18n>Draft_InProcessByUser}"/>
</Annotations>
<Annotations Target="WithDraft.DraftAdministrativeData/DraftIsProcessedByMe">
<Annotation Term="Common.Label" String="{i18n>Draft_DraftIsProcessedByMe}"/>
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.EntityContainer/Foo_bar">
<Annotation Term="Common.DraftNode">
<Record Type="Common.DraftNodeType">
<PropertyValue Property="PreparationAction" String="WithDraft.draftPrepare"/>
</Record>
</Annotation>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/IsActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasActiveEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/HasDraftEntity">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
<Annotations Target="WithDraft.Foo_bar/DraftAdministrativeData">
<Annotation Term="UI.Hidden" Bool="true"/>
</Annotations>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="WithoutDraft" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="EntityContainer">
<EntitySet Name="Boo" EntityType="WithoutDraft.Boo">
<NavigationPropertyBinding Path="bar" Target="Foo_bar"/>
</EntitySet>
<EntitySet Name="Foo_bar" EntityType="WithoutDraft.Foo_bar">
<NavigationPropertyBinding Path="up_" Target="Boo"/>
</EntitySet>
</EntityContainer>
<EntityType Name="Boo">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="bar" Type="Collection(WithoutDraft.Foo_bar)" Partner="up_">
<OnDelete Action="Cascade"/>
</NavigationProperty>
</EntityType>
<EntityType Name="Foo_bar">
<Key>
<PropertyRef Name="up__ID"/>
<PropertyRef Name="pos"/>
</Key>
<Property Name="up__ID" Type="Edm.Guid" Nullable="false"/>
<NavigationProperty Name="up_" Type="WithoutDraft.Boo" Nullable="false" Partner="bar">
<ReferentialConstraint Property="up__ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="pos" Type="Edm.Int32" Nullable="false"/>
</EntityType>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

32038
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,36 +11,29 @@
"@capire/hello": "./hello",
"@capire/media": "./media",
"@capire/orders": "./orders",
"@capire/reviews": "./reviews",
"@capire/suppliers": "./suppliers",
"@sap/cds": "^5",
"express": "^4"
"@capire/reviews": "./reviews"
},
"devDependencies": {
"cds-swagger-ui-express": "^0.2.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"sqlite3": "^5.0.0"
"sqlite3": "^5"
},
"scripts": {
"cleanup": "rm -rf node_modules && rm -rf */node_modules && rm -rf */*/node_modules",
"registry": "node .registry/server.js",
"registry": "cd .registry && node server.js",
"bookshop": "cds watch bookshop",
"fiori": "cds watch fiori",
"media": "cds watch media",
"mocha": "npx mocha || echo",
"jest": "npx jest",
"test": "npm run jest --silent && npm run test:hello",
"test:hello": "cd hello && npm test"
},
"jest": {
"testEnvironment": "node",
"testMatch": ["**/*.test.js"]
"test": "npm run jest --silent"
},
"mocha": {
"parallel": true
},
"jest": {
"testEnvironment": "node"
},
"license": "SAP SAMPLE CODE LICENSE",
"private": true
}

View File

@@ -37,7 +37,7 @@ const reviews = new Vue ({
reviews.message = {}
},
newReview () {
async newReview () {
reviews.review = {}
reviews.message = {}
setTimeout (()=> $('form > input').focus(), 111)

View File

@@ -7,7 +7,7 @@
"index.cds"
],
"dependencies": {
"@sap/cds": "^5",
"@sap/cds": "^4",
"express": "^4.17.1"
},
"scripts": {

View File

@@ -2,23 +2,20 @@
## Run all-in-one
Open a terminal window and run the `fiori` app in it:
Open a terminal window and run the bookshop in it:
```sh
npm run fiori
npm run bookshop
```
## Run as Separate Services
Open two terminal windows. In the first one start the reviews service stand-alone:
## Run as separate services
Open two terminal windows, and in the first one start the reviews service stand-alone:
```sh
npm run reviews-service
```
In the second one start the `fiori` app:
In the the second one start the bookshop:
```sh
npm run fiori
npm run bookshop
```

View File

@@ -8,16 +8,16 @@ service ReviewsService {
action unlike (review: type of Reviews:ID);
// Async API
event reviewed : projection on Reviews {
subject, //> recieved new reviews
rating //> new avg rating
event reviewed : {
subject: type of Reviews:subject;
rating: Decimal(2,1)
}
// Input validation
annotate Reviews with {
subject @mandatory;
title @mandatory;
rating @assert.range;
rating @assert.enum;
}
}

View File

@@ -1,5 +1,5 @@
const cds = require ('@sap/cds')
module.exports = cds.service.impl (function(){
module.exports = cds.service.impl (async function(){
// Get the CSN definition for Reviews from the db schema for sub-sequent queries
// ( Note: we explicitly specify the namespace to support embedded reuse )
@@ -16,7 +16,7 @@ module.exports = cds.service.impl (function(){
SELECT.one (['round(avg(rating),2) as rating']) .from (Reviews) .where ({subject})
)
global.it || console.log ('< emitting:', 'reviewed', { subject, rating })
await this.emit ('reviewed', { subject, rating })
this.emit ('reviewed', { subject, rating })
})
// Increment counter for reviews considered helpful

View File

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

View File

@@ -1,19 +0,0 @@
{
"name": "@capire/fiori",
"version": "1.0.0",
"dependencies": {
"@capire/bookshop": "*",
"@capire/reviews": "*",
"@sap/cds": "^5",
"express": "^4.17.1"
},
"cds": {
"requires": {
"auth": { "strategy": "dummy" },
"ReviewsService": {
"kind": "odata",
"model": "@capire/reviews"
}
}
}
}

View File

@@ -1,20 +0,0 @@
const cds = require ('@sap/cds')
cds.once('bootstrap',(app)=>{
// Delegate to imported apps (reviews only when mocked)
app.serve ('/bookshop').from ('@capire/bookshop','app/vue')
app.serve ('/reviews',).from ('@capire/reviews','app/vue')
})
cds.once('served', async ()=>{
// Update Books' average ratings when ReviewsService signals updated reviews
const ReviewsService = await cds.connect.to ('ReviewsService')
ReviewsService.on ('reviewed', (msg) => {
console.debug ('> received:', msg.event, msg.data)
const { subject, rating } = msg.data
return UPDATE('Books',subject).with({rating})
})
})
module.exports = cds.server

View File

@@ -1,11 +0,0 @@
namespace sap.capire.bookshop; //> allows UPDATE('Books')...
//
// Extend Books with access to Reviews and average ratings
//
using { CatalogService.ListOfBooks, sap.capire.bookshop.Books } from '@capire/bookshop';
using { ReviewsService.Reviews } from '@capire/reviews';
extend Books with {
reviews : Composition of many Reviews on reviews.subject = $self.ID;
rating : Reviews:rating;
}
extend projection ListOfBooks with { rating }

View File

@@ -1,20 +1,19 @@
# Overview of Samples
The following list gives an overview of the samples provided in subdirectories.
Each sub directory essentially is an individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup.
The list below gives an overview of the samples provided in subdirectories.
Each sub directory essentially is a individual npm package arranged in an [all-in-one monorepo](all-in-one-monorepo) umbrella setup.
## [@capire/hello-world](hello)
- A simplistic [Hello World](https://cap.cloud.sap/docs/get-started/hello-world) service using [CDS](https://cap.cloud.sap/docs/cds/) and [cds.services](https://cap.cloud.sap/docs/node.js/api#services-api).
- [Typescript support](https://cap.cloud.sap/docs/get-started/using-typescript)
## [@capire/bookshop](bookshop)
- [Getting Started](https://cap.cloud.sap/docs/get-started/in-a-nutshell) with CAP, briefly introducing:
- [Project Setup](https://cap.cloud.sap/docs/get-started/) and [Layouts](https://cap.cloud.sap/docs/get-started/projects)
- [Domain Modeling](https://cap.cloud.sap/docs/guides/domain-models)
- [Domain Modelling](https://cap.cloud.sap/docs/guides/domain-models)
- [Defining Services](https://cap.cloud.sap/docs/guides/providing-services)
- [Generic Providers](https://cap.cloud.sap/docs/guides/generic-providers)
- [Adding Custom Logic](https://cap.cloud.sap/docs/guides/service-impl)
@@ -23,7 +22,7 @@ Each sub directory essentially is an individual npm package arranged in an [all-
## [@capire/common](common)
- Showcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering:
- Showcases how to extend [@sap/cds/common](https://cap.cloud.sap/docs/cds/common) thereby covering...
- Building [extension packages](https://cap.cloud.sap/docs/guides/domain-models#aspects-extensibility)
- Providing [reuse packages](https://cap.cloud.sap/docs/get-started/projects#sharing-and-reusing-content)
- [Verticalization](https://cap.cloud.sap/docs/cds/common#adapting-to-your-needs)
@@ -33,46 +32,38 @@ Each sub directory essentially is an individual npm package arranged in an [all-
## [@capire/orders](orders)
- A standalone orders management service, demonstrating:
- A standalone orders mgmt service, demonstrating...
- Using [Compositions](https://cap.cloud.sap/docs/cds/cdl#compositions) in [Domain Models](https://cap.cloud.sap/docs/guides/domain-models), along with
- [Serving deeply nested documents](https://cap.cloud.sap/docs/guides/generic-providers#serving-structured-data)
## [@capire/reviews](reviews)
- Shows how to implement a modular service to manage product reviews, including:
- Shows how to implement a modular service to manage product reviews, including...
- Consuming other services synchronously and asynchronously
- Serving requests synchronously
- Emitting events asynchronously
- Grow as you go, with:
- Grow as you go, with...
- Mocking app services
- Running service meshes
- Late-cut Micro Services
- As well as managed data, input validations, and authorization
## [@capire/suppliers](suppliers)
- Shows how to integrate remote services, in this case the BusinessPartner service from SAP S/4HANA.
- Extending [@capire/bookshop](bookshop) with suppliers from SAP S/4HANA
- Providing that as a pre-built integration & extension package
- Used in [@capire/fiori](fiori)
- As well as managed data, input validations and authorization
## [@capire/fiori](fiori)
- A [composite app, reusing and combining](https://cap.cloud.sap/docs/guides/verticalize) these packages:
- [@capire/bookshop](bookshop)
- [@capire/common](common)
- [@capire/orders](orders)
- [@capire/reviews](reviews)
- [@capire/suppliers](suppliers)
- [Adds an SAP Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to:
- [@capire/orders](orders)
- [@capire/common](common)
- [Adds a Fiori elements application](https://cap.cloud.sap/docs/guides/fiori/) to bookshop, thereby introducing to...
- [OData Annotations](https://cap.cloud.sap/docs/guides/fiori#adding-odata-annotations) in `.cds` files
- Support for [Fiori Draft](https://cap.cloud.sap/docs/guides/fiori#draft)
- Support for [Value Helps](https://cap.cloud.sap/docs/guides/fiori#value-help)
- Serving SAP Fiori apps locally
- Serving Fiori apps locally
- [The Vue.js app](bookshop/app/vue) imported from bookshop is served as well
- [OpenAPI export + Swagger UI](https://cap.cloud.sap/docs/advanced/openapi)
<br>

View File

@@ -1 +0,0 @@
PORT = 4006

View File

@@ -1,31 +0,0 @@
Books = Books
Book = Book
ID = ID
Title = Title
Author = Author
AuthorID = Author ID
Stock = Stock
Name = Name
AuthorName = Author's Name
DateOfBirth = Date of Birth
DateOfDeath = Date of Death
PlaceOfBirth = Place of Birth
PlaceOfDeath = Place of Death
Age = Age
Authors = Authors
Order = Order
Orders = Orders
Price = Price
Genre = Genre
Genres = Genres
SubGenres = Sub Genres
NumCode = Numeric Code
MinorUnit = Minor Unit
Exponent = Exponent
Supplier = Supplier
SupplierName = Supplier Name
Id = Id
Name = Name

View File

@@ -1,18 +0,0 @@
Books = Bücher
Book = Buch
ID = ID
Title = Titel
Authors = Autoren
Author = Autor
AuthorID = ID des Autors
AuthorName = Name des Autors
Age = Alter
Name = Name
Stock = Bestand
Order = Bestellung
Orders = Bestellungen
Price = Preis
Supplier = Lieferant
SupplierName = Lieferantenname
Id = Id
Name = Name

View File

@@ -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: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=bookshop",
applicationType : "URL",
url: "/browse/webapp",
navigationMode: "embedded"
},
"manage-books": {
title: "Manage Books",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=admin",
applicationType : "URL",
url: "/admin/webapp",
navigationMode: "embedded"
},
"manage-orders": {
title: "Manage Orders",
description: "w/ SAP Fiori Elements",
additionalInformation: "SAPUI5.Component=orders",
applicationType : "URL",
url: "/orders/webapp",
navigationMode: "embedded"
}
}
};
</script>
<script id="sap-ushell-bootstrap" src="https://sapui5.hana.ondemand.com/test-resources/sap/ushell/bootstrap/sandbox.js"></script>
<script id="sap-ui-bootstrap" src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.m, sap.ushell, sap.collaboration, sap.ui.layout"
data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-frameOptions="allow"
></script>
<script>
sap.ui.getCore().attachInit(()=> sap.ushell.Container.createRenderer().placeAt("content"))
</script>
</head>
<body class="sapUiBody" id="content"></body>
</html>

View File

@@ -1,105 +0,0 @@
//using { AdminService } from '../../db/schema';
////////////////////////////////////////////////////////////////////////////
//
// Books Object Page
//
annotate AdminService.Books with @(
UI: {
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>General}', Target: '@UI.FieldGroup#General'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Translations}', Target: 'texts/@UI.LineItem'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Supplier}', Target: '@UI.FieldGroup#Supplier'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Admin}', Target: '@UI.FieldGroup#Admin'},
],
FieldGroup#General: {
Data: [
{Value: title},
{Value: author_ID},
{Value: genre_ID},
{Value: descr},
]
},
FieldGroup#Details: {
Data: [
{Value: stock},
{Value: price},
{Value: currency_code, Label: '{i18n>Currency}'},
]
},
FieldGroup#Supplier: {
Data: [
{Value: supplier_ID, Label: '{i18n>Id}'},
{Value: supplier.name, Label: '{i18n>Name}'}
]
},
FieldGroup#Admin: {
Data: [
{Value: createdBy},
{Value: createdAt},
{Value: modifiedBy},
{Value: modifiedAt}
]
}
}
);
annotate AdminService.Authors with @(
UI: {
HeaderInfo: {
Description: {Value: lifetime}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Books}', Target: 'books/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: placeOfBirth},
{Value: placeOfDeath},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: age, Label: '{i18n>Age}'},
]
},
}
);
////////////////////////////////////////////////////////////
//
// Draft for Localized Data
//
annotate sap.capire.bookshop.Books with @fiori.draft.enabled;
annotate AdminService.Books with @odata.draft.enabled;
annotate AdminService.Books.texts with @(
UI: {
Identification: [{Value:title}],
SelectionFields: [ locale, title ],
LineItem: [
{Value: locale, Label: 'Locale'},
{Value: title, Label: 'Title'},
{Value: descr, Label: 'Description'}
]
}
);
// Add Value Help for Locales
annotate AdminService.Books.texts {
locale @ValueList:{entity:'Languages',type:#fixed}
}
// In addition we need to expose Languages through AdminService
using { sap } from '@sap/cds/common';
extend service AdminService {
entity Languages as projection on sap.common.Languages;
}
annotate AdminService.Suppliers with
@(Capabilities.SearchRestrictions : {
Searchable : false
});

View File

@@ -1,8 +0,0 @@
sap.ui.define(["sap/fe/core/AppComponent"], function(AppComponent) {
"use strict";
return AppComponent.extend("admin.Component", {
metadata: { manifest: "json" }
});
});
/* eslint no-undef:0 */

View File

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

View File

@@ -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.templates": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"uri": "i18n/i18n.properties"
},
"": {
"dataSource": "AdminService",
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect" : true,
"earlyRequests": true,
"groupProperties": {
"default": {
"submit": "Auto"
}
}
}
}
},
"routing": {
"routes": [
{
"pattern": ":?query:",
"name": "BooksList",
"target": "BooksList"
},
{
"pattern": "Books({key}):?query:",
"name": "BooksDetails",
"target": "BooksDetails"
},
{
"pattern": "Books({key}/author({key2}):?query:",
"name": "AuthorsDetails",
"target": "AuthorsDetails"
}
],
"targets": {
"BooksList": {
"type": "Component",
"id": "BooksList",
"name": "sap.fe.templates.ListReport",
"options": {
"settings" : {
"entitySet" : "Books",
"navigation" : {
"Books" : {
"detail" : {
"route" : "BooksDetails"
}
}
}
}
}
},
"BooksDetails": {
"type": "Component",
"id": "BooksDetailsList",
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings" : {
"entitySet" : "Books",
"navigation" : {
"Authors" : {
"detail" : {
"route" : "AuthorsDetails"
}
}
}
}
}
},
"AuthorsDetails": {
"type": "Component",
"id": "AuthorsDetailsList",
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings" : {
"entitySet" : "Authors"
}
}
}
}
},
"contentDensities": {
"compact": true,
"cozy": true
}
},
"sap.ui": {
"technology": "UI5",
"fullWidth": false
},
"sap.fiori": {
"registrationIds": [],
"archeType": "transactional"
}
}

View File

@@ -1,265 +0,0 @@
/*
Common Annotations shared by all apps
*/
using { sap.capire.bookshop as my } from '@capire/bookshop';
using { sap.common } from '@capire/common';
////////////////////////////////////////////////////////////////////////////
//
// Books Lists
//
annotate my.Books with @(
Common.SemanticKey: [title],
UI: {
Identification: [{Value:title}],
SelectionFields: [ ID, author_ID, price, currency_code, supplier.ID, supplier.name ],
LineItem: [
{Value: ID},
{Value: title},
{Value: author.name, Label:'{i18n>Author}'},
{Value: genre.name},
{Value: stock},
{Value: price},
{Value: currency.symbol, Label:' '},
{Value: supplier.ID},
{Value: supplier.name}
]
}
) {
author @ValueList.entity:'Authors';
};
////////////////////////////////////////////////////////////////////////////
//
// Books Details
//
annotate my.Books with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Book}',
TypeNamePlural: '{i18n>Books}',
Title: {Value: title},
Description: {Value: author.name}
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Books Elements
//
annotate my.Books with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
title @title:'{i18n>Title}';
genre @title:'{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly };
author @title:'{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly };
price @title:'{i18n>Price}' @Measures.ISOCurrency: currency_code;
stock @title:'{i18n>Stock}';
descr @UI.MultiLineText;
supplier_ID @title:'{i18n>Supplier}';
}
annotate my.Suppliers with {
name @title:'{i18n>SupplierName}';
ID @title:'{i18n>Supplier}';
}
////////////////////////////////////////////////////////////////////////////
//
// Genres List
//
annotate my.Genres with @(
Common.SemanticKey: [name],
UI: {
SelectionFields: [ name ],
LineItem:[
{Value: name},
{Value: parent.name, Label: 'Main Genre'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genre Details
//
annotate my.Genres with @(
UI: {
Identification: [{Value:name}],
HeaderInfo: {
TypeName: '{i18n>Genre}',
TypeNamePlural: '{i18n>Genres}',
Title: {Value: name},
Description: {Value: ID}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>SubGenres}', Target: 'children/@UI.LineItem'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Genres Elements
//
annotate my.Genres with {
ID @title: '{i18n>ID}';
name @title: '{i18n>Genre}';
}
////////////////////////////////////////////////////////////////////////////
//
// Authors List
//
annotate my.Authors with @(
Common.SemanticKey: [name],
UI: {
Identification: [{Value:name}],
SelectionFields: [ name ],
LineItem:[
{Value: ID},
{Value: name},
{Value: dateOfBirth},
{Value: dateOfDeath},
{Value: placeOfBirth},
{Value: placeOfDeath},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Author Details
//
annotate my.Authors with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Author}',
TypeNamePlural: '{i18n>Authors}',
Title: {Value: name},
Description: {Value: dateOfBirth}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Target: 'books/@UI.LineItem'},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Authors Elements
//
annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>Name}';
dateOfBirth @title:'{i18n>DateOfBirth}';
dateOfDeath @title:'{i18n>DateOfDeath}';
placeOfBirth @title:'{i18n>PlaceOfBirth}';
placeOfDeath @title:'{i18n>PlaceOfDeath}';
}
////////////////////////////////////////////////////////////////////////////
//
// Languages List
//
annotate common.Languages with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: code},
{Value: name},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Language Details
//
annotate common.Languages with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Language}',
TypeNamePlural: '{i18n>Languages}',
Title: {Value: name},
Description: {Value: descr}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
],
FieldGroup#Details: {
Data: [
{Value: code},
{Value: name},
{Value: descr}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies List
//
annotate common.Currencies with @(
Common.SemanticKey: [code],
Identification: [{Value:code}],
UI: {
SelectionFields: [ name, descr ],
LineItem:[
{Value: descr},
{Value: symbol},
{Value: code},
],
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currency Details
//
annotate common.Currencies with @(
UI: {
HeaderInfo: {
TypeName: '{i18n>Currency}',
TypeNamePlural: '{i18n>Currencies}',
Title: {Value: descr},
Description: {Value: code}
},
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Extended}', Target: '@UI.FieldGroup#Extended'},
],
FieldGroup#Details: {
Data: [
{Value: name},
{Value: symbol},
{Value: code},
{Value: descr}
]
},
FieldGroup#Extended: {
Data: [
{Value: numcode},
{Value: minor},
{Value: exponent}
]
},
}
);
////////////////////////////////////////////////////////////////////////////
//
// Currencies Elements
//
annotate common.Currencies with {
numcode @title:'{i18n>NumCode}';
minor @title:'{i18n>MinorUnit}';
exponent @title:'{i18n>Exponent}';
}

View File

@@ -1,12 +0,0 @@
/*
This model controls what gets served to Fiori frontends...
*/
using from './admin/fiori-service';
//using from './browse/fiori-service';
using from './common';
using from '@capire/common';
// only works in case of embedded orders service
using from '@capire/orders/app/orders/fiori-service';

View File

@@ -1 +0,0 @@
using from './srv/mashup';

View File

@@ -1 +0,0 @@
<mxfile host="Electron" modified="2021-06-16T08:41:31.745Z" agent="5.0 (Macintosh; Intel Mac OS X 11_4_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="keOnkQccb51SB06o_xsB" version="14.6.13" type="device"><diagram id="kIZwyAu_b989F7xyfxsf" name="Page-1">7Z1bc6M2GIZ/TabthXdAEpK4zGbTwzSdbutOu3u1g0G2mWBwAcdJf31FQNhI2NY6BuSWm10ksDDo4TvpxbmBd6vnH1JvvfwlCVh0A6zg+QZ+uAHAti3M/yt6Xqoe4pKyZ5GGQdln7Tqm4T+sOlD0bsKAZVVf2ZUnSZSH62ann8Qx8/NGn5emybZ52DyJgkbH2luwxtcoOqa+FzHlsL/CIF+WvRSQXf+PLFwsxZlt7JZ7Vp44uBo4W3pBst3rgvc38C5NkrzcWj3fsai4e8378v2BvfUXS1mc63zA/Xj/2wZtYn/rfdnmf9wv8gxOqlGevGhTXfCUj4B41/tNFsYsyz56aR6z9KYYHUf8TO9nvIEXxZboydZeXFxh/lLdNvz3pris9/Mkzifb6v7c8kPiJF150e4AMdK3fN/vbJXkjG9MWfoU+nwLe6s13xnPsuK/78Tp+Dcszyi+BWicHKTJJg5Ycdk2371dhjmbrj2/2LvlmPK+Zb6Kqt3ZI8v9YqYs3nhiaR7yyb+NwkXM+/KkONqrWhGbF+fzUr/ilJTX7ofxgrforvXweuQHXN2CafXdxN3mZ2HPB6fRruHgjxVLVixPX/gh1QeAWwFWPVEurNrbHZ7ARWXfch9NpwLRqx6JRT32jhq+UYHTDtGXT7/gzfa36f2E/BxMH371pg94golC0UPiF9NsffByb+Zl7BLsIMtqByereHk9Kpjxf/4zqChctNBzEBUM7QYqNkAKKsgmKirQsjtCRRhDLYPzzWhxTMCopqHCiFCsaXEQ6Qoj1W/VrIhJuv2iEHWIJv4dZnJfED61Q7fDJ1ytE34TA755+/Gno2an7m4MezGKBC8+n2V+oU0GQNEOo+guiZL09UxwTn3m+8UoeZo8sr09M+ogx6r3iJgH1N/3bShh0HReVLVINnVVlHBXvguA0yRNN+t1FLI0uzhBJTbZZpaxnG9ssleYrgejOcPtGAXEnVm9YUSxPTRGsAUjaV5YHNwWGQlvzaLEf+T3gndVht3GZfP7sDjx62zszdLBe5Ylm9RnGnznXrpguYZJZYFIiA7Mwf49FvczZZGXh0/NnKntJlfDfUzCON/NJxGBSTWfDoTNIcrLrD61n+NIA3GX0wTDlSa8vA3KQK9zXl/jGzBACga3wSqMd/FFp/GMiLrr042BcGsEg62WQJg4LRGMg7syGI5Cyh3PlaJkcUFWDmZMIyjtoNTVqSOg8JSpV1CwAsqYXBuACgans6K+k2u1DnMwlv0pnicXD2dTxsf2vTIlCjid1xTMBh6j89ZgFvuUzeZdBbNExI7HciLc4py6C2Zpp8Esv1Xpy6f9xuf9xofn6iNl60W0nsP80972573t3UeKhvhE4GXLmpw3RNBEN4IGw0XQkEqJtWxkdCNoCMjxgbqOoN3T7GVLb11s+i9RyI1DCk9bhllpRh5mdYfnPy5ejcuvm5wPw4QJKQlGsjW5wIPuulLWaqlZq/D2+8856Oo5h2oxVvEX75Pk8fJ1j6iKYEYfcZ6PaC149OojxNLCHjtZI7Q47C+SNYtlj7DvKqQw73zLLfg+bbmJpuWupmNivUNAxHvCUCoTItbb9ufDuYy5l1d2XAucZ+4pbBZMbBtIIx2w97uRxIHJfP5axby8T4DgIGtv4awPsHRDgh1YPOVseuCJDfpEi0J6mUiC0vMiiV7Jaivb9lb9n3PnAaw/i3P/yKL1VTlCh9EAtTlCCmYQ464cISRNe4Wx+nD07AjVku8xo9Rf5V+wbXTegokU2FBpyVjX2jjEPj5Qx3kLVOu5PcXS7HmdXNm64VDWwyHNmM0A66FWdxVsvn0Is4L0ZM4n2BohGhgiKsXeBkDUVvc1wgU5mi5IBNRDuCBE8DunMaMEofOckDoUdXp2Q92Wbt+AAr4CFAjE75BlE4hsC1lIUpmcjYULrHeOSwF2qOu4jpRg9Y0Iaqv6DZJFa1sHqInELot2MGnKpCe2ujzXYRZNoLSoc24W7WC50mNeFi0WxMe3CK5m9RraTawmiELl+WgV9Xa3fI1Ai2EaRb3mi3pJ009OuEtTWOpXjokGreuNqt7LcIQsOjRHphb3kG5xTxjVYcLpZrF2QqCkktPW9UoCYZ7yulph0MVCHLW8Nwp7DQxjMFZdT8/KXqSW9EZl7+CkUIucJqVnaS8a35s1khVZXdGaHPWs7XU0qjZD6OIMEUg5wJGmbGhxnaPxiuoorjOBHblW15539Lqi46j567CKJ0Hz6Xzj62u1gyueULMgP4GYNsfQljxJrw+pIxlQrXXactpR86Rhn4bSPFEp+cUilh3OPrWpXUyoiwi4L2enuhA9Ucnh1G9vfPXqELVPjNRxXcTR0K+MipWhZU+SVN0EA6LxquKoezKKIorkWtvwFJmqdnF01S4irh5E+ERltdKkTqO/WvmkjoVEFN+XK9J4c3AYGHRfIB0SBoKOSZ/OB8OFx7RPvUOCTRE/6VsIpAmFQeInJMe35ybUsqrfyIQaq5W+Uf5kdoVfXjds+2XMntVPwuKO6qfrVj9B4VEGE63gUfx0feInIv2ckAEYmap9wrraJ2FSjdA+2eIXAt6qfQJALwS6WHgzSp+uIYSpiyHDKZ/wqHwyEBRF+dQCSs/CJ6xRt/sfi1moHFKKH0AZSsuC20pro5blCrQstvhrIYPV6IlacRtWyiJgPh0+Xr+UBcAzg05ZyaIMZEDdjah1t1HIYvIKIhz8l02FYzUupxUsX84o9aBbAecuCsmyFWWgjlNaolEhG/UGpqhWhrMWGkrJUaRiFDS1SGU4aEyVRhLtsqk7nItRdSTnSiPVkWy3Zy/Tpo00ggRdkeyQJBwXpJxLxXE5Su+EfN0Pd3WXE2vbBhGnXrEa5eycWBaj9JsT8+bubwWXh+/+5DK8/xc=</diagram></mxfile>

View File

@@ -1,40 +0,0 @@
{
"name": "@capire/suppliers",
"version": "1.0.0",
"description": "Shows integration with SAP S/4HANA, in turn provided as a reusable extension package to bookshop.",
"private": true,
"dependencies": {
"@capire/common": "*",
"@sap/cds": ">=4",
"express": "^4"
},
"scripts": {
"start": "cds run --in-memory?",
"watch": "cds watch",
"mocked-s4": "cds mock API_BUSINESS_PARTNER"
},
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external/API_BUSINESS_PARTNER",
"[hybrid]": {
"credentials": {
"destination": "cap-api098",
"path": "/sap/opu/odata/sap/API_BUSINESS_PARTNER"
}
}
},
"[local-hybrid]": {
"messaging": {
"kind": "file-based-messaging"
}
},
"[hybrid]": {
"messaging": {
"kind": "enterprise-messaging-shared"
}
}
}
}
}

View File

@@ -1,51 +0,0 @@
@server = http://localhost:4006
@bpServer = http://localhost:5001
@authAlice = Authorization: Basic alice:
###
### Replication on changed Business Partner
###
### 1. Check supplier name before update ("A Company Making Everything")
GET {{server}}/admin/Books(299)?$expand=supplier
{{authAlice}}
### 2. Change Business Partner -> Triggers event that updates supplier replication
PATCH {{bpServer}}/api-business-partner/A_BusinessPartner('ACME')
Content-Type: application/json
{
"BusinessPartnerFullName": "A Company Making Everything *better*"
}
### 3. Check supplier name after update ("A Company Making Everything *better*")
GET {{server}}/admin/Books(299)?$expand=supplier
{{authAlice}}
###
### Replication on new assigned supplier
###
### 1. No supplier is assigned to "Wuthering Heights"
GET {{server}}/admin/Books(201)?$expand=supplier
{{authAlice}}
### 2. Assign supplier ID "PNG"
PATCH {{server}}/admin/Books(201)
{{authAlice}}
Content-Type: application/json
{
"supplier_ID": "PNG"
}
### 3. Supplier information is replicated
GET {{server}}/admin/Books(201)?$expand=supplier
{{authAlice}}

View File

@@ -1,3 +0,0 @@
const cds = require ('@sap/cds')
cds.once('served', require('./srv/mashup'))
module.exports = cds.server

View File

@@ -1,2 +0,0 @@
ID;title;descr;author_ID;stock;price;currency_code;genre_ID;supplier_ID;
299;Mobby Dick;"""Moby-Dick""" or """The Whale""" is an 1851 novel by American writer Herman Melville. The book is the sailor Ishmael's narrative of the obsessive quest of Ahab, captain of the whaling ship Pequod, for revenge on Moby Dick, the giant white sperm whale that on the ship's previous voyage bit off Ahab's leg at the knee. A contribution to the literature of the American Renaissance, Moby-Dick was published to mixed reviews, was a commercial failure, and was out of print at the time of the author's death in 1891.;105;99;15.20;GBP;11;ACME
Can't render this file because it contains an unexpected character in line 2 and column 30.

View File

@@ -1,3 +0,0 @@
ID;name
ACME;A Company Making Everything (replicated)
1 ID name
2 ACME A Company Making Everything (replicated)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
const cds = require('@sap/cds');
module.exports = cds.service.impl(function () {
const { A_BusinessPartner } = this.entities;
// https://api.sap.com/event/SAPS4HANACloudBusinessEvents_BusinessPartner/resource
this.after('UPDATE', A_BusinessPartner, async data => {
await this.emit("BusinessPartner.Changed", { BusinessPartner: data.BusinessPartner });
});
});

View File

@@ -1,7 +0,0 @@
BusinessPartner;BusinessPartnerFullName
ACME;A Company Making Everything
B4U;Books for You
S&C;Shakespeare & Co.
WSL;Waterstones
TLD;Thalia
PNG;Penguin Books
1 BusinessPartner BusinessPartnerFullName
2 ACME A Company Making Everything
3 B4U Books for You
4 S&C Shakespeare & Co.
5 WSL Waterstones
6 TLD Thalia
7 PNG Penguin Books

View File

@@ -1,72 +0,0 @@
using { API_BUSINESS_PARTNER as S4 } from './external/API_BUSINESS_PARTNER.csn';
using { CatalogService, sap.capire.bookshop.Books } from '@capire/bookshop';
namespace sap.capire.bookshop;
/**
Add projections to external entities to capture the subset of fields we're
actually interested in. This fosters both: (a) minimized network traffic as
well as (b) options to dynamically add extension fields by SaaS tenants.
*/
entity Suppliers as projection on S4.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as name,
// to_BusinessPartnerAddress[1: ValidityStartDate <= $now and $now < ValidityEndDate].CityName,
// to_BusinessPartnerAddress[1: ValidityStartDate <= $now and $now < ValidityEndDate].Country,
}
/**
We can mashup entities from external services, or projections thereof, with
our project's own entities, e.g. by adding relationships as below.
*/
extend Books with {
supplier : Association to Suppliers;
}
/*
Optionally expose external entities through own services, e.g. for Value Helps,
or to display details fetched on demand.
For this to work, we need to delegate the respective calls addressed to our
services into calls to the external service.
*/
extend service AdminService with {
@cds.odata.valuelist
entity Suppliers as projection on bookshop.Suppliers;
}
/*
Optionally add local persistency to replicate data for fast access,
e.g. to display lists containing remote data.
*/
annotate Suppliers with @cds.persistence: {table,skip:false};
/**
Having locally cached replicas also allows to display supplier data in lists
of books, which otherwise would generate unwanted traffic on S4 backends.
*/
extend projection CatalogService.ListOfBooks with {
supplier.name as supplier
}
/**
Optionally declare events emitted from the source, but not included in
imported APIs (e.g. as in case of EDMXes from API Hub).
This allows CAP's advanced support for events and messaging to kick in,
e.g. to automatically emit to and subscribe to events from message brokers
behind the scenes.
Note: as sync and async APIs from S/4 sources are not correlated, we have
to specify the event type names, e.g. as be found at:
https://api.sap.com/event/SAPS4HANACloudBusinessEvents_BusinessPartner/resource
*/
extend service S4 {
event BusinessPartner.Changed @(type: 'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1') {
BusinessPartner: S4.A_BusinessPartner:BusinessPartner;
}
}

View File

@@ -1,50 +0,0 @@
////////////////////////////////////////////////////////////////////////////
//
// Mashing up provided and required services...
//
module.exports = async()=>{ // called by server.js
if (!cds.services.AdminService) return //> mocking S4 service only
// Connect to services we want to mashup below...
const S4bupa = await cds.connect.to('API_BUSINESS_PARTNER') //> external S4 service
const admin = await cds.connect.to('AdminService') //> local domain service
const db = await cds.connect.to('db') //> our primary database
// Reflect CDS definition of the Suppliers entity
const { Suppliers } = db.entities
admin.prepend (()=>{ //> to ensure our .on handlers below go before the default ones
// Delegate Value Help reads for Suppliers to S4 backend
admin.on ('READ', 'Suppliers', req => {
console.log ('>> delegating to S4 service...')
return S4bupa.run(req.query)
})
// Replicate Supplier data when edited Books have suppliers
admin.after (['CREATE','UPDATE'], 'Books', async data => {
const { supplier_ID: ID } = data
if (ID) {
let replicated = await db.exists (Suppliers,ID)
if (!replicated) { // initially replicate Supplier info
let supplier = await S4bupa.read (Suppliers,ID)
await INSERT(supplier) .into (Suppliers)
}
}
})
})
// Subscribe to changes in the S4 origin of Suppliers data
S4bupa.on ('BusinessPartner.Changed', async msg => { //> would be great if we had batch events from S/4
console.log(">>", msg.event, msg.data)
const ID = msg.data.BusinessPartner;
let replicated = await db.exists (Suppliers,ID)
if (replicated) { // update replicated Supplier info
let supplier = await S4bupa.read (Suppliers,ID)
await UPDATE(Suppliers,ID) .with (supplier)
}
})
}

View File

@@ -1,22 +0,0 @@
const cds = require ('@sap/cds/lib')
const { expect } = cds.test.in (__dirname)
describe('cds.events tests', ()=>{
let m; before (async ()=> m = await cds.load('events.cds'))
it ('should have the model loaded', ()=>{
expect(m.definitions).to.have.property('Sue.Foo')
})
it ('should compile the model to edmx', ()=>{
const edmx = cds.compile(m).to.edmx({service:'Sue'})
expect(edmx).to.match(/<EntitySet Name="Foo" EntityType="Sue.Foo"\/>/)
})
it ('should compile the model to sql', ()=>{
const sql = cds.compile(m).to.sql().join(';\n')
expect(sql).not.to.match(/CREATE TABLE Sue_Foo/)
expect(sql).to.match(/CREATE TABLE Sue_Bar/)
})
})

View File

@@ -1,5 +1,5 @@
const { expect } = require('../test')
const cds = require('@sap/cds/lib')
const { expect } = cds.test
const CQL = ([cql]) => cds.parse.cql(cql)
const Foo = { name: 'Foo' }
const Books = { name: 'capire.bookshop.Books' }
@@ -280,7 +280,7 @@ describe('cds.ql → cqn', () => {
).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: cds.version >= '5.3.0'
where: cdr
? [
// '(', //> this one is not required
{ ref: ['ID'] },
@@ -289,7 +289,7 @@ describe('cds.ql → cqn', () => {
'and',
{ ref: ['args'] },
'in',
{ list: args.map(val => ({ val })) },
{ val: args },
'and',
'(', //> this one is missing, and that's changing the logic -> that's a BUG
{ ref: ['x'] },
@@ -325,26 +325,7 @@ describe('cds.ql → cqn', () => {
})
// using CQL fragments -> uses cds.parse.expr
const is_v2 = !!cds.parse.expr('(1,2)').list
if (is_v2) expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [
{ ref: ['ID'] },
'=',
{ val: ID },
'and',
{ ref: ['x'] },
'in',
{list:[
{ ref: ['foo'] },
{ val: 'bar' },
{ val: 3 },
]}
],
},
})
else expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
expect((cqn = CQL`SELECT from Foo where ID=11 and x in ( foo, 'bar', 3)`)).to.eql({
SELECT: {
from: { ref: ['Foo'] },
where: [
@@ -365,7 +346,7 @@ describe('cds.ql → cqn', () => {
},
})
if (!is_v2) expect(
expect(
SELECT.from(Foo).where(`x=`, 1, `or y.z is null and (a>`, 2, `or b=`, 3, `)`)
).to.eql(CQL`SELECT from Foo where x=1 or y.z is null and (a>2 or b=3)`)

View File

@@ -1,7 +1,7 @@
const { expect } = require('../test') .run (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
)
const cds = require('@sap/cds/lib')
const { expect } = cds.test (
'serve', 'AdminService', '--from', '@capire/bookshop,@capire/common', '--in-memory'
).in(__dirname)
describe('Consuming Services locally', () => {
//

View File

@@ -1,14 +1,16 @@
const { GET, POST, expect } = require('../test') .run ('bookshop')
const cds = require('@sap/cds/lib')
if (cds.User.default) cds.User.default = cds.User.Privileged // hard core monkey patch
else cds.User = cds.User.Privileged // hard core monkey patch for older cds releases
const cds = require('@sap/cds/lib'); cds.User = cds.User.Privileged // skip auth
const { GET, POST, expect } = cds.test('bookshop').in(__dirname,'..')
describe('Custom Handlers', () => {
it('should reject out-of-stock orders', async () => {
await POST('/browse/submitOrder', { book: 201, amount: 5 })
await POST('/browse/submitOrder', { book: 201, amount: 5 })
await expect(POST('/browse/submitOrder', { book: 201, amount: 5 })).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
await expect(
Promise.all([
POST('/browse/submitOrder', { book: 201, amount: 5 }),
POST('/browse/submitOrder', { book: 201, amount: 5 }),
POST('/browse/submitOrder', { book: 201, amount: 5 }),
])
).to.be.rejectedWith(/409 - 5 exceeds stock for book #201/)
const { data } = await GET`/admin/Books/201/stock/$value`
expect(data).to.equal(2)
})

View File

@@ -1,6 +0,0 @@
service Sue {
@cds.persistence.skip
entity Foo { key ID:Integer; title:String; status:String(1); }
entity Bar { key ID:Integer; foo: Association to Foo }
event Foo.changed : projection on Foo { ID, status }
}

Some files were not shown because too many files have changed in this diff Show More