Compare commits
32 Commits
deploy-ext
...
data-load
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11f3b357e4 | ||
|
|
5f89334403 | ||
|
|
e612fa97ea | ||
|
|
da2ea39466 | ||
|
|
117000df71 | ||
|
|
f3ffb69d3a | ||
|
|
f908484973 | ||
|
|
c4529f3cd7 | ||
|
|
0220400484 | ||
|
|
c1911b6e96 | ||
|
|
19083d156e | ||
|
|
48d547e6cd | ||
|
|
bae491a832 | ||
|
|
ca41a2141c | ||
|
|
08f409af73 | ||
|
|
efa60550fb | ||
|
|
f599206bf4 | ||
|
|
2f5d159428 | ||
|
|
2be3d50389 | ||
|
|
46b58f1b5c | ||
|
|
e87527cbcd | ||
|
|
091844219b | ||
|
|
de796e5a89 | ||
|
|
f988088412 | ||
|
|
c4a51ab719 | ||
|
|
f58376607a | ||
|
|
839048d87c | ||
|
|
3b3463f889 | ||
|
|
d3396304ec | ||
|
|
ae09caf7ad | ||
|
|
e1052c209b | ||
|
|
cd3aad59e1 |
4
.github/workflows/node.js.yml
vendored
4
.github/workflows/node.js.yml
vendored
@@ -5,9 +5,9 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -105,5 +105,5 @@
|
|||||||
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
|
"description": "### Summary\n\nThat's it! You have seen: \n- How to integrate database-specific functions in a CDS model.\n- How to switch between the two implementations for SQLite and SAP HANA."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ref": "master"
|
"ref": "main"
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
|
|||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
|
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const books = new Vue ({
|
|||||||
data: {
|
data: {
|
||||||
list: [],
|
list: [],
|
||||||
book: undefined,
|
book: undefined,
|
||||||
order: { amount:1, succeeded:'', failed:'' }
|
order: { quantity:1, succeeded:'', failed:'' }
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -26,18 +26,18 @@ const books = new Vue ({
|
|||||||
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
||||||
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
||||||
Object.assign (book, res.data)
|
Object.assign (book, res.data)
|
||||||
books.order = { amount:1 }
|
books.order = { quantity:1 }
|
||||||
setTimeout (()=> $('form > input').focus(), 111)
|
setTimeout (()=> $('form > input').focus(), 111)
|
||||||
},
|
},
|
||||||
|
|
||||||
async submitOrder () {
|
async submitOrder () {
|
||||||
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
|
const {book,order} = books, quantity = parseInt (order.quantity) || 1 // REVISIT: Okra should be less strict
|
||||||
try {
|
try {
|
||||||
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
|
||||||
book.stock = res.data.stock
|
book.stock = res.data.stock
|
||||||
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
|
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
books.order = { amount, failed: e.response.data.error.message }
|
books.order = { quantity, failed: e.response.data.error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
{{ book.stock }} in stock
|
{{ book.stock }} in stock
|
||||||
</label>
|
</label>
|
||||||
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
||||||
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em">
|
<input type="number" v-model="order.quantity" v-bind:class="{ failed: order.failed }" style="width:5em">
|
||||||
<input type="submit" value="Order:" class="muted-button">
|
<input type="submit" value="Order:" class="muted-button">
|
||||||
</form>
|
</form>
|
||||||
<h4> {{ book.title }} </h4>
|
<h4> {{ book.title }} </h4>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
|
|||||||
} excluding { createdBy, modifiedBy };
|
} excluding { createdBy, modifiedBy };
|
||||||
|
|
||||||
@requires: 'authenticated-user'
|
@requires: 'authenticated-user'
|
||||||
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
|
||||||
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ class CatalogService extends cds.ApplicationService { init(){
|
|||||||
|
|
||||||
// Reduce stock of ordered books if available stock suffices
|
// Reduce stock of ordered books if available stock suffices
|
||||||
this.on ('submitOrder', async req => {
|
this.on ('submitOrder', async req => {
|
||||||
const {book,amount} = req.data
|
const {book,quantity} = req.data
|
||||||
let {stock} = await SELECT `stock` .from (Books,book)
|
let {stock} = await SELECT `stock` .from (Books,book)
|
||||||
if (stock >= amount) {
|
if (stock >= quantity) {
|
||||||
await UPDATE (Books,book) .with (`stock -=`, amount)
|
await UPDATE (Books,book) .with (`stock -=`, quantity)
|
||||||
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
|
||||||
return { stock }
|
return { stock }
|
||||||
}
|
}
|
||||||
else return req.error (409,`${amount} exceeds stock for book #${book}`)
|
else return req.error (409,`${quantity} exceeds stock for book #${book}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add some discount for overstocked books
|
// Add some discount for overstocked books
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ POST {{server}}/browse/submitOrder
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
{{me}}
|
{{me}}
|
||||||
|
|
||||||
{ "book":201, "amount":5 }
|
{ "book":201, "quantity":5 }
|
||||||
|
|
||||||
|
|
||||||
### ------------------------------------------------------------------------
|
### ------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using { AdminService, sap.capire.bookshop } from '../../db/schema';
|
using { AdminService } from '../../db/schema';
|
||||||
|
using from '../common'; // to help UI linter get the complete annotations
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -86,9 +87,8 @@ annotate AdminService.Books.texts with @(
|
|||||||
annotate AdminService.Books.texts {
|
annotate AdminService.Books.texts {
|
||||||
locale @ValueList:{entity:'Languages',type:#fixed}
|
locale @ValueList:{entity:'Languages',type:#fixed}
|
||||||
}
|
}
|
||||||
// In addition we need to expose Languages and Books.texts through AdminService
|
// In addition we need to expose Languages through AdminService as a target for ValueList
|
||||||
using { sap } from '@sap/cds/common';
|
using { sap } from '@sap/cds/common';
|
||||||
extend service AdminService {
|
extend service AdminService {
|
||||||
entity Languages as projection on sap.common.Languages;
|
@readonly entity Languages as projection on sap.common.Languages;
|
||||||
entity Books.texts as projection on bookshop.Books.texts;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ using CatalogService from '@capire/bookshop';
|
|||||||
//
|
//
|
||||||
annotate CatalogService.Books with @(
|
annotate CatalogService.Books with @(
|
||||||
UI: {
|
UI: {
|
||||||
HeaderInfo: {
|
HeaderInfo: {
|
||||||
TypeName: 'Book',
|
TypeName: 'Book',
|
||||||
TypeNamePlural: 'Books',
|
TypeNamePlural: 'Books',
|
||||||
Description: {Value: author}
|
Description: {Value: author}
|
||||||
},
|
},
|
||||||
HeaderFacets: [
|
HeaderFacets: [
|
||||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'},
|
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'},
|
||||||
],
|
],
|
||||||
@@ -38,7 +38,7 @@ annotate CatalogService.Books with @(
|
|||||||
//
|
//
|
||||||
annotate CatalogService.Books with @(
|
annotate CatalogService.Books with @(
|
||||||
UI: {
|
UI: {
|
||||||
SelectionFields: [ ID, price, currency_code ],
|
SelectionFields: [ ID, price, currency_code ],
|
||||||
LineItem: [
|
LineItem: [
|
||||||
{Value: title},
|
{Value: title},
|
||||||
{Value: author, Label:'{i18n>Author}'},
|
{Value: author, Label:'{i18n>Author}'},
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ cds.once('bootstrap',(app)=>{
|
|||||||
cds.once('served', require('./srv/mashup'))
|
cds.once('served', require('./srv/mashup'))
|
||||||
|
|
||||||
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
try {
|
||||||
const cds_swagger = require ('cds-swagger-ui-express')
|
const cds_swagger = require ('cds-swagger-ui-express')
|
||||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ module.exports = async()=>{ // called by server.js
|
|||||||
// Create an order with the OrdersService when CatalogService signals a new order
|
// Create an order with the OrdersService when CatalogService signals a new order
|
||||||
//
|
//
|
||||||
CatalogService.on ('OrderedBook', async (msg) => {
|
CatalogService.on ('OrderedBook', async (msg) => {
|
||||||
const { book, amount, buyer } = msg.data
|
const { book, quantity, buyer } = msg.data
|
||||||
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
||||||
return OrdersService.tx(msg).create ('Orders').entries({
|
return OrdersService.tx(msg).create ('Orders').entries({
|
||||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
||||||
Items: [{ product:{ID:`${book}`}, title, price, amount }],
|
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
||||||
buyer, createdBy: buyer
|
buyer, createdBy: buyer
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -51,9 +51,9 @@ module.exports = async()=>{ // called by server.js
|
|||||||
//
|
//
|
||||||
OrdersService.on ('OrderChanged', (msg) => {
|
OrdersService.on ('OrderChanged', (msg) => {
|
||||||
console.debug ('> received:', msg.event, msg.data)
|
console.debug ('> received:', msg.event, msg.data)
|
||||||
const { product, deltaAmount } = msg.data
|
const { product, deltaQuantity } = msg.data
|
||||||
return UPDATE (Books) .where ('ID =', product)
|
return UPDATE (Books) .where ('ID =', product)
|
||||||
.and ('stock >=', deltaAmount)
|
.and ('stock >=', deltaQuantity)
|
||||||
.set ('stock -=', deltaAmount)
|
.set ('stock -=', deltaQuantity)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
ID;createdAt;createdBy;buyer;OrderNo;currency_code
|
ID;createdAt;createdBy;buyer;OrderNo;currency_code;status_code
|
||||||
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR
|
7e2f2640-6866-4dcf-8f4d-3027aa831cad;2019-01-31;john.doe@test.com;john.doe@test.com;1;EUR;O
|
||||||
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR
|
64e718c9-ff99-47f1-8ca3-950c850777d4;2019-01-30;jane.doe@test.com;jane.doe@test.com;2;EUR;C
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
ID;up__ID;amount;product_ID;title;price
|
ID;up__ID;quantity;product_ID;title;price
|
||||||
58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11
|
58040e66-1dcd-4ffb-ab10-fdce32028b79;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;201;Wuthering Heights;11.11
|
||||||
64e718c9-ff99-47f1-8ca3-950c850777d4;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;271;Catweazle;15
|
64e718c9-ff99-47f1-8ca3-950c850777d4;7e2f2640-6866-4dcf-8f4d-3027aa831cad;1;271;Catweazle;15
|
||||||
e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28
|
e9641166-e050-4261-bfee-d1e797e6cb7f;64e718c9-ff99-47f1-8ca3-950c850777d4;2;252;Eleonora;28
|
||||||
|
15
hello/README.md
Normal file
15
hello/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Hello World Getting Started Sample
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- To run the JavaScript implementation, open a new terminal and run `cds watch`.
|
||||||
|
- To run the TypeScript implementation, open a new terminal and run `cds-ts watch`.
|
||||||
|
|
||||||
|
Then call the service at: http://localhost:4004/say/hello(to='world')
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
Learn more about:
|
||||||
|
|
||||||
|
- [Hello World!](https://cap.cloud.sap/docs/get-started/hello-world)
|
||||||
|
- [Using TypeScript](https://cap.cloud.sap/docs/get-started/using-typescript)
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npx jest --silent",
|
"test": "npx jest --silent",
|
||||||
"watch": "cds serve world.cds",
|
"start": "cds serve srv/world.cds",
|
||||||
"watch:ts": "cds-ts serve world.cds"
|
"start:ts": "cds-ts serve srv/world.cds"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
@@ -25,5 +25,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"env": {
|
||||||
|
"es2020": true,
|
||||||
|
"node": true,
|
||||||
|
"jest": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"SELECT": true,
|
||||||
|
"INSERT": true,
|
||||||
|
"UPDATE": true,
|
||||||
|
"DELETE": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"DROP": true,
|
||||||
|
"CDL": true,
|
||||||
|
"CQL": true,
|
||||||
|
"CXL": true,
|
||||||
|
"cds": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"require-atomic-updates": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
hello/srv/world.ts
Normal file
5
hello/srv/world.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = class say {
|
||||||
|
hello(req: any) {
|
||||||
|
return `Hello ${req.data.to} from a TypeScript file!`
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ process.env.CDS_TYPESCRIPT = 'true';
|
|||||||
import * as cds from '@sap/cds';
|
import * as cds from '@sap/cds';
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const {GET} = cds.test.in(__dirname,'..').run('serve', 'world.cds');
|
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
|
||||||
|
|
||||||
describe('Hello world!', () => {
|
describe('Hello world!', () => {
|
||||||
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = class say {
|
|
||||||
hello(req: any) {
|
|
||||||
return `Hello ${req.data.to} from a typescript file!`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,8 @@ annotate OrdersService.Orders with @(
|
|||||||
],
|
],
|
||||||
FieldGroup#Details: {
|
FieldGroup#Details: {
|
||||||
Data: [
|
Data: [
|
||||||
{Value: currency.code, Label:'Currency'}
|
{Value: currency.code, Label:'Currency'},
|
||||||
|
{Value: status.code, Label:'Status'},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
FieldGroup#Created: {
|
FieldGroup#Created: {
|
||||||
@@ -66,6 +67,9 @@ annotate OrdersService.Orders with @(
|
|||||||
createdBy @UI.HiddenFilter:false;
|
createdBy @UI.HiddenFilter:false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
annotate OrdersService.OrderStatus with {
|
||||||
|
code @Common: { Text: name, TextArrangement: #TextOnly };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
annotate OrdersService.Orders_Items with @(
|
annotate OrdersService.Orders_Items with @(
|
||||||
@@ -74,10 +78,10 @@ annotate OrdersService.Orders_Items with @(
|
|||||||
{Value: product_ID, Label:'Product ID'},
|
{Value: product_ID, Label:'Product ID'},
|
||||||
{Value: title, Label:'Product Title'},
|
{Value: title, Label:'Product Title'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'Unit Price'},
|
||||||
{Value: amount, Label:'Quantity'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
],
|
],
|
||||||
Identification: [ //Is the main field group
|
Identification: [ //Is the main field group
|
||||||
{Value: amount, Label:'Amount'},
|
{Value: quantity, Label:'Quantity'},
|
||||||
{Value: title, Label:'Product'},
|
{Value: title, Label:'Product'},
|
||||||
{Value: price, Label:'Unit Price'},
|
{Value: price, Label:'Unit Price'},
|
||||||
],
|
],
|
||||||
@@ -86,7 +90,7 @@ annotate OrdersService.Orders_Items with @(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
amount @(
|
quantity @(
|
||||||
Common.FieldControl: #Mandatory
|
Common.FieldControl: #Mandatory
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
4
orders/db/data/sap.capire.orders-OrderStatus.csv
Normal file
4
orders/db/data/sap.capire.orders-OrderStatus.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
code;name;descr
|
||||||
|
O;Open;Order is open
|
||||||
|
P;In Process;Order is about to be processed
|
||||||
|
C;Closed;Order is closed
|
||||||
|
500 Internal Server Error
Gitea Version: 1.24.4 |