Add BO-especific basic business Logic using RAP Cloud Environment
Desarrollo de lógica de negocio para una aplicación en ABAP Cloud (Determination, validations y actions).
Objetivo
Adecuar una aplicación CRUD para añadir lógica básica de negocio, usando el modelo RestFul Application programming model en Eclipse utilizando ABAP Development Tools.
Prerequisitos
Ampliar la aplicación ABAP.
- Determinar valores por default (Determinations).
- Hacer validaciones en entrada de datos (Validations).
- Crear botones con acciones específicas (Actions).
- Visualizar la Aplicación (Preview).
Nota: Al ajustar cualquier código no olvidar grabar y activar .
1.- Determinar valores por default (Determinations).
Utilizar determinations para poner el valor de estátus predeterminado en Open.
Agregar la siguiente sentencia en el behavior del data definition.
determination serStatusToOpen on modify {create;}
Presionamos el botón del foco que aparece a un lado de la númeración del código y presionamos doble clic al texto Add Method for determination setstatusoopen of entity…
Implementar el código del método en la clase (behavior pool) del proyecto.
CLASS lhc_zr_rap100_travel_m DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
CONSTANTS:
BEGIN OF travel_status,
open TYPE c LENGTH 1 VALUE 'O', "Open
accepted TYPE c LENGTH 1 VALUE 'A', "Accepted
rejected TYPE c LENGTH 1 VALUE 'X', "Rejected
END OF travel_status.
METHODS:
get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING
REQUEST requested_authorizations FOR Travel
RESULT result,
earlynumbering_create FOR NUMBERING
IMPORTING entities FOR CREATE Travel,
setStatusToOpen FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setStatusToOpen.
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_false if you get the ABAP Runtime error 'BEHAVIOR_ILLEGAL_STATEMENT'
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.
METHOD setStatusToOpen.
"Read travel instances of the transferred keys
READ ENTITIES OF ZR_RAP100_TRAVEL_M IN LOCAL MODE
ENTITY Travel
FIELDS ( OverallStatus )
WITH CORRESPONDING #( keys )
RESULT DATA(travels)
FAILED DATA(read_failed).
"If overall travel status is already set, do nothing, i.e. remove such instances
DELETE travels WHERE OverallStatus IS NOT INITIAL.
CHECK travels IS NOT INITIAL.
"else set overall travel status to open ('O')
MODIFY ENTITIES OF ZR_RAP100_TRAVEL_M IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( OverallStatus )
WITH VALUE #( FOR travel IN travels ( %tky = travel-%tky
OverallStatus = travel_status-open ) )
REPORTED DATA(update_reported).
"Set the changing parameter
reported = CORRESPONDING #( DEEP update_reported ).
ENDMETHOD.
ENDCLASS.
2.- Hacer validaciones en entrada de datos (Validations).
Utilizar validations para verificar los campos de cliente y la validez de las fechas capturadas.
Agregar las siguientes sentencias en el behavior del data definition.
Implementar el código del método validateCustomer en la clase del proyecto.
**********************************************************************
* Validation: Check the validity of the entered customer data
**********************************************************************
METHOD validateCustomer.
"read relevant travel instance data
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
FIELDS ( CustomerID )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
DATA customers TYPE SORTED TABLE OF /dmo/customer WITH UNIQUE KEY customer_id.
"optimization of DB select: extract distinct non-initial customer IDs
customers = CORRESPONDING #( travels DISCARDING DUPLICATES MAPPING customer_id = customerID EXCEPT * ).
DELETE customers WHERE customer_id IS INITIAL.
IF customers IS NOT INITIAL.
"check if customer ID exists
SELECT FROM /dmo/customer FIELDS customer_id
FOR ALL ENTRIES IN @customers
WHERE customer_id = @customers-customer_id
INTO TABLE @DATA(valid_customers).
ENDIF.
"raise msg for non existing and initial customer id
LOOP AT travels INTO DATA(travel).
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_CUSTOMER'
) TO reported-travel.
IF travel-CustomerID IS INITIAL.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_CUSTOMER'
%msg = NEW /dmo/cm_flight_messages(
textid = /dmo/cm_flight_messages=>enter_customer_id
severity = if_abap_behv_message=>severity-error )
%element-CustomerID = if_abap_behv=>mk-on
) TO reported-travel.
ELSEIF travel-CustomerID IS NOT INITIAL AND NOT line_exists( valid_customers[ customer_id = travel-CustomerID ] ).
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_CUSTOMER'
%msg = NEW /dmo/cm_flight_messages(
customer_id = travel-customerid
textid = /dmo/cm_flight_messages=>customer_unkown
severity = if_abap_behv_message=>severity-error )
%element-CustomerID = if_abap_behv=>mk-on
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
Implementar el código del método validateDates en la clase del proyecto.
**********************************************************************
* Validation: Check the validity of begin and end dates
**********************************************************************
METHOD validateDates.
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate EndDate TravelID )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels INTO DATA(travel).
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_DATES' ) TO reported-travel.
IF travel-BeginDate IS INITIAL.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_DATES'
%msg = NEW /dmo/cm_flight_messages(
textid = /dmo/cm_flight_messages=>enter_begin_date
severity = if_abap_behv_message=>severity-error )
%element-BeginDate = if_abap_behv=>mk-on ) TO reported-travel.
ENDIF.
IF travel-BeginDate < cl_abap_context_info=>get_system_date( ) AND travel-BeginDate IS NOT INITIAL.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_DATES'
%msg = NEW /dmo/cm_flight_messages(
begin_date = travel-BeginDate
textid = /dmo/cm_flight_messages=>begin_date_on_or_bef_sysdate
severity = if_abap_behv_message=>severity-error )
%element-BeginDate = if_abap_behv=>mk-on ) TO reported-travel.
ENDIF.
IF travel-EndDate IS INITIAL.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_DATES'
%msg = NEW /dmo/cm_flight_messages(
textid = /dmo/cm_flight_messages=>enter_end_date
severity = if_abap_behv_message=>severity-error )
%element-EndDate = if_abap_behv=>mk-on ) TO reported-travel.
ENDIF.
IF travel-EndDate < travel-BeginDate AND travel-BeginDate IS NOT INITIAL
AND travel-EndDate IS NOT INITIAL.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
APPEND VALUE #( %tky = travel-%tky
%state_area = 'VALIDATE_DATES'
%msg = NEW /dmo/cm_flight_messages(
textid = /dmo/cm_flight_messages=>begin_date_bef_end_date
begin_date = travel-BeginDate
end_date = travel-EndDate
severity = if_abap_behv_message=>severity-error )
%element-BeginDate = if_abap_behv=>mk-on
%element-EndDate = if_abap_behv=>mk-on ) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
3.- Crear botones con acciones específicas (Actions).
Utilizar Actions para agregar dos botones con acciones específicas.
Un (non-factory) Action, para agregar botón que solicite un % de descuento al booking fee.
Un (factory) Action, para agregar un botón ejecutando un copiar como…
Agregar las siguientes sentencias en el behavior del data definition.
Nota: Se estará usando aquí un parámetro ya existente en el repositorio /dmo/a_travel_discount.
Implementar el código del método deductDiscount en la clase del proyecto.
**************************************************************************
* Instance-bound non-factory action:
* Deduct the specified discount from the booking fee (BookingFee)
**************************************************************************
METHOD deductDiscount.
DATA travels_for_update TYPE TABLE FOR UPDATE zr_rap100_travel_m.
DATA(keys_with_valid_discount) = keys.
" read relevant travel instance data (only booking fee)
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
FIELDS ( BookingFee )
WITH CORRESPONDING #( keys_with_valid_discount )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
DATA percentage TYPE decfloat16.
DATA(discount_percent) = keys_with_valid_discount[ KEY draft %tky = <travel>-%tky ]-%param-discount_percent.
percentage = discount_percent / 100 .
DATA(reduced_fee) = <travel>-BookingFee * ( 1 - 3 / 10 ) .
APPEND VALUE #( %tky = <travel>-%tky
BookingFee = reduced_fee
) TO travels_for_update.
ENDLOOP.
" update data with reduced fee
MODIFY ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( BookingFee )
WITH travels_for_update.
" read changed data for action result
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
ALL FIELDS WITH
CORRESPONDING #( travels )
RESULT DATA(travels_with_discount).
" set action result
result = VALUE #( FOR travel IN travels_with_discount ( %tky = travel-%tky
%param = travel ) ).
ENDMETHOD.
Implementar el código del método copyTravel también en la clase del proyecto.
**************************************************************************
* Instance-bound factory action `copyTravel`:
* Copy an existing travel instance
**************************************************************************
METHOD copyTravel.
DATA:
travels TYPE TABLE FOR CREATE zr_rap100_travel_m\\travel.
" remove travel instances with initial %cid (i.e., not set by caller API)
READ TABLE keys WITH KEY %cid = '' INTO DATA(key_with_inital_cid).
ASSERT key_with_inital_cid IS INITIAL.
" read the data from the travel instances to be copied
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY travel
ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(travel_read_result)
FAILED failed.
LOOP AT travel_read_result ASSIGNING FIELD-SYMBOL(<travel>).
" fill in travel container for creating new travel instance
APPEND VALUE #( %cid = keys[ KEY entity %key = <travel>-%key ]-%cid
%is_draft = keys[ KEY entity %key = <travel>-%key ]-%param-%is_draft
%data = CORRESPONDING #( <travel> EXCEPT TravelID )
)
TO travels ASSIGNING FIELD-SYMBOL(<new_travel>).
" adjust the copied travel instance data
"" BeginDate must be on or after system date
<new_travel>-BeginDate = cl_abap_context_info=>get_system_date( ).
"" EndDate must be after BeginDate
<new_travel>-EndDate = cl_abap_context_info=>get_system_date( ) + 30.
"" OverallStatus of new instances must be set to open ('O')
<new_travel>-OverallStatus = travel_status-open.
ENDLOOP.
" create new BO instance
MODIFY ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY travel
CREATE FIELDS ( AgencyID CustomerID BeginDate EndDate BookingFee
TotalPrice CurrencyCode OverallStatus Description )
WITH travels
MAPPED DATA(mapped_create).
" set the new BO instances
mapped-travel = mapped_create-travel .
ENDMETHOD.
Agregar las siguientes sentencias al behavior del projection.
Opcionalmente agregaré dos botones más para aceptar o rechazar el travel.
Implementar el código del método acceptTravel en la clase del proyecto.
*************************************************************************************
* Instance-bound non-factory action: Set the overall travel status to 'A' (accepted)
*************************************************************************************
METHOD acceptTravel.
" modify travel instance
MODIFY ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( OverallStatus )
WITH VALUE #( FOR key IN keys ( %tky = key-%tky
OverallStatus = travel_status-accepted ) ) " 'A'
FAILED failed
REPORTED reported.
" read changed data for action result
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
ALL FIELDS WITH
CORRESPONDING #( keys )
RESULT DATA(travels).
" set the action result parameter
result = VALUE #( FOR travel IN travels ( %tky = travel-%tky
%param = travel ) ).
ENDMETHOD.
Implementar el código del método rejectTravel en la clase del proyecto.
*************************************************************************************
* Instance-bound non-factory action: Set the overall travel status to 'X' (rejected)
*************************************************************************************
METHOD rejectTravel.
" modify travel instance(s)
MODIFY ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( OverallStatus )
WITH VALUE #( FOR key IN keys ( %tky = key-%tky
OverallStatus = travel_status-rejected ) ) " 'X'
FAILED failed
REPORTED reported.
" read changed data for action result
READ ENTITIES OF zr_rap100_travel_m IN LOCAL MODE
ENTITY Travel
ALL FIELDS WITH
CORRESPONDING #( keys )
RESULT DATA(travels).
" set the action result parameter
result = VALUE #( FOR travel IN travels ( %tky = travel-%tky
%param = travel ) ).
ENDMETHOD.
Agregar las siguientes sentencias al behavior del projection.
Finalmente agregar al Metadata Extension el siguiente código.
@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 }
,{ type: #FOR_ACTION, dataAction: 'copyTravel', label: 'Copy Travel' }
,{ type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' }
,{ type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' }
]
@UI.identification: [
{ position: 10 , importance: #HIGH }
,{ type: #FOR_ACTION, dataAction: 'deductDiscount', label: 'Deduct Discount' }
,{ type: #FOR_ACTION, dataAction: 'acceptTravel', label: 'Accept Travel' }
,{ type: #FOR_ACTION, dataAction: 'rejectTravel', label: 'Reject Travel' }
]
@UI.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
} ]
@UI.identification: [ {
position: 100
} ]
@UI.textArrangement: #TEXT_ONLY
OverallStatus;
@UI.identification: [ { position: 110 } ]
Attachment;
@UI.hidden: true
MimeType;
@UI.hidden: true
FileName;
@UI.hidden: true
LocalLastChangedAt;
}
4.- Visualizar la Aplicación (Preview).
Seleccionar la Entidad Travel en el Service Binding. Presionar Preview.
Autentificarse si es requerido.
Visualizamos la aplicación con los ajustes realizados a la lista de Travels (aceptar, rechazar, copiar…).
Visualizamos la aplicación con los ajustes realizados en el detalle Travel (como valor por default en estatus, validaciones a cliente, fechas y acción de solicitar descuento).
Pantalla de parámetro para el cálculo de descuento.
Continua…
Añadir entidad de detalle a la aplicación creada.
Add child Entity to Data Model App using RAP Cloud Environment