Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .cdsrc.yaml

This file was deleted.

22 changes: 11 additions & 11 deletions app/appconfig/fioriSandboxConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
]
},
Expand All @@ -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",
Expand Down Expand Up @@ -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": {
Expand Down
86 changes: 71 additions & 15 deletions app/genres/fiori-service.cds
Original file line number Diff line number Diff line change
@@ -1,31 +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 @readonly;

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
};
2 changes: 1 addition & 1 deletion app/genres/webapp/i18n/i18n.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#XTIT
appTitle=Browse Genres
appTitle=Manage Genres
#XTXT
appDescription=Genres as Tree View
2 changes: 1 addition & 1 deletion app/genres/webapp/i18n/i18n_de.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
appTitle=Zeige Genres
appTitle=Verwalte Genres
appDescription=Genres als Baumansicht
25 changes: 14 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "git+https://github.tools.sap/cap/cds#fix/hierarchy-direct-crud-exclusion"
},
"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",
Expand All @@ -41,6 +41,9 @@
},
"log": {
"service": true
},
"fiori": {
"direct_crud": true
}
}
}
54 changes: 27 additions & 27 deletions srv/mashup.cds
Original file line number Diff line number Diff line change
@@ -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';
110 changes: 55 additions & 55 deletions srv/mashup.js
Original file line number Diff line number Diff line change
@@ -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)
// })
// })
Loading