Address lookup to mocked S/4HANA system

What's working:
- Address information on OP
- Reading of S/4HANA Addresses
- Translations
This commit is contained in:
D065023
2019-11-30 14:17:17 +01:00
parent ed3ecd502f
commit 4a2139a5f2
13 changed files with 5892 additions and 18 deletions

2
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"type": "node", "type": "node",
"runtimeExecutable": "npx", "runtimeExecutable": "npx",
"runtimeArgs": ["-n"], "runtimeArgs": ["-n"],
"args": ["--", "cds", "run", "--with-mocks", "--in-memory?"], // the leading "--" arg ensures it works with as well as without debugging "args": ["--", "cds", "run", "--with-mocks", "--in-memory"], // the leading "--" arg ensures it works with as well as without debugging
"cwd": "${workspaceFolder}/packages/${input:service}", "cwd": "${workspaceFolder}/packages/${input:service}",
"console": "integratedTerminal", "console": "integratedTerminal",
"serverReadyAction": { "serverReadyAction": {

View File

@@ -11,3 +11,6 @@ Authors = Authors
Order = Order Order = Order
Orders = Orders Orders = Orders
Price = Price Price = Price
ShippingAddress = Shipping Address
HouseNumber = House Number
StreetName = Street Name

View File

@@ -72,3 +72,10 @@ annotate my.Authors with {
ID @title:'{i18n>ID}' @UI.HiddenFilter; ID @title:'{i18n>ID}' @UI.HiddenFilter;
name @title:'{i18n>AuthorName}'; name @title:'{i18n>AuthorName}';
} }
annotate my.ShippingAddresses with {
AddressID @title:'{i18n>AddressID}';
CityName @title:'{i18n>CityName}';
StreetName @title:'{i18n>StreetName}';
HouseNumber @title:'{i18n>HouseNumber}';
}

View File

@@ -52,6 +52,7 @@ annotate AdminService.Orders with @(
HeaderFacets: [ HeaderFacets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Created}', Target: '@UI.FieldGroup#Created'}, {$Type: 'UI.ReferenceFacet', Label: '{i18n>Created}', Target: '@UI.FieldGroup#Created'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Modified}', Target: '@UI.FieldGroup#Modified'}, {$Type: 'UI.ReferenceFacet', Label: '{i18n>Modified}', Target: '@UI.FieldGroup#Modified'},
{$Type: 'UI.ReferenceFacet', Label: '{i18n>ShippingAddress}', Target: '@UI.FieldGroup#ShippingAddress'},
], ],
Facets: [ Facets: [
{$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'}, {$Type: 'UI.ReferenceFacet', Label: '{i18n>Details}', Target: '@UI.FieldGroup#Details'},
@@ -74,6 +75,14 @@ annotate AdminService.Orders with @(
{Value: modifiedAt}, {Value: modifiedAt},
] ]
}, },
// TODO: Trigger side effects when `shippingAddress_AddressID` is changed
FieldGroup#ShippingAddress: {
Data: [
{Value: shippingAddress_AddressID, Label:'{i18n>ShippingAddress}'},
{Value: shippingAddress.HouseNumber, Label:'{i18n>HouseNumber}'},
{Value: shippingAddress.StreetName, Label:'{i18n>StreetName}'}
]
},
}, },
) { ) {
createdAt @UI.HiddenFilter:false; createdAt @UI.HiddenFilter:false;

View File

@@ -1,5 +1,6 @@
namespace sap.capire.bookshop; namespace sap.capire.bookshop;
using { Currency, managed, cuid } from '@sap/cds/common'; using { Currency, managed, cuid } from '@sap/cds/common';
using { API_BUSINESS_PARTNER.A_BusinessPartnerAddress as extAddresses } from '../srv/external/API_BUSINESS_PARTNER.csn';
entity Books : managed { entity Books : managed {
key ID : Integer; key ID : Integer;
@@ -26,6 +27,7 @@ entity Orders : cuid, managed {
Items : Composition of many OrderItems on Items.parent = $self; Items : Composition of many OrderItems on Items.parent = $self;
total : Decimal(9,2) @readonly; total : Decimal(9,2) @readonly;
currency : Currency; currency : Currency;
shippingAddress : Association to one ShippingAddresses; // TODO: Composition or Association?
} }
entity OrderItems : cuid { entity OrderItems : cuid {
parent : Association to Orders; parent : Association to Orders;
@@ -33,3 +35,11 @@ entity OrderItems : cuid {
amount : Integer; amount : Integer;
netAmount : Decimal(9,2); netAmount : Decimal(9,2);
} }
// TODO: Use external information
entity ShippingAddresses {
key AddressID: String;
CityName: String @readonly;
StreetName: String @readonly;
HouseNumber: String @readonly;
}

View File

@@ -5,9 +5,26 @@
"license": "SAP SAMPLE CODE LICENSE", "license": "SAP SAMPLE CODE LICENSE",
"dependencies": { "dependencies": {
"@sap/cds": "latest", "@sap/cds": "latest",
"express": "*" "express": "*",
"sqlite3": "^4.1.0"
}, },
"scripts": { "scripts": {
"start": "npx cds run" "start": "npx cds run"
},
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external/API_BUSINESS_PARTNER"
},
"db": {
"kind": "sqlite",
"model": [
"db/",
"srv/",
"app/"
]
}
}
} }
} }

View File

@@ -4,6 +4,7 @@ service AdminService @(_requires:'authenticated-user') {
entity Books as projection on my.Books; entity Books as projection on my.Books;
entity Authors as projection on my.Authors; entity Authors as projection on my.Authors;
entity Orders as select from my.Orders; entity Orders as select from my.Orders;
entity Addresses as projection on my.ShippingAddresses;
} }
// Enable Fiori Draft for Orders // Enable Fiori Draft for Orders

View File

@@ -0,0 +1,67 @@
const cds = require('@sap/cds')
const { Books, ShippingAddresses } = cds.entities
const bupaSrv = cds.connect.to('API_BUSINESS_PARTNER')
/** Service implementation for CatalogService */
module.exports = cds.service.impl(function () {
this.before('CREATE', 'Orders', _reduceStock)
// this.before('CREATE', 'Orders', _fillAddress)
this.before('PATCH', 'Orders', _fillAddress)
this.on('READ', 'Addresses', _readAddresses)
})
function _readAddresses (req) {
// TODO: Delegate to external service
}
/** Fill Address data from external service */
async function _fillAddress (req) {
console.log('retrieving addresses')
if (req.data.shippingAddress_AddressID) {
const tx = bupaSrv.transaction(req)
const response = await tx.run(
SELECT.from('API_BUSINESS_PARTNER.A_BusinessPartnerAddress')
.columns('AddressID', 'CityName', 'StreetName', 'HouseNumber')
.where({ AddressID: req.data.shippingAddress_AddressID })
)
if (response && response.length > 0) {
console.log('filling addresses')
const tx2 = cds.transaction(req)
try {
await tx2.run(
INSERT.into('sap.capire.bookshop.ShippingAddresses').entries(response)
)
} catch (e) {
// already in there
}
} else {
req.error('Shipping address not found.')
}
}
}
/** Reduce stock of ordered books if available stock suffices */
async function _reduceStock (req) {
const { Items: OrderItems } = req.data
if (OrderItems && OrderItems.length > 0) {
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

@@ -1,5 +1,6 @@
using { sap.capire.bookshop as my } from '../db/schema'; using { sap.capire.bookshop as my } from '../db/schema';
@path:'/browse' @path:'/browse'
service CatalogService { service CatalogService {
@@ -9,5 +10,6 @@ service CatalogService {
@requires_: 'authenticated-user' @requires_: 'authenticated-user'
@insertonly entity Orders as projection on my.Orders; @insertonly entity Orders as projection on my.Orders;
entity Addresses as projection on my.ShippingAddresses;
} }

View File

@@ -1,5 +1,7 @@
const cds = require('@sap/cds') const cds = require('@sap/cds')
const { Books } = cds.entities const { Books, ShippingAddresses } = cds.entities
const bupaSrv = cds.connect.to('API_BUSINESS_PARTNER')
/** Service implementation for CatalogService */ /** Service implementation for CatalogService */
module.exports = cds.service.impl(function () { module.exports = cds.service.impl(function () {
@@ -9,32 +11,64 @@ module.exports = cds.service.impl(function () {
each => each.stock > 111 && _addDiscount2(each, 11) each => each.stock > 111 && _addDiscount2(each, 11)
) )
this.before('CREATE', 'Orders', _reduceStock) this.before('CREATE', 'Orders', _reduceStock)
this.before('CREATE', 'Orders', _fillAddress)
this.on('READ', 'Addresses', _readAddresses)
// this.after('READ', 'Orders', (data, req) => {
// if (req.query.SELECT.columns.includes('shippingAddress')) {
// data.shippingAddress = _readAddresses()
// }
// })
}) })
function _readAddresses (req) {
// TODO: Delegate to external service
return [
{ AddressID: 'add1', CityName: 'Walldorf', StreetName: 'ExampleStreet' },
{ AddressID: 'add2', CityName: 'Schwetzingen', StreetName: 'BestStreet' }
]
}
/** Add some discount for overstocked books */ /** Add some discount for overstocked books */
function _addDiscount2(each, discount) { function _addDiscount2 (each, discount) {
each.title += ` -- ${discount}% discount!` each.title += ` -- ${discount}% discount!`
} }
/** Reduce stock of ordered books if available stock suffices */ async function _fillAddress (req) {
async function _reduceStock(req) { console.log('filling addresses')
const { Items: OrderItems } = req.data if (req.data.shippingAdress_AddressID) {
const all = await cds const tx = bupaSrv.transaction(req)
.transaction(req) const result = tx.run(
.run(() => SELECT.one(['AdressID, CityName, StreetName, HouseNumber'])
OrderItems.map(order => .from('A_BusinessPartnerAddress')
UPDATE(Books) .where({ AddressID: req.data.shippingAdress_AddressID })
.set('stock -=', order.amount)
.where('ID =', order.book_ID)
.and('stock >=', order.amount)
)
) )
if (result) {
const tx2 = cds.transaction(req)
console.log('filling addresses for real')
tx2.run(INSERT.into(ShippingAddresses).entries([result]))
}
}
}
/** Reduce stock of ordered books if available stock suffices */
async function _reduceStock (req) {
const { Items: OrderItems } = req.data
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) => { all.forEach((affectedRows, i) => {
if (affectedRows === 0) if (affectedRows === 0)
req.error( req.error(
409, 409,
`${OrderItems[i].amount} exceeds stock for book #${ `${OrderItems[i].amount} exceeds stock for book #${
OrderItems[i].book_ID OrderItems[i].book_ID
}` }`
) )
}) })

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,37 @@
module.exports = db => {
const { A_BusinessPartnerAddress: Addresses } = db.entities(
'API_BUSINESS_PARTNER'
)
DELETE.from(Addresses)
INSERT.into(Addresses).entries(
{
BusinessPartner: 'anonymous',
AddressID: '28238',
CityName: 'Walldorf',
StreetName: 'Dietmar-Hopp-Allee',
HouseNumber: '123'
},
{
BusinessPartner: '1003765',
AddressID: '28241',
CityName: 'Palo Alto',
StreetName: 'Hillview Avenue',
HouseNumber: '26'
},
{
BusinessPartner: '1003766',
AddressID: '28244',
CityName: 'Hallbergmoos',
StreetName: 'Zeppelinstraße',
HouseNumber: '93'
},
{
BusinessPartner: '1003767',
AddressID: '28247',
CityName: 'Potsdam',
StreetName: 'Konrad-Zuse-Ring',
HouseNumber: '29'
}
)
}