Compare commits

...

147 Commits

Author SHA1 Message Date
D065023
a2febd57db Merge branch 'dkom' of https://github.com/SAP-samples/cloud-cap-samples into dkom 2020-02-10 18:05:44 +01:00
D065023
5073af37d3 change folder 2020-02-10 18:05:34 +01:00
Dr. David Kunz
e8cda443ba Update README.md 2020-02-10 13:43:01 +01:00
D065023
da63d6c287 Merge branch 'dkom' of https://github.com/SAP-samples/cloud-cap-samples into dkom 2020-02-07 18:41:56 +01:00
Dr. David Kunz
009dfc53c5 Update README.md 2020-02-05 16:09:49 +01:00
Dr. David Kunz
d760516e3e Update README.md 2020-02-05 16:09:29 +01:00
Dr. David Kunz
261d238738 Update README.md 2020-02-04 09:40:53 +01:00
D065023
136eae69eb barebone 2020-01-08 18:29:28 +01:00
D065023
e0306850b4 prettier 2020-01-08 16:04:45 +01:00
D065023
b7f9b78988 prettier 2020-01-08 16:00:50 +01:00
D065023
4e5f31a29d where -> and 2020-01-08 16:00:24 +01:00
D065023
2d7efafd30 prettier 2020-01-08 16:00:07 +01:00
D065023
cf0b9c2a52 csv -> data 2020-01-08 15:28:01 +01:00
D065023
2053450404 rm tombstone 2020-01-08 13:52:23 +01:00
D065023
920600a5ff prettier 2020-01-08 13:49:59 +01:00
D065023
dc1ea91d9e prettier 2020-01-08 13:45:53 +01:00
D065023
9ba5aae999 fix bug 2020-01-08 13:43:38 +01:00
D065023
29aee15cf3 Alice doesnt need a password anymore 2020-01-07 14:47:05 +01:00
D065023
2ca5a79c7e renaming ext service 2020-01-07 12:47:30 +01:00
Dr. David Kunz
857b28aad0 Update README.md 2020-01-07 12:37:56 +01:00
Dr. David Kunz
91d1a9bcb2 Update README.md 2020-01-07 12:36:26 +01:00
Daniel
a33dbff74e restored master .cds for db/schema and cat-service 2020-01-04 07:33:35 +01:00
Daniel
7f65729dc9 Merge branch 'master' into dkom 2020-01-04 07:28:09 +01:00
Daniel
3c4aa7427e Merge branch 'master' into dkom 2020-01-03 03:10:26 +01:00
D065023
28859993ba rm file 2020-01-02 09:20:50 +01:00
D065023
e503f157f6 cosmetics 2019-12-30 14:20:11 +01:00
D065023
4eebfb5df4 cosmetics 2019-12-30 14:12:51 +01:00
D065023
28362cc835 cosmetics 2019-12-30 14:12:14 +01:00
D065023
78e6138718 minor 2019-12-30 14:09:57 +01:00
D065023
7db2c6e781 tombstone logic 2019-12-30 14:06:41 +01:00
D065023
afc5be2610 comment 2019-12-30 13:55:06 +01:00
D065023
5bc8d4dde0 cosmetics 2019-12-30 13:54:20 +01:00
D065023
60563dc816 cosmetics 2019-12-30 13:51:02 +01:00
D065023
4a1887f424 translations 2019-12-27 12:01:20 +01:00
D065023
c28287f9e4 typo 2019-12-27 11:01:58 +01:00
D065023
8cadac0051 cleanup readme 2019-12-27 10:25:35 +01:00
D065023
034c3b84f7 adjusted 2019-12-26 14:18:35 +01:00
D065023
b155754a72 adjusted 2019-12-26 14:16:59 +01:00
D065023
782e8a6696 adjusted README 2019-12-26 14:16:18 +01:00
D065023
f3c14a0625 cds -> latest 2019-12-23 18:06:04 +01:00
D065023
4cffa85079 rm todo 2019-12-23 17:26:40 +01:00
D065023
837e6bf1c8 minor 2019-12-23 17:23:00 +01:00
D065023
b394dbd234 rm event modelling 2019-12-23 17:19:53 +01:00
D065023
fdd0a256c4 made async api work 2019-12-23 15:54:52 +01:00
D065023
5b5d9da82e added eventing 2019-12-23 11:53:24 +01:00
D065023
2af54a520c Added README 2019-12-23 10:59:44 +01:00
D065023
128213aba7 rm comment 2019-12-23 10:14:14 +01:00
D065023
4bc2257cea cleanup 2019-12-23 10:12:00 +01:00
D065023
e16a343ce3 prettier 2019-12-23 10:10:03 +01:00
D065023
1f01bdf202 req.user.id === 'anonymous' in case of not logged in 2019-12-23 10:08:42 +01:00
Daniel
45843ab7bd cleaned up 2019-12-21 18:01:58 +01:00
D065023
421cea9f2b require utils 2019-12-20 16:12:29 +01:00
D065023
aa919f9d62 async conntext 2019-12-20 16:11:10 +01:00
D065023
ed814a1f75 hack for app studio 2019-12-20 16:09:11 +01:00
Daniel
c04c93cca6 c2 2019-12-20 13:37:50 +01:00
Daniel
2cde812edd cleaned up and working end to end now 2019-12-20 13:37:31 +01:00
Daniel
2f576dbb1b gaaaanz schmaler korridor 2019-12-19 18:23:38 +01:00
Daniel
7f7cd43bff adding test for GET Orders 2019-12-19 18:23:16 +01:00
Daniel
294f9feb36 added launch conf for bookshop 2019-12-19 18:22:54 +01:00
Daniel
2ebfcd8871 fixed: .contact 2019-12-19 18:22:31 +01:00
Daniel
963d0fbb6c constraint violation 2019-12-19 17:09:29 +01:00
Daniel
eebdd74bfe more work-arounds 2019-12-19 17:04:32 +01:00
Daniel
37810c9027 quick fix for empty value help 2019-12-19 16:55:02 +01:00
Daniel
48a086e9a1 quick fix for empty valuehelp 2019-12-19 16:54:21 +01:00
Daniel
659c347c71 .BusinessPartner -> .contact 2019-12-19 16:48:18 +01:00
Daniel
0bbb8e3d3b + auth conf --> we need to remove that later 2019-12-19 16:39:43 +01:00
Daniel
b3abcbcaae cleanup 2019-12-19 16:26:17 +01:00
Daniel
3716d4d5e3 fixed: auto-exposed addresses were missing 2019-12-19 16:22:42 +01:00
Daniel
226094e85c doesn' work with hana 2019-12-19 15:26:03 +01:00
Daniel
de149ea9b3 Merge branch 'D-kom-Demo' into dkom 2019-12-17 14:35:34 +01:00
Daniel
b6c1610817 updates parser version 2019-12-17 14:24:04 +01:00
Daniel
3df0981992 BUPA -> contact 2019-12-17 13:01:35 +01:00
Daniel
8c8c5f3f9d some more cosmetics tweaks 2019-12-17 12:55:09 +01:00
Daniel
2c0f69a161 some more cleanup 2019-12-17 12:46:10 +01:00
Daniel
54d0c8b35d simplified models and impl -> requires latest snapshots 2019-12-17 12:30:13 +01:00
D065023
3027a7a1e5 hack for app studio 2019-12-17 10:03:17 +01:00
David-Kunz
8eaf34f5d3 Update package.json 2019-12-17 09:59:11 +01:00
David-Kunz
0b0a22d126 Update schema.cds 2019-12-17 09:40:17 +01:00
Daniel
c0e1fb38ac cosmetics 2019-12-16 22:12:37 +01:00
D065023
74c155ca62 better 2019-12-16 21:56:32 +01:00
D065023
db16577235 no more bla 2019-12-16 21:55:33 +01:00
Daniel
53989cf609 Merge branch 'D-kom-Demo' into dkom 2019-12-16 21:54:49 +01:00
D065023
d678b51320 revert prefix 2019-12-16 21:52:43 +01:00
Daniel
5720d73b76 revised 2019-12-16 21:50:23 +01:00
D065023
06a6ac2201 fixed bug 2019-12-16 21:14:56 +01:00
D065023
125edc34e2 fixed 2019-12-16 21:12:03 +01:00
D065023
dc8e8c55df fix bug 2019-12-16 21:05:28 +01:00
D065023
f89acc00dd fix typo 2019-12-16 21:04:04 +01:00
D065023
3e725bcc26 refactor 2019-12-16 21:02:57 +01:00
D065023
dfea19334d use prefix 2019-12-16 20:22:12 +01:00
D065023
8f11de5430 cosmetics 2019-12-16 19:18:21 +01:00
D065023
38ce94d5cd syntax cleanup 2019-12-16 18:42:59 +01:00
D065023
ffe633a493 cleanup 2019-12-16 11:31:29 +01:00
D065023
e081182a7c cleanup 2019-12-16 11:30:42 +01:00
D065023
e4f8f13dbf typo 2019-12-16 11:28:06 +01:00
D065023
cc698ec23f rename ql -> query 2019-12-16 11:13:34 +01:00
D065023
382a4c562d prettier 2019-12-16 11:11:45 +01:00
D065023
811694cdf1 revert 2019-12-16 11:11:25 +01:00
D065023
83653bd095 Merge branch 'D-kom-Demo' of https://github.com/SAP-samples/cloud-cap-samples into D-kom-Demo 2019-12-16 10:29:44 +01:00
D065023
e27275d29a go to own database when remote system unavailable 2019-12-16 10:29:36 +01:00
Daniel
b9330d7f77 Merge branch 'master' into D-kom-Demo 2019-12-16 08:41:04 +01:00
D065023
f413b45e24 more checks 2019-12-13 14:26:22 +01:00
D065023
4a21b9edc3 cleaner 2019-12-13 14:20:04 +01:00
D065023
4bfd4430e1 rm comment 2019-12-13 10:42:18 +01:00
D065023
0bb3144aea rm console.logs 2019-12-13 10:41:11 +01:00
D065023
c1d2c4caef translations and cleanup 2019-12-13 09:00:56 +01:00
D065023
59f68c0f28 code reduction 2019-12-13 08:47:36 +01:00
D065023
5ba3458b27 added limit 2019-12-13 08:46:09 +01:00
D065023
199b2c8045 it works 2019-12-12 17:33:21 +01:00
Elena Oresharova
3b4abf5600 Fix side effects for address 2019-12-12 11:12:01 +01:00
D065023
f69c0ae190 fiori3 theme 2019-12-12 10:19:44 +01:00
D065023
f48cd1cc2f cosmetics 2019-12-12 10:14:23 +01:00
D065023
6bded9df98 more test data 2019-12-12 10:05:15 +01:00
D065023
5a659774b5 use msg as transaction box 2019-12-12 09:53:46 +01:00
D065023
17d6dc8cf8 rm delete 2019-12-12 08:47:50 +01:00
D065023
1a1686e340 cleanup 2019-12-11 16:38:38 +01:00
D065023
b298c9b708 fixed a bug 2019-12-11 16:37:51 +01:00
D065023
348a7b191e format 2019-12-11 13:26:22 +01:00
D065023
f56d4fe093 readonly 2019-12-11 13:22:00 +01:00
D065023
02942f5e1a nothing 2019-12-11 13:20:45 +01:00
D065023
1aa9237d20 needed to use several transactions 2019-12-11 13:19:50 +01:00
D065023
7a760cfaf8 cleanup 2019-12-11 12:52:06 +01:00
D065023
6c0d8fa444 bug fix 2019-12-11 12:49:26 +01:00
D065023
3b06003328 fixed bugs - do not change key names 2019-12-11 11:46:55 +01:00
D065023
e686b1819b fixed bugs 2019-12-11 11:46:44 +01:00
D065023
d36c2a97fa bug fix 2019-12-11 11:24:05 +01:00
D065023
f4e119342b bug fix 2019-12-11 11:19:21 +01:00
D065023
ddd02b52f2 eventing, renaming, subselecting 2019-12-11 11:12:57 +01:00
D065023
e6d5183cce impl msg mock 2019-12-06 14:35:02 +01:00
D065023
a191ecf88d rm toUpperCase for user id 2019-12-06 14:11:21 +01:00
D065023
140db39cd4 include authorization 2019-12-06 11:32:50 +01:00
D065023
68ee29598a translations 2019-12-06 11:03:59 +01:00
D065023
7deae997bb fixed minor issues 2019-12-06 10:59:50 +01:00
D065023
12aee3e38c fixed keys 2019-12-06 10:54:45 +01:00
Elena Oresharova
dfe876e2cf Fix 'City Name' label for the Address 2019-12-03 16:17:20 +01:00
Elena Oresharova
9e2c7a0974 Add side effect of filling the fields of an address once we have a selection 2019-12-03 13:54:02 +01:00
Elena Oresharova
30b2854fac Merge branch 'D-kom-Demo' of https://github.com/SAP-samples/cloud-cap-samples into D-kom-Demo 2019-12-03 13:37:07 +01:00
Elena Oresharova
4ca7e425ec Add additional columns to the value list for Addresses 2019-12-03 13:32:50 +01:00
D065023
28a51f4837 Added Country and Postal Code 2019-12-03 10:58:53 +01:00
D065023
692a360065 minor 2019-12-02 12:01:38 +01:00
D065023
b3d9fdb8b3 refactor 2019-12-02 10:57:52 +01:00
D065023
e9d10986ff rm console.log 2019-12-02 10:35:53 +01:00
D065023
7191f61806 User mapping and refactoring 2019-12-02 10:35:22 +01:00
D065023
e688e7ecee value help 2019-11-30 14:39:00 +01:00
D065023
4a2139a5f2 Address lookup to mocked S/4HANA system
What's working:
- Address information on OP
- Reading of S/4HANA Addresses
- Translations
2019-11-30 14:17:17 +01:00
D065023
ed3ecd502f async style 2019-11-29 12:24:22 +01:00
D065023
8f3d112558 prettier 2019-11-29 12:11:29 +01:00
16 changed files with 6227 additions and 113 deletions

View File

@@ -0,0 +1,3 @@
{
"printWidth": 120
}

View File

@@ -0,0 +1,65 @@
# Bookshop With Address Data From SAP S/4HANA
This is an extended bookshop with business-partner address data from SAP S/4HANA.
When the user creates an order and uses the value help of the shipping address,
a synchronous request to SAP S/4HANA is triggered yielding all possible addresses
belonging to this business partner. Once an address is selected, its data
is replicated into a local database. To keep data in sync, an event handler
is registered which listens to all changes of business partners and updates the
local database table.
## Prerequisites
`@sap/cds` >= 1.30
## Running With Mocks
Just execute the following command in the `bookshop` folder.
```
cds run --in-memory --with-mocks
```
## Running With an S/4HANA Backend
To run your app in non-mock mode you need an SAP S/4HANA Cloud system and connect it to your SAP Cloud Platform. You can use the
[SAP Cloud Platform Extension Factory](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/346864df64f24011b49abee07bbd79af.html) to automate parts of this task. You need to enable synchronous APIs as well as events that are sent whenever business partners are changed.
To run the app locally, you need to create a `default-env.json` file in the `bookshop` folder containing the binding information (credentials of Enterprise Messaging as well as the destination to the business-partner service).
Provide the credentials in the `cds.requires` section of the `package.json` file in the `bookshop` folder, e.g.
```json
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external",
"credentials": {
"destination": "cap-api098"
}
},
"messaging": {
"kind": "enterprise-messaging",
"credentials": {
"prefix": "sap/S4HANAOD/c098/BO"
}
}
}
}
```
Here, `destination` is the destination of your business-partner service and `prefix` is the prefix
of the topic of the events.
Then simply run the following command in the `bookshop` folder.
```
cds run --in-memory
```
## User Flow
After starting the app, go to http://localhost:4004/fiori.html#Shell-home and open the app `Manage Orders` to create an order.
Use the value help of the shipping address to select an address. Create an order item and save the order.
Then change the address of your business partner (in the mocked case you can trigger the PATCH request in `req.http` ). Refresh
the object page of your order and see the change.

View File

@@ -9,5 +9,14 @@ Name = Name
AuthorName = Author's Name
Authors = Authors
Order = Order
OrderItems = Order Items
Orders = Orders
Price = Price
shippingAddress = Shipping Address
cityName = City Name
houseNumber = House Number
streetName = Street Name
postalCode = Postal Code
country = Country
AddressID = Address ID
contact = Contact

View File

@@ -72,3 +72,13 @@ annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>AuthorName}';
}
annotate my.Addresses with {
ID @title:'{i18n>AddressID}';
contact @title:'{i18n>contact}';
@readonly cityName @title:'{i18n>cityName}';
@readonly streetName @title:'{i18n>streetName}';
@readonly postalCode @title:'{i18n>postalCode}';
@readonly country @title:'{i18n>country}';
@readonly houseNumber @title:'{i18n>houseNumber}';
}

View File

@@ -1,117 +1,243 @@
using AdminService from '../../srv/admin-service';
annotate AdminService.Books with {
price @Common.FieldControl: #ReadOnly;
price @Common.FieldControl : #ReadOnly;
}
////////////////////////////////////////////////////////////////////////////
//
// Common
//
annotate AdminService.OrderItems with {
book @(
Common: {
Text: book.title,
FieldControl: #Mandatory
},
ValueList.entity:'Books',
);
amount @(
Common.FieldControl: #Mandatory
);
book @(
Common : {
Text : book.title,
FieldControl : #Mandatory
},
ValueList.entity : 'Books',
);
amount @(Common.FieldControl : #Mandatory);
}
annotate AdminService.Orders with {
shippingAddress @(Common : {
FieldControl : #Mandatory,
ValueList : {
CollectionPath : 'Addresses',
Label : 'Addresses',
SearchSupported : 'true',
Parameters : [
{
$Type : 'Common.ValueListParameterOut',
LocalDataProperty : 'shippingAddress_ID',
ValueListProperty : 'ID'
},
{
$Type : 'Common.ValueListParameterOut',
LocalDataProperty : 'shippingAddress_contact',
ValueListProperty : 'contact'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'postalCode'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'cityName'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'country'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'streetName'
},
{
$Type : 'Common.ValueListParameterDisplayOnly',
ValueListProperty : 'houseNumber'
},
]
},
});
}
annotate AdminService.Orders with @(
UI: {
////////////////////////////////////////////////////////////////////////////
//
// Lists of Orders
//
SelectionFields: [ createdAt, createdBy ],
LineItem: [
{Value: createdBy, Label:'Customer'},
{Value: createdAt, Label:'Date'}
],
////////////////////////////////////////////////////////////////////////////
//
// Order Details
//
HeaderInfo: {
TypeName: 'Order', TypeNamePlural: 'Orders',
Title: {
Label: 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
Value: OrderNo
},
Description: {Value: createdBy}
},
Identification: [ //Is the main field group
{Value: createdBy, Label:'Customer'},
{Value: createdAt, Label:'Date'},
{Value: OrderNo },
],
HeaderFacets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Created}', Target: '@UI.FieldGroup#Created'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Modified}', Target: '@UI.FieldGroup#Modified'},
],
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: 'Items/@UI.LineItem'},
],
FieldGroup#Details: {
Data: [
{Value: currency_code, Label:'Currency'}
]
},
FieldGroup#Created: {
Data: [
{Value: createdBy},
{Value: createdAt},
]
},
FieldGroup#Modified: {
Data: [
{Value: modifiedBy},
{Value: modifiedAt},
]
},
},
////////////////////////////////////////////////////////////////////////////
//
// UI
//
annotate AdminService.Orders with @(UI : {
////////////////////////////////////////////////////////////////////////////
//
// Lists of Orders
//
SelectionFields : [
createdAt,
createdBy
],
LineItem : [
{
Value : createdBy,
Label : 'Customer'
},
{
Value : createdAt,
Label : 'Date'
}
],
////////////////////////////////////////////////////////////////////////////
//
// Order Details
//
HeaderInfo : {
TypeName : 'Order',
TypeNamePlural : 'Orders',
Title : {
Label : 'Order number ', //A label is possible but it is not considered on the ObjectPage yet
Value : OrderNo
},
Description : {Value : createdBy}
},
Identification : [ //Is the main field group
// labels not considered
{
Value : createdBy,
Label : 'Customer'
},
{
Value : createdAt,
Label : 'Date'
},
{Value : OrderNo},
{
Value : 'shippingAddress_ID',
Label : 'Address ID'
}
],
HeaderFacets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Created}',
Target : '@UI.FieldGroup#Created'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Modified}',
Target : '@UI.FieldGroup#Modified'
},
],
Facets : [
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>shippingAddress}',
Target : '@UI.FieldGroup#ShippingAddress'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>Details}',
Target : '@UI.FieldGroup#Details'
},
{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>OrderItems}',
Target : 'Items/@UI.LineItem'
},
],
FieldGroup #Details : {Data : [{
Value : currency_code,
Label : 'Currency'
}]},
FieldGroup #Created : {Data : [
{Value : createdBy},
{Value : createdAt},
]},
FieldGroup #Modified : {Data : [
{Value : modifiedBy},
{Value : modifiedAt},
]},
FieldGroup #ShippingAddress : {Data : [
{
Value : shippingAddress_ID,
Label : '{i18n>shippingAddress}'
},
{
Value : shippingAddress.houseNumber,
Label : '{i18n>houseNumber}'
},
{
Value : shippingAddress.streetName,
Label : '{i18n>streetName}'
},
{
Value : shippingAddress.cityName,
Label : '{i18n>cityName}'
},
{
Value : shippingAddress.postalCode,
Label : '{i18n>postalCode}'
},
]},
},
Common.SideEffects : {
EffectTypes : #ValueChange,
SourceProperties : [shippingAddress_ID],
TargetProperties : [
shippingAddress.country,
shippingAddress.houseNumber,
shippingAddress.streetName,
shippingAddress.cityName,
shippingAddress.postalCode
]
},
) {
createdAt @UI.HiddenFilter:false;
createdBy @UI.HiddenFilter:false;
createdAt @UI.HiddenFilter : false;
createdBy @UI.HiddenFilter : false;
};
//The enity types name is AdminService.my_bookshop_OrderItems
//The annotations below are not generated in edmx WHY?
annotate AdminService.OrderItems with @(
UI: {
HeaderInfo: {
TypeName: 'Order Item', TypeNamePlural: ' ',
Title: {
Value: book.title
},
Description: {Value: book.descr}
},
// There is no filterbar for items so the selctionfileds is not needed
SelectionFields: [ book_ID ],
////////////////////////////////////////////////////////////////////////////
//
// Lists of OrderItems
//
LineItem: [
{Value: book_ID, Label:'Book'},
//The following entry is only used to have the assoication followed in the read event
{Value: book.price, Label:'Book Price'},
{Value: amount, Label:'Quantity'},
],
Identification: [ //Is the main field group
//{Value: ID, Label:'ID'}, //A guid shouldn't be on the UI
{Value: book_ID, Label:'Book'},
{Value: amount, Label:'Amount'},
],
Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>OrderItems}', Target: '@UI.Identification'},
],
},
);
annotate AdminService.OrderItems with @(UI : {
HeaderInfo : {
TypeName : 'Order Item',
TypeNamePlural : ' ',
Title : {Value : book.title},
Description : {Value : book.descr}
},
// There is no filterbar for items so the selctionfileds is not needed
SelectionFields : [book_ID],
////////////////////////////////////////////////////////////////////////////
//
// Lists of OrderItems
//
LineItem : [
{
Value : book_ID,
Label : 'Book'
},
//The following entry is only used to have the assoication followed in the read event
{
Value : book.price,
Label : 'Book Price'
},
{
Value : amount,
Label : 'Quantity'
},
],
Identification : [ //Is the main field group
//{Value: ID, Label:'ID'}, //A guid shouldn't be on the UI
{
Value : book_ID,
Label : 'Book'
},
{
Value : amount,
Label : 'Amount'
},
],
Facets : [{
$Type : 'UI.ReferenceFacet',
Label : '{i18n>OrderItems}',
Target : '@UI.Identification'
}, ],
}, );

View File

@@ -5,10 +5,43 @@
"license": "SAP SAMPLE CODE LICENSE",
"dependencies": {
"@sap/cds": "latest",
"express": "*"
"@sap/xb-msg-amqp-v100": "^0.9.31-SNAPSHOT",
"express": "*",
"passport": "^0.4.0",
"sqlite3": "^4.1.0"
},
"scripts": {
"start": "cds run --in-memory?",
"watch": "cds watch"
},
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external",
"--credentials": {
"destination": "cap-api098"
}
},
"--messaging": {
"kind": "enterprise-messaging",
"credentials": {
"prefix": "sap/S4HANAOD/c098/BO"
}
}
},
"auth": {
"passport": {
"strategy": "mock",
"users": {
"alice": {
"roles": [
"admin"
],
"ID": "ALICE"
}
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
PATCH http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ALICE',AddressID='62640')
Content-Type: application/json
Authorization: Basic QUxJQ0Utc2VjcmV0
{
"PostalCode": "123456",
"CityName": "AlteredTown"
}
###
GET http://localhost:4004/admin/Orders(ID=7e2f2640-6866-4dcf-8f4d-3027aa831cad,IsActiveEntity=false)?
&$expand=shippingAddress($select=cityName,houseNumber,postalCode,streetName)
Authorization: Basic QUxJQ0U6c2VjcmV0
###
DELETE http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ALICE',AddressID='62640')

View File

@@ -1,6 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema';
service AdminService @(_requires:'authenticated-user') {
service AdminService @(requires:'admin') {
entity Books as projection on my.Books;
entity Authors as projection on my.Authors;
entity Orders as select from my.Orders;

View File

@@ -0,0 +1,77 @@
const cds = require('@sap/cds')
module.exports = cds.service.impl(async () => {
// We are mashing up three services...
const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
const admin = await cds.connect.to('AdminService')
const db = await cds.connect.to('db')
// Using reflected definitions from connected services/database
const { Addresses: externalAddresses } = bupa.entities // projection on external addresses
const { Books, Addresses } = db.entities('sap.capire.bookshop') // entities in local database
// Delegate ValueHelp requests to S/4 backend, fetching current user's addresses from there
admin.on('READ', 'Addresses', req => {
console.log('Delegating to S/4 bupa service...')
const UsersAddresses = SELECT.from(externalAddresses)
.where({ contact: req.user.id })
.and(req.query.SELECT.where)
return bupa.tx(req).run(UsersAddresses)
})
// Replicate chosen addresses from S/4 when filling orders.
admin.before('PATCH', 'Orders', async req => {
const assigned = { ID: req.data.shippingAddress_ID, contact: req.user.id }
if (!assigned.ID) return //> something else
const local = db.transaction(req)
const [replica] = await local.read(Addresses).where(assigned)
if (replica) return //> already replicated
const [address] = await bupa.tx(req).run(SELECT.from(externalAddresses).where(assigned))
if (address) return local.create(Addresses).entries(address)
})
// Subscribe to S/4 event to update local replicas when sources change in S/4.
bupa.on('BusinessPartner/Changed', async msg => {
console.log('>> received:', msg.data)
const BuPaID = msg.data.KEY[0].BUSINESSPARTNER //> S/4HANA's weird payload format
const { SELECT, UPDATE } = cds.ql(msg) //> convenient alternative to <srv>.transaction(req).run(SELECT...)
// fetch affected entries from local replicas
const replicas = await SELECT.from(Addresses).where({ contact: BuPaID })
if (replicas.length === 0) return //> not affected
// fetch changed data from S/4 -> might be less than local due to deletes
const changed = await SELECT.from(externalAddresses).where({
contact: BuPaID,
ID: replicas.map(({ ID }) => ID)
})
// update local replicas with changes from S/4
const local = db.transaction(msg) //> using that variant to benefit from bulk runs
return local.run(changed.map(a => UPDATE(Addresses, a.ID).with(a)))
})
// Validate incoming orders and reduce books' stocks.
admin.before('CREATE', 'Orders', async req => {
const { Items } = req.data
// validate input...
if (!Items || Items.length === 0) return req.reject('Please order at least one item.')
if (!req.data.shippingAddress_ID) return req.reject('Please enter a valid shipping address.', 'shippingAddress_ID')
// reduce stock on ordered books...
const all = await db.tx(req).run(
Items.map(each =>
UPDATE(Books)
.where('ID =', each.book_ID)
.and('stock >=', each.amount)
.set('stock -=', each.amount)
)
)
all.forEach(
(affectedRows, i) =>
affectedRows > 0 || req.error(409, `${Items[i].amount} exceeds stock for book #${Items[i].book_ID}`)
)
})
})
require('./utils')

View File

@@ -2,25 +2,28 @@ const cds = require('@sap/cds')
const { Books } = cds.entities
/** Service implementation for CatalogService */
module.exports = cds.service.impl(function() {
this.after ('READ', 'Books', each => each.stock > 111 && _addDiscount2(each,11))
this.before ('CREATE', 'Orders', _reduceStock)
module.exports = cds.service.impl(function () {
this.after('READ', 'Books', each => each.stock > 111 && _addDiscount2(each, 11))
this.before('CREATE', 'Orders', _reduceStock)
})
/** Add some discount for overstocked books */
function _addDiscount2 (each,discount) {
function _addDiscount2 (each, discount) {
each.title += ` -- ${discount}% discount!`
}
/** Reduce stock of ordered books if available stock suffices */
async function _reduceStock (req) {
const { Items: OrderItems } = req.data
return cds.transaction(req) .run (()=> OrderItems.map (order =>
UPDATE (Books) .set ('stock -=', order.amount)
.where ('ID =', order.book_ID) .and ('stock >=', order.amount)
)) .then (all => all.forEach ((affectedRows,i) => {
if (affectedRows === 0) req.error (409,
`${OrderItems[i].amount} exceeds stock for book #${OrderItems[i].book_ID}`
const all = await cds.transaction(req).run(() =>
OrderItems.map(order =>
UPDATE(Books)
.set('stock -=', order.amount)
.where('ID =', order.book_ID)
.and('stock >=', order.amount)
)
}))
)
all.forEach((affectedRows, i) => {
if (affectedRows === 0) req.error(409, `${OrderItems[i].amount} exceeds stock for book #${OrderItems[i].book_ID}`)
})
}

View File

@@ -0,0 +1,39 @@
using { API_BUSINESS_PARTNER as external } from './external/API_BUSINESS_PARTNER.csn';
/**
* Tailor the imported API to our needs...
*/
extend service external with {
/**
* Simplified view on external addresses
*/
// @cds.persistence.skip: false
@mashup entity Addresses as projection on external.A_BusinessPartnerAddress {
key AddressID as ID,
key BusinessPartner as contact,
Country as country,
CityName as cityName,
PostalCode as postalCode,
StreetName as streetName,
HouseNumber as houseNumber
}
}
/**
* Add an entity to replicate external address data for quick access,
* e.g. when displaying lists of orders.
*/
@cds.persistence:{table,skip:false} //> create a table with the view's inferred signature
@cds.autoexpose //> auto-expose in services as targets for ValueHelps and joins
entity sap.capire.bookshop.Addresses as projection on external.Addresses;
/**
* Extend Orders with references to replicated external Addresses
*/
using { sap.capire.bookshop } from '../db/schema';
extend bookshop.Orders with {
shippingAddress : Association to bookshop.Addresses;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
module.exports = srv => {
srv.on(['CREATE', 'UPDATE', 'DELETE'], req => {
const payload = {
KEY: [{ BUSINESSPARTNER: req.data.BusinessPartner }]
}
console.log('<< emitting:', payload)
srv.emit('BusinessPartner/Changed', payload)
})
}

View File

@@ -0,0 +1,5 @@
BusinessPartner;AddressID;CityName;PostalCode;Country;StreetName;HouseNumber
ALICE;62640;Walldorf;69190;GER;Dietmer-Hopp-Allee;16
ALICE;62641;Berlin;69390;GER;Berlin-Street;19
BOB;62341;Karlsruhe;61390;GER;Karlsruhe-Street;19
anonymous;61321;Sometown;61290;GER;Sometown-Street;19
1 BusinessPartner AddressID CityName PostalCode Country StreetName HouseNumber
2 ALICE 62640 Walldorf 69190 GER Dietmer-Hopp-Allee 16
3 ALICE 62641 Berlin 69390 GER Berlin-Street 19
4 BOB 62341 Karlsruhe 61390 GER Karlsruhe-Street 19
5 anonymous 61321 Sometown 61290 GER Sometown-Street 19

View File

@@ -0,0 +1,27 @@
// Hack for SAP Application Studio
process.env['http_proxy'] = ''
process.env['https_proxy'] = ''
process.env['HTTP_PROXY'] = ''
process.env['HTTPS_PROXY'] = ''
const diff = (obj1, obj2) =>
Object.keys(obj1).reduce((res, curr) => (obj1[curr] === obj2[curr] ? res : (res[curr] = obj2[curr]) && res), {})
const queriesToUpdateDifferences = (entity, ownEntries, otherEntries) =>
ownEntries
.map(ownEntry => {
const otherEntry = otherEntries.find(otherEntry =>
Object.keys(entity.keys).reduce((res, curr) => res && otherEntry[curr] === ownEntry[curr], true)
)
if (otherEntry) {
const differences = diff(ownEntry, otherEntry)
if (Object.keys(differences).length) {
return UPDATE(entity)
.set(differences)
.where(Object.keys(entity.keys).reduce((res, curr) => (res[curr] = ownEntry[curr]) && res, {}))
}
}
})
.filter(el => el)
module.exports = { diff, queriesToUpdateDifferences }