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:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -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": {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}';
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
67
packages/bookshop/srv/admin-service.js
Normal file
67
packages/bookshop/srv/admin-service.js
Normal 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
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
2426
packages/bookshop/srv/external/API_BUSINESS_PARTNER.csn
vendored
Normal file
2426
packages/bookshop/srv/external/API_BUSINESS_PARTNER.csn
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3261
packages/bookshop/srv/external/API_BUSINESS_PARTNER.edmx
vendored
Normal file
3261
packages/bookshop/srv/external/API_BUSINESS_PARTNER.edmx
vendored
Normal file
File diff suppressed because it is too large
Load Diff
37
packages/bookshop/srv/init.js
Normal file
37
packages/bookshop/srv/init.js
Normal 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'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user