From 788b0aa426f553d29f4003659c2c13607515013b Mon Sep 17 00:00:00 2001 From: D070615 Date: Tue, 25 Nov 2025 17:30:32 +0100 Subject: [PATCH 1/7] tree table modifications --- app/appconfig/fioriSandboxConfig.json | 22 +++++++++++----------- app/genres/fiori-service.cds | 5 ++++- app/genres/webapp/i18n/i18n.properties | 2 +- app/genres/webapp/i18n/i18n_de.properties | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/appconfig/fioriSandboxConfig.json b/app/appconfig/fioriSandboxConfig.json index aae8e77..dedcb15 100644 --- a/app/appconfig/fioriSandboxConfig.json +++ b/app/appconfig/fioriSandboxConfig.json @@ -18,14 +18,6 @@ "title": "Browse Books", "targetURL": "#Books-display" } - }, - { - "id": "BrowseGenres", - "tileType": "sap.ushell.ui.tile.StaticTile", - "properties": { - "title": "Browse Genres", - "targetURL": "#Genres-display" - } } ] }, @@ -52,6 +44,14 @@ "targetURL": "#Authors-manage" } }, + { + "id": "ManageGenres", + "tileType": "sap.ushell.ui.tile.StaticTile", + "properties": { + "title": "Manage Genres", + "targetURL": "#Genres-manage" + } + }, { "id": "ManageOrders", "tileType": "sap.ushell.ui.tile.StaticTile", @@ -114,10 +114,10 @@ "url": "/admin-authors/webapp" } }, - "BrowseGenres": { + "ManageGenres": { "semanticObject": "Genres", - "action": "display", - "title": "Browse Genres", + "action": "manage", + "title": "Manage Genres", "signature": { "parameters": { "Genre.ID": { diff --git a/app/genres/fiori-service.cds b/app/genres/fiori-service.cds index 88ff918..ce75d02 100644 --- a/app/genres/fiori-service.cds +++ b/app/genres/fiori-service.cds @@ -1,7 +1,10 @@ using { sap.capire.bookshop.Genres } from '@capire/bookshop'; +annotate AdminService.Genres with @odata.draft.enabled; +// needed for changing a parent +annotate AdminService.Genres with @odata.draft.bypass; + annotate Genres with @cds.search: {name}; -annotate Genres with @readonly; annotate Genres with { name @title: '{i18n>Genre}'; } diff --git a/app/genres/webapp/i18n/i18n.properties b/app/genres/webapp/i18n/i18n.properties index 42da067..ebbb467 100644 --- a/app/genres/webapp/i18n/i18n.properties +++ b/app/genres/webapp/i18n/i18n.properties @@ -1,4 +1,4 @@ #XTIT -appTitle=Browse Genres +appTitle=Manage Genres #XTXT appDescription=Genres as Tree View diff --git a/app/genres/webapp/i18n/i18n_de.properties b/app/genres/webapp/i18n/i18n_de.properties index e8714e9..9d0cacf 100644 --- a/app/genres/webapp/i18n/i18n_de.properties +++ b/app/genres/webapp/i18n/i18n_de.properties @@ -1,2 +1,2 @@ -appTitle=Zeige Genres +appTitle=Verwalte Genres appDescription=Genres als Baumansicht From a612ca33809f12356309629e72db4d052274f364 Mon Sep 17 00:00:00 2001 From: I548646 Date: Fri, 16 Jan 2026 10:02:14 +0100 Subject: [PATCH 2/7] feat: replace bypass_draft with direct_crud --- app/genres/fiori-service.cds | 2 -- package.json | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/genres/fiori-service.cds b/app/genres/fiori-service.cds index 59b0b54..27b1f26 100644 --- a/app/genres/fiori-service.cds +++ b/app/genres/fiori-service.cds @@ -1,8 +1,6 @@ using { sap.capire.bookshop.Genres } from '@capire/bookshop'; annotate AdminService.Genres with @odata.draft.enabled; -// needed for changing a parent -annotate AdminService.Genres with @odata.draft.bypass; annotate Genres with @cds.search: {name}; annotate Genres with { diff --git a/package.json b/package.json index a9d828e..20fed6a 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,9 @@ }, "log": { "service": true + }, + "fiori": { + "direct_crud": true } } } From ed72538ed20730f6ff2258ac13398665580792df Mon Sep 17 00:00:00 2001 From: I548646 Date: Fri, 16 Jan 2026 11:11:59 +0100 Subject: [PATCH 3/7] fix: use manual approach for hierarchy definition --- app/genres/fiori-service.cds | 83 ++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/app/genres/fiori-service.cds b/app/genres/fiori-service.cds index 27b1f26..53ad3ce 100644 --- a/app/genres/fiori-service.cds +++ b/app/genres/fiori-service.cds @@ -1,32 +1,87 @@ -using { sap.capire.bookshop.Genres } from '@capire/bookshop'; +using {sap.capire.bookshop.Genres} from '@capire/bookshop'; annotate AdminService.Genres with @odata.draft.enabled; annotate Genres with @cds.search: {name}; + annotate Genres with { name @title: '{i18n>Genre}'; } // Lists annotate Genres with @( - Common.SemanticKey : [name], - UI.SelectionFields : [name], - UI.LineItem : [ - { Value: name, Label: '{i18n>Name}' }, - ], + Common.SemanticKey: [name], + UI.SelectionFields: [name], + UI.LineItem : [{ + Value: name, + Label: '{i18n>Name}' + }, ], ); // Details -annotate Genres with @(UI : { - Identification : [{ Value: name }], - HeaderInfo : { - TypeName : '{i18n>Genre}', - TypeNamePlural : '{i18n>Genres}', - Title : { Value: name }, - Description : { Value: ID } +annotate Genres with @(UI: { + Identification: [{Value: name}], + HeaderInfo : { + TypeName : '{i18n>Genre}', + TypeNamePlural: '{i18n>Genres}', + Title : {Value: name}, + Description : {Value: ID} } }); // Tree Views -annotate AdminService.Genres with @hierarchy; +// TODO: In my setup, using the @hierarchy annotation does not work as expected. +// TODO: > I am getting a UI error that reports 'NodeProperty' can't be read from undefined +// TODO: > Using the 'Manual Approach' seems to work. +// TODO: > Outdated versions? +// TODO: > +// TODO: > @sap/cds: 9.6.3 +// TODO: > @sap/cds-compiler: 6.6.0 +// TODO: > @sap/cds-dk: 9.6.1 +// TODO: > @sap/cds-dk (global): 9.6.1 +// TODO: > @sap/cds-fiori: 2.1.1 +// TODO: > @sap/cds-mtxs: 3.6.1 +// TODO: > @cap-js/asyncapi: 1.0.3 +// TODO: > @cap-js/cds-test: 0.4.1 +// TODO: > @cap-js/db-service: 2.8.1 +// TODO: > @cap-js/openapi: 1.3.1 +// TODO: > @cap-js/sqlite: 2.1.2 +// TODO: > Node.js: v22.21.1 +// annotate AdminService.Genres with @hierarchy; + +// 'Manual Approach' from capire +// declare a hierarchy with the qualifier "GenresHierarchy" +annotate AdminService.Genres with @Aggregation.RecursiveHierarchy #GenresHierarchy: { + NodeProperty : ID, // identifies a node, usually the key + ParentNavigationProperty: parent // navigates to a node's parent +}; + +extend AdminService.Genres with @( + // The computed properties expected by Fiori to be present in hierarchy entities + Hierarchy.RecursiveHierarchy #GenresHierarchy : { + LimitedDescendantCount: LimitedDescendantCount, + DistanceFromRoot : DistanceFromRoot, + DrillState : DrillState, + LimitedRank : LimitedRank + }, + // Disallow filtering on these properties from Fiori UIs + Capabilities.FilterRestrictions.NonFilterableProperties: [ + 'LimitedDescendantCount', + 'DistanceFromRoot', + 'DrillState', + 'LimitedRank' + ], + // Disallow sorting on these properties from Fiori UIs + Capabilities.SortRestrictions.NonSortableProperties : [ + 'LimitedDescendantCount', + 'DistanceFromRoot', + 'DrillState', + 'LimitedRank' + ], +) columns { // Ensure we can query these columns from the database + null as LimitedDescendantCount : Int16, + null as DistanceFromRoot : Int16, + null as DrillState : String, + null as LimitedRank : Int16 +}; From 15d30ee7e58823f1cb356a0204834a22fd270899 Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 15 Apr 2026 18:08:51 +0200 Subject: [PATCH 4/7] chore: update dependencies --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 7735fb5..7404ef8 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,19 @@ "description": "Shows how to reuse and compose with CAP.", "repository": "https://github.com/capire/bookstore", "dependencies": { - "@cap-js/hana": ">=1", - "@capire/bookshop": "*", - "@capire/common": "*", - "@capire/data-viewer": "*", - "@capire/orders": "*", - "@capire/reviews": "*", - "@sap-cloud-sdk/http-client": "^4", - "@sap-cloud-sdk/resilience": "^4", - "@sap/cds": ">=5" + "@cap-js/hana": "^2.7.0", + "@capire/bookshop": "^2.1.7", + "@capire/common": "^2.0.2", + "@capire/data-viewer": "^0.1.5", + "@capire/orders": "^2.0.1", + "@capire/reviews": "^2.0.2", + "@sap-cloud-sdk/http-client": "^4.6.0", + "@sap-cloud-sdk/resilience": "^4.6.0", + "@sap/cds": "^9.8.5" }, "devDependencies": { - "@cap-js/cds-test": ">=0.4.1", - "@cap-js/sqlite": ">=1" + "@cap-js/cds-test": "^0.4.1", + "@cap-js/sqlite": "^2.2.0" }, "scripts": { "start": "cds-serve", From 25ee6de9b18c88fb05783a15e60b090af5d0ca19 Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 15 Apr 2026 18:09:34 +0200 Subject: [PATCH 5/7] chore: remove redundant cdsrc --- .cdsrc.yaml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .cdsrc.yaml diff --git a/.cdsrc.yaml b/.cdsrc.yaml deleted file mode 100644 index 394fa0d..0000000 --- a/.cdsrc.yaml +++ /dev/null @@ -1,2 +0,0 @@ -fiori: - direct_crud: true From 8685100f90ee20fc231984781376003a764bf8be Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 15 Apr 2026 18:46:33 +0200 Subject: [PATCH 6/7] chore: use patched cds --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7404ef8..c9e7096 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@capire/reviews": "^2.0.2", "@sap-cloud-sdk/http-client": "^4.6.0", "@sap-cloud-sdk/resilience": "^4.6.0", - "@sap/cds": "^9.8.5" + "@sap/cds": "git+https://github.tools.sap/cap/cds#fix/hierarchy-direct-crud-exclusion" }, "devDependencies": { "@cap-js/cds-test": "^0.4.1", From ce0d59e0233eba34c684aa6dcda7020d65d4e418 Mon Sep 17 00:00:00 2001 From: I548646 Date: Wed, 15 Apr 2026 18:46:58 +0200 Subject: [PATCH 7/7] chore: skip fix for testing --- srv/mashup.cds | 54 ++++++++++++------------ srv/mashup.js | 110 ++++++++++++++++++++++++------------------------- 2 files changed, 82 insertions(+), 82 deletions(-) diff --git a/srv/mashup.cds b/srv/mashup.cds index 7560bc0..d81433c 100644 --- a/srv/mashup.cds +++ b/srv/mashup.cds @@ -1,34 +1,34 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Enhancing bookshop with Reviews and Orders provided through -// respective reuse packages and services -// +// //////////////////////////////////////////////////////////////////////////// +// // +// // Enhancing bookshop with Reviews and Orders provided through +// // respective reuse packages and services +// // -// -// Extend Books with access to Reviews and average ratings -// -using { ReviewsService.AverageRatings } from '@capire/reviews'; -using { sap.capire.bookshop.Books } from '@capire/bookshop'; -extend Books with { - rating : type of AverageRatings:rating; // average rating - reviews : Integer @title : '{i18n>NumberOfReviews}'; -} +// // +// // Extend Books with access to Reviews and average ratings +// // +// using { ReviewsService.AverageRatings } from '@capire/reviews'; +// using { sap.capire.bookshop.Books } from '@capire/bookshop'; +// extend Books with { +// rating : type of AverageRatings:rating; // average rating +// reviews : Integer @title : '{i18n>NumberOfReviews}'; +// } -// -// Extend Orders with Books as Products -// -using { sap.capire.orders.Orders } from '@capire/orders'; -extend Orders:Items with { - book : Association to Books on product.ID = book.ID -} +// // +// // Extend Orders with Books as Products +// // +// using { sap.capire.orders.Orders } from '@capire/orders'; +// extend Orders:Items with { +// book : Association to Books on product.ID = book.ID +// } -// Ensure models from all imported packages are loaded -using from '@capire/orders/app/fiori'; -using from '@capire/data-viewer'; -using from '@capire/common'; +// // Ensure models from all imported packages are loaded +// using from '@capire/orders/app/fiori'; +// using from '@capire/data-viewer'; +// using from '@capire/common'; -// Restrict admin access to AdminService -annotate AdminService with @requires:'admin'; +// // Restrict admin access to AdminService +// annotate AdminService with @requires:'admin'; diff --git a/srv/mashup.js b/srv/mashup.js index 5dd40f6..bfae714 100644 --- a/srv/mashup.js +++ b/srv/mashup.js @@ -1,65 +1,65 @@ -const cds = require ('@sap/cds') +// const cds = require ('@sap/cds') -// Add routes to UIs from imported packages -if (!cds.env.production) cds.once ('bootstrap', (app) => { - app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') - app.serve ('/reviews') .from ('@capire/reviews','app/vue') - app.serve ('/orders') .from('@capire/orders','app/orders') -}) +// // Add routes to UIs from imported packages +// if (!cds.env.production) cds.once ('bootstrap', (app) => { +// app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') +// app.serve ('/reviews') .from ('@capire/reviews','app/vue') +// app.serve ('/orders') .from('@capire/orders','app/orders') +// }) -// Mashing up bookshop services with required services... -cds.once ('served', async ()=>{ +// // Mashing up bookshop services with required services... +// cds.once ('served', async ()=>{ - const CatalogService = await cds.connect.to ('CatalogService') - const ReviewsService = await cds.connect.to ('ReviewsService') - const OrdersService = await cds.connect.to ('OrdersService') - const db = await cds.connect.to ('db') +// const CatalogService = await cds.connect.to ('CatalogService') +// const ReviewsService = await cds.connect.to ('ReviewsService') +// const OrdersService = await cds.connect.to ('OrdersService') +// const db = await cds.connect.to ('db') - // reflect entity definitions used below... - const { Books } = cds.entities ('sap.capire.bookshop') +// // reflect entity definitions used below... +// const { Books } = cds.entities ('sap.capire.bookshop') - // - // Delegate requests to read reviews to the ReviewsService - // Note: prepend is neccessary to intercept generic default handler - // - CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { - console.debug ('> delegating request to ReviewsService') // eslint-disable-line no-console - const [id] = req.params, { columns, limit } = req.query.SELECT - return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)}) - })) +// // +// // Delegate requests to read reviews to the ReviewsService +// // Note: prepend is neccessary to intercept generic default handler +// // +// CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { +// console.debug ('> delegating request to ReviewsService') // eslint-disable-line no-console +// const [id] = req.params, { columns, limit } = req.query.SELECT +// return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)}) +// })) - // - // Create an order with the OrdersService when CatalogService signals a new order - // - CatalogService.before ('submitOrder', async (req) => { - const { book, quantity, buyer = req.user.id } = req.data - const { title, price, currency } = await db.read (Books, book, b => { b.title, b.price, b.currency(c => c.code) }) - await OrdersService.create ('OrdersNoDraft').entries({ - OrderNo: 'Order at '+ (new Date).toLocaleString(), - Items: [{ product:{ID:`${book}`}, title, price, quantity }], - buyer, createdBy: buyer, currency - }) - }) +// // +// // Create an order with the OrdersService when CatalogService signals a new order +// // +// CatalogService.before ('submitOrder', async (req) => { +// const { book, quantity, buyer = req.user.id } = req.data +// const { title, price, currency } = await db.read (Books, book, b => { b.title, b.price, b.currency(c => c.code) }) +// await OrdersService.create ('OrdersNoDraft').entries({ +// OrderNo: 'Order at '+ (new Date).toLocaleString(), +// Items: [{ product:{ID:`${book}`}, title, price, quantity }], +// buyer, createdBy: buyer, currency +// }) +// }) - // - // Update Books' average ratings when ReviewsService signals updated reviews - // - ReviewsService.on ('AverageRatings.Changed', (msg) => { - console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console - const { subject, reviews, rating } = msg.data - return UPDATE (Books, subject) .with ({ reviews, rating }) - }) +// // +// // Update Books' average ratings when ReviewsService signals updated reviews +// // +// ReviewsService.on ('AverageRatings.Changed', (msg) => { +// console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console +// const { subject, reviews, rating } = msg.data +// return UPDATE (Books, subject) .with ({ reviews, rating }) +// }) - // - // Reduce stock of ordered books for orders are created from Orders admin UI - // - OrdersService.on ('OrderChanged', (msg) => { - console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console - const { product, deltaQuantity } = msg.data - return UPDATE (Books) .where ('ID =', product) - .and ('stock >=', deltaQuantity) - .set ('stock -=', deltaQuantity) - }) -}) +// // +// // Reduce stock of ordered books for orders are created from Orders admin UI +// // +// OrdersService.on ('OrderChanged', (msg) => { +// console.debug ('> received:', msg.event, msg.data) // eslint-disable-line no-console +// const { product, deltaQuantity } = msg.data +// return UPDATE (Books) .where ('ID =', product) +// .and ('stock >=', deltaQuantity) +// .set ('stock -=', deltaQuantity) +// }) +// })