From ddd02b52f29c492fb1e8f4c66622e4e2cc2af37a Mon Sep 17 00:00:00 2001 From: D065023 Date: Wed, 11 Dec 2019 11:12:57 +0100 Subject: [PATCH] eventing, renaming, subselecting --- packages/bookshop/app/_i18n/i18n.properties | 16 +- packages/bookshop/app/common.cds | 14 +- .../bookshop/app/orders/fiori-service.cds | 35 +-- packages/bookshop/db/schema.cds | 44 ++-- packages/bookshop/package.json | 19 +- packages/bookshop/req.http | 3 +- packages/bookshop/srv/admin-service.js | 244 +++++++++--------- .../srv/external/API_BUSINESS_PARTNER.js | 2 +- 8 files changed, 193 insertions(+), 184 deletions(-) diff --git a/packages/bookshop/app/_i18n/i18n.properties b/packages/bookshop/app/_i18n/i18n.properties index fd305545..23c28aaa 100644 --- a/packages/bookshop/app/_i18n/i18n.properties +++ b/packages/bookshop/app/_i18n/i18n.properties @@ -11,11 +11,11 @@ Authors = Authors Order = Order Orders = Orders Price = Price -ShippingAddress = Shipping Address -CityName = City Name -HouseNumber = House Number -StreetName = Street Name -PostalCode = Postal Code -Country = Country -AddressID = Address ID -BusinessPartner = Business Partner +shippingAddress = Shipping Address +cityName = City Name +houseNumber = House Number +streetName = Street Name +postalCode = Postal Code +country = Country +addressID = Address ID +businessPartner = Business Partner diff --git a/packages/bookshop/app/common.cds b/packages/bookshop/app/common.cds index b493514c..719301a7 100644 --- a/packages/bookshop/app/common.cds +++ b/packages/bookshop/app/common.cds @@ -74,11 +74,11 @@ annotate my.Authors with { } annotate my.ShippingAddresses with { - AddressID @title:'{i18n>AddressID}'; - BusinessPartner @title:'{i18n>BusinessPartner}'; - CityName @title:'{i18n>CityName}'; - StreetName @title:'{i18n>StreetName}'; - PostalCode @title:'{i18n>PostalCode}'; - Country @title:'{i18n>Country}'; - HouseNumber @title:'{i18n>HouseNumber}'; + addressID @title:'{i18n>addressID}'; + businessPartner @title:'{i18n>businessPartner}'; + cityName @title:'{i18n>cityName}'; + streetName @title:'{i18n>streetName}'; + postalCode @title:'{i18n>postalCode}'; + country @title:'{i18n>country}'; + houseNumber @title:'{i18n>houseNumber}'; } \ No newline at end of file diff --git a/packages/bookshop/app/orders/fiori-service.cds b/packages/bookshop/app/orders/fiori-service.cds index 06bf6c21..56cd16c6 100644 --- a/packages/bookshop/app/orders/fiori-service.cds +++ b/packages/bookshop/app/orders/fiori-service.cds @@ -29,23 +29,24 @@ annotate AdminService.Orders with { Label: 'Addresses', SearchSupported: 'true', Parameters: [ - { $Type: 'Common.ValueListParameterOut', LocalDataProperty: 'shippingAddress_AddressID', ValueListProperty: 'AddressID'}, - { $Type: 'Common.ValueListParameterOut', LocalDataProperty: 'shippingAddress_BusinessPartner', ValueListProperty: 'BusinessPartner'}, - { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'PostalCode'}, - { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'CityName'}, - { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'StreetName'}, - { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'HouseNumber'}, + { $Type: 'Common.ValueListParameterOut', LocalDataProperty: 'shippingAddress_addressID', ValueListProperty: 'addressID'}, + { $Type: 'Common.ValueListParameterOut', LocalDataProperty: 'shippingAddress_businessPartner', ValueListProperty: 'businessPartner'}, + { $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'}, ] }, SideEffects : { EffectTypes : #ValueChange, - SourceProperties : [shippingAddress_AddressID], + SourceProperties : [shippingAddress_addressID], TargetProperties : [ - shippingAddress.Country, - shippingAddress.HouseNumber, - shippingAddress.StreetName, - shippingAddress.CityName, - shippingAddress.PostalCode + shippingAddress.country, + shippingAddress.houseNumber, + shippingAddress.streetName, + shippingAddress.cityName, + shippingAddress.postalCode ] } } @@ -114,11 +115,11 @@ annotate AdminService.Orders with @( }, FieldGroup#ShippingAddress: { Data: [ - {Value: shippingAddress_AddressID, 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}'}, + {Value: shippingAddress_addressID, 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}'}, ] }, }, diff --git a/packages/bookshop/db/schema.cds b/packages/bookshop/db/schema.cds index fb16d362..10e9ac55 100644 --- a/packages/bookshop/db/schema.cds +++ b/packages/bookshop/db/schema.cds @@ -37,29 +37,23 @@ entity OrderItems : cuid { } // TODO: Use external information -// @cds.persistence.skip: false -// @cds.persistence.table -// entity ShippingAddresses as projection on extAddresses { -// key AddressID, -// key BusinessPartner, -// Country, -// CityName, -// PostalCode, -// StreetName, -// HouseNumber -// // key AddressID: String; -// // Country: String @readonly; -// // CityName: String @readonly; -// // PostalCode: String @readonly; -// // StreetName: String @readonly; -// // HouseNumber: String @readonly; -// } -entity ShippingAddresses { - key AddressID: String; - key BusinessPartner: String; - Country: String @readonly; - CityName: String @readonly; - PostalCode: String @readonly; - StreetName: String @readonly; - HouseNumber: String @readonly; +@cds.persistence.skip: false +@cds.persistence.table +entity ShippingAddresses as projection on extAddresses { + key AddressID as addressID, + key BusinessPartner as businessPartner, + Country as country, + CityName as cityName, + PostalCode as postalCode, + StreetName as streetName, + HouseNumber as houseNumber } +// entity ShippingAddresses { +// key AddressID: String; +// key BusinessPartner: String; +// Country: String @readonly; +// CityName: String @readonly; +// PostalCode: String @readonly; +// StreetName: String @readonly; +// HouseNumber: String @readonly; +// } diff --git a/packages/bookshop/package.json b/packages/bookshop/package.json index 9d63306c..fe698d9b 100644 --- a/packages/bookshop/package.json +++ b/packages/bookshop/package.json @@ -17,17 +17,28 @@ "requires": { "API_BUSINESS_PARTNER": { "kind": "odata", - "model": "srv/external/API_BUSINESS_PARTNER" + "model": "srv/external/API_BUSINESS_PARTNER", + "--credentials": { + "url": "http://www.google.com" + } }, "messaging": { - "kind": "enterprise-messaging" + "kind": "file-based-messaging" } }, "auth": { "passport": { "strategy": "mock", - "users": { "alice": { "roles": [ "admin" ], "password": "secret", "ID": "ALICE" } } + "users": { + "alice": { + "roles": [ + "admin" + ], + "password": "secret", + "ID": "ALICE" + } + } } } } -} +} \ No newline at end of file diff --git a/packages/bookshop/req.http b/packages/bookshop/req.http index 0be9cd90..e6e35cfc 100644 --- a/packages/bookshop/req.http +++ b/packages/bookshop/req.http @@ -1,5 +1,6 @@ -PATCH http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ANONYMOUS',AddressID='62640') +PATCH http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='ALICE',AddressID='62640') Content-Type: application/json +Authorization: Basic QUxJQ0Utc2VjcmV0 { "PostalCode": "123456" diff --git a/packages/bookshop/srv/admin-service.js b/packages/bookshop/srv/admin-service.js index e3520a5c..3275ad27 100644 --- a/packages/bookshop/srv/admin-service.js +++ b/packages/bookshop/srv/admin-service.js @@ -1,133 +1,135 @@ const cds = require('@sap/cds') -const { Books, ShippingAddresses, Orders } = cds.entities -const RELEVANT_ADDRESS_COLUMNS = [ - 'AddressID', - 'BusinessPartner', - 'CityName', - 'StreetName', - 'PostalCode', - 'Country', - 'HouseNumber' -] +const { Books, ShippingAddresses } = cds.entities const bupaSrv = cds.connect.to('API_BUSINESS_PARTNER') -const messagingSrv = cds.connect.to('messaging') -messagingSrv.on('sap/messaging/ccf/BO/BusinessPartner/Changed', async msg => { +const _diff = (obj1, obj2) => + Object.keys(obj1).reduce( + (res, curr) => + obj1[curr] === obj2[curr] ? res : (res[curr] = obj2[curr]) && res, + {} + ) + +const _qlsToUpdateDifferences = (ownAddresses, remoteAddresses) => + ownAddresses + .map(ownAddress => { + const remoteAddress = remoteAddresses.find( + address => + address.businessPartner === ownAddress.businessPartner && + address.addressID === ownAddress.addressID + ) + if (remoteAddress) { + const diff = _diff(ownAddress, remoteAddress) + console.log('changing', diff) + return ( + Object.keys(diff).length && + UPDATE(ShippingAddresses) + .set(diff) + .where({ + businessPartner: ownAddress.businessPartner, + addressID: ownAddress.addressID + }) + ) + } + return DELETE(ShippingAddresses).where({ + businessPartner: ownAddress.businessPartner, + addressID: ownAddress.addressID + }) + }) + .filter(el => el) + +bupaSrv.on('sap/messaging/ccf/BO/BusinessPartner/Changed', async msg => { console.log('>> Message:', msg.data) - const BusinessPartner = msg.data.KEY[0].BUSINESSPARTNER - // TODO: Remove toLower hack. - // Every BusinessPartner from S/4HANA is UPPERCASE. - const ownOrders = await cds.run(SELECT.from(Orders).where('createdBy like', BusinessPartner)) - console.log(ownOrders) - // const ownAddresses = await cds.run( - // SELECT(['AddressID']) - // .from(ShippingAddresses) - // .where({ BusinessPartner: businessPartner }) - // ) - // if (ownAddresses && ownAddresses.length > 0) { - // console.log('found business partner', businessPartner) - // } - // const tx = bupaSrv.transaction() - // const remoteAddresses = await Promise.all( - // ownAddresses.map(addressResult => { - // return tx.run( - // SELECT.one.from('API_BUSINESS_PARTNER.A_BusinessPartnerAddress') - // .columns(RELEVANT_ADDRESS_COLUMNS) - // .where({ - // AddressID: addressResult.AddressID, - // BusinessPartner: businessPartner - // }) - // ) - // }) - // ) - // console.log('addresses found:', remoteAddresses) - // await Promise.all(remoteAddresses.map(address => { - // console.log('updating', address) - // if (address) { - // return cds.run(UPDATE(ShippingAddresses).set(address)) - // } - // })) + const businessPartner = msg.data.KEY[0].BUSINESSPARTNER + const tx = cds.transaction() + const selectQl = SELECT.from(ShippingAddresses).where({ businessPartner }) + const selectQlToBeDeleted = SELECT.from(ShippingAddresses).where({ + businessPartner + }) + + const ownAddresses = await tx.run(selectQl) + await tx.commit() + console.log('own:', ownAddresses) + if (ownAddresses && ownAddresses.length > 0) { + console.log('found') + const txExt = bupaSrv.transaction() + const remoteAddresses = await txExt.run(selectQlToBeDeleted) + + await _qlsToUpdateDifferences(ownAddresses, remoteAddresses).map(ql => + tx.run(ql) + ) + await tx.commit() + } }) -/** Service implementation for CatalogService */ module.exports = cds.service.impl(function () { + async function _readAddresses (req) { + console.log('Addresses', ShippingAddresses) + const businessPartner = req.user.id + const tx = bupaSrv.transaction(req) + const ql = SELECT.from(ShippingAddresses).where({ + businessPartner + }) + if (req.query && req.query.SELECT && req.query.SELECT.columns) { + ql.columns(req.query.SELECT.columns) + } + if (req.query && req.query.SELECT && req.query.SELECT.where) { + ql.where(req.query.SELECT.where) + } + + const result = await tx.run(ql) + delete result.businessPartner + return result + } + + async function _fillAddress (req) { + if (req.data.shippingAddress_addressID) { + const businessPartner = req.user.id + const tx = bupaSrv.transaction(req) + const response = await tx.run( + SELECT.from(ShippingAddresses).where({ + addressID: req.data.shippingAddress_addressID, + businessPartner + }) + ) + if (response && response.length > 0) { + const tx2 = cds.transaction(req) + try { + const qlStatement = INSERT.into(ShippingAddresses).entries(response) + await tx2.run(qlStatement) + } catch (e) { + // already in there + } + } else { + req.error('Shipping address not found.') + } + } + } + + 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 + }` + ) + }) + } + } + this.before('CREATE', 'Orders', _reduceStock) this.before('PATCH', 'Orders', _fillAddress) this.on('READ', 'Addresses', _readAddresses) }) - -async function _readAddresses (req) { - const businessPartner = req.user.id - if (!businessPartner) { - return req.reject('You need to be authorized.') - } - const tx = bupaSrv.transaction(req) - const ql = SELECT.from('API_BUSINESS_PARTNER.A_BusinessPartnerAddress').where( - { BusinessPartner: businessPartner } - ) - if (req.query && req.query.SELECT && req.query.SELECT.columns) { - ql.columns(req.query.SELECT.columns) - } else { - ql.columns(RELEVANT_ADDRESS_COLUMNS) - } - if (req.query && req.query.SELECT && req.query.SELECT.where) { - ql.where(req.query.SELECT.where) - } - const result = await tx.run(ql) - delete result.BusinessPartner - return result -} - -/** Fill Address data from external service */ -async function _fillAddress (req) { - if (req.data.shippingAddress_AddressID) { - const businessPartner = req.user.id - if (!businessPartner) { - return req.reject('You need to be authorized.') - } - const tx = bupaSrv.transaction(req) - const response = await tx.run( - SELECT.from('API_BUSINESS_PARTNER.A_BusinessPartnerAddress') - .columns(RELEVANT_ADDRESS_COLUMNS) - .where({ - AddressID: req.data.shippingAddress_AddressID, - BusinessPartner: businessPartner - }) - ) - if (response && response.length > 0) { - const tx2 = cds.transaction(req) - try { - await tx2.run(INSERT.into(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 - }` - ) - }) - } -} diff --git a/packages/bookshop/srv/external/API_BUSINESS_PARTNER.js b/packages/bookshop/srv/external/API_BUSINESS_PARTNER.js index 951d0290..b3228755 100644 --- a/packages/bookshop/srv/external/API_BUSINESS_PARTNER.js +++ b/packages/bookshop/srv/external/API_BUSINESS_PARTNER.js @@ -3,7 +3,7 @@ module.exports = srv => { srv.on('UPDATE', req => { const payload = { - KEY: [{ BUSINESSPARTNER: req.user.id }] + KEY: [{ BUSINESSPARTNER: req.data.BusinessPartner }] } console.log('<< Message:', payload) srv.emit('sap/messaging/ccf/BO/BusinessPartner/Changed', payload)