RAP - Komplexe Entität
Schauen wir uns einmal eine RAP Anwendung mit zwei Entitäten an und wie diese miteinander verbunden sind. Dabei gehen wir vor allem auf die Unterschiede ein.
Inhaltsverzeichnis
Bereits vor einer Weile haben wir uns eine einfache Entität angeschaut und wie wir diese komplett auf RAP aufbauen. In diesem Artikel schauen wir uns einmal an, was die Unterschiede sind, wenn wir mehr als eine Entität in eine Anwendung einbauen. Wenn du mehr zum Aufbau von einfachen Entitäten wissen willst, dann beginne am besten am Anfang der Serie.
Einleitung
Wir hatten uns bereits intensiv angeschaut, wie wir eine RAP Anwendung anhand einer Entität aufbauen und was wir alles benötigen. In dieser kleinen Serie werden wir uns den Aufbau von komplexen Anwendungen anschauen, in dem wir Rechnungen und Positionen in einer Anwendung zusammenführen. Dabei werden wir wieder mit dem Datenmodell auf unserer CDS-Reihe arbeiten.
Datenmodell
Wir möchten die beiden Entitäten Rechnung und Position zusammen in eine App bringen und bearbeitbar machen, dazu sieht das Datenmodell wie folgt aus:
Die Entität für Material benötigen wir, da die Mengeneinheit benötigt wird und diese nur am Material verfügbar ist. Wie immer erzeugen wir dazu eine Interface Schicht und setzen für die Applikation den Consumption Layer oben drauf.
CDS
Die erste Anpassung gibt es bereits auf der Ebene der Datenmodellierung. Zwischen den Entitäten benötigen wir Verknüpfungen, da wir später von den Rechnungen auf die Positionen navigieren wollen und weil diese beiden Objekte zusammengehören. Schauen wir uns dazu die beiden Schichten einmal genauer an.
Interface
Auf der Ebene der Interfaces verbinden wir die Rechnungen mit den Positionen. Dazu legen wir vom Root Knoten eine "Composition" Richtung Position an, der View sieht damit wie folgt aus:
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for ZBS_DMO_INVOICE'
define root view entity ZBS_R_RAPCInvoice
as select from zbs_dmo_invoice
composition [0..*] of ZBS_I_RAPCPosition as _Position
{
key document as Document,
doc_date as DocDate,
doc_time as DocTime,
partner as Partner,
_Position
}
Auf Positionsebene müssen wir nun eine "Association To Parent" anlegen, damit machen wir deutlich, dass hier eine Beziehung nach oben existiert. Die zweite Assoziation ist für die Einheit, die wir für die Daten benötigen.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Interface for ZBS_DMO_POSITION'
define view entity ZBS_I_RAPCPosition
as select from zbs_dmo_position
association to parent ZBS_R_RAPCInvoice as _Invoice on $projection.Document = _Invoice.Document
association [1] to ZBS_I_RAPCMaterial as _Material on $projection.Material = _Material.Material
{
key document as Document,
key pos_number as PositionNumber,
material as Material,
@Semantics.quantity.unitOfMeasure : 'Unit'
quantity as Quantity,
_Material.StockUnit as Unit,
price as Price,
currency as Currency,
_Invoice
}
Consumption
Auf der Consumption Ebene sieht es so ähnlich aus, hier werden aber die Beziehungen anders definiert. Dazu fügen wir hinter der Assoziation einen "Redirect to Composition Child" in der Wurzel-Entität ein.
@EndUserText.label: 'Consumption for ZBS_R_RAPCINVOICE'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define root view entity ZBS_C_RAPCInvoice
provider contract transactional_query
as projection on ZBS_R_RAPCInvoice as Invoice
{
key Document,
DocDate,
DocTime,
Partner,
_Position : redirected to composition child ZBS_C_RAPCPosition
}
Bei den Positionen benötigen wir einen "Redirect To Parent", also zur Rechnung. Die Beziehungen dienen im späteren Service für die Navigation zwischen den Entitäten.
@EndUserText.label: 'Consumption for ZBS_I_RAPCPOSITION'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@Metadata.allowExtensions: true
define view entity ZBS_C_RAPCPosition
as projection on ZBS_I_RAPCPosition as Position
{
key Document,
key PositionNumber,
Material,
Quantity,
Unit,
Price,
Currency,
_Invoice : redirected to parent ZBS_C_RAPCInvoice
}
Verhaltensdefinition
Wie sieht es nun mit der Verwaltung der Verhaltensimplementierung aus? Hier benötigst du zwei Definitionen, für jede Entität eine. Besonderheiten die man hier beachten sollte wären:
- Anlage der Position geschieht über die Assoziation der Rechnung und nicht direkt über die Position.
- Assoziationen werden in den Entitäten bekannt gegeben.
- Sperren und berechtigen auf die Daten der Position, geschieht über die Rechnung.
- Die Verhaltensimplementierung kann pro Entität implementiert werden oder über eine Klasse im Kopf der Verhaltensdefinition.
Die Verhaltensdefinition sieht damit wie folgt aus:
managed implementation in class zbp_bs_rapcinvoice unique;
strict ( 1 );
define behavior for ZBS_R_RAPCInvoice alias Invoice
persistent table zbs_dmo_invoice
lock master
authorization master ( instance )
{
create;
update;
delete;
field ( readonly:update ) Document;
association _Position { create; }
mapping for zbs_dmo_invoice
{
Document = document;
Partner = partner;
DocDate = doc_date;
DocTime = doc_time;
}
}
define behavior for ZBS_I_RAPCPosition alias Position
persistent table zbs_dmo_position
lock dependent by _Invoice
authorization dependent by _Invoice
{
update;
delete;
field ( readonly ) Document;
field ( readonly:update ) PositionNumber;
association _Invoice;
mapping for zbs_dmo_position
{
Document = document;
PositionNumber = pos_number;
Material = material;
Price = price;
Quantity = quantity;
Currency = currency;
}
}
Das Handling der Assoziationen siehst du aber am Besten in der Projektion der Verhaltensdefinition:
projection;
strict ( 1 );
define behavior for ZBS_C_RAPCInvoice alias Invoice
{
use create;
use update;
use delete;
use association _Position { create; }
}
define behavior for ZBS_C_RAPCPosition alias Position
{
use update;
use delete;
use association _Invoice;
}
Service
Die Service Definition und die Service Bindung kann dann wie gewohnt angelegt werden, es sollten beide Entitäten mitgegeben werden, damit der Zugriff dafür verfügbar ist. Nach der Anlage der Bindung sollten die beiden Entitäten sichtbar sein, sowie die Verbindungen untereinander.
App
Die Anwendung an sich besitzt erst einmal keine Design, Felder oder Einschränkungsmöglichkeiten. Über den Fiori Elements Preview kannst du aber wie bisher, ein Grundlayout definieren, zumindest um einmal testen zu können. Eine Navigation auf die Positionen ist damit aktuell nicht möglich, dazu müssen wir erst die UI Annotationen anlegen.
GitHub
Die Änderungen haben wir wie immer im GitHub Repository zur Verfügung gestellt. Der Commit mit den Anpassungen hat neue Objekte im Paket ZBS_DEMO_RAP_COMPLEXE angelegt, wenn du nicht alles durchsuchen möchtest.
Fazit
Der Aufbau der Anwendung ist so ähnlich wie bei unserer einfachen Anwendung, allerdings sind ein paar Dinge zu beachten, zum Beispiel, dass die einzelnen Entitäten untereinander verknüpft sind. Alle Änderungen findest du wieder wie gewohnt im GitHub Repository.