From f81229dfd30db7da73bd56272d4f7948bb95d3cf Mon Sep 17 00:00:00 2001 From: Yannis Moser Date: Wed, 25 Mar 2026 08:51:33 +0100 Subject: [PATCH 01/24] sample project --- tests/sample/status-management/LICENSE | 201 ++++++++++++++++++ .../app/authors/.appGenInfo.json | 33 +++ .../status-management/app/authors/README.md | 36 ++++ .../app/authors/annotations.cds | 74 +++++++ .../app/authors/package.json | 19 ++ .../status-management/app/authors/ui5.yaml | 28 +++ .../app/authors/webapp/Component.js | 12 ++ .../app/authors/webapp/i18n/i18n.properties | 9 + .../app/authors/webapp/index.html | 35 +++ .../app/authors/webapp/manifest.json | 141 ++++++++++++ .../webapp/test/integration/FirstJourney.js | 37 ++++ .../test/integration/opaTests.qunit.html | 27 +++ .../webapp/test/integration/opaTests.qunit.js | 29 +++ .../test/integration/pages/AuthorsList.js | 17 ++ .../integration/pages/AuthorsObjectPage.js | 17 ++ .../test/integration/pages/JourneyRunner.js | 19 ++ .../authors/webapp/test/testsuite.qunit.html | 9 + .../authors/webapp/test/testsuite.qunit.js | 11 + .../app/books/.appGenInfo.json | 33 +++ .../status-management/app/books/README.md | 36 ++++ .../app/books/annotations.cds | 109 ++++++++++ .../status-management/app/books/package.json | 19 ++ .../status-management/app/books/ui5.yaml | 28 +++ .../app/books/webapp/Component.js | 12 ++ .../app/books/webapp/i18n/i18n.properties | 9 + .../app/books/webapp/index.html | 35 +++ .../app/books/webapp/manifest.json | 141 ++++++++++++ .../webapp/test/integration/FirstJourney.js | 37 ++++ .../test/integration/opaTests.qunit.html | 27 +++ .../webapp/test/integration/opaTests.qunit.js | 29 +++ .../test/integration/pages/BooksList.js | 17 ++ .../test/integration/pages/BooksObjectPage.js | 17 ++ .../test/integration/pages/JourneyRunner.js | 19 ++ .../books/webapp/test/testsuite.qunit.html | 9 + .../app/books/webapp/test/testsuite.qunit.js | 11 + .../sample/status-management/app/services.cds | 4 + .../db/data/sap.capire.bookshop-Authors.csv | 5 + .../db/data/sap.capire.bookshop-Books.csv | 6 + .../data/sap.capire.bookshop-Books.texts.csv | 5 + .../db/data/sap.capire.bookshop-Genres.csv | 43 ++++ tests/sample/status-management/db/init.js | 21 ++ tests/sample/status-management/db/schema.cds | 38 ++++ tests/sample/status-management/index.cds | 8 + tests/sample/status-management/package.json | 33 +++ tests/sample/status-management/readme.md | 41 ++++ .../srv/admin-constraints.cds | 46 ++++ .../status-management/srv/admin-service.cds | 25 +++ .../status-management/srv/admin-service.js | 14 ++ .../status-management/srv/cat-service.cds | 23 ++ .../status-management/srv/cat-service.js | 29 +++ .../status-management/test/requests-hcql.http | 55 +++++ .../status-management/test/requests.http | 30 +++ 52 files changed, 1768 insertions(+) create mode 100644 tests/sample/status-management/LICENSE create mode 100644 tests/sample/status-management/app/authors/.appGenInfo.json create mode 100644 tests/sample/status-management/app/authors/README.md create mode 100644 tests/sample/status-management/app/authors/annotations.cds create mode 100644 tests/sample/status-management/app/authors/package.json create mode 100644 tests/sample/status-management/app/authors/ui5.yaml create mode 100644 tests/sample/status-management/app/authors/webapp/Component.js create mode 100644 tests/sample/status-management/app/authors/webapp/i18n/i18n.properties create mode 100644 tests/sample/status-management/app/authors/webapp/index.html create mode 100644 tests/sample/status-management/app/authors/webapp/manifest.json create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js create mode 100644 tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js create mode 100644 tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html create mode 100644 tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js create mode 100644 tests/sample/status-management/app/books/.appGenInfo.json create mode 100644 tests/sample/status-management/app/books/README.md create mode 100644 tests/sample/status-management/app/books/annotations.cds create mode 100644 tests/sample/status-management/app/books/package.json create mode 100644 tests/sample/status-management/app/books/ui5.yaml create mode 100644 tests/sample/status-management/app/books/webapp/Component.js create mode 100644 tests/sample/status-management/app/books/webapp/i18n/i18n.properties create mode 100644 tests/sample/status-management/app/books/webapp/index.html create mode 100644 tests/sample/status-management/app/books/webapp/manifest.json create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js create mode 100644 tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js create mode 100644 tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html create mode 100644 tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js create mode 100644 tests/sample/status-management/app/services.cds create mode 100644 tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv create mode 100644 tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv create mode 100644 tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv create mode 100644 tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv create mode 100644 tests/sample/status-management/db/init.js create mode 100644 tests/sample/status-management/db/schema.cds create mode 100644 tests/sample/status-management/index.cds create mode 100644 tests/sample/status-management/package.json create mode 100644 tests/sample/status-management/readme.md create mode 100644 tests/sample/status-management/srv/admin-constraints.cds create mode 100644 tests/sample/status-management/srv/admin-service.cds create mode 100644 tests/sample/status-management/srv/admin-service.js create mode 100644 tests/sample/status-management/srv/cat-service.cds create mode 100644 tests/sample/status-management/srv/cat-service.js create mode 100644 tests/sample/status-management/test/requests-hcql.http create mode 100644 tests/sample/status-management/test/requests.http diff --git a/tests/sample/status-management/LICENSE b/tests/sample/status-management/LICENSE new file mode 100644 index 00000000..1653b640 --- /dev/null +++ b/tests/sample/status-management/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/tests/sample/status-management/app/authors/.appGenInfo.json b/tests/sample/status-management/app/authors/.appGenInfo.json new file mode 100644 index 00000000..0b197f56 --- /dev/null +++ b/tests/sample/status-management/app/authors/.appGenInfo.json @@ -0,0 +1,33 @@ +{ + "generationParameters": { + "generationDate": "Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time)", + "generatorPlatform": "Visual Studio Code", + "serviceType": "Local CAP", + "metadataFilename": "", + "serviceUrl": "http://localhost:4004/admin/", + "appName": "authors", + "appTitle": "Manage Authors", + "appDescription": "An SAP Fiori application.", + "appNamespace": "", + "ui5Theme": "sap_horizon", + "ui5Version": "1.146.0", + "enableCodeAssist": false, + "enableEslint": false, + "enableTypeScript": false, + "showMockDataInfo": false, + "generatorVersion": "1.20.0", + "template": "List Report Page V4", + "generatorName": "SAP Fiori Application Generator", + "entityRelatedConfig": [ + { + "type": "Main Entity", + "value": "Authors" + }, + { + "type": "Navigation Entity", + "value": "None" + } + ], + "launchText": "To launch the generated app, start your CAP project: and navigate to the following location in your browser:\n\nhttp://localhost:4004/authors/index.html" + } +} diff --git a/tests/sample/status-management/app/authors/README.md b/tests/sample/status-management/app/authors/README.md new file mode 100644 index 00000000..5051dc47 --- /dev/null +++ b/tests/sample/status-management/app/authors/README.md @@ -0,0 +1,36 @@ +## Application Details +| | +| ------------- | +|**Generation Date and Time**
Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time)| +|**App Generator**
SAP Fiori Application Generator| +|**App Generator Version**
1.20.0| +|**Generation Platform**
Visual Studio Code| +|**Template Used**
List Report Page V4| +|**Service Type**
Local CAP| +|**Service URL**
http://localhost:4004/admin/| +|**Module Name**
authors| +|**Application Title**
Manage Authors| +|**Namespace**
| +|**UI5 Theme**
sap_horizon| +|**UI5 Version**
1.146.0| +|**Enable Code Assist Libraries**
False| +|**Enable TypeScript**
False| +|**Add Eslint configuration**
False| +|**Main Entity**
Authors| +|**Navigation Entity**
None| + +## authors + +An SAP Fiori application. + +### Starting the generated app + +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: + +http://localhost:4004/authors/index.html + +#### Pre-requisites: + +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) + + diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds new file mode 100644 index 00000000..a9847beb --- /dev/null +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -0,0 +1,74 @@ +using AdminService as service from '../../srv/admin-service'; +annotate service.Authors with @( + UI.FieldGroup #GeneratedGroup : { + $Type : 'UI.FieldGroupType', + Data : [ + { + $Type : 'UI.DataField', + Label : 'ID', + Value : ID, + }, + { + $Type : 'UI.DataField', + Label : 'name', + Value : name, + }, + { + $Type : 'UI.DataField', + Label : 'dateOfBirth', + Value : dateOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'dateOfDeath', + Value : dateOfDeath, + }, + { + $Type : 'UI.DataField', + Label : 'placeOfBirth', + Value : placeOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'placeOfDeath', + Value : placeOfDeath, + }, + ], + }, + UI.Facets : [ + { + $Type : 'UI.ReferenceFacet', + ID : 'GeneratedFacet1', + Label : 'General Information', + Target : '@UI.FieldGroup#GeneratedGroup', + }, + ], + UI.LineItem : [ + { + $Type : 'UI.DataField', + Label : 'ID', + Value : ID, + }, + { + $Type : 'UI.DataField', + Label : 'name', + Value : name, + }, + { + $Type : 'UI.DataField', + Label : 'dateOfBirth', + Value : dateOfBirth, + }, + { + $Type : 'UI.DataField', + Label : 'dateOfDeath', + Value : dateOfDeath, + }, + { + $Type : 'UI.DataField', + Label : 'placeOfBirth', + Value : placeOfBirth, + }, + ], +); + diff --git a/tests/sample/status-management/app/authors/package.json b/tests/sample/status-management/app/authors/package.json new file mode 100644 index 00000000..38c9414b --- /dev/null +++ b/tests/sample/status-management/app/authors/package.json @@ -0,0 +1,19 @@ +{ + "name": "authors", + "version": "0.0.1", + "description": "An SAP Fiori application.", + "keywords": [ + "ui5", + "openui5", + "sapui5" + ], + "main": "webapp/index.html", + "dependencies": {}, + "devDependencies": { + "@ui5/cli": "^4.0.33", + "@sap/ux-ui5-tooling": "1" + }, + "scripts": { + "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf" + } +} diff --git a/tests/sample/status-management/app/authors/ui5.yaml b/tests/sample/status-management/app/authors/ui5.yaml new file mode 100644 index 00000000..8b1a1abb --- /dev/null +++ b/tests/sample/status-management/app/authors/ui5.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: "4.0" +metadata: + name: authors +type: application +server: + customMiddleware: + - name: fiori-tools-proxy + afterMiddleware: compression + configuration: + ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + ui5: + path: + - /resources + - /test-resources + url: https://sapui5.hana.ondemand.com + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + flp: + theme: sap_horizon diff --git a/tests/sample/status-management/app/authors/webapp/Component.js b/tests/sample/status-management/app/authors/webapp/Component.js new file mode 100644 index 00000000..72751153 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/Component.js @@ -0,0 +1,12 @@ +sap.ui.define( + ["sap/fe/core/AppComponent"], + function (Component) { + "use strict"; + + return Component.extend("authors.Component", { + metadata: { + manifest: "json" + } + }); + } +); \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties b/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties new file mode 100644 index 00000000..baffc2b1 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/i18n/i18n.properties @@ -0,0 +1,9 @@ +# This is the resource bundle for authors + +#Texts for manifest.json + +#XTIT: Application name +appTitle=Manage Authors + +#YDES: Application description +appDescription=An SAP Fiori application. \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/index.html b/tests/sample/status-management/app/authors/webapp/index.html new file mode 100644 index 00000000..b03c0bcf --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/index.html @@ -0,0 +1,35 @@ + + + + + + + Manage Authors + + + + +
+ + \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/manifest.json b/tests/sample/status-management/app/authors/webapp/manifest.json new file mode 100644 index 00000000..e7e5b62e --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/manifest.json @@ -0,0 +1,141 @@ +{ + "_version": "1.76.0", + "sap.app": { + "id": "authors", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap/generator-fiori:lrop", + "version": "1.20.0", + "toolsId": "da4ba4be-cfef-4cfc-bd01-4c50a2f9f514" + }, + "dataSources": { + "mainService": { + "uri": "/admin/", + "type": "OData", + "settings": { + "annotations": [], + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "flexEnabled": true, + "dependencies": { + "minUI5Version": "1.146.0", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.fe.templates": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "authors.i18n.i18n" + } + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + }, + "@i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + } + }, + "resources": { + "css": [] + }, + "routing": { + "config": {}, + "routes": [ + { + "pattern": ":?query:", + "name": "AuthorsList", + "target": "AuthorsList" + }, + { + "pattern": "Authors({key}):?query:", + "name": "AuthorsObjectPage", + "target": "AuthorsObjectPage" + } + ], + "targets": { + "AuthorsList": { + "type": "Component", + "id": "AuthorsList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Authors", + "variantManagement": "Page", + "navigation": { + "Authors": { + "detail": { + "route": "AuthorsObjectPage" + } + } + }, + "controlConfiguration": { + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "type": "ResponsiveTable" + } + } + } + } + } + }, + "AuthorsObjectPage": { + "type": "Component", + "id": "AuthorsObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "editableHeaderContent": false, + "contextPath": "/Authors" + } + } + } + } + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js new file mode 100644 index 00000000..c3ef5255 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js @@ -0,0 +1,37 @@ +sap.ui.define([ + "sap/ui/test/opaQunit", + "./pages/JourneyRunner" +], function (opaTest, runner) { + "use strict"; + + function journey() { + QUnit.module("First journey"); + + opaTest("Start application", function (Given, When, Then) { + Given.iStartMyApp(); + + Then.onTheAuthorsList.iSeeThisPage(); + + }); + + + opaTest("Navigate to ObjectPage", function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + + When.onTheAuthorsList.onFilterBar().iExecuteSearch(); + + Then.onTheAuthorsList.onTable().iCheckRows(); + + When.onTheAuthorsList.onTable().iPressRow(0); + Then.onTheAuthorsObjectPage.iSeeThisPage(); + + }); + + opaTest("Teardown", function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } + + runner.run([journey]); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html new file mode 100644 index 00000000..5f0ae9d9 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html @@ -0,0 +1,27 @@ + + + + Integration tests + + + + + + + + + + +
+
+ + diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js new file mode 100644 index 00000000..7e836005 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js @@ -0,0 +1,29 @@ +sap.ui.loader.config({ + shim: { + "sap/ui/qunit/qunit-junit": { + deps: ["sap/ui/thirdparty/qunit-2"] + }, + "sap/ui/qunit/qunit-coverage": { + deps: ["sap/ui/thirdparty/qunit-2"] + }, + "sap/ui/thirdparty/sinon-qunit": { + deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon"] + }, + "sap/ui/qunit/sinon-qunit-bridge": { + deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon-4"] + } + } +}); + +window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); + +sap.ui.require( + [ + "sap/ui/thirdparty/qunit-2", + "sap/ui/qunit/qunit-junit", + "sap/ui/qunit/qunit-coverage", + 'authors/test/integration/FirstJourney' + ], function (QUnit) { + "use strict"; + QUnit.start(); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js new file mode 100644 index 00000000..57341622 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ListReport'], function(ListReport) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {} + }; + + return new ListReport( + { + appId: 'authors', + componentId: 'AuthorsList', + contextPath: '/Authors' + }, + CustomPageDefinitions + ); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js new file mode 100644 index 00000000..f95d2597 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ObjectPage'], function(ObjectPage) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {} + }; + + return new ObjectPage( + { + appId: 'authors', + componentId: 'AuthorsObjectPage', + contextPath: '/Authors' + }, + CustomPageDefinitions + ); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js new file mode 100644 index 00000000..f2d4f765 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js @@ -0,0 +1,19 @@ +sap.ui.define([ + "sap/fe/test/JourneyRunner", + "authors/test/integration/pages/AuthorsList", + "authors/test/integration/pages/AuthorsObjectPage" +], function (JourneyRunner, AuthorsList, AuthorsObjectPage) { + 'use strict'; + + var runner = new JourneyRunner({ + launchUrl: sap.ui.require.toUrl('authors') + '/test/flp.html#app-preview', + pages: { + onTheAuthorsList: AuthorsList, + onTheAuthorsObjectPage: AuthorsObjectPage + }, + async: true + }); + + return runner; +}); + diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html new file mode 100644 index 00000000..1fea4a26 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html @@ -0,0 +1,9 @@ + + + + QUnit test suite + + + + + \ No newline at end of file diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js new file mode 100644 index 00000000..a37a5c97 --- /dev/null +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js @@ -0,0 +1,11 @@ +window.suite = function() { + 'use strict'; + + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + + return oSuite; +}; \ No newline at end of file diff --git a/tests/sample/status-management/app/books/.appGenInfo.json b/tests/sample/status-management/app/books/.appGenInfo.json new file mode 100644 index 00000000..da196849 --- /dev/null +++ b/tests/sample/status-management/app/books/.appGenInfo.json @@ -0,0 +1,33 @@ +{ + "generationParameters": { + "generationDate": "Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time)", + "generatorPlatform": "Visual Studio Code", + "serviceType": "Local CAP", + "metadataFilename": "", + "serviceUrl": "http://localhost:4004/admin/", + "appName": "books", + "appTitle": "Manage Books", + "appDescription": "An SAP Fiori application.", + "appNamespace": "", + "ui5Theme": "sap_horizon", + "ui5Version": "1.146.0", + "enableCodeAssist": false, + "enableEslint": false, + "enableTypeScript": false, + "showMockDataInfo": false, + "generatorVersion": "1.20.0", + "template": "List Report Page V4", + "generatorName": "SAP Fiori Application Generator", + "entityRelatedConfig": [ + { + "type": "Main Entity", + "value": "Books" + }, + { + "type": "Navigation Entity", + "value": "None" + } + ], + "launchText": "To launch the generated app, start your CAP project: and navigate to the following location in your browser:\n\nhttp://localhost:4004/books/webapp/index.html" + } +} diff --git a/tests/sample/status-management/app/books/README.md b/tests/sample/status-management/app/books/README.md new file mode 100644 index 00000000..8f501678 --- /dev/null +++ b/tests/sample/status-management/app/books/README.md @@ -0,0 +1,36 @@ +## Application Details +| | +| ------------- | +|**Generation Date and Time**
Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time)| +|**App Generator**
SAP Fiori Application Generator| +|**App Generator Version**
1.20.0| +|**Generation Platform**
Visual Studio Code| +|**Template Used**
List Report Page V4| +|**Service Type**
Local CAP| +|**Service URL**
http://localhost:4004/admin/| +|**Module Name**
books| +|**Application Title**
Manage Books| +|**Namespace**
| +|**UI5 Theme**
sap_horizon| +|**UI5 Version**
1.146.0| +|**Enable Code Assist Libraries**
False| +|**Enable TypeScript**
False| +|**Add Eslint configuration**
False| +|**Main Entity**
Books| +|**Navigation Entity**
None| + +## books + +An SAP Fiori application. + +### Starting the generated app + +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: + +http://localhost:4004/books/webapp/index.html + +#### Pre-requisites: + +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) + + diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds new file mode 100644 index 00000000..8a17741d --- /dev/null +++ b/tests/sample/status-management/app/books/annotations.cds @@ -0,0 +1,109 @@ +using AdminService as service from '../../srv/admin-service'; +annotate service.Books with @( + UI.FieldGroup #GeneratedGroup : { + $Type : 'UI.FieldGroupType', + Data : [ + { + $Type : 'UI.DataField', + Label : 'ID', + Value : ID, + }, + { + $Type : 'UI.DataField', + Label : 'author_ID', + Value : author_ID, + }, + { + $Type : 'UI.DataField', + Label : 'title', + Value : title, + }, + { + $Type : 'UI.DataField', + Label : 'descr', + Value : descr, + }, + { + $Type : 'UI.DataField', + Label : 'stock', + Value : stock, + }, + { + $Type : 'UI.DataField', + Label : 'price', + Value : price, + }, + { + $Type : 'UI.DataField', + Label : 'currency_code', + Value : currency_code, + }, + ], + }, + UI.Facets : [ + { + $Type : 'UI.ReferenceFacet', + ID : 'GeneratedFacet1', + Label : 'General Information', + Target : '@UI.FieldGroup#GeneratedGroup', + }, + ], + UI.LineItem : [ + { + $Type : 'UI.DataField', + Label : 'ID', + Value : ID, + }, + { + $Type : 'UI.DataField', + Label : 'author_ID', + Value : author_ID, + }, + { + $Type : 'UI.DataField', + Label : 'title', + Value : title, + }, + { + $Type : 'UI.DataField', + Label : 'descr', + Value : descr, + }, + { + $Type : 'UI.DataField', + Label : 'stock', + Value : stock, + }, + ], +); + +annotate service.Books with { + author @Common.ValueList : { + $Type : 'Common.ValueListType', + CollectionPath : 'Authors', + Parameters : [ + { + $Type : 'Common.ValueListParameterInOut', + LocalDataProperty : author_ID, + ValueListProperty : 'ID', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'name', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'dateOfBirth', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'dateOfDeath', + }, + { + $Type : 'Common.ValueListParameterDisplayOnly', + ValueListProperty : 'placeOfBirth', + }, + ], + } +}; + diff --git a/tests/sample/status-management/app/books/package.json b/tests/sample/status-management/app/books/package.json new file mode 100644 index 00000000..545d54d3 --- /dev/null +++ b/tests/sample/status-management/app/books/package.json @@ -0,0 +1,19 @@ +{ + "name": "books", + "version": "0.0.1", + "description": "An SAP Fiori application.", + "keywords": [ + "ui5", + "openui5", + "sapui5" + ], + "main": "webapp/index.html", + "dependencies": {}, + "devDependencies": { + "@ui5/cli": "^4.0.33", + "@sap/ux-ui5-tooling": "1" + }, + "scripts": { + "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf" + } +} diff --git a/tests/sample/status-management/app/books/ui5.yaml b/tests/sample/status-management/app/books/ui5.yaml new file mode 100644 index 00000000..2cdfdc2e --- /dev/null +++ b/tests/sample/status-management/app/books/ui5.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: "4.0" +metadata: + name: books +type: application +server: + customMiddleware: + - name: fiori-tools-proxy + afterMiddleware: compression + configuration: + ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + ui5: + path: + - /resources + - /test-resources + url: https://sapui5.hana.ondemand.com + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + flp: + theme: sap_horizon diff --git a/tests/sample/status-management/app/books/webapp/Component.js b/tests/sample/status-management/app/books/webapp/Component.js new file mode 100644 index 00000000..a4e0d2e4 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/Component.js @@ -0,0 +1,12 @@ +sap.ui.define( + ["sap/fe/core/AppComponent"], + function (Component) { + "use strict"; + + return Component.extend("books.Component", { + metadata: { + manifest: "json" + } + }); + } +); \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/i18n/i18n.properties b/tests/sample/status-management/app/books/webapp/i18n/i18n.properties new file mode 100644 index 00000000..68fbdc10 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/i18n/i18n.properties @@ -0,0 +1,9 @@ +# This is the resource bundle for books + +#Texts for manifest.json + +#XTIT: Application name +appTitle=Manage Books + +#YDES: Application description +appDescription=An SAP Fiori application. \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/index.html b/tests/sample/status-management/app/books/webapp/index.html new file mode 100644 index 00000000..305d9770 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/index.html @@ -0,0 +1,35 @@ + + + + + + + Manage Books + + + + +
+ + \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/manifest.json b/tests/sample/status-management/app/books/webapp/manifest.json new file mode 100644 index 00000000..036c9607 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/manifest.json @@ -0,0 +1,141 @@ +{ + "_version": "1.76.0", + "sap.app": { + "id": "books", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap/generator-fiori:lrop", + "version": "1.20.0", + "toolsId": "ea000289-e433-433f-9d67-da5f23449471" + }, + "dataSources": { + "mainService": { + "uri": "/admin/", + "type": "OData", + "settings": { + "annotations": [], + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "flexEnabled": true, + "dependencies": { + "minUI5Version": "1.146.0", + "libs": { + "sap.m": {}, + "sap.ui.core": {}, + "sap.fe.templates": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "books.i18n.i18n" + } + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + }, + "@i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + } + }, + "resources": { + "css": [] + }, + "routing": { + "config": {}, + "routes": [ + { + "pattern": ":?query:", + "name": "BooksList", + "target": "BooksList" + }, + { + "pattern": "Books({key}):?query:", + "name": "BooksObjectPage", + "target": "BooksObjectPage" + } + ], + "targets": { + "BooksList": { + "type": "Component", + "id": "BooksList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Books", + "variantManagement": "Page", + "navigation": { + "Books": { + "detail": { + "route": "BooksObjectPage" + } + } + }, + "controlConfiguration": { + "@com.sap.vocabularies.UI.v1.LineItem": { + "tableSettings": { + "type": "ResponsiveTable" + } + } + } + } + } + }, + "BooksObjectPage": { + "type": "Component", + "id": "BooksObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "editableHeaderContent": false, + "contextPath": "/Books" + } + } + } + } + } + }, + "sap.fiori": { + "registrationIds": [], + "archeType": "transactional" + } +} diff --git a/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js new file mode 100644 index 00000000..c723ae76 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js @@ -0,0 +1,37 @@ +sap.ui.define([ + "sap/ui/test/opaQunit", + "./pages/JourneyRunner" +], function (opaTest, runner) { + "use strict"; + + function journey() { + QUnit.module("First journey"); + + opaTest("Start application", function (Given, When, Then) { + Given.iStartMyApp(); + + Then.onTheBooksList.iSeeThisPage(); + + }); + + + opaTest("Navigate to ObjectPage", function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + + When.onTheBooksList.onFilterBar().iExecuteSearch(); + + Then.onTheBooksList.onTable().iCheckRows(); + + When.onTheBooksList.onTable().iPressRow(0); + Then.onTheBooksObjectPage.iSeeThisPage(); + + }); + + opaTest("Teardown", function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } + + runner.run([journey]); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html new file mode 100644 index 00000000..4ebafbc9 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html @@ -0,0 +1,27 @@ + + + + Integration tests + + + + + + + + + + +
+
+ + diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js new file mode 100644 index 00000000..984ce862 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js @@ -0,0 +1,29 @@ +sap.ui.loader.config({ + shim: { + "sap/ui/qunit/qunit-junit": { + deps: ["sap/ui/thirdparty/qunit-2"] + }, + "sap/ui/qunit/qunit-coverage": { + deps: ["sap/ui/thirdparty/qunit-2"] + }, + "sap/ui/thirdparty/sinon-qunit": { + deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon"] + }, + "sap/ui/qunit/sinon-qunit-bridge": { + deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon-4"] + } + } +}); + +window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); + +sap.ui.require( + [ + "sap/ui/thirdparty/qunit-2", + "sap/ui/qunit/qunit-junit", + "sap/ui/qunit/qunit-coverage", + 'books/test/integration/FirstJourney' + ], function (QUnit) { + "use strict"; + QUnit.start(); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js new file mode 100644 index 00000000..6ee5be21 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ListReport'], function(ListReport) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {} + }; + + return new ListReport( + { + appId: 'books', + componentId: 'BooksList', + contextPath: '/Books' + }, + CustomPageDefinitions + ); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js new file mode 100644 index 00000000..4f18b75d --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js @@ -0,0 +1,17 @@ +sap.ui.define(['sap/fe/test/ObjectPage'], function(ObjectPage) { + 'use strict'; + + var CustomPageDefinitions = { + actions: {}, + assertions: {} + }; + + return new ObjectPage( + { + appId: 'books', + componentId: 'BooksObjectPage', + contextPath: '/Books' + }, + CustomPageDefinitions + ); +}); \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js new file mode 100644 index 00000000..a58c0f83 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js @@ -0,0 +1,19 @@ +sap.ui.define([ + "sap/fe/test/JourneyRunner", + "books/test/integration/pages/BooksList", + "books/test/integration/pages/BooksObjectPage" +], function (JourneyRunner, BooksList, BooksObjectPage) { + 'use strict'; + + var runner = new JourneyRunner({ + launchUrl: sap.ui.require.toUrl('books') + '/test/flp.html#app-preview', + pages: { + onTheBooksList: BooksList, + onTheBooksObjectPage: BooksObjectPage + }, + async: true + }); + + return runner; +}); + diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html new file mode 100644 index 00000000..1fea4a26 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html @@ -0,0 +1,9 @@ + + + + QUnit test suite + + + + + \ No newline at end of file diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js new file mode 100644 index 00000000..a37a5c97 --- /dev/null +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js @@ -0,0 +1,11 @@ +window.suite = function() { + 'use strict'; + + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + + return oSuite; +}; \ No newline at end of file diff --git a/tests/sample/status-management/app/services.cds b/tests/sample/status-management/app/services.cds new file mode 100644 index 00000000..f0d0841d --- /dev/null +++ b/tests/sample/status-management/app/services.cds @@ -0,0 +1,4 @@ + +using from './books/annotations'; + +using from './authors/annotations'; \ No newline at end of file diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv new file mode 100644 index 00000000..93a4d579 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv @@ -0,0 +1,5 @@ +ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath +101,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire" +107,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire" +150,Edgar Allan Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland" +170,Richard Carpenter,1929-08-14,"King’s Lynn, Norfolk",2012-02-26,"Hertfordshire, England" diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv new file mode 100644 index 00000000..d9cc9ee2 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv @@ -0,0 +1,6 @@ +ID,title,descr,author_ID,stock,price,currency_code,genre_ID +201,Wuthering Heights,"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.",101,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +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,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +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,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +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,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +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,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv new file mode 100644 index 00000000..97d60e65 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv @@ -0,0 +1,5 @@ +ID_texts,ID,locale,title,descr +d2a65a27-9f2a-480f-bc38-84ee8ec5c13e,201,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts." +8c42c706-a979-41cf-9ffe-91e6cf1383a0,201,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal." +9e1c4c81-dc90-4600-85b1-e9dd4bf12ce0,207,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte" +9be0524b-4cb9-4fc1-9dc2-d65b1c13cf53,252,de,Eleonora,"“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit." diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv new file mode 100644 index 00000000..8e22e9b2 --- /dev/null +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Genres.csv @@ -0,0 +1,43 @@ +ID,parent_ID,name +10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Fiction +11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Drama +12aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Poetry +13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fantasy +131aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Fairy Tale +132aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Epic Fantasy +133aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,High Fantasy +134aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Gothic +14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Science Fiction +141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian and Dystopian +1411aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Utopian +1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,141aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Dystopian +14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1412aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cyberpunk +141211aa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14121aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Steampunk +142aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Space Opera +143aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Time Travel +144aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,14aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tech Noir +15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romance +151aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Contemporary Romance +152aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Historical Romance +153aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Romantic Suspense +16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Mystery +161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Crime +1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Thriller +16111aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Police Procedural +16112aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Legal Thriller +16113aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Medical Thriller +16114aaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,1611aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Spy Thriller +1612aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Detective +1613aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,161aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Suspense +162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Noir +1621aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Nordic Noir +1622aaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,162aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Tart Noir +163aaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Cozy Mystery +17aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Adventure +18aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Short Story +19aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,10aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Graphic Novel +20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,,Non-Fiction +21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Biography +22aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,21aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Autobiography +23aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Essay +24aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,20aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa,Speech diff --git a/tests/sample/status-management/db/init.js b/tests/sample/status-management/db/init.js new file mode 100644 index 00000000..77a50c0f --- /dev/null +++ b/tests/sample/status-management/db/init.js @@ -0,0 +1,21 @@ +const cds = require('@sap/cds') + +/** + * In order to keep basic bookshop sample as simple as possible, we don't add + * reuse dependencies. This db/init.js ensures we still have a minimum set of + * currencies, if not obtained through @capire/common. + */ + +// NOTE: We use cds.on('served') to delay the UPSERTs after the db init +// to run after all INSERTs from .csv files happened. +module.exports = cds.on('served', ()=> + UPSERT.into ('sap.common.Currencies') .columns ( + [ 'code', 'symbol', 'name' ] + ) .rows ( + [ 'EUR', '€', 'Euro' ], + [ 'USD', '$', 'US Dollar' ], + [ 'GBP', '£', 'British Pound' ], + [ 'ILS', '₪', 'Shekel' ], + [ 'JPY', '¥', 'Yen' ], + ) +) diff --git a/tests/sample/status-management/db/schema.cds b/tests/sample/status-management/db/schema.cds new file mode 100644 index 00000000..e8decf26 --- /dev/null +++ b/tests/sample/status-management/db/schema.cds @@ -0,0 +1,38 @@ +using { Currency, cuid, managed, sap } from '@sap/cds/common'; +namespace sap.capire.bookshop; + +entity Books : managed { + key ID : Integer; + author : Association to Authors @mandatory; + title : localized String @mandatory; + descr : localized String(2000); + genre : Association to Genres; + stock : Integer; + price : Price; + currency : Currency; +} + +entity Authors : managed { + key ID : Integer; + name : String @mandatory; + dateOfBirth : Date; + dateOfDeath : Date; + placeOfBirth : String; + placeOfDeath : String; + books : Association to many Books on books.author = $self; +} + +/** Hierarchically organized Code List for Genres */ +entity Genres : cuid, sap.common.CodeList { + parent : Association to Genres; + children : Composition of many Genres on children.parent = $self; +} + +type Price : Decimal(9,2); + + +// -------------------------------------------------------------------------------- +// Temporary workaround for this situation: +// - Fiori apps in bookstore annotate Books with @fiori.draft.enabled. +// - Because of that .csv data has to eagerly fill in ID_texts column. +annotate Books with @fiori.draft.enabled; diff --git a/tests/sample/status-management/index.cds b/tests/sample/status-management/index.cds new file mode 100644 index 00000000..06d1dbdb --- /dev/null +++ b/tests/sample/status-management/index.cds @@ -0,0 +1,8 @@ + +// This file allows other projects to import the bookshop model by +// using {...} from '@capire/bookshop'; + +namespace sap.capire.status-management; //> important for reflection +using from './db/schema'; +using from './srv/cat-service'; +using from './srv/admin-service'; diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json new file mode 100644 index 00000000..f9cef225 --- /dev/null +++ b/tests/sample/status-management/package.json @@ -0,0 +1,33 @@ +{ + "name": "@capire/bookshop", + "version": "2.1.9", + "description": "Our primer sample for getting started in a nutshell.", + "repository": "https://github.com/capire/bookshop", + "files": [ + "app", + "srv", + "db", + "index.cds" + ], + "devDependencies": { + "@cap-js/sqlite": "^2.1.1", + "cds-plugin-ui5": "^0.13.0" + }, + "dependencies": { + "@sap/cds": "^9.6.2" + }, + "scripts": { + "start": "cds-serve", + "watch": "cds watch", + "watch-books": "cds watch --open books/index.html?sap-ui-xx-viewCache=false --livereload false", + "watch-authors": "cds watch --open authors/index.html?sap-ui-xx-viewCache=false --livereload false" + }, + "license": "Apache-2.0", + "workspaces": [ + "app/*" + ], + "sapux": [ + "app/books", + "app/authors" + ] +} diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md new file mode 100644 index 00000000..88511949 --- /dev/null +++ b/tests/sample/status-management/readme.md @@ -0,0 +1,41 @@ +# @capire/bookshop + +This is our primer sample for [Getting Started in a Nutshell](https://cap.cloud.sap/docs/get-started/in-a-nutshell). + + +## Get it + +```sh +git clone https://github.com/capire/bookshop +``` + + +## Run it + +```sh +cds watch bookshop +``` + +This should print something like that: + +```sh +... +[cds] - server listening on { url: 'http://localhost:4004' } +[cds] - server v9.4.0 launched in 247 ms +[cds] - [ terminate with ^C ] +``` + +`Cmd-click` the http://localhost:4004 link in the terminal to open the app in a browser. + + + + +## Get help + +- Visit the [*capire* docs](https://cap.cloud.sap) to learn about CAP. +- Especially [*Getting Started in a Nutshell*](https://cap.cloud.sap/docs/get-started/in-a-nutshell). + + +## License + +Copyright (c) 2022 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) file. diff --git a/tests/sample/status-management/srv/admin-constraints.cds b/tests/sample/status-management/srv/admin-constraints.cds new file mode 100644 index 00000000..8e8195b5 --- /dev/null +++ b/tests/sample/status-management/srv/admin-constraints.cds @@ -0,0 +1,46 @@ +using { AdminService } from './admin-service.cds'; + +// Add constraints for input validation on Books +annotate AdminService.Books with { + + title @mandatory; + + author @assert: (case + when not exists author then 'Specified Author does not exist' + end); + + genre @assert: (case + when not exists genre then 'Specified Genre does not exist' + end); + + price @assert.range: [1,111]; // 1 ... 111 inclusive + stock @assert.range: [(0),_]; // positive numbers only +} + +// Add constraints for Authors +annotate AdminService.Authors with { + + name @mandatory; + + dateOfBirth @assert: (case + when dateOfBirth > dateOfDeath then 'Date of birth cannot be after date of death' + end); + + dateOfDeath @assert: (case + when dateOfDeath < dateOfBirth then 'Date of death cannot be before date of birth' + end); +} + +// Add constraints for Genres +annotate AdminService.Genres with { + + name @mandatory; + + parent @assert: (case + when parent == ID then 'A genre cannot be its own parent' + end); +} + +// Require 'admin' role to access AdminService +// (disabled for getting-started guide) +// annotate AdminService with @requires:'admin'; diff --git a/tests/sample/status-management/srv/admin-service.cds b/tests/sample/status-management/srv/admin-service.cds new file mode 100644 index 00000000..a8dff303 --- /dev/null +++ b/tests/sample/status-management/srv/admin-service.cds @@ -0,0 +1,25 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service AdminService @(odata:'/admin') { + entity Authors as projection on my.Authors; + + @bpm.process.start : { + id: '', + on: 'CREATE', + inputs: [ + $self.title, + $self.author, + $self.price, + ] + } + @bpm.process.businessKey: (title) + @bpm.process.cancel : { + id: '', + on: 'DELETE', + } + entity Books as projection on my.Books; + entity Genres as projection on my.Genres; +} + +// Additionally serve via HCQL and REST +annotate AdminService with @hcql @rest; diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js new file mode 100644 index 00000000..f3e910f4 --- /dev/null +++ b/tests/sample/status-management/srv/admin-service.js @@ -0,0 +1,14 @@ +const cds = require('@sap/cds') + +module.exports = class AdminService extends cds.ApplicationService { init(){ + this.before (['NEW','CREATE'],'Authors', genid) + this.before (['NEW','CREATE'],'Books', genid) + return super.init() +}} + +/** Generate primary keys for target entity in request */ +async function genid (req) { + if (req.data.ID) return + const {id} = await SELECT.one.from(req.target).columns('max(ID) as id') + req.data.ID = id + 4 // Note: that is not safe! ok for this sample only. +} diff --git a/tests/sample/status-management/srv/cat-service.cds b/tests/sample/status-management/srv/cat-service.cds new file mode 100644 index 00000000..38c92b96 --- /dev/null +++ b/tests/sample/status-management/srv/cat-service.cds @@ -0,0 +1,23 @@ +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 { + *, currency.symbol as currency, + } + excluding { descr }; + + /** For display in details pages */ + @readonly entity Books as projection on my.Books { + *, // all fields with the following denormalizations: + author.name as author, + genre.name as genre, + } excluding { createdBy, modifiedBy }; + + @requires: 'authenticated-user' + action submitOrder ( book: Books:ID, quantity: Integer ); +} + +// Serve via OData, HCQL and REST +annotate CatalogService with @odata @hcql @rest; diff --git a/tests/sample/status-management/srv/cat-service.js b/tests/sample/status-management/srv/cat-service.js new file mode 100644 index 00000000..1598f053 --- /dev/null +++ b/tests/sample/status-management/srv/cat-service.js @@ -0,0 +1,29 @@ +const cds = require('@sap/cds') + +class CatalogService extends cds.ApplicationService { init() { + + const { Books } = cds.entities ('sap.capire.bookshop') + const { ListOfBooks } = this.entities + + // Add some discount for overstocked books + this.after('each', ListOfBooks, book => { + if (book.stock > 111) book.title += ` -- 11% discount!` + }) + + // Reduce stock of ordered books if available stock suffices + this.on ('submitOrder', async req => { + let { book:id, quantity } = req.data + if (quantity < 1) return req.error (400, `quantity has to be 1 or more`) + let succeeded = await UPDATE (Books,id) + .with `stock = stock - ${quantity}` + .where `stock >= ${quantity}` + if (succeeded) return + else if (!this.exists(Books,id)) req.error (404, `Book #${id} doesn't exist`) + else req.error (409, `${quantity} exceeds stock for book #${id}`) + }) + + // Delegate requests to the underlying generic service + return super.init() +}} + +module.exports = CatalogService diff --git a/tests/sample/status-management/test/requests-hcql.http b/tests/sample/status-management/test/requests-hcql.http new file mode 100644 index 00000000..732a1e04 --- /dev/null +++ b/tests/sample/status-management/test/requests-hcql.http @@ -0,0 +1,55 @@ +@cats = http://localhost:4004/hcql/browse +@admin = http://localhost:4004/hcql/admin + + +### CatalogService.read Books +GET {{cats}}/Books { ID, title, author } where author like '%Bro%' + + +### Same with body fragment +GET {{cats}}/Books +Content-Type: text/plain + +{ ID, title, author } +where author like '%Bro%' + + +### Same with POST +POST {{cats}} +Content-Type: text/plain + +SELECT from Books [where author like '%Bro%'] { + ID, title, author +} + + +### AdminService.read Authors +POST {{admin}} +Authorization: Basic alice: +Content-Type: text/plain + +SELECT from Authors { + ID, name, books { + ID, title, + genre.name as genre + } +} + + +### Same with body as CQN +POST {{admin}} +Authorization: Basic alice: +Content-Type: application/json + +{ "SELECT": { + "from": { "ref": [ "Authors" ] }, + "columns": [ + { "ref": [ "ID" ] }, + { "ref": [ "name" ] }, + { "ref": [ "books" ], "expand": [ + { "ref": [ "ID" ] }, + { "ref": [ "title" ] }, + { "ref": [ "genre", "name" ], "as": "genre" } + ]} + ] +}} diff --git a/tests/sample/status-management/test/requests.http b/tests/sample/status-management/test/requests.http new file mode 100644 index 00000000..9cb0d78c --- /dev/null +++ b/tests/sample/status-management/test/requests.http @@ -0,0 +1,30 @@ + +### CatalogService.read Books +GET http://localhost:4004/browse/Books? +&$select=ID,title,author +&$filter=contains(author,'Bro') + + +### AdmingService.read Authors +GET http://localhost:4004/admin/Authors? +&$select=ID,name +&$expand=books($select=ID,title;$expand=genre($select=name)) +Authorization: Basic alice: + + +### Same with inofficial genre/name syntax +GET http://localhost:4004/admin/Authors? +&$select=ID,name +&$expand=books($select=ID,title,genre/name) +Authorization: Basic alice: + + +### CatalogService.submitOrder() +POST http://localhost:4004/browse/submitOrder +Content-Type: application/json +Authorization: Basic bob: + +{ + "book": 201, + "quantity": 3 +} From 74b26d14ec982cbe0148fa90a1ab1689e18e322a Mon Sep 17 00:00:00 2001 From: Til Weber Date: Wed, 25 Mar 2026 15:09:52 +0100 Subject: [PATCH 02/24] initial sample workflow --- .../app/books/annotations.cds | 29 +++- .../db/data/sap.capire.bookshop-Books.csv | 2 +- tests/sample/status-management/package.json | 18 +- .../srv/admin-constraints.cds | 1 - .../status-management/srv/admin-process.cds | 22 +++ .../status-management/srv/admin-service.cds | 22 +-- .../status-management/srv/admin-service.js | 59 +++++-- ...applicationproject.bookApprovalProcess.cds | 77 +++++++++ ...pplicationproject.bookApprovalProcess.json | 157 ++++++++++++++++++ 9 files changed, 354 insertions(+), 33 deletions(-) create mode 100644 tests/sample/status-management/srv/admin-process.cds create mode 100644 tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds create mode 100644 tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds index 8a17741d..8ca41884 100644 --- a/tests/sample/status-management/app/books/annotations.cds +++ b/tests/sample/status-management/app/books/annotations.cds @@ -38,6 +38,21 @@ annotate service.Books with @( Label : 'currency_code', Value : currency_code, }, + { + $Type : 'UI.DataField', + Label : 'genre_ID', + Value : genre_ID, + }, + { + $Type : 'UI.DataField', + Label : 'processStatus', + Value : processStatus, + }, + { + $Type : 'UI.DataField', + Label : 'isApproved', + Value : isApproved, + }, ], }, UI.Facets : [ @@ -71,8 +86,18 @@ annotate service.Books with @( }, { $Type : 'UI.DataField', - Label : 'stock', - Value : stock, + Label : 'price', + Value : price, + }, + { + $Type : 'UI.DataField', + Label : 'processStatus', + Value : processStatus, + }, + { + $Type : 'UI.DataField', + Label : 'isApproved', + Value : isApproved, }, ], ); diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv index d9cc9ee2..a359e7c2 100644 --- a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv @@ -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,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa 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,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa 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,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa -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,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +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,3,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json index f9cef225..3e5cbacd 100644 --- a/tests/sample/status-management/package.json +++ b/tests/sample/status-management/package.json @@ -14,7 +14,11 @@ "cds-plugin-ui5": "^0.13.0" }, "dependencies": { - "@sap/cds": "^9.6.2" + "@sap/cds": "^9.6.2", + "@cap-js/process": "file:../../../cap-js-process-1.0.0.tgz", + "@sap-cloud-sdk/connectivity": "^4", + "@sap-cloud-sdk/http-client": "^4", + "@sap-cloud-sdk/resilience": "^4" }, "scripts": { "start": "cds-serve", @@ -29,5 +33,13 @@ "sapux": [ "app/books", "app/authors" - ] -} + ], + "cds": { + "requires": { + "eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService": { + "kind": "process-service", + "model": "srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess" + } + } + } +} \ No newline at end of file diff --git a/tests/sample/status-management/srv/admin-constraints.cds b/tests/sample/status-management/srv/admin-constraints.cds index 8e8195b5..8efcd751 100644 --- a/tests/sample/status-management/srv/admin-constraints.cds +++ b/tests/sample/status-management/srv/admin-constraints.cds @@ -13,7 +13,6 @@ annotate AdminService.Books with { when not exists genre then 'Specified Genre does not exist' end); - price @assert.range: [1,111]; // 1 ... 111 inclusive stock @assert.range: [(0),_]; // positive numbers only } diff --git a/tests/sample/status-management/srv/admin-process.cds b/tests/sample/status-management/srv/admin-process.cds new file mode 100644 index 00000000..3a391beb --- /dev/null +++ b/tests/sample/status-management/srv/admin-process.cds @@ -0,0 +1,22 @@ +using { AdminService } from './admin-service.cds'; + +annotate AdminService.Books with @( + + bpm.process.businessKey: (title), + bpm.process.start : { + id: 'eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess', + on: 'CREATE', + inputs: [ + { path: $self.title, as: 'booktitle'}, + { path: $self.descr, as: 'description'}, + $self.author.name, + $self.author.dateOfBirth, + $self.price, + ], + if: (price > 50) + }, + bpm.process.cancel : { + on: 'UPDATE', + if: (price <= 50) + } +); \ No newline at end of file diff --git a/tests/sample/status-management/srv/admin-service.cds b/tests/sample/status-management/srv/admin-service.cds index a8dff303..c6388ea0 100644 --- a/tests/sample/status-management/srv/admin-service.cds +++ b/tests/sample/status-management/srv/admin-service.cds @@ -2,22 +2,12 @@ using { sap.capire.bookshop as my } from '../db/schema'; service AdminService @(odata:'/admin') { entity Authors as projection on my.Authors; - - @bpm.process.start : { - id: '', - on: 'CREATE', - inputs: [ - $self.title, - $self.author, - $self.price, - ] - } - @bpm.process.businessKey: (title) - @bpm.process.cancel : { - id: '', - on: 'DELETE', - } - entity Books as projection on my.Books; + @odata.draft.enabled + entity Books as projection on my.Books { + *, + virtual processStatus: String, + virtual isApproved: Boolean default false + }; entity Genres as projection on my.Genres; } diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js index f3e910f4..04d3d687 100644 --- a/tests/sample/status-management/srv/admin-service.js +++ b/tests/sample/status-management/srv/admin-service.js @@ -1,14 +1,53 @@ -const cds = require('@sap/cds') +const cds = require('@sap/cds'); -module.exports = class AdminService extends cds.ApplicationService { init(){ - this.before (['NEW','CREATE'],'Authors', genid) - this.before (['NEW','CREATE'],'Books', genid) - return super.init() -}} +module.exports = class AdminService extends cds.ApplicationService { + init() { + const { Books } = this.entities; + this.before(['NEW', 'CREATE'], 'Authors', genid); + this.before(['NEW', 'CREATE'], 'Books', genid); + + this.after('READ', Books, async (results, _req) => { + for (const book of results) { + const bookTitle = book.title; + + const processService = await cds.connect.to('ProcessService'); + const instances = await processService.getInstancesByBusinessKey(bookTitle, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0] && instances[0].id && instances[0].status) { + if (instances[0].status === 'RUNNING') { + // Get attributes from running process + const attributes = await processService.getAttributes(instances[0].id); + const currentStatus = attributes[0].value; + book.processStatus = currentStatus; + book.isApproved = false; + } else if (instances[0].status === 'COMPLETED') { + // get outputs from completed process + const outputs = await processService.getOutputs(instances[0].id); + console.log(outputs); + const { finalstatus, isapproved } = outputs; + book.processStatus = finalstatus; + book.isApproved = isapproved; + } else if (instances[0].status === 'CANCELED') { + book.processStatus = 'Process has been cancelled and is not required'; + book.isApproved = true; + } + } else { + book.processStatus = 'No process started'; + book.isApproved = true; + } + } + }); + return super.init(); + } +}; /** Generate primary keys for target entity in request */ -async function genid (req) { - if (req.data.ID) return - const {id} = await SELECT.one.from(req.target).columns('max(ID) as id') - req.data.ID = id + 4 // Note: that is not safe! ok for this sample only. +async function genid(req) { + if (req.data.ID) return; + const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); + req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. } diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds new file mode 100644 index 00000000..faae4b76 --- /dev/null +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds @@ -0,0 +1,77 @@ +/* checksum : d228165baeb7c907ccb0fea23d8b708d */ +namespace eu12.cdsmunich.sampleapplicationproject; + +/** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ +@protocol : 'none' +@bpm.process : 'eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess' +service BookApprovalProcessService { + type Author { + name : String; + dateOfBirth : String; + }; + + type ProcessInputs { + booktitle : String; + author : Author; + description : String; + price : DecimalFloat; + }; + + type ProcessOutputs { + isapproved : Boolean not null; + finalstatus : String not null; + }; + + type ProcessAttribute { + id : String not null; + label : String not null; + value : String; + type : String not null; + }; + + type ProcessAttributes : many ProcessAttribute; + + type ProcessInstance { + definitionId : String; + definitionVersion : String; + id : String; + status : String; + startedAt : String; + startedBy : String; + }; + + type ProcessInstances : many ProcessInstance; + + action start( + inputs : ProcessInputs + ); + + function getAttributes( + processInstanceId : String not null + ) returns ProcessAttributes; + + function getOutputs( + processInstanceId : String not null + ) returns ProcessOutputs; + + function getInstancesByBusinessKey( + businessKey : String not null, + status : many String + ) returns ProcessInstances; + + action suspend( + businessKey : String not null, + cascade : Boolean + ); + + action resume( + businessKey : String not null, + cascade : Boolean + ); + + action cancel( + businessKey : String not null, + cascade : Boolean + ); +}; + diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json new file mode 100644 index 00000000..d961fb56 --- /dev/null +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json @@ -0,0 +1,157 @@ +{ + "uid": "76a6d601-ef75-43aa-8fed-346fb0fc2727", + "name": "Book Approval Process", + "description": "", + "type": "bpi.process", + "createdAt": "2026-03-25T11:46:56.091829Z", + "updatedAt": "2026-03-25T13:23:41.616274Z", + "header": { + "inputs": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "booktitle": { + "type": "string", + "title": "bookTitle", + "description": "" + }, + "author": { + "$ref": "$.cfc75dd1-c8d4-407b-914a-3592082e1004", + "refName": "Author", + "title": "author", + "description": "" + }, + "description": { + "type": "string", + "title": "description", + "description": "" + }, + "price": { + "type": "number", + "title": "price", + "description": "" + } + }, + "required": [] + }, + "outputs": { + "title": "outputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "isapproved": { + "type": "boolean", + "title": "isApproved", + "description": "" + }, + "finalstatus": { + "type": "string", + "title": "finalStatus", + "description": "" + } + }, + "required": [ + "isapproved", + "finalstatus" + ] + }, + "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", + "type": "object", + "properties": {}, + "required": [] + }, + "requiresBusinessUser": false + }, + "dependencies": [ + { + "artifactUid": "5227d63c-f620-476c-893a-517bd14c302e", + "type": "content" + }, + { + "artifactUid": "3e51af12-9a42-4a95-9a3a-d12ef176ea42", + "type": "content" + }, + { + "artifactUid": "cfc75dd1-c8d4-407b-914a-3592082e1004", + "type": "both" + } + ], + "identifier": "bookApprovalProcess", + "valid": true, + "projectId": "eu12.cdsmunich.sampleapplicationproject", + "dataTypes": [ + { + "uid": "cfc75dd1-c8d4-407b-914a-3592082e1004", + "name": "Author", + "description": "", + "type": "datatype", + "createdAt": "2026-03-25T11:54:07.324680Z", + "updatedAt": "2026-03-25T11:54:32.177064Z", + "header": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Author", + "properties": { + "name": { + "type": "string", + "uid": "35918fe0-f647-4e16-918c-c5dfaa1d5c9b" + }, + "dateOfBirth": { + "type": "string", + "uid": "ac1a6ddf-073d-4365-8a8d-f97fe4e49915" + } + }, + "version": 1 + }, + "identifier": "author", + "valid": true + } + ] +} \ No newline at end of file From 307cd722f65738bf42371da912575e6faedeb785 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 09:29:04 +0100 Subject: [PATCH 03/24] initial update --- .../app/authors/annotations.cds | 19 ++-- .../app/books/annotations.cds | 39 ++++---- .../status-management/srv/admin-service.cds | 3 +- .../status-management/srv/admin-service.js | 89 ++++++++++++------- 4 files changed, 92 insertions(+), 58 deletions(-) diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds index a9847beb..8370896a 100644 --- a/tests/sample/status-management/app/authors/annotations.cds +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -10,27 +10,27 @@ annotate service.Authors with @( }, { $Type : 'UI.DataField', - Label : 'name', + Label : 'Name', Value : name, }, { $Type : 'UI.DataField', - Label : 'dateOfBirth', + Label : 'Date of Birth', Value : dateOfBirth, }, { $Type : 'UI.DataField', - Label : 'dateOfDeath', + Label : 'Date of Death', Value : dateOfDeath, }, { $Type : 'UI.DataField', - Label : 'placeOfBirth', + Label : 'Place of Birth', Value : placeOfBirth, }, { $Type : 'UI.DataField', - Label : 'placeOfDeath', + Label : 'Place of Death', Value : placeOfDeath, }, ], @@ -51,24 +51,23 @@ annotate service.Authors with @( }, { $Type : 'UI.DataField', - Label : 'name', + Label : 'Name', Value : name, }, { $Type : 'UI.DataField', - Label : 'dateOfBirth', + Label : 'Date of Birth', Value : dateOfBirth, }, { $Type : 'UI.DataField', - Label : 'dateOfDeath', + Label : 'Date of Death', Value : dateOfDeath, }, { $Type : 'UI.DataField', - Label : 'placeOfBirth', + Label : 'Place of Birth', Value : placeOfBirth, }, ], ); - diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds index 8ca41884..6376743e 100644 --- a/tests/sample/status-management/app/books/annotations.cds +++ b/tests/sample/status-management/app/books/annotations.cds @@ -10,47 +10,48 @@ annotate service.Books with @( }, { $Type : 'UI.DataField', - Label : 'author_ID', + Label : 'Author', Value : author_ID, }, { $Type : 'UI.DataField', - Label : 'title', + Label : 'Title', Value : title, }, { $Type : 'UI.DataField', - Label : 'descr', + Label : 'Description', Value : descr, }, { $Type : 'UI.DataField', - Label : 'stock', + Label : 'Stock', Value : stock, }, { $Type : 'UI.DataField', - Label : 'price', + Label : 'Price', Value : price, }, { $Type : 'UI.DataField', - Label : 'currency_code', + Label : 'Currency', Value : currency_code, }, { $Type : 'UI.DataField', - Label : 'genre_ID', + Label : 'Genre', Value : genre_ID, }, { $Type : 'UI.DataField', - Label : 'processStatus', + Label : 'Approval Status', Value : processStatus, + Criticality : processCriticality, }, { $Type : 'UI.DataField', - Label : 'isApproved', + Label : 'Approved', Value : isApproved, }, ], @@ -71,37 +72,44 @@ annotate service.Books with @( }, { $Type : 'UI.DataField', - Label : 'author_ID', + Label : 'Author', Value : author_ID, }, { $Type : 'UI.DataField', - Label : 'title', + Label : 'Title', Value : title, }, { $Type : 'UI.DataField', - Label : 'descr', + Label : 'Description', Value : descr, }, { $Type : 'UI.DataField', - Label : 'price', + Label : 'Price', Value : price, }, { $Type : 'UI.DataField', - Label : 'processStatus', + Label : 'Approval Status', Value : processStatus, + Criticality : processCriticality, }, { $Type : 'UI.DataField', - Label : 'isApproved', + Label : 'Approved', Value : isApproved, }, ], ); +annotate service.Books with { + author @Common.Text : author.name @Common.TextArrangement : #TextOnly; + genre @Common.Text : genre.name @Common.TextArrangement : #TextOnly; + currency @Common.Text : currency.name @Common.TextArrangement : #TextOnly; +}; + annotate service.Books with { author @Common.ValueList : { $Type : 'Common.ValueListType', @@ -131,4 +139,3 @@ annotate service.Books with { ], } }; - diff --git a/tests/sample/status-management/srv/admin-service.cds b/tests/sample/status-management/srv/admin-service.cds index c6388ea0..d4394738 100644 --- a/tests/sample/status-management/srv/admin-service.cds +++ b/tests/sample/status-management/srv/admin-service.cds @@ -6,7 +6,8 @@ service AdminService @(odata:'/admin') { entity Books as projection on my.Books { *, virtual processStatus: String, - virtual isApproved: Boolean default false + virtual isApproved: Boolean default false, + virtual processCriticality: Integer default 0 }; entity Genres as projection on my.Genres; } diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js index 04d3d687..58c19846 100644 --- a/tests/sample/status-management/srv/admin-service.js +++ b/tests/sample/status-management/srv/admin-service.js @@ -7,40 +7,67 @@ module.exports = class AdminService extends cds.ApplicationService { this.before(['NEW', 'CREATE'], 'Books', genid); this.after('READ', Books, async (results, _req) => { - for (const book of results) { - const bookTitle = book.title; - - const processService = await cds.connect.to('ProcessService'); - const instances = await processService.getInstancesByBusinessKey(bookTitle, [ - 'RUNNING', - 'COMPLETED', - 'CANCELED', - ]); - - if (instances[0] && instances[0].id && instances[0].status) { - if (instances[0].status === 'RUNNING') { - // Get attributes from running process - const attributes = await processService.getAttributes(instances[0].id); - const currentStatus = attributes[0].value; - book.processStatus = currentStatus; - book.isApproved = false; - } else if (instances[0].status === 'COMPLETED') { - // get outputs from completed process - const outputs = await processService.getOutputs(instances[0].id); - console.log(outputs); - const { finalstatus, isapproved } = outputs; - book.processStatus = finalstatus; - book.isApproved = isapproved; - } else if (instances[0].status === 'CANCELED') { - book.processStatus = 'Process has been cancelled and is not required'; + const processService = await cds.connect.to( + 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService', + ); + + // Parallelize all process lookups using Promise.all instead of sequential loop + await Promise.all( + results.map(async (book) => { + const bookTitle = book.title; + if (!bookTitle) { + // Draft entries without a title yet + book.processStatus = 'No Approval Required'; book.isApproved = true; + book.processCriticality = 0; // Neutral + return; + } + + try { + const instances = await processService.getInstancesByBusinessKey(bookTitle, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + if (status === 'RUNNING') { + const attributes = await processService.getAttributes(id); + book.processStatus = attributes[0]?.value ?? 'Pending Approval'; + book.isApproved = false; + book.processCriticality = 2; // Warning (yellow) + } else if (status === 'COMPLETED') { + const outputs = await processService.getOutputs(id); + const { finalstatus, isapproved } = outputs; + book.processStatus = finalstatus; + book.isApproved = isapproved; + book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) + } else if (status === 'CANCELED') { + book.processStatus = 'Cancelled'; + book.isApproved = true; + book.processCriticality = 0; // Neutral + } + } else if (book.price > 50) { + // Process was likely just triggered but hasn't registered in SBPA yet + book.processStatus = 'Pending Approval'; + book.isApproved = false; + book.processCriticality = 2; // Warning (yellow) + } else { + book.processStatus = 'No Approval Required'; + book.isApproved = true; + book.processCriticality = 0; // Neutral + } + } catch (err) { + book.processStatus = 'Status Unavailable'; + book.isApproved = false; + book.processCriticality = 0; // Neutral } - } else { - book.processStatus = 'No process started'; - book.isApproved = true; - } - } + }), + ); }); + return super.init(); } }; From f1b1d9e0c2b76599b5e1edf6353a91df9ba9fee2 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 10:22:10 +0100 Subject: [PATCH 04/24] added programmatic approach as example --- .../app/authors/annotations.cds | 25 +++- tests/sample/status-management/package.json | 4 + .../status-management/srv/admin-service.cds | 8 +- .../status-management/srv/admin-service.js | 102 ++++++++++++++- ...ationproject.authorVerificationProcess.cds | 71 +++++++++++ ...tionproject.authorVerificationProcess.json | 117 ++++++++++++++++++ 6 files changed, 318 insertions(+), 9 deletions(-) create mode 100644 tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds create mode 100644 tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds index 8370896a..06b4ff3e 100644 --- a/tests/sample/status-management/app/authors/annotations.cds +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -33,6 +33,17 @@ annotate service.Authors with @( Label : 'Place of Death', Value : placeOfDeath, }, + { + $Type : 'UI.DataField', + Label : 'Verification Status', + Value : verificationStatus, + Criticality : verificationCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Verified', + Value : isVerified, + }, ], }, UI.Facets : [ @@ -61,13 +72,19 @@ annotate service.Authors with @( }, { $Type : 'UI.DataField', - Label : 'Date of Death', - Value : dateOfDeath, + Label : 'Place of Birth', + Value : placeOfBirth, }, { $Type : 'UI.DataField', - Label : 'Place of Birth', - Value : placeOfBirth, + Label : 'Verification Status', + Value : verificationStatus, + Criticality : verificationCriticality, + }, + { + $Type : 'UI.DataField', + Label : 'Verified', + Value : isVerified, }, ], ); diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json index 3e5cbacd..49ef22de 100644 --- a/tests/sample/status-management/package.json +++ b/tests/sample/status-management/package.json @@ -39,6 +39,10 @@ "eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService": { "kind": "process-service", "model": "srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess" + }, + "eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService": { + "kind": "process-service", + "model": "srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess" } } } diff --git a/tests/sample/status-management/srv/admin-service.cds b/tests/sample/status-management/srv/admin-service.cds index d4394738..00ec2802 100644 --- a/tests/sample/status-management/srv/admin-service.cds +++ b/tests/sample/status-management/srv/admin-service.cds @@ -1,7 +1,13 @@ using { sap.capire.bookshop as my } from '../db/schema'; service AdminService @(odata:'/admin') { - entity Authors as projection on my.Authors; + @odata.draft.enabled + entity Authors as projection on my.Authors { + *, + virtual verificationStatus: String, + virtual isVerified: Boolean default false, + virtual verificationCriticality: Integer default 0 + }; @odata.draft.enabled entity Books as projection on my.Books { *, diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js index 58c19846..3e35a2b3 100644 --- a/tests/sample/status-management/srv/admin-service.js +++ b/tests/sample/status-management/srv/admin-service.js @@ -1,15 +1,19 @@ const cds = require('@sap/cds'); +const BOOK_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService'; +const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService'; + module.exports = class AdminService extends cds.ApplicationService { init() { - const { Books } = this.entities; + const { Authors, Books } = this.entities; this.before(['NEW', 'CREATE'], 'Authors', genid); this.before(['NEW', 'CREATE'], 'Books', genid); + // --------------------------------------------------------------- + // Books: Enrich with approval process status (declarative start) + // --------------------------------------------------------------- this.after('READ', Books, async (results, _req) => { - const processService = await cds.connect.to( - 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService', - ); + const processService = await cds.connect.to(BOOK_PROCESS); // Parallelize all process lookups using Promise.all instead of sequential loop await Promise.all( @@ -68,6 +72,96 @@ module.exports = class AdminService extends cds.ApplicationService { ); }); + // --------------------------------------------------------------- + // Authors: Programmatic process lifecycle (start, cancel, status) + // --------------------------------------------------------------- + + // Start verification process when a new author is created + this.after('CREATE', 'Authors', async (author, req) => { + try { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + await verificationService.start({ + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); + } catch (err) { + req.warn(200, 'Author created, but verification process could not be started.'); + } + }); + + // Cancel verification process when an unverified author is deleted + this.after('DELETE', 'Authors', async (author, req) => { + if (!author.name) return; + + try { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + const instances = await verificationService.getInstancesByBusinessKey(author.name, [ + 'RUNNING', + ]); + if (instances.length > 0) { + await verificationService.cancel({ businessKey: author.name, cascade: true }); + } + } catch (err) { + req.warn(200, 'Author deleted, but verification process could not be cancelled.'); + } + }); + + // Enrich authors with verification process status + this.after('READ', Authors, async (results, _req) => { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + + await Promise.all( + results.map(async (author) => { + if (!author.name) { + author.verificationStatus = 'Pending'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + return; + } + + try { + const instances = await verificationService.getInstancesByBusinessKey(author.name, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + if (status === 'RUNNING') { + const attributes = await verificationService.getAttributes(id); + author.verificationStatus = attributes[0]?.value ?? 'Verification In Progress'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } else if (status === 'COMPLETED') { + const outputs = await verificationService.getOutputs(id); + author.verificationStatus = outputs.verificationstatus; + author.isVerified = outputs.isverified; + author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red + } else if (status === 'CANCELED') { + author.verificationStatus = 'Cancelled'; + author.isVerified = false; + author.verificationCriticality = 0; // Neutral + } + } else { + // Every new author triggers a verification process, so if no instance + // is found it's either a race condition (just created) or a pre-existing + // author that was never verified -- both correctly shown as pending. + author.verificationStatus = 'Verification Pending'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } + } catch (err) { + author.verificationStatus = 'Status Unavailable'; + author.isVerified = false; + author.verificationCriticality = 0; // Neutral + } + }), + ); + }); + return super.init(); } }; diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds new file mode 100644 index 00000000..0a06ac89 --- /dev/null +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds @@ -0,0 +1,71 @@ +/* checksum : c0ca6e98ae2cb22ff127e1e4dbf34192 */ +namespace eu12.cdsmunich.sampleapplicationproject; + +/** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ +@protocol : 'none' +@bpm.process : 'eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess' +service AuthorVerificationProcessService { + type ProcessInputs { + authorname : String not null; + dateofbirth : String; + placeofbirth : String; + }; + + type ProcessOutputs { + isverified : Boolean not null; + verificationstatus : String not null; + }; + + type ProcessAttribute { + id : String not null; + label : String not null; + value : String; + type : String not null; + }; + + type ProcessAttributes : many ProcessAttribute; + + type ProcessInstance { + definitionId : String; + definitionVersion : String; + id : String; + status : String; + startedAt : String; + startedBy : String; + }; + + type ProcessInstances : many ProcessInstance; + + action start( + inputs : ProcessInputs not null + ); + + function getAttributes( + processInstanceId : String not null + ) returns ProcessAttributes; + + function getOutputs( + processInstanceId : String not null + ) returns ProcessOutputs; + + function getInstancesByBusinessKey( + businessKey : String not null, + status : many String + ) returns ProcessInstances; + + action suspend( + businessKey : String not null, + cascade : Boolean + ); + + action resume( + businessKey : String not null, + cascade : Boolean + ); + + action cancel( + businessKey : String not null, + cascade : Boolean + ); +}; + diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json new file mode 100644 index 00000000..eae4c5ab --- /dev/null +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json @@ -0,0 +1,117 @@ +{ + "uid": "7b477c46-74fa-4c87-8414-09acb470663f", + "name": "Author Verification Process", + "description": "", + "type": "bpi.process", + "createdAt": "2026-03-26T08:54:22.179844Z", + "updatedAt": "2026-03-26T09:17:11.736398Z", + "header": { + "inputs": { + "title": "inputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "authorname": { + "type": "string", + "title": "authorName", + "description": "" + }, + "dateofbirth": { + "type": "string", + "title": "dateOfBirth", + "description": "" + }, + "placeofbirth": { + "type": "string", + "title": "placeOfBirth", + "description": "" + } + }, + "required": [ + "authorname" + ] + }, + "outputs": { + "title": "outputs", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "date": { + "type": "string", + "format": "date" + }, + "dateTime": { + "type": "string", + "format": "date-time" + }, + "password": { + "type": "string", + "password": true + }, + "time": { + "type": "string", + "format": "time" + }, + "documentFolder": { + "type": "string", + "format": "document-folder" + } + }, + "properties": { + "isverified": { + "type": "boolean", + "title": "isverified", + "description": "" + }, + "verificationstatus": { + "type": "string", + "title": "verificationStatus", + "description": "" + } + }, + "required": [ + "isverified", + "verificationstatus" + ] + }, + "processAttributes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "processAttributes", + "type": "object", + "properties": {}, + "required": [] + } + }, + "dependencies": [ + { + "artifactUid": "f31afeb5-9107-423c-932c-69ce21e93880", + "type": "content" + } + ], + "identifier": "authorVerificationProcess", + "valid": true, + "projectId": "eu12.cdsmunich.sampleapplicationproject", + "dataTypes": [] +} \ No newline at end of file From c4b49ca14b86e171e6e84367fe4c232add84e16a Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 10:30:15 +0100 Subject: [PATCH 05/24] added launchpad config --- .../app/authors/webapp/manifest.json | 13 +++++ .../app/books/webapp/manifest.json | 13 +++++ tests/sample/status-management/app/fiori.html | 55 +++++++++++++++++++ .../status-management/srv/admin-service.js | 6 +- 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 tests/sample/status-management/app/fiori.html diff --git a/tests/sample/status-management/app/authors/webapp/manifest.json b/tests/sample/status-management/app/authors/webapp/manifest.json index e7e5b62e..b6af4db4 100644 --- a/tests/sample/status-management/app/authors/webapp/manifest.json +++ b/tests/sample/status-management/app/authors/webapp/manifest.json @@ -15,6 +15,19 @@ "version": "1.20.0", "toolsId": "da4ba4be-cfef-4cfc-bd01-4c50a2f9f514" }, + "crossNavigation": { + "inbounds": { + "authors-manage": { + "semanticObject": "Authors", + "action": "manage", + "title": "{{appTitle}}", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + } + } + } + }, "dataSources": { "mainService": { "uri": "/admin/", diff --git a/tests/sample/status-management/app/books/webapp/manifest.json b/tests/sample/status-management/app/books/webapp/manifest.json index 036c9607..972f000f 100644 --- a/tests/sample/status-management/app/books/webapp/manifest.json +++ b/tests/sample/status-management/app/books/webapp/manifest.json @@ -15,6 +15,19 @@ "version": "1.20.0", "toolsId": "ea000289-e433-433f-9d67-da5f23449471" }, + "crossNavigation": { + "inbounds": { + "books-manage": { + "semanticObject": "Books", + "action": "manage", + "title": "{{appTitle}}", + "signature": { + "parameters": {}, + "additionalParameters": "allowed" + } + } + } + }, "dataSources": { "mainService": { "uri": "/admin/", diff --git a/tests/sample/status-management/app/fiori.html b/tests/sample/status-management/app/fiori.html new file mode 100644 index 00000000..4deb6397 --- /dev/null +++ b/tests/sample/status-management/app/fiori.html @@ -0,0 +1,55 @@ + + + + + + + Bookshop Launchpad + + + + + + + + + + + diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js index 3e35a2b3..b6318166 100644 --- a/tests/sample/status-management/srv/admin-service.js +++ b/tests/sample/status-management/srv/admin-service.js @@ -49,9 +49,9 @@ module.exports = class AdminService extends cds.ApplicationService { book.isApproved = isapproved; book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) } else if (status === 'CANCELED') { - book.processStatus = 'Cancelled'; + book.processStatus = 'Approval process cancelled as price has been reduced'; book.isApproved = true; - book.processCriticality = 0; // Neutral + book.processCriticality = 3; // Positive (green) } } else if (book.price > 50) { // Process was likely just triggered but hasn't registered in SBPA yet @@ -61,7 +61,7 @@ module.exports = class AdminService extends cds.ApplicationService { } else { book.processStatus = 'No Approval Required'; book.isApproved = true; - book.processCriticality = 0; // Neutral + book.processCriticality = 3; // Positive } } catch (err) { book.processStatus = 'Status Unavailable'; From 9d073d2fdf202e8bedf1153aba40d2f25f0f68ba Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 10:53:40 +0100 Subject: [PATCH 06/24] refactoring --- .../app/authors/annotations.cds | 2 +- .../app/authors/webapp/manifest.json | 2 +- .../app/books/annotations.cds | 2 +- .../app/books/webapp/manifest.json | 2 +- tests/sample/status-management/app/fiori.html | 4 +- tests/sample/status-management/index.cds | 3 +- .../srv/admin-constraints.cds | 45 ----- .../status-management/srv/admin-service.cds | 22 --- .../status-management/srv/admin-service.js | 174 ------------------ .../srv/authors-constraints.cds | 19 ++ .../status-management/srv/authors-service.cds | 13 ++ .../status-management/srv/authors-service.js | 99 ++++++++++ .../srv/books-constraints.cds | 27 +++ .../{admin-process.cds => books-process.cds} | 4 +- .../status-management/srv/books-service.cds | 15 ++ .../status-management/srv/books-service.js | 76 ++++++++ 16 files changed, 259 insertions(+), 250 deletions(-) delete mode 100644 tests/sample/status-management/srv/admin-constraints.cds delete mode 100644 tests/sample/status-management/srv/admin-service.cds delete mode 100644 tests/sample/status-management/srv/admin-service.js create mode 100644 tests/sample/status-management/srv/authors-constraints.cds create mode 100644 tests/sample/status-management/srv/authors-service.cds create mode 100644 tests/sample/status-management/srv/authors-service.js create mode 100644 tests/sample/status-management/srv/books-constraints.cds rename tests/sample/status-management/srv/{admin-process.cds => books-process.cds} (85%) create mode 100644 tests/sample/status-management/srv/books-service.cds create mode 100644 tests/sample/status-management/srv/books-service.js diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds index 06b4ff3e..da38e364 100644 --- a/tests/sample/status-management/app/authors/annotations.cds +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -1,4 +1,4 @@ -using AdminService as service from '../../srv/admin-service'; +using AuthorsService as service from '../../srv/authors-service'; annotate service.Authors with @( UI.FieldGroup #GeneratedGroup : { $Type : 'UI.FieldGroupType', diff --git a/tests/sample/status-management/app/authors/webapp/manifest.json b/tests/sample/status-management/app/authors/webapp/manifest.json index b6af4db4..1c2ba9d5 100644 --- a/tests/sample/status-management/app/authors/webapp/manifest.json +++ b/tests/sample/status-management/app/authors/webapp/manifest.json @@ -30,7 +30,7 @@ }, "dataSources": { "mainService": { - "uri": "/admin/", + "uri": "/api/authors/", "type": "OData", "settings": { "annotations": [], diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds index 6376743e..801e0a7c 100644 --- a/tests/sample/status-management/app/books/annotations.cds +++ b/tests/sample/status-management/app/books/annotations.cds @@ -1,4 +1,4 @@ -using AdminService as service from '../../srv/admin-service'; +using BooksService as service from '../../srv/books-service'; annotate service.Books with @( UI.FieldGroup #GeneratedGroup : { $Type : 'UI.FieldGroupType', diff --git a/tests/sample/status-management/app/books/webapp/manifest.json b/tests/sample/status-management/app/books/webapp/manifest.json index 972f000f..a7135ee3 100644 --- a/tests/sample/status-management/app/books/webapp/manifest.json +++ b/tests/sample/status-management/app/books/webapp/manifest.json @@ -30,7 +30,7 @@ }, "dataSources": { "mainService": { - "uri": "/admin/", + "uri": "/api/books/", "type": "OData", "settings": { "annotations": [], diff --git a/tests/sample/status-management/app/fiori.html b/tests/sample/status-management/app/fiori.html index 4deb6397..7c1fb0bd 100644 --- a/tests/sample/status-management/app/fiori.html +++ b/tests/sample/status-management/app/fiori.html @@ -15,7 +15,7 @@ description: "Books Fiori Elements App", additionalInformation: "SAPUI5.Component=books", applicationType: "URL", - url: "/books/webapp", + url: "/books", navigationMode: "embedded" }, "authors-manage": { @@ -23,7 +23,7 @@ description: "Authors Fiori Elements App", additionalInformation: "SAPUI5.Component=authors", applicationType: "URL", - url: "/authors/webapp", + url: "/authors", navigationMode: "embedded" } } diff --git a/tests/sample/status-management/index.cds b/tests/sample/status-management/index.cds index 06d1dbdb..ee859769 100644 --- a/tests/sample/status-management/index.cds +++ b/tests/sample/status-management/index.cds @@ -5,4 +5,5 @@ namespace sap.capire.status-management; //> important for reflection using from './db/schema'; using from './srv/cat-service'; -using from './srv/admin-service'; +using from './srv/books-service'; +using from './srv/authors-service'; diff --git a/tests/sample/status-management/srv/admin-constraints.cds b/tests/sample/status-management/srv/admin-constraints.cds deleted file mode 100644 index 8efcd751..00000000 --- a/tests/sample/status-management/srv/admin-constraints.cds +++ /dev/null @@ -1,45 +0,0 @@ -using { AdminService } from './admin-service.cds'; - -// Add constraints for input validation on Books -annotate AdminService.Books with { - - title @mandatory; - - author @assert: (case - when not exists author then 'Specified Author does not exist' - end); - - genre @assert: (case - when not exists genre then 'Specified Genre does not exist' - end); - - stock @assert.range: [(0),_]; // positive numbers only -} - -// Add constraints for Authors -annotate AdminService.Authors with { - - name @mandatory; - - dateOfBirth @assert: (case - when dateOfBirth > dateOfDeath then 'Date of birth cannot be after date of death' - end); - - dateOfDeath @assert: (case - when dateOfDeath < dateOfBirth then 'Date of death cannot be before date of birth' - end); -} - -// Add constraints for Genres -annotate AdminService.Genres with { - - name @mandatory; - - parent @assert: (case - when parent == ID then 'A genre cannot be its own parent' - end); -} - -// Require 'admin' role to access AdminService -// (disabled for getting-started guide) -// annotate AdminService with @requires:'admin'; diff --git a/tests/sample/status-management/srv/admin-service.cds b/tests/sample/status-management/srv/admin-service.cds deleted file mode 100644 index 00ec2802..00000000 --- a/tests/sample/status-management/srv/admin-service.cds +++ /dev/null @@ -1,22 +0,0 @@ -using { sap.capire.bookshop as my } from '../db/schema'; - -service AdminService @(odata:'/admin') { - @odata.draft.enabled - entity Authors as projection on my.Authors { - *, - virtual verificationStatus: String, - virtual isVerified: Boolean default false, - virtual verificationCriticality: Integer default 0 - }; - @odata.draft.enabled - entity Books as projection on my.Books { - *, - virtual processStatus: String, - virtual isApproved: Boolean default false, - virtual processCriticality: Integer default 0 - }; - entity Genres as projection on my.Genres; -} - -// Additionally serve via HCQL and REST -annotate AdminService with @hcql @rest; diff --git a/tests/sample/status-management/srv/admin-service.js b/tests/sample/status-management/srv/admin-service.js deleted file mode 100644 index b6318166..00000000 --- a/tests/sample/status-management/srv/admin-service.js +++ /dev/null @@ -1,174 +0,0 @@ -const cds = require('@sap/cds'); - -const BOOK_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService'; -const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService'; - -module.exports = class AdminService extends cds.ApplicationService { - init() { - const { Authors, Books } = this.entities; - this.before(['NEW', 'CREATE'], 'Authors', genid); - this.before(['NEW', 'CREATE'], 'Books', genid); - - // --------------------------------------------------------------- - // Books: Enrich with approval process status (declarative start) - // --------------------------------------------------------------- - this.after('READ', Books, async (results, _req) => { - const processService = await cds.connect.to(BOOK_PROCESS); - - // Parallelize all process lookups using Promise.all instead of sequential loop - await Promise.all( - results.map(async (book) => { - const bookTitle = book.title; - if (!bookTitle) { - // Draft entries without a title yet - book.processStatus = 'No Approval Required'; - book.isApproved = true; - book.processCriticality = 0; // Neutral - return; - } - - try { - const instances = await processService.getInstancesByBusinessKey(bookTitle, [ - 'RUNNING', - 'COMPLETED', - 'CANCELED', - ]); - - if (instances[0]?.id && instances[0]?.status) { - const { id, status } = instances[0]; - - if (status === 'RUNNING') { - const attributes = await processService.getAttributes(id); - book.processStatus = attributes[0]?.value ?? 'Pending Approval'; - book.isApproved = false; - book.processCriticality = 2; // Warning (yellow) - } else if (status === 'COMPLETED') { - const outputs = await processService.getOutputs(id); - const { finalstatus, isapproved } = outputs; - book.processStatus = finalstatus; - book.isApproved = isapproved; - book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) - } else if (status === 'CANCELED') { - book.processStatus = 'Approval process cancelled as price has been reduced'; - book.isApproved = true; - book.processCriticality = 3; // Positive (green) - } - } else if (book.price > 50) { - // Process was likely just triggered but hasn't registered in SBPA yet - book.processStatus = 'Pending Approval'; - book.isApproved = false; - book.processCriticality = 2; // Warning (yellow) - } else { - book.processStatus = 'No Approval Required'; - book.isApproved = true; - book.processCriticality = 3; // Positive - } - } catch (err) { - book.processStatus = 'Status Unavailable'; - book.isApproved = false; - book.processCriticality = 0; // Neutral - } - }), - ); - }); - - // --------------------------------------------------------------- - // Authors: Programmatic process lifecycle (start, cancel, status) - // --------------------------------------------------------------- - - // Start verification process when a new author is created - this.after('CREATE', 'Authors', async (author, req) => { - try { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - await verificationService.start({ - authorname: author.name, - dateofbirth: author.dateOfBirth ?? '', - placeofbirth: author.placeOfBirth ?? '', - }); - } catch (err) { - req.warn(200, 'Author created, but verification process could not be started.'); - } - }); - - // Cancel verification process when an unverified author is deleted - this.after('DELETE', 'Authors', async (author, req) => { - if (!author.name) return; - - try { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - const instances = await verificationService.getInstancesByBusinessKey(author.name, [ - 'RUNNING', - ]); - if (instances.length > 0) { - await verificationService.cancel({ businessKey: author.name, cascade: true }); - } - } catch (err) { - req.warn(200, 'Author deleted, but verification process could not be cancelled.'); - } - }); - - // Enrich authors with verification process status - this.after('READ', Authors, async (results, _req) => { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - - await Promise.all( - results.map(async (author) => { - if (!author.name) { - author.verificationStatus = 'Pending'; - author.isVerified = false; - author.verificationCriticality = 2; // Warning (yellow) - return; - } - - try { - const instances = await verificationService.getInstancesByBusinessKey(author.name, [ - 'RUNNING', - 'COMPLETED', - 'CANCELED', - ]); - - if (instances[0]?.id && instances[0]?.status) { - const { id, status } = instances[0]; - - if (status === 'RUNNING') { - const attributes = await verificationService.getAttributes(id); - author.verificationStatus = attributes[0]?.value ?? 'Verification In Progress'; - author.isVerified = false; - author.verificationCriticality = 2; // Warning (yellow) - } else if (status === 'COMPLETED') { - const outputs = await verificationService.getOutputs(id); - author.verificationStatus = outputs.verificationstatus; - author.isVerified = outputs.isverified; - author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red - } else if (status === 'CANCELED') { - author.verificationStatus = 'Cancelled'; - author.isVerified = false; - author.verificationCriticality = 0; // Neutral - } - } else { - // Every new author triggers a verification process, so if no instance - // is found it's either a race condition (just created) or a pre-existing - // author that was never verified -- both correctly shown as pending. - author.verificationStatus = 'Verification Pending'; - author.isVerified = false; - author.verificationCriticality = 2; // Warning (yellow) - } - } catch (err) { - author.verificationStatus = 'Status Unavailable'; - author.isVerified = false; - author.verificationCriticality = 0; // Neutral - } - }), - ); - }); - - return super.init(); - } -}; - -/** Generate primary keys for target entity in request */ -async function genid(req) { - if (req.data.ID) return; - const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); - req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. -} diff --git a/tests/sample/status-management/srv/authors-constraints.cds b/tests/sample/status-management/srv/authors-constraints.cds new file mode 100644 index 00000000..d6097ece --- /dev/null +++ b/tests/sample/status-management/srv/authors-constraints.cds @@ -0,0 +1,19 @@ +using { AuthorsService } from './authors-service.cds'; + +// Add constraints for Authors +annotate AuthorsService.Authors with { + + name @mandatory; + + dateOfBirth @assert: (case + when dateOfBirth > dateOfDeath then 'Date of birth cannot be after date of death' + end); + + dateOfDeath @assert: (case + when dateOfDeath < dateOfBirth then 'Date of death cannot be before date of birth' + end); +} + +// Require 'admin' role to access AuthorsService +// (disabled for getting-started guide) +// annotate AuthorsService with @requires:'admin'; diff --git a/tests/sample/status-management/srv/authors-service.cds b/tests/sample/status-management/srv/authors-service.cds new file mode 100644 index 00000000..0ba3724b --- /dev/null +++ b/tests/sample/status-management/srv/authors-service.cds @@ -0,0 +1,13 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service AuthorsService @(path:'/api/authors', protocol: 'odata-v4') { + @odata.draft.enabled + entity Authors as projection on my.Authors { + *, + virtual verificationStatus: String, + virtual isVerified: Boolean default false, + virtual verificationCriticality: Integer default 0 + }; +} + + diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js new file mode 100644 index 00000000..52b4d9b3 --- /dev/null +++ b/tests/sample/status-management/srv/authors-service.js @@ -0,0 +1,99 @@ +const cds = require('@sap/cds'); + +const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService'; + +module.exports = class AuthorsService extends cds.ApplicationService { + init() { + const { Authors } = this.entities; + this.before(['NEW', 'CREATE'], 'Authors', genid); + + // --------------------------------------------------------------- + // Authors: Programmatic process lifecycle (start, cancel, status) + // --------------------------------------------------------------- + + // Start verification process when a new author is created + this.after('CREATE', 'Authors', async (author, req) => { + try { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + await verificationService.start({ + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); + } catch (err) { + req.warn(200, 'Author created, but verification process could not be started.'); + } + }); + + // Cancel verification process when an unverified author is deleted + this.after('DELETE', 'Authors', async (author, req) => { + if (!author.name) return; + + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + const instances = await verificationService.getInstancesByBusinessKey(author.name, [ + 'RUNNING', + ]); + if (instances.length > 0) { + await verificationService.cancel({ businessKey: author.name, cascade: true }); + } + }); + + // Enrich authors with verification process status + this.after('READ', Authors, async (results, _req) => { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + + await Promise.all( + results.map(async (author) => { + if (!author.name) { + author.verificationStatus = 'Pending'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + return; + } + + const instances = await verificationService.getInstancesByBusinessKey(author.name, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + if (status === 'RUNNING') { + const attributes = await verificationService.getAttributes(id); + author.verificationStatus = attributes[0]?.value ?? 'Verification In Progress'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } else if (status === 'COMPLETED') { + const outputs = await verificationService.getOutputs(id); + author.verificationStatus = outputs.verificationstatus; + author.isVerified = outputs.isverified; + author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red + } else if (status === 'CANCELED') { + author.verificationStatus = 'Cancelled'; + author.isVerified = false; + author.verificationCriticality = 0; // Neutral + } + } else { + // Every new author triggers a verification process, so if no instance + // is found it's either a race condition (just created) or a pre-existing + // author that was never verified -- both correctly shown as pending. + author.verificationStatus = 'Verification Pending'; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } + }), + ); + }); + + return super.init(); + } +}; + +/** Generate primary keys for target entity in request */ +async function genid(req) { + if (req.data.ID) return; + const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); + req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. +} diff --git a/tests/sample/status-management/srv/books-constraints.cds b/tests/sample/status-management/srv/books-constraints.cds new file mode 100644 index 00000000..6e2d8e4e --- /dev/null +++ b/tests/sample/status-management/srv/books-constraints.cds @@ -0,0 +1,27 @@ +using { BooksService } from './books-service.cds'; + +// Add constraints for input validation on Books +annotate BooksService.Books with { + + title @mandatory; + + author @assert: (case + when not exists author then 'Specified Author does not exist' + end); + + genre @assert: (case + when not exists genre then 'Specified Genre does not exist' + end); + + stock @assert.range: [(0),_]; // positive numbers only +} + +// Add constraints for Genres +annotate BooksService.Genres with { + + name @mandatory; + + parent @assert: (case + when parent == ID then 'A genre cannot be its own parent' + end); +} diff --git a/tests/sample/status-management/srv/admin-process.cds b/tests/sample/status-management/srv/books-process.cds similarity index 85% rename from tests/sample/status-management/srv/admin-process.cds rename to tests/sample/status-management/srv/books-process.cds index 3a391beb..0f2cfbca 100644 --- a/tests/sample/status-management/srv/admin-process.cds +++ b/tests/sample/status-management/srv/books-process.cds @@ -1,6 +1,6 @@ -using { AdminService } from './admin-service.cds'; +using { BooksService } from './books-service.cds'; -annotate AdminService.Books with @( +annotate BooksService.Books with @( bpm.process.businessKey: (title), bpm.process.start : { diff --git a/tests/sample/status-management/srv/books-service.cds b/tests/sample/status-management/srv/books-service.cds new file mode 100644 index 00000000..8916c88c --- /dev/null +++ b/tests/sample/status-management/srv/books-service.cds @@ -0,0 +1,15 @@ +using { sap.capire.bookshop as my } from '../db/schema'; + +service BooksService @(path:'/api/books', protocol: 'odata-v4') { + @odata.draft.enabled + entity Books as projection on my.Books { + *, + virtual processStatus: String, + virtual isApproved: Boolean default false, + virtual processCriticality: Integer default 0 + }; + entity Genres as projection on my.Genres; + @readonly entity Authors as projection on my.Authors; +} + + diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js new file mode 100644 index 00000000..759c21b9 --- /dev/null +++ b/tests/sample/status-management/srv/books-service.js @@ -0,0 +1,76 @@ +const cds = require('@sap/cds'); + +const BOOK_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService'; + +module.exports = class BooksService extends cds.ApplicationService { + init() { + const { Books } = this.entities; + this.before(['NEW', 'CREATE'], 'Books', genid); + + // --------------------------------------------------------------- + // Books: Enrich with approval process status (declarative start) + // --------------------------------------------------------------- + this.after('READ', Books, async (results, _req) => { + const processService = await cds.connect.to(BOOK_PROCESS); + + // Parallelize all process lookups using Promise.all instead of sequential loop + await Promise.all( + results.map(async (book) => { + const bookTitle = book.title; + if (!bookTitle) { + // Draft entries without a title yet + book.processStatus = 'No Approval Required'; + book.isApproved = true; + book.processCriticality = 0; // Neutral + return; + } + + const instances = await processService.getInstancesByBusinessKey(bookTitle, [ + 'RUNNING', + 'COMPLETED', + 'CANCELED', + ]); + + if (instances[0]?.id && instances[0]?.status) { + const { id, status } = instances[0]; + + if (status === 'RUNNING') { + const attributes = await processService.getAttributes(id); + book.processStatus = attributes[0]?.value ?? 'Pending Approval'; + book.isApproved = false; + book.processCriticality = 2; // Warning (yellow) + } else if (status === 'COMPLETED') { + const outputs = await processService.getOutputs(id); + const { finalstatus, isapproved } = outputs; + book.processStatus = finalstatus; + book.isApproved = isapproved; + book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) + } else if (status === 'CANCELED') { + book.processStatus = 'Approval process cancelled as price has been reduced'; + book.isApproved = true; + book.processCriticality = 3; // Positive (green) + } + } else if (book.price > 50) { + // Process was likely just triggered but hasn't registered in SBPA yet + book.processStatus = 'Pending Approval'; + book.isApproved = false; + book.processCriticality = 2; // Warning (yellow) + } else { + book.processStatus = 'No Approval Required'; + book.isApproved = true; + book.processCriticality = 3; // Positive + } + }), + ); + }); + + return super.init(); + } +}; + +/** Generate primary keys for target entity in request */ +async function genid(req) { + if (req.data.ID) return; + const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); + req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. +} From d91260c20aae12a0fc01a0f80f11f32ac8a11ee7 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 11:08:09 +0100 Subject: [PATCH 07/24] added readme --- tests/sample/status-management/readme.md | 151 ++++++++++++++++++++--- 1 file changed, 132 insertions(+), 19 deletions(-) diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md index 88511949..acc268fb 100644 --- a/tests/sample/status-management/readme.md +++ b/tests/sample/status-management/readme.md @@ -1,40 +1,153 @@ -# @capire/bookshop +# Status Management Bookshop -This is our primer sample for [Getting Started in a Nutshell](https://cap.cloud.sap/docs/get-started/in-a-nutshell). +A CAP sample application demonstrating how to integrate SAP Build Process Automation (SBPA) workflows into a bookshop scenario using the `@cap-js/process` plugin. The project showcases two distinct integration patterns -- **declarative** and **programmatic** -- for managing process lifecycles. +## Overview -## Get it +The application consists of two SAP Fiori Elements apps, each backed by its own CAP service: -```sh -git clone https://github.com/capire/bookshop -``` +| App | Service | Path | Purpose | +| ------------------ | ---------------- | -------------- | ---------------------------------------------------------------------------- | +| **Manage Books** | `BooksService` | `/api/books` | CRUD for books with an approval workflow triggered when the price exceeds 50 | +| **Manage Authors** | `AuthorsService` | `/api/authors` | CRUD for authors with a verification workflow triggered on every new author | +A third read-only service (`CatalogService` at `/browse`) exposes books for public browsing. -## Run it +Both apps are accessible from a shared **Fiori Launchpad sandbox** at `/fiori.html`. -```sh -cds watch bookshop +## Project Structure + +``` +db/ + schema.cds # Domain model: Books, Authors, Genres + data/ # CSV seed data +srv/ + books-service.cds # BooksService definition + books-service.js # Books handler: approval status enrichment + books-constraints.cds # Input validation for Books and Genres + authors-service.cds # AuthorsService definition + authors-service.js # Authors handler: verification lifecycle + status enrichment + authors-constraints.cds # Input validation for Authors + admin-process.cds # Declarative BPM annotations for Books + cat-service.cds # CatalogService (read-only browse) + cat-service.js # CatalogService handler + external/ # Generated process service definitions (do not edit) +app/ + fiori.html # Local Fiori Launchpad sandbox + services.cds # Imports annotations from both apps + books/ # Fiori Elements app for Manage Books + authors/ # Fiori Elements app for Manage Authors ``` -This should print something like that: +## How the `@cap-js/process` Plugin Is Used + +The [`@cap-js/process`](https://www.npmjs.com/package/@cap-js/process) plugin provides a CAP-native way to interact with SAP Build Process Automation. It generates typed service definitions from SBPA process definitions and offers both declarative CDS annotations and a programmatic API for managing process instances. + +### Pattern 1: Declarative Process Integration (Books) + +The book approval process is managed entirely through CDS annotations in `srv/admin-process.cds`, with no JavaScript needed for start/cancel: + +```cds +annotate BooksService.Books with @( + bpm.process.businessKey: (title), + bpm.process.start : { + id: 'eu12...bookApprovalProcess', + on: 'CREATE', + inputs: [ + { path: $self.title, as: 'booktitle' }, + { path: $self.descr, as: 'description' }, + $self.author.name, + $self.author.dateOfBirth, + $self.price, + ], + if: (price > 50) + }, + bpm.process.cancel : { + on: 'UPDATE', + if: (price <= 50) + } +); +``` -```sh -... -[cds] - server listening on { url: 'http://localhost:4004' } -[cds] - server v9.4.0 launched in 247 ms -[cds] - [ terminate with ^C ] +- **`@bpm.process.businessKey`** -- Correlates process instances back to entities using the book title. +- **`@bpm.process.start`** -- Automatically starts the approval process on `CREATE` when the price exceeds 50. Entity fields are mapped to process inputs, with support for renaming (`as`) and navigation paths (`$self.author.name`). +- **`@bpm.process.cancel`** -- Automatically cancels the running process on `UPDATE` when the price drops to 50 or below. + +### Pattern 2: Programmatic Process Integration (Authors) + +The author verification process is managed entirely in JavaScript (`srv/authors-service.js`), giving full control over the lifecycle: + +```js +// Start verification on author creation +this.after('CREATE', 'Authors', async (author, req) => { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + await verificationService.start({ + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); +}); + +// Cancel verification on author deletion +this.after('DELETE', 'Authors', async (author, req) => { + const verificationService = await cds.connect.to(AUTHOR_PROCESS); + const instances = await verificationService.getInstancesByBusinessKey(author.name, ['RUNNING']); + if (instances.length > 0) { + await verificationService.cancel({ businessKey: author.name, cascade: true }); + } +}); ``` -`Cmd-click` the http://localhost:4004 link in the terminal to open the app in a browser. +### Process Status Enrichment (Both Apps) + +Both services use the same pattern to display live process status in the UI. Virtual fields (`processStatus`, `isApproved`, `processCriticality` for Books; `verificationStatus`, `isVerified`, `verificationCriticality` for Authors) are declared in the CDS projections and populated in `after('READ')` handlers: + +1. **Look up** the process instance via `getInstancesByBusinessKey(businessKey, statusFilters)` +2. **Based on status:** + - `RUNNING` -- Fetch current step via `getAttributes(instanceId)` + - `COMPLETED` -- Fetch final result via `getOutputs(instanceId)` + - `CANCELED` -- Show cancellation message +3. **Set criticality** for Fiori UI coloring (0 = neutral, 1 = negative/red, 2 = warning/yellow, 3 = positive/green) + +### Declarative vs Programmatic: When to Use Which +| Aspect | Declarative (Books) | Programmatic (Authors) | +| -------------------- | ------------------------------------------------- | -------------------------------------------------------------- | +| Process start | CDS annotation `@bpm.process.start` | `processService.start(inputs)` in JS | +| Process cancel | CDS annotation `@bpm.process.cancel` | `processService.cancel(options)` in JS | +| Conditional triggers | CDS expression: `if: (price > 50)` | Custom JS logic | +| Input mapping | Declarative `inputs` array with `path`/`as` | Manual JS object construction | +| Best for | Standard workflows with simple trigger conditions | Complex logic, custom error handling, multi-step orchestration | +## Getting Started +### Prerequisites -## Get help +- Node.js >= 18 +- SAP CDS CLI (`npm i -g @sap/cds-dk`) -- Visit the [*capire* docs](https://cap.cloud.sap) to learn about CAP. -- Especially [*Getting Started in a Nutshell*](https://cap.cloud.sap/docs/get-started/in-a-nutshell). +### Install and Run + +```sh +npm install +cds watch +``` + +Open http://localhost:4004 in your browser. From there: + +- **Fiori Launchpad**: http://localhost:4004/fiori.html (both apps as tiles) +- **Manage Books**: http://localhost:4004/books/index.html +- **Manage Authors**: http://localhost:4004/authors/index.html + +### Hybrid Testing with SBPA + +To test against a real SAP Build Process Automation instance, configure the binding in `.cdsrc-private.json` and run: + +```sh +cds watch --profile hybrid +``` +For more information how to setup the plugin, please refer to the [plugins documentation](https://github.com/cap-js/process). ## License From c8e9114eef9b1c172d1882b7687d154f528005fd Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 11:10:10 +0100 Subject: [PATCH 08/24] fixed readme --- tests/sample/status-management/readme.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md index acc268fb..dc93cc95 100644 --- a/tests/sample/status-management/readme.md +++ b/tests/sample/status-management/readme.md @@ -41,11 +41,11 @@ app/ ## How the `@cap-js/process` Plugin Is Used -The [`@cap-js/process`](https://www.npmjs.com/package/@cap-js/process) plugin provides a CAP-native way to interact with SAP Build Process Automation. It generates typed service definitions from SBPA process definitions and offers both declarative CDS annotations and a programmatic API for managing process instances. +The [`@cap-js/process`](https://github.com/cap-js/process) plugin provides a CAP-native way to interact with SAP Build Process Automation. It generates typed service definitions from SBPA process definitions and offers both declarative CDS annotations and a programmatic API for managing process instances. ### Pattern 1: Declarative Process Integration (Books) -The book approval process is managed entirely through CDS annotations in `srv/admin-process.cds`, with no JavaScript needed for start/cancel: +The book approval process is managed entirely through CDS annotations in `srv/books-process.cds`, with no JavaScript needed for start/cancel: ```cds annotate BooksService.Books with @( @@ -148,7 +148,3 @@ cds watch --profile hybrid ``` For more information how to setup the plugin, please refer to the [plugins documentation](https://github.com/cap-js/process). - -## License - -Copyright (c) 2022 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) file. From 8118f901a389215b503aa095c24d8b7a2c60632f Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 12:45:05 +0100 Subject: [PATCH 09/24] fix --- tests/sample/status-management/index.cds | 2 +- .../status-management/srv/authors-service.js | 86 +++++++++---------- .../status-management/srv/books-service.js | 69 ++++++++------- 3 files changed, 77 insertions(+), 80 deletions(-) diff --git a/tests/sample/status-management/index.cds b/tests/sample/status-management/index.cds index ee859769..383b0034 100644 --- a/tests/sample/status-management/index.cds +++ b/tests/sample/status-management/index.cds @@ -2,7 +2,7 @@ // This file allows other projects to import the bookshop model by // using {...} from '@capire/bookshop'; -namespace sap.capire.status-management; //> important for reflection +namespace sap.capire.statusmanagement; //> important for reflection using from './db/schema'; using from './srv/cat-service'; using from './srv/books-service'; diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index 52b4d9b3..38ce84ef 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -2,56 +2,47 @@ const cds = require('@sap/cds'); const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificationProcessService'; +const VERIFICATION_PENDING = 'Verification Pending'; +const VERIFICATION_CANCELED = 'Cancelled'; module.exports = class AuthorsService extends cds.ApplicationService { - init() { + async init() { const { Authors } = this.entities; - this.before(['NEW', 'CREATE'], 'Authors', genid); - + const authorProcess = await cds.connect.to(AUTHOR_PROCESS); // --------------------------------------------------------------- // Authors: Programmatic process lifecycle (start, cancel, status) // --------------------------------------------------------------- // Start verification process when a new author is created - this.after('CREATE', 'Authors', async (author, req) => { - try { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - await verificationService.start({ - authorname: author.name, - dateofbirth: author.dateOfBirth ?? '', - placeofbirth: author.placeOfBirth ?? '', - }); - } catch (err) { - req.warn(200, 'Author created, but verification process could not be started.'); - } + this.after('CREATE', Authors, async (author, req) => { + await authorProcess.start({ + authorname: author.name, + dateofbirth: author.dateOfBirth ?? '', + placeofbirth: author.placeOfBirth ?? '', + }); }); // Cancel verification process when an unverified author is deleted - this.after('DELETE', 'Authors', async (author, req) => { + this.after('DELETE', Authors, async (author, req) => { if (!author.name) return; - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - const instances = await verificationService.getInstancesByBusinessKey(author.name, [ - 'RUNNING', - ]); + const instances = await authorProcess.getInstancesByBusinessKey(author.name, ['RUNNING']); if (instances.length > 0) { - await verificationService.cancel({ businessKey: author.name, cascade: true }); + await authorProcess.cancel({ businessKey: author.name, cascade: true }); } }); // Enrich authors with verification process status this.after('READ', Authors, async (results, _req) => { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - await Promise.all( results.map(async (author) => { if (!author.name) { - author.verificationStatus = 'Pending'; + author.verificationStatus = VERIFICATION_PENDING; author.isVerified = false; author.verificationCriticality = 2; // Warning (yellow) return; } - const instances = await verificationService.getInstancesByBusinessKey(author.name, [ + const instances = await authorProcess.getInstancesByBusinessKey(author.name, [ 'RUNNING', 'COMPLETED', 'CANCELED', @@ -60,26 +51,36 @@ module.exports = class AuthorsService extends cds.ApplicationService { if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; - if (status === 'RUNNING') { - const attributes = await verificationService.getAttributes(id); - author.verificationStatus = attributes[0]?.value ?? 'Verification In Progress'; - author.isVerified = false; - author.verificationCriticality = 2; // Warning (yellow) - } else if (status === 'COMPLETED') { - const outputs = await verificationService.getOutputs(id); - author.verificationStatus = outputs.verificationstatus; - author.isVerified = outputs.isverified; - author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red - } else if (status === 'CANCELED') { - author.verificationStatus = 'Cancelled'; - author.isVerified = false; - author.verificationCriticality = 0; // Neutral + switch (status) { + case 'RUNNING': + { + const attributes = await authorProcess.getAttributes(id); + author.verificationStatus = attributes[0]?.value ?? VERIFICATION_PENDING; + author.isVerified = false; + author.verificationCriticality = 2; // Warning (yellow) + } + break; + case 'COMPLETED': + { + const outputs = await authorProcess.getOutputs(id); + author.verificationStatus = outputs.verificationstatus; + author.isVerified = outputs.isverified; + author.verificationCriticality = outputs.isverified ? 3 : 1; // Green or Red + } + break; + case 'CANCELED': + { + author.verificationStatus = VERIFICATION_CANCELED; + author.isVerified = false; + author.verificationCriticality = 0; // Neutral + } + break; } } else { // Every new author triggers a verification process, so if no instance // is found it's either a race condition (just created) or a pre-existing // author that was never verified -- both correctly shown as pending. - author.verificationStatus = 'Verification Pending'; + author.verificationStatus = VERIFICATION_PENDING; author.isVerified = false; author.verificationCriticality = 2; // Warning (yellow) } @@ -90,10 +91,3 @@ module.exports = class AuthorsService extends cds.ApplicationService { return super.init(); } }; - -/** Generate primary keys for target entity in request */ -async function genid(req) { - if (req.data.ID) return; - const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); - req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. -} diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js index 759c21b9..577d740f 100644 --- a/tests/sample/status-management/srv/books-service.js +++ b/tests/sample/status-management/srv/books-service.js @@ -2,30 +2,30 @@ const cds = require('@sap/cds'); const BOOK_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.BookApprovalProcessService'; +const NO_APPROVAL_REQUIRED = 'No Approval Required'; +const PENDING_APPROVAL = 'Pending Approval'; +const APPROVAL_CANCELLED = 'Approval process cancelled as price has been reduced'; + module.exports = class BooksService extends cds.ApplicationService { - init() { + async init() { const { Books } = this.entities; - this.before(['NEW', 'CREATE'], 'Books', genid); - + const bookProcess = await cds.connect.to(BOOK_PROCESS); // --------------------------------------------------------------- // Books: Enrich with approval process status (declarative start) // --------------------------------------------------------------- this.after('READ', Books, async (results, _req) => { - const processService = await cds.connect.to(BOOK_PROCESS); - - // Parallelize all process lookups using Promise.all instead of sequential loop await Promise.all( results.map(async (book) => { const bookTitle = book.title; if (!bookTitle) { // Draft entries without a title yet - book.processStatus = 'No Approval Required'; + book.processStatus = NO_APPROVAL_REQUIRED; book.isApproved = true; book.processCriticality = 0; // Neutral return; } - const instances = await processService.getInstancesByBusinessKey(bookTitle, [ + const instances = await bookProcess.getInstancesByBusinessKey(bookTitle, [ 'RUNNING', 'COMPLETED', 'CANCELED', @@ -34,31 +34,41 @@ module.exports = class BooksService extends cds.ApplicationService { if (instances[0]?.id && instances[0]?.status) { const { id, status } = instances[0]; - if (status === 'RUNNING') { - const attributes = await processService.getAttributes(id); - book.processStatus = attributes[0]?.value ?? 'Pending Approval'; - book.isApproved = false; - book.processCriticality = 2; // Warning (yellow) - } else if (status === 'COMPLETED') { - const outputs = await processService.getOutputs(id); - const { finalstatus, isapproved } = outputs; - book.processStatus = finalstatus; - book.isApproved = isapproved; - book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) - } else if (status === 'CANCELED') { - book.processStatus = 'Approval process cancelled as price has been reduced'; - book.isApproved = true; - book.processCriticality = 3; // Positive (green) + switch (status) { + case 'RUNNING': + { + let attributes = await bookProcess.getAttributes(id); + book.processStatus = attributes[0]?.value ?? PENDING_APPROVAL; + book.isApproved = false; + book.processCriticality = 2; + } + break; + case 'COMPLETED': + { + const outputs = await bookProcess.getOutputs(id); + const { finalstatus, isapproved } = outputs; + book.processStatus = finalstatus; + book.isApproved = isapproved; + book.processCriticality = isapproved ? 3 : 1; // Positive (green) or Negative (red) + } + break; + case 'CANCELED': + { + book.processStatus = APPROVAL_CANCELLED; + book.isApproved = true; + book.processCriticality = 3; // Positive (green) + } + break; } } else if (book.price > 50) { // Process was likely just triggered but hasn't registered in SBPA yet - book.processStatus = 'Pending Approval'; + book.processStatus = PENDING_APPROVAL; book.isApproved = false; book.processCriticality = 2; // Warning (yellow) } else { - book.processStatus = 'No Approval Required'; + book.processStatus = NO_APPROVAL_REQUIRED; book.isApproved = true; - book.processCriticality = 3; // Positive + book.processCriticality = 3; // Positive (green) } }), ); @@ -67,10 +77,3 @@ module.exports = class BooksService extends cds.ApplicationService { return super.init(); } }; - -/** Generate primary keys for target entity in request */ -async function genid(req) { - if (req.data.ID) return; - const { id } = await SELECT.one.from(req.target).columns('max(ID) as id'); - req.data.ID = id + 4; // Note: that is not safe! ok for this sample only. -} From b5a94946e6c4bbd2509968505bc9e76526df92ae Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 13:06:41 +0100 Subject: [PATCH 10/24] changed ID from integer to UUID --- .../status-management/app/authors/annotations.cds | 10 ---------- .../status-management/app/books/annotations.cds | 10 ---------- .../db/data/sap.capire.bookshop-Authors.csv | 8 ++++---- .../db/data/sap.capire.bookshop-Books.csv | 10 +++++----- .../db/data/sap.capire.bookshop-Books.texts.csv | 10 +++++----- tests/sample/status-management/db/schema.cds | 12 ++---------- .../sample/status-management/srv/authors-service.js | 7 ++++--- tests/sample/status-management/srv/books-process.cds | 3 ++- tests/sample/status-management/srv/books-service.cds | 1 + tests/sample/status-management/srv/books-service.js | 6 +++--- ...eapplicationproject.authorVerificationProcess.cds | 3 ++- ....sampleapplicationproject.bookApprovalProcess.cds | 3 ++- ...applicationproject.authorVerificationProcess.json | 7 ++++++- ...sampleapplicationproject.bookApprovalProcess.json | 11 ++++++++--- 14 files changed, 44 insertions(+), 57 deletions(-) diff --git a/tests/sample/status-management/app/authors/annotations.cds b/tests/sample/status-management/app/authors/annotations.cds index da38e364..044a5e2b 100644 --- a/tests/sample/status-management/app/authors/annotations.cds +++ b/tests/sample/status-management/app/authors/annotations.cds @@ -3,11 +3,6 @@ annotate service.Authors with @( UI.FieldGroup #GeneratedGroup : { $Type : 'UI.FieldGroupType', Data : [ - { - $Type : 'UI.DataField', - Label : 'ID', - Value : ID, - }, { $Type : 'UI.DataField', Label : 'Name', @@ -55,11 +50,6 @@ annotate service.Authors with @( }, ], UI.LineItem : [ - { - $Type : 'UI.DataField', - Label : 'ID', - Value : ID, - }, { $Type : 'UI.DataField', Label : 'Name', diff --git a/tests/sample/status-management/app/books/annotations.cds b/tests/sample/status-management/app/books/annotations.cds index 801e0a7c..d69a2eb5 100644 --- a/tests/sample/status-management/app/books/annotations.cds +++ b/tests/sample/status-management/app/books/annotations.cds @@ -3,11 +3,6 @@ annotate service.Books with @( UI.FieldGroup #GeneratedGroup : { $Type : 'UI.FieldGroupType', Data : [ - { - $Type : 'UI.DataField', - Label : 'ID', - Value : ID, - }, { $Type : 'UI.DataField', Label : 'Author', @@ -65,11 +60,6 @@ annotate service.Books with @( }, ], UI.LineItem : [ - { - $Type : 'UI.DataField', - Label : 'ID', - Value : ID, - }, { $Type : 'UI.DataField', Label : 'Author', diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv index 93a4d579..63723e7a 100644 --- a/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Authors.csv @@ -1,5 +1,5 @@ ID,name,dateOfBirth,placeOfBirth,dateOfDeath,placeOfDeath -101,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire" -107,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire" -150,Edgar Allan Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland" -170,Richard Carpenter,1929-08-14,"King’s Lynn, Norfolk",2012-02-26,"Hertfordshire, England" +a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d,Emily Brontë,1818-07-30,"Thornton, Yorkshire",1848-12-19,"Haworth, Yorkshire" +b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e,Charlotte Brontë,1818-04-21,"Thornton, Yorkshire",1855-03-31,"Haworth, Yorkshire" +c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,Edgar Allan Poe,1809-01-19,"Boston, Massachusetts",1849-10-07,"Baltimore, Maryland" +d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a,Richard Carpenter,1929-08-14,"King's Lynn, Norfolk",2012-02-26,"Hertfordshire, England" diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv index a359e7c2..c96b0a13 100644 --- a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.csv @@ -1,6 +1,6 @@ ID,title,descr,author_ID,stock,price,currency_code,genre_ID -201,Wuthering Heights,"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.",101,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa -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,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa -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,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa -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,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa -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,3,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,Wuthering Heights,"Wuthering Heights, Emily Brontë's only novel, was published in 1847 under the pseudonym ""Ellis Bell"". It was written between October 1845 and June 1846. Wuthering Heights and Anne Brontë's Agnes Grey were accepted by publisher Thomas Newby before the success of their sister Charlotte's novel Jane Eyre. After Emily's death, Charlotte edited the manuscript of Wuthering Heights and arranged for the edited version to be published as a posthumous second edition in 1850.",a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d,12,11.11,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c,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.",b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e,11,12.34,GBP,11aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +a7b8c9d0-e1f2-4a3b-4c5d-6e7f8a9b0c1d,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.",c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,333,13.13,USD,16aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e,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.",c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f,555,14,USD,15aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +c9d0e1f2-a3b4-4c5d-6e7f-8a9b0c1d2e3f,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.",d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a,22,3,JPY,13aaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa diff --git a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv index 97d60e65..af8f78ee 100644 --- a/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv +++ b/tests/sample/status-management/db/data/sap.capire.bookshop-Books.texts.csv @@ -1,5 +1,5 @@ -ID_texts,ID,locale,title,descr -d2a65a27-9f2a-480f-bc38-84ee8ec5c13e,201,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts." -8c42c706-a979-41cf-9ffe-91e6cf1383a0,201,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d’Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal." -9e1c4c81-dc90-4600-85b1-e9dd4bf12ce0,207,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte" -9be0524b-4cb9-4fc1-9dc2-d65b1c13cf53,252,de,Eleonora,"“Eleonora” ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit." +ID,locale,title,descr +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,de,Sturmhöhe,"Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman der englischen Schriftstellerin Emily Brontë (1818–1848). Der 1847 unter dem Pseudonym Ellis Bell veröffentlichte Roman wurde vom viktorianischen Publikum weitgehend abgelehnt, heute gilt er als ein Klassiker der britischen Romanliteratur des 19. Jahrhunderts." +e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b,fr,Les Hauts de Hurlevent,"Les Hauts de Hurlevent (titre original : Wuthering Heights), parfois orthographié Les Hauts de Hurle-Vent, est l'unique roman d'Emily Brontë, publié pour la première fois en 1847 sous le pseudonyme d'Ellis Bell. Loin d'être un récit moralisateur, Emily Brontë achève néanmoins le roman dans une atmosphère sereine, suggérant le triomphe de la paix et du Bien sur la vengeance et le Mal." +f6a7b8c9-d0e1-4f2a-3b4c-5d6e7f8a9b0c,de,Jane Eyre,"Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography), erstmals erschienen im Jahr 1847 unter dem Pseudonym Currer Bell, ist der erste veröffentlichte Roman der britischen Autorin Charlotte Brontë und ein Klassiker der viktorianischen Romanliteratur des 19. Jahrhunderts. Der Roman erzählt in Form einer Ich-Erzählung die Lebensgeschichte von Jane Eyre (ausgesprochen /ˌdʒeɪn ˈɛə/), die nach einer schweren Kindheit eine Stelle als Gouvernante annimmt und sich in ihren Arbeitgeber verliebt, jedoch immer wieder um ihre Freiheit und Selbstbestimmung kämpfen muss. Als klein, dünn, blass, stets schlicht dunkel gekleidet und mit strengem Mittelscheitel beschrieben, gilt die Heldin des Romans Jane Eyre nicht zuletzt aufgrund der Kino- und Fernsehversionen der melodramatischen Romanvorlage als die bekannteste englische Gouvernante der Literaturgeschichte" +b8c9d0e1-f2a3-4b4c-5d6e-7f8a9b0c1d2e,de,Eleonora,"„Eleonora" ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841 erstveröffentlicht. In ihr geht es um das Paradox der Treue in der Treulosigkeit." diff --git a/tests/sample/status-management/db/schema.cds b/tests/sample/status-management/db/schema.cds index e8decf26..ad69f361 100644 --- a/tests/sample/status-management/db/schema.cds +++ b/tests/sample/status-management/db/schema.cds @@ -1,8 +1,7 @@ using { Currency, cuid, managed, sap } from '@sap/cds/common'; namespace sap.capire.bookshop; -entity Books : managed { - key ID : Integer; +entity Books : cuid, managed { author : Association to Authors @mandatory; title : localized String @mandatory; descr : localized String(2000); @@ -12,8 +11,7 @@ entity Books : managed { currency : Currency; } -entity Authors : managed { - key ID : Integer; +entity Authors : cuid, managed { name : String @mandatory; dateOfBirth : Date; dateOfDeath : Date; @@ -30,9 +28,3 @@ entity Genres : cuid, sap.common.CodeList { type Price : Decimal(9,2); - -// -------------------------------------------------------------------------------- -// Temporary workaround for this situation: -// - Fiori apps in bookstore annotate Books with @fiori.draft.enabled. -// - Because of that .csv data has to eagerly fill in ID_texts column. -annotate Books with @fiori.draft.enabled; diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index 38ce84ef..3cfffac2 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -15,6 +15,7 @@ module.exports = class AuthorsService extends cds.ApplicationService { // Start verification process when a new author is created this.after('CREATE', Authors, async (author, req) => { await authorProcess.start({ + entityid: author.ID, authorname: author.name, dateofbirth: author.dateOfBirth ?? '', placeofbirth: author.placeOfBirth ?? '', @@ -25,9 +26,9 @@ module.exports = class AuthorsService extends cds.ApplicationService { this.after('DELETE', Authors, async (author, req) => { if (!author.name) return; - const instances = await authorProcess.getInstancesByBusinessKey(author.name, ['RUNNING']); + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); if (instances.length > 0) { - await authorProcess.cancel({ businessKey: author.name, cascade: true }); + await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } }); @@ -42,7 +43,7 @@ module.exports = class AuthorsService extends cds.ApplicationService { return; } - const instances = await authorProcess.getInstancesByBusinessKey(author.name, [ + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, [ 'RUNNING', 'COMPLETED', 'CANCELED', diff --git a/tests/sample/status-management/srv/books-process.cds b/tests/sample/status-management/srv/books-process.cds index 0f2cfbca..9e2adb16 100644 --- a/tests/sample/status-management/srv/books-process.cds +++ b/tests/sample/status-management/srv/books-process.cds @@ -2,11 +2,12 @@ using { BooksService } from './books-service.cds'; annotate BooksService.Books with @( - bpm.process.businessKey: (title), + bpm.process.businessKey: (ID), bpm.process.start : { id: 'eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess', on: 'CREATE', inputs: [ + { path: $self.ID, as: 'entityid' }, { path: $self.title, as: 'booktitle'}, { path: $self.descr, as: 'description'}, $self.author.name, diff --git a/tests/sample/status-management/srv/books-service.cds b/tests/sample/status-management/srv/books-service.cds index 8916c88c..a62b023d 100644 --- a/tests/sample/status-management/srv/books-service.cds +++ b/tests/sample/status-management/srv/books-service.cds @@ -3,6 +3,7 @@ using { sap.capire.bookshop as my } from '../db/schema'; service BooksService @(path:'/api/books', protocol: 'odata-v4') { @odata.draft.enabled entity Books as projection on my.Books { + descr @mandatory, *, virtual processStatus: String, virtual isApproved: Boolean default false, diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js index 577d740f..df1b5a66 100644 --- a/tests/sample/status-management/srv/books-service.js +++ b/tests/sample/status-management/srv/books-service.js @@ -16,8 +16,8 @@ module.exports = class BooksService extends cds.ApplicationService { this.after('READ', Books, async (results, _req) => { await Promise.all( results.map(async (book) => { - const bookTitle = book.title; - if (!bookTitle) { + const bookID = book.ID; + if (!bookID) { // Draft entries without a title yet book.processStatus = NO_APPROVAL_REQUIRED; book.isApproved = true; @@ -25,7 +25,7 @@ module.exports = class BooksService extends cds.ApplicationService { return; } - const instances = await bookProcess.getInstancesByBusinessKey(bookTitle, [ + const instances = await bookProcess.getInstancesByBusinessKey(bookID, [ 'RUNNING', 'COMPLETED', 'CANCELED', diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds index 0a06ac89..dec633f0 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.cds @@ -1,4 +1,4 @@ -/* checksum : c0ca6e98ae2cb22ff127e1e4dbf34192 */ +/* checksum : e4e85b6560cc4b66e72dcd48b9b3ee30 */ namespace eu12.cdsmunich.sampleapplicationproject; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -9,6 +9,7 @@ service AuthorVerificationProcessService { authorname : String not null; dateofbirth : String; placeofbirth : String; + entityid : String; }; type ProcessOutputs { diff --git a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds index faae4b76..1c78ad8a 100644 --- a/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds +++ b/tests/sample/status-management/srv/external/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.cds @@ -1,4 +1,4 @@ -/* checksum : d228165baeb7c907ccb0fea23d8b708d */ +/* checksum : 814a8d2cd20e74f6fa5a984733c3fe07 */ namespace eu12.cdsmunich.sampleapplicationproject; /** DO NOT EDIT. THIS IS A GENERATED SERVICE THAT WILL BE OVERRIDDEN ON NEXT IMPORT. */ @@ -15,6 +15,7 @@ service BookApprovalProcessService { author : Author; description : String; price : DecimalFloat; + entityid : String; }; type ProcessOutputs { diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json index eae4c5ab..897b70d8 100644 --- a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json @@ -4,7 +4,7 @@ "description": "", "type": "bpi.process", "createdAt": "2026-03-26T08:54:22.179844Z", - "updatedAt": "2026-03-26T09:17:11.736398Z", + "updatedAt": "2026-03-26T11:59:23.987727Z", "header": { "inputs": { "title": "inputs", @@ -47,6 +47,11 @@ "type": "string", "title": "placeOfBirth", "description": "" + }, + "entityid": { + "type": "string", + "title": "entityID", + "description": "" } }, "required": [ diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json index d961fb56..c701a320 100644 --- a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json @@ -4,7 +4,7 @@ "description": "", "type": "bpi.process", "createdAt": "2026-03-25T11:46:56.091829Z", - "updatedAt": "2026-03-25T13:23:41.616274Z", + "updatedAt": "2026-03-26T12:00:00.525166Z", "header": { "inputs": { "title": "inputs", @@ -53,6 +53,11 @@ "type": "number", "title": "price", "description": "" + }, + "entityid": { + "type": "string", + "title": "entityID", + "description": "" } }, "required": [] @@ -111,11 +116,11 @@ }, "dependencies": [ { - "artifactUid": "5227d63c-f620-476c-893a-517bd14c302e", + "artifactUid": "3e51af12-9a42-4a95-9a3a-d12ef176ea42", "type": "content" }, { - "artifactUid": "3e51af12-9a42-4a95-9a3a-d12ef176ea42", + "artifactUid": "5227d63c-f620-476c-893a-517bd14c302e", "type": "content" }, { From fc51f9bafb303873b6b415be9a06d835b7a5979d Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 13:14:01 +0100 Subject: [PATCH 11/24] minor fix --- .../status-management/srv/authors-service.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index 3cfffac2..c0bd322a 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -4,16 +4,21 @@ const AUTHOR_PROCESS = 'eu12.cdsmunich.sampleapplicationproject.AuthorVerificati const VERIFICATION_PENDING = 'Verification Pending'; const VERIFICATION_CANCELED = 'Cancelled'; +const VERIFICATION_SUCCESSFUL = 'Author verified'; + module.exports = class AuthorsService extends cds.ApplicationService { async init() { const { Authors } = this.entities; const authorProcess = await cds.connect.to(AUTHOR_PROCESS); + // Track authors created at runtime so we can distinguish them from seed data + const createdAuthorIDs = new Set(); // --------------------------------------------------------------- // Authors: Programmatic process lifecycle (start, cancel, status) // --------------------------------------------------------------- // Start verification process when a new author is created this.after('CREATE', Authors, async (author, req) => { + createdAuthorIDs.add(author.ID); await authorProcess.start({ entityid: author.ID, authorname: author.name, @@ -77,13 +82,16 @@ module.exports = class AuthorsService extends cds.ApplicationService { } break; } - } else { - // Every new author triggers a verification process, so if no instance - // is found it's either a race condition (just created) or a pre-existing - // author that was never verified -- both correctly shown as pending. + } else if (createdAuthorIDs.has(author.ID)) { + // Just created but process instance not yet visible (race condition) author.verificationStatus = VERIFICATION_PENDING; author.isVerified = false; author.verificationCriticality = 2; // Warning (yellow) + } else { + // Pre-existing seed data author with no process — treat as verified + author.verificationStatus = VERIFICATION_SUCCESSFUL; + author.isVerified = true; + author.verificationCriticality = 3; // Positive (green) } }), ); From 264060e5fc083f07aee8c1a47e29276139dc81fb Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 13:29:00 +0100 Subject: [PATCH 12/24] eslint config --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index b969ea33..d63c26c4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,7 +3,7 @@ import tseslint from 'typescript-eslint'; export default [ { - ignores: ['dist/**', 'gen/**', 'node_modules/**', '@cds-models/**'], + ignores: ['dist/**', 'gen/**', 'node_modules/**', '@cds-models/**', 'tests/sample/**'], }, ...cds, ...tseslint.configs.recommended, From 32b1b3f394bdc82d64ae479fe2cbce5462a6e9c0 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 13:29:07 +0100 Subject: [PATCH 13/24] minor enhancements --- tests/sample/status-management/srv/authors-constraints.cds | 2 +- tests/sample/status-management/srv/authors-service.cds | 6 +++--- tests/sample/status-management/srv/books-constraints.cds | 7 ++++--- tests/sample/status-management/srv/books-service.cds | 7 +++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/sample/status-management/srv/authors-constraints.cds b/tests/sample/status-management/srv/authors-constraints.cds index d6097ece..f32b7306 100644 --- a/tests/sample/status-management/srv/authors-constraints.cds +++ b/tests/sample/status-management/srv/authors-constraints.cds @@ -5,7 +5,7 @@ annotate AuthorsService.Authors with { name @mandatory; - dateOfBirth @assert: (case + dateOfBirth @mandatory@assert: (case when dateOfBirth > dateOfDeath then 'Date of birth cannot be after date of death' end); diff --git a/tests/sample/status-management/srv/authors-service.cds b/tests/sample/status-management/srv/authors-service.cds index 0ba3724b..28fe4a14 100644 --- a/tests/sample/status-management/srv/authors-service.cds +++ b/tests/sample/status-management/srv/authors-service.cds @@ -4,9 +4,9 @@ service AuthorsService @(path:'/api/authors', protocol: 'odata-v4') { @odata.draft.enabled entity Authors as projection on my.Authors { *, - virtual verificationStatus: String, - virtual isVerified: Boolean default false, - virtual verificationCriticality: Integer default 0 + @UI.Hidden: (not $draft.IsActiveEntity) virtual verificationStatus: String, + @UI.Hidden: (not $draft.IsActiveEntity) virtual isVerified: Boolean default false, + @UI.Hidden: (not $draft.IsActiveEntity) virtual verificationCriticality: Integer default 0 }; } diff --git a/tests/sample/status-management/srv/books-constraints.cds b/tests/sample/status-management/srv/books-constraints.cds index 6e2d8e4e..c06c53ef 100644 --- a/tests/sample/status-management/srv/books-constraints.cds +++ b/tests/sample/status-management/srv/books-constraints.cds @@ -4,12 +4,13 @@ using { BooksService } from './books-service.cds'; annotate BooksService.Books with { title @mandatory; - - author @assert: (case + descr @mandatory; + price @mandatory; + author @mandatory @assert: (case when not exists author then 'Specified Author does not exist' end); - genre @assert: (case + genre @mandatory @assert: (case when not exists genre then 'Specified Genre does not exist' end); diff --git a/tests/sample/status-management/srv/books-service.cds b/tests/sample/status-management/srv/books-service.cds index a62b023d..818623c1 100644 --- a/tests/sample/status-management/srv/books-service.cds +++ b/tests/sample/status-management/srv/books-service.cds @@ -3,11 +3,10 @@ using { sap.capire.bookshop as my } from '../db/schema'; service BooksService @(path:'/api/books', protocol: 'odata-v4') { @odata.draft.enabled entity Books as projection on my.Books { - descr @mandatory, *, - virtual processStatus: String, - virtual isApproved: Boolean default false, - virtual processCriticality: Integer default 0 + @UI.Hidden: (not $draft.IsActiveEntity) virtual processStatus: String, + @UI.Hidden: (not $draft.IsActiveEntity) virtual isApproved: Boolean default false, + @UI.Hidden: (not $draft.IsActiveEntity) virtual processCriticality: Integer default 0 }; entity Genres as projection on my.Genres; @readonly entity Authors as projection on my.Authors; From 71111c1a330d94668de1f35abe8cb87b9cb68c9a Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 13:34:47 +0100 Subject: [PATCH 14/24] prettier changes --- .../status-management/app/authors/README.md | 45 ++++++----- .../status-management/app/authors/ui5.yaml | 2 +- .../app/authors/webapp/Component.js | 19 ++--- .../app/authors/webapp/index.html | 54 +++++++------- .../webapp/test/integration/FirstJourney.js | 50 ++++++------- .../test/integration/opaTests.qunit.html | 32 ++++---- .../webapp/test/integration/opaTests.qunit.js | 44 +++++------ .../test/integration/pages/AuthorsList.js | 30 ++++---- .../integration/pages/AuthorsObjectPage.js | 30 ++++---- .../test/integration/pages/JourneyRunner.js | 28 +++---- .../authors/webapp/test/testsuite.qunit.html | 4 +- .../authors/webapp/test/testsuite.qunit.js | 15 ++-- .../status-management/app/books/README.md | 45 ++++++----- .../status-management/app/books/ui5.yaml | 2 +- .../app/books/webapp/Component.js | 19 ++--- .../app/books/webapp/index.html | 54 +++++++------- .../webapp/test/integration/FirstJourney.js | 50 ++++++------- .../test/integration/opaTests.qunit.html | 32 ++++---- .../webapp/test/integration/opaTests.qunit.js | 44 +++++------ .../test/integration/pages/BooksList.js | 30 ++++---- .../test/integration/pages/BooksObjectPage.js | 30 ++++---- .../test/integration/pages/JourneyRunner.js | 28 +++---- .../books/webapp/test/testsuite.qunit.html | 4 +- .../app/books/webapp/test/testsuite.qunit.js | 15 ++-- tests/sample/status-management/app/fiori.html | 74 +++++++++---------- tests/sample/status-management/db/init.js | 24 +++--- tests/sample/status-management/package.json | 2 +- .../status-management/srv/cat-service.js | 48 ++++++------ ...tionproject.authorVerificationProcess.json | 11 +-- ...pplicationproject.bookApprovalProcess.json | 7 +- 30 files changed, 429 insertions(+), 443 deletions(-) diff --git a/tests/sample/status-management/app/authors/README.md b/tests/sample/status-management/app/authors/README.md index 5051dc47..c67f0fc8 100644 --- a/tests/sample/status-management/app/authors/README.md +++ b/tests/sample/status-management/app/authors/README.md @@ -1,23 +1,24 @@ ## Application Details -| | -| ------------- | -|**Generation Date and Time**
Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time)| -|**App Generator**
SAP Fiori Application Generator| -|**App Generator Version**
1.20.0| -|**Generation Platform**
Visual Studio Code| -|**Template Used**
List Report Page V4| -|**Service Type**
Local CAP| -|**Service URL**
http://localhost:4004/admin/| -|**Module Name**
authors| -|**Application Title**
Manage Authors| -|**Namespace**
| -|**UI5 Theme**
sap_horizon| -|**UI5 Version**
1.146.0| -|**Enable Code Assist Libraries**
False| -|**Enable TypeScript**
False| -|**Add Eslint configuration**
False| -|**Main Entity**
Authors| -|**Navigation Entity**
None| + +| | +| -------------------------------------------------------------------------------------------------- | +| **Generation Date and Time**
Tue Mar 24 2026 16:30:55 GMT+0100 (Central European Standard Time) | +| **App Generator**
SAP Fiori Application Generator | +| **App Generator Version**
1.20.0 | +| **Generation Platform**
Visual Studio Code | +| **Template Used**
List Report Page V4 | +| **Service Type**
Local CAP | +| **Service URL**
http://localhost:4004/admin/ | +| **Module Name**
authors | +| **Application Title**
Manage Authors | +| **Namespace**
| +| **UI5 Theme**
sap_horizon | +| **UI5 Version**
1.146.0 | +| **Enable Code Assist Libraries**
False | +| **Enable TypeScript**
False | +| **Add Eslint configuration**
False | +| **Main Entity**
Authors | +| **Navigation Entity**
None | ## authors @@ -25,12 +26,10 @@ An SAP Fiori application. ### Starting the generated app -- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: http://localhost:4004/authors/index.html #### Pre-requisites: -1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) - - +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) diff --git a/tests/sample/status-management/app/authors/ui5.yaml b/tests/sample/status-management/app/authors/ui5.yaml index 8b1a1abb..2f5e482f 100644 --- a/tests/sample/status-management/app/authors/ui5.yaml +++ b/tests/sample/status-management/app/authors/ui5.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json -specVersion: "4.0" +specVersion: '4.0' metadata: name: authors type: application diff --git a/tests/sample/status-management/app/authors/webapp/Component.js b/tests/sample/status-management/app/authors/webapp/Component.js index 72751153..f9204049 100644 --- a/tests/sample/status-management/app/authors/webapp/Component.js +++ b/tests/sample/status-management/app/authors/webapp/Component.js @@ -1,12 +1,9 @@ -sap.ui.define( - ["sap/fe/core/AppComponent"], - function (Component) { - "use strict"; +sap.ui.define(['sap/fe/core/AppComponent'], function (Component) { + 'use strict'; - return Component.extend("authors.Component", { - metadata: { - manifest: "json" - } - }); - } -); \ No newline at end of file + return Component.extend('authors.Component', { + metadata: { + manifest: 'json', + }, + }); +}); diff --git a/tests/sample/status-management/app/authors/webapp/index.html b/tests/sample/status-management/app/authors/webapp/index.html index b03c0bcf..b3aab9d6 100644 --- a/tests/sample/status-management/app/authors/webapp/index.html +++ b/tests/sample/status-management/app/authors/webapp/index.html @@ -1,35 +1,39 @@ - + - - - - + + + + Manage Authors - - + +
- - \ No newline at end of file + + diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js index c3ef5255..5730f8c2 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js +++ b/tests/sample/status-management/app/authors/webapp/test/integration/FirstJourney.js @@ -1,37 +1,31 @@ -sap.ui.define([ - "sap/ui/test/opaQunit", - "./pages/JourneyRunner" -], function (opaTest, runner) { - "use strict"; +sap.ui.define(['sap/ui/test/opaQunit', './pages/JourneyRunner'], function (opaTest, runner) { + 'use strict'; - function journey() { - QUnit.module("First journey"); + function journey() { + QUnit.module('First journey'); - opaTest("Start application", function (Given, When, Then) { - Given.iStartMyApp(); + opaTest('Start application', function (Given, When, Then) { + Given.iStartMyApp(); - Then.onTheAuthorsList.iSeeThisPage(); + Then.onTheAuthorsList.iSeeThisPage(); + }); - }); + opaTest('Navigate to ObjectPage', function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + When.onTheAuthorsList.onFilterBar().iExecuteSearch(); - opaTest("Navigate to ObjectPage", function (Given, When, Then) { - // Note: this test will fail if the ListReport page doesn't show any data - - When.onTheAuthorsList.onFilterBar().iExecuteSearch(); - - Then.onTheAuthorsList.onTable().iCheckRows(); + Then.onTheAuthorsList.onTable().iCheckRows(); - When.onTheAuthorsList.onTable().iPressRow(0); - Then.onTheAuthorsObjectPage.iSeeThisPage(); + When.onTheAuthorsList.onTable().iPressRow(0); + Then.onTheAuthorsObjectPage.iSeeThisPage(); + }); - }); + opaTest('Teardown', function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } - opaTest("Teardown", function (Given, When, Then) { - // Cleanup - Given.iTearDownMyApp(); - }); - } - - runner.run([journey]); -}); \ No newline at end of file + runner.run([journey]); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html index 5f0ae9d9..c7f7083c 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.html @@ -1,27 +1,27 @@ - + - + Integration tests - + - + data-sap-ui-animation="false" + data-sap-ui-compatVersion="edge" + data-sap-ui-async="true" + > - + - - - + +
- + diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js index 7e836005..82796aff 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js +++ b/tests/sample/status-management/app/authors/webapp/test/integration/opaTests.qunit.js @@ -1,29 +1,31 @@ sap.ui.loader.config({ - shim: { - "sap/ui/qunit/qunit-junit": { - deps: ["sap/ui/thirdparty/qunit-2"] - }, - "sap/ui/qunit/qunit-coverage": { - deps: ["sap/ui/thirdparty/qunit-2"] - }, - "sap/ui/thirdparty/sinon-qunit": { - deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon"] - }, - "sap/ui/qunit/sinon-qunit-bridge": { - deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon-4"] - } - } + shim: { + 'sap/ui/qunit/qunit-junit': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/qunit/qunit-coverage': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/thirdparty/sinon-qunit': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon'], + }, + 'sap/ui/qunit/sinon-qunit-bridge': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon-4'], + }, + }, }); window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); sap.ui.require( [ - "sap/ui/thirdparty/qunit-2", - "sap/ui/qunit/qunit-junit", - "sap/ui/qunit/qunit-coverage", - 'authors/test/integration/FirstJourney' - ], function (QUnit) { - "use strict"; + 'sap/ui/thirdparty/qunit-2', + 'sap/ui/qunit/qunit-junit', + 'sap/ui/qunit/qunit-coverage', + 'authors/test/integration/FirstJourney', + ], + function (QUnit) { + 'use strict'; QUnit.start(); -}); + }, +); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js index 57341622..303d1799 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsList.js @@ -1,17 +1,17 @@ -sap.ui.define(['sap/fe/test/ListReport'], function(ListReport) { - 'use strict'; +sap.ui.define(['sap/fe/test/ListReport'], function (ListReport) { + 'use strict'; - var CustomPageDefinitions = { - actions: {}, - assertions: {} - }; + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; - return new ListReport( - { - appId: 'authors', - componentId: 'AuthorsList', - contextPath: '/Authors' - }, - CustomPageDefinitions - ); -}); \ No newline at end of file + return new ListReport( + { + appId: 'authors', + componentId: 'AuthorsList', + contextPath: '/Authors', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js index f95d2597..335fe077 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/AuthorsObjectPage.js @@ -1,17 +1,17 @@ -sap.ui.define(['sap/fe/test/ObjectPage'], function(ObjectPage) { - 'use strict'; +sap.ui.define(['sap/fe/test/ObjectPage'], function (ObjectPage) { + 'use strict'; - var CustomPageDefinitions = { - actions: {}, - assertions: {} - }; + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; - return new ObjectPage( - { - appId: 'authors', - componentId: 'AuthorsObjectPage', - contextPath: '/Authors' - }, - CustomPageDefinitions - ); -}); \ No newline at end of file + return new ObjectPage( + { + appId: 'authors', + componentId: 'AuthorsObjectPage', + contextPath: '/Authors', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js index f2d4f765..27e50b33 100644 --- a/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js +++ b/tests/sample/status-management/app/authors/webapp/test/integration/pages/JourneyRunner.js @@ -1,19 +1,21 @@ -sap.ui.define([ - "sap/fe/test/JourneyRunner", - "authors/test/integration/pages/AuthorsList", - "authors/test/integration/pages/AuthorsObjectPage" -], function (JourneyRunner, AuthorsList, AuthorsObjectPage) { +sap.ui.define( + [ + 'sap/fe/test/JourneyRunner', + 'authors/test/integration/pages/AuthorsList', + 'authors/test/integration/pages/AuthorsObjectPage', + ], + function (JourneyRunner, AuthorsList, AuthorsObjectPage) { 'use strict'; var runner = new JourneyRunner({ - launchUrl: sap.ui.require.toUrl('authors') + '/test/flp.html#app-preview', - pages: { - onTheAuthorsList: AuthorsList, - onTheAuthorsObjectPage: AuthorsObjectPage - }, - async: true + launchUrl: sap.ui.require.toUrl('authors') + '/test/flp.html#app-preview', + pages: { + onTheAuthorsList: AuthorsList, + onTheAuthorsObjectPage: AuthorsObjectPage, + }, + async: true, }); return runner; -}); - + }, +); diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html index 1fea4a26..edfb63ee 100644 --- a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.html @@ -1,4 +1,4 @@ - + QUnit test suite @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js index a37a5c97..416193df 100644 --- a/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js +++ b/tests/sample/status-management/app/authors/webapp/test/testsuite.qunit.js @@ -1,11 +1,10 @@ -window.suite = function() { - 'use strict'; +window.suite = function () { + 'use strict'; - // eslint-disable-next-line - var oSuite = new parent.jsUnitTestSuite(), - + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); - oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); - return oSuite; -}; \ No newline at end of file + return oSuite; +}; diff --git a/tests/sample/status-management/app/books/README.md b/tests/sample/status-management/app/books/README.md index 8f501678..9df4b765 100644 --- a/tests/sample/status-management/app/books/README.md +++ b/tests/sample/status-management/app/books/README.md @@ -1,23 +1,24 @@ ## Application Details -| | -| ------------- | -|**Generation Date and Time**
Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time)| -|**App Generator**
SAP Fiori Application Generator| -|**App Generator Version**
1.20.0| -|**Generation Platform**
Visual Studio Code| -|**Template Used**
List Report Page V4| -|**Service Type**
Local CAP| -|**Service URL**
http://localhost:4004/admin/| -|**Module Name**
books| -|**Application Title**
Manage Books| -|**Namespace**
| -|**UI5 Theme**
sap_horizon| -|**UI5 Version**
1.146.0| -|**Enable Code Assist Libraries**
False| -|**Enable TypeScript**
False| -|**Add Eslint configuration**
False| -|**Main Entity**
Books| -|**Navigation Entity**
None| + +| | +| -------------------------------------------------------------------------------------------------- | +| **Generation Date and Time**
Tue Mar 24 2026 16:28:38 GMT+0100 (Central European Standard Time) | +| **App Generator**
SAP Fiori Application Generator | +| **App Generator Version**
1.20.0 | +| **Generation Platform**
Visual Studio Code | +| **Template Used**
List Report Page V4 | +| **Service Type**
Local CAP | +| **Service URL**
http://localhost:4004/admin/ | +| **Module Name**
books | +| **Application Title**
Manage Books | +| **Namespace**
| +| **UI5 Theme**
sap_horizon | +| **UI5 Version**
1.146.0 | +| **Enable Code Assist Libraries**
False | +| **Enable TypeScript**
False | +| **Add Eslint configuration**
False | +| **Main Entity**
Books | +| **Navigation Entity**
None | ## books @@ -25,12 +26,10 @@ An SAP Fiori application. ### Starting the generated app -- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. To launch the generated app, start your CAP project: and navigate to the following location in your browser: http://localhost:4004/books/webapp/index.html #### Pre-requisites: -1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) - - +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) diff --git a/tests/sample/status-management/app/books/ui5.yaml b/tests/sample/status-management/app/books/ui5.yaml index 2cdfdc2e..5481e8ff 100644 --- a/tests/sample/status-management/app/books/ui5.yaml +++ b/tests/sample/status-management/app/books/ui5.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json -specVersion: "4.0" +specVersion: '4.0' metadata: name: books type: application diff --git a/tests/sample/status-management/app/books/webapp/Component.js b/tests/sample/status-management/app/books/webapp/Component.js index a4e0d2e4..917c6ce3 100644 --- a/tests/sample/status-management/app/books/webapp/Component.js +++ b/tests/sample/status-management/app/books/webapp/Component.js @@ -1,12 +1,9 @@ -sap.ui.define( - ["sap/fe/core/AppComponent"], - function (Component) { - "use strict"; +sap.ui.define(['sap/fe/core/AppComponent'], function (Component) { + 'use strict'; - return Component.extend("books.Component", { - metadata: { - manifest: "json" - } - }); - } -); \ No newline at end of file + return Component.extend('books.Component', { + metadata: { + manifest: 'json', + }, + }); +}); diff --git a/tests/sample/status-management/app/books/webapp/index.html b/tests/sample/status-management/app/books/webapp/index.html index 305d9770..6f14444b 100644 --- a/tests/sample/status-management/app/books/webapp/index.html +++ b/tests/sample/status-management/app/books/webapp/index.html @@ -1,35 +1,39 @@ - + - - - - + + + + Manage Books - - + +
- - \ No newline at end of file + + diff --git a/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js index c723ae76..57b99d44 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js +++ b/tests/sample/status-management/app/books/webapp/test/integration/FirstJourney.js @@ -1,37 +1,31 @@ -sap.ui.define([ - "sap/ui/test/opaQunit", - "./pages/JourneyRunner" -], function (opaTest, runner) { - "use strict"; +sap.ui.define(['sap/ui/test/opaQunit', './pages/JourneyRunner'], function (opaTest, runner) { + 'use strict'; - function journey() { - QUnit.module("First journey"); + function journey() { + QUnit.module('First journey'); - opaTest("Start application", function (Given, When, Then) { - Given.iStartMyApp(); + opaTest('Start application', function (Given, When, Then) { + Given.iStartMyApp(); - Then.onTheBooksList.iSeeThisPage(); + Then.onTheBooksList.iSeeThisPage(); + }); - }); + opaTest('Navigate to ObjectPage', function (Given, When, Then) { + // Note: this test will fail if the ListReport page doesn't show any data + When.onTheBooksList.onFilterBar().iExecuteSearch(); - opaTest("Navigate to ObjectPage", function (Given, When, Then) { - // Note: this test will fail if the ListReport page doesn't show any data - - When.onTheBooksList.onFilterBar().iExecuteSearch(); - - Then.onTheBooksList.onTable().iCheckRows(); + Then.onTheBooksList.onTable().iCheckRows(); - When.onTheBooksList.onTable().iPressRow(0); - Then.onTheBooksObjectPage.iSeeThisPage(); + When.onTheBooksList.onTable().iPressRow(0); + Then.onTheBooksObjectPage.iSeeThisPage(); + }); - }); + opaTest('Teardown', function (Given, When, Then) { + // Cleanup + Given.iTearDownMyApp(); + }); + } - opaTest("Teardown", function (Given, When, Then) { - // Cleanup - Given.iTearDownMyApp(); - }); - } - - runner.run([journey]); -}); \ No newline at end of file + runner.run([journey]); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html index 4ebafbc9..a8f9cd90 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.html @@ -1,27 +1,27 @@ - + - + Integration tests - + - + data-sap-ui-animation="false" + data-sap-ui-compatVersion="edge" + data-sap-ui-async="true" + > - + - - - + +
- + diff --git a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js index 984ce862..675fb9fa 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js +++ b/tests/sample/status-management/app/books/webapp/test/integration/opaTests.qunit.js @@ -1,29 +1,31 @@ sap.ui.loader.config({ - shim: { - "sap/ui/qunit/qunit-junit": { - deps: ["sap/ui/thirdparty/qunit-2"] - }, - "sap/ui/qunit/qunit-coverage": { - deps: ["sap/ui/thirdparty/qunit-2"] - }, - "sap/ui/thirdparty/sinon-qunit": { - deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon"] - }, - "sap/ui/qunit/sinon-qunit-bridge": { - deps: ["sap/ui/thirdparty/qunit-2", "sap/ui/thirdparty/sinon-4"] - } - } + shim: { + 'sap/ui/qunit/qunit-junit': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/qunit/qunit-coverage': { + deps: ['sap/ui/thirdparty/qunit-2'], + }, + 'sap/ui/thirdparty/sinon-qunit': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon'], + }, + 'sap/ui/qunit/sinon-qunit-bridge': { + deps: ['sap/ui/thirdparty/qunit-2', 'sap/ui/thirdparty/sinon-4'], + }, + }, }); window.QUnit = Object.assign({}, window.QUnit, { config: { autostart: false } }); sap.ui.require( [ - "sap/ui/thirdparty/qunit-2", - "sap/ui/qunit/qunit-junit", - "sap/ui/qunit/qunit-coverage", - 'books/test/integration/FirstJourney' - ], function (QUnit) { - "use strict"; + 'sap/ui/thirdparty/qunit-2', + 'sap/ui/qunit/qunit-junit', + 'sap/ui/qunit/qunit-coverage', + 'books/test/integration/FirstJourney', + ], + function (QUnit) { + 'use strict'; QUnit.start(); -}); + }, +); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js index 6ee5be21..d9f9c7c7 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksList.js @@ -1,17 +1,17 @@ -sap.ui.define(['sap/fe/test/ListReport'], function(ListReport) { - 'use strict'; +sap.ui.define(['sap/fe/test/ListReport'], function (ListReport) { + 'use strict'; - var CustomPageDefinitions = { - actions: {}, - assertions: {} - }; + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; - return new ListReport( - { - appId: 'books', - componentId: 'BooksList', - contextPath: '/Books' - }, - CustomPageDefinitions - ); -}); \ No newline at end of file + return new ListReport( + { + appId: 'books', + componentId: 'BooksList', + contextPath: '/Books', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js index 4f18b75d..eee04536 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/BooksObjectPage.js @@ -1,17 +1,17 @@ -sap.ui.define(['sap/fe/test/ObjectPage'], function(ObjectPage) { - 'use strict'; +sap.ui.define(['sap/fe/test/ObjectPage'], function (ObjectPage) { + 'use strict'; - var CustomPageDefinitions = { - actions: {}, - assertions: {} - }; + var CustomPageDefinitions = { + actions: {}, + assertions: {}, + }; - return new ObjectPage( - { - appId: 'books', - componentId: 'BooksObjectPage', - contextPath: '/Books' - }, - CustomPageDefinitions - ); -}); \ No newline at end of file + return new ObjectPage( + { + appId: 'books', + componentId: 'BooksObjectPage', + contextPath: '/Books', + }, + CustomPageDefinitions, + ); +}); diff --git a/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js index a58c0f83..8ff464df 100644 --- a/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js +++ b/tests/sample/status-management/app/books/webapp/test/integration/pages/JourneyRunner.js @@ -1,19 +1,21 @@ -sap.ui.define([ - "sap/fe/test/JourneyRunner", - "books/test/integration/pages/BooksList", - "books/test/integration/pages/BooksObjectPage" -], function (JourneyRunner, BooksList, BooksObjectPage) { +sap.ui.define( + [ + 'sap/fe/test/JourneyRunner', + 'books/test/integration/pages/BooksList', + 'books/test/integration/pages/BooksObjectPage', + ], + function (JourneyRunner, BooksList, BooksObjectPage) { 'use strict'; var runner = new JourneyRunner({ - launchUrl: sap.ui.require.toUrl('books') + '/test/flp.html#app-preview', - pages: { - onTheBooksList: BooksList, - onTheBooksObjectPage: BooksObjectPage - }, - async: true + launchUrl: sap.ui.require.toUrl('books') + '/test/flp.html#app-preview', + pages: { + onTheBooksList: BooksList, + onTheBooksObjectPage: BooksObjectPage, + }, + async: true, }); return runner; -}); - + }, +); diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html index 1fea4a26..edfb63ee 100644 --- a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.html @@ -1,4 +1,4 @@ - + QUnit test suite @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js index a37a5c97..416193df 100644 --- a/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js +++ b/tests/sample/status-management/app/books/webapp/test/testsuite.qunit.js @@ -1,11 +1,10 @@ -window.suite = function() { - 'use strict'; +window.suite = function () { + 'use strict'; - // eslint-disable-next-line - var oSuite = new parent.jsUnitTestSuite(), - + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1); - oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); + oSuite.addTestPage(sContextPath + 'integration/opaTests.qunit.html'); - return oSuite; -}; \ No newline at end of file + return oSuite; +}; diff --git a/tests/sample/status-management/app/fiori.html b/tests/sample/status-management/app/fiori.html index 7c1fb0bd..c68aa6f3 100644 --- a/tests/sample/status-management/app/fiori.html +++ b/tests/sample/status-management/app/fiori.html @@ -1,55 +1,55 @@ - + - + Bookshop Launchpad - - + + diff --git a/tests/sample/status-management/db/init.js b/tests/sample/status-management/db/init.js index 77a50c0f..fe3fdc82 100644 --- a/tests/sample/status-management/db/init.js +++ b/tests/sample/status-management/db/init.js @@ -1,4 +1,4 @@ -const cds = require('@sap/cds') +const cds = require('@sap/cds'); /** * In order to keep basic bookshop sample as simple as possible, we don't add @@ -8,14 +8,14 @@ const cds = require('@sap/cds') // NOTE: We use cds.on('served') to delay the UPSERTs after the db init // to run after all INSERTs from .csv files happened. -module.exports = cds.on('served', ()=> - UPSERT.into ('sap.common.Currencies') .columns ( - [ 'code', 'symbol', 'name' ] - ) .rows ( - [ 'EUR', '€', 'Euro' ], - [ 'USD', '$', 'US Dollar' ], - [ 'GBP', '£', 'British Pound' ], - [ 'ILS', '₪', 'Shekel' ], - [ 'JPY', '¥', 'Yen' ], - ) -) +module.exports = cds.on('served', () => + UPSERT.into('sap.common.Currencies') + .columns(['code', 'symbol', 'name']) + .rows( + ['EUR', '€', 'Euro'], + ['USD', '$', 'US Dollar'], + ['GBP', '£', 'British Pound'], + ['ILS', '₪', 'Shekel'], + ['JPY', '¥', 'Yen'], + ), +); diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json index 49ef22de..740e57cc 100644 --- a/tests/sample/status-management/package.json +++ b/tests/sample/status-management/package.json @@ -46,4 +46,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/sample/status-management/srv/cat-service.js b/tests/sample/status-management/srv/cat-service.js index 1598f053..a8c31192 100644 --- a/tests/sample/status-management/srv/cat-service.js +++ b/tests/sample/status-management/srv/cat-service.js @@ -1,29 +1,29 @@ -const cds = require('@sap/cds') +const cds = require('@sap/cds'); -class CatalogService extends cds.ApplicationService { init() { +class CatalogService extends cds.ApplicationService { + init() { + const { Books } = cds.entities('sap.capire.bookshop'); + const { ListOfBooks } = this.entities; - const { Books } = cds.entities ('sap.capire.bookshop') - const { ListOfBooks } = this.entities + // Add some discount for overstocked books + this.after('each', ListOfBooks, (book) => { + if (book.stock > 111) book.title += ` -- 11% discount!`; + }); - // Add some discount for overstocked books - this.after('each', ListOfBooks, book => { - if (book.stock > 111) book.title += ` -- 11% discount!` - }) + // Reduce stock of ordered books if available stock suffices + this.on('submitOrder', async (req) => { + let { book: id, quantity } = req.data; + if (quantity < 1) return req.error(400, `quantity has to be 1 or more`); + let succeeded = await UPDATE(Books, id).with`stock = stock - ${quantity}` + .where`stock >= ${quantity}`; + if (succeeded) return; + else if (!this.exists(Books, id)) req.error(404, `Book #${id} doesn't exist`); + else req.error(409, `${quantity} exceeds stock for book #${id}`); + }); - // Reduce stock of ordered books if available stock suffices - this.on ('submitOrder', async req => { - let { book:id, quantity } = req.data - if (quantity < 1) return req.error (400, `quantity has to be 1 or more`) - let succeeded = await UPDATE (Books,id) - .with `stock = stock - ${quantity}` - .where `stock >= ${quantity}` - if (succeeded) return - else if (!this.exists(Books,id)) req.error (404, `Book #${id} doesn't exist`) - else req.error (409, `${quantity} exceeds stock for book #${id}`) - }) + // Delegate requests to the underlying generic service + return super.init(); + } +} - // Delegate requests to the underlying generic service - return super.init() -}} - -module.exports = CatalogService +module.exports = CatalogService; diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json index 897b70d8..2ca36be2 100644 --- a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.authorVerificationProcess.json @@ -54,9 +54,7 @@ "description": "" } }, - "required": [ - "authorname" - ] + "required": ["authorname"] }, "outputs": { "title": "outputs", @@ -96,10 +94,7 @@ "description": "" } }, - "required": [ - "isverified", - "verificationstatus" - ] + "required": ["isverified", "verificationstatus"] }, "processAttributes": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -119,4 +114,4 @@ "valid": true, "projectId": "eu12.cdsmunich.sampleapplicationproject", "dataTypes": [] -} \ No newline at end of file +} diff --git a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json index c701a320..0a7eb2ac 100644 --- a/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json +++ b/tests/sample/status-management/workflows/eu12.cdsmunich.sampleapplicationproject.bookApprovalProcess.json @@ -100,10 +100,7 @@ "description": "" } }, - "required": [ - "isapproved", - "finalstatus" - ] + "required": ["isapproved", "finalstatus"] }, "processAttributes": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -159,4 +156,4 @@ "valid": true } ] -} \ No newline at end of file +} From be79ba1d621a724d89fc1685363677cb34a2999d Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 15:15:28 +0100 Subject: [PATCH 15/24] fix dependency --- package.json | 3 ++- tests/sample/status-management/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dee5a4a9..5f0d7639 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "#cds-models/*": "./@cds-models/*/index.js" }, "workspaces": [ - "tests/bookshop" + "tests/bookshop", + "tests/samples/status-management" ], "dependencies": { "@sap-cloud-sdk/connectivity": "^4.5.1", diff --git a/tests/sample/status-management/package.json b/tests/sample/status-management/package.json index 740e57cc..ee4c67d1 100644 --- a/tests/sample/status-management/package.json +++ b/tests/sample/status-management/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@sap/cds": "^9.6.2", - "@cap-js/process": "file:../../../cap-js-process-1.0.0.tgz", + "@cap-js/process": "file:../../../", "@sap-cloud-sdk/connectivity": "^4", "@sap-cloud-sdk/http-client": "^4", "@sap-cloud-sdk/resilience": "^4" From dcd0afcf28e1af3446903569a83cbfdf3aa886ae Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 15:15:34 +0100 Subject: [PATCH 16/24] readme --- tests/sample/status-management/readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md index dc93cc95..672a1ce0 100644 --- a/tests/sample/status-management/readme.md +++ b/tests/sample/status-management/readme.md @@ -1,3 +1,5 @@ +// TODO: update, reference in main readme; screenshots of process; update usage local + # Status Management Bookshop A CAP sample application demonstrating how to integrate SAP Build Process Automation (SBPA) workflows into a bookshop scenario using the `@cap-js/process` plugin. The project showcases two distinct integration patterns -- **declarative** and **programmatic** -- for managing process lifecycles. From ed684b63dfae040e2b7e9836aab6306712ec4f35 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:15:50 +0100 Subject: [PATCH 17/24] fix readme --- tests/sample/status-management/readme.md | 124 +++++++++++++++++++---- 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md index 672a1ce0..2a56399f 100644 --- a/tests/sample/status-management/readme.md +++ b/tests/sample/status-management/readme.md @@ -1,5 +1,3 @@ -// TODO: update, reference in main readme; screenshots of process; update usage local - # Status Management Bookshop A CAP sample application demonstrating how to integrate SAP Build Process Automation (SBPA) workflows into a bookshop scenario using the `@cap-js/process` plugin. The project showcases two distinct integration patterns -- **declarative** and **programmatic** -- for managing process lifecycles. @@ -17,28 +15,118 @@ A third read-only service (`CatalogService` at `/browse`) exposes books for publ Both apps are accessible from a shared **Fiori Launchpad sandbox** at `/fiori.html`. +## Processes + +
+ +Book approval process + + +``` + ┌────────────────────────────────────────┐ + │ Process Start │ + └───────────────────┬────────────────────┘ + │ + ┌───────────────────▼────────────────────┐ + │ Set status to 'Manager Approval │ + │ Pending' │ + └───────────────────┬────────────────────┘ + │ + ┌───────────────────▼────────────────────┐ + │ Manager Approval │ + └──────────────┬──────────────┬──────────┘ + │ │ + [Approve] [Reject] + │ │ + ┌──────────────▼────────┐ ┌──▼──────────────────────────┐ + │ status = 'Manager │ │ status = 'Manager rejected │ + │ Approved, Author │ │ request' │ + │ Approval Pending' │ │ isApproved = false │ + └──────────────┬────────┘ └──┬──────────────────────────┘ + │ │ + ┌──────────────▼────────┐ │ + │ Author Approval │ │ + └──────┬───────────┬────┘ │ + │ │ │ + [Approve] [Reject] │ + │ │ │ + ┌─────────────────▼───┐ ┌────▼──────────────────────────┐ + │ status = 'Manager │ │ status = 'Manager approved, │ + │ and Author │ │ but Author rejected request' │ + │ Approved' │ │ isApproved = false │ + │ isApproved = true │ └────┬──────────────────────────┘ + └─────────────────┬───┘ │ │ + └─────┬─────┘ │ + │ │ + └────────┬───────┘ + │ + ┌──────────▼──────────┐ + │ End │ + └─────────────────────┘ +``` + +
+ +
+ + +Author verification process + + +``` + ┌─────────────────────────────────────────┐ + │ Process Start │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ status = "Verification Pending" │ + └──────────────────┬──────────────────────┘ + │ + ┌──────────────────▼──────────────────────┐ + │ Author Verification Approval │ + └──────────┬──────────────────────┬───────┘ + │ │ + [Verify] [Reject] + │ │ + ┌───────────────▼──────────┐ ┌────────▼────────────────────┐ + │ status = "Author │ │ status = "Author was not │ + │ verified" │ │ verified" │ + │ isVerified = true │ │ isVerified = false │ + └───────────────┬──────────┘ └────────┬────────────────────┘ + │ │ + └──────────┬───────────┘ + │ + ┌─────────────────────▼─────────────────────┐ + │ End │ + └───────────────────────────────────────────┘ +``` + +
+ ## Project Structure ``` + db/ - schema.cds # Domain model: Books, Authors, Genres - data/ # CSV seed data +schema.cds # Domain model: Books, Authors, Genres +data/ # CSV seed data srv/ - books-service.cds # BooksService definition - books-service.js # Books handler: approval status enrichment - books-constraints.cds # Input validation for Books and Genres - authors-service.cds # AuthorsService definition - authors-service.js # Authors handler: verification lifecycle + status enrichment - authors-constraints.cds # Input validation for Authors - admin-process.cds # Declarative BPM annotations for Books - cat-service.cds # CatalogService (read-only browse) - cat-service.js # CatalogService handler - external/ # Generated process service definitions (do not edit) +books-service.cds # BooksService definition +books-service.js # Books handler: approval status enrichment +books-constraints.cds # Input validation for Books and Genres +authors-service.cds # AuthorsService definition +authors-service.js # Authors handler: verification lifecycle + status enrichment +authors-constraints.cds # Input validation for Authors +admin-process.cds # Declarative BPM annotations for Books +cat-service.cds # CatalogService (read-only browse) +cat-service.js # CatalogService handler +external/ # Generated process service definitions (do not edit) app/ - fiori.html # Local Fiori Launchpad sandbox - services.cds # Imports annotations from both apps - books/ # Fiori Elements app for Manage Books - authors/ # Fiori Elements app for Manage Authors +fiori.html # Local Fiori Launchpad sandbox +services.cds # Imports annotations from both apps +books/ # Fiori Elements app for Manage Books +authors/ # Fiori Elements app for Manage Authors + ``` ## How the `@cap-js/process` Plugin Is Used From c75ff4ddcd226af34d9a239dacdc813344880394 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:22:27 +0100 Subject: [PATCH 18/24] fix --- tests/sample/status-management/app/fiori.html | 55 ------------------- tests/sample/status-management/readme.md | 4 -- 2 files changed, 59 deletions(-) delete mode 100644 tests/sample/status-management/app/fiori.html diff --git a/tests/sample/status-management/app/fiori.html b/tests/sample/status-management/app/fiori.html deleted file mode 100644 index c68aa6f3..00000000 --- a/tests/sample/status-management/app/fiori.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - Bookshop Launchpad - - - - - - - - - - - diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/readme.md index 2a56399f..0e5718b8 100644 --- a/tests/sample/status-management/readme.md +++ b/tests/sample/status-management/readme.md @@ -13,8 +13,6 @@ The application consists of two SAP Fiori Elements apps, each backed by its own A third read-only service (`CatalogService` at `/browse`) exposes books for public browsing. -Both apps are accessible from a shared **Fiori Launchpad sandbox** at `/fiori.html`. - ## Processes
@@ -122,7 +120,6 @@ cat-service.cds # CatalogService (read-only browse) cat-service.js # CatalogService handler external/ # Generated process service definitions (do not edit) app/ -fiori.html # Local Fiori Launchpad sandbox services.cds # Imports annotations from both apps books/ # Fiori Elements app for Manage Books authors/ # Fiori Elements app for Manage Authors @@ -225,7 +222,6 @@ cds watch Open http://localhost:4004 in your browser. From there: -- **Fiori Launchpad**: http://localhost:4004/fiori.html (both apps as tiles) - **Manage Books**: http://localhost:4004/books/index.html - **Manage Authors**: http://localhost:4004/authors/index.html From 942e6628f6c0600c8ab512178413abfa517f9114 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:28:52 +0100 Subject: [PATCH 19/24] Rename readme.md to README.md --- tests/sample/status-management/{readme.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/sample/status-management/{readme.md => README.md} (100%) diff --git a/tests/sample/status-management/readme.md b/tests/sample/status-management/README.md similarity index 100% rename from tests/sample/status-management/readme.md rename to tests/sample/status-management/README.md From bbd47dae486b89084de4be777ad40bc69a808032 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:41:51 +0100 Subject: [PATCH 20/24] fix --- package.json | 2 +- tests/sample/status-management/README.md | 24 +++++++++++-------- tests/sample/status-management/index.cds | 3 --- .../status-management/srv/authors-service.js | 4 ++-- .../status-management/srv/books-service.js | 2 +- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 5f0d7639..2ea74703 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "workspaces": [ "tests/bookshop", - "tests/samples/status-management" + "tests/sample/status-management" ], "dependencies": { "@sap-cloud-sdk/connectivity": "^4.5.1", diff --git a/tests/sample/status-management/README.md b/tests/sample/status-management/README.md index 0e5718b8..57416fe0 100644 --- a/tests/sample/status-management/README.md +++ b/tests/sample/status-management/README.md @@ -115,7 +115,7 @@ books-constraints.cds # Input validation for Books and Genres authors-service.cds # AuthorsService definition authors-service.js # Authors handler: verification lifecycle + status enrichment authors-constraints.cds # Input validation for Authors -admin-process.cds # Declarative BPM annotations for Books +books-process.cds # Declarative BPM annotations for Books cat-service.cds # CatalogService (read-only browse) cat-service.js # CatalogService handler external/ # Generated process service definitions (do not edit) @@ -136,11 +136,12 @@ The book approval process is managed entirely through CDS annotations in `srv/bo ```cds annotate BooksService.Books with @( - bpm.process.businessKey: (title), + bpm.process.businessKey: (ID), bpm.process.start : { id: 'eu12...bookApprovalProcess', on: 'CREATE', inputs: [ + { path: $self.ID, as: 'entityid' }, { path: $self.title, as: 'booktitle' }, { path: $self.descr, as: 'description' }, $self.author.name, @@ -156,7 +157,7 @@ annotate BooksService.Books with @( ); ``` -- **`@bpm.process.businessKey`** -- Correlates process instances back to entities using the book title. +- **`@bpm.process.businessKey`** -- Correlates process instances back to entities using the book's ID. - **`@bpm.process.start`** -- Automatically starts the approval process on `CREATE` when the price exceeds 50. Entity fields are mapped to process inputs, with support for renaming (`as`) and navigation paths (`$self.author.name`). - **`@bpm.process.cancel`** -- Automatically cancels the running process on `UPDATE` when the price drops to 50 or below. @@ -165,10 +166,12 @@ annotate BooksService.Books with @( The author verification process is managed entirely in JavaScript (`srv/authors-service.js`), giving full control over the lifecycle: ```js +const authorProcess = await cds.connect.to(AUTHOR_PROCESS); + // Start verification on author creation -this.after('CREATE', 'Authors', async (author, req) => { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - await verificationService.start({ +this.after('CREATE', Authors, async (author, req) => { + await authorProcess.start({ + entityid: author.ID, authorname: author.name, dateofbirth: author.dateOfBirth ?? '', placeofbirth: author.placeOfBirth ?? '', @@ -176,11 +179,12 @@ this.after('CREATE', 'Authors', async (author, req) => { }); // Cancel verification on author deletion -this.after('DELETE', 'Authors', async (author, req) => { - const verificationService = await cds.connect.to(AUTHOR_PROCESS); - const instances = await verificationService.getInstancesByBusinessKey(author.name, ['RUNNING']); +this.after('DELETE', Authors, async (author, req) => { + if (!author.ID) return; + + const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); if (instances.length > 0) { - await verificationService.cancel({ businessKey: author.name, cascade: true }); + await authorProcess.cancel({ businessKey: author.ID, cascade: true }); } }); ``` diff --git a/tests/sample/status-management/index.cds b/tests/sample/status-management/index.cds index 383b0034..f229305a 100644 --- a/tests/sample/status-management/index.cds +++ b/tests/sample/status-management/index.cds @@ -1,7 +1,4 @@ -// This file allows other projects to import the bookshop model by -// using {...} from '@capire/bookshop'; - namespace sap.capire.statusmanagement; //> important for reflection using from './db/schema'; using from './srv/cat-service'; diff --git a/tests/sample/status-management/srv/authors-service.js b/tests/sample/status-management/srv/authors-service.js index c0bd322a..d765b946 100644 --- a/tests/sample/status-management/srv/authors-service.js +++ b/tests/sample/status-management/srv/authors-service.js @@ -29,7 +29,7 @@ module.exports = class AuthorsService extends cds.ApplicationService { // Cancel verification process when an unverified author is deleted this.after('DELETE', Authors, async (author, req) => { - if (!author.name) return; + if (!author.ID) return; const instances = await authorProcess.getInstancesByBusinessKey(author.ID, ['RUNNING']); if (instances.length > 0) { @@ -41,7 +41,7 @@ module.exports = class AuthorsService extends cds.ApplicationService { this.after('READ', Authors, async (results, _req) => { await Promise.all( results.map(async (author) => { - if (!author.name) { + if (!author.ID) { author.verificationStatus = VERIFICATION_PENDING; author.isVerified = false; author.verificationCriticality = 2; // Warning (yellow) diff --git a/tests/sample/status-management/srv/books-service.js b/tests/sample/status-management/srv/books-service.js index df1b5a66..4f6dd9f2 100644 --- a/tests/sample/status-management/srv/books-service.js +++ b/tests/sample/status-management/srv/books-service.js @@ -18,7 +18,7 @@ module.exports = class BooksService extends cds.ApplicationService { results.map(async (book) => { const bookID = book.ID; if (!bookID) { - // Draft entries without a title yet + // Draft entries book.processStatus = NO_APPROVAL_REQUIRED; book.isApproved = true; book.processCriticality = 0; // Neutral From ea4ba421a690fa6f5bd840f5de260c446521822f Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:44:23 +0100 Subject: [PATCH 21/24] fix --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 37b095d6..489260d9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![REUSE status](https://api.reuse.software/badge/github.com/cap-js/process)](https://api.reuse.software/info/github.com/cap-js/process) +CAP Plugin to interact with SAP Build Process Automation to manage processes. + ## Table of Contents - [Setup](#setup) From 3f1c342c8b538f69b1e86a6d7cbd89e4af936c94 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:48:44 +0100 Subject: [PATCH 22/24] update --- package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2ea74703..c9c2d65f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@cap-js/process", "version": "0.1.0", - "description": "", + "description": "CAP Plugin to interact with SAP Build Process Automation to manage processes.", "main": "cds-plugin.js", "files": [ "cds-plugin.js", @@ -36,8 +36,10 @@ "import:process:importSimple": "cd tests/bookshop && cds import --from process --name eu12.cdsmunich.capprocesspluginhybridtest.importProcess_Simple_Inputs --force" }, "keywords": [], - "author": "", - "license": "ISC", + "author": "SAP SE (https://www.sap.com)", + "repository": "cap-js/process", + "homepage": "https://cap.cloud.sap", + "license": "Apache-2.0", "type": "commonjs", "peerDependencies": { "@sap/cds": ">=9" From 521839c9656c22d6e112e1111fe2c1ec27b4e1f1 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:56:12 +0100 Subject: [PATCH 23/24] changelog --- CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bccb93f..9f1d7934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,8 @@ - The format is based on [Keep a Changelog](https://keepachangelog.com/). - This project adheres to [Semantic Versioning](https://semver.org/). -## Version 1.0.0 - TBD +## Version 0.1.0 - 2026-03-27 ### Added -### Changed - -### Fixed - -### Removed +- Initial release From d834e0c5ae7eac855cdd2f6dcc63cd04f9866f72 Mon Sep 17 00:00:00 2001 From: Til Weber Date: Thu, 26 Mar 2026 16:57:19 +0100 Subject: [PATCH 24/24] changelog v2 --- .github/workflows/check-changelog.yml | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml index 0373022d..335894f1 100644 --- a/.github/workflows/check-changelog.yml +++ b/.github/workflows/check-changelog.yml @@ -1,14 +1,14 @@ -# name: Check Changelog -# on: -# pull_request: -# types: [assigned, opened, synchronize, reopened, labeled, unlabeled] -# branches: -# - main -# jobs: -# Check-Changelog: -# name: Check Changelog Action -# runs-on: ubuntu-latest -# steps: -# - uses: tarides/changelog-check-action@v3 -# with: -# changelog: CHANGELOG.md +name: Check Changelog +on: + pull_request: + types: [assigned, opened, synchronize, reopened, labeled, unlabeled] + branches: + - main +jobs: + Check-Changelog: + name: Check Changelog Action + runs-on: ubuntu-latest + steps: + - uses: tarides/changelog-check-action@v3 + with: + changelog: CHANGELOG.md