Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c49454f44 | ||
|
|
d570ad20b2 | ||
|
|
d891a1b905 | ||
|
|
9f604b9d13 |
6
.github/workflows/node.js.yml
vendored
6
.github/workflows/node.js.yml
vendored
@@ -5,9 +5,9 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -24,5 +24,5 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,5 +15,3 @@ default-env.json
|
||||
packages/messageBox
|
||||
reviews/msg-box
|
||||
reviews/db/test.db
|
||||
|
||||
*.openapi3.json
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1,3 +0,0 @@
|
||||
# Ensure we always use public packages, i.e. avoid using local registries from ~/.npmrc
|
||||
@sap:registry=https://registry.npmjs.org/
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -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."
|
||||
}
|
||||
],
|
||||
"ref": "main"
|
||||
"ref": "master"
|
||||
}
|
||||
@@ -17,7 +17,7 @@ Find here a collection of samples for the [SAP Cloud Application Programming Mod
|
||||
|
||||
### Download
|
||||
|
||||
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/main.zip).
|
||||
If you've [Git](https://git-scm.com/downloads) installed, clone this repo as shown below, otherwise [download as ZIP file](archive/master.zip).
|
||||
|
||||
```sh
|
||||
git clone https://github.com/sap-samples/cloud-cap-samples samples
|
||||
|
||||
@@ -10,7 +10,7 @@ const books = new Vue ({
|
||||
data: {
|
||||
list: [],
|
||||
book: undefined,
|
||||
order: { quantity:1, succeeded:'', failed:'' }
|
||||
order: { amount:1, succeeded:'', failed:'' }
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -26,18 +26,18 @@ const books = new Vue ({
|
||||
const book = books.book = books.list [eve.currentTarget.rowIndex-1]
|
||||
const res = await GET(`/Books/${book.ID}?$select=descr,stock,image`)
|
||||
Object.assign (book, res.data)
|
||||
books.order = { quantity:1 }
|
||||
books.order = { amount:1 }
|
||||
setTimeout (()=> $('form > input').focus(), 111)
|
||||
},
|
||||
|
||||
async submitOrder () {
|
||||
const {book,order} = books, quantity = parseInt (order.quantity) || 1 // REVISIT: Okra should be less strict
|
||||
const {book,order} = books, amount = parseInt (order.amount) || 1 // REVISIT: Okra should be less strict
|
||||
try {
|
||||
const res = await POST(`/submitOrder`, { quantity, book: book.ID })
|
||||
const res = await POST(`/submitOrder`, { amount, book: book.ID })
|
||||
book.stock = res.data.stock
|
||||
books.order = { quantity, succeeded: `Successfully ordered ${quantity} item(s).` }
|
||||
books.order = { amount, succeeded: `Successfully ordered ${amount} item(s).` }
|
||||
} catch (e) {
|
||||
books.order = { quantity, failed: e.response.data.error.message }
|
||||
books.order = { amount, failed: e.response.data.error.message }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
{{ book.stock }} in stock
|
||||
</label>
|
||||
<form @submit.prevent="submitOrder" style="float:right; display:flex; flex-direction:row-reverse">
|
||||
<input type="number" v-model="order.quantity" v-bind:class="{ failed: order.failed }" style="width:5em">
|
||||
<input type="number" v-model="order.amount" v-bind:class="{ failed: order.failed }" style="width:5em">
|
||||
<input type="submit" value="Order:" class="muted-button">
|
||||
</form>
|
||||
<h4> {{ book.title }} </h4>
|
||||
|
||||
@@ -11,6 +11,6 @@ service CatalogService @(path:'/browse') {
|
||||
} excluding { createdBy, modifiedBy };
|
||||
|
||||
@requires: 'authenticated-user'
|
||||
action submitOrder ( book: Books:ID, quantity: Integer ) returns { stock: Integer };
|
||||
event OrderedBook : { book: Books:ID; quantity: Integer; buyer: String };
|
||||
action submitOrder ( book: Books:ID, amount: Integer ) returns { stock: Integer };
|
||||
event OrderedBook : { book: Books:ID; amount: Integer; buyer: String };
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ class CatalogService extends cds.ApplicationService { init(){
|
||||
|
||||
// Reduce stock of ordered books if available stock suffices
|
||||
this.on ('submitOrder', async req => {
|
||||
const {book,quantity} = req.data
|
||||
const {book,amount} = req.data
|
||||
let {stock} = await SELECT `stock` .from (Books,book)
|
||||
if (stock >= quantity) {
|
||||
await UPDATE (Books,book) .with (`stock -=`, quantity)
|
||||
await this.emit ('OrderedBook', { book, quantity, buyer:req.user.id })
|
||||
if (stock >= amount) {
|
||||
await UPDATE (Books,book) .with (`stock -=`, amount)
|
||||
await this.emit ('OrderedBook', { book, amount, buyer:req.user.id })
|
||||
return { stock }
|
||||
}
|
||||
else return req.error (409,`${quantity} exceeds stock for book #${book}`)
|
||||
else return req.error (409,`${amount} exceeds stock for book #${book}`)
|
||||
})
|
||||
|
||||
// Add some discount for overstocked books
|
||||
|
||||
@@ -71,7 +71,7 @@ POST {{server}}/browse/submitOrder
|
||||
Content-Type: application/json
|
||||
{{me}}
|
||||
|
||||
{ "book":201, "quantity":5 }
|
||||
{ "book":201, "amount":5 }
|
||||
|
||||
|
||||
### ------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using { AdminService } from '../../db/schema';
|
||||
using from '../common'; // to help UI linter get the complete annotations
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -87,8 +86,8 @@ annotate AdminService.Books.texts with @(
|
||||
annotate AdminService.Books.texts {
|
||||
locale @ValueList:{entity:'Languages',type:#fixed}
|
||||
}
|
||||
// In addition we need to expose Languages through AdminService as a target for ValueList
|
||||
// In addition we need to expose Languages through AdminService
|
||||
using { sap } from '@sap/cds/common';
|
||||
extend service AdminService {
|
||||
@readonly entity Languages as projection on sap.common.Languages;
|
||||
entity Languages as projection on sap.common.Languages;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ using CatalogService from '@capire/bookshop';
|
||||
//
|
||||
annotate CatalogService.Books with @(
|
||||
UI: {
|
||||
HeaderInfo: {
|
||||
HeaderInfo: {
|
||||
TypeName: 'Book',
|
||||
TypeNamePlural: 'Books',
|
||||
Description: {Value: author}
|
||||
},
|
||||
Description: {Value: author}
|
||||
},
|
||||
HeaderFacets: [
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Description}', Target: '@UI.FieldGroup#Descr'},
|
||||
],
|
||||
@@ -38,7 +38,7 @@ annotate CatalogService.Books with @(
|
||||
//
|
||||
annotate CatalogService.Books with @(
|
||||
UI: {
|
||||
SelectionFields: [ ID, price, currency_code ],
|
||||
SelectionFields: [ ID, price, currency_code ],
|
||||
LineItem: [
|
||||
{Value: title},
|
||||
{Value: author, Label:'{i18n>Author}'},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const cds = require ('@sap/cds')
|
||||
module.exports = cds.server
|
||||
|
||||
cds.once('bootstrap',(app)=>{
|
||||
app.use ('/orders/webapp', _from('@capire/orders/app/orders/webapp/manifest.json'))
|
||||
@@ -9,13 +8,7 @@ cds.once('bootstrap',(app)=>{
|
||||
|
||||
cds.once('served', require('./srv/mashup'))
|
||||
|
||||
// Swagger UI - see https://cap.cloud.sap/docs/advanced/openapi
|
||||
try {
|
||||
const cds_swagger = require ('cds-swagger-ui-express')
|
||||
cds.once ('bootstrap', app => app.use (cds_swagger()) )
|
||||
} catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') throw err
|
||||
}
|
||||
module.exports = cds.server
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -19,7 +19,7 @@ extend Books with {
|
||||
// Extend Orders with Books as Products
|
||||
//
|
||||
|
||||
using { sap.capire.orders.Orders_Items } from '@capire/orders';
|
||||
extend Orders_Items with {
|
||||
using { sap.capire.orders.Orders } from '@capire/orders';
|
||||
extend Orders.Items with {
|
||||
book : Association to Books on product.ID = book.ID
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ module.exports = async()=>{ // called by server.js
|
||||
// Create an order with the OrdersService when CatalogService signals a new order
|
||||
//
|
||||
CatalogService.on ('OrderedBook', async (msg) => {
|
||||
const { book, quantity, buyer } = msg.data
|
||||
const { book, amount, buyer } = msg.data
|
||||
const { title, price } = await db.tx(msg).read (Books, book, b => { b.title, b.price })
|
||||
return OrdersService.tx(msg).create ('Orders').entries({
|
||||
OrderNo: 'Order at '+ (new Date).toLocaleString(),
|
||||
Items: [{ product:{ID:`${book}`}, title, price, quantity }],
|
||||
Items: [{ product:{ID:`${book}`}, title, price, amount }],
|
||||
buyer, createdBy: buyer
|
||||
})
|
||||
})
|
||||
@@ -51,9 +51,9 @@ module.exports = async()=>{ // called by server.js
|
||||
//
|
||||
OrdersService.on ('OrderChanged', (msg) => {
|
||||
console.debug ('> received:', msg.event, msg.data)
|
||||
const { product, deltaQuantity } = msg.data
|
||||
const { product, deltaAmount } = msg.data
|
||||
return UPDATE (Books) .where ('ID =', product)
|
||||
.and ('stock >=', deltaQuantity)
|
||||
.set ('stock -=', deltaQuantity)
|
||||
.and ('stock >=', deltaAmount)
|
||||
.set ('stock -=', deltaAmount)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# 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)
|
||||
@@ -2,53 +2,6 @@
|
||||
"name": "@capire/hello-world",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "npx jest --silent",
|
||||
"start": "cds serve srv/world.cds",
|
||||
"start:ts": "cds-ts serve srv/world.cds"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.12.0",
|
||||
"ts-jest": "^27.0.2",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"preset": "ts-jest",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": {
|
||||
"_comment": "see https://githubmemory.com/repo/kulshekhar/ts-jest/issues/2722",
|
||||
"ignoreCodes": [
|
||||
151001
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"watch": "cds serve world.cds"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = class say {
|
||||
hello(req: any) {
|
||||
return `Hello ${req.data.to} from a TypeScript file!`
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
process.env.CDS_TYPESCRIPT = 'true';
|
||||
import * as cds from '@sap/cds';
|
||||
|
||||
//@ts-ignore
|
||||
const {GET} = cds.test.in(__dirname,'../srv').run('serve', 'world.cds');
|
||||
|
||||
describe('Hello world!', () => {
|
||||
afterAll(() => { delete process.env.CDS_TYPESCRIPT; });
|
||||
|
||||
it('should say hello with class impl from a typescript file', async () => {
|
||||
const {data} = await GET`/say/hello(to='world')`
|
||||
expect(data.value).toMatch(/Hello world.*typescript.*/i)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,2 +1,3 @@
|
||||
cds.requires.messaging.kind = file-based-messaging
|
||||
PORT = 4006
|
||||
# cds.odata.flavor = x4
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
|
||||
using { OrdersService } from '../../srv/orders-service';
|
||||
using { sap.common } from '@capire/common';
|
||||
|
||||
|
||||
@odata.draft.enabled
|
||||
@@ -45,8 +46,7 @@ annotate OrdersService.Orders with @(
|
||||
],
|
||||
FieldGroup#Details: {
|
||||
Data: [
|
||||
{Value: currency.code, Label:'Currency'},
|
||||
{Value: status.code, Label:'Status'},
|
||||
{Value: currency.code, Label:'Currency'}
|
||||
]
|
||||
},
|
||||
FieldGroup#Created: {
|
||||
@@ -67,30 +67,36 @@ annotate OrdersService.Orders with @(
|
||||
createdBy @UI.HiddenFilter:false;
|
||||
};
|
||||
|
||||
annotate OrdersService.OrderStatus with {
|
||||
code @Common: { Text: name, TextArrangement: #TextOnly };
|
||||
}
|
||||
|
||||
|
||||
annotate OrdersService.Orders_Items with @(
|
||||
annotate OrdersService.Orders.Items with @(
|
||||
UI: {
|
||||
LineItem: [
|
||||
{Value: product_ID, Label:'Product ID'},
|
||||
{Value: title, Label:'Product Title'},
|
||||
{Value: price, Label:'Unit Price'},
|
||||
{Value: quantity, Label:'Quantity'},
|
||||
{Value: amount, Label:'Quantity'},
|
||||
],
|
||||
Identification: [ //Is the main field group
|
||||
{Value: quantity, Label:'Quantity'},
|
||||
{Value: title, Label:'Product'},
|
||||
{Value: product_ID, Label:'Product ID'},
|
||||
{Value: title, Label:'Product Title'},
|
||||
{Value: amount, Label:'Amount'},
|
||||
{Value: price, Label:'Unit Price'},
|
||||
],
|
||||
Facets: [
|
||||
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
|
||||
],
|
||||
HeaderInfo: {
|
||||
TypeName: 'Order Item', TypeNamePlural: 'Order Items',
|
||||
Title: {
|
||||
Label: 'Product ID ', //A label is possible but it is not considered on the ObjectPage yet
|
||||
Value: product_ID
|
||||
},
|
||||
Description: {Value: createdBy}
|
||||
},
|
||||
},
|
||||
) {
|
||||
quantity @(
|
||||
amount @(
|
||||
Common.FieldControl: #Mandatory
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
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 |