I had to create an E-Mail template where I did not need a CDS view or any variable at all. I simply needed the defined mail template without any rendering of class cl_smtg_email_api. Solution: Reading the plain mail template can be achieved using class cl_smtg_email_template and method get_tmpl_cont:
In my case it was not possible to fetch the data via CDS View, I therefore had to manually fill the variables. Following a simplified version of my code.
You must create a CDS View, as this is the structure in which the variables defined in the mail text are mapped to, even if you do this manually.
define view entity ZMAIL_MY_TEMPLATE as select from pa0001 {
key pa0001.pernr,
pa0001.ename,
cast ( '' as abap.char( 20 ) ) as custom_field // this way you can add a field, that does not exist in the selected table
}
This is just one way, how you can manually fill the variables. Other ways are possible using the class cl_smtg_email_api. I choose the method render_w_data because it returned the mail text, and you manually can do additional changes to the mail text, like adding HTML-Table content, which otherwise gets “escaped” when filled via a replacement variable.
" dynamicaly build table for mail variables
DATA(lt_comp) = VALUE abap_component_tab( ( name = 'ename' type = CAST #( cl_abap_typedescr=>describe_by_name( 'EMNAM' ) ) )
( name = 'pernr' type = CAST #( cl_abap_typedescr=>describe_by_name( 'PERNR_D' ) ) ) ) .
" fill table with concrete data
DATA(lo_struct) = cl_abap_structdescr=>get( lt_comp ).
CREATE DATA mr_data TYPE HANDLE lo_struct.
ASSIGN mr_data->* TO FIELD-SYMBOL(<ls_data>).
<ls_data>-(1) = ename.
<ls_data>-(2) = pernr.
" create instance of eMail API, provide name of Mail template
DATA(lo_email_api) = cl_smtg_email_api=>get_instance( iv_template_id = 'ZMAIL_MY_TEMPLATE' ).
" render E-Mail for CL_BCS class. This replaces all placeholders with real data
lo_email_api->render_w_data( EXPORTING iv_language = sy-langu
ir_data = mr_data
IMPORTING ev_subject = DATA(subject)
ev_body_html = DATA(body_html) ).
TRY.
" create Sender & Receiver
DATA(lo_sender) = cl_cam_address_bcs=>create_internet_address( i_address_string = 'noreply@test.de'
i_address_name = 'Test' ).
DATA(lo_recipient) = cl_cam_address_bcs=>create_internet_address( i_address_string = 'receiver@test.de').
" create mail document
DATA(lo_mail_document) = cl_document_bcs=>create_document( i_type = 'HTM'
i_subject = CONV #( subject )
i_text = cl_bcs_convert=>string_to_soli( body_html ) ).
" create Business Communication Service
DATA(lo_bcs) = cl_bcs=>create_persistent( ).
lo_bcs->set_document( lo_mail_document ).
lo_bcs->set_sender( lo_sender ).
lo_bcs->add_recipient( lo_recipient ).
lo_bcs->send( ).
COMMIT WORK.
CATCH cx_smtg_email_common INTO DATA(ls_cx).
DATA(lv_message) = ls_cx->get_text( ).
MESSAGE s899(id) WITH 'Unable to send message:'(004) lv_message.
ENDTRY.
If you forward a workitem manually from the MyInbox or via function module SAP_WAPI_FORWARD_WORKITEM, the function module SWW_WI_FORWARD will be called. There the SAP implemented a BAdI call of the BAdI WF_WI_FORWARD.
BAdI WF_WI_FORWARD
The BAdI provides a method to add additional checks, when forwarding a workitem. I will use it, to send notification mails.
BAdI Filter
Create your own BAdI implementation and add a task id as filter. In my case it’s the task TS21500003 of the leave request approval workflow. Now your implementation will only be called, if this specific workitem is forwarded. Now we implement the method CHECK_BEFORE_FORWARD. The method has enough parameters to get all necessary information to enrich the mail text.
First the workitem container is read out of the context. There we get the _WI_OBJECT_ID element, which contains the request object reference. With this information we are able to get the current request object out of the workflow. I pass this into antother class where I already have a mail sending implementation.
METHOD if_ex_wf_wi_workitem~check_before_forward.
*---------------------------------------------------------------------------------*
* This BAdI implementation is used to send an info mail when a workitem is forwarded.
*---------------------------------------------------------------------------------*
TRY.
"Get workitem container and requestId
DATA(container) = im_workitem_context->get_wi_container( ).
container->get_value_ref( EXPORTING name = |_WI_OBJECT_ID|
IMPORTING value_ref = DATA(lr_req_id) ).
ASSIGN lr_req_id->* TO FIELD-SYMBOL(<lpor>).
"Get current request object
DATA(lo_req) = NEW cl_pt_req_wf_attribs( )->bi_persistent~find_by_lpor( lpor = <lpor> ).
"Send an info mail to each new agent (should be only one)
LOOP AT im_table_new_agents INTO DATA(new_agent).
zcl_hcm_leave_request_assist=>send_mail( io_req = CAST cl_pt_req_wf_attribs( lo_req )
iv_tdname = mc_mailtext
iv_pernr = cl_hcmfab_employee_api=>get_instance( )->get_employeenumber_from_user( iv_user = new_agent-objid ) ).
ENDLOOP.
CATCH cx_hcmfab_common.
CATCH cx_swf_cnt_elem_not_found.
CATCH cx_swf_cnt_container.
"In error case, do nothing. The workitem should still be forwarded.
RETURN.
ENDTRY.
"Write Info to WF Log
MESSAGE s001(00) WITH |Forward: Mail { mc_mailtext }| INTO DATA(lv_message).
im_workitem_context->set_message_to_log( im_function = CONV #( lv_message ) "max char30
im_message = VALUE #( msgid = sy-msgid
msgty = sy-msgty
msgno = sy-msgno
msgv1 = sy-msgv1 ) ).
COMMIT WORK.
ENDMETHOD.
At the end I’m writing a little notification in the workflow log. The workitem context provides the method set_message_to_log for this. The log will look like this.
Recently I got the demand to enhance the leave request approvel workflow with some mail steps. These mails should also include the attachments, which can be uploaded in the “Leave Request” Fiori App.
To achive this, I used the exit functionality in the mail step.
There you have to provide a class name. The class has to implement the following interface:
IF_SWF_IFS_WORKITEM_EXIT
You will get a method you can implement.
This Method has two parameters, the “Event Name” and the “Workitem Context”.
With the event, you are able to control, in which situations your exit should be fired. In my case, I just need my code run when the workitem is created (event “CREATED”). You’ll find all usable events in domain SWW_EVTTYP.
To get and pass back the attachment to the mail step, a few things need to be done. First, get the curent workitem container and check, if there is already any attachment. If not, read the request id from the leave request. With this request id, you are able to find the attachment you need. I’ve already written a method for that, you’ll find below. Then convert the xstring to a solix table, create the attachment document with function module “SO_DOCUMENT_INSERT_API1” and “SWO_CREATE” and finally pass it to the container.
METHOD if_swf_ifs_workitem_exit~event_raised.
DATA: lv_container TYPE REF TO if_swf_ifs_parameter_container,
lv_attach TYPE TABLE OF obj_record,
lv_folder_id TYPE soodk,
wa_document_info TYPE sofolenti1,
lv_data TYPE sodocchgi1,
lv_objtype TYPE swo_objtyp,
lv_objkey TYPE swo_typeid,
lv_return TYPE swotreturn,
lv_sofm TYPE swo_objhnd,
lv_objject TYPE obj_record,
tb_obj TYPE TABLE OF obj_record,
it_solix_tab1 TYPE solix_tab,
req_id TYPE tim_req_id,
lv_doc_type TYPE so_obj_tp.
CHECK im_event_name = 'CREATED'.
* Fetch Container
lv_container = im_workitem_context->get_wi_container( ).
* Read attachment to confirm that there is no duplication
TRY.
lv_container->get(
EXPORTING
name = '_ATTACH_OBJECTS'
IMPORTING
value = lv_attach ).
CATCH: cx_swf_cnt_elem_not_found,
cx_swf_cnt_elem_type_conflict,
cx_swf_cnt_unit_type_conflict,
cx_swf_cnt_container.
ENDTRY.
CHECK lv_attach IS INITIAL.
CALL FUNCTION 'SO_FOLDER_ROOT_ID_GET'
EXPORTING
owner = sy-uname
region = 'B'
IMPORTING
folder_id = lv_folder_id.
* Get Request ID
lv_container->get(
EXPORTING
name = 'RequestId'
IMPORTING
value = req_id ).
* Get XSTRING of attachment
zcl_xxx=>get_req_attachment(
EXPORTING
iv_req_id = req_id
IMPORTING
ev_xstring = DATA(xstring)
es_attachment = DATA(ls_attachment) ).
CHECK xstring IS NOT INITIAL.
* Get document type
SELECT SINGLE doc_type FROM toadd INTO @lv_doc_type
WHERE mimetype EQ @ls_attachment-file_type.
IF sy-subrc NE 0.
lv_doc_type = 'PDF'. "If doc_type not found, at least try with pdf.
ENDIF.
* Create and set document
it_solix_tab1 = cl_document_bcs=>xstring_to_solix( xstring ).
* Creating First attachment
lv_data-obj_name = ls_attachment-file_name.
lv_data-obj_descr = ls_attachment-file_name.
lv_data-obj_langu = sy-langu.
lv_data-sensitivty = 'P'.
* lv_data-doc_size = ls_attachment-file_size.
CALL FUNCTION 'SO_DOCUMENT_INSERT_API1'
EXPORTING
folder_id = lv_folder_id
document_data = lv_data
document_type = lv_doc_type
IMPORTING
document_info = wa_document_info
TABLES
contents_hex = it_solix_tab1
EXCEPTIONS
folder_not_exist = 1
document_type_not_exist = 2
operation_no_authorization = 3
parameter_error = 4
x_error = 5
enqueue_error = 6
OTHERS = 7.
* Populate object type and object key for create an instance
lv_objtype = 'SOFM'.
lv_objkey = wa_document_info-doc_id.
CALL FUNCTION 'SWO_CREATE'
EXPORTING
objtype = lv_objtype
objkey = lv_objkey
IMPORTING
object = lv_sofm
return = lv_return
EXCEPTIONS
no_remote_objects = 1
OTHERS = 2.
* Prepare for attaching the object to container
lv_objject-header = 'OBJH'.
lv_objject-type = 'SWO'.
lv_objject-handle = lv_sofm.
APPEND lv_objject TO tb_obj.
*—can be used for other workitems
lv_container->set(
EXPORTING
name = '_ATTACH_OBJECTS'
value = tb_obj[] ).
*–this will add the attachment to email
lv_container->set(
EXPORTING
name = 'ATTACHMENTS'
value = tb_obj[] ).
* Commit the changes
im_workitem_context->do_commit_work( ).
ENDMETHOD.
The following function modules are needed to read the leave request attachment: