Build a SAP Fiori App using RAP Cloud Environment
Desarrollo de un servicio OData para una aplicación FIORI (Managed).
Objetivo
Crear una aplicación CRUD con nuestra propia tabla con referencia al escenario FLIGHT usando el modelo RestFul Application programming model en Eclipse utilizando ABAP Development Tools.
Prerequisitos
- Una cuenta trial SAP Cloud BTP.
- Instalar Eclipse y Abap Development Tools.
- Una cuenta ABAP Environment Cloud tial.
Pasos
- Crear persistencia de datos (tabla).
- Agregar datos demo a la tabla.
- Generar objetos de repositorio ABAP.
- Visualizar Aplicación (Preview).
- Agregar filtros de búsqueda.
- Agregar númeración interna.
- Visualizar Aplicación (Preview).
1.- Crear la persistencia de datos
Seleccionar el paquete de la aplicación.
En el menú de contexto seleccionar New -> Other ABAP Repository Object.
Seleccionar Database Table. Presionar Next.
Capturar nombre y descripción de la tabla a crear. Presionar Next.
Seleccionar un Transport Request. Presionar Finish.
Reemplazar la propuesta con el siguiente código.
Grabar (Ctrl+S) y Activar (CTRL+F3).
A todo código se le puede dar formato con Shift + F1.
@EndUserText.label : 'Travel data'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zrap100_travel_m {
key client : abap.clnt not null;
key travel_id : /dmo/travel_id not null;
agency_id : /dmo/agency_id;
customer_id : /dmo/customer_id;
begin_date : /dmo/begin_date;
end_date : /dmo/end_date;
@Semantics.amount.currencyCode : 'zrap100_travel_m.currency_code'
booking_fee : /dmo/booking_fee;
@Semantics.amount.currencyCode : 'zrap100_travel_m.currency_code'
total_price : /dmo/total_price;
currency_code : /dmo/currency_code;
description : /dmo/description;
overall_status : /dmo/overall_status;
attachment : /dmo/attachment;
mime_type : /dmo/mime_type;
file_name : /dmo/filename;
created_by : abp_creation_user;
created_at : abp_creation_tstmpl;
local_last_changed_by : abp_locinst_lastchange_user;
local_last_changed_at : abp_locinst_lastchange_tstmpl;
last_changed_at : abp_lastchange_tstmpl;
}
2.- Añadir datos de prueba.
Crear una clase para generar automáticamente datos en la tabla creada.
Seleccionar el paquete de la aplicación.
Seleccionar del menú contextual la opción New -> ABAP Class
Capturar un nombre de clase y su descripción.
Presionar Next.
Seleccionar un Transport Request.
Presionar Finish.
Reemplazar el código predetrminado de la clase por el siguiente.
CLASS zcl_rap100_gen_data_dev_100 DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_rap100_gen_data_dev_100 IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
group_id TYPE string VALUE 'dev_100',
attachment TYPE /dmo/attachment,
file_name TYPE /dmo/filename,
mime_type TYPE /dmo/mime_type.
* clear data
DELETE FROM zrap100_travel_m.
*DELETE FROM zrap100_dtravel.
"insert travel demo data
INSERT zrap100_travel_m FROM (
SELECT
FROM /dmo/travel AS travel
FIELDS
travel~travel_id AS travel_id,
travel~agency_id AS agency_id,
travel~customer_id AS customer_id,
travel~begin_date AS begin_date,
travel~end_date AS end_date,
travel~booking_fee AS booking_fee,
travel~total_price AS total_price,
travel~currency_code AS currency_code,
travel~description AS description,
CASE travel~status "[N(New) | P(Planned) | B(Booked) | X(Cancelled)]
WHEN 'N' THEN 'O'
WHEN 'P' THEN 'O'
WHEN 'B' THEN 'A'
ELSE 'X'
END AS overall_status,
@attachment AS attachment,
@mime_type AS mime_type,
@file_name AS file_name,
travel~createdby AS created_by,
travel~createdat AS created_at,
travel~lastchangedby AS last_changed_by,
travel~lastchangedat AS last_changed_at,
travel~lastchangedat AS local_last_changed_at
WHERE travel_id <> '00000000'
ORDER BY travel_id UP TO 10 ROWS
).
COMMIT WORK.
out->write( |[RAP100] Demo data generated for table ZRAP100_TRAVEL_M. | ).
ENDMETHOD.
ENDCLASS.
Grabar (Ctrl+S) y Activar (CTRL+F3).
Ejecutar la clase creada con la opción Run as -> 1 ABAP Application (Console) ó F9.
Seleccionar la tabla y en el menú contextual la opción Open With -> Data Preview
Se visualizarán los datos demo cargados para pruebas.
3.- Generar los objetos de repositorio ABAP
Seleccionar la tabla creada.
En el menú de contexto seleccionar Generate ABAP Repository Objects…
Seleccionar la opción OData UI Services. Presionar Next.
Presionar Next.
Capturar una descripción (opcional). Presionar Next.
Seleccionar el Business Object Data Model y asignarle un alias.
Presionar Next.
Seleccionar un Transport Request. Presionar Finish.
Los objetos se habrán creado en el paquete seleccionado para la aplicación.
4.- Visualizar la aplicación.
Aparecerá en pantalla el objeto Service Binding.
Presionar el botón Publish.
Seleccionar la Entidad Travel. Presionar Preview.
Autentificarse si es requerido.
Tendremos hasta aquí una aplicación CRUD (Create, Read, Update, Delete) con manejo de draft.
Presionar Go.
Aparecerán los datos demo cargados a la tabla para pruebas.
Sin embargo la aplicación carece de filtros, los campos no tienen ayudas de captura y nos pide el Id del registro a crear (númeración externa) que eso en la práctica no es común.
A continuación se harán esos ajustes esenciales: Filtros, ayudas y númeración interna.
Nota: Al ajustar cualquier código no olvidar grabar y activar .
5.- Agregar filtros de búsquedas.
Esto lo hacemos agregando nuevas asociaciones en la definición de datos para relacionar campos a sus catálogos e incluímos además algunas anotaciones para manejo de anexos OData Stream.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '##GENERATED My RAP Travel Managed App'
define root view entity ZR_RAP100_TRAVEL_M
as select from zrap100_travel_m
association [0..1] to /DMO/I_Agency as _Agency on $projection.AgencyID = _Agency.AgencyID
association [0..1] to /DMO/I_Customer as _Customer on $projection.CustomerID = _Customer.CustomerID
association [1..1] to /DMO/I_Overall_Status_VH as _OverallStatus on $projection.OverallStatus =
_OverallStatus.OverallStatus
association [0..1] to I_Currency as _Currency on $projection.CurrencyCode = _Currency.Currency
{
key travel_id as TravelID,
agency_id as AgencyID,
customer_id as CustomerID,
begin_date as BeginDate,
end_date as EndDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
booking_fee as BookingFee,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_price as TotalPrice,
currency_code as CurrencyCode,
description as Description,
overall_status as OverallStatus,
@Semantics.largeObject: { mimeType: 'MimeType', //case-sensitive
fileName: 'FileName', //case-sensitive
acceptableMimeTypes: ['image/png', 'image/jpeg'],
contentDispositionPreference: #ATTACHMENT }
attachment as Attachment,
@Semantics.mimeType: true
mime_type as MimeType,
file_name as FileName,
@Semantics.user.createdBy: true
created_by as CreatedBy,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@Semantics.user.localInstanceLastChangedBy: true
local_last_changed_by as LocalLastChangedBy,
@Semantics.systemDateTime.localInstanceLastChangedAt: true
local_last_changed_at as LocalLastChangedAt,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
//public associations
_Customer,
_Agency,
_OverallStatus,
_Currency
}
Ajustar el CDS projection view del data definition con las anotaciones para el consumo de datos.
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
@EndUserText.label: '##GENERATED Travel App (DEV_100)'
@Search.searchable: true
@ObjectModel.semanticKey: ['TravelID'] //case-sensitive
define root view entity ZC_RAP100_TRAVEL_M
provider contract transactional_query
as projection on ZR_RAP100_TRAVEL_M
{
@Search.defaultSearchElement: true
@Search.fuzzinessThreshold: 0.90
key TravelID,
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['AgencyName'] //case-sensitive
@Consumption.valueHelpDefinition: [{ entity : {name: '/DMO/I_Agency_StdVH', element: 'AgencyID' },
useForValidation: true }]
AgencyID,
_Agency.Name as AgencyName,
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['CustomerName'] //case-sensitive
@Consumption.valueHelpDefinition: [{ entity : {name: '/DMO/I_Customer_StdVH', element: 'CustomerID' },
useForValidation: true }]
CustomerID,
_Customer.LastName as CustomerName,
BeginDate,
EndDate,
BookingFee,
TotalPrice,
@Consumption.valueHelpDefinition: [{ entity: {name: 'I_CurrencyStdVH', element: 'Currency' },
useForValidation: true }]
CurrencyCode,
Description,
@ObjectModel.text.element: ['OverallStatusText'] //case-sensitive
@Consumption.valueHelpDefinition: [{ entity: {name: '/DMO/I_Overall_Status_VH', element: 'OverallStatus' },
useForValidation: true }]
OverallStatus,
_OverallStatus._Text.Text as OverallStatusText : localized,
Attachment,
MimeType,
FileName,
LocalLastChangedAt
}
Y en el metadata agregar las anotaciones correspondientes para ayudas y ajuste de columnas.
@Metadata.layer: #CUSTOMER
@UI: {
headerInfo: {
typeName: 'Travel',
typeNamePlural: 'Travels',
imageUrl: 'Attachment', //case-sensitive
description: { type: #STANDARD, value: 'TravelID' } //case-sensitive
}
}
annotate view ZC_RAP100_TRAVEL_M with
{
@UI.facet: [ {
id: 'idIdentification',
type: #IDENTIFICATION_REFERENCE,
label: 'Travel',
position: 10
} ]
@UI: {
lineItem: [ { position: 10, importance: #HIGH } ],
identification: [ { position: 10 } ],
selectionField: [ { position: 10 } ]
}
TravelID;
@UI: {
lineItem: [ { position: 20, importance: #HIGH } ],
identification: [ { position: 20 } ],
selectionField: [ { position: 20 } ]
}
AgencyID;
@UI: {
lineItem: [ { position: 30, importance: #HIGH } ],
identification: [ { position: 30 } ],
selectionField: [ { position: 30 } ]
}
CustomerID;
@UI: {
lineItem: [ { position: 40, importance: #MEDIUM } ],
identification: [ { position: 40 } ]
}
BeginDate;
@UI: {
lineItem: [ { position: 50, importance: #MEDIUM } ],
identification: [ { position: 50 } ]
}
EndDate;
@UI.identification: [ { position: 60 } ]
BookingFee;
@UI.identification: [ { position: 70 } ]
TotalPrice;
@UI.identification: [ { position: 90 } ]
Description;
@UI: {
lineItem: [ { position: 100, importance: #HIGH } ],
identification: [ { position: 100 } ],
textArrangement: #TEXT_ONLY
}
OverallStatus;
@UI.identification: [ { position: 110 } ]
Attachment;
@UI.hidden: true
MimeType;
@UI.hidden: true
FileName;
@UI.hidden: true
LocalLastChangedAt;
}
6.- Agregar numeración interna.
Agregar al behavior definition un alias Travel, la cláusula early numbering y el ID en readonly.
Posicionarse al inicio de la sentencia create, con un clic en el icono
Implementar el método que nos está indicando con doble clic a Add earlynumbering method for create of entity…
Ajustar la Clase con el método earlynumbering_create a implementar.
CLASS lhc_zr_rap100_travel_m DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS:
get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING
REQUEST requested_authorizations FOR Travel
RESULT result,
earlynumbering_create FOR NUMBERING
IMPORTING entities FOR CREATE Travel.
ENDCLASS.
CLASS lhc_zr_rap100_travel_m IMPLEMENTATION.
METHOD get_global_authorizations.
ENDMETHOD.
METHOD earlynumbering_create.
DATA:
entity TYPE STRUCTURE FOR CREATE zr_rap100_travel_m,
travel_id_max TYPE /dmo/travel_id,
" change to abap_true if you have your own number object...
use_number_range TYPE abap_bool VALUE abap_false.
"Ensure Travel ID is not set yet (idempotent)- must be checked when BO is draft-enabled
LOOP AT entities INTO entity WHERE TravelID IS NOT INITIAL.
APPEND CORRESPONDING #( entity ) TO mapped-travel.
ENDLOOP.
DATA(entities_wo_travelid) = entities.
"Remove the entries with an existing Travel ID
DELETE entities_wo_travelid WHERE TravelID IS NOT INITIAL.
IF use_number_range = abap_true.
"Get numbers
TRY.
cl_numberrange_runtime=>number_get(
EXPORTING
nr_range_nr = '01'
object = '/DMO/TRV_M'
quantity = CONV #( lines( entities_wo_travelid ) )
IMPORTING
number = DATA(number_range_key)
returncode = DATA(number_range_return_code)
returned_quantity = DATA(number_range_returned_quantity)
).
CATCH cx_number_ranges INTO DATA(lx_number_ranges).
LOOP AT entities_wo_travelid INTO entity.
APPEND VALUE #( %cid = entity-%cid
%key = entity-%key
%is_draft = entity-%is_draft
%msg = lx_number_ranges
) TO reported-travel.
APPEND VALUE #( %cid = entity-%cid
%key = entity-%key
%is_draft = entity-%is_draft
) TO failed-travel.
ENDLOOP.
EXIT.
ENDTRY.
"determine the first free travel ID from the number range
travel_id_max = number_range_key - number_range_returned_quantity.
ELSE.
"determine the first free travel ID without number range
"Get max travel ID from active table
SELECT SINGLE FROM zrap100_travel_m FIELDS MAX( travel_id ) AS travelID INTO @travel_id_max.
"Get max travel ID from draft table
SELECT SINGLE FROM zrap100_tra001_d FIELDS MAX( travelid ) INTO @DATA(max_travelid_draft).
IF max_travelid_draft > travel_id_max.
travel_id_max = max_travelid_draft.
ENDIF.
ENDIF.
"Set Travel ID for new instances w/o ID
LOOP AT entities_wo_travelid INTO entity.
travel_id_max += 1.
entity-TravelID = travel_id_max.
APPEND VALUE #( %cid = entity-%cid
%key = entity-%key
%is_draft = entity-%is_draft
) TO mapped-travel.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
7.- Visualizar la Aplicación.
Seleccionar la Entidad Travel en el Service Binding. Presionar Preview.
Autentificarse si es requerido.
Ahora tendremos una aplicación CRUD con búsquedas, ayudas y numeración interna.
Presionamos Crear.
Nos aparecerá la númeración en automático, en modo lectura.
Y los campos Agency Id, Customer Id, Currency y Estatus tendrán ayudas de captura.
Podemos presionar Crear.
O también podemos presionar Discard Draft.
Presionar Discard para confirmar.
Y el Draft quedará descartado, los datos no grabados, por lo tanto no afectará la númeración.
Continua…
Añadir lógica de negocio a la aplicación creada.
Add BO-especific basic business Logic using RAP Cloud Environment